Skip to content

Commit

Permalink
add lazy load field
Browse files Browse the repository at this point in the history
  • Loading branch information
ypankovych committed May 19, 2021
1 parent ab2e2c1 commit e073574
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 7 deletions.
6 changes: 3 additions & 3 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Now lets load it:
from pankoff.combinator import combine
from pankoff.exceptions import ValidationError
from pankoff.magic import autoinit, Alias
from pankoff.validators import String, Number, BaseValidator, Predicate
from pankoff.validators import String, Number, BaseValidator, Predicate, LazyLoad
class Salary(BaseValidator):
Expand Down Expand Up @@ -58,10 +58,10 @@ Now lets load it:
error_message="Invalid value for {field_name}, person got into wrong position: {value}"
)
payment = Alias("salary")
job_desc = LazyLoad(factory=lambda instance: f"{instance.position} at {instance.office}")
person = Person.from_path("data.json")
print(person) # Person(name=Yaroslav, age=22, salary=100 USD, office=Central Office, position=Manager)
print(person) # Person(name=Yaroslav, age=22, salary=100 USD, office=Central Office, position=Manager, job_desc=Manager at Central Office)
Trying invalid data. Change your ``data.json``:

Expand Down
14 changes: 13 additions & 1 deletion docs/source/validators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Pankoff defines a small preset of validator, and it allow you to define your own
Default validators
==================
By default, Pankoff defines a few validators, ``String``, ``Number``, ``Type``, ``Sized``,
``Predicate``. We'll go over each one below.
``Predicate``, ``LazyLoad``. We'll go over each one below.

.. autoclass:: pankoff.validators.String()

Expand Down Expand Up @@ -40,6 +40,18 @@ By default, Pankoff defines a few validators, ``String``, ``Number``, ``Type``,

>>> box = Box(size=40)

.. autoclass:: pankoff.validators.LazyLoad(factory)

>>> @autoinit
>>> class Person(Container):
... name = String()
... surname = String()
... full_name = LazyLoad(factory=lambda instance: f"{instance.name} {instance.surname}")

>>> person = Person(name="Yaroslav", surname="Pankovych")
>>> print(person)
Person(name=Yaroslav, surname=Pankovych, full_name=Yaroslav Pankovych)

.. autoclass:: pankoff.validators.Predicate(predicate, default=None, error_message=None)

>>> @autoinit
Expand Down
9 changes: 8 additions & 1 deletion pankoff/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ def __init__(self, __mro__=None, **kwargs):
kw[parameter.name] = kwargs.pop(parameter.name)
if base.__setup__ is not BaseValidator.__setup__:
if not getattr(base.__setup__, "__called__", False):
base.__setup__(self, **kw)
try:
base.__setup__(self, **kw)
except Exception:
_invalidate_call_cache(self, target="__setup__")
raise
super(base, self).__init__(__mro__=__mro__, **kwargs)

def __set__(self, instance, value, __mro__=None, errors=None):
Expand All @@ -172,6 +176,9 @@ def __set__(self, instance, value, __mro__=None, errors=None):
value = ret
except ValidationError as exc:
errors.append(str(exc))
except Exception:
_invalidate_call_cache(self, target="validate")
raise
super(base, self).__set__(instance, value, __mro__=__mro__, errors=errors)

def __setup__(self, *args, **kwargs):
Expand Down
9 changes: 7 additions & 2 deletions pankoff/magic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pankoff.base import BaseValidator
from pankoff.validators import UNSET, LazyLoad

init_template = "def __init__({arguments}):\n\t{assignments}"

Expand Down Expand Up @@ -39,11 +40,15 @@ def inner(cls):

attrs = ["self"]
assignments = []
namespace = {}
namespace = {"UNSET": UNSET}
for attr in vars(cls).values():
if isinstance(attr, BaseValidator):
attrs.append(attr.field_name)
if isinstance(attr, LazyLoad):
attrs.append(f"{attr.field_name}=UNSET")
else:
attrs.append(attr.field_name)
assignments.append(f"self.{attr.field_name} = {attr.field_name}")
attrs.sort(key=lambda item: "=" in item) # move default parameters to the end
init = init_template.format(
arguments=", ".join(attrs),
assignments="\n\t".join(assignments or ["pass"])
Expand Down
16 changes: 16 additions & 0 deletions pankoff/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,19 @@ def validate(self, instance, value):
value=value
)
)


class LazyLoad(BaseValidator):
"""
Calculate an attribute based on other fields.
:param factory: callable to calculate value for current field, accepts current instance
"""

def __setup__(self, factory):
self.factory = factory

def validate(self, instance, value):
if value is not UNSET:
raise RuntimeError(f"`{self.field_name}` is a `LazyLoad` field, you're not allowed to set it directly")
return self.factory(instance)

0 comments on commit e073574

Please sign in to comment.