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.
Metadata
Release files for promised 1.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| promised-1.2.2.tar.gz | 18.4 kB | Without provenance | Details for promised-1.2.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| promised-1.2.2-py3-none-any.whl | Python 3 | none | any | Details for promised-1.2.2-py3-none-any.whl |
Total release size: 36.4 kB
Release files / promised-1.2.2.tar.gz
| Download URL | promised-1.2.2.tar.gz |
|---|---|
| Size | 18.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1b3b379d939944b42855e258f8b34ec59895c5478f93beb1411a91dd6cedcf85
|
|
BLAKE2b-256 checksum How to use checksums |
1873e5fecffc11ca9cbf14f528a53ed7187e7cedba603b6842398fe0264913b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/1.13.0 pkginfo/1.4.2 requests/2.21.0 setuptools/40.0.0 requests-toolbelt/0.8.0 tqdm/4.23.3 CPython/3.6.6
|
Release files / promised-1.2.2-py3-none-any.whl
| Download URL | promised-1.2.2-py3-none-any.whl |
|---|---|
| Size | 17.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ba168973f022b4316ba243ffac55cc184d26bda63968a2c91b09c176c602066b
|
|
BLAKE2b-256 checksum How to use checksums |
75d9b361361dfcc56697fce0d159caa805f270af75b2d11be28c75f4a2a6cb0a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/1.13.0 pkginfo/1.4.2 requests/2.21.0 setuptools/40.0.0 requests-toolbelt/0.8.0 tqdm/4.23.3 CPython/3.6.6
|
Security for promised 1.2.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.