Do-Py
Do-Py, shorthand for DataObject Python, is a data-validation and standardization library wrapping Python dictionaries.
Project milestones
Quick-Start
Make a basic DataObject.
We will make a class and call it MyFavoriteStuff. We
will inherit the DataObject class to gain all its wonderful features.
Here you can see we must define the '_restrictions' attribute.
from do_py import DataObject, R
class MyFavoriteStuff(DataObject):
"""
A DataObject that contains all of my favorite items.
:restriction favorite_number: The number I favor the most. Strings not allowed.
:restriction favorite_candy: My favorite candy, this is restricted by value.
:restriction favorite_movie: My favorite movie. This is optional because a `None` IS allowed!
"""
# There are two kinds of restrictions, type and value.
_restrictions = {
# Type restrictions restrict the type a value can have: int, str, bool, or other DataObjects's
'favorite_number': R.INT,
# Value restrictions restrict the value to a specific value in a list.
'favorite_candy': R('Jolly Ranchers', 'Nerds'),
# This is a type restriction that allows `None` as a value.
'favorite_movie': R.NULL_STR
}
# Instantiate your new DataObject.
instance = MyFavoriteStuff({
'favorite_number': 1985,
'favorite_candy': 'Jolly Ranchers',
'favorite_movie': 'Jolly Green Giant'
})
print(instance)
# output: MyFavoriteStuff{"favorite_candy": "Jolly Ranchers", "favorite_number": 1985, "favorite_movie": "Jolly Green Giant"}
# You can access values using dot notation or like a `dict`.
print(instance.favorite_number == instance['favorite_number'])
# output: True
print(instance.favorite_number)
print(instance.favorite_candy)
print(instance.favorite_movie)
# output: 1985
# output: Jolly Ranchers
# output: Jolly Green Giant
# Editing the values can also be done very easily.
instance.favorite_number = 2013
print(instance.favorite_number)
# output: 2013
Using restrictions.
Restrictions are written using do_py.R. R allows developers to define custom value restrictions as well as type
restrictions using the special shortcuts. Here are a few examples of how you can write value restrictions and type
restrictions using the type short-cuts.
from do_py import DataObject, R
class TypeShorCuts(DataObject):
"""
All of the restrictions written for this DataObject us R's type shortcuts.
"""
_restrictions = {
# integer
'int': R.INT,
'nullable_int': R.NULL_INT,
# string
'str': R.STR,
'nullable_str': R.NULL_STR,
# bool
'bool': R.BOOL,
# date and datetime
'date': R.DATE,
'nullable_date': R.NULL_DATE,
'datetime': R.DATETIME,
'nullable_datetime': R.NULL_DATETIME,
# other (these are rarely used(aqw
'set': R.SET,
'list': R.LIST,
}
class ValueRestrictions(DataObject):
"""
All of the restrictions for this class are value restrictions.
"""
_restrictions = {
# number values
'integers': R(1, 2, 3),
'integers and None': R(1, 2, 3, None),
# string values
'strings': R('hello', 'hi', 'sup'),
'nullable_strings': R('hello', 'hi', 'sup', None),
}
Give the DataObject default values.
DataObjects are able to define the default value for their restrictions. If a developer is not sure
if a value will be available, defaults are a very useful utility. We have updated the original example to have
a default value for it's restriction favorite_candy.
In order to use the default value when instantiating a DataObject, we must instantiate it in non-strict mode.
Strict instantiation is used by default. In strict instantiation, the data passed in must contain all the
keys defined in the DataObject's _restrictions.
With non-strict initialization, it is acceptable to have some keys missing per DO _restrictions. For all missing keys,
the default restriction value is used. This section provides an example of using a DataObject in non-strict mode
so that we can use the default values for favorite_candy.
from do_py import DataObject, R
class MyFavoriteStuff(DataObject):
"""
:restriction favorite_number: The default value is 1.
:restriction favorite_candy: The default value is is "Unknown".
:restriction favorite_movie: When nullable, the default value is `None`.
"""
_restrictions = {
'favorite_number': R.INT.with_default(1),
'favorite_candy': R('Jolly Ranchers', 'Nerds', 'Unknown', default='Unknown'),
'favorite_movie': R.NULL_STR
}
# In order to use the default value when instantiating a DataObject, we must instantiate it in non-strict mode.
# Any values that are not provided will use defaults.
instance = MyFavoriteStuff({}, strict=False)
print(instance)
# output: MyFavoriteStuff{"favorite_candy": "Unknown", "favorite_number": 1, "favorite_movie": null}
Nest a DataObject in another DataObject.
from do_py import DataObject, R
class Contact(DataObject):
"""
Contact information for an author.
:restriction phone_number: A phone number as a string.
"""
_restrictions = {
'phone_number': R.STR
}
class Author(DataObject):
"""
An author of a video game.
:restriction id: The author's id.
:restriction name: The author's name.
:restriction contact: Nested Contact DataObject.
"""
_restrictions = {
'id': R.INT,
'name': R.STR,
'contact': Contact
}
class VideoGame(DataObject):
"""
A video game, authored by someone.
:restriction id: The video game's id.
:restriction name: The video game's name (optional).
:restriction author: Nested Author DataObject.
"""
_restrictions = {
'id': R.INT,
'name': R.NULL_STR,
'author': Author
}
# DataObjects must be instantiated with a dictionary and strict=True (default) or False.
# Nested DataObjects are built recursively from nested dicts.
instance = VideoGame({
'id': 1985,
'name': 'Super Mario Bros.',
'author': {
'id': 1,
'name': 'Shigeru Miyamoto',
'contact': {
'phone_number': '555-0100'
}
}
})
print(instance)
Nest a list of DataObjects in another DataObject.
from do_py import DataObject, R
from do_py.common.managed_list import ManagedList
class Book(DataObject):
"""
There are multiple books in the library!
:restriction name: Name of the book.
:restriction author: The author of the book.
"""
_restrictions = {
'name': R.STR,
'author': R.STR,
}
class Library(DataObject):
"""
This DataObject represents a library which contains multiple books.
:restriction city: The city the library is located in.
:restriction books: A list of instances of the DataObject "Book".
"""
_restrictions = {
'city': R.STR,
'books': ManagedList(Book)
}
What is a DataObject?
A DataObject allows us to create Python classes that have strictly defined fields called "restrictions". Restrictions
are defined for a DataObject using the _restriction attribute. See the Quick-start section.
There are two kinds of restrictions, type and value:
- Value restrictions restrict the value to a specific value in a list.
- Type restrictions restrict the type a value can have: int, str, bool, or other DataObjects.
Advanced Uses
Advanced DataObject validations.
Certain use-cases require more complex validations or restrictions that cannot be supported without code execution.
The parent class Validator allows us to execute code at instantiation and any time a key is updated. A child of
Validator is required to define a _validate instance method.
from do_py import R
from do_py.data_object.validator import Validator
class Validated(Validator):
"""
This DataObject validates that we only have one of key or id, but not both. Since this can't be accomplished only
using restrictions, we are inheriting from `Validator` so we can attach extra validations.
"""
_restrictions = {
'key': R.NULL_STR,
'id': R.NULL_INT
}
def _validate(self):
"""
Validate that we have exactly one of key or id.
This function runs at instantiation and any time the instance is updated.
"""
assert any([self.key, self.id]) and not all([self.key, self.id]), \
'We need exactly one of id or key to not be None.'
Metadata
Release files for do-py 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| do_py-1.0.0.tar.gz | 25.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| do_py-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 55.0 kB
Release files / do_py-1.0.0.tar.gz
| Download URL | do_py-1.0.0.tar.gz |
|---|---|
| Size | 25.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9cbf3ece3a82f8e8dc79ac7fc7bf212383b4902c4782e6039ff0746c968edb61
|
|
BLAKE2b-256 checksum How to use checksums |
29a19374ef66ae800636db66ac33c4693628c0b317a7fb053abf2668c66065f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / do_py-1.0.0-py3-none-any.whl
| Download URL | do_py-1.0.0-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d85d297c64ed9939f6cd6bf4a452390d658eccc2220d6d76d8880a1040087e9
|
|
BLAKE2b-256 checksum How to use checksums |
6516431e64751e15500e32e61a52535daa5461b41d294275f3db1ba0beaa95a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for do-py 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.