
Research
Malicious Go “crypto” Module Steals Passwords and Deploys Rekoobe Backdoor
An impersonated golang.org/x/crypto clone exfiltrates passwords, executes a remote shell stager, and delivers a Rekoobe backdoor on Linux.
objetto
Advanced tools
.. 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.
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.You can install Objetto by using pip <https://pypi.org/>_:
pip install objetto
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'
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
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:
IntegerFloatingPointRegexMatchRegexSubStringCuratedBooleanExample: 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_attributedict_attributeset_attributeExample: 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_attributeprotected_attribute_pairprotected_list_attribute_pairprotected_dict_attribute_pairprotected_set_attribute_pairExample: 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'
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:
Objects <Object_>_ can only have one parentData_ 'mirroring': The Data_ structure will replace child Objects <Object_>_ with their Data_ according to the hierarchySerialization_: The .serialize() and .deserialize() methods
utilize the hierarchy to format their input/outputAction_ 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 wayHistory <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
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_ 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
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:
Change_ (change)Object_ receiving the Action_ (receiver)Object_ where the change originated from (sender)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>_:
BatchUpdateDictUpdateListInsertListDeleteListUpdateListMoveSetUpdateSetRemoveReaction
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:
UniqueAttributesLimitChildrenLimitExample: 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'
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
Object-oriented framework for building smart applications and APIs
We found that objetto demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Research
An impersonated golang.org/x/crypto clone exfiltrates passwords, executes a remote shell stager, and delivers a Rekoobe backdoor on Linux.

Security News
npm rolls out a package release cooldown and scalable trusted publishing updates as ecosystem adoption of install safeguards grows.

Security News
AI agents are writing more code than ever, and that's creating new supply chain risks. Feross joins the Risky Business Podcast to break down what that means for open source security.