Package: python-datamodel
DataModelDataModel is a simple library based on python +3.8 to use Dataclass-syntax for interacting with
Data, using the same syntax of Dataclass, users can write Python Objects
and work with Data in the same way (like ORM's), is a reimplementation of python Dataclasses supporting true inheritance (without decorators), true composition and other good features.The key features are:Easy to use: No more using decorators, concerns abour re-ordering attributes or common problems with using dataclasses with inheritance.Extensibility: Can use other dataclasses, Data objects or primitives as data-types.Fast: DataModel is a replacement 100% full compatible with dataclasses, without any overhead.RequirementsPython 3.8+Installation$pipinstallpython-datamodel---> 100%Successfully installed datamodelQuickstartfromdatamodelimportField,BaseModelfromdataclassesimportdataclass,fields,is_dataclass# This pure Dataclass:@dataclassclassPoint:x:int=Field(default=0,min=0,max=10)y:int=Field(default=0,min=0,max=10)point=Point(x=10,y=10)print(point)print(fields(point))print('IS a Dataclass?: ',is_dataclass(point))# Can be represented by BaseModelclassnewPoint(BaseModel):x:int=Field(default=0,min=0,max=10)y:int=Field(default=0,min=0,max=10)defget_coordinate(self):return(self.x,self.y)point=newPoint(x=10,y=10)print(point)print(fields(point))print('IS a Dataclass?: ',is_dataclass(point))print(point.get_coordinate())Supported typesDataModel support recursive transformation of fields, so you can easily work with nested dataclasses or complex types.DataModel supports automatic conversion of:datetimeobjects.datetimeobjects are encoded to str exactly like orjson conversion, any str typed as datetime is decoded to datetime.
The same behavior is used to decoding time, date and timedelta objects.UUIDobjects. They
are encoded asstr(JSON string) and decoded back to uuid.UUID objects.Decimalobjects. They are
also encoded asfloatand decoded back to Decimal.Also, "custom" encoders are supported.importuuidfromtypingimport(List,Optional,Union)fromdataclassesimportdataclass,fieldfromdatamodelimportBaseModel,Field@dataclassclassPoint:x:int=Field(default=0,min=0,max=10)y:int=Field(default=0,min=0,max=10)classcoordinate(BaseModel,intSum):latitude:floatlongitude:floatdefget_location(self)->tuple:return(self.latitude,self.longitude)defauto_uid():returnuuid.uuid4()defdefault_rect():return[0,0,0,0]defvalid_zipcode(field,value):returnvalue>1000classAddress(BaseModel):id:uuid.UUID=field(default_factory=auto_uid)street:str=Field(required=True)zipcode:int=Field(required=False,default=1010,validator=valid_zipcode)location:Optional[coordinate]box:List[Optional[Point]]rect:List[int]=Field(factory=default_rect)addr=Address(street="Calle Mayor",location=(18.1,22.1),zipcode=3021,box=[(2,10),(4,8)],rect=[1,2,3,4])print('IS a Dataclass?: ',is_dataclass(addr))print(addr.location.get_location())#returnsAddress(id=UUID('24b34dd5-8d35-4cfd-8916-7876b28cdae3'), street='Calle Mayor', zipcode=3021, location=coordinate(latitude=18.1, longitude=22.1), box=[Point(x=2, y=10), Point(x=4, y=8)], rect=[1, 2, 3, 4])Fast and convenience conversion from-to JSON (using orjson):importorjsonb=addr.json()print(b){"id":"24b34dd5-8d35-4cfd-8916-7876b28cdae3","street":"Calle Mayor","zipcode":3021,"location":{"latitude":18.1,"longitude":22.1}, "box":[{"x":2,"y":10},{"x":4,"y":8}],"rect":[1,2,3,4]}# and re-imported from jsonnew_addr=Address.from_json(b)# load directly from json string# or using a dictionary decoded by orjsondata=orjson.loads(b)new_addr=Address(**data)Inheritancepython-datamodel supports inheritance of classes.importuuidfromtypingimportUnion,Listfromdataclassesimportdataclass,fieldfromdatamodelimportBaseModel,Column,Fielddefauto_uid():returnuuid.uuid4()classUser(BaseModel):id:uuid.UUID=field(default_factory=auto_uid)name:strfirst_name:strlast_name:str@dataclassclassAddress:street:strcity:strstate:strzipcode:strcountry:Optional[str]='US'def__str__(self)->str:"""Provides pretty response of address"""lines=[self.street]lines.append(f"{self.city},{self.zipcode}{self.state}")lines.append(f"{self.country}")return"\n".join(lines)classEmployee(User):"""Base Employee."""role:straddress:Address# composition of a dataclass inside of DataModel is possible.# Supporting multiple inheritance and composition# Wage PoliciesclassMonthlySalary(BaseModel):salary:Union[float,int]defcalculate_payroll(self)->Union[float,int]:returnself.salaryclassHourlySalary(BaseModel):salary:Union[float,int]=Field(default=0)hours_worked:Union[float,int]=Field(default=0)defcalculate_payroll(self)->Union[float,int]:return(self.hours_worked*self.salary)# employee typesclassSecretary(Employee,MonthlySalary):"""Secretary.Person with montly salary policy and no commissions."""role:str='Secretary'classFactoryWorker(Employee,HourlySalary):"""FactoryWorker is an employee with hourly salary policy and no commissions."""role:str='Factory Worker'classPayrollSystem:defcalculate_payroll(self,employees:List[dataclass])->None:print('=== Calculating Payroll === ')foremployeeinemployees:print(f"Payroll for employee{employee.id}-{employee.name}")print(f"-{employee.role}Amount:{employee.calculate_payroll()}")ifemployee.address:print('- Sent to:')print(employee.address)print("")jane=Secretary(name='Jane Doe',first_name='Jane',last_name='Doe',salary=1500)bob=FactoryWorker(name='Bob Doyle',first_name='Bob',last_name='Doyle',salary=15,hours_worked=40)mitch=FactoryWorker(name='Mitch Brian',first_name='Mitch',last_name='Brian',salary=20,hours_worked=35)payroll=PayrollSystem()payroll.calculate_payroll([jane,bob,mitch])A sample of output:```console
=== Calculating Payroll ===
Payroll for employee 745a2623-d4d2-4da6-bf0a-1fa691bafd33 - Jane Doe
- Secretary Amount: 1500
- Sent to:
Rodeo Drive, Rd
Los Angeles, 31050 CA
USContributingFirst of all, thank you for being interested in contributing to this library.
I really appreciate you taking the time to work on this project.If you're just interested in getting into the code, a good place to start are
issues tagged as bugs.If introducing a new feature, especially one that modifies the public API,
consider submitting an issue for discussion before a PR. Please also take a look
at existing issues / PRs to see what you're proposing has  already been covered
before / exists.I like to follow the commit conventions documentedhereLicenseThis project is licensed under the terms of the BSD v3. license.