A flexible cached property with get/set/del/init/dependant/cached-mapping capabilities.
Project description
Promised
promise
A flexible delayed-evaluation cached property with get/set/del/init capabilities for inter-property relationships.
linked
A dependency-managing promise which will refresh dependent properties when any of its linker methods are called. (typically, deleter and setter)
Member
A cached-mapping extension class designed for the @promise decorator, similar to explicitly (im-)mutable memoization.
Get
Type this into terminal / command-line:
pip install promised
And this into Python:
from promised import promise, linked, Member # linked are dependent promises, Member is for cached-mapping extension.
Purpose
This project currently functions as an easy method for managing property dependencies, for example:
class _TestLine(object):
@linked
def length(self):
self._length = 2.0
class _TestSquare(object):
@linked(chain=True)
def side(self):
self._side = _TestLine()
@side.chain("length")
def width(self):
self._width = self.side.length
@side.chain("length")
def height(self):
self._height = self.side.length
@width.linked
@height.linked
def area(self):
self._area = self.width * self.height
class _TestBox(object):
"""This is a test class for linked promises. I don't know what more you're expecting."""
@linked(chain=True)
def side(self):
self._side = _TestLine()
@linked(chain=True)
def base(self):
self._base = _TestSquare()
@side.chain("length")
@base.chain("area")
def volume(self):
self._volume = self.base.area * self.side.length
def _test_area():
box = _TestBox()
assert box.volume == 8.0, "Box volume is 2.0 * 2.0 * 2.0 as Line's default length is 2.0"
box.side.length = 4
assert box.volume == 16.0, "Box volume has updated due to change in side's length."
box.base.side.length = 10
assert box.volume == 400.0, "Box volume has update due to change in base's side length."
line = _TestLine()
line.length = 0.5
box.side = line
assert box.volume == 50.0, "Box volume has updated due to changed side."
This started because I found myself doing this too often:
@property
def property_public_name(self):
'''Why am I typing the same lines with tiny changes in every project all the time?'''
try:
return self._property_public_name_with_leading_underscore
except AttributeError:
self._property_public_name_with_leading_underscore = self._method_to_calculate_property()
return self._property_public_name_with_leading_underscore
Usage
Now, it looks like this:
@promise
def property_public_name(self):
'''Now this is promising!'''
self._property_public_name_with_leading_underscore = self._method_to_calculate_property()
It's still accessed like this:
property_value = self.property_public_name
And you can still do this:
@property_public_name.setter
@property_public_name.deleter
@property_public_name.getter
You can group a bunch of promises up with the same keeper by passing in the name of the private variable (the variable initially set in the promise's keeper) to the promise's __init__:
def _set_associated_properties(self):
associated_map_one = {}
associated_map_two = {}
for thing in self.iterable:
associated_map_one = thing.map_one(associated_map_one)
associated_map_two = thing.map_two(associated_map_two)
self._property_one_public_name = associated_map_one
self._property_two_public_name = associated_map_two
property_one_public_name = promised(_set_associated_properties, name="_property_one_public_name")
property_two_public_name = promised(_set_associated_properties, name="_property_two_public_name")
You can link dependent attributes together using an @linked property (which functions similarly to a promised property) and decorating any of the dependent properties' getter / setter / deleter / keeper methods with the @linked_property_name.linked decorator a single time per dependent property:
@linked
def heroes(self):
self._heroes = None
@heroes.linked
@promise
def future_of_townsville(self):
self._future_of_townsville = "Bleak" if not self.heroes else "FAN-tastic!"
@future_of_townsville.deleter
def future_of_townsville(self):
del self._future_of_townsville
@heroes.linker
@heroes.setter
def heroes(self, value):
self._heroes = value
def test_town_turnaround(self):
""Setting self.heroes to a different value should reset its dependent properties."""
assert not hasattr(self, "_heroes"), "promise should not have already been kept!"
assert not hasattr(self, "_future_of_townsville"), "promise should not have already been kept!"
assert self.future_of_townsville == "Bleak", "There should be no heroes - yet!"
assert self.heroes is None, "There should be no heroes - yet!"
self.heroes = "POWER-PUFF GIRLS"
assert not hasattr(self, "_future_of_townsville"), "The future of townsville is dependent on heroes, so it should be deleted once changed!"
assert self.future_of_townsville == "FAN-tastic!", "The future of townsville should be looking up!"
@linked properties will automatically refresh dependent properties when a @linker method of theirs is called. For ease of use, as this will require at least a deletion method in dependent properties, @linked properties are @promise properties with default deleters and setters which are also default linkers. Using defaults on linked properties, the previous example becomes:
@linked
def heroes(self):
self._heroes = None
@heroes.linked
def future_of_townsville(self):
self._future_of_townsville = "Bleak" if not self.heroes else "FAN-tastic!"
def test_town_turnaround(self):
""Setting self.heroes to a different value should reset its dependent properties."""
...
See documentation in boiler_property.py for further details on removing default deleters / setters / linkers:
@linked(linkers=("keeper",)
def property_which_refreshes_dependent_properties_when_keeper_method_used(self):
"""This would typically reset all dependent properties after this property is accessed for the first time and first access post-refresh/deletion."""
self._property_which_refreshes_dependent_properties_when_keeper_method_used = "RESET"
@linked(deleter=False, setter=False, linkers=("getter",)
def read_only_property_which_refreshes_dependent_properties_on_every_access(self):
"""Not advised for properties which access this property once reset (as the typical dependent property would.)"""
self._read_only_property_which_refreshes_dependent_properties_on_every_access = None
You can use the chain=True init argument of @linked properties to designate an inter-class dependency source.
@linked(chain=True)
def side(self):
self._side = _TestLine()
@linked(chain=True)
def base(self):
self._base = _TestSquare()
And use @dependency_source.chain("dependent_property_name") to mimic the intra-class behavior of @property_name.linked.
@side.chain("length")
@base.chain("area")
def volume(self):
self._volume = self.base.area * self.side.length
You can use the Member class to create a cached promised property which varies on input (like memoization, but explicitly mutable / not-mutable):
def _children_of_parent_with_attribute_value(self, parent, child_attribute_value):
return self.parent_children_map[parent] & self.attribute_value_to_set_of_objects_map[child_attribute_value]
@promise
def adult_children(self):
self._adult_children = Member(self._children_of_parent_with_attribute_value, "The White House")
Which is then accessed like this:
donnie = countries.adult_children["America"]
Future
These are just the first steps in patterns I've recognized as useful for explicit cached properties, and I'm very interested in building in more automated support for associated & dependent properties - please feel free to share any suggestions.
Copyright
promised module by Andrew M. Hogan. (promised © 2019 Hogan Consulting Group)
License
Licensed under the Apache License.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.