Marshmallow Schema subclass that auto-defines fields based on SQLAlchemy classes
Project description
golden-marshmallows
A better integration between SQLAlchemy and Marshmallow. A little (SQL)alchemy to turn marshmallow
s into gold.
Note: The default unknown field handling has been defaulted to EXCLUDE
so it handles
closer to Marshmallow v2.
Installation
Simply install with pip
:
$ pip install golden-marshmallows
Usage
Serialization
Take these SQLAlchemy models as examples:
class WizardCollege(Base):
__tablename__ = 'wizard_college'
id = Column(Integer, primary_key=True)
name = Column(String)
alchemists = relationship('Alchemist')
def __repr__(self):
return '<WizardCollege(name={self.name!r})>'.format(self=self)
class Alchemist(Base):
__tablename__ = 'alchemists'
id = Column(Integer, primary_key=True)
name = Column(String)
school_id = Column(Integer, ForeignKey('wizard_college.id'))
formulae = relationship('Formula')
def __repr__(self):
return '<Alchemist(name={self.name!r})>'.format(self=self)
class Formula(Base):
__tablename__ = 'forumulae'
id = Column(Integer, primary_key=True)
title = Column(String)
author_id = Column(Integer, ForeignKey('alchemists.id'))
def __repr__(self):
return '<Formula(title={self.title!r})>'.format(self=self)
The GoldenSchema
class allows quick and easy generation of marshmallow
schemas that can be used for SQLAlchemy object serialization/deserialization. Simply pass the model class on initialization and you're ready to go:
import json
from golden_marshmallows.schema import GoldenSchema
from models import Alchemist, Formula, WizardCollege
alchemist = Alchemist(name='Albertus Magnus', school_id=1)
session.add(alchemist)
session.flush()
schema = GoldenSchema(Alchemist)
serialized = schema.dump(alchemist)
print(json.dump(serialized, indent=4))
# {
# "id": 1,
# "name": "Albertus Magnus",
# "school_id": 1
# }
That's it! No need to define your own Schema
subclass, unless you really want to (more on that below).
Nested objects
But what about this alchemist's formulae? Nested objects can easily be added to the mix by passing in a dictionary mapping each field that contains a nested object (or objects) to the relevant SQLAlchemy class:
nested_map = {
'formulae': {
'class': Formula,
'many': True
}
}
formula = Formula(title='transmutation')
alchemist.formulae.append(formula)
session.commit()
schema = GoldenSchema(Alchemist, nested_map=nested_map)
serialized = schema.dump(alchemist)
print(json.dump(serialized, indent=4))
# {
# "id": 1,
# "name": "Albertus Magnus",
# "school_id": 1,
# "formulae" : [
# {
# "title": "transmutation"
# }
# ]
# }
In fact, the GoldenSchema
class supports arbitrary nesting in this fashion, simply adjust the map as necessary:
nested_map = {
'alchemists': {
'class': Alchemist,
'many': True,
'nested_map': {
'formulae': {
'class': Formula,
'many': True
}
}
}
}
college = WizardCollege(name='Bogwarts')
college.alchemists.append(alchemist)
session.add(college)
session.flush()
schema = GoldenSchema(WizardCollege, nested_map=nested_map)
serialized = schema.dump(college)
print(json.dump(serialized, indent=4))
# {
# "id": 1,
# "name": "Bogwarts",
# "alchemists": [
# {
# "id": 1,
# "school_id": 1,
# "name": "Albertus Magnus",
# "formulae": [
# {
# "title": "transmutation",
# "author_id": 1,
# "id": 1
# }
# ]
# }
# ]
# }
You may need more control over the GoldenSchema
instances that are nested into your top-level schema in the nested_map
parameter. If that's the case, you can simply create a nested GoldenSchema
instance and pass it in directly like so:
from marshmallow.fields import List, String
FormulaSchema = GoldenSchema(Formula)
class FormulaSchemaWithIngredients(FormulaSchema):
ingredients = List(String())
nested_map = {
'formulae': {
'class': FormulaSchemaWithIngredients,
'many': True
}
}
alchemist = session.query(Alchemist).first()
formula = alchemist.formulae[0]
formula.ingredients = ['lead', 'magic']
schema = GoldenSchema(Alchemist, nested_map=nested_map)
serialized = schema.dump(alchemist)
print(json.dump(serialized, indent=4))
# {
# "id": 1,
# "name": "Albertus Magnus",
# "school_id": 1,
# "formulae" : [
# {
# "title": "transmutation",
# "ingredients": [
# "lead",
# "magic"
# ]
# }
# ]
# }
Deserialization
Of course, you can deserialize data into SQLAlchemy objects just as easily:
# Start at the end of the last example and work backwards
data = {
"id": 1,
"name": "Bogwarts",
"alchemists": [
{
"formulae": [
{
"title": "transmutation",
"author_id": 1,
"id": 1
}
],
"school_id": 1,
"id": 1,
"name": "Albertus Magnus"
}
]
}
college = schema.load(data)
print(college)
# <WizardCollege(name='Bogwarts')>
print(college.alchemists)
# [<Alchemist(name='Albertus Magnus')>]
print(college.alchemists[0].formulae)
# [<Formula(title='transmutation')>]
Extra Features
camelCasing/snake_casing
The snake_to_camel
flag allows serde to/from camelCase, for example when serializing Python data into JSON to send as an API Response:
# `Formula.author_id` is easily converted to camelCase
schema = GoldenSchema(Formula, snake_to_camel=True)
serialized = schema.dump(formula)
print(json.dumps(serialized, indent=4))
# Notice `author_id` has become `authorId`
# {
# "title": "transmutation",
# "authorId": 1,
# "id": 1
# }
The same GoldenSchema
instance, when used to load
(deserialize) data, will expect camelCased attributes and load them as snake_cased attributes:
data = {
"title": "transmutation",
"authorId": 1,
"id": 1
}
formula = schema.load(data)
print(formula.author_id)
# 1
A flag with the opposite behavior, camel_to_snake
, is also included.
This feature also works for manually declared fields; that is, fields you yourself declare when subclassing GoldenSchema
like so:
class MySchema(GoldenSchema):
manually_declared = fields.Function(lambda obj: 'my special value')
my_schema = MySchema(Formula, snake_to_camel=True)
serialized = schema.dump(formula)
print(json.dumps(serialized, indent=4))
# `manually_declared` has become camelCase
# {
# "title": "transmutation",
# "authorId": 1,
# "id": 1,
# "manuallyDeclared": "my special value"
# }
In fact, you can use this feature without involving SQLAlchemy at all; just use CaseChangingSchema
, the parent class of GoldenSchema
:
from golden_marshmallows.schema import CaseChangingSchema
class SnakeSchema(CaseChangingSchema):
attr_one = fields.String()
attr_two = fields.Integer()
class SnakeObj:
def __init__(self, attr_one, attr_two):
self.attr_one = attr_one
self.attr_two = attr_two
schema = SnakeSchema(snake_to_camel=True)
obj = SnakeObj('field1', 2)
serialized = schema.dump(obj)
print(json.dumps(serialized, indent=4))
# {
# 'attrOne': 'field1',
# 'attrTwo': 2
# }
Copying objects
As a minor convenience, you can pass the new_obj
flag on initialization to indicate that any fields named id
should be ignored during deserialization:
schema = GoldenSchema(Formula, snake_to_camel=True, new_obj=True)
data = {
"title": "transmutation",
"authorId": 1,
"id": 1
}
new_formula = schema.load(data)
print(new_formula.title)
# 'transmutation'
print(new_formula.id) # None
This allows you to quickly deserialize data representations of existing objects into new copies.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file golden_marshmallows-1.0.0.tar.gz
.
File metadata
- Download URL: golden_marshmallows-1.0.0.tar.gz
- Upload date:
- Size: 8.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/47.1.0 requests-toolbelt/0.9.1 tqdm/4.49.0 CPython/3.8.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1e61dc795be4bf43fc50ba6ec8d67a32c275ba42bd3b52350cfe79359b1faf22 |
|
MD5 | 035bbf58d93f1af3a6a2148864e7354f |
|
BLAKE2b-256 | 7a71048e43b7a735a7c79f44220b8d19f41913a5ceb516c93d7709aa37ec4000 |
File details
Details for the file golden_marshmallows-1.0.0-py3-none-any.whl
.
File metadata
- Download URL: golden_marshmallows-1.0.0-py3-none-any.whl
- Upload date:
- Size: 7.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/47.1.0 requests-toolbelt/0.9.1 tqdm/4.49.0 CPython/3.8.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | d8f131e1d69c624e1e372287f6e85f66a0bdc9215b60331d5d0f86b419f6dbe1 |
|
MD5 | e67d9a3a2761c9a8fbeda68080e944af |
|
BLAKE2b-256 | 0c242e42344530dfc8f4a90d466fca887600f44ec0f76bac49366147730aeafa |