Package: serpyco-rs
serpyco-rs: a serializer for python dataclassesWhat is serpyco-rs ?Serpyco is a serialization library forPython 3.9+ dataclassesthat works just by defining your dataclasses:importdataclassesimportserpyco_rs@dataclasses.dataclassclassExample:name:strnum:inttags:list[str]serializer=serpyco_rs.Serializer(Example)result=serializer.dump(Example(name="foo",num=2,tags=["hello","world"]))print(result)>>{'name':'foo','num':2,'tags':['hello','world']}Inspired byserpyco.serpyco-rs works by analysing the dataclass fields and can recognize many types :list,tuple,Optional...
You can also embed other dataclasses in a definition.The main use-case for serpyco-rs is to serialize objects for an API, but it can be helpful whenever you need to transform objects to/from builtin Python types.InstallationUse pip to install:$pipinstallserpyco-rsFeaturesSerialization and deserialization of dataclassesValidation of input dataVery fastSupport recursive schemasGenerate JSON Schema Specification (Draft 2020-12)Support custom encoders/decoders for fieldsSupported field typesThere is support for generic types from the standard typing module:DecimalUUIDTimeDateDateTimeEnumListDictBytes (pass through)TypedDictMappingSequenceTuple (fixed size)Literal[str, ...]Tagged unions (restricted)BenchmarksLinuxLoadLibraryMedian latency (milliseconds)Operations per secondRelative (latency)serpyco_rs0.166318.11mashumaro0.452244.42.81pydantic0.571753.93.56serpyco0.821228.35.17marshmallow8.49117.453.35DumpLibraryMedian latency (milliseconds)Operations per secondRelative (latency)serpyco_rs0.07137981serpyco0.07136221.02mashumaro0.110219.51.36pydantic0.224615.52.99marshmallow249727.69MacOSmacOS Monterey / Apple M1 Pro / 16GB RAM / Python 3.11.0LoadLibraryMedian latency (milliseconds)Operations per secondRelative (latency)serpyco_rs0.19865.11mashumaro0.249682pydantic0.342866.73.42serpyco0.691444.16.87marshmallow4.14241.841.05DumpLibraryMedian latency (milliseconds)Operations per secondRelative (latency)serpyco_rs0.0422602.61serpyco0.0521232.91.06mashumaro0.0615903.41.42pydantic0.166262.63.61marshmallow1.0496223.5Supported annotationsserpyco-rssupports changing load/dump behavior withtyping.Annotated.Currently available:AliasFieldFormat (CamelCase / NoFormat)NoneFormat (OmitNone / KeepNone)DiscriminatorMin / MaxMinLength / MaxLengthCustomEncoderNoneAsDefaultForOptional (ForceDefaultForOptional)AliasAliasis needed to override the field name in the structure used forload/dump.fromdataclassesimportdataclassfromtypingimportAnnotatedfromserpyco_rsimportSerializerfromserpyco_rs.metadataimportAlias@dataclassclassA:foo:Annotated[int,Alias('bar')]ser=Serializer(A)print(ser.load({'bar':1}))>>A(foo=1)print(ser.dump(A(foo=1)))>>{'bar':1}FieldFormatUsed to have response bodies in camelCase while keeping your python code in snake_case.fromdataclassesimportdataclassfromtypingimportAnnotatedfromserpyco_rsimportSerializerfromserpyco_rs.metadataimportCamelCase,NoFormat@dataclassclassB:buz_filed:str@dataclassclassA:foo_filed:intbar_filed:Annotated[B,NoFormat]ser=Serializer(Annotated[A,CamelCase])# or ser = Serializer(A, camelcase_fields=True)print(ser.dump(A(foo_filed=1,bar_filed=B(buz_filed='123'))))>>{'fooFiled':1,'barFiled':{'buz_filed':'123'}}print(ser.load({'fooFiled':1,'barFiled':{'buz_filed':'123'}}))>>A(foo_filed=1,bar_filed=B(buz_filed='123'))NoneFormatViaOmitNonewe can drop None values for non required fields in the serialized dictsfromdataclassesimportdataclassfromserpyco_rsimportSerializer@dataclassclassA:required_val:bool|Noneoptional_val:bool|None=Noneser=Serializer(A,omit_none=True)# or Serializer(Annotated[A, OmitNone])print(ser.dump(A(required_val=None,optional_val=None)))>>>{'required_val':None}Tagged unionsSupports tagged joins with discriminator field.All classes in the union must be dataclasses or attrs with discriminator fieldLiteral[str].The discriminator field is always mandatory.fromtypingimportAnnotated,Literalfromdataclassesimportdataclassfromserpyco_rsimportSerializerfromserpyco_rs.metadataimportDiscriminator@dataclassclassFoo:type:Literal['foo']value:int@dataclass(kw_only=True)classBar:type:Literal['bar']='bar'value:strser=Serializer(list[Annotated[Foo|Bar,Discriminator('type')]])print(ser.load([{'type':'foo','value':1},{'type':'bar','value':'buz'}]))>>>[Foo(type='foo',value=1),Bar(type='bar',value='buz')]Min / MaxSupported forint/float/Decimaltypes and only for validation on load.fromtypingimportAnnotatedfromserpyco_rsimportSerializerfromserpyco_rs.metadataimportMin,Maxser=Serializer(Annotated[int,Min(1),Max(10)])ser.load(123)>>SchemaValidationError:[ErrorItem(message='123 is greater than the maximum of 10',instance_path='')]MinLength / MaxLengthMinLength/MaxLengthcan be used to restrict the length of loaded strings.fromtypingimportAnnotatedfromserpyco_rsimportSerializerfromserpyco_rs.metadataimportMinLengthser=Serializer(Annotated[str,MinLength(5)])ser.load("1234")>>SchemaValidationError:[ErrorItem(message='"1234" is shorter than 5 characters',instance_path='')]NoneAsDefaultForOptionalForceDefaultForOptional/KeepDefaultForOptionalcan be used to set None as default value for optional (nullable) fields.fromdataclassesimportdataclassfromserpyco_rsimportSerializer@dataclassclassFoo:val:int# not nullable + requiredval1:int|None# nullable + requiredval2:int|None=None# nullable + not requiredser_force_default=Serializer(Foo,force_default_for_optional=True)# or Serializer(Annotated[Foo, ForceDefaultForOptional])ser=Serializer(Foo)# all fields except val are optional and nullableassertser_force_default.load({'val':1})==Foo(val=1,val1=None,val2=None)# val1 field is required and nullable and val1 should be present in the dictser.load({'val':1})>>SchemaValidationError:[ErrorItem(message='"val1" is a required property',instance_path='')]Custom encoders for fieldsYou can provide CustomEncoder withserializeanddeserializefunctions, orserialize_withanddeserialize_withannotations.fromtypingimportAnnotatedfromdataclassesimportdataclassfromserpyco_rsimportSerializerfromserpyco_rs.metadataimportCustomEncoder@dataclassclassFoo:val:Annotated[str,CustomEncoder[str,str](serialize=str.upper,deserialize=str.lower)]ser=Serializer(Foo)val=ser.dump(Foo(val='bar'))>>{'val':'BAR'}assertser.load(val)==Foo(val='bar')Note:CustomEncoderhas no effect to validation and JSON Schema generation.Bytes fieldsserpyco-rscan loads bytes fields as is (without base64 encoding and validation).fromdataclassesimportdataclassfromserpyco_rsimportSerializer@dataclassclassFoo:val:bytesser=Serializer(Foo)ser.load({'val':b'123'})==Foo(val=b'123')Getting JSON Schemaserpyco-rscan generate JSON Schema for your dataclasses (Draft 2020-12).fromdataclassesimportdataclassfromserpyco_rsimportSerializer@dataclassclassA:"""Description of A"""foo:intbar:strser=Serializer(A)print(ser.get_json_schema())>>{'$schema':'https://json-schema.org/draft/2020-12/schema','$ref':'#/components/schemas/A[no_format,keep_nones]','components':{'schemas':{'A[no_format,keep_nones]':{'properties':{'foo':{'type':'integer'},'bar':{'type':'string'}},'required':['foo','bar'],'type':'object','description':'Description of A'}}}}