{ "info": { "author": "Rob Gaddi", "author_email": "rgaddi@highlandtechnology.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4" ], "description": "==================\nIndexed Properties\n==================\n\n:Author: Rob Gaddi, Highland Technology\n:Date: 19-Sep-2017\n:Version: 0.1.4\n\nOverview\n========\n\nThis package supports indexed properties on class instances. Whereas the\nbuiltin property function allows you create functions for single-valued \nproperties, the indexedproperty package allows you to create properties that\naccept an index argument like the __getitem__/__setitem__/__delitem__\nset:\n\n.. code:: python\n\n >>> from indexedproperty import indexedproperty\n >>> class PropertyTest:\n ... def __init__(self):\n ... self._singleprop = None\n ... self._dict = {}\n ... \n ... # Standard builtin property\n ... @property\n ... def sprop(self):\n ... return self._singleprop\n ... \n ... @sprop.setter\n ... def sprop(self, value):\n ... print('Setting sprop', value)\n ... self._singleprop = value\n ... \n ... # Fancy new indexed property\n ... @indexedproperty\n ... def iprop(self, key):\n ... return self._dict[key]\n ... \n ... @iprop.setter\n ... def iprop(self, key, value):\n ... print('Setting iprop {0}={1}'.format(key, value))\n ... self._dict[key] = value\n ...\n >>> x = PropertyTest()\n >>> x.sprop = 5\n Setting sprop 5\n >>> x.sprop\n 5\n >>> x.iprop['Gilliam'] = 'Terry'\n Setting iprop Gilliam=Terry\n >>> x.iprop['Gilliam']\n 'Terry'\n \nDecorator Functions\n===================\n\nThe most convenient use for this library is through the decorator functions.\n\n indexedproperty\n Marks a function as the getter (__getitem__) for an IndexedProperty. \n The .setter (__setitem__) and .deleter(__delitem__) methods may be \n called on the returned IndexedProperty to add additional functionality \n for the property\n \n containerproperty(base)\n Marks a function as the getter (__getitem__) for a ContainerProperty. A \n ContainerProperty is indexed on the container provided by base and \n raises a KeyError if the users's key is not \"in\" the base.\n \n If the base supports iter() and len(), the ContainerProperty does as \n well, and adds an items() iterator similar to the one provided by dict. \n Broadcasting is supported on list and tuple keys.\n \n rangeproperty([start=0], stop)\n Marks a function as the getter (__getitem__) for an RangeProperty. This \n property is indexed on the half-open numeric range defined by \n range(start, stop), and behaves like a Sequence, though with a \n potentially non-zero lower bound.\n \n RangeProperty elements support iteration, negative index wraparound (if \n start >= 0), and broadcasting on list, tuple, range, and slice keys.\n \nDecorating Methods\n==================\n\nAkin to *property*, the basic operations are to define the setter, getter, and\ndeleter for an IndexedProperty. The functional form of the decorator assumes\nthe getter, and further assignments are made by the .setter and .deleter\nmethods of the property.\n\nUnlike a property, however, decorators can be created to provide any function\nincluding \"magic\" methods like *__iter__* and *__contains__*, which will work\nas expected. In all cases, the first argument to the function (typically `self`)\nwill point to an instance of the class that the property is defined in.\n\nOr to say it another way, when you have:\n\n.. code:: python\n\n class C:\n @indexedproperty\n def x(self, key):\n ...\n \n @x.__iter__\n def fancyproperty(self):\n ...\n \n def unfancymethod(self):\n ...\n \nAll of those *self* references will be to an instance of a **C**. __getitem__, \n__setitem__, and __delitem__ cannot be patched, as it would break the magic,\nthese are assigned with .getter, .setter, and .deleter respectively.\n\nOn the base IndexedProperty, you can assign to .iterable_indices with a tuple\nof classes that should be given special treatment. If the key in the brackets\nis an instance of one of then, then rather than pass the key on to the\ngetter/setter/deleter functions directly, it will be iterated into successive\ncalls. So if x.iterable_indices = (list, tuple)\n\n+-------------------------------+---------------------------------+\n|Doing | Does |\n+===============================+=================================+\n| :: | :: |\n| | |\n| x[5] | return x.getter(self, 5) |\n+-------------------------------+---------------------------------+\n| :: | :: |\n| | | \n| x[5,10,15] | return [ |\n| | x.getter(self, 5), |\n| | x.getter(self, 10), |\n| | x.getter(self, 15) |\n| | ] |\n+-------------------------------+---------------------------------+\n| :: | :: |\n| | | \n| x[5] = 'Larry' | x.setter(self, 5, 'Larry') |\n+-------------------------------+---------------------------------+\n| :: | :: |\n| | | \n| x[5,10,15] = 'Larry' | x.setter(self, 5, 'Larry') |\n| | x.setter(self, 10, 'Larry') |\n| | x.setter(self, 15, 'Larry') |\n+-------------------------------+---------------------------------+\n| :: | :: |\n| | |\n| x[5,10,15] = [ | x.setter(self, 5, 'Larry') | \n| \"Larry\", \"can't\", | x.setter(self, 10, \"can't\") |\n| \"spel\" | x.setter(self, 15, 'spel') |\n| ] | |\n+-------------------------------+---------------------------------+\n\nThe setter broadcasting concept is taken from numpy; you can assign either a\nsingle value or an iterable of values that is the same length as the list of keys.\nStrings are treated as single values, and non-string iterables of a different\nlength than the key list raise ValueError.\n\nSubclasses\n==========\n\nContainerProperty\n-----------------\n\nContainerProperty is an IndexedProperty linked to a *collections.abc.Container*,\nwhich is anything with a *__contains__* method (i.e. that supports the ``x in y``\nconstruction). ContainerProperty automatically checks to ensure that the\nkey provided on accesses is in the container and raises KeyError otherwise. This\nsaves having to check explicitly in the accessor code.\n\nContainerProperty also supports broadcasting over list and tuple keys.\n\nIn almost every case, the container will actually be a *collections.abc.Collection*,\nmeaning that it also supports *len()* and *iter()*. Expecting this, the\nContainerProperty also provides:\n\n* ``len(prop)`` : Returns the number of keys in the collection.\n* ``iter(prop)`` : Iterates over the keys in the collection.\n* ``prop.items()`` : Iterates over (key, value) pairs in the collection.\n\nIn this way, a ContainerProperty implements much of the functionality of a ``dict``.\n\n.. code:: python\n\n >>> import indexedproperty as ix\n >>> class FoodRestrictions:\n ... _foodlist = ['apples', 'bananas', 'pears']\n ... \n ... def __init__(self):\n ... self.fooddict = { k : [] for k in self._foodlist }\n ... \n ... @ix.containerproperty(_foodlist)\n ... def lunch(self, idx):\n ... return self.fooddict[idx]\n ... \n ... @lunch.setter\n ... def lunch(self, idx, value):\n ... self.fooddict[idx] = value\n ... \n >>> x = FoodRestrictions()\n >>> x.lunch['apples'] = 'I have an apple'\n >>> x.lunch['bread'] = 'But I want bread'\n Traceback (most recent call last):\n KeyError: 'bread'\n >>> x.lunch['pears'] = 5\n >>> x.lunch['apples', 'pears']\n ['I have an apple', 5]\n >>> sorted(x.lunch)\n ['apples', 'bananas', 'pears']\n\nRangeProperty\n-------------\n\nRangeProperty is an IndexedProperty linked to a range of integer values. Much\nlike a ``list``, keys support slicing and negative indices. Also like a list,\niteration is considered to be over values rather than over keys. It provides:\n \n* ``len(prop)`` : Returns the number of elements in the property\n* ``iter(prop)`` : Iterates over the values in the property from start to stop.\n* ``reverse(prop)`` : Allows the ``reversed`` function to iterate from stop to start.\n* ``prop.items()`` : Iterates over (index, value) pairs from start to stop.\n* ``prop.range`` : A read-only range object representing the range of the property.\n\nHere we have both *rangeproperty* in it's natural habitat, and an utterly\ngratuitious use of assigning additional functions to the property:\n\n.. code:: python\n\n >>> import indexedproperty as ix\n >>> class Uint32:\n ... def __init__(self, val=0):\n ... self.word = val\n ... \n ... def __repr__(self):\n ... return \"{0}(0x{1:x})\".format(type(self).__name__, self.word)\n ... \n ... @ix.RangeProperty(32)\n ... def bit(self, b):\n ... return self.word & (1 << b)\n ... \n ... @bit.setter\n ... def bit(self, b, val):\n ... v = 1 << b\n ... self.word |= v\n ... if not val:\n ... self.word ^= v\n ... \n ... @bit.count\n ... def bit(self):\n ... return sum(bool(b) for b in self.bit)\n ... \n ... @bit.lowest\n ... def bit(self):\n ... for idx, b in self.bit.items():\n ... if b:\n ... return idx\n ... return None\n ...\n ... @bit.highest\n ... def bit(self):\n ... for idx in reversed(self.bit.range):\n ... if self.bit[idx]:\n ... return idx\n ... return None\n ... \n ... def clear(self):\n ... self.word = 0\n ... \n >>> x = Uint32()\n >>> x.bit[1::4] = True\n >>> x\n Uint32(0x22222222)\n >>> x.bit[:8]\n [0, 2, 0, 0, 0, 32, 0, 0]\n >>> x.bit[15:7:-1]\n [0, 0, 8192, 0, 0, 0, 512, 0]\n >>> x.bit.count()\n 8\n >>> x.bit.lowest()\n 1\n >>> x.bit.highest()\n 29\n >>> (list(x.bit))[::-1] == list(reversed(x.bit))\n True\n \nWhat's Under The Hood\n=====================\n\nWhen you get a class member defined as an IndexedProperty, what is returned is\na subclass of Trampoline. The definition of that class is local to the \nspecific IndexedProperty under discussion, and is updated every time a new\nmember is created by one of the IndexedProperty's decorators. In the above\nexample, when @lunch.setter is executed it updates the class definition for\nthe lunch Trampoline to include a setter() method.\n\nSo when you ask for x.lunch, you get a new instance of that Trampoline subclass\nthat has that setter function, as well as getter, __iter__, __len__, and items,\nand a .obj pointer to x. The Trampoline the function calls against it back\nagainst the functions originally decorated.\n\nExtending IndexedProperty\n=========================\n\nNew types of indexed properties (such as RangeProperty) can be created by \nsubclassing IndexedProperty. This can be a bit tricky, because the class \ndoes some of the work and the Trampoline subclass does the rest.\n\nSee the source code for ContainerProperty and RangeProperty for examples of how \nthis is done. Start with ContainerProperty, it's the more straightforward of \nthe two.\n\nThe important logic to follow is\n\n1) The IndexedProperty subclass has a ._Trampoline member, which is a subclass\nof Trampoline. Class methods for the trampoline that are not specific to a\ngiven instance of the IndexedProperty can be defined here. In these methods,\nthe object that the property is a member of is available as ``self.obj``.\n\n2) For class methods (and properties) that **are** instance specific, the\nIndexedProperty subclass has a .tdict member, which is the class dictionary\nfor the Trampoline.\n\n3) Having modified the .tdict (probably in __init__), a call to updatetrampoline()\nwill recreate the **instance's** ._trampolinecls, which is a subclass of the\nIndexedProperty ._Trampoline with overloading defined by .tdict. This is what\nputs the methods, such as the getter, setter, etc, into the _Trampoline.\n\nAnother very common usage is wanting a variant on ContainerProperty that \nperforms some minor transformation on the key before checking it against the \ncontainer. For all IndexedProperty subclasses, the modindex and moduserindex\nmethods can be overloaded to handle verification and modification of\nkeys. For example, if the key is a string it should be made uppercase:\n\n.. code:: python\n\n >>> from indexedproperty import ContainerProperty\n >>> class UCProperty(ContainerProperty):\n ... \"\"\"A ContainerProperty that transforms string keys to uppercase.\"\"\"\n ... class _Trampoline(ContainerProperty._Trampoline):\n ... def modindex(self, index):\n ... index = index.upper()\n ... return super().modindex(index)\n ... \n >>> class TestClass:\n ... _indices = {'PI':3.14, 'E':2.718, 'I':(0+1j), 'TAU':6.28}\n ... \n ... @UCProperty(_indices)\n ... def constant(self, key):\n ... return self._indices[key]\n \n >>> x = TestClass()\n >>> x.constant['pi']\n 3.14\n >>> x.constant['PI']\n 3.14\n >>> x.constant['PI'] = 5\n Traceback (most recent call last):\n NotImplementedError: no property setter defined\n >>> sorted(x.constant.items())\n [('E', 2.718), ('I', 1j), ('PI', 3.14), ('TAU', 6.28)]\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/NJDFan/indexedproperty", "keywords": "properties", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "indexedproperty", "package_url": "https://pypi.org/project/indexedproperty/", "platform": "", "project_url": "https://pypi.org/project/indexedproperty/", "project_urls": { "Homepage": "https://github.com/NJDFan/indexedproperty" }, "release_url": "https://pypi.org/project/indexedproperty/0.1.4/", "requires_dist": null, "requires_python": "", "summary": "Properties with item-style access", "version": "0.1.4" }, "last_serial": 3186716, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "b1ad483e1fa97ea6dd965e677c6b5edc", "sha256": "c32c22bfa3eb5c0e63732fc793565cb2a590ee8fdaa7881ae406e24e80a082b9" }, "downloads": -1, "filename": "indexedproperty-0.1.0.tar.gz", "has_sig": false, "md5_digest": "b1ad483e1fa97ea6dd965e677c6b5edc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11585, "upload_time": "2016-03-19T01:29:49", "url": "https://files.pythonhosted.org/packages/39/b0/ad9fe74eb9c21418e89ad4d6c92d647391ef8e44b390aaf6d640a3977696/indexedproperty-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "45da528426fe1bdb75f8a99fc8c30c91", "sha256": "10aaf3b88af0ae585e7564bbc73783f3455104269a03628500772d076f0877a3" }, "downloads": -1, "filename": "indexedproperty-0.1.1.tar.gz", "has_sig": false, "md5_digest": "45da528426fe1bdb75f8a99fc8c30c91", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11811, "upload_time": "2016-03-21T18:06:46", "url": "https://files.pythonhosted.org/packages/f5/70/55f3da2c479bdd0014eb254f19b8f06b8b9a8bc96bf02f656d0346692d49/indexedproperty-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "cc267d9f394246b09fa2b69555687b2e", "sha256": "d60e797a0e85432a2f18aa90b65d8c4a99c22e19c1a8b60cf7cf6307a94c0c67" }, "downloads": -1, "filename": "indexedproperty-0.1.2.tar.gz", "has_sig": false, "md5_digest": "cc267d9f394246b09fa2b69555687b2e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12850, "upload_time": "2017-07-05T21:14:17", "url": "https://files.pythonhosted.org/packages/ba/f1/75be13eca0232bbf830383816dd184a69b95b4866291de7b013ad7ea898c/indexedproperty-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "b69a6e7c139218f8e450ba31af9af512", "sha256": "928feef4eb7dfe317e55751394e3905e6b8bbfd317c5dba90f56769008c200d3" }, "downloads": -1, "filename": "indexedproperty-0.1.3.tar.gz", "has_sig": false, "md5_digest": "b69a6e7c139218f8e450ba31af9af512", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18319, "upload_time": "2017-07-07T17:39:20", "url": "https://files.pythonhosted.org/packages/33/d1/a6e48289011610b8e7e4e441b6611bb965d898515c7ed640d3e21fd1d996/indexedproperty-0.1.3.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "76490ac63b34c733cf4a94a021d416dd", "sha256": "b0b7cda071b67698e401513a5192a3dcd6a18ea044430e08d797658f9cabeec8" }, "downloads": -1, "filename": "indexedproperty-0.1.4.tar.gz", "has_sig": false, "md5_digest": "76490ac63b34c733cf4a94a021d416dd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18321, "upload_time": "2017-09-19T22:01:33", "url": "https://files.pythonhosted.org/packages/54/cb/24522d66c38a3c1579973fb077f137b299a897d1ab3e8aa90e0a5ce7c449/indexedproperty-0.1.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "76490ac63b34c733cf4a94a021d416dd", "sha256": "b0b7cda071b67698e401513a5192a3dcd6a18ea044430e08d797658f9cabeec8" }, "downloads": -1, "filename": "indexedproperty-0.1.4.tar.gz", "has_sig": false, "md5_digest": "76490ac63b34c733cf4a94a021d416dd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18321, "upload_time": "2017-09-19T22:01:33", "url": "https://files.pythonhosted.org/packages/54/cb/24522d66c38a3c1579973fb077f137b299a897d1ab3e8aa90e0a5ce7c449/indexedproperty-0.1.4.tar.gz" } ] }