Another backwards compatibility accessor for
RecordList.values
allows assignment to proceed.class MyFoo(Record): bar = ListProperty(of=SomeRecord) foo = MyFoo(bar=[]) # this will now warn instead of throwing Exception foo.bar.values = list_of_some_records # these forms will not warn: foo.bar = list_of_some_records foo.bar[:] = list_of_some_records
- the
RecordList.values
removal in 0.9.0 has been changed to be a deprecation with a warning instead of a hard error.
ListProperty
attribute can now be treated like lists; they support almost all of the same methods the built-inlist
type does, and type-checks values inserted into them with coercion.note: if you were using
.values
to access the internal array, this is now not present onRecordList
instances. You should be able to just remove the.values
:class MyFoo(Record): bar = ListProperty(of=SomeRecord) foo = MyFoo(bar=[somerecord1, somerecord2]) # before: foo.bar.values.extend(more_records) foo.bar.values[-1:] = even_more_records # now: foo.bar.extend(more_records) foo.bar[-1:] = even_more_records
DictProperty
can now be used, and these also support the importantdict
methods, with type-checking.You can now construct typed collections using
list_of
anddict_of
:from normalize.coll import list_of, dict_of complex = dict_of(list_of(int))() complex['foo'] = ["1"] # ok complex['foo'].append("bar") # raises a CoercionError
Be warned if using
str
as a type constraint that just about anything will happily coerce to a string, but that might not be what you want. Consider usingbasestring
instead, which will never coerce successfully.
bool(record)
was reverted to pre-0.7.x behavior: always True, unless a Collection in which case Falsy depending on the number of members in the collection.- Empty psuedo-attributes now return
normalize.empty.EmptyVal
objects, which are alwaysFalse
and perform a limited amount of sanity checking/type inference, so that misspellings of sub-properties can sometimes be caught.
- A regression which introduced subtle bugs in 0.7.0, which became more significant with the new feature delivered in 0.7.3 was fixed.
- An exception with some forms of dereferencing MultiFieldSelectors was fixed.
- Added a new option to diff to suppress diffs found when comparing lists of objects for which all populated fields are filtered.
- Fixed a regression with the new 'json_out' behavior I decided was big enough to pull 0.7.1 from PyPI for.
VisitorPattern.visit with visit_filter would not visit everything in the filter due to the changes in 0.7.0
MultiFieldSelector subscripting, where the result is now a "complete" MultiFieldSelector (ie, matches all fields/values) is now more efficient by using a singleton
the return of 'json_out' is no longer unconditionally passed to
to_json
: call it explicitly if you desire this behavior:class Foo(Record): bar = Property(isa=Record, json_out=lambda x: {"bar": x})
If you are using
json_out
like this, and expectingRecord
values or anything with ajson_data
method to have that called, then you can wrap the whole thing into_json
:from normalize.record.json import to_json class Foo(Record): bar = Property(isa=Record, json_out=lambda x: to_json({"bar": x}))
Lots of long awaited and behavior-changing features:
empty pseudo-attributes are now available which return (usually falsy) values when the attribute is not set, instead of throwing AttributeError like the regular getters.
The default is to call this the same as the regular attribute, but with a '0' appended;
class Foo(Record): bar = Property() foo = Foo() foo.bar # raises AttributeError foo.bar0 # None
The default 'empty' value depends on the passed
isa=
type constraint, and can be set toNone
or the empty string, as desired, usingempty=
:class Dated(Record): date = Property(isa=MyType, empty=None)
It's also possible to disable this functionality for particular attributes using
empty_attr=None
.Property uses which are not safe will see a new warning raised which includes instructions on the changes recommended.
accordingly, bool(record) now also returns false if the record has no attributes defined; this allows you to use '0' in a chain with properties that are record types:
if some_record.sub_prop0.foobar0: pass
Instead of the previous:
if hasattr(some_record, "sub_prop") and \ getattr(some_record.sub_prop, "foobar", False): pass
This currently involves creating a new (empty) instance of the object for each of the intermediate properties; but this may in the future be replaced by a proxy object for performance.
The main side effect of this change is that this kind of code is no longer safe:
try: foo = FooJsonRecord(json_data) except: foo = None if foo: #... doesn't imply an exception happened
The mechanism by which
empty=
delivers psuedo-attributes is available via theaux_props
sub-class API on Property.Various ambiguities around the way MultiFieldSelectors and their
__getattr__
and__contains__
operators (ie,multi_field_selector[X]
andX in multi_field_selector
) are defined have been updated based on findings from using them in real applications. See the function definitions for more.
- Fix
FieldSelector.delete
andFieldSelector.get
when some of the items in a collection are missing attributes
- lazy properties would fire extra times when using visitor APIs or other direct use of __get__ on the meta-property (#50)
- The 'path' form of a multi field selector can now round-trip, using
MultiFieldSelector.from_path
- Two new operations on
MultiFieldSelector
:delete
andpatch
- Add support in to_json for marshaling out a property of a record
- The 'path' form of a field selector can now round-trip, using
FieldSelector.from_path
- A false positive match was fixed in the fuzzy matching code.
- Gracefully handle unknown keyword arguments to Property() previously this would throw an awful internal exception.
- Be sure to emit NO_CHANGE diff events if deep, fuzzy matching found no differences
- Diff will now attempt to do fuzzy matching when comparing collections. This should result in more fine-grained differences when comparing data where the values have to be matched by content. This implementation in this version can be slow (O(N²)), if comparing very large sets with few identical items.
- Lots of improvements to exceptions with the Visitor
- More records should now round-trip ('visit' and 'cast') cleanly with
the default Visitor mappings; particularly
RecordList
types with extra, extraneous properties. - ListProperties were allowing unsafe assignment; now all collections will always be safe (unless marked 'unsafe' or read-only)
- values in attributes of type 'set' get serialized to JSON as lists by default now (Dale Hui)
- fixed a corner case with collection diff & filters (github issue #45)
- fixed
Property(list_of=SomeRecordType)
, which should have worked likeListProperty(of=SomeRecordType)
, but didn't due to a bug in the metaclass.
- You can now pass an object method to
compare_as=
on a property definition. - New sub-class API hook in
DiffOptions
:normalize_object_slot
, which receives the object as well as the value. - passing methods to
default=
which do not call their first argument 'self' is now a warning.
- Subscripting a MultiFieldSelector with an empty (zero-length) FieldSelector now works, and returns the original field selector. This fixed a bug in the diff code when the top level object was a collection.
- normalize.visitor overhaul. Visitor got split into a sub-class API, VisitorPattern, which is all class methods, and Visitor, the instance which travels with the operation to provide context. Hugely backwards incompatible, but the old API was undocumented and sucked anyway.
added support for comparing filtered objects;
__pk__()
object method no longer honored. Seetests/test_mfs_diff.py
for examplesMultiFieldSelector can now be traversed by indexing, and supports the
in
operator, with individual indices or FieldSelector objects as the member. Seetests/test_selector.py
for examples.extraneous
diff option now customizable via theDiffOptions
sub-class API.Diff
,JsonDiff
andMultiFieldSelector
now have more useful default stringification.The 'ignore_empty_slots' diff option is now capable of ignoring empty records as well as None-y values. This even works if the records are not actually None but all of the fields that have values are filtered by the DiffOptions compare_filter parameter.
added Diffas property trait, so you can easily add 'compare_as=lambda x: scrub(x)' for field-specific clean-ups specific to comparison.
errors thrown from property coerce functions are now wrapped in another exception to supply the extra context. For instance, the example in the intro will now print an error like:
- CoerceError: coerce to datetime for Comment.edited failed with
value '2001-09-09T01:47:22': datetime constructor raised: an integer is required
- enhancement to diff to allow custom, per-field normalization of values before comparison
- Some inconsistancies in JSON marshalling in were fixed
- the return value from
coerce
functions is now checked against the type constraints (isa
andcheck
properties) - added capability of Property constructor to dynamically mix variants
as needed; Almost everyone can now use plain
Property()
,ListProperty()
, or a shorthand typed property declaration (likeStringProperty()
); other properties likeSafe
andLazy
will be automatically added as needed. Property types such asLazySafeJsonProperty
are no longer needed and were savagely expunged from the codebase. SafeProperty
is now only a safe base class forProperty
sub-classes which have type constraints. Uses ofmake_property_type
which did not add type constraints must be changed toProperty
type, or will raiseexc.PropertyTypeMixNotFound
- bug fix for pickling
JsonRecord
classes - filtering objects via
MultiFieldSelector.get(obj)
now works forJsonRecord
classes. - The
AttributeError
raised when an attribute is not defined now includes the full name of the attribute (class + attribute)
- much work on the diff mechanisms, results, and record identity
- records which set a tuple for
isa
now work properly on stringification - semi-structured exceptions (
normalize.exc
) - the collections 'tuple protocol' (which models all collections as a sequence of (K, V) tuples) was reworked and made to work with more cases, such as iterators and generators.
- Added
DateProperty
andDatetimeProperty