Skip to content

Commit

Permalink
Merge pull request #101 from insightindustry/pydantic-support
Browse files Browse the repository at this point in the history
Adding Pydantic Support
  • Loading branch information
insightindustry committed Feb 17, 2021
2 parents 8e6fed6 + 4e595eb commit becf540
Show file tree
Hide file tree
Showing 27 changed files with 2,284 additions and 57 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -93,6 +93,7 @@ celerybeat-schedule
# virtualenv
.venv
venv/
.py35
.python38
.py38
.python37
Expand Down
6 changes: 4 additions & 2 deletions README.rst
Expand Up @@ -266,8 +266,10 @@ Key SQLAthanor Features
* Customize the validation used when de-serializing particular columns to match
your needs.
* Works with Declarative Reflection and the SQLAlchemy Automap extension.
* Programmatically generate Declarative Base Models from serialized data.
* Programmatically generate SQLAlchemy ``Table`` objects from serialized data.
* Programmatically generate Declarative Base Models from serialized data or Pydantic
models.
* Programmatically generate SQLAlchemy ``Table`` objects from serialized data or Pydantic
models.


**SQLAthanor** vs Alternatives
Expand Down
3 changes: 3 additions & 0 deletions docs/_import_sqlathanor.rst
Expand Up @@ -164,3 +164,6 @@ The table below shows how `SQLAlchemy`_ classes and functions map to their
.. code-block:: python
from sqlathanor.automap import automap_base
.. _SQLAlchemy: http://www.sqlalchemy.org
.. _Flask-SQLAlchemy: http://flask-sqlalchemy.pocoo.org/2.3/
69 changes: 69 additions & 0 deletions docs/_versus_alternatives.rst
Expand Up @@ -28,6 +28,72 @@ it might be helpful to compare **SQLAthanor** to some commonly-used alternatives
find that I never really roll my own serialization/de-serialization approach
when working `SQLAlchemy`_ models any more.

.. tab:: Pydantic

.. tip::

Because `Pydantic`_ is growing in popularity, we have decided to integrate `Pydantic`_
support within **SQLAthanor**.

Using
:func:`generate_model_from_pydantic() <sqlathanor.declarative.generate_model_from_pydantic>`,
you can now programmatically generate a **SQLAthanor**
:class:`BaseModel <sqlathanor.declarative.BaseModel>` from your
:term:`Pydantic models <Pydantic Model>`.

This allows you to only maintain *one* representation of your data model (the
`Pydantic`_ one), while still being able to use SQLAthanor's rich serialization /
de-serialization configuration functionality.

`Pydantic`_ is an amazing object parsing library that leverages native Python typing
to provide simple syntax and rich functionality. While I have philosophical quibbles
about some of its API semantics and architectural choices, I cannot deny that it is
elegant, extremely performant, and all around excellent.

Since `FastAPI`_, one of the fastest-growing web application frameworks in the Python
ecosystem is tightly coupled with `Pydantic`_, it has gained significant ground within
the community.

However, when compared to **SQLAthanor** it has a number of architectural limitations:

While `Pydantic`_ has excellent serialization and deserialization functionality to
JSON, it is extremely limited with its serialization/deserialization support for other
common data formats like CSV or YAML.

Second, by its design `Pydantic`_ forces you to maintain **multiple** representations
of your data model. On the one hand, you will need your `SQLAlchemy`_ ORM
representation, but then you will *also* need one or more `Pydantic`_ models that will
be used to serialize/de-serialize your model instances.

Third, by its design, `Pydantic`_ tends to lead to significant amounts of duplicate
code, maintaining similar-but-not-quite-identical versions of your data models (with
one `Pydantic`_ schema for each context in which you might serialize/de-serialize your
data model).

Fourth, its API semantics can get extremely complicated when trying to use it as a
true serialization/de-serialization library.

While `Pydantic`_ has made efforts to integrate ORM support into its API, that
functionality is relatively limited in its current form.

.. tip::

**When to use it?**

`Pydantic`_ is easy to use when building simple web applications due to its
reliance on native Python typing. The need to maintain multiple representations
of your data models is a trivial burden with small applications or relatively
simple data models.

`Pydantic`_ is also practically required when building applications using the
excellent `FastAPI`_ framework.

So given these two things, we recommend using `Pydantic`_ *in combination* with
**SQLAthanor** to get the best of both words: native Python typing for validation
against your Python model (via `Pydantic`_) with rich configurable
serialization/de-serialization logic (via **SQLAthanor**), all integrated into
the underlying `SQLAlchemy`_ ORM.

.. tab:: Marshmallow

The `Marshmallow`_ library and its `Marshmallow-SQLAlchemy`_ extension are
Expand Down Expand Up @@ -161,6 +227,9 @@ it might be helpful to compare **SQLAthanor** to some commonly-used alternatives

.. _Marshmallow: https://marshmallow.readthedocs.io/en/3.0/
.. _Marshmallow-SQLAlchemy: https://marshmallow-sqlalchemy.readthedocs.io/en/latest/
.. _Pydantic: https://pydantic-docs.helpmanual.io/
.. _FastAPI: https://fastapi.tiangolo.com/
.. _Colander: https://docs.pylonsproject.org/projects/colander/en/latest/
.. _ColanderAlchemy: https://colanderalchemy.readthedocs.io/en/latest/
.. _pandas: http://pandas.pydata.org/
.. _SQLAlchemy: http://www.sqlalchemy.org
11 changes: 11 additions & 0 deletions docs/api.rst
Expand Up @@ -79,6 +79,13 @@ generate_model_from_dict()

----------------------------------------

generate_model_from_pydantic()
-------------------------------------

.. autofunction:: generate_model_from_pydantic

--------------------------------------------------------

.. module:: sqlathanor.schema

Schema
Expand Down Expand Up @@ -197,6 +204,8 @@ Table

.. automethod:: Table.from_yaml

.. automethod:: Table.from_pydantic

----------------------------

.. module:: sqlathanor.attributes
Expand All @@ -220,6 +229,8 @@ AttributeConfiguration

.. automethod:: AttributeConfiguration.__init__

.. automethod:: from_pydantic_model

----------------------

validate_serialization_config()
Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Expand Up @@ -227,10 +227,11 @@
intersphinx_mapping = {
'python': ('https://docs.python.org/3.6', None),
'python27': ('https://docs.python.org/2.7', None),
'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
'sqlalchemy': ('http://docs.sqlalchemy.org/en/13/', None),
'simplejson': ('http://simplejson.readthedocs.io/en/latest/', None),
'validator-collection': ('http://validator-collection.readthedocs.io/en/latest/', None),
'flask_sqlalchemy': ('http://flask-sqlalchemy.pocoo.org/2.3/', None),
#'pydantic': ('https://pydantic-docs.helpmanual.io/', None),
}

# -- Options for todo extension ----------------------------------------------
Expand Down
37 changes: 37 additions & 0 deletions docs/glossary.rst
Expand Up @@ -31,6 +31,22 @@ Glossary
with fields (columns) separated by a delimiter character (typically a comma
``,`` or pipe ``|``).

Configuration Set
A named set of attribute configurations which determine which :term:`model class`
attributes get :term:`serialized <Serialization>` /
:term:`de-serialized <De-serialization>` under given circumstances (when indicating
the configuration set during the serialization / de-serialization operation).

.. tip::

Think of a configuration set as a set of "rules" that determine what gets
processed when serializing or de-serializing a :term:`model class`.

.. note::

It is possible for a single :term:`model class` to have many different configuration
sets, so long as each set has a unique name.

Declarative Configuration
A way of configuring :term:`serialization` and :term:`de-serialization`
for particular :term:`model attributes <model attribute>` when defining
Expand Down Expand Up @@ -187,6 +203,27 @@ Glossary
module from the standard Python library, or an outside pickling library like
`dill <https://github.com/uqfoundation/dill>`_.

Pydantic Model
A Pydantic Model is a representation of a class which contains data and some
attributes that inherits from the
:class:`pydantic.BaseModel <pydantic:pydantic.main.BaseModel>` class. This model is
used by the `Pydantic <https://pydantic-docs.helpmanual.io>`_ library to either
validate the typing of data upon deserialization or to serialize data to appropriate
types when needed.

By definition, each Pydantic model is self-contained (though they may inherit across
models). Pydantic models are inherently reliant on Python's native typing support,
relying on type hints and annotations to provide the canonical instructions against
which to validate or based on which to serialize.

.. seealso::

* :doc:`SQLAthanor and Pydantic <pydantic>`
* :func:`generate_model_from_pydantic() <sqlathanor.declarative.generate_model_from_pydantic>`
* :meth:`Table.from_pydantic() <sqlathanor.schema.Table.from_pydantic>`
* :meth:`AttributeConfiguration.from_pydantic_model() <sqlathanor.attributes.AttributeConfiguration.from_pydantic_model>`


Relationship
A connection between two database tables or their corresponding
:term:`model classes <model class>` defined using a foreign key constraint.
Expand Down
5 changes: 3 additions & 2 deletions docs/index.rst
Expand Up @@ -42,6 +42,7 @@ SQLAthanor
Home <self>
Quickstart: Patterns and Best Practices <quickstart>
Using SQLAthanor <using>
SQLAthanor and Pydantic <pydantic>
API Reference <api>
Default Serialization Functions <default_serialization_functions>
Default De-serialization Functions <default_deserialization_functions>
Expand Down Expand Up @@ -175,9 +176,9 @@ Key SQLAthanor Features
your needs.
* Works with :ref:`Declarative Reflection <using_reflection>` and the
:ref:`Automap Extension <using_automap>`.
* Programmatically :ref:`generate Declarative Base Models from serialized data <defining_models>`.
* Programmatically :ref:`generate Declarative Base Models from serialized data or Pydantic models <defining_models>`.
* Programmatically create :ref:`SQLAlchemy Table objects <generating_tables>` from
serialized data.
serialized data or :term:`Pydantic models <Pydantic Model>`.

|
Expand Down

0 comments on commit becf540

Please sign in to comment.