Generic schema with full typing support and minimal boilerplate
Go to file
Daniil Fajnberg 3f0e3db427
👷 Use own workflow for creating and publishing new releases
2023-04-26 17:04:12 +02:00
.github/workflows 👷 Use own workflow for creating and publishing new releases 2023-04-26 17:04:12 +02:00
docs 👨‍💻 Explicitly (re-)export in the package `__init__.py` (including `marshmallow` exports) 2023-03-13 18:54:18 +01:00
requirements 📌 Restrict main dependency `marshmallow>=3.12.0`; 2023-04-21 15:47:03 +02:00
scripts 🔨 Rework and refactor development scripts 2023-04-21 15:09:09 +02:00
src/marshmallow_generic 🚨 Set explicit `stacklevel` when warning about schema-wide `many` setting depending on initialization state. 2023-04-21 15:52:33 +02:00
tests ♻️ Move the schema-wide `many` warning to `__setattr__` instead 2023-03-13 15:48:05 +01:00
.gitignore 🔧 Store miscellaneous dev cache files in their own directory 2023-03-13 18:35:14 +01:00
LICENSE 🎉 Initial commit 2023-03-11 16:23:21 +01:00
README.md 🎉 Initial commit 2023-03-11 16:23:21 +01:00
mkdocs.yaml 🔧 Show more function signature details in documentation 2023-03-13 00:10:37 +01:00
pyproject.toml 📌 Restrict main dependency `marshmallow>=3.12.0`; 2023-04-21 15:47:03 +02:00

README.md

marshmallow-generic

Generic schema with full typing support and minimal boilerplate


Documentation: daniil-berg.github.io/marshmallow-generic

Source Code: github.com/daniil-berg/marshmallow-generic


Extension for marshmallow to make deserialization to objects easier and improve type safety.

The main GenericSchema class extends marshmallow.Schema making it generic in terms of the class that data should be deserialized to, when calling load/loads.

With GenericSchema there is no need to explicitly write post_load hooks to initialize the object anymore. 🎉

If the "model" class is (for example) User, it just needs to be passed as the type argument, when subclassing GenericSchema. The output of the load/loads method will then be automatically inferred as either User or list[User] (depending on whether many is True or not) by any competent type checker.

Usage Example

from marshmallow_generic import GenericSchema, fields


class User:
    def __init__(self, name: str, email: str) -> None:
        self.name = name
        self.email = email

    def __repr__(self) -> str:
        return "<User(name={self.name!r})>".format(self=self)

...

class UserSchema(GenericSchema[User]):
    name = fields.Str()
    email = fields.Email()


user_data = {"name": "Monty", "email": "monty@python.org"}
schema = UserSchema()
single_user = schema.load(user_data)
print(single_user)  # <User(name='Monty')>

json_data = '''[
    {"name": "Monty", "email": "monty@python.org"},
    {"name": "Ronnie", "email": "ronnie@stones.com"}
]'''
multiple_users = schema.loads(json_data, many=True)
print(multiple_users)  # [<User(name='Monty')>, <User(name='Ronnie')>]

Adding reveal_type(single_user) and reveal_type(multiple_users) at the bottom and running that code through mypy would yield the following output:

# note: Revealed type is "User"
# note: Revealed type is "builtins.list[User]"

With the regular marshmallow.Schema, the output of mypy would instead be this:

# note: Revealed type is "Any"
# note: Revealed type is "Any"

This also means your IDE will be able to infer the types and thus provide useful auto-suggestions for the loaded objects. 👨‍💻

Here is PyCharm with the example from above:

Image title

Installation

pip install marshmallow-generic

Dependencies

Python Version 3.9+ and marshmallow (duh)