Skip to main content

golden-marshmallows

A better integration between SQLAlchemy and Marshmallow. A little (SQL)alchemy to turn marshmallows 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.

Metadata

Release files for golden-marshmallows 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for golden-marshmallows 1.0.0
File Size Uploaded
golden_marshmallows-1.0.0.tar.gz 8.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for golden-marshmallows 1.0.0
File Interpreter ABI Platform
golden_marshmallows-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 15.9 kB

Release files / golden_marshmallows-1.0.0.tar.gz

Download URL golden_marshmallows-1.0.0.tar.gz
Size 8.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1e61dc795be4bf43fc50ba6ec8d67a32c275ba42bd3b52350cfe79359b1faf22
BLAKE2b-256 checksum
How to use checksums
7a71048e43b7a735a7c79f44220b8d19f41913a5ceb516c93d7709aa37ec4000
Upload date
Uploaded using Trusted Publishing?
What is 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

Release files / golden_marshmallows-1.0.0-py3-none-any.whl

Download URL golden_marshmallows-1.0.0-py3-none-any.whl
Size 7.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d8f131e1d69c624e1e372287f6e85f66a0bdc9215b60331d5d0f86b419f6dbe1
BLAKE2b-256 checksum
How to use checksums
0c242e42344530dfc8f4a90d466fca887600f44ec0f76bac49366147730aeafa
Upload date
Uploaded using Trusted Publishing?
What is 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

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.3.0

1 release file

0.2.1

2 release files

0.2.0

1 release file

0.1.2

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page