Commits

Jean-Tiare Le Bigot committed 712ceb1

add Item documentation

  • Participants
  • Parent commits 64304f4

Comments (0)

Files changed (3)

ddbmock/database/item.py

 
 
 class Item(dict):
+    """
+    Internal Item representation. The Item is stored in its raw DynamoDB request
+    form and no parsing is involved unless specifically needed.
+
+    It adds a couple of handful helpers to the dict class such as DynamoDB actions,
+    condition validations and specific size computation.
+    """
     def __init__(self, dico={}):
+        """
+        Load a raw DynamoDb Item and enhance it with ou helpers. Also set the
+        cached :py:class:`ItemSize` to ``None`` to mark it as not computed. This
+        avoids unnecessary computations on temporary Items.
+
+        :param dico: Raw DynamoDB request Item
+        """
         self.update(dico)
         self.size = None
 
         ``fields`` evaluates to False (None, empty, ...), the original dict is
         returned untouched.
 
-        :ivar fields: array of name of keys to keep
-        :return: filtered ``item``
+        Internal :py:class:`ItemSize` of the filtered Item is set to original
+        Item size as you pay for the data you operated on, not for what was
+        actually sent over the wires.
+
+        :param fields: array of name of keys to keep
+
+        :return: filtered ``Item``
         """
         if fields:
             filtered = Item((k, v) for k, v in self.items() if k in fields)
         return self
 
     def _apply_action(self, fieldname, action):
+        """
+        Internal function. Applies a single action to a single field.
+
+        :param fieldname: Valid field name
+        :param action: Raw DynamoDB request action specification
+
+        :raises: :py:exc:`ddbmock.errors.ValidationException` whenever attempting an illegual action
+        """
         # Rewrite this function, it's disgustting code
         if action[u'Action'] == u"PUT":
             self[fieldname] = action[u'Value']
                 self[fieldname] = action[u'Value']
 
     def apply_actions(self, actions):
+        """
+        Apply ``actions`` to the current item. Mostly used by ``UpdateItem``.
+        This also resets the cached item size.
+
+        .. warning:: There is a corner case in ``ADD`` action. It will always behave
+            as though the item already existed before that is to say, it the target
+            field is a non existing set, it will always start a new one with this
+            single value in it. In real DynamoDB, if Item was new, it should fail.
+
+        :param action: Raw DynamoDB request actions specification
+
+        :raises: :py:exc:`ddbmock.errors.ValidationException` whenever attempting an illegual action
+        """
         map(self._apply_action, actions.keys(), actions.values())
         self.size = None  # reset cache
 
     def assert_match_expected(self, expected):
         """
-        Raise ConditionalCheckFailedException if ``self`` does not match ``expected``
-        values. ``expected`` schema is raw conditions as defined by DynamoDb.
+        Make sure this Items matches the ``expected`` values. This may be used
+        by any signe item write operation such as ``DeleteItem``, ``UpdateItem``
+        and ``PutItem``.
 
-        :ivar expected: conditions to validate
-        :raises: ConditionalCheckFailedException
+        :param expected: Raw DynamoDB request expected values
+
+        :raises: :py:exc:`ddbmock.errors.ConditionalCheckFailedException` if any of the expected values is not valid
         """
         for fieldname, condition in expected.iteritems():
             if u'Exists' in condition and not condition[u'Exists']:
                     fieldname, condition[u'Value'], self[fieldname]))
 
     def match(self, conditions):
+        """
+        Check if the current item matches conditions. Return False if a field is not
+        found, or does not match. If condition is None, it is considered to match.
+
+        Condition name are assumed to be valid as Onctuous is in charge of input
+        validation. Expect crashes otherwise :)
+
+        :param fieldname: Valid field name
+        :param condition: Raw DynamoDB request condition of the form ``{"OPERATOR": FIELDDEFINITION}``
+
+        :return: ``True`` on success or ``False`` on first failure
+        """
         for name, condition in conditions.iteritems():
             if not self.field_match(name, condition):
                 return False
 
         return True
 
-    def field_match(self, name, condition):
-        """Check if a field matches a condition. Return False when field not
+    def field_match(self, fieldname, condition):
+        """
+        Check if a field matches a condition. Return False when field not
         found, or do not match. If condition is None, it is considered to match.
 
-        :ivar name: name of the field to test
-        :ivar condition: raw dict describing the condition {"OPERATOR": FIELDDEFINITION}
-        :return: True on success
+        Condition name are assumed to be valid as Onctuous is in charge of input
+        validation. Expect crashes otherwise :)
+
+        :param fieldname: Valid field name
+        :param condition: Raw DynamoDB request condition of the form ``{"OPERATOR": FIELDDEFINITION}``
+
+        :return: ``True`` on success
         """
         # Arcording to specif, no condition means match
         if condition is None:
             return True
 
         # read the item
-        if name not in self:
+        if fieldname not in self:
             value = None
         else:
-            value = self[name]
+            value = self[fieldname]
 
-        # Load the test operator from the comparison module. Thamks to input
+        # Load the test operator from the comparison module. Thanks to input
         # validation, no try/except required
         condition_operator = condition[u'ComparisonOperator'].lower()
         operator = getattr(comparison, condition_operator)
         return operator(value, *condition[u'AttributeValueList'])
 
     def read_key(self, key, name=None, max_size=0):
-        """Provided ``key``, read field value at ``name`` or ``key.name`` if not
-        specified. If the field does not exist, this is a "ValueError". In case
-        it exists, also check the type compatibility. If it does not match, raise
-        TypeError.
+        """
+        Provided ``key``, read field value at ``name`` or ``key.name`` if not
+        specified.
 
-        :ivar key: ``Key`` or ``PrimaryKey`` to read
-        :ivar name: override name field of key
-        :ivar max_size: if specified, check that the item is bellow a treshold
-        :return: field value
+        :param key: ``Key`` or ``PrimaryKey`` to read
+        :param name: override name field of key
+        :param max_size: if specified, check that the item is bellow a treshold
+
+        :return: field value at ``key``
+
+        :raises: :py:exc:`ddbmock.errors.ValidationException` if field does not exist, type does not match or is above ``max_size``
         """
         if key is None:
             return False
         return key.read(field)
 
     def _internal_item_size(self, base_type, value):
+        """
+        Internal DynamoDB field size computation. ``base_type`` is assumed to
+        be valid as it went through Onctous before and this helper is only
+        supposed to be called internally.
+
+        :param base_type: valid base type. Must be in ``['N', 'S', 'B']``.
+        :param value: compute the size of this value.
+        """
         if base_type == 'N': return 8 # assumes "double" internal type on ddb side
         if base_type == 'S': return len(value.encode('utf-8'))
-        if base_type == 'B': return len(value.encode('utf-8'))*3/4 # base64 overead
+        if base_type == 'B': return len(value.encode('utf-8'))*3/4 # base64 overhead
 
-    def get_field_size(self, key):
-        """Return value size in bytes or 0 if not found"""
-        if not key in self:
+    def get_field_size(self, fieldname):
+        """
+        Compute field size in bytes.
+
+        :param fieldname: Valid field name
+
+        :return: Size of the field in bytes or 0 if the field was not found. Remember that empty fields are represented as missing values in DynamoDB.
+        """
+        if not fieldname in self:
             return 0
 
-        typename, value = _decode_field(self[key])
+        typename, value = _decode_field(self[fieldname])
         base_type = typename[0]
 
         if len(typename) == 1:
         return value_size
 
     def get_size(self):
-        """Compute Item size as DynamoDB would. This is especially useful for
+        """
+        Compute Item size as DynamoDB would. This is especially useful for
         enforcing the 64kb per item limit as well as the capacityUnit cost.
 
-        note: the result is cached for efficiency. If you ever happend to directly
-        edit values for any reason, do not forget to invalidate it: ``self.size=None``
+        .. note:: the result is cached for efficiency. If you ever happend to
+            directly edit values for any reason, do not forget to invalidate the
+            cache: ``self.size=None``
 
-        :return: the computed size
+        :return: :py:class:`ItemSize` DynamoDB item size in bytes
         """
 
         # Check cache and compute
         return ItemSize(self.size)
 
     def __sub__(self, other):
+        """
+        Utility function to compute a 'diff' of 2 Items. All fields of ``self``
+        (left operand) identical to those of ``other`` (right operand) are dicarded.
+        The other fields from ``self`` are kept. This proves to be extremely
+        useful to support ``ALL_NEW`` and ``ALL_OLD`` return specification of
+        ``UpdateItem`` in a clean and readable manner.
+
+        :param other: ``Item`` to be used as filter
+
+        :return: dict with fields of ``self`` not in or different from ``other``
+        """
         # Thanks mnoel :)
         return {k:v for k,v in self.iteritems() if k not in other or v != other[k]}

ddbmock/database/key.py

 
 class Key(object):
     """
-    Abstraction layer over DynamoDB Keys in :py:class:`ddbmock.databas.item.Item`
+    Abstraction layer over DynamoDB Keys in :py:class:`ddbmock.database.item.Item`
     """
     def __init__(self, name, typename):
         """
         :param key: Raw DynamoDB request key.
 
         :return: the value of the key
+
+        :raises: :py:exc:`ddbmock.errors.ValidationException` if field types does not match
+
         """
         typename, value = key.items()[0]
         if self.typename != typename:

docs/api/database/item.rst

+##########
+Item class
+##########
+
+.. currentmodule:: ddbmock.database.item
+
+.. autoclass:: Item
+
+Constructors
+============
+
+__init__
+--------
+
+.. automethod:: Item.__init__
+
+Item manipulations
+==================
+
+filter
+------
+
+.. automethod:: Item.filter
+
+apply_actions
+-------------
+
+.. automethod:: Item.apply_actions
+
+assert_match_expected
+---------------------
+
+.. automethod:: Item.assert_match_expected
+
+match
+-----
+
+.. automethod:: Item.match
+
+field_match
+-----------
+
+.. automethod:: Item.field_match
+
+read_key
+--------
+
+.. automethod:: Item.read_key
+
+get_field_size
+--------------
+
+.. automethod:: Item.get_field_size
+
+get_size
+--------
+
+.. automethod:: Item.get_size
+
+__sub__
+-------
+
+.. automethod:: Item.__sub__
+
+
+