Package: unitdoc
UnitdocUnitdoc deals with data objects which describe physical objects, by providing properties with physical units and easy serialization and deserialization.Let's look at an example. First, import unitdoc and create the registry that you will use in your application:fromunitdocimportUnitDocRegistryudr=UnitDocRegistry()Let's create a class that represents a batteryimportattr@udr.serialize()@attr.s()classBattery(object):name=attr.ib()weight=udr.attrib(default='45g')volume=udr.attrib(default='16ml',default_unit='ml')capacity=udr.attrib(default='3.0Ah')voltage=udr.attrib(default='3.6V',description='Average voltage')Let's make aBatterya_battery=Battery(name='battery',weight='43g')print(a_battery)# outputs: Battery(name='battery', weight=<Quantity(43, 'gram')>, volume=<Quantity(16, 'milliliter')>, capacity=<Quantity(3.0, 'Ah')>, voltage=<Quantity(3.6, 'volt')>)Let's do interesting calculations:energy=(a_battery.capacity*a_battery.voltage).to('Wh')print(f'{energy}')# outputs: 10.8 Wh... and moreenergy_density=(energy/a_battery.weight).to('Wh/kg')print(f'{energy}@{energy_density}')# outputs: 10.8 Wh @  251.2 Wh / kgLet's save the battery to a file and reloaded again:fn='a_battery.yaml'# save to yaml filewithopen(fn,'w')asf:f.write(a_battery.serialize())# load from yaml filewithopen(fn,'r')asf:a_loaded_battery=Battery.deserialize(f.read())asserta_battery==a_loaded_batteryIf we look at thea_battery.yamlfile, we will find:name:batteryweight:!unit43 gvolume:!unit16 mlcapacity:!unit3 Ahvoltage:!unit3.6 VThis serialization, we can directly get by# look at serialized formprint(a_battery.serialize())# outputs:name:batteryweight:!unit43gvolume:!unit16mlcapacity:!unit3Ahvoltage:!unit3.6VHave fun!More featuresUnitdoc facilitates certain operations, which can improve your code.If you specifydefault_unitin an attribute, quantities are automatically normalized to that unit:a_battery=Battery(name='battery',volume='15903 mm^3')print(a_battery.volume)# outputs: 15.9 mlIf adefault_unitis specified, any incompatible unit will raise an exception:fromunitdocimportDimensionalityErrortry:a_battery=Battery(name='battery',volume='42 g')exceptDimensionalityErrorase:print(e)# outputs: Cannot convert from 'gram' ([mass]) to 'milliliter' ([length] ** 3)You can retrieve description of parameters, for e.g. data representation codefromunitdocimportget_attr_descriptionprint(get_attr_description(a_battery.__class__,'voltage'))# outputs: Average voltageUnitdoc uses theattrs library), check it out!InstallationUse the package managerpipto install unitdoc:pipinstallunitdocAlternatively, install the latest version from git:gitclonehttps://github.com/deniz195/unitdoc
pythonunitdoc/setup.pyinstall--userRelated packagesUnitdoc is based on the following amazing packages:pintdeals with the unitsruamel.yamlsdeals with (de)serializing from semi-structured data (nested dictionaries)attrsdeals with the boilerplate of data classescattrdeals with the unstructuring and restructuring of classes for (de)serializationThe UnitDocRegistry creates registries/converters/parsers for each package and aggregates them. You can leverage the features of each package:Use unit registry from pint:q=udr.ureg('1000gram').to('kg')print(q)# outputs: 1 kgUse yaml parser from ruaml.yaml:q_yaml=udr.yaml.dump(dict(weight=q))print(q_yaml)# outputs: weight: !unit 1 kgUse cattr converter:@udr.serialize()@attr.s()classThing(object):weight=udr.attrib(default='45g',description='Total weight')a_thing=Thing()a_thing_dict=udr.cattr.unstructure(a_thing)asserttype(a_thing_dict)==dictprint(a_thing_dict['weight'])# output: 45 gRestrictionsGiven the restrictions of the attrs package, updating attributes safely requires certain precautions. E.g. given theBatteryclass from above the following is possible but not desirablea_battery=Battery(name='battery')a_battery.volume=99type(a_battery.volume)# outputs: intThis is not desirable, because unit check and normalizatin is not performed.An good way to avoid this (and other problems) is to use keyword only (kw_only=True) and frozen (frozen=True)attrobjects.@udr.serialize()@attr.s(kw_only=True,frozen=True)classBetterBattery(object):name=attr.ib()weight=udr.attrib(default='45g')volume=udr.attrib(default='16ml',default_unit='ml')capacity=udr.attrib(default='3.0Ah')voltage=udr.attrib(default='3.6V',description='Average voltage')The keyword only restriction, will not allow the creation of objects from positional parameters, so that the following line fails with a Type error:a_battery=BetterBattery('battery','42g','16ml')This is good, because positional arguments can be dangerous when data model changes over time. The following line creates a new object and is stable if the class changesa_battery=BetterBattery(name='battery',weight='42g',volume='16ml')The frozen instance restriction does not allow to mutate an object, so that this line will fail with aFrozenInstanceErrora_battery.volume=99To update values, you can use the attr.evolve function, which creates a new object with the updated valuea_battery=attr.evolve(a_battery,volume='12cm^3')In this case unit conversion and checks are performed as expected.While unitdoc works with regularattrclasses (@attr.s()), we strongly recommend using@attr.s(kw_only=True, frozen=True).ContributingPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.Please make sure to update tests as appropriate.LicenseMIT