diff --git a/docs/conditional.rst b/docs/conditional.rst
index 12521a7..bcac16c 100644
--- a/docs/conditional.rst
+++ b/docs/conditional.rst
@@ -1,3 +1,5 @@
+.. _conditional_operations:
+
 Conditional Operations
 ======================
 
diff --git a/docs/local.rst b/docs/local.rst
index 2349727..982d09e 100644
--- a/docs/local.rst
+++ b/docs/local.rst
@@ -15,9 +15,9 @@ that your server is listening on.
 .. note::
 
     If you are using DynamoDB Local and also use ``rate_limited_scan`` on your models, you must also
-    set ``allow_rate_limited_scan_without_consumed_capacity`` to ``True`` in the
-    `settings file <settings.rst#allow_rate_limited_scan_without_consumed_capacity>`_ (dynalite does not require
-    this step because it implements returning of consumed capacity in responses, which is used by ``rate_limited_scan``).
+    set ``allow_rate_limited_scan_without_consumed_capacity`` to ``True`` in :ref:`settings`
+    (dynalite does not require this step because it implements returning of consumed capacity in
+    responses, which is used by ``rate_limited_scan``).
 
 .. note::
 
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 3d46d71..1405c46 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -32,7 +32,7 @@ PynamoDB allows you to create the table:
 
     >>> UserModel.create_table(read_capacity_units=1, write_capacity_units=1)
 
-Now you can create a user:
+Now you can create a user in local memory:
 
     >>> user = UserModel('test@example.com', first_name='Samuel', last_name='Adams')
     dynamodb-user<test@example.com>
@@ -62,6 +62,49 @@ Ready to delete the user?
 
     >>> user.delete()
 
+Changing items
+^^^^^^^^^^^^^^
+
+Changing existing items in the database can be done using either
+`update()` or `save()`. There are important differences between the
+two.
+
+Use of `save()` looks like this::
+
+    user = UserModel.get('test@example.com')
+    user.first_name = 'Robert'
+    user.save()
+
+Use of `update()` (in its simplest form) looks like this::
+
+    user = UserModel.get('test@example.com')
+    user.update(
+      actions=[
+        UserModel.first_name.set('Robert')
+      ]
+    )
+
+`save()` will entirely replace an object (it internally uses `PutItem
+<http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html>`_). As
+a consequence, even if you modify only one attribute prior to calling
+`save()`, the entire object is re-written. Any modifications done to
+the same user by other processes will be lost, even if made to other
+attributues that you did not change. To avoid this, use `update()` to
+perform more fine grained updates or see the
+:ref:`conditional_operations` for how to avoid race conditions
+entirely.
+
+Additionally, PynamoDB ignores attributes it does not know about when
+reading an object from the database. As a result, if the item in
+DynamoDB contains attributes not declared in your model, `save()` will
+cause those attributes to be deleted.
+
+In particular, performing a rolling upgrade of your application after
+having added an attribute is an example of such a situation. To avoid
+data loss, either avoid using `save()` or perform a multi-step update
+with the first step is to upgrade to a version that merely declares
+the attribute on the model without ever setting it to any value.
+
 Querying
 ^^^^^^^^
 
diff --git a/docs/settings.rst b/docs/settings.rst
index f61e00a..3b9288a 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -1,3 +1,5 @@
+.. _settings:
+
 Settings
 ========
 
@@ -59,7 +61,9 @@ Default: ``False``
 
 If ``True``, ``rate_limited_scan()`` will proceed silently (without
 rate limiting) if the DynamoDB server does not return consumed
-capacity information in responses.
+capacity information in responses. If ``False``, scans will fail
+should the server not return consumed capacity information in an
+effort to prevent unintentional capacity usage..
 
 Overriding settings
 ~~~~~~~~~~~~~~~~~~~
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index dd03776..6b2db70 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -102,7 +102,7 @@ Here is an example that specifies both the ``host`` and the ``region`` to use:
             table_name = 'Thread'
             # Specifies the region
             region = 'us-west-1'
-            # Specifies the hostname
+            # Optional: Specify the hostname only if it needs to be changed from the default AWS setting
             host = 'http://localhost'
             # Specifies the write capacity
             write_capacity_units = 10
diff --git a/pynamodb/connection/base.py b/pynamodb/connection/base.py
index ae163f5..ac92ae0 100644
--- a/pynamodb/connection/base.py
+++ b/pynamodb/connection/base.py
@@ -420,8 +420,8 @@ class Connection(object):
             for attr in six.itervalues(data[LAST_EVALUATED_KEY]):
                 _convert_binary(attr)
         if UNPROCESSED_KEYS in data:
-            for item_list in six.itervalues(data[UNPROCESSED_KEYS]):
-                for item in item_list:
+            for table_data in six.itervalues(data[UNPROCESSED_KEYS]):
+                for item in table_data[KEYS]:
                     for attr in six.itervalues(item):
                         _convert_binary(attr)
         if UNPROCESSED_ITEMS in data:
diff --git a/pynamodb/models.py b/pynamodb/models.py
index 92dda13..af4b42b 100644
--- a/pynamodb/models.py
+++ b/pynamodb/models.py
@@ -61,9 +61,14 @@ class BatchWrite(ModelContextManager):
     """
     def save(self, put_item):
         """
-        This adds `put_item` to the list of pending writes to be performed.
-        Additionally, the a BatchWriteItem will be performed if the length of items
-        reaches 25.
+        This adds `put_item` to the list of pending operations to be performed.
+
+        If the list currently contains 25 items, which is the DynamoDB imposed
+        limit on a BatchWriteItem call, one of two things will happen. If auto_commit
+        is True, a BatchWriteItem operation will be sent with the already pending
+        writes after which put_item is appended to the (now empty) list. If auto_commit
+        is False, ValueError is raised to indicate additional items cannot be accepted
+        due to the DynamoDB imposed limit.
 
         :param put_item: Should be an instance of a `Model` to be written
         """
@@ -76,8 +81,14 @@ class BatchWrite(ModelContextManager):
 
     def delete(self, del_item):
         """
-        This adds `del_item` to the list of pending deletes to be performed.
-        If the list of items reaches 25, a BatchWriteItem will be called.
+        This adds `del_item` to the list of pending operations to be performed.
+
+        If the list currently contains 25 items, which is the DynamoDB imposed
+        limit on a BatchWriteItem call, one of two things will happen. If auto_commit
+        is True, a BatchWriteItem operation will be sent with the already pending
+        operations after which put_item is appended to the (now empty) list. If auto_commit
+        is False, ValueError is raised to indicate additional items cannot be accepted
+        due to the DynamoDB imposed limit.
 
         :param del_item: Should be an instance of a `Model` to be deleted
         """
@@ -294,9 +305,13 @@ class Model(AttributeContainer):
     @classmethod
     def batch_write(cls, auto_commit=True):
         """
-        Returns a context manager for a batch operation'
+        Returns a BatchWrite context manager for a batch operation.
 
-        :param auto_commit: Commits writes automatically if `True`
+        :param auto_commit: If true, the context manager will commit writes incrementally
+                            as items are written to as necessary to honor item count limits
+                            in the DynamoDB API (see BatchWrite). Regardless of the value
+                            passed here, changes automatically commit on context exit
+                            (whether successful or not).
         """
         return BatchWrite(cls, auto_commit=auto_commit)
 
@@ -455,7 +470,8 @@ class Model(AttributeContainer):
     def get(cls,
             hash_key,
             range_key=None,
-            consistent_read=False):
+            consistent_read=False,
+            attributes_to_get=None):
         """
         Returns a single object using the provided keys
 
@@ -466,7 +482,8 @@ class Model(AttributeContainer):
         data = cls._get_connection().get_item(
             hash_key,
             range_key=range_key,
-            consistent_read=consistent_read
+            consistent_read=consistent_read,
+            attributes_to_get=attributes_to_get
         )
         if data:
             item_data = data.get(ITEM)

Error while _handle_binary_attributes when UNPROCESSED_KEYS in batch_get request
Hi,
I encounter some problems whenever I exceed the provisionned throughput (if it does not exceed everything works well). Instead of retrying to request unprocessed items, it raises an error while trying to handle binary attributes.

This occurs when trying to process batch_get requests.

```
/Users/leonardbinet/anaconda/envs/api_transilien/lib/python3.6/site-packages/pynamodb/connection/base.py in dispatch(self, operation_name, operation_kwargs)
    262         self._log_debug(operation_name, operation_kwargs)
    263
--> 264         data = self._make_api_call(operation_name, operation_kwargs)
    265
    266         if data and CONSUMED_CAPACITY in data:

/Users/leonardbinet/anaconda/envs/api_transilien/lib/python3.6/site-packages/pynamod/connection/base.py in _make_api_call(self, operation_name, operation_kwargs)
    355                         continue
    356
--> 357             return self._handle_binary_attributes(data)
    358
    359     @staticmethod

/Users/leonardbinet/anaconda/envs/api_transilien/lib/python3.6/site-packages/pynamodb/connection/base.py in _handle_binary_attributes(data)
    378             for item_list in six.itervalues(data[UNPROCESSED_KEYS]):
    379                 for item in item_list:
--> 380                     for attr in six.itervalues(item):
    381                         _convert_binary(attr)
    382         if UNPROCESSED_ITEMS in data:

/Users/leonardbinet/anaconda/envs/api_transilien/lib/python3.6/site-packages/six.py in itervalues(d, **kw)
    576
    577     def itervalues(d, **kw):
--> 578         return iter(d.values(**kw))
    579
    580     def iteritems(d, **kw):

AttributeError: 'str' object has no attribute 'values'
```

Here is more detail about objects when investigating with ipython:
```
ipdb> d
'Keys'

ipdb> item
'Keys'

ipdb> item_list.keys()
dict_keys(['Keys'])

ipdb> item_list
{'Keys': [{'station_id': {'S': '8739322'}, 'day_train_num': {'S': '20170401_496702'}}, {'station_id': {'S':'8738149'}, 'day_train_num': {'S': '20170401_497601'}}, 
...
...
 {'station_id': {'S': '8727608'}, 'day_train_num': {'S': '20170401_496538'}}, {'station_id': {'S': '8738237'}, 'day_train_num': {'S': '20170401_496704'}}
]}

ipdb> data.keys()
dict_keys(['ConsumedCapacity', 'Responses', 'UnprocessedKeys'])

ipdb> data["UnprocessedKeys"]
{'real_departures_2': {'Keys': [{'station_id': {'S': '8739322'}, 'day_train_num': {'S': '20170401_496702'}}, {'station_id': {'S': '8738149'}, 'day_train_num': {'S': '20170401_497601'}}, {'station_id': {'S': '8711386'}, 'day_train_num': {'S': '20170401_499001'}}, {'station_id': {'S': '8738237'}, 'day_train_num': {'S': '20170401_496703'}},
.... 
```

Obviously the problem is that item_list variable is not a list, but a dictionary containing the item_list as value under key "Keys": 
`{"Keys": item_list}`

And that initially: 
`data["UnprocessedKeys"]`is structured as follow:
`{"table_name":{"Keys": item_list}}`
instead of `{"Keys": item_list}`

I think that we only need to add a line in code (line 377 in pynamodb/connection/base.py): 
Instead of:
```
if UNPROCESSED_KEYS in data:
            for item_list in six.itervalues(data[UNPROCESSED_KEYS]):
                for item in item_list:
                    for attr in six.itervalues(item):
                        _convert_binary(attr)
``` 
insert another loop:

```
if UNPROCESSED_KEYS in data:
            for table_unprocessed_keys in six.itervalues(data[UNPROCESSED_KEYS]):
                for item_list in six.itervalues(table_unprocessed_keys):
                    for item in item_list:
                        for attr in six.itervalues(item):
                            _convert_binary(attr)
``` 
diff --git a/pynamodb/tests/test_base_connection.py b/pynamodb/tests/test_base_connection.py
index 5d36fad..4095d6c 100644
--- a/pynamodb/tests/test_base_connection.py
+++ b/pynamodb/tests/test_base_connection.py
@@ -2586,6 +2586,44 @@ class ConnectionTestCase(TestCase):
             _assert=True
         )
 
+    def test_handle_binary_attributes_for_unprocessed_keys(self):
+        binary_blob = six.b('\x00\xFF\x00\xFF')
+        unprocessed_keys = {
+            'UnprocessedKeys': {
+                'MyTable': {
+                    'AttributesToGet': ['ForumName'],
+                    'Keys': [
+                        {
+                            'ForumName': {'S': 'FooForum'},
+                            'Subject': {'B': base64.b64encode(binary_blob).decode(DEFAULT_ENCODING)}
+                        },
+                        {
+                            'ForumName': {'S': 'FooForum'},
+                            'Subject': {'S': 'thread-1'}
+                        }
+                    ],
+                    'ConsistentRead': False
+                },
+                'MyOtherTable': {
+                    'AttributesToGet': ['ForumName'],
+                    'Keys': [
+                        {
+                            'ForumName': {'S': 'FooForum'},
+                            'Subject': {'B': base64.b64encode(binary_blob).decode(DEFAULT_ENCODING)}
+                        },
+                        {
+                            'ForumName': {'S': 'FooForum'},
+                            'Subject': {'S': 'thread-1'}
+                        }
+                    ],
+                    'ConsistentRead': False
+                }
+            }
+        }
+        data = Connection._handle_binary_attributes(unprocessed_keys)
+        self.assertEqual(data['UnprocessedKeys']['MyTable']['Keys'][0]['Subject']['B'], binary_blob)
+        self.assertEqual(data['UnprocessedKeys']['MyOtherTable']['Keys'][0]['Subject']['B'], binary_blob)
+
     def test_get_expected_map(self):
         conn = Connection(self.region)
         with patch(PATCH_METHOD) as req:

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
botocore==1.2.0
certifi==2021.5.30
coverage==6.2
docutils==0.18.1
importlib-metadata==4.8.3
iniconfig==1.1.1
jmespath==0.7.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
-e git+https://github.com/pynamodb/PynamoDB.git@3adf6063bf7d627638e52733d23be7a8867acc6b#egg=pynamodb
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
pytest-mock==3.6.1
python-dateutil==2.9.0.post0
six==1.9.0
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: PynamoDB
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - botocore==1.2.0
      - coverage==6.2
      - docutils==0.18.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jmespath==0.7.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - python-dateutil==2.9.0.post0
      - six==1.9.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/PynamoDB

pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_handle_binary_attributes_for_unprocessed_keys
pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_create_connection
pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_batch_get_item pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_batch_write_item pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_create_prepared_request pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_create_table pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_delete_item pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_delete_table pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_describe_table pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_get_expected_map pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_get_item pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_get_query_filter_map pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_handle_binary_attributes_for_unprocessed_items pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_list_tables pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_make_api_call_retries_properly pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_make_api_call_throws_retry_disabled pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_make_api_call_throws_verbose_error_after_backoff pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_make_api_call_throws_verbose_error_after_backoff_later_succeeds pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_make_api_call_throws_when_retries_exhausted pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_put_item pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_query pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_rate_limited_scan pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_rate_limited_scan_retries_max_sleep pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_rate_limited_scan_retries_min_sleep pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_rate_limited_scan_retries_on_rate_unavailable pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_rate_limited_scan_retries_on_rate_unavailable_within_s pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_rate_limited_scan_retries_timeout pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_ratelimited_scan_exception_on_max_threshold pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_ratelimited_scan_raises_non_client_error pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_ratelimited_scan_raises_other_client_errors pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_ratelimited_scan_retries_on_throttling pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_ratelimited_scan_with_pagination_ends pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_scan pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_subsequent_client_is_cached_when_credentials_truthy pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_subsequent_client_is_not_cached_when_credentials_none pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_update_item pynamodb/tests/test_base_connection.py::ConnectionTestCase::test_update_table
docs/quickstart.rst docs/settings.rst pynamodb/models.py docs/local.rst docs/tutorial.rst docs/conditional.rst pynamodb/connection/base.py
docs/quickstart.rst docs/settings.rst pynamodb/models.py docs/local.rst docs/tutorial.rst docs/conditional.rst pynamodb/connection/base.py