# Do-Py
Do-Py, shorthand for DataObject Python, is a data-validation and standardization library wrapping Python dictionaries.
![release](https://img.shields.io/github/package-json/v/do-py-together/do-py?label=release&logo=release&style=flat-square)
![build](https://img.shields.io/github/workflow/status/do-py-together/do-py/test?style=flat-square)
![coverage](https://img.shields.io/codecov/c/github/do-py-together/do-py?style=flat-square)
![dependencies](https://img.shields.io/librariesio/release/pypi/do-py?style=flat-square)
##### Project milestones
![Beta ready](https://img.shields.io/github/milestones/progress/do-py-together/do-py/1?label=Issues%20until%20Beta&style=flat-square)
![Stable ready](https://img.shields.io/github/milestones/progress/do-py-together/do-py/2?label=Issues%20until%20Stable&style=flat-square)
## 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.
```python
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.
```python
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`.
```python
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.
```python
from do_py import DataObject, R
class Contact(DataObject):
_restrictions = {
'phone_number'
}
class Author(DataObject):
"""
A DataObject that contains all of my favorite items.
:restriction id:
: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!
"""
_restrictions = {
'id': R.INT,
'name': R.STR,
'contact': Contact
}
class VideoGame(DataObject):
"""
A DataObject that contains all of my favorite items.
:restriction id:
: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!
"""
_restrictions = {
'id': R.INT,
'name': R.NULL_STR,
'author': Author
}
# Data objects must be instantiated at their **init** with a dictionary and
# strict(True(default) or False)
instance = VideoGame({
'favorite_number': 1985,
'favorite_candy': 'Jolly Ranchers',
'favorite_movie': 'Jolly Green Giant'
})
print(instance)
```
### Nest a list of DataObjects in another DataObject.
```python
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.
```python
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.'
```
Raw data
{
"_id": null,
"home_page": "https://github.com/do-py-together/do-py",
"name": "do-py",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "development,OO",
"author": "Tim Davis",
"author_email": "timdavis.3991@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/e3/82/ab7853338419f8bcdf4e994f9856c552f356a9c55239226e4ab693ef518a/do-py-0.4.1.tar.gz",
"platform": null,
"description": "# Do-Py\nDo-Py, shorthand for DataObject Python, is a data-validation and standardization library wrapping Python dictionaries.\n\n![release](https://img.shields.io/github/package-json/v/do-py-together/do-py?label=release&logo=release&style=flat-square)\n![build](https://img.shields.io/github/workflow/status/do-py-together/do-py/test?style=flat-square)\n![coverage](https://img.shields.io/codecov/c/github/do-py-together/do-py?style=flat-square)\n![dependencies](https://img.shields.io/librariesio/release/pypi/do-py?style=flat-square)\n\n##### Project milestones\n\n![Beta ready](https://img.shields.io/github/milestones/progress/do-py-together/do-py/1?label=Issues%20until%20Beta&style=flat-square)\n![Stable ready](https://img.shields.io/github/milestones/progress/do-py-together/do-py/2?label=Issues%20until%20Stable&style=flat-square)\n\n## Quick-Start\n\n### Make a basic DataObject.\nWe will make a class and call it `MyFavoriteStuff`. We\nwill inherit the DataObject class to gain all its wonderful features.\nHere you can see we must define the '_restrictions' attribute.\n```python\nfrom do_py import DataObject, R\n\n\nclass MyFavoriteStuff(DataObject):\n \"\"\"\n A DataObject that contains all of my favorite items.\n :restriction favorite_number: The number I favor the most. Strings not allowed.\n :restriction favorite_candy: My favorite candy, this is restricted by value.\n :restriction favorite_movie: My favorite movie. This is optional because a `None` IS allowed!\n \"\"\"\n # There are two kinds of restrictions, type and value.\n _restrictions = {\n # Type restrictions restrict the type a value can have: int, str, bool, or other DataObjects's\n 'favorite_number': R.INT,\n # Value restrictions restrict the value to a specific value in a list.\n 'favorite_candy': R('Jolly Ranchers', 'Nerds'),\n # This is a type restriction that allows `None` as a value.\n 'favorite_movie': R.NULL_STR\n }\n\n\n# Instantiate your new DataObject.\ninstance = MyFavoriteStuff({\n 'favorite_number': 1985,\n 'favorite_candy': 'Jolly Ranchers',\n 'favorite_movie': 'Jolly Green Giant'\n })\n\nprint(instance)\n# output: MyFavoriteStuff{\"favorite_candy\": \"Jolly Ranchers\", \"favorite_number\": 1985, \"favorite_movie\": \"Jolly Green Giant\"}\n\n# You can access values using dot notation or like a `dict`.\nprint(instance.favorite_number == instance['favorite_number'])\n# output: True\n\nprint(instance.favorite_number)\nprint(instance.favorite_candy)\nprint(instance.favorite_movie)\n# output: 1985\n# output: Jolly Ranchers\n# output: Jolly Green Giant\n\n# Editing the values can also be done very easily.\ninstance.favorite_number = 2013\nprint(instance.favorite_number)\n# output: 2013\n```\n\n\n### Using restrictions.\n\nRestrictions are written using `do_py.R`. `R` allows developers to define custom value restrictions as well as type\nrestrictions using the special shortcuts. Here are a few examples of how you can write value restrictions and type\nrestrictions using the type short-cuts.\n```python\nfrom do_py import DataObject, R\n\n\nclass TypeShorCuts(DataObject):\n \"\"\"\n All of the restrictions written for this DataObject us R's type shortcuts.\n \"\"\"\n _restrictions = {\n # integer\n 'int': R.INT,\n 'nullable_int': R.NULL_INT,\n # string\n 'str': R.STR,\n 'nullable_str': R.NULL_STR,\n # bool\n 'bool': R.BOOL,\n # date and datetime\n 'date': R.DATE,\n 'nullable_date': R.NULL_DATE,\n 'datetime': R.DATETIME,\n 'nullable_datetime': R.NULL_DATETIME,\n # other (these are rarely used(aqw\n 'set': R.SET,\n 'list': R.LIST,\n }\n\n\nclass ValueRestrictions(DataObject):\n \"\"\"\n All of the restrictions for this class are value restrictions.\n \"\"\"\n _restrictions = {\n # number values\n 'integers': R(1, 2, 3),\n 'integers and None': R(1, 2, 3, None),\n # string values\n 'strings': R('hello', 'hi', 'sup'),\n 'nullable_strings': R('hello', 'hi', 'sup', None),\n }\n```\n\n\n### Give the DataObject default values.\nDataObjects are able to define the default value for their restrictions. If a developer is not sure\nif a value will be available, defaults are a very useful utility. We have updated the original example to have\na default value for it's restriction `favorite_candy.`\n\nIn order to use the default value when instantiating a DataObject, we must instantiate it in non-strict mode.\n\nStrict instantiation is used by default. In strict instantiation, the data passed in must contain all the\nkeys defined in the DataObject's `_restrictions`.\n\nWith non-strict initialization, it is acceptable to have some keys missing per DO _restrictions. For all missing keys,\nthe default restriction value is used. This section provides an example of using a DataObject in non-strict mode\nso that we can use the default values for `favorite_candy`.\n```python\nfrom do_py import DataObject, R\n\n\nclass MyFavoriteStuff(DataObject):\n \"\"\"\n :restriction favorite_number: The default value is 1.\n :restriction favorite_candy: The default value is is \"Unknown\".\n :restriction favorite_movie: When nullable, the default value is `None`.\n \"\"\"\n _restrictions = {\n 'favorite_number': R.INT.with_default(1),\n 'favorite_candy': R('Jolly Ranchers', 'Nerds', 'Unknown', default='Unknown'),\n 'favorite_movie': R.NULL_STR\n }\n\n\n# In order to use the default value when instantiating a DataObject, we must instantiate it in non-strict mode.\n# Any values that are not provided will use defaults.\ninstance = MyFavoriteStuff({}, strict=False)\n\nprint(instance)\n# output: MyFavoriteStuff{\"favorite_candy\": \"Unknown\", \"favorite_number\": 1, \"favorite_movie\": null}\n```\n\n\n### Nest a DataObject in another DataObject.\n```python\nfrom do_py import DataObject, R\n\n\nclass Contact(DataObject):\n _restrictions = {\n 'phone_number'\n }\n\n\nclass Author(DataObject):\n \"\"\"\n A DataObject that contains all of my favorite items.\n :restriction id:\n :restriction favorite_candy: My favorite candy, this is restricted by value.\n :restriction favorite_movie: My favorite movie. This is optional because a `None` IS allowed!\n \"\"\"\n _restrictions = {\n 'id': R.INT,\n 'name': R.STR,\n 'contact': Contact\n }\n\n\nclass VideoGame(DataObject):\n \"\"\"\n A DataObject that contains all of my favorite items.\n :restriction id:\n :restriction favorite_candy: My favorite candy, this is restricted by value.\n :restriction favorite_movie: My favorite movie. This is optional because a `None` IS allowed!\n \"\"\"\n _restrictions = {\n 'id': R.INT,\n 'name': R.NULL_STR,\n 'author': Author\n }\n\n\n# Data objects must be instantiated at their **init** with a dictionary and\n# strict(True(default) or False)\ninstance = VideoGame({\n 'favorite_number': 1985,\n 'favorite_candy': 'Jolly Ranchers',\n 'favorite_movie': 'Jolly Green Giant'\n })\n\nprint(instance)\n```\n\n\n### Nest a list of DataObjects in another DataObject.\n```python\nfrom do_py import DataObject, R\nfrom do_py.common.managed_list import ManagedList\n\n\nclass Book(DataObject):\n \"\"\"\n There are multiple books in the library!\n :restriction name: Name of the book.\n :restriction author: The author of the book.\n \"\"\"\n _restrictions = {\n 'name': R.STR,\n 'author': R.STR,\n }\n\n\nclass Library(DataObject):\n \"\"\"\n This DataObject represents a library which contains multiple books.\n :restriction city: The city the library is located in.\n :restriction books: A list of instances of the DataObject \"Book\".\n \"\"\"\n _restrictions = {\n 'city': R.STR,\n 'books': ManagedList(Book)\n }\n```\n\n\n\n## What is a DataObject?\n\nA DataObject allows us to create Python classes that have strictly defined fields called \"restrictions\". Restrictions\nare defined for a DataObject using the `_restriction` attribute. See the Quick-start section.\n\nThere are two kinds of restrictions, type and value:\n* Value restrictions restrict the value to a specific value in a list.\n* Type restrictions restrict the type a value can have: int, str, bool, or other DataObjects.\n\n## Advanced Uses\n\n### Advanced DataObject validations.\n\nCertain use-cases require more complex validations or restrictions that cannot be supported without code execution.\nThe parent class `Validator` allows us to execute code at instantiation and any time a key is updated. A child of\n`Validator` is required to define a `_validate` instance method.\n```python\nfrom do_py import R\nfrom do_py.data_object.validator import Validator\n\n\nclass Validated(Validator):\n \"\"\"\n This DataObject validates that we only have one of key or id, but not both. Since this can't be accomplished only\n using restrictions, we are inheriting from `Validator` so we can attach extra validations.\n \"\"\"\n _restrictions = {\n 'key': R.NULL_STR,\n 'id': R.NULL_INT\n }\n\n def _validate(self):\n \"\"\"\n Validate that we have exactly one of key or id.\n\n This function runs at instantiation and any time the instance is updated.\n \"\"\"\n assert any([self.key, self.id]) and not all([self.key, self.id]), \\\n 'We need exactly one of id or key to not be None.'\n```\n\n\n",
"bugtrack_url": null,
"license": "",
"summary": "Data validation and standardization library wrapping Python dictionaries.",
"version": "0.4.1",
"project_urls": {
"Homepage": "https://github.com/do-py-together/do-py"
},
"split_keywords": [
"development",
"oo"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "375e44aec4469865cf7078f5d942c9835ae86915926880a1a0e98e2e7d5af139",
"md5": "f4ff8af5f933859549979e5e39019402",
"sha256": "d554a2d5ecaa63a3fd392a2c87b04cb8f0cf1df9d355973ae97d2a3e79227fd4"
},
"downloads": -1,
"filename": "do_py-0.4.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f4ff8af5f933859549979e5e39019402",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 29461,
"upload_time": "2023-05-20T14:13:23",
"upload_time_iso_8601": "2023-05-20T14:13:23.568946Z",
"url": "https://files.pythonhosted.org/packages/37/5e/44aec4469865cf7078f5d942c9835ae86915926880a1a0e98e2e7d5af139/do_py-0.4.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "e382ab7853338419f8bcdf4e994f9856c552f356a9c55239226e4ab693ef518a",
"md5": "56462b02ffc77a9361720fd96e4c5614",
"sha256": "1afc307e5c50607d8eb4877fbeccab3a8339289eeae649ca1d368029e8529e9d"
},
"downloads": -1,
"filename": "do-py-0.4.1.tar.gz",
"has_sig": false,
"md5_digest": "56462b02ffc77a9361720fd96e4c5614",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 27081,
"upload_time": "2023-05-20T14:13:26",
"upload_time_iso_8601": "2023-05-20T14:13:26.038860Z",
"url": "https://files.pythonhosted.org/packages/e3/82/ab7853338419f8bcdf4e994f9856c552f356a9c55239226e4ab693ef518a/do-py-0.4.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-05-20 14:13:26",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "do-py-together",
"github_project": "do-py",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "do-py"
}