{ "info": { "author": "Andrew M. Hogan", "author_email": "drewthedruid@gmail.com", "bugtrack_url": null, "classifiers": [ "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities" ], "description": "# Promised\n\n### promise\n\nA flexible delayed-evaluation cached property with get/set/del/init capabilities for inter-property relationships.\n\n### linked\n\nA dependency-managing promise which will refresh dependent properties when any of its linker methods are called. (typically, deleter and setter)\n\n### Member\n\nA cached-mapping extension class designed for the @promise decorator, similar to explicitly (im-)mutable memoization.\n\n## Get\n\nType this into terminal / command-line:\n```\npip install promised\n```\n\nAnd this into Python:\n```\nfrom promised import promise, linked, Member # linked are dependent promises, Member is for cached-mapping extension.\n```\n\n## Purpose\nThis project currently functions as an easy method for managing property dependencies, for example:\n\n```\nclass _TestLine(object):\n @linked\n def length(self):\n self._length = 2.0\n```\n```\nclass _TestSquare(object):\n @linked(chain=True)\n def side(self):\n self._side = _TestLine()\n\n @side.chain(\"length\")\n def width(self):\n self._width = self.side.length\n\n @side.chain(\"length\")\n def height(self):\n self._height = self.side.length\n\n @width.linked\n @height.linked\n def area(self):\n self._area = self.width * self.height\n```\n```\nclass _TestBox(object):\n \"\"\"This is a test class for linked promises. I don't know what more you're expecting.\"\"\"\n @linked(chain=True)\n def side(self):\n self._side = _TestLine()\n\n @linked(chain=True)\n def base(self):\n self._base = _TestSquare()\n\n @side.chain(\"length\")\n @base.chain(\"area\")\n def volume(self):\n self._volume = self.base.area * self.side.length\n```\n```\ndef _test_area():\n box = _TestBox()\n assert box.volume == 8.0, \"Box volume is 2.0 * 2.0 * 2.0 as Line's default length is 2.0\"\n box.side.length = 4\n assert box.volume == 16.0, \"Box volume has updated due to change in side's length.\"\n box.base.side.length = 10\n assert box.volume == 400.0, \"Box volume has update due to change in base's side length.\"\n line = _TestLine()\n line.length = 0.5\n box.side = line\n assert box.volume == 50.0, \"Box volume has updated due to changed side.\"\n```\n\nThis started because I found myself doing this too often:\n```\n@property\ndef property_public_name(self):\n '''Why am I typing the same lines with tiny changes in every project all the time?'''\n try:\n return self._property_public_name_with_leading_underscore\n except AttributeError:\n self._property_public_name_with_leading_underscore = self._method_to_calculate_property()\n return self._property_public_name_with_leading_underscore\n```\n\n## Usage\n\nNow, it looks like this:\n```\n@promise\ndef property_public_name(self):\n '''Now this is promising!'''\n self._property_public_name_with_leading_underscore = self._method_to_calculate_property()\n```\n\nIt's still accessed like this:\n```\nproperty_value = self.property_public_name\n```\n\nAnd you can still do this:\n```\n@property_public_name.setter\n@property_public_name.deleter\n@property_public_name.getter\n```\n\nYou 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\\_\\_:\n```\ndef _set_associated_properties(self):\n associated_map_one = {}\n associated_map_two = {}\n for thing in self.iterable:\n associated_map_one = thing.map_one(associated_map_one)\n associated_map_two = thing.map_two(associated_map_two)\n self._property_one_public_name = associated_map_one\n self._property_two_public_name = associated_map_two\n\nproperty_one_public_name = promised(_set_associated_properties, name=\"_property_one_public_name\")\nproperty_two_public_name = promised(_set_associated_properties, name=\"_property_two_public_name\")\n```\nYou 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:\n```\n@linked\ndef heroes(self):\n self._heroes = None\n\n@heroes.linked\n@promise\ndef future_of_townsville(self):\n self._future_of_townsville = \"Bleak\" if not self.heroes else \"FAN-tastic!\"\n\n@future_of_townsville.deleter\ndef future_of_townsville(self):\n del self._future_of_townsville\n\n@heroes.linker\n@heroes.setter\ndef heroes(self, value):\n self._heroes = value\n\ndef test_town_turnaround(self):\n \"\"Setting self.heroes to a different value should reset its dependent properties.\"\"\"\n assert not hasattr(self, \"_heroes\"), \"promise should not have already been kept!\"\n assert not hasattr(self, \"_future_of_townsville\"), \"promise should not have already been kept!\"\n assert self.future_of_townsville == \"Bleak\", \"There should be no heroes - yet!\"\n assert self.heroes is None, \"There should be no heroes - yet!\"\n self.heroes = \"POWER-PUFF GIRLS\"\n assert not hasattr(self, \"_future_of_townsville\"), \"The future of townsville is dependent on heroes, so it should be deleted once changed!\"\n assert self.future_of_townsville == \"FAN-tastic!\", \"The future of townsville should be looking up!\"\n```\n\n@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:\n```\n@linked\ndef heroes(self):\n self._heroes = None\n\n@heroes.linked\ndef future_of_townsville(self):\n self._future_of_townsville = \"Bleak\" if not self.heroes else \"FAN-tastic!\"\n\ndef test_town_turnaround(self):\n \"\"Setting self.heroes to a different value should reset its dependent properties.\"\"\"\n ...\n```\n\nSee documentation in boiler_property.py for further details on removing default deleters / setters / linkers:\n```\n@linked(linkers=(\"keeper\",)\ndef property_which_refreshes_dependent_properties_when_keeper_method_used(self):\n \"\"\"This would typically reset all dependent properties after this property is accessed for the first time and first access post-refresh/deletion.\"\"\"\n self._property_which_refreshes_dependent_properties_when_keeper_method_used = \"RESET\"\n\n@linked(deleter=False, setter=False, linkers=(\"getter\",)\ndef read_only_property_which_refreshes_dependent_properties_on_every_access(self):\n \"\"\"Not advised for properties which access this property once reset (as the typical dependent property would.)\"\"\"\n self._read_only_property_which_refreshes_dependent_properties_on_every_access = None\n```\n\nYou can use the chain=True init argument of @linked properties to designate an inter-class dependency source.\n```\n@linked(chain=True)\ndef side(self):\n self._side = _TestLine()\n\n@linked(chain=True)\ndef base(self):\n self._base = _TestSquare()\n```\n\nAnd use @dependency_source.chain(\"dependent_property_name\") to mimic the intra-class behavior of @property_name.linked.\n```\n@side.chain(\"length\")\n@base.chain(\"area\")\ndef volume(self):\n self._volume = self.base.area * self.side.length\n```\n\nYou can use the Member class to create a cached promised property which varies on input (like memoization, but explicitly mutable / not-mutable):\n```\ndef _children_of_parent_with_attribute_value(self, parent, child_attribute_value):\n return self.parent_children_map[parent] & self.attribute_value_to_set_of_objects_map[child_attribute_value]\n\n@promise\ndef adult_children(self):\n self._adult_children = Member(self._children_of_parent_with_attribute_value, \"The White House\")\n```\n\nWhich is then accessed like this:\n```\ndonnie = countries.adult_children[\"America\"]\n```\n\n## Future\n\nThese 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.\n\n## Copyright\n\npromised module by Andrew M. Hogan. (promised © 2019 Hogan Consulting Group)\n\n## License\n\nLicensed under the Apache License.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/Andrew-Hogan/Promised", "keywords": "", "license": "Apache License 2.0", "maintainer": "", "maintainer_email": "", "name": "promised", "package_url": "https://pypi.org/project/promised/", "platform": "any", "project_url": "https://pypi.org/project/promised/", "project_urls": { "Homepage": "https://github.com/Andrew-Hogan/Promised" }, "release_url": "https://pypi.org/project/promised/1.2.2/", "requires_dist": null, "requires_python": "", "summary": "A flexible cached property with get/set/del/init/dependant/cached-mapping capabilities.", "version": "1.2.2" }, "last_serial": 5344231, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "d2f62f167992681780125fa7addfea9d", "sha256": "86116b0e98208ec98027f6698c26cb908dddd294e9a262ce16a4770038871cbc" }, "downloads": -1, "filename": "promised-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "d2f62f167992681780125fa7addfea9d", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 8651, "upload_time": "2019-02-26T21:54:57", "url": "https://files.pythonhosted.org/packages/44/ce/91ab0518933893ac473a617fd29c6a5184064942fd7c2336fe009feeb7bc/promised-1.0.0-py3-none-any.whl" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "510cc5361d6a515cdea0a1e68ffe959c", "sha256": "f0ffd4601d30bbb8ae1835e55f6f745fc6bb1a4a0b6bcb5dabd229994f444d12" }, "downloads": -1, "filename": "promised-1.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "510cc5361d6a515cdea0a1e68ffe959c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 12427, "upload_time": "2019-02-27T20:52:55", "url": "https://files.pythonhosted.org/packages/25/ef/5b8f572ac05864b654dadf7783615639f6e25795c68ef250fc0c6b938501/promised-1.1.0-py3-none-any.whl" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "bf1dc7143fa9e50057a39297e843dfd0", "sha256": "0c3e5a0c16711cbff356bb72a5a0a808e1d383f3ad2fee4bc41ec96c635df805" }, "downloads": -1, "filename": "promised-1.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "bf1dc7143fa9e50057a39297e843dfd0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 12505, "upload_time": "2019-02-28T00:49:53", "url": "https://files.pythonhosted.org/packages/b2/4a/3a3720d432d907b124f91155b04fcda2501ad365a035f80693151a3f0628/promised-1.1.1-py3-none-any.whl" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "afa84d4baaeec23a1e9ee30abd655748", "sha256": "b7b38e7f7677d7a495c3603e00aa047bd07a1dd1dd4a67d18b5a3e980d4d4739" }, "downloads": -1, "filename": "promised-1.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "afa84d4baaeec23a1e9ee30abd655748", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 17296, "upload_time": "2019-05-30T19:58:40", "url": "https://files.pythonhosted.org/packages/a1/4e/3a12230c492a1eb41c229c0c0d5fc91e899d22e1b5fdc9f5dc38045aedea/promised-1.2.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b76f9921e40dd3c32ecc6930787b07e3", "sha256": "e33e12b66e58e3db3dbbfd39fae102f58688aba70a87a410f4043bc4d6096296" }, "downloads": -1, "filename": "promised-1.2.1.tar.gz", "has_sig": false, "md5_digest": "b76f9921e40dd3c32ecc6930787b07e3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17841, "upload_time": "2019-05-30T19:58:41", "url": "https://files.pythonhosted.org/packages/0d/01/46006bca0975a970d2ad56e40b49b8b53c7a305daa1fd7b6a80509d42e81/promised-1.2.1.tar.gz" } ], "1.2.2": [ { "comment_text": "", "digests": { "md5": "71354a96b5b240d11417e09bada1d052", "sha256": "ba168973f022b4316ba243ffac55cc184d26bda63968a2c91b09c176c602066b" }, "downloads": -1, "filename": "promised-1.2.2-py3-none-any.whl", "has_sig": false, "md5_digest": "71354a96b5b240d11417e09bada1d052", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 17950, "upload_time": "2019-05-31T19:43:41", "url": "https://files.pythonhosted.org/packages/75/d9/b361361dfcc56697fce0d159caa805f270af75b2d11be28c75f4a2a6cb0a/promised-1.2.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d46f9d71df3850fcd1f60bd0a31a831a", "sha256": "1b3b379d939944b42855e258f8b34ec59895c5478f93beb1411a91dd6cedcf85" }, "downloads": -1, "filename": "promised-1.2.2.tar.gz", "has_sig": false, "md5_digest": "d46f9d71df3850fcd1f60bd0a31a831a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18420, "upload_time": "2019-05-31T19:43:43", "url": "https://files.pythonhosted.org/packages/18/73/e5fecffc11ca9cbf14f528a53ed7187e7cedba603b6842398fe0264913b4/promised-1.2.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "71354a96b5b240d11417e09bada1d052", "sha256": "ba168973f022b4316ba243ffac55cc184d26bda63968a2c91b09c176c602066b" }, "downloads": -1, "filename": "promised-1.2.2-py3-none-any.whl", "has_sig": false, "md5_digest": "71354a96b5b240d11417e09bada1d052", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 17950, "upload_time": "2019-05-31T19:43:41", "url": "https://files.pythonhosted.org/packages/75/d9/b361361dfcc56697fce0d159caa805f270af75b2d11be28c75f4a2a6cb0a/promised-1.2.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d46f9d71df3850fcd1f60bd0a31a831a", "sha256": "1b3b379d939944b42855e258f8b34ec59895c5478f93beb1411a91dd6cedcf85" }, "downloads": -1, "filename": "promised-1.2.2.tar.gz", "has_sig": false, "md5_digest": "d46f9d71df3850fcd1f60bd0a31a831a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18420, "upload_time": "2019-05-31T19:43:43", "url": "https://files.pythonhosted.org/packages/18/73/e5fecffc11ca9cbf14f528a53ed7187e7cedba603b6842398fe0264913b4/promised-1.2.2.tar.gz" } ] }