Skip to main content

Property descriptors for Pydantic

Project description

Added a field_property which gives property like functionality to pydantic models.

Note:

Due to the way pydantic is written the field_property will be slow and inefficient. Pydantic heavily uses and modifies the __dict__ attribute while overloading __setattr__. Additionally, Pydantic’s metaclass modifies the class __dict__ before class creation removing all property objects from the class definition. These changes prevent property objects and normal descriptors from working.

To work around this setting the field_property value must run the normal pydantic __setattr__, run the object __setattr__ to call the property setter, and must update the __dict__ with all field_property values. The Pydantic model __dict__ is only updated on __setattr__ and does not dynamically retrieve values.

Simple Example

from pydantic import PrivateAttr
from pydantic_property import PropertyModel, field_property

class Props(PropertyModel):
    x: int = field_property('_x', default=0)  # getter is created with '_x'

    @x.setter
    def x(self, value):
        self._x = value  # Note: matches field_property('_x')

    y: int = field_property('_y', default=0)  # Need to define '_y' for __private_attribute__

    @y.getter
    def y(self) -> int:
        return getattr(self, '_y', 0)

    @y.setter
    def y(self, value):
        if isinstance(value, float):
            self._x = int((value % 1) * 10)  # Must update all field_property with __dict__ for _x to be seen
        self._y = int(value)

    # Does not have a __private_attribute__ so '_z' will fail. we hack this later for testing
    @field_property(default=0)
    def z(self):
        return getattr(self, '_z', 0)

    @z.setter
    def z(self, value):
        self._z = value

    # Have to add private attribute to allow _z to work.
    # z.private_name = '_z'
    # or
    _z = PrivateAttr(default=0)  # field_property.PrivateAttr

p = Props()
print(p)
p.x = 2
p.y = 1.5
print(p)

assert p.x == 5, '{} != {}'.format(p.x, 5)
assert p.y == 1, '{} != {}'.format(p.y, 1)

msg = p.dict()
assert p.x == msg['x'], '{} != {}'.format(p.x, msg['x'])
assert p.y == msg['y'], '{} != {}'.format(p.y, msg['y'])
assert p.z == msg['z'], '{} != {}'.format(p.z, msg['z'])

Pydantic Notes Example

Pydantic __dict__ Example below does not work, but shows what pydantic is doing in the background.

from pydantic import BaseModel, PrivateAttr

class MyModel(BaseModel):
    x: int = 1

    # Property doesn't really work. This is to show what pydantic does.
    _y: int = PrivateAttr(default=1)

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self._y = value

    def set_point(self, x, y):
        self.x = x
        self._y = y

m = MyModel()
m.x = 2  # This actually sets self.__dict__['x'] = 2
assert m.dict() == {'x': 2}

m.y = 3  # Essentially this would change self.__dict__['y'] = 2
assert m.dict() == {'x': 2, 'y': 3}
assert m.__dict__ == {'x': 2, 'y': 3}

# This sets self.__dict__['x'] = 4 and the instance value m._y to 5, but does not change self.__dict__['y']
m.set_point(4, 5)
m.dict() == {'x': 4, 'y': 3}  # y is not updated ._y is 5. self.__dict__['y'] still == 3

# This is why field_property must update __dict__ for all field_property values.
# This makes the field_property inefficient.

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

pydantic_property-0.0.1.tar.gz (6.8 kB view details)

Uploaded Source

Built Distribution

pydantic_property-0.0.1-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_property-0.0.1.tar.gz.

File metadata

  • Download URL: pydantic_property-0.0.1.tar.gz
  • Upload date:
  • Size: 6.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/47.1.1 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.8.8

File hashes

Hashes for pydantic_property-0.0.1.tar.gz
Algorithm Hash digest
SHA256 a91aa6554c8a96a21e807076c8710aeeccc065298cdb6bc63b1ae413c029498d
MD5 f46d31f44db1bd5ac5fd868675bfc7ac
BLAKE2b-256 615dd36d2f43df3bb47fdd45a612aa71b3dea7c583975fa8df7256c72f066126

See more details on using hashes here.

File details

Details for the file pydantic_property-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: pydantic_property-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 10.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/47.1.1 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.8.8

File hashes

Hashes for pydantic_property-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 724d17dcae2c34a2b6c2d9f1d3eea93262234833a53ab878e05ee4e38b00808c
MD5 fdcff1d7c86f8cb9d74934d124bc1809
BLAKE2b-256 35112051e76d04f39c3d255e6dd68dbe69d27936986edaa49866c4e19d2fb98f

See more details on using hashes here.

Supported by

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