Skip to content

Commit

Permalink
Merge branch 'master' into validation-using-pure-python
Browse files Browse the repository at this point in the history
  • Loading branch information
oz123 committed Apr 19, 2024
2 parents c6b3bf3 + a23ddeb commit 24442fb
Show file tree
Hide file tree
Showing 28 changed files with 2,168 additions and 629 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
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 All @@ -25,5 +25,5 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Run Tests
run: |
pip install -e .[tests]
pip install -e .[tests,validation]
pytest -v .
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
1.0.0 (2024-04-04)
==================
- Rewrite the entire library using dataclasses.
- Add more rigorous testing.

0.4.4 (2022-12-15)
==================

Expand Down
655 changes: 376 additions & 279 deletions Pipfile.lock

Large diffs are not rendered by default.

8 changes: 1 addition & 7 deletions docs/files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ This method takes a file-like object to load the file::
>>> with open('Pipfile', encoding='utf-8') as f:
... pipfile = plette.Pipfile.load(f)

.. warning::

This will not work for Python 2, since the loader is very strict about
file encodings, and only accepts a Unicode file. You are required to use
``io.open()`` to open the file instead.

For manipulating a binary file (maybe because you want to interact with a
temporary file created via ``tempfile.TemporaryFile()``), ``load()`` accepts
a second, optional argument::
Expand Down Expand Up @@ -50,5 +44,5 @@ implementation, i.e.::
an optional ``encoding`` argument. If set, the output would be in bytes,
encoded with the specified encoding.

Both the Pipfile and Pipfile.lock are guarenteed to be dumped with a trailing
Both the Pipfile and Pipfile.lock are guaranteed to be dumped with a trailing
newline at the end.
71 changes: 35 additions & 36 deletions docs/nested.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,49 @@ type conversion for you. Sometimes, however, you would want to peek under the
hood. This chapter discusses how you can handle those structures yourself.

The ``plette.models`` submodule contains definitions of nested structures in
Pipfile and Pipfile.lock, such as indivisual entries in ``[packages]``,
Pipfile and Pipfile.lock, such as individual entries in ``[packages]``,
``[dev-packages]``, and ``lockfile['_meta']``.


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

Every non-scalar valuea 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 strucuture via the ``_data`` attribute::
Every non-scalar value you get from Plette (e.g. sequence, mapping) is
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')
2 changes: 1 addition & 1 deletion docs/sections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,5 @@ The automatically generated source contains the following data
.. warning::

You *can* delete either the automatically generated source, or the source
section itself from the model after it is aloaded. Plette assumes you know
section itself from the model after it is loaded. Plette assumes you know
what you’re doing.
54 changes: 1 addition & 53 deletions docs/validation.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

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

Loading

0 comments on commit 24442fb

Please sign in to comment.