Skip to content

Commit

Permalink
Merge pull request #52 from sarugaku/validation-using-dataclasses
Browse files Browse the repository at this point in the history
Validation using dataclasses
  • Loading branch information
oz123 committed Jan 12, 2024
2 parents 4736271 + e493f6a commit 4455574
Show file tree
Hide file tree
Showing 31 changed files with 1,807 additions and 1,159 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", 3.11-dev]
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
os: [ubuntu-latest]

steps:
Expand Down
67 changes: 33 additions & 34 deletions docs/nested.rst
Expand Up @@ -12,46 +12,45 @@ Pipfile and Pipfile.lock, such as individual entries in ``[packages]``,
``[dev-packages]``, and ``lockfile['_meta']``.


The Data View
=============
Base Model
===========

Every non-scalar value you get from Plette (e.g. sequence, mapping) is
represented as a `DataView`, or one of its subclasses. This class is simply a
wrapper around the basic collection class, and you can access the underlying
data structure via the ``_data`` attribute::
represented inherits from `models.BaseModel`, which is a Python `dataclass`::

>>> import plette.models
>>> source = plette.models.Source({
>>> source = plette.models.Source(**{
... 'name': 'pypi',
... 'url': 'https://pypi.org/simple',
... 'verify_ssl': True,
... })
...
>>> source._data
{'name': 'pypi', 'url': 'https://pypi.org/simple', 'verify_ssl': True}


Data View Collections
=====================

There are two special collection classes, ``DataViewMapping`` and
``DataViewSequence``, that hold homogeneous ``DataView`` members. They are
also simply wrappers to ``dict`` and ``list``, respectively, but have specially
implemented magic methods to automatically coerce contained data into a
``DataView`` subclass::

>>> sources = plette.models.SourceCollection([source._data])
>>> sources._data
[{'name': 'pypi', 'url': 'https://pypi.org/simple', 'verify_ssl': True}]
>>> type(sources[0])
<class 'plette.models.sources.Source'>
>>> sources[0] == source
True
>>> sources[0] = {
... 'name': 'devpi',
... 'url': 'http://localhost/simple',
... 'verify_ssl': True,
... }
...
>>> sources._data
[{'name': 'devpi', 'url': 'http://localhost/simple', 'verify_ssl': True}]
>>> source
Source(name='pypi', verify_ssl=True, url='https://pypi.org/simple')


Collections
===========

There a few special collection classes, which can be I dentified by the
suffix ``Collection`` or ``Specifiers``.
They group attributes and behave like ``list`` or ``mappings``.
These classes accept a list of dictionaries as input,
and convert them to the correct object type::

>>> SourceCollection([{'name': 'r-pi', 'url': '192.168.1.129:8000', 'verify_ssl': False}, {'name': 'pypi', 'url': 'https://pypi.org/simple', 'verify_ssl': True}])
SourceCollection(sources=[Source(name='r-pi', verify_ssl=False, url='192.168.1.129:8000'), Source(name='pypi', verify_ssl=True, url='https://pypi.org/simple')])

In addition, they can also accept a list of items of the correct type::

>>> rpi = models.Source(**{'name': 'r-pi', 'url': '192.168.1.129:8000', 'verify_ssl': False})
>>> pypi = models.Source(**{'name': 'pypi', 'url': 'https://pypi.org/simple', 'verify_ssl': True})
>>> SourceCollection([rpi, pypi])
SourceCollection(sources=[Source(name='r-pi', verify_ssl=False, url='192.168.1.129:8000'), Source(name='pypi', verify_ssl=True, url='https://pypi.org/simple')])

They can also be indexed by name, and can be iterated over::

>>> sc = SourceCollection([{'name': 'r-pi', 'url': '192.168.1.129:8000', 'verify_ssl': False}, {'name': 'pypi', 'url': 'https://pypi.org/simple', 'verify_ssl': True}])
>>> sc[0]
Source(name='r-pi', verify_ssl=False, url='192.168.1.129:8000')
54 changes: 1 addition & 53 deletions docs/validation.rst
Expand Up @@ -2,56 +2,4 @@
Validating Data
===============

Plette provides optional validation for input data. This chapter discusses
how validation works.


Setting up Validation
=====================

Validation is provided by the Cerberus_ library. You can install it along with
Plette manually, or by specifying the “validation” extra when installing
Plette:

.. code-block:: none
pip install plette[validation]
Plette automatically enables validation when Cerberus is available.

.. _Cerberus: http://docs.python-cerberus.org/


Validating Data
===============

Data is validated on input (or when a model is loaded). ``ValidationError`` is
raised when validation fails::

>>> plette.models.Source({})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "plette/models/base.py", line 37, in __init__
self.validate(data)
File "plette/models/base.py", line 67, in validate
return validate(cls, data)
File "plette/models/base.py", line 27, in validate
raise ValidationError(data, v)
plette.models.base.ValidationError: {}

This exception class has a ``validator`` member to allow you to access the
underlying Cerberus validator, so you can know what exactly went wrong::

>>> try:
... plette.models.Source({'verify_ssl': True})
... except plette.models.ValidationError as e:
... for error in e.validator._errors:
... print(error.schema_path)
...
('name', 'required')
('url', 'required')

See `Ceberus’s error handling documentation`_ to know how the errors are
represented and reported.

.. _`Ceberus’s error handling documentation`: http://docs.python-cerberus.org/en/stable/errors.html
Validation is no longer an optional feature. All data models are validated.
2 changes: 1 addition & 1 deletion examples/Pipfile.invalid.extras-list
@@ -1,4 +1,4 @@
# package specifiers should be a dict
# package extras should be a list
[packages]
msal = {version= "==1.20.0", extras = "broker"}

2 changes: 2 additions & 0 deletions examples/Pipfile.invalid.with-categories
@@ -1,3 +1,5 @@
# this file contains a section with one package with wrong extras
# extras should be passed as a list
[packages]
plette = { path = '.', extras = ['validation'], editable = true }

Expand Down
6 changes: 6 additions & 0 deletions examples/extras-list/Pipfile
@@ -0,0 +1,6 @@

# extras should be a list
[packages]
msal = {version="==1.20.0", extras=["broker"]}
parver = '*'

0 comments on commit 4455574

Please sign in to comment.