Skip to main content

A framework that enables classes that describe physical objects with units and easy serialization.

Project description

Unitdoc

Unitdoc is a Python library for dealing with data objects which describe physical objects with units and easy serialization. Let's look at an example. First, import unitdoc and create the registry that you will use in your application:

from unitdoc import UnitDocRegistry

udr = UnitDocRegistry()

Let's create a class that represents a battery

import attr

@udr.serialize()   
@attr.s()
class Battery(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')

We can create an instance of Battery, which will be a normal attr object (see attrs library), which features e.g. a nice repr function:

a_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')>)

We can use the attributes of the battery in any operation that are allowed by the pint package:

energy = (a_battery.capacity * a_battery.voltage).to('Wh')
energy_density = (energy / a_battery.weight).to('Wh/kg')
print(f'{energy} @  {energy_density}')
# outputs: 10.8 Wh @  251.2 Wh / kg

Now let's save and reload our battery object:

# look at serialized form
print(a_battery.serialize())

# outputs:
name: battery
weight: !unit 43 g
volume: !unit 16 ml
capacity: !unit 3 Ah
voltage: !unit 3.6 V

This can be easily saved to a file and reloaded again:

fn = 'a_battery.yaml'
# save to yaml file
with open(fn, 'w') as f:
    f.write(a_battery.serialize())

# load from yaml file
with open(fn, 'r') as f:
    a_loaded_battery = Battery.deserialize(f.read())

assert a_battery == a_loaded_battery    

More features

Unitdoc facilitates certain operations, which can improve your code.

If you specify default_unit in 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 ml

If a default_unit is specified, any incompatible unit will raise an exception:

from unitdoc import DimensionalityError

try:
    a_battery = Battery(name = 'battery', volume='42 g')
except DimensionalityError as e:
    print(e)
# outputs: Cannot convert from 'gram' ([mass]) to 'milliliter' ([length] ** 3)

You can retrieve description of parameters, for e.g. data representation code

from unitdoc import get_attr_description
print(get_attr_description(a_battery.__class__, 'voltage'))
# outputs: Average voltage

Installation

Use the package manager pip to install unitdoc:

pip install unitdoc

Alternatively, install the latest version from git:

git clone https://github.com/deniz195/unitdoc
python unitdoc/setup.py install --user

Related packages

Unitdoc is based on the following amazing packages:

  • pint deals with the units
  • ruamel.yamls deals with (de)serializing from semi-structured data (nested dictionaries)
  • attrs deals with the boilerplate of data classes
  • cattr deals with the unstructuring and restructuring of classes for (de)serialization

The 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 kg

Use yaml parser from ruaml.yaml:

q_yaml = udr.yaml.dump(dict(weight=q))
print(q_yaml)
# outputs: weight: !unit 1 kg

Use cattr converter:

@udr.serialize()   
@attr.s()
class Thing(object):
    weight = udr.attrib(default='45g', description ='Total weight')

a_thing = Thing()
a_thing_dict = udr.cattr.unstructure(a_thing)

assert type(a_thing_dict) == dict
print(a_thing_dict['weight'])
# output: 45 g

Restrictions

Given the restrictions of the attrs package, updating attributes safely requires certain precautions. E.g. given the Battery class from above the following is possible but not desirable

a_battery = Battery(name = 'battery')
a_battery.volume = 99
type(a_battery.volume)
# outputs: int

This 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) attr objects.

@udr.serialize()   
@attr.s(kw_only=True, frozen=True)
class BetterBattery(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 changes

a_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 a FrozenInstanceError

a_battery.volume = 99 

To update values, you can use the attr.evolve function, which creates a new object with the updated value

a_battery = attr.evolve(a_battery, volume='12cm^3')

In this case unit conversion and checks are performed as expected.

While unitdoc works with regular attr classes (@attr.s()), we strongly recommend using @attr.s(kw_only=True, frozen=True).

Contributing

Pull 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.

License

MIT

Project details


Release history Release notifications

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for unitdoc, version 0.2.1
Filename, size File type Python version Upload date Hashes
Filename, size unitdoc-0.2.1-py3-none-any.whl (7.5 kB) File type Wheel Python version py3 Upload date Hashes View hashes
Filename, size unitdoc-0.2.1.tar.gz (8.5 kB) File type Source Python version None Upload date Hashes View hashes

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page