Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

objetto

Package Overview
Dependencies
Maintainers
1
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

objetto

Object-oriented framework for building smart applications and APIs

pipPyPI
Version
1.29.2
Maintainers
1

Objetto

.. image:: https://github.com/brunonicko/objetto/workflows/MyPy/badge.svg :target: https://github.com/brunonicko/objetto/actions?query=workflow%3AMyPy

.. image:: https://github.com/brunonicko/objetto/workflows/Lint/badge.svg :target: https://github.com/brunonicko/objetto/actions?query=workflow%3ALint

.. image:: https://github.com/brunonicko/objetto/workflows/Tests/badge.svg :target: https://github.com/brunonicko/objetto/actions?query=workflow%3ATests

.. image:: https://readthedocs.org/projects/objetto/badge/?version=stable :target: https://objetto.readthedocs.io/en/stable/

.. image:: https://img.shields.io/github/license/brunonicko/objetto?color=light-green :target: https://github.com/brunonicko/objetto/blob/master/LICENSE

.. image:: https://static.pepy.tech/personalized-badge/objetto?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads :target: https://pepy.tech/project/objetto

.. image:: https://img.shields.io/pypi/pyversions/objetto?color=light-green&style=flat :target: https://pypi.org/project/objetto/

Objetto is an opinionated object-oriented framework for building modular applications and APIs.

Overview

Objetto allows for the creation of an Application_ that consists of high-level mutable structures referred to as Objects <Object_>_.

  • Objects <Object_>_ are associated with an Application_ when initialized.
  • Objects <Object_>_ are part of a parent-children Hierarchy_ tree.
  • Objects <Object_>_ have their schema defined by Attributes <Attribute>_.
  • Objects <Object_>_ encase an immutable subset version of themselves referred to as Data_.
  • Objects <Object_>_ will send an Action_ to themselves, their parent, and grandparents everytime a Change_ happens.
  • Objects <Object_>_ can perform Reactions <Reaction>_ in response to Actions <Action>_ received from themselves, their children, and grandchildren.
  • Objects <Object_>_ can be observed by external Observers <Action Observer>_ such as GUI widgets.
  • Objects <Object_>_ feature built-in human-readable Serialization_ and Deserialization_ capabilities.
  • Objects <Object_>_ can be automatically tracked by a History <Undo/Redo History>_, which allows for easy and selective undo/redo functionality.

How to install

You can install Objetto by using pip <https://pypi.org/>_:

pip install objetto

Application

An Application_ oversees all Objects <Object_>_ that are meant to work together. It provides different contexts for managing and keeping track of their Changes <Change>_.

Objects <Object_>_ that are part of different Applications <Application>_ see each other as regular values and can never be part of the same Hierarchy_.

An Application_ can have Root Objects <Roots>, which are Objects <Object_> that are always available at the top of the hierarchy, and cannot be parented under other Objects <Object_>_.

Example: Instantiate a new Application_.

.. code:: python

>>> from objetto import Application

>>> app = Application()  # instantiate a new application

Read Context

While in a Read Context, all Objects <Object> in the Application_ are guaranteed not to be modified.

Example: Enter a Read Context_.

.. code:: python

>>> from objetto import Application

>>> app = Application()
>>> with app.read_context():
...     pass  # read access only, no changes allowed
...

Write Context

While in a Write Context, Actions <Action> are only sent internally until the outermost Write Context_ exits without errors, after which external Observers <Action Observer>_ will then receive them.

If an unhandled exception gets raised, all changes are reverted to the moment the context was entered, and external Observers <Action Observer>_ will not receive any Actions <Action>_. This behavior is similar to transactions in a database.

.. note:: You cannot enter a Write Context_ while in a Read Context_.

Example: Enter a Write Context_.

.. code:: python

>>> from objetto import Application

>>> app = Application()
>>> with app.write_context():
...     pass  # send actions to external observers only at the end, revert if errors
...

Roots

Root Objects <Object_>_ can be declared when creating a subclass of an Application_ by using a root descriptor and specifying the Object_ type and initialization arguments.

Example: Define Root Objects <Roots>_ when subclassing Application_.

.. code:: python

>>> from objetto import Application, Object, attribute, root

>>> class Document(Object):
...     title = attribute(str)
...
>>> class CustomApplication(Application):  # inherit from Application
...     document = root(Document, title="untitled")  # specify object type and args
...
>>> app = CustomApplication()
>>> type(app.document).__name__
'Document'

Object

Objects <Object_>_ are the building blocks of an Application. An Object is mutable, has state, and can be a parent/child of another Object_.

To define our own Object, we have to inherit from objetto.Object and use Attributes <Attribute> to define its schema. You need to instantiate it by passing an Application_, which can later be accessed through the .app property:

Example: Make our own Object_ subclass and instantiate it.

.. code:: python

>>> from objetto import Application, Object, attribute

>>> class Hobby(Object):  # inherit from Object
...     description = attribute(str)  # example attribute called 'description'
...
>>> app = Application()  # we need an application
>>> hobby = Hobby(app, description="biking")  # instantiate our object
>>> hobby.app is app
True

Attribute

Attributes <Attribute>_ describe the schema of an Object. When defining one, we can specify relationship parameters between the Object that owns it and the value being stored, such as a Value Type, Hierarchy settings, History <Undo/Redo History>_ propagation, Serialization_ and Deserialization_ options, etc.

Example: Define custom Objects <Object_>_ with multiple Attributes <Attribute>_.

.. code:: python

>>> from objetto import Application, Object, attribute

>>> class Hobby(Object):
...     description = attribute(str)  # specify value type, only takes strings
...
>>> class Person(Object):
...     name = attribute(str, default="Phil")  # specify a default value
...     hobby = attribute(Hobby)  # specify value type, only takes 'Hobby' objects
...     busy = attribute(bool, serialized=False, default=False)  # not serialized
...
>>> app = Application()
>>> hobby = Hobby(app, description="biking")
>>> person = Person(app, hobby=hobby)
>>> person.name
'Phil'
>>> person.name = "Gaimon"
>>> person.name
'Gaimon'

Value Type

When defining an Attribute, we can specify its Value Type. This is leveraged by the runtime type checking and by static ones such as mypy <http://mypy-lang.org/>_.

Defining types is also helpful to inform Objetto about the schema of our Objects <Object>, which is needed for proper Serialization and Deserialization_.

Import strings are also valid (using the syntax module.submodule|Class.NestedClass), and they will be imported lazily during runtime. It's also possible to use multiple Types <Value Type>_ by specifying them in a tuple.

.. note:: Static type checkers such as mypy <http://mypy-lang.org/>_ might not understand types correctly when multiple/lazy types are declared. In that case, you can help the type checker by adding a type hint/comment using the Attribute_ base like so:

**Example**: Helping static type checkers with a type hint for the attribute.

.. code:: python

    >>> from typing import Union
    >>> from objetto.objects import Attribute  # use 'Attribute' base for type hint
    >>> from objetto import Object, attribute

    >>> class Example(Object):
    ...     foo = attribute(
    ...         (str, int, "__main__|Example")
    ...     )  # type: Attribute[Union[str, int, Example]]
    ...

The types are interpreted 'exactly' by default. This means they are checked and compared by identity, so instances of subclasses are not accepted. However that behavior can be changed by specifying subtypes=True when defining an Attribute_.

If None is also accepted as a value, we can specify None as a valid type.

Example: Define the Value Types <Value Type>_ of Attributes <Attribute>_.

.. code:: python

>>> from objetto import Object, attribute

>>> class Person(Object):
...     name = attribute(str)  # single exact value type
...     friend = attribute(("__main__|Person", None))  # import path, accepts None
...     hobby = attribute("module.hobby|Hobby") # import path with module path
...     points = attribute((int, float))  # multiple basic types
...     _status = attribute(serialized=False)  # no value type, not serialized
...     _pet = attribute(
...         "pets|AbstractPet", subtypes=True
...     )  # accepts instances of 'AbstractPet' subclasses

Value Factory

An Attribute_ can conform and/or verify new values by using a Value Factory, which is simply a function or callable that takes the newly input value, does something to it, and then return the actual value that gets stored in the Object.

.. note:: There's a very important thing to note when it comes to defining your own <Value Factory>, which is that any value returned by the factory should always produce itself in case it's fed again through the same factory. Also, the <Value Factory> needs to be deterministic.

You can use simple functions or callable types as Value Factories <Value Factory>_, but Objetto offers some very useful pre-defined ones that can be easily configured with parameters.

Here are some of those built-in Value Factories <Value Factory>_, which can be imported from objetto.factories:

  • Integer
  • FloatingPoint
  • RegexMatch
  • RegexSub
  • String
  • Curated
  • Boolean

Example: Use Value Factories <Value Factory>_ to conform/verify attribute values.

.. code:: python

>>> from objetto import Object, attribute
>>> from objetto.factories import RegexMatch, Integer, Curated, String, Boolean

>>> class Person(Object):
...     name = attribute(str, factory=RegexMatch(r"^[a-z ,.'-]+$"))  # regex match
...     age = attribute(int, factory=Integer(minimum=1))  # minimum integer
...     pet = attribute(str, factory=Curated(("cat", "dog"))) # curated values
...     job = attribute(str, factory=String())  # force string
...     happy = attribute(bool, factory=Boolean(), default=True)  # force boolean

Auxiliary Attribute

These are special Attributes <Attribute>_ that will hold multiple values instead of just one.

The most basic Auxiliary Attributes <Auxiliary Attribute>_ are:

  • list_attribute
  • dict_attribute
  • set_attribute

Example: Use Auxiliary Attributes <Auxiliary Attribute>_ to hold values.

.. code:: python

>>> from objetto import Application, Object, attribute, list_attribute

>>> class Hobby(Object):
...     description = attribute(str)
...
>>> class Person(Object):
...     hobbies = list_attribute(Hobby)  # holds multiple 'hobbies'
...
>>> app = Application()
>>> hobby_a = Hobby(app, description="biking")
>>> hobby_b = Hobby(app, description="gaming")
>>> person = Person(app, hobbies=(hobby_a, hobby_b))  # initialize with iterable
>>> person.hobbies[0] is hobby_a
True

Delegated Attribute

Attributes <Attribute>_ can have delegate methods that will get, set and/or delete the values of other Attributes <Attribute>_ in the same Object_.

When defining delegates, you have to specify which Attributes <Attribute>_ they will read from as dependencies.

.. note:: The results of delegate methods are cached, and because of that they should never rely on mutable external objects. Think of delegates as 'pure functions' in the context of the Object_ they belong to.

If an `Attribute`_ value needs to change according to external factors,
`Reactions <Reaction>`_ or regular methods are encouraged to be used instead of
delegates.

Example: Define a Delegated Attribute_ with a getter and a setter.

.. code:: python

>>> from objetto import Application, Object, attribute

>>> class Person(Object):
...     first_name = attribute(str)
...     last_name = attribute(str)
...     name = attribute(
...         str, delegated=True, dependencies=(first_name, last_name)
...     )  # delegated attribute with read dependencies
...
...     @name.getter  # define a getter delegate
...     def name(self):
...         return self.first_name + " " + self.last_name
...
...     @name.setter  # define a setter delegate
...     def name(self, value):
...         self.first_name, self.last_name = value.split()
...
>>> app = Application()
>>> person = Person(app, first_name="Katherine", last_name="Johnson")
>>> person.name
'Katherine Johnson'
>>> person.name = "Grace Hopper"
>>> person.name
'Grace Hopper'
>>> person.first_name
'Grace'
>>> person.last_name
'Hopper'

Attribute Helper

There are patterns that come up very often when defining Attributes <Attribute>. Instead of re-writing those patterns everytime, it's possible to use helper functions known as Attribute Helpers <Attribute Helper> to get the same effect.

Here are some examples of Attribute Helpers <Attribute Helper>_:

  • constant_attribute
  • protected_attribute_pair
  • protected_list_attribute_pair
  • protected_dict_attribute_pair
  • protected_set_attribute_pair

Example: Define a simple Attribute Helper_.

.. code:: python

>>> from objetto import Application, Object, protected_attribute_pair

>>> class Person(Object):
...     _name, name = protected_attribute_pair(str, default="King")  # helper
...
...     def set_name(self, name):
...         self._name = name.upper()  # set the changeable private attribute
...
>>> app = Application()
>>> person = Person(app)
>>> person.name
'King'
>>> person.name = "bb king"  # can't set non-changeable public attribute
Traceback (most recent call last):
AttributeError: attribute 'name' is read-only
>>> person.set_name("bb king")  # we have to use the method instead
>>> person.name
'BB KING'

Hierarchy

An Object_ can have one parent and/or multiple children.

The parent-children hierarchy is central to the way Objetto works, as it provides an elegant way to structure our Application_. It's essential for features like:

  • Preventing cyclic references: Objects <Object_>_ can only have one parent
  • Immutable Data_ 'mirroring': The Data_ structure will replace child Objects <Object_>_ with their Data_ according to the hierarchy
  • Human-readable Serialization_: The .serialize() and .deserialize() methods utilize the hierarchy to format their input/output
  • Action_ sending and subsequent Reaction\ response: Actions <Action> will propagate from where the Change_ happened all the way up the hierarchy to the topmost grandparent, triggering Reactions <Reaction>_ along the way
  • Automatic History <Undo/Redo History>_ propagation: Children can automatically be assigned to the same History <Undo/Redo History>_ of the parent if desired.

.. note:: The hierarchical relationship can be turned off selectively at the expense of those features by specifying child=False when we define an Attribute_.

Also note that the hierarchical relationship will only work between
`Objects <Object_>`_ within the same `Application`_.

Example: Access ._parent and ._children properties.

.. code:: python

>>> from objetto import Application, Object, attribute

>>> class Hobby(Object):
...     description = attribute(str)
...
>>> class Person(Object):
...     name = attribute(str)
...     hobby = attribute(Hobby)  # child=True is the default behavior
...
>>> app = Application()
>>> hobby = Hobby(app, description="animation")
>>> person = Person(app, name="Hayao Miyazaki", hobby=hobby)
>>> hobby._parent is person  # 'person' is the parent of 'hobby'
True
>>> hobby in person._children  # 'hobby' is a child of 'person'
True

Undo/Redo History

Objetto has built-in support for a undo/redo History <Undo/Redo History>_. It takes care of managing its validity for internal changes by flushing itself automatically when necessary, and it is extremely easy to implement.

A history can be associated with an Object_ by adding a history_descriptor to the class definition. Accessing that attribute from an Object_'s instance will give us the history itself.

A history will be propagated to children/grandchildren of the Object_ which defines it, however it's possible to prevent that behavior by specifying history=False when we define an Attribute_.

Undo/redo can be triggered by running the history's methods .undo() and .redo().

Histories are Objects <Object_>_ too, so they do send Actions <Action>_ that can be observed by Observers <Action Observer>_.

Example: Associate a History <Undo/Redo History>_ with an Object_.

.. code:: python

>>> from objetto import Application, Object, history_descriptor, attribute

>>> class Person(Object):
...     history = history_descriptor()  # specify a history
...     name = attribute(str)
...
>>> app = Application()
>>> person = Person(app, name="Dave")
>>> person.name
'Dave'
>>> person.name = "Dave Grohl"
>>> person.name
'Dave Grohl'
>>> person.history.undo()  # undo the name change
>>> person.name
'Dave'

Batch Context

An Object_ can enter a Batch Context, which will group multiple Changes <Change> happening to itself and/or to other Objects <Object>_ into one single entry in the associated History <Undo/Redo History>_.

A special Action_ carrying the the name and the metadata of the batch context will be sent when entering (PRE Phase) and when exiting the context (POST Phase).

Example: Enter a Batch Context_.

.. code:: python

>>> from objetto import Application, Object, history_descriptor, attribute

>>> class Hobby(Object):
...     description = attribute(str)
...
>>> class Person(Object):
...     history = history_descriptor()  # specify a history
...     name = attribute(str)
...     hobby = attribute(Hobby)  # history will propagate by default
...
...     def set_info(self, name, hobby_description):
...         with self._batch_context("Set Person Info"):  # enter batch
...             self.name = name  # single change
...             self.hobby.description = hobby_description  # single change
...
>>> app = Application()
>>> hobby = Hobby(app, description="sailing")
>>> person = Person(app, name="Albert", hobby=hobby)
>>> person.name, person.hobby.description
('Albert', 'sailing')
>>> person.set_info("Einstein", "physics")  # batch change
>>> person.name, person.hobby.description
('Einstein', 'physics')
>>> person.history.undo()  # single undo will revert both changes
>>> person.name, person.hobby.description
('Albert', 'sailing')

Data

Data_ are analog structures to Objects <Object_>_, but they are immutable.

Everytime an Object_ changes, their internal Data_ and all of its parent's and grandparents' Data_ get replaced with a new one that reflects those changes.

By default, every Object_ class/subclass with automatically generate it's Data_ class based on its attributes and schema. You can access the data type of an Object_ through its .Data class property.

The Data_ instance for an Object_ can be accessed through its .data property.

Example: Access internal Data_ of an Object_.

.. code:: python

>>> from objetto import Application, Object, attribute

>>> class Hobby(Object):
...     description = attribute(str)
...
>>> class Person(Object):
...     hobby = attribute(Hobby)
...
>>> Person.Data.__fullname__  # access to automatically generated 'Data' class
'Person.Data'
>>> app = Application()
>>> hobby = Hobby(app, description="biking")
>>> person = Person(app, hobby=hobby)
>>> hobby_data = person.data.hobby  # access 'hobby' data through 'person' data
>>> hobby_data is hobby.data
True
>>> hobby_data.description
'biking'

If you want to bind methods from the Object_ to the Data_ as well, you can use the data_method decorator.

Example: Using the data_method decorator.

.. code:: python

>>> from objetto import Application, Object, attribute, data_method

>>> class Hobby(Object):
...     description = attribute(str)
...
...     @data_method
...     def get_description(self):
...         return "Description: {}".format(self.description)
...
>>> app = Application()
>>> hobby = Hobby(app, description="biking")
>>> hobby.get_description()
'Description: biking'
>>> hobby.data.get_description()  # 'hobby' data also has the method
'Description: biking'

And finally, if you want more control, you can define a custom Data_ class for an Object, but this only recommended for advanced behavior. Keep in mind that the class must match the schema of the Object <Object>'s Attributes <Attribute>_.

Example: Defining a custom Data_ class for an Object <Object>_.

.. code:: python

>>> from objetto import Application, Object, Data, attribute, data_attribute

>>> class Hobby(Object):
...     description = attribute(str)
...
...     class Data(Data):
...         description = data_attribute(str, factory=lambda v, **_: v.upper())
...
>>> app = Application()
>>> hobby = Hobby(app, description="biking")
>>> hobby.description
'biking'
>>> hobby.data.description  # data attribute has a custom factory
'BIKING'

It's also possible to use Data_ on its own, without an encasing Object. Remember that Data instances are immutable, so the only way to produce changes is by calling methods that return a new version of the data when subclassing from an interactive Data_ class.

Example: Using an interactive Data_ on its own.

.. code:: python

>>> from objetto import InteractiveData, data_attribute

>>> class HobbyData(InteractiveData):  # inherit from InteractiveData
...     description = data_attribute(str)  # use data attributes
...
>>> class PersonData(InteractiveData):
...     hobby = data_attribute((HobbyData, None))  # specify data types
...
>>> hobby_data = HobbyData(description="biking")
>>> new_hobby_data = hobby_data.set("description", "programming")  # make new
>>> person_data = PersonData(hobby=hobby_data)
>>> person_data.hobby = None  # data is immutable
Traceback (most recent call last):
AttributeError: 'PersonData' object attribute 'hobby' is read-only

Action

Every time an Object_ changes, it will automatically send an Action_ up the Hierarchy_ to its parent and grandparents.

The Action_ carries information such as:

  • The description of the Change_ (change)
  • A reference to the Object_ receiving the Action_ (receiver)
  • A reference to the Object_ where the change originated from (sender)
  • A list of relative indexes/keys from the receiver to the sender (locations)

Phase

A constant value that tells whether the change in the state is about to happen (PRE) or if the change already happened (POST).

Change

A Change_ describes what exactly changed in the state of an Object_.

Here are some of the Changes <Change>_ provided by Objects <Object>_:

  • Batch
  • Update
  • DictUpdate
  • ListInsert
  • ListDelete
  • ListUpdate
  • ListMove
  • SetUpdate
  • SetRemove

Reaction

Objects <Object_>_ can define Reactions <Reaction>_ that will get triggered once Actions <Action>_ are received. Reactions <Reaction>_ are special methods of Objects <Object_>_ that respond to Actions <Action>_ received from themselves, their children, and grandchildren.

.. note:: While an Object_ can react to its own changes, its triggered Reaction_ cannot perform any further changes to the same Object_, only to its children and grandchildren.

If an `Attribute`_ value needs to change when another `Attribute`_ in the same
`Object`_ changes, `Delegated Attributes <Delegated Attribute>`_ should be used
instead of `Reactions <Reaction>`_.

Example: Define Reaction_ methods.

.. code:: python

>>> from objetto import Application, Object, attribute, reaction, POST

>>> class MyObject(Object):
...     value = attribute(int, default=0)
...
...     @reaction
...     def __on_received(self, action, phase):
...         if not self._initializing and phase is POST:
...             print(("LAST -", action.change.name, phase))
...
...     @reaction(priority=1)
...     def __on_received_first(self, action, phase):
...         if not self._initializing and phase is POST:
...             print(("FIRST -", action.change.name, phase))
...
>>> app = Application()
>>> my_obj = MyObject(app)
>>> my_obj.value = 42
('FIRST -', 'Update Attributes', <Phase.POST: 'POST'>)
('LAST -', 'Update Attributes', <Phase.POST: 'POST'>)

Action Observer

After all internal Reactions <Reaction>_ within an Write Context_ run without any errors, the Actions <Action>_ are then finally sent to external Action Observers <Action Observer>_ so they have a chance to synchronize.

Graphical user interface widgets are a good example of Action Observers <Action Observer>_.

Example: Register an external Action Observer_.

.. code:: python

>>> from objetto import Application, Object, ActionObserver, attribute

>>> class Person(Object):
...     name = attribute(str, default="Nina")
...
>>> class PersonObserver(ActionObserver):
...
...     def __observe__(self, action, phase):
...         print((action.change.name, phase.value))
...
>>> app = Application()
>>> person = Person(app)
>>> observer = PersonObserver()
>>> token = observer.start_observing(person)
>>> person.name = "Simone"
('Update Attributes', 'PRE')
('Update Attributes', 'POST')

Auxiliary Attribute Reaction

It is possible to specify Reactions <Reaction>_ methods/callables when defining Auxiliary Attributes <Auxiliary Attribute>_. Objetto offers configurable reactions that can be used for that purpose.

Here are some of them:

  • UniqueAttributes
  • LimitChildren
  • Limit

Example: Ensure unique names.

.. code:: python

>>> from objetto import Application, Object, attribute, list_attribute
>>> from objetto.reactions import UniqueAttributes

>>> class Person(Object):
...     name = attribute(str)
...
>>> class Band(Object):
...     musicians = list_attribute(Person, reactions=UniqueAttributes("name"))
...
>>> app = Application()
>>> person_a = Person(app, name="Paul")
>>> person_b = Person(app, name="John")
>>> band = Band(app, musicians=(person_a, person_b))
>>> person_c = Person(app, name="Paul")
>>> band.musicians.append(person_c)
Traceback (most recent call last):
ValueError: another object already has 'name' set to 'Paul'

Serialization

Objects <Object>_ support human-readable serialization out of the box.

Example: Serialize an Object_.

.. code:: python

>>> from objetto import Application, Object, attribute, list_attribute

>>> class Person(Object):
...     name = attribute(str)
...
>>> class Band(Object):
...     musicians = list_attribute(Person)
...
>>> app = Application()
>>> person_a = Person(app, name="Oscar")
>>> person_b = Person(app, name="Ray")
>>> band = Band(app, musicians=(person_a, person_b))
>>> band.serialize()
{'musicians': [{'name': 'Oscar'}, {'name': 'Ray'}]}

Deserialization

Objects <Object>_ support human-readable deserialization out of the box.

Example: Deserialize an Object_.

.. code:: python

>>> from objetto import Application, Object, attribute, list_attribute

>>> class Person(Object):
...     name = attribute(str)
...
>>> class Band(Object):
...     musicians = list_attribute(Person)
...
>>> app = Application()
>>> Band.deserialize({"musicians": [{"name": "Oscar"}, {"name": "Ray"}]}, app=app)
Band(musicians=[<Person at ...>, <Person at ...>])

Custom Serializer/Deserializer

You can specify custom serializer/deserializer functions for attributes.

Example: Serialize an Enum using lambdas.

.. code:: python

>>> from enum import Enum
>>> from objetto import Application, Object, attribute

>>> class Hobby(Enum):
...     GUITAR = 1
...     BIKING = 2
...
>>> class Person(Object):
...     hobby = attribute(
...         Hobby,
...         serializer=lambda value, **_: value.name.lower(),
...         deserializer=lambda value, **_: Hobby[value.upper()],
...     )
...
>>> app = Application()
>>> person = Person(app, hobby=Hobby.GUITAR)
>>> person.serialize()
{'hobby': 'guitar'}
>>> Person.deserialize({"hobby": "biking"}, app=app)
Person(hobby=<Hobby.BIKING: 2>)

Example: Serialize an Enum using provided serializer/deserializer.

.. code:: python

>>> from enum import Enum
>>> from objetto import Application, Object, attribute
>>> from objetto.serializers import EnumSerializer
>>> from objetto.deserializers import EnumDeserializer

>>> class Hobby(Enum):
...     GUITAR = 1
...     BIKING = 2
...
>>> class Job(Enum):
...     PROGRAMMER = 1
...     TEACHER = 2
...
>>> class Person(Object):
...     hobby = attribute(
...         Hobby,
...         serializer=EnumSerializer(),
...         deserializer=EnumDeserializer(Hobby),
...     )
...     job = attribute(
...         Job,
...         serializer=EnumSerializer(by_name=True),
...         deserializer=EnumDeserializer(Job, by_name=True),
...     )
...
>>> app = Application()
>>> person = Person(app, hobby=Hobby.GUITAR, job=Job.PROGRAMMER)
>>> serialized = person.serialize()
>>> serialized["hobby"]
1
>>> serialized["job"]
'PROGRAMMER'
>>> Person.deserialize({"hobby": 2, "job": "TEACHER"}, app=app)
Person(hobby=<Hobby.BIKING: 2>, job=<Job.TEACHER: 2>)

... And More!

Take a look at the API documentation to learn more about Objetto.

FAQs

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts