Package: ctor
ctorctoris an object tree construction and deconstruction library for python>=3.7.
It provides non-invasive way of constructing and deconstructing objects based on type annotations.Installation:pipinstallctorFeatures0 dependenciesYour classes are yours! Library does not mess with your code.No subclassing / metaclassingNo special class-level field annotationsLibrary does no replace/generate__init__and/or other methodsWorks withdataclasses,attrs,PyDanticand even pure-python classes.Extendable if neededSupportsUnion,Literal,Annotated, forward references, cyclic-types ...PyCharm and other IDEs highlight constructor arguments natively without pluginsSupports Python 3.7+Example with python dataclassesfromdataclassesimportdataclass# standard python@dataclassclassMyClass:name:strvalue:int# some_other_file.py#   note that this import might be in a different file#   so that your code remains untouched by serialization logicimportctor# Dumpingmy_object=MyClass(name='hello',value=42)data=ctor.dump(my_object)print(data)# {'name': 'hello', 'value': 42}# Loadingdata={'name':'World','value':-42}restored_object=ctor.load(MyClass,data)print(restored_object)# MyClass(name='world', value=-42)Nested objects with dataclassesfromdataclassesimportdataclass# standard pythonfromtypingimportList@dataclassclassTreeNode:name:strchildren:List['TreeNode']# Note: forward reference making class cyclicgraph=TreeNode(name="root",children=[TreeNode(name="A",children=[]),TreeNode(name="B",children=[TreeNode(name="x",children=[]),TreeNode(name="y",children=[]),]),TreeNode(name="C",children=[]),])# Note: Even in this complex scenario, classes and data are still untouched by the library code.importctorgraph_data=ctor.dump(graph)print(graph_data)# {'name': 'root', 'children': [{'name': 'A', 'children': []}, {'name': 'B', 'children': [{'name': 'x', 'children': []}, {'name': 'y', 'children': []}]}, {'name': 'C', 'children': []}]}Bare classesclassUser:def__init__(self,uid:int,email:str,name:str):self.uid=uidself.email=emailself.name=nameclassPost:def__init__(self,title:str,content:str,author:User):self.title=titleself.content=contentself.author=authorimportctor# Import placed here just to highlight that your business-level code still does not require serialization librarydata={'content':'Fields can go in any order also','author':{'uid':123,'email':'john@doe.org','name':'John Doe'},'title':'Unbelievable'}post=ctor.load(Post,data)# post is a normal python object hereprint(post.author.name)# post.user is a UserNote that this is not justPost(**data)because you need to constructUserobject first.
This works with objects of any depth.Implementation described lower in the README.RationaleSerialization logic should not be coupled with business logicBusiness logic should not depend on 3rd party librariesImplementationctorheavily relies on typing annotations and utilizes lots of features from pythontypingmodule.To obtain object schemactorlooks at the signature of the callable you want to construct/deconstruct.
Classes are callable, soctorlooks at__init__method to find information about attributes and annotations, trying to specify a converter for each of the arguments.About python 3.6 or lesstypingbecame more or less stable only after python 3.7 (including).
API for types in 3.6 differs significantly from 3.7 onward.It is hard to maintain two different APIs that's why I decided to stick to the newer one.
However, if you feel brave enough and really need that 3.6 support you can open a PullRequest and contact me if you have any questions.Running testsTo test against current python version:pytestTo tests all python versions:tox --parallel