All Projects → n0nSmoker → Sqlalchemy Serializer

n0nSmoker / Sqlalchemy Serializer

Licence: mit
Serrializer for SQLAlchemy models.

Programming Languages

python
139335 projects - #7 most used programming language

Projects that are alternatives of or similar to Sqlalchemy Serializer

Acts as api
makes creating API responses in Rails easy and fun
Stars: ✭ 506 (+468.54%)
Mutual labels:  serializer
Jmsserializerbundlebridge
[READ ONLY] Symfony bundle for using JMSSerializer as message serializer for SimpleBus/Asynchronous
Stars: ✭ 10 (-88.76%)
Mutual labels:  serializer
Hprose Golang
Hprose is a cross-language RPC. This project is Hprose for Golang.
Stars: ✭ 1,143 (+1184.27%)
Mutual labels:  serializer
Hprose Java
Hprose is a cross-language RPC. This project is Hprose 2.0 for Java
Stars: ✭ 542 (+508.99%)
Mutual labels:  serializer
Preact Jest Snapshot Test Boilerplate
🚀 Test Preact components using Jest snapshots
Stars: ✭ 24 (-73.03%)
Mutual labels:  serializer
Eminim
JSON serialization framework for Nim, works from a Stream directly to any type and back. Depends only on stdlib.
Stars: ✭ 32 (-64.04%)
Mutual labels:  serializer
Azure Iot Sdk C
A C99 SDK for connecting devices to Microsoft Azure IoT services
Stars: ✭ 412 (+362.92%)
Mutual labels:  serializer
Mini Yaml
Single header YAML 1.0 C++11 serializer/deserializer.
Stars: ✭ 79 (-11.24%)
Mutual labels:  serializer
Bert Rs
BERT (Binary ERlang Term) serializer for Rust
Stars: ✭ 8 (-91.01%)
Mutual labels:  serializer
Cesil
Modern CSV (De)Serializer
Stars: ✭ 59 (-33.71%)
Mutual labels:  serializer
Blueprinter
Simple, Fast, and Declarative Serialization Library for Ruby
Stars: ✭ 623 (+600%)
Mutual labels:  serializer
Dsongo
Encoding, decoding, marshaling, unmarshaling, and verification of the DSON (Doge Serialized Object Notation)
Stars: ✭ 23 (-74.16%)
Mutual labels:  serializer
Serd
A lightweight C library for RDF syntax
Stars: ✭ 43 (-51.69%)
Mutual labels:  serializer
N3.js
Lightning fast, spec-compatible, streaming RDF for JavaScript
Stars: ✭ 521 (+485.39%)
Mutual labels:  serializer
Dictfier
Python library to convert/serialize class instances(Objects) both flat and nested into a dictionary data structure. It's very useful in converting Python Objects into JSON format
Stars: ✭ 67 (-24.72%)
Mutual labels:  serializer
Ponder
C++ reflection library with Lua binding, and JSON and XML serialisation.
Stars: ✭ 442 (+396.63%)
Mutual labels:  serializer
Enzyme To Json
Snapshot test your Enzyme wrappers
Stars: ✭ 954 (+971.91%)
Mutual labels:  serializer
Udt
UDT spec, reference library and related packages
Stars: ✭ 89 (+0%)
Mutual labels:  serializer
Servicestack.text
.NET's fastest JSON, JSV and CSV Text Serializers
Stars: ✭ 1,157 (+1200%)
Mutual labels:  serializer
Serializer Pack
A Symfony Pack for Symfony Serializer
Stars: ✭ 1,068 (+1100%)
Mutual labels:  serializer

SQLAlchemy-serializer

Mixin for SQLAlchemy models serialization without pain.

If you want to serialize SQLAlchemy model instances with only one line of code, and tools like marshmallow seems to be redundant and too complex for such a simple task, this mixin definitely suits you.

Contents

Installation

pip install SQLAlchemy-serializer

Usage

If you want SQLAlchemy model to become serializable, add SerializerMixin in class definition:

from sqlalchemy_serializer import SerializerMixin


class SomeModel(db.Model, SerializerMixin):
    ...

This mixin adds .to_dict() method to model instances. So now you can do something like this:

item = SomeModel.query.filter(.....).one()
result = item.to_dict()

You get values of all SQLAlchemy fields in the result var, even nested relationships In order to change the default output you shuld pass tuple of fieldnames as an argument

  • If you want to exclude or add some extra fields (not from database) You should pass rules argument
  • If you want to define the only fields to be presented in serializer's output use only argument

If you want to exclude a few fields for this exact item:

result = item.to_dict(rules=('-somefield', '-some_relation.nested_one.another_nested_one'))

If you want to add a field which is not defined as an SQLAlchemy field:

class SomeModel(db.Model, SerializerMixin):
    non_sql_field = 123

    def method(self):
        return anything

result = item.to_dict(rules=('non_sql_field', 'method'))

Note that method or a function should have no arguments except self, in order to let serializer call it without hesitations.

If you want to get exact fields:

result = item.to_dict(only=('non_sql_field', 'method', 'somefield'))

Note that if somefield is an SQLAlchemy instance, you get all it's serializable fields. So if you want to get only some of them, you should define it like below:

result = item.to_dict(only=('non_sql_field', 'method', 'somefield.id', 'somefield.etc'))

You can use negative rules in only param too. So item.to_dict(only=('somefield', -'somefield.id')) will return somefiled without id. See Negative rules in ONLY section

If you want to define schema for all instances of particular SQLAlchemy model, add serialize properties to model definition:

class SomeModel(db.Model, SerializerMixin):
    serialize_only = ('somefield.id',)
    serialize_rules = ()
    ...
    somefield = db.relationship('AnotherModel')

result = item.to_dict()

So the result in this case will be {'somefield': [{'id': some_id}]} serialize_only and serialize_rules work the same way as to_dict's arguments

Advanced usage

For more examples see tests

class FlatModel(db.Model, SerializerMixin):
    """
    to_dict() of all instances of this model now returns only following two fields
    """
    serialize_only = ('non_sqlalchemy_field', 'id')
    serialize_rules = ()

    id = db.Column(db.Integer, primary_key=True)
    string = db.Column(db.String(256), default='Some string!')
    time = db.Column(db.DateTime, default=datetime.utcnow())
    date = db.Column(db.Date, default=datetime.utcnow())
    boolean = db.Column(db.Boolean, default=True)
    boolean2 = db.Column(db.Boolean, default=False)
    null = db.Column(db.String)
    non_sqlalchemy_dict = dict(qwerty=123)


class ComplexModel(db.Model, SerializerMixin):
   """
   Schema is not defined so
   we will get all SQLAlchemy attributes of the instance by default
   without `non_sqlalchemy_list`
   """

    id = db.Column(db.Integer, primary_key=True)
    string = db.Column(db.String(256), default='Some string!')
    boolean = db.Column(db.Boolean, default=True)
    null = db.Column(db.String)
    flat_id = db.Column(db.ForeignKey('test_flat_model.id'))
    rel = db.relationship('FlatModel')
    non_sqlalchemy_list = [dict(a=12, b=10), dict(a=123, b=12)]

item = ComplexModel.query.first()


# Now by default the result looks like this:
item.to_dict()

dict(
    id=1,
    string='Some string!',
    boolean=True,
    null=None,
    flat_id=1,
    rel=[dict(
        id=1,
        non_sqlalchemy_dict=dict(qwerty=123)
    )]


# Extend schema
item.to_dict(rules=('-id', '-rel.id', 'rel.string', 'non_sqlalchemy_list'))

dict(
    string='Some string!',
    boolean=True,
    null=None,
    flat_id=1,
    non_sqlalchemy_list=[dict(a=12, b=10), dict(a=123, b=12)],
    rel=dict(
        string='Some string!',
        non_sqlalchemy_dict=dict(qwerty=123)
    )
)


# Exclusive schema
item.to_dict(only=('id', 'flat_id', 'rel.id', 'non_sqlalchemy_list.a'))

dict(
    id=1,
    flat_id=1,
    non_sqlalchemy_list=[dict(a=12), dict(a=123)],
    rel=dict(
        id=1
    )
)

Custom formats

If you want to change datetime/date/time/decimal format in one model you can specify it like below:

from sqlalchemy_serializer import SerializerMixin

class SomeModel(db.Model, SerializerMixin):
    __tablename__ = 'custom_table_name'
    
    date_format = '%s'  # Unixtimestamp (seconds)
    datetime_format = '%Y %b %d %H:%M:%S.%f'
    time_format = '%H:%M.%f'
    decimal_format = '{:0>10.3}'

    id = sa.Column(sa.Integer, primary_key=True)
    date = sa.Column(sa.Date)
    datetime = sa.Column(sa.DateTime)
    time = sa.Column(sa.Time)
    money = Decimal('12.123')  # same result with sa.Float(asdecimal=True, ...)

If you want to change format in every model, you should write your own mixin class inherited from SerializerMixin:

from sqlalchemy_serializer import SerializerMixin

class CustomSerializerMixin(SerializerMixin):
    date_format = '%s'  # Unixtimestamp (seconds)
    datetime_format = '%Y %b %d %H:%M:%S.%f'
    time_format = '%H:%M.%f'
    decimal_format = '{:0>10.3}'

And later use it as usual:

from decimal import Decimal
import sqlalchemy as sa
from some.lib.package import CustomSerializerMixin


class CustomSerializerModel(db.Model, CustomSerializerMixin):
    __tablename__ = 'custom_table_name'

    id = sa.Column(sa.Integer, primary_key=True)
    date = sa.Column(sa.Date)
    datetime = sa.Column(sa.DateTime)
    time = sa.Column(sa.Time)
    money = Decimal('12.123')  # same result with sa.Float(asdecimal=True, ...)

All date/time/datetime/decimal fields will be serialized using your custom formats.

  • Decimal uses python format syntax
  • To get unixtimestamp use %s,
  • Other datetime formats you can find in docs

Custom types

By default the library can serialize the following types:

- int 
- str 
- float 
- bytes 
- bool 
- type(None)
- time
- datetime
- date
- Decimal
- Enum
- dict (if values and keys are one of types mentioned above, or inherit one of them)
- any Iterable (if types of values are mentioned above, or inherit one of them)

If you want to add serialization of any other type or redefine the default behaviour. You should add something like this:

serialize_types = (
    (SomeType, lambda x: some_expression),
    (AnyOtherType, some_function)
)

To your own mixin class inherited from SerializerMixin:

from sqlalchemy_serializer import SerializerMixin
from geoalchemy2.elements import WKBElement
from geoalchemy2.shape import to_shape

def serialize_int(value):
    return value + 100

class CustomSerializerMixin(SerializerMixin):
    serialize_types = (
        (WKBElement, lambda x: to_shape(x).to_wkt()),
        (int, serialize_int)
    )

... or directly to the model:

from geoalchemy2 import Geometry
from sqlalchemy_serializer import SerializerMixin

class Point(Base, SerializerMixin):
    serialize_types = (
        (WKBElement, lambda x: to_shape(x).to_wkt()),
        (AnyOtherType, serialize_smth)
    )
    __tablename__ = 'point'
    id = Column(Integer, primary_key=True)
    position = Column(Geometry('POINT'))

Unfortunately you can not access formats or tzinfo in that functions. I'll implement this logic later if any of users needs it.

Timezones

To keep datetimes consistent its better to store it in the database normalized to UTC. But when you return response, sometimes (mostly in web, mobile applications can do it themselves) you need to convert all datetimes to user's timezone. So you need to tell serializer what timezone to use. There are two ways to do it:

  • The simplest one is to pass timezone directly as an argument for to_dict function
import pytz

item.to_dict(timezone=pytz.timezone('Europe/Moscow'))
  • But if you do not want to write this code in every function, you should define timezone logic in your custom mixin (how to use customized mixin see Castomization)
import pytz
from sqlalchemy_serializer import SerializerMixin
from some.package import get_current_user

class CustomSerializerMixin(SerializerMixin):
   def get_tzinfo(self):
       # you can write your own logic here, 
       # the example below will work if you store timezone
       # in user's profile
       return pytz.timezone(get_current_user()['timezone'])

Troubleshooting

Max recursion

If you've faced with maximum recursion depth exceeded exception, most likely the serializer have found instance of the same class somewhere among model's relationships. Especially if you use backrefs. In this case you need to tell it where to stop like below:

class User(Base, SerializerMixin):
    __tablename__ = 'users'
    
    # Exclude nested model of the same class to avoid max recursion error
    serialize_rules = ('-related_models.user',)
    ...
    related_models = relationship("RelatedModel", backref='user')
    
    
class RelatedModel(Base, SerializerMixin):
    __tablename__ = 'some_table'

    ...
    user_id = Column(Integer, ForeignKey('users.id'))
    ...

If for some reason you need the field user to be presented in related_models field. You can change serialize_rules to ('-related_models.user.related_models',) To break the chain of serialisation a bit further.

Controversial rules

If you add controversial rules like serialize_rules = ('-prop', 'prop.id') The serializer will exclude the whole prop. You need to revert these rules or use serialize_only option.

Negative rules in ONLY section

If you pass rules in serialize_only the serializer becomes NOT greedy and returns ONLY fields listed there. So serialize_only = ('-model.id',) will return nothing But serialize_only = ('model', '-model.id',) will return model field without id

One element tuples

Do not forget to add comma at the end of one element tuples, it is trivial, but a lot of developers forget about it:

serialize_only = ('some_field',)  # <--- Thats right!
serialize_only = ('some_field')  # <--- WRONG it is actually not a tuple

Tests

To run tests and see tests coverage report just type the following command:(doker and doker-compose should be installed on you local machine)

make test

To run a particular test use

make test file=tests/some_file.py
make test file=tests/some_file.py::test_func

I will appreciate any help in improving this library, so feel free to submit issues or pull requests.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].