diff --git a/doc/conf.py b/doc/conf.py index 605c6f6..367e848 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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' diff --git a/doc/intro.rst b/doc/intro.rst index 646fb3e..efe75d3 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -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. :: @@ -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: :: diff --git a/doc/selector.rst b/doc/selector.rst index b65fc93..2e1e5b2 100644 --- a/doc/selector.rst +++ b/doc/selector.rst @@ -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 --------------- @@ -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__ diff --git a/normalize/property/__init__.py b/normalize/property/__init__.py index 1af3693..4dce0d0 100644 --- a/normalize/property/__init__.py +++ b/normalize/property/__init__.py @@ -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: diff --git a/normalize/selector.py b/normalize/selector.py index d60b8ac..31be59b 100644 --- a/normalize/selector.py +++ b/normalize/selector.py @@ -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: @@ -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. """