Skip to content

Commit

Permalink
Documentation updates for 0.7.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
samv committed Feb 19, 2015
1 parent 66da491 commit 1656cb2
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 25 deletions.
4 changes: 2 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
project = u'normalize'
copyright = u'2014, Sam Vilain, Hearsay Social'

version = '0.6'
release = '0.6.4'
version = '0.7'
release = '0.7.0'

exclude_patterns = ['sphinx-build']
pygments_style = 'sphinx'
Expand Down
54 changes: 42 additions & 12 deletions doc/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,39 @@ function.

.. _defaults:

Property Defaults
-----------------
Property Defaults and Empty psuedo-attributes
---------------------------------------------

Normally, when you access an attribute which is not set, you will get
an ``AttributeError``. This is largely an artefact of Python's core
design. Typically you would guard against attributes not being set
like this:

::

if hasattr(record, "some_property", False) and \
record.some_property == "foo":
# ...

However, this gets repetitive quickly. Instead, you can access the
``empty_attr`` attribute, a read-only attribute which is implicitly
created, and named the same as your own attribute but with '0'
appended. This reduces the above to:

::

if record.some_property0 == "foo":
# ...

If you don't like the name, you can override it on a per-attribute
basis using ``empty_attr``, or define a Property type that overrides
``aux_props`` and use that property type.

One alternative way to do to this is to set defaults on attributes.
It's possible to pass a value or function to the ``default=``
parameter, to set a default value for a property in case one is not
provided.

You can even use this to make properties that do not raise
``AttributeError`` if they were not set:
provided. That way, because there is always a value in the slot,
there is no chance that ``AttributeError`` will be raised.

::

Expand All @@ -250,12 +274,18 @@ You can even use this to make properties that do not raise
Sloppy(anything=None, goes='', here=None)
>>>

Beware that the value is assigned without consideration about whether
it needs to be copied or not. For immutable value types like strings,
integers, etc this is fine. For mutable lists, dictionaries, etc, it
is likely to be a problem if you want to change the value after
construction. An easy way around this is to supply a function that
returns a new instance of the value:
However, you should be careful with this approach. Does it really
make sense for the information about whether a value is present to be
"in-band" with the value? In general, ``default=`` should be reserved
for true default values, and not simply to avoid the
``AttributeError``.

Also beware that the value is assigned without consideration about
whether it needs to be copied or not. For immutable value types like
strings, integers, etc this is fine. For mutable lists, dictionaries,
etc, it is likely to be a problem if you want to change the value
after construction. An easy way around this is to supply a function
that returns a new instance of the value:

::

Expand Down
31 changes: 21 additions & 10 deletions doc/selector.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,29 @@ There are two main purposes of ``normalize.selector``:
that the result from a :py:meth:`normalize.diff.diff` operation can
specify the field where the difference(s) were discovered.

* to *aggregate* collections of these expressions, so that *filters*
can be created.
* to *aggregate* collections of these expressions, so that *sets* of
attributes can be manipulated

Other functions exist, such as the ability for the fields referred to
by selectors to be updated (see :py:meth:`FieldSelector.put`), perhaps
even with auto-vivification of intermediate records and collection
items (:py:meth:`FieldSelector.post`).
The feature set builds on these basic purposes, starting with
retrieval (see :py:meth:`normalize.selector.FieldSelector.get`),
assignment (see :py:meth:`normalize.selector.FieldSelector.put`),
assignment with auto-vivification of intermediate records and
collection items (:py:meth:`normalize.selector.FieldSelector.post`),
and finally deletion
(:py:meth:`normalize.selector.FieldSelector.delete`).

Multple ``FieldSelector`` objects can be combined, to make a
``MultiFieldSelector``. This can be used to create a "filtered"
object, and also can be passed to :py:func:`normalize.diff.diff` to
compare only a selection of fields in a data structure.
``MultiFieldSelector``. It also supports
:py:meth:`normalize.selector.MultiFieldSelector.get` which returns a
"filtered" object,
:py:meth:`normalize.selector.MultiFieldSelector.patch` which can be
used to selectively assign values from one object to another, and
:py:meth:`normalize.selector.MultiFieldSelector.delete`.

The ``MultiFieldSelector`` can be used to restrict the action of
visitor functions (such as :py:func:`normalize.diff.diff` and
:py:class:`normalize.visitor.VisitorPattern`) to compare or visit only
a selection of fields in a data structure.

Class reference
---------------
Expand All @@ -29,5 +40,5 @@ Class reference
:special-members: __init__, __getnewargs__, __eq__, __ne__, __lt__, __str__, __repr__, __add__, __len__, __getitem__

.. autoclass:: normalize.selector.MultiFieldSelector
:members: get, __init__, __iter__, __repr__, __str__, __getitem__, __contains__
:members: get, delete, patch, from_path, path, __init__, __iter__, __repr__, __str__, __getitem__, __contains__

9 changes: 9 additions & 0 deletions normalize/property/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,15 @@ def __str__(self):
return "<%s %s>" % (metaclass, self.fullname)

def aux_props(self):
"""This method is available for property traits to provide extra class
attributes which are added to the class they are defined in during
class creation. The default implementation is responsible for defining
``empty_attr`` attributes.
The return value should be an iterable list of 2-tuples, with the first
member of each 2-tuple being the attribute name and the second being
the value to insert.
"""
if self.empty_attr is not None:
return ((self.empty_attr, EmptyAuxProp(self)), )
else:
Expand Down
6 changes: 5 additions & 1 deletion normalize/selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,10 @@ def __str__(self):

@property
def path(self):
"""The path attribute returns a stringified, concise representation of
the MultiFieldSelector. It can be reversed by the ``from_path``
constructor.
"""
if len(self.heads) == 1:
return _fmt_mfs_path(self.heads.keys()[0], self.heads.values()[0])
else:
Expand Down Expand Up @@ -811,7 +815,7 @@ def patch(self, target, source, copy=False):
``source=``\ *OBJECT*
the object to lift the fields from
``copy=``\ *BOOL*\ |\ *FUNCTION*
``copy=``\ *BOOL*\ \|\ *FUNCTION*
deep copy the values set, using copy.deepcopy (or the passed
function). False by default.
"""
Expand Down

0 comments on commit 1656cb2

Please sign in to comment.