Skip to main content

Unitdoc

GitHub GitHub Pipenv locked Python version PyPI version shields.io

Unitdoc 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:

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

Let's make a Battery

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

Let's do interesting calculations:

energy = (a_battery.capacity * a_battery.voltage).to('Wh')
print(f'{energy}')
# outputs: 10.8 Wh

... and more

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

Let's save the battery 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    

If we look at the a_battery.yaml file, we will find:

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

This serialization, we can directly get by

# 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

Have fun!

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

Unitdoc uses the attrs library), check it out!

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

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

Metadata

Release files for unitdoc 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for unitdoc 0.3.2
File Size Uploaded Provenance Actions
unitdoc-0.3.2.tar.gz 9.1 kB Without provenance Details for unitdoc-0.3.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for unitdoc 0.3.2
File Interpreter ABI Platform Actions
unitdoc-0.3.2-py3-none-any.whl Python 3 none any Details for unitdoc-0.3.2-py3-none-any.whl

Total release size: 17.1 kB

Release files / unitdoc-0.3.2.tar.gz

Download URL unitdoc-0.3.2.tar.gz
Size 9.1 kB
Tags Source
SHA-256 checksum
How to use checksums
30c5d778c3f3c5ea185a085a9b9a63291231bc3b1e101bb6db6b1321448665bb
BLAKE2b-256 checksum
How to use checksums
1ba7aef0cdb1e9a2d2ddfd1c5190613e57a81f2c5bc0d2b2a8f1df9c571e3c00
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.4.2 requests/2.18.4 setuptools/40.4.3 requests-toolbelt/0.8.0 tqdm/4.26.0 CPython/3.6.2

Release files / unitdoc-0.3.2-py3-none-any.whl

Download URL unitdoc-0.3.2-py3-none-any.whl
Size 8.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ce05cf24fe3c3008a0c94c9579408530194e108edcaf2ebd724b6441553735df
BLAKE2b-256 checksum
How to use checksums
b575024cfbac9161dc195b51d846b862732280a6667b40a2bfde833edff5975f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.4.2 requests/2.18.4 setuptools/40.4.3 requests-toolbelt/0.8.0 tqdm/4.26.0 CPython/3.6.2

Security for unitdoc 0.3.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page