Skip to main content

Data validation and standardization library wrapping Python dictionaries.

Project description

Do-Py

Do-Py, shorthand for DataObject Python, is a data-validation and standardization library wrapping Python dictionaries.

release build coverage dependencies

Project milestones

Beta ready Stable ready

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.'

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

do_py-1.0.0.tar.gz (25.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

do_py-1.0.0-py3-none-any.whl (29.4 kB view details)

Uploaded Python 3

File details

Details for the file do_py-1.0.0.tar.gz.

File metadata

  • Download URL: do_py-1.0.0.tar.gz
  • Upload date:
  • Size: 25.6 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for do_py-1.0.0.tar.gz
Algorithm Hash digest
SHA256 9cbf3ece3a82f8e8dc79ac7fc7bf212383b4902c4782e6039ff0746c968edb61
MD5 e9fb615e4f74d1568e165b9a906a0aef
BLAKE2b-256 29a19374ef66ae800636db66ac33c4693628c0b317a7fb053abf2668c66065f2

See more details on using hashes here.

File details

Details for the file do_py-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: do_py-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 29.4 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for do_py-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8d85d297c64ed9939f6cd6bf4a452390d658eccc2220d6d76d8880a1040087e9
MD5 ff66062641d52ee47aac857cdceb8a90
BLAKE2b-256 6516431e64751e15500e32e61a52535daa5461b41d294275f3db1ba0beaa95a8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page