Package: golden-marshmallows
golden-marshmallowsA better integration between SQLAlchemy and Marshmallow. A little (SQL)alchemy to turnmarshmallows into gold.Note: The default unknown field handling has been defaulted toEXCLUDEso it handles
closer to Marshmallow v2.InstallationSimply install withpip:$ pip install golden-marshmallowsUsageSerializationTake these SQLAlchemy models as examples:classWizardCollege(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)classAlchemist(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)classFormula(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)TheGoldenSchemaclass allows quick and easy generation ofmarshmallowschemas that can be used for SQLAlchemy object serialization/deserialization. Simply pass the model class on initialization and you're ready to go:importjsonfromgolden_marshmallows.schemaimportGoldenSchemafrommodelsimportAlchemist,Formula,WizardCollegealchemist=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 ownSchemasubclass, unless you really want to (more on that below).Nested objectsBut 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, theGoldenSchemaclass 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 theGoldenSchemainstances that are nested into your top-level schema in thenested_mapparameter. If that's the case, you can simply create a nestedGoldenSchemainstance and pass it in directly like so:frommarshmallow.fieldsimportList,StringFormulaSchema=GoldenSchema(Formula)classFormulaSchemaWithIngredients(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"#             ]#         }#     ]# }DeserializationOf course, you can deserialize data into SQLAlchemy objects just as easily:# Start at the end of the last example and work backwardsdata={"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 FeaturescamelCasing/snake_casingThesnake_to_camelflag 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 camelCaseschema=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 sameGoldenSchemainstance, when used toload(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)# 1A 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 subclassingGoldenSchemalike so:classMySchema(GoldenSchema):manually_declared=fields.Function(lambdaobj:'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 useCaseChangingSchema, the parent class ofGoldenSchema:fromgolden_marshmallows.schemaimportCaseChangingSchemaclassSnakeSchema(CaseChangingSchema):attr_one=fields.String()attr_two=fields.Integer()classSnakeObj:def__init__(self,attr_one,attr_two):self.attr_one=attr_oneself.attr_two=attr_twoschema=SnakeSchema(snake_to_camel=True)obj=SnakeObj('field1',2)serialized=schema.dump(obj)print(json.dumps(serialized,indent=4))# {#     'attrOne': 'field1',#     'attrTwo': 2# }Copying objectsAs a minor convenience, you can pass thenew_objflag on initialization to indicate that any fields namedidshould 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)# NoneThis allows you to quickly deserialize data representations of existing objects into new copies.