Skip to content
/ objgen Public

dynamic generation of python objects from data structure specs -- conceptual basis of generic REST multi-API client

License

Notifications You must be signed in to change notification settings

oaao/objgen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 

Repository files navigation

objgen

Dynamically generate class objects by digesting key-value / pairwise data structures as object specifications.

objgen has no external dependencies, can be used immediately, and is more fun (and less limiting) than calling type('NewClassName', (*inheritances), {'attr': 'value'}).

usage

>>> from objgen.generators.generic import Base
>>> base = Base()
>>> vars(base)
{}

Pass a dictionary to a generator instance to construct its object, then use the object.

>>> hydrated = Base({'attr1': 'val1', 'attr2': 'val2'}, attr3='val3')
>>> vars(hydrated)
{'attr1': 'val1', 'attr2': 'val2', 'attr3': 'val3'}
>>> hydrated.attr1, hydrated.attr2, hydrated.attr3
('val1', 'val2', 'val3')

Any input that can be cast into a dictionary will be digested; any data that cannot will be discarded.

>>> from objgen.generators.generic import Base
>>> b = Base(
...     {'this': 'is', 'valid': 'input'},
...     'this_is_not_pairwise_data',
...     [('this', 'however'), ('will', 'work'), ('just', 'fine')]
... )
Element cannot be cast into dict, and is being discarded: <class 'str'> this_is_not_pairwise_data

If multiples of a field exists in a given collection of input arguments, the most recent passed value for that field will be preserved.

>>> b = Base({'this': 'is'}, [('this', 'however'), ])
>>> b
Base ['this']
>>> b.this
'however'

gore ( fun! ( examples ) )

nesting - manual

Say we want to create a dog, but also be able to access/call a dog.actions.{action}.

>>> dog_info = {
...         'name': 'monty',
...         'breed': 'donkey child',
...         'is_good_boy': True,
...         'actions': {'run_to': 'car', 'bark_at': 'squirrel', 'pee_on': 'hydrant'},
...     }

Instantiate and hydrate a generator as dog, then do the same to the resulting dog.actions.

>>> from objgen.generators.generic import Base
>>>
>>> dog = Base(dog_info)
>>> dog.actions
{'run_to': 'car', 'bark_at': 'squirrel', 'pee_on': 'hydrant'}
>>> dog.actions = Base(dog.actions)
>>> dog.actions.run_to
'car'

back

nesting - recursive

Let's make a Recursive generator to digest the full depth of the data structure -- having to call cls.attr = Base(cls.attr) for every single nested attributes at each given 'depth' is ridiculous.

Whereas a Base generator will simply create cls.key = value relationships,

                setattr(self, field, spec[field])

a Recursive generator will continue to digest any dictionary objects it encounters along any nesting path.

                if isinstance(spec[field], dict):
                    setattr(self, field, Recursive(spec[field]))

Let's try making our dog again, but with a Recursive generator:

>>> from objgen.generators.generic import Recursive
>>>
>>> dog = Recursive(dog_info)
>>> dog.actions
<objgen.generators.generic.Recursive object at 0x7f2bb4ef2c18>
>>> dog.actions.run_to
'car'

back

nesting - selectively recursive

So far so good - except, what if we want certain things to stay as dictionaries, rather than be indiscriminately digested?

For example, we want to add our dog's friends, and be able to access dog.friends - however, it would be silly for each {friend} to become a dog.friends.{friend} attribute instead of staying preserved as elements in a data structure.

... dog_info.update(
...     {
...         'friends': {
...             'lassie': {'breed': 'collie', 'met_at': 'dog park'},
...             'marnie': {'breed': 'shih tzu', 'met_at': 'dms'},
...             'air_bud': {'breed': 'golden retriever', 'met_at': 'basketball courts'},
...             'laika': {'breed': 'mongrel', 'met_at': 'outer space'}
...         }
...    }
...)

Let's make a RecursiveFiltered that deals with this for us.

Optionally, supply either _exclude or _include_only keyword arguments when creating the RecursiveFilter to determine which fields are (dis)allowed to be digested.

>>> from objgen.generators.generic import RecursiveFiltered
>>>
>>> dog = RecursiveFiltered(dog_info, _exclude='friends')
>>>
>>> type(dog.actions)
<class 'objgen.generators.generic.RecursiveFiltered'>
>>> type(dog.friends)
<class 'dict'>

Note that excluding a field means that digestion along that path in the data tree will terminate at that field, rather than skip that field and continue down that path's depth.


back

About

dynamic generation of python objects from data structure specs -- conceptual basis of generic REST multi-API client

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages