Package: tri.declarative
tri.declarativetri.declarative contains tools that make it easy to write declarative code. This includes:class decoratorsto define classes with subclass semantics in the style of django Model classesrecursivelyevaluatingembedded lambda expressions in complex data structuresrecursivelyfilteringof complex data structureskeyword argument dispatchingget/set attribute given a path string(e.g. ‘foo__bar__baz’)Class decoratorsWith just a few lines of code, turn your API from:quux=Foo(things=[Bar(name='a',param=1),Bar(name='b',param=2),Bar(name='c',param=2)],baz=3)into:classQuux(Foo):a=Bar(param=1)b=Bar(param=2)c=Bar(param=2)classMeta:baz=3And you can still use the first style when it’s more convenient!More detailed usage examples on@declarativebelow.Evaluatingd=dict(foo=lambdax:x*2,bar=lambday:y+5,baz=[foo=lambdax:x*6,],)# evaluate only one levelassertevaluate(d,x=2)==dict(foo=4,bar=lambday:y+5,# this function doesn't match the signature so isn't evaluatedbaz=[foo=lambdax:x*6,# one level down so isn't evaluated],)# evaluate recursivelyassertevaluate_recursive(d,x=2)==dict(foo=4,bar=lambday:y+5,# this function doesn't match the signature so isn't evaluatedbaz=[foo=12,],)Filteringd=dict(foo=dict(show=False,x=1,),bar=dict(show=True,x=2,),)assertfilter_show_recursive(d)==dict(bar=dict(show=True,x=2,),)Keyword argument dispatching@dispatch:@dispatch(bar={},baz__foo=2)deffoo(bar,baz):do_bar(**bar)do_baz(**baz)Get/set attribute given a path stringclassFoo:def__init__(a):self.a=aclassBar:def__init__(b):self.b=bclassBaz:def__init__(c):self.c=cx=Foo(Bar(Baz(c=3)))assertgetattr_path(x,'a__b__c')==3assertsetattr_path(x,'a__b__c',10)assertgetattr_path(x,'a__b__c')==10Running testsYou need tox installed then justmake test.LicenseBSDDocumentationhttps://trideclarative.readthedocs.org.Usage@declarativeIn the example below, the@declarative(str)decorator will ensure that allstrmembers of class Foo will be
collected and sent asmembersconstructor keyword argument.fromtri_declarativeimportdeclarative@declarative(str)classFoo:bar='barbar'baz='bazbaz'boink=17def__init__(self,members):assertmembers['bar']=='barbar'assertmembers['baz']=='bazbaz'assert'boink'notinmembersf=Foo()The value of themembersargument will also be collected from sub-classes:fromtri_declarativeimportdeclarative@declarative(str)classFoo:def__init__(self,members):assertmembers['bar']=='barbar'assertmembers['baz']=='bazbaz'classMyFoo(Foo):bar='barbar'baz='bazbaz'def__init__(self):super(MyFoo,self).__init__()f=MyFoo()Themembersargument can be given another name (thingsin the example below).fromtri_declarative.declarativeimportdeclarative@declarative(str,'things')classFoo:bar='barbar'def__init__(self,**kwargs):assert'things'inkwargsassertkwargs['things']['bar']=='barbar'f=Foo()Note that the collected dict is ordered by class inheritance and by usingsortedof the values within each class. (In the ‘str’ example,sortedyields in alphabetical order).Also note that the collection ofclassmembers based on their class doesnotinterfere withinstanceconstructor
argument of the same type.fromtri_declarativeimportdeclarative@declarative(str)classFoo:charlie='3'alice='1'def__init__(self,members):assertlist(members.items())==[('alice','1'),('charlie','3'),('bob','2'),('dave','4'),('eric','5')])assert'animal'notinmembersclassMyFoo(Foo):dave='4'bob='2'classMyOtherFoo(MyFoo):eric='5'def__init__(self,animal)assertanimal=='elephant'f=MyOtherFoo('elephant')Real world use-caseBelow is a more complete example of using @declarative:fromtri_declarativeimportdeclarative,creation_ordered@creation_orderedclassField:passclassIntField(Field):defrender(self,value):return'%s'%valueclassStringField(Field):defrender(self,value):return"'%s'"%value@declarative(Field,'table_fields')classSimpleSQLModel:def__init__(self,**kwargs):self.table_fields=kwargs.pop('table_fields')fornameinkwargs:assertnameinself.table_fieldssetattr(self,name,kwargs[name])definsert_statement(self):return'INSERT INTO%s(%s) VALUES (%s)'%(self.__class__.__name__,', '.join(self.table_fields.keys()),', '.join([field.render(getattr(self,name))forname,fieldinself.table_fields.items()]))classUser(SimpleSQLModel):username=StringField()password=StringField()age=IntField()my_user=User(username='Bruce_Wayne',password='Batman',age=42)assertmy_user.username=='Bruce_Wayne'assertmy_user.password=='Batman'assertmy_user.insert_statement()=="INSERT INTO User(username, password, age) VALUES ('Bruce_Wayne', 'Batman', 42)"# Fields are ordered by creation time (due to having used the @creation_ordered decorator)assertlist(my_user.get_declared('table_fields').keys())==['username','password','age']@with_metaClass decorator to enable a class (and it’s sub-classes) to have a ‘Meta’ class attribute.The members of the Meta class will be injected as arguments to constructor calls. e.g.:fromtri_declarativeimportwith_meta@with_metaclassFoo:classMeta:foo='bar'def__init__(self,foo,buz):assertfoo=='bar'assertbuz=='buz'foo=Foo(buz='buz')# Members of the 'Meta' class can be accessed thru the get_meta() class method.assertfoo.get_meta()=={'foo':'bar'}assertFoo.get_meta()=={'foo':'bar'}Foo()# Crashes, has 'foo' parameter, but no has no 'buz' parameter.The passing of the merged name space to the constructor is optional.
It can be disabled by passingadd_init_kwargs=Falseto the decorator.fromtri_declarativeimportwith_meta@with_meta(add_init_kwargs=False)classFoo:classMeta:foo='bar'Foo()# No longer crashesassertFoo().get_meta()=={'foo':'bar'}Another example:fromtri_declarativeimportwith_metaclassFoo:classMeta:foo='bar'bar='bar'@with_metaclassBar(Foo):classMeta:foo='foo'buz='buz'def__init__(self,*args,**kwargs):assertkwargs['foo']=='foo'# from Bar (overrides Foo)assertkwargs['bar']=='bar'# from Fooassertkwargs['buz']=='buz'# from BarThis can be used e.g to enable sub-classes to modify constructor default arguments.Changelog5.7.0 (2020-12-18)Make getattr_path more in line with the standard library getattr semanticsIf a default value is provided, return that on missing attributesIf no default value is given, give a more detailed error message of what was missingAdded the special case of the empty path returning the object5.6.0 (2020-12-02)Fix corner case of class Meta failing to merge with None namespace values5.5.0 (2020-08-21)Include tri.struct 4.x as possible requirement5.4.1 (2020-06-34)Optimizations5.4.0 (2020-04-16)Minor bug fix on trailing comma explanation TypeExceptionFix bug when nesting@class_shortcutwith same name i sub classesRefactor code to separate modules to get better stack traces5.3.0 (2020-04-01)Enable@class_shortcutto override baseclass shortcuts with the same name.Fix@with_metafailing on method declarations with@staticmethoddeclaration5.2.0 (2020-02-28)The namespace merge is narrowed to only affect the @with_meta case.Handle callingNamespacewithcall_target__attribute=None5.1.1 (2020-02-11)Improve namespace merge in @with_meta to not trip up @declarative5.1.0 (2020-02-11)Fix @with_meta argument injector to merge namespaces5.0.1 (2019-02-03)A minor update to the documentation generation to make it play nice with rST5.0.0 (2019-01-30)Added private field to shortcuts:__tri_declarative_shortcut_stack. This is useful to be able to figure out a shortcut stack after the factget_callable_descriptionthought stuff that contained a lambda in its string representation was a lambdaRemoved all deprecated APIs/behaviors:creation_orderedThe promotion of string values to keys inNamespaceMuch improved error messages4.0.1 (2019-10-23)Bugfix to correctly handle Namespace as callable/not callable depending on content4.0.0 (2019-10-11)get_meta()now collects extra arguments in aNamespaceto get consistent override behaviour.should_showno longer accepts a callable as a valid return value. It will assert on this, because it’s always a mistake.Addedevaluate_strictandevaluate_recursive_strictthat will not accept callables left over after the evaluation. If possible prefer these methods because they will stop the user of your library from making the mistake of not matching the given signature and ending up with an unevaluated callable in the output.3.1.0 (2019-06-28)Fixed issues when Namespace contained a key called any of items, values, keys, or getRemoved sorting on Namespace kwargs that isn’t needed in python 3 anymore. The sorting also destroys the given order which can be surprisingRemoved old obsolete functions collect_namespaces, extract_subkeys, and setdefaults3.0.0 (2019-06-10)Renamed module fromtri.declarativetotri_declarative. This is a breaking changeDropped support for python22.0.0 (2019-04-12)Fixedget_signaturecache to not pollute struct-like dictsNew call_target semantics for class method shortcuts, this is a potential breaking change1.2.1 (2019-13-15)Improved documentation output1.2.0 (2019-13-14)Add get_members function to enable reuse of @declarative attribute collectionAdd @class_shortcut decorator to enable @with_meta aware class shortcuts1.1.0 (2018-11-22)Addedgenerate_rst_docsfunction.1.0.6 (2018-09-28)Shortcutis now a special case when mergingNamespaceobjects.
When already in a Namespace, a Shortcut now get overwritten bysetitem_path(), not merged into the written value.1.0.5 (2018-09-21)Fix broken handling of empty key1.0.4 (2018-09-21)Cleanup Namespace path logic and make sure it is symmetrical and tested.Added deprecation warning on string to dict promotion on namespace merge.1.0.3 (2018-06-26)Fixed release functionality1.0.2 (2018-06-18)Don’t supportRefinableObjectinevaluate_recursive. This was a mistake.1.0.1 (2018-06-15)SupportRefinableObjectinevaluate_recursive.1.0.0 (2018-05-23)Cleanup deprecation warnings from inspect.getargspec0.34.0 (2017-08-21)Fix bug in 0.33.0 when promoting callable toNamespace.0.33.0 (2017-08-21)Fix bug when promoting callable toNamespace.Fix handling ofEMPTYmarker.0.32.0 (2017-07-04)Added promoting callable namespace members toNamespacewithcall_targetinsetdefaults_path.0.31.0 (2017-06-15)Improvesort_afterto allow more combinations ofafter=…specifications.
e.g. by name of an entry also moved by spec.Changed name of first parameter ofsetdefaults_pathto__target__to avoid
collitions with namespace parameters.AddedRefinableObjectbase for reuse by classes wanting to be able to be configured
via constructor kwarg parameters in a declarative fashion. (The namespace of possible
constructor overrides are declared withRefinable()for values and the decorator@refinablefor methods.Added first incarnation of crawling the definitions to recursively find available
parameters on objects and their aggregates.AddedShortcutabstraction to be able to find pre-defined set of overrides ofRefinableObjectclasses.0.30.0 (2017-02-10)evaluateandevaluate_recursivealso works for methods as well as for functions.0.29.0 (2016-09-12)Fixed loop detection in flatten forNamespaces. This resulted in data
corruption.0.28.0 (2016-07-15)AddedNamespacesubclass oftri.struct.Structto explicit capture the
path splitting semantics. (And added method for flattening aNamespaceback
to path notation.)0.27.0 (2016-07-13)Fix bug inevaluatesignature detection with optional arguments.
(lambda a, b=17: a+bwas correctly matched butlambda b, a=17: a+bwas not)0.26.0 (2016-05-06)AddedEMPTYmarker tosetdefaults_pathto avoid mixup when empty dict is
provided in function defaults.0.25.0 (2016-04-28)Added @dispatch decorator0.24.0 (2016-04-20)Fix bug insetdefault_pathtripping up on key ordering.Droppednamespace_factorykeyword argument tosetdefaults_pathnot likely
ever beeing used.0.23.0 (2016-04-15)setdefaults_pathnow accepts multiple default dicts. (To simplify the pattern of
shortcuts in tri.form, tri.query and tri.table where we now will end up with:new_kwargs = setdefaults_path(Struct(), kwargs, dict(….))0.22.0 (2016-03-24)sort_after()should produce an error when attempting to sort after non-existant keysTweaked namespace merge insetdefaults_path0.21.0 (2016-03-01)Fix corner case in collect_namespaces where one parameter imply a value and
others imply a namespace.Addedsetdefaults_pathhelper with__namespace traversal.0.20.0 (2016-02-29)Addedassert_kwargs_not_emptyconvenience function.Improved documentation.0.19.0 (2016-01-12)When making instances of a class decorated with@declarativethe declared
values are copied (shallow) before being passed to__init__.Instances will get an own copy of the declared attributes written to their__dict__