Giter VIP home page Giter VIP logo

golden-marshmallows's Introduction

golden-marshmallows

A better integration between SQLAlchemy and Marshmallow. A little (SQL)alchemy to turn marshmallows into gold.

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_marshmallow.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).data

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).data

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).data

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
#                 }
#             ]
#         }
#     ]
# }

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).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).data

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).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).data
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).data
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).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.

golden-marshmallows's People

Contributors

gchorn avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.