Skip to main content

Power Dataclass: dataclasses with auto typecasting and other power features

Project description

⚡ Power Dataclass ⚡

Build Status

DISCLAIMER

This library is leveraging the inner mechanics of Python 3.7's typing module, and thus only works with Python 3.7 You may experience real shock when it breaks. Wear protective gloves.

Installation

pip install powerdataclass

Usage

Python 3.7 have introduced a spiritual successor of NamedTuple: the dataclass. While being nice, the dataclass type hinting is only, well, hinting.

This library gives you an ability to create dataclasses with field values automatically casted to the types defined in the dataclass's type hints:

@dataclasses.dataclass
class Coordinates(powerdataclass.PowerDataclass):
    x: int
    y: int

c1 = Coordinates(1,2)
c2 = Coordinates('1', '2')
c3 = Coordinates(**{'x': 1.1, 'y': 2.2})


>>> c1
Coordinates(x=1, y=2)
>>> c1 == c2 == c3 
True

This also works with every generic type that has a Python primitive type as it's origin. This applies to subscriptable types of any level of nestedness as well:

@dataclasses.dataclass
class Vector(powerdataclass.PowerDataclass):
    items: List[int]

v1 = Vector(['1', '2', '3'])
v2 = Vector({1.1, 2.2, 3.3})
v3 = Vector(range(1, 4))

>>> v1
Vector(items=[1, 2, 3])
>>> v1 == v2 == v3 
True

The typecasting also respects other dataclasses (and Power Dataclasses) declared in type hints. If you pass a mapping in place of actual dataclass instance, Power Dataclass will attempt to unpack it to a corresponding dataclass:

@dataclasses.dataclass
class Vector(powerdataclass.PowerDataclass):
    items: List[int]

@dataclasses.dataclass
class Tensor(powerdataclass.PowerDataclass):
    vectors: List[Vector]

t1 = Tensor(**{
    'vectors': [
        {'items': [1, 2, 3]},
        {'items': [4, 5, 6]},
        {'items': [7, 8, 9]},
    ]
})

>>> t1
Tensor(vectors=[Vector(items=[1, 2, 3]), Vector(items=[4, 5, 6]), Vector(items=[7, 8, 9])])

You can modify the behaviour of type casting by registering two types of handlers on your fancy PowerDataclass:

  • type handlers: an unary method marked as a type handler will be applied to any value that has a matching type declared in your dataclass typehints.
  • field handlers: an unary method marked as a field handler will be applied to a value of a specific PDC field.

Those functions must always return a value.

You can do this by marking your methods with special decorators:

@dataclasses.dataclass
class CoolBool(powerdataclass.PowerDataclass):
    string_bool: bool
    negated_bool: bool

    @powerdataclass.register_pdc_type_handler(bool)
    def handle_bools(self, v):
        if type(v) is str:
            return v.lower() in ['y', 'yes', '1', 'True']
        else:
            return bool(v)

    @powerdataclass.register_pdc_field_handler('negated_bool')
    def handle_xored_bools(self, v):
        return not self.handle_bools(v)

>>> CoolBool('yes', 'no')
CoolBool(string_bool=True, negated_bool=True)

Field handlers take precedence over the type handlers. Field handlers and type handlers are scoped to a particular Power Dataclass. Inheritance is respected.

If you want to accept None as a valid value but also want non-null values to be typecasted you can mark your field as nullable by either setting the corresponding flag in the fields's metadata dictionary or using a premade partial:

@dataclasses.dataclass
class Nihilus(powerdataclass.PowerDataclass):
    x: int = dataclasses.field(metadata={powerdataclass.FieldMeta.NULLABLE: True})
    y: int = powerdataclass.nullable_field()

>>> Nihilus(None, None)
Nihilus(x=None, y=None) 
>>> Nihilus('1', None)
Nihilus(x=1, y=None)

If you want to disable type checking for a specific field you can mark your field as nullable by either setting the corresponding flag in the fields's metadata dictionary or using a premade partial:

@dataclasses.dataclass
class Noncasted(powerdataclass.PowerDataclass):
    x: int = field(metadata={powerdataclass.FieldMeta.SKIP_TYPECASTING: True})
    y: int = powerdataclass.noncasted_field()

>>> Noncasted('1', 2.2)
Noncasted(x='1', y=2.2)

If some of your field processing requires other fields typecasted before you can declare this field dependencies by name by setting the corresponding value in the fields's metadata:

@dataclasses.dataclass
class Dependent(powerdataclass.PowerDataclass):
    a: int
    b: int = field(metadata={powerdataclass.FieldMeta.DEPENDS_ON_FIELDS: ['a']})
    c: int = field(metadata={powerdataclass.FieldMeta.DEPENDS_ON_FIELDS: ['d', 'b']})
    d: int = field(metadata={powerdataclass.FieldMeta.DEPENDS_ON_FIELDS: ['a']})

Fields will be topologically sorted by their dependencies and type casting will be done in this order. For this example, the order will be:

  1. a
  2. b
  3. d
  4. c

You can use a combination of field handlers and dependent fields to declare calculated fields:

@dataclasses.dataclass
class CubeSquarer(PowerDataclass):
    n: int
    n_square: int = field(default=None, metadata={FieldMeta.DEPENDS_ON_FIELDS: ['n']})
    n_cube: int = powerdataclass.calculated_field(depends_on=['n'])

    @register_pdc_field_handler('n_square')
    def handle_n_square(self, v):
        return self.n ** 2

    @register_pdc_field_handler('n_cube')
    def handle_n_cube(self, v):
        return self.n ** 3

>>> CubeSquarer(4)
CubeSquarer(n=4, n_square=16, n_cube=256)

It is an error to declare a field as calculatable without registering a corresponding field_handler

Other features

  • Automatic recursive conversion to dict with the .as_dict() method.
  • Automatic recursive conversion to and from JSON strings with the .as_json() and .from_json() methods.

Made with ⚡ by Arish Pyne (https://github.com/arishpyne/powerdataclass)

Project details


Download files

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

Source Distribution

powerdataclass-1.1.0.tar.gz (6.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

powerdataclass-1.1.0-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file powerdataclass-1.1.0.tar.gz.

File metadata

  • Download URL: powerdataclass-1.1.0.tar.gz
  • Upload date:
  • Size: 6.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.32.2 CPython/3.7.3

File hashes

Hashes for powerdataclass-1.1.0.tar.gz
Algorithm Hash digest
SHA256 5ab537a728f29e33f03b7c190f47618545c195888a3517ef7039e6697ad23e80
MD5 b3c4b1d1ef5041ed0a6845e2e526f589
BLAKE2b-256 4e8ef844a9e47dca2007f9d04b9164b2ce715cf3258d96c39ed7726d576a6e96

See more details on using hashes here.

File details

Details for the file powerdataclass-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: powerdataclass-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.32.2 CPython/3.7.3

File hashes

Hashes for powerdataclass-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 57168413c4ebee8f8324a05a81b0ff8cdc03a6b4784e4e1ba8186194ebb58380
MD5 4f6c724601dabd30228bb7b381e9ed00
BLAKE2b-256 a88f07406dee632d2748ca6a41feaa7d2d88f2efb5363d6ba1777511bc0d5b8b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page