{ "info": { "author": "gocept gmbh & co. kg", "author_email": "mail@gocept.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Framework :: ZODB", "Framework :: Zope3", "Intended Audience :: Developers", "License :: OSI Approved", "License :: OSI Approved :: Zope Public License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2 :: Only", "Topic :: Database", "Topic :: Software Development" ], "description": "Copyright (c) 2007-2017 gocept gmbh & co. kg and contributors.\n\nAll Rights Reserved.\n\nThis software is subject to the provisions of the Zope Public License,\nVersion 2.1 (ZPL). A copy of the ZPL should accompany this distribution.\nTHIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL EXPRESS OR IMPLIED\nWARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS\nFOR A PARTICULAR PURPOSE.\n\n\n============\nIntroduction\n============\n\nThis package provides a reference implementation.\n\nThe specific properties of this implementation are:\n\n- intended to be used for intrinsic references\n\n- provides integrity enforcement\n\n- modelled partially after relational `foreign keys`\n\n.. contents::\n\n\nMotivation\n==========\n\nWhen developing an application we often find the need to reference objects\nthat are stored as application data. Examples of such objects include\ncentrally managed 'master data'.\n\nThe reference to those objects is typically intrinsic to the application we\ndevelop so they should behave like normal Python object references while being\nunder the control of our application.\n\nWithin the world of Zope and ZODB there are different ways to achieve this. The\nvarious approaches have different semantics and side effects. Our goal is to\nunify the way of intrinsically referencing objects and to provide the ability to\nswitch between different semantics as needed without rewriting application code\nand without the need to migrate persistent data structures (at least from the\napplication's point of view).\n\n\nModel comparison\n================\n\nOur goal was to determine the advantages and disadvantages of the different\nexisting approaches. We included three general approaches from the world of\nPython/Zope/ZODB as well as the standard relational approach to normalisation\ntables.\n\nWe used four criteria to describe each solution:\n\nReference data\n What data is stored to describe the reference?\n\nReference semantics\n What meaning does the reference have? How can its meaning change?\n\nIntegrity\n What might happen to the application if data that is involved in the\n reference changes or is deleted?\n\nSet/Lookup\n What does the application developer have to do to set a reference or\n look up a referenced object?\n\n\n====================== ========================================= =========================================== ======================================== ====================================================\nProperty Python references Weak references Key reference Relational DBs\n====================== ========================================= =========================================== ======================================== ====================================================\nReference data OID OID application-specific key application-specific (primary key + table name)\n\nReference semantics Refers to a specific Refers to a specific Refers to an object which Refers to an object (row) that is associated\n Python object Python object is associated with the saved key with the primary key at the time of the lookup.\n at the time of the lookup.\n\nIntegrity The reference stays valid, however, The reference might have become stale The reference might have become Dependening on the use of `foreign keys`\n the target object might have lost its and leave the referencing object in an stale. and the databases implementation of constraints.\n meaning for the application. invalid state. Can usually be forced to stay valid.\n\nSet/Lookup Normal Python attribute access. Use WeakRef wrapper to store and Depends on the implementation. Explicitly store the primary key.\n __call__ to lookup. Might use properties Might use properties for convenience. Use JOIN to look up.\n for convenience.\n====================== ========================================= =========================================== ======================================== ====================================================\n\n\nObservations\n============\n\n- Relational: every object (row) has a canonical place that defines a primary\n key.\n\n The ZODB (like a filesystem) can have multiple hard links to an object.\n Objects are deleted when the last hard link to an object is removed. This\n makes it impossible to use hard links for referencing an object because\n object deletion will not be noticed and the objects will continue to live.\n The ZODB itself does not have a notion of a canonical place where an object\n is defined.\n\n- Relational: When referencing an object we can enforce integrity by declaring\n a foreign key. This is orthogonal to the data stored.\n\n- Relational: As an application-level key is used for identifying the target\n of a reference, the application can choose to delete a row and re-add a row\n with the same primary key later. If the integrity is enforced this requires\n support on the database level to temporarily ignore broken foreign keys.\n\n- Normal Python references embed themselves naturally in the application.\n Properties allow hiding the implementation of looking up and storing\n references.\n\n\nConclusions & Requirements for the reference implementation\n===========================================================\n\n- Allow configuration of `foreign key` constraints (none, always, at the end\n of the transaction). This configuration must be changable at any time with\n an automatic migration path provided.\n\n- Use application level keys to refer to an object.\n\n- Use a canonical location and a primary key to store objects and to determine\n whether an object was deleted.\n\n- Distinguish between two use cases when modifying an object's key:\n\n 1. The application references the right object but has the wrong key (as the\n key itself might have meaning for the application). In this case the\n object must be updated to receive the new, correct key and the references\n must be updated to refer to this new key.\n\n 2. The application references the wrong object with the right key. In this\n case the object referenced by the key must be replaced with a different\n object.\n\n\nImplementation notes\n====================\n\n- Canonical location is determined by location/containment. The primary key for\n a reference is the referenced object's location.\n\n- Constraints are enforced by monitoring containment events.\n\n- The different ways of updating/changing a key's meaning are supported by an\n indirection that enumerates all keys and stores a `reference id` on the\n referencing object instead of the location. The two use cases for changing\n the meaning are implemented by:\n\n 1. associating a new path with an existing reference id\n\n 2. associating a new reference id with an existing path\n\n\n===================\nReferencing objects\n===================\n\nSimple references\n=================\n\nFor working with references you have to have a located site set:\n\n>>> import zope.site.hooks\n>>> root = getRootFolder()\n>>> zope.site.hooks.setSite(root)\n\nFor demonstration purposes we define two classes, one for referenced objects,\nthe other defining the reference. Classes using references have to implement\nIAttributeAnnotatable as references are stored as annotations:\n\n>>> from zope.container.contained import Contained\n>>> import gocept.reference\n>>> import zope.interface\n>>> from zope.annotation.interfaces import IAttributeAnnotatable\n\n>>> class Address(Contained):\n... zope.interface.implements(IAttributeAnnotatable)\n... city = gocept.reference.Reference()\n\n>>> class City(Contained):\n... pass\n\nAs instances of classes defined in a doctest cannot be persisted, we import\nimplementations of the classes from a real Python module:\n\n>>> from gocept.reference.testing import Address, City\n\nThe referenced objects must be stored in the ZODB and must be located:\n\n>>> root['dessau'] = City()\n>>> root['halle'] = City()\n>>> root['jena'] = City()\n\nIn order to reference an object, the object only needs to be assigned to the\nattribute implemented as a reference descriptor:\n\n>>> theuni = Address()\n>>> theuni.city = root['dessau']\n>>> theuni.city\n\n\nIt is also possible to assign `None` to let the reference point to no\nobject:\n\n>>> theuni.city = None\n>>> print theuni.city\nNone\n\nValues can be deleted, the descriptor raises an AttributeError then:\n\n>>> del theuni.city\n>>> theuni.city\nTraceback (most recent call last):\nAttributeError: city\n\nOnly contained objects can be assigned to a reference that has\nintegrity ensurance enabled:\n\n>>> theuni.city = 12\nTraceback (most recent call last):\nTypeError: ...\n\n\nIntegrity-ensured references\n============================\n\n>>> class Monument(Contained):\n... zope.interface.implements(IAttributeAnnotatable)\n... city = gocept.reference.Reference(ensure_integrity=True)\n>>> from gocept.reference.testing import Monument\n\nLocated source\n--------------\n\nReferential integrity can be ensured if the source of the reference is\nlocated:\n\n>>> root['fuchsturm'] = Monument()\n>>> root['fuchsturm'].city = root['dessau']\n>>> root['fuchsturm'].city is root['dessau']\nTrue\n\n>>> import transaction\n>>> transaction.commit()\n\n>>> del root['dessau']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move\n .\n The (sub-)object is\n still being referenced.\n\n>>> transaction.commit()\nTraceback (most recent call last):\nDoomedTransaction\n\n>>> transaction.abort()\n>>> 'dessau' in root\nTrue\n\nTo check whether an object is referenced, it can be adapted to\nIReferenceTarget:\n\n>>> from gocept.reference.interfaces import IReferenceTarget\n>>> IReferenceTarget(root['dessau']).is_referenced()\nTrue\n\n>>> root['fuchsturm'].city = None\n>>> IReferenceTarget(root['dessau']).is_referenced()\nFalse\n\n>>> del root['dessau']\n>>> 'dessau' in root\nFalse\n\nXXX References will also be correctly cancelled when the attribute or the\nsource is deleted.\n\n>>> del root['fuchsturm']\n\nNon-located source\n------------------\n\nIf the source of a reference is not located, we can do anything we want with\nreferences, including breaking them:\n\n>>> fuchsturm = Monument()\n>>> fuchsturm.city = root['jena']\n>>> fuchsturm.city is root['jena']\nTrue\n\n>>> del fuchsturm.city\n>>> fuchsturm.city\nTraceback (most recent call last):\nAttributeError: city\n\n>>> fuchsturm.city = root['jena']\n>>> fuchsturm.city is root['jena']\nTrue\n\n>>> del root['jena']\n>>> fuchsturm.city\nTraceback (most recent call last):\nLookupError: Reference target u'/jena' no longer exists.\n\n\nChanging the location state of the source\n-----------------------------------------\n\nWe cannot put an object with a broken reference back into containment since\nreferential integrity is not given:\n\n>>> transaction.commit()\n\n>>> root['fuchsturm'] = fuchsturm\nTraceback (most recent call last):\nLookupError: Reference target u'/jena' no longer exists.\n\nThe transaction was doomed, let's recover the last working state:\n\n>>> transaction.commit()\nTraceback (most recent call last):\nDoomedTransaction\n\n>>> transaction.abort()\n\nWe have to repair the fuchsturm object by hand as it was not part of the\ntransaction:\n\n>>> fuchsturm.__parent__ = fuchsturm.__name__ = None\n\n>>> from gocept.reference.interfaces import IReferenceSource\n>>> IReferenceSource(fuchsturm).verify_integrity()\nFalse\n\n>>> IReferenceTarget(root['halle']).is_referenced()\nFalse\n>>> fuchsturm.city = root['halle']\n>>> IReferenceSource(fuchsturm).verify_integrity()\nTrue\n>>> IReferenceTarget(root['halle']).is_referenced()\nFalse\n\n>>> root['fuchsturm'] = fuchsturm\n>>> IReferenceTarget(root['halle']).is_referenced()\nTrue\n\n>>> fuchsturm = root['fuchsturm']\n>>> del root['fuchsturm']\n>>> fuchsturm.city is root['halle']\nTrue\n\n>>> del root['halle']\n>>> 'halle' in root\nFalse\n\nHierarchical structures\n-----------------------\n\nTrying to delete objects that contain referenced objects with ensured\nintegrity is also forbidden:\n\n>>> import zope.container.sample\n>>> root['folder'] = zope.container.sample.SampleContainer()\n>>> root['folder']['frankfurt'] = City()\n>>> messeturm = Monument()\n>>> messeturm.city = root['folder']['frankfurt']\n>>> root['messeturm'] = messeturm\n\nDeleting the `folder` will fail now, because a subobject is being referenced.\nThe reference target API (IReferenceTarget) allows us to inspect it\nbeforehand:\n\n>>> from gocept.reference.interfaces import IReferenceTarget\n>>> folder_target = IReferenceTarget(root['folder'])\n>>> folder_target.is_referenced()\nTrue\n>>> folder_target.is_referenced(recursive=False)\nFalse\n\n\n>>> del root['folder']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move\n .\n The (sub-)object is still\n being referenced.\n\n\nUpgrading from unconstrained to constrained references\n------------------------------------------------------\n\nXXX\n\nDowngrading from integrity ensured references to unensured\n----------------------------------------------------------\n\nXXX\n\n\n=====================\nReference collections\n=====================\n\nTo have an attribute of an object reference multiple other objects using a\ncollection you can use a ReferenceCollection property.\n\nA collection behaves like a set and manages references while objects are added\nor removed from the set:\n\n>>> import zope.site.hooks\n>>> root = getRootFolder()\n>>> zope.site.hooks.setSite(root)\n\nWe need a class defining a ReferenceCollection. (Importing the class\nfrom the test module is necassary to persist instances of the class):\n\n>>> from zope.container.contained import Contained\n>>> import gocept.reference\n>>> import zope.interface\n>>> from zope.annotation.interfaces import IAttributeAnnotatable\n\n>>> class City(Contained):\n... zope.interface.implements(IAttributeAnnotatable)\n... cultural_institutions = gocept.reference.ReferenceCollection(\n... ensure_integrity=True)\n>>> from gocept.reference.testing import City\n\nInitially, the collection isn't set and accessing it causes an\nAttributeError:\n\n>>> halle = City()\n>>> halle.cultural_institutions\nTraceback (most recent call last):\nAttributeError: cultural_institutions\n\nSo we define some cultural institutions:\n\n>>> class CulturalInstitution(Contained):\n... title = None\n>>> from gocept.reference.testing import CulturalInstitution\n\n>>> root['theatre'] = CulturalInstitution()\n>>> root['cinema'] = CulturalInstitution()\n>>> root['park'] = CulturalInstitution()\n>>> import transaction\n>>> transaction.commit()\n\nTrying to set an individual value instead of a collection, raises a\nTypeError:\n\n>>> halle.cultural_institutions = root['park']\nTraceback (most recent call last):\nTypeError: can't be assigned as a reference collection: only sets are allowed.\n\n\nManaging whole sets\n===================\n\nAssigning a set works:\n\n>>> halle.cultural_institutions = set([root['park'], root['cinema']])\n>>> len(halle.cultural_institutions)\n2\n>>> list(halle.cultural_institutions)\n[,\n ]\n\nAs `halle` isn't located yet, the integrity ensurance doesn't notice\nreferenced objects being deleted:\n\n>>> del root['cinema']\n\nThe result is a broken reference:\n\n>>> list(halle.cultural_institutions)\nTraceback (most recent call last):\nLookupError: Reference target u'/cinema' no longer exists.\n\nAlso, we can not locate `halle` right now, as long as the reference is broken:\n\n>>> root['halle'] = halle\nTraceback (most recent call last):\nLookupError: Reference target u'/cinema' no longer exists.\n\nThe transaction was doomed, so we abort:\n\n>>> transaction.abort()\n\nUnfortunately, the `abort` doesn't roll-back the attributes of Halle because it\nwasn't part of the transaction yet (as it couldn't be added to the database).\nWe need to clean up manually, otherwise the next assignment won't raise any\nevents:\n\n>>> halle.__name__ = None\n>>> halle.__parent__ = None\n\nThe cinema is back now, and Halle is in an operational state again:\n\n>>> list(halle.cultural_institutions)\n[,\n ]\n\nNow we can add it to the database:\n\n>>> root['halle'] = halle\n\nAnd deleting a referenced object will cause an error:\n\n>>> del root['cinema']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move . The (sub-)object is still being referenced.\n\nWhen we remove the referencing collection, the target can be deleted again:\n\n>>> halle.cultural_institutions = None\n>>> del root['cinema']\n\nManaging individual items of sets\n=================================\n\nNote: We did not implement the set API 100%. We'll add methods as we need\nthem.\n\nIn addition to changing sets by assigning complete new sets, we can modify the\nsets with individual items just as the normal `set` API allows us to do.\n\nWe'll start out with an empty set:\n\n>>> root['jena'] = City()\n>>> root['jena'].cultural_institutions = set()\n\nOur reference engine turns this set into a different object which manages the\nreferences:\n\n>>> ci = root['jena'].cultural_institutions\n>>> ci\nInstrumentedSet([])\n\nWe can add new references, by adding objects to this set and the referenced\nintegrity is ensured:\n\n>>> ci.add(root['park'])\n>>> del root['park']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move . The (sub-)object is still being referenced.\n\nRemoving and discarding works:\n\n>>> ci.remove(root['park'])\n>>> del root['park']\n>>> root['park'] = CulturalInstitution()\n>>> ci.add(root['park'])\n>>> del root['park']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move . The (sub-)object is still being referenced.\n>>> ci.discard(root['park'])\n>>> del root['park']\n>>> ci.discard(root['halle'])\n\nClearing works:\n\n>>> ci.add(root['theatre'])\n>>> del root['theatre']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move . The (sub-)object is still being referenced.\n>>> ci.clear()\n>>> len(ci)\n0\n>>> del root['theatre']\n\n>>> root['cinema'] = CulturalInstitution()\n>>> root['cinema'].title = 'Cinema'\n>>> ci.add(root['cinema'])\n>>> del root['cinema']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move . The (sub-)object is still being referenced.\n>>> ci.pop().title\n'Cinema'\n>>> del root['cinema']\n\nUpdating works:\n\n>>> root['cinema'] = CulturalInstitution()\n>>> root['theatre'] = CulturalInstitution()\n>>> ci.update([root['cinema'], root['theatre']])\n>>> len(ci)\n2\n>>> del root['cinema']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move . The (sub-)object is still being referenced.\n>>> del root['theatre']\nTraceback (most recent call last):\nIntegrityError: Can't delete or move . The (sub-)object is still being referenced.\n\n\n=============================\nVerifying reference existence\n=============================\n\nIt is not so easy to verify a class implements an attribute as a\nreference as their usage is transparent.\n\nReferences\n==========\n\nLet's build an example interface and class using a reference:\n\n>>> import zope.interface\n>>> import gocept.reference\n>>> import zope.annotation.interfaces\n>>> class IAddress(zope.interface.Interface):\n... city = zope.interface.Attribute(\"City the address belonges to.\")\n>>> class Address(object):\n... zope.interface.implements(\n... zope.annotation.interfaces.IAttributeAnnotatable, IAddress)\n... city = gocept.reference.Reference()\n\nverifyClass does not check for attributes:\n\n>>> import zope.interface.verify\n>>> zope.interface.verify.verifyClass(IAddress, Address)\nTrue\n\nverifyObject tells that the object does not completly fulfill the\ninterface:\n\n>>> zope.interface.verify.verifyObject(IAddress, Address())\nTraceback (most recent call last):\nBrokenImplementation: An object has failed to implement interface \nThe city attribute was not provided.\n\nSetting a value on the reference attribute does not help because after\nthat it ist not possible to check if there is a reference as the\nreference is transparent. Even worse, a class which does not define\nthe required attribute and an instance thereof with the attribute set,\nlets the test pass without defining the reference at all:\n\n>>> class AddressWithoutReference(object):\n... zope.interface.implements(IAddress)\n>>> address_without_ref = AddressWithoutReference()\n>>> address_without_ref.city = None\n>>> zope.interface.verify.verifyObject(IAddress, address_without_ref)\nTrue\n\nSo we need a special verifyObject function which does a check on the\nclass if there is a missing attribute:\n\n>>> import gocept.reference.verify\n>>> gocept.reference.verify.verifyObject(IAddress, Address())\nTrue\n\nThis function is not fully fool proof because it also works with the\ninstance which has the attribute set. The reason for this behavior is\nthat the interface does not tell that the attribute must be\nimplemented as a reference:\n\n>>> gocept.reference.verify.verifyObject(IAddress, address_without_ref)\nTrue\n\nBut if the attribute which does not exist on the instance does not\nhave a reference descriptior on the class the gocept.reference's\nverifyObject can detect this:\n\n>>> class StrangeAddress(object):\n... zope.interface.implements(IAddress)\n... @property\n... def city(self):\n... raise AttributeError\n>>> strange_address = StrangeAddress()\n>>> gocept.reference.verify.verifyObject(IAddress, strange_address)\nTraceback (most recent call last):\nBrokenImplementation: An object has failed to implement interface \nThe city attribute was not provided.\n\nLike ``zope.interface.verify.verifyObject`` detects, too:\n\n>>> zope.interface.verify.verifyObject(IAddress, strange_address)\nTraceback (most recent call last):\nBrokenImplementation: An object has failed to implement interface \nThe city attribute was not provided.\n\n\nReference collections\n=====================\n\nReference collections suffer the same problem when checked with\nzope.inferface.verify.verfyObject:\n\n>>> class ICity(zope.interface.Interface):\n... cultural_institutions = zope.interface.Attribute(\n... \"Cultural institutions the city has.\")\n>>> class City(object):\n... zope.interface.implements(\n... zope.annotation.interfaces.IAttributeAnnotatable, ICity)\n... cultural_institutions = gocept.reference.ReferenceCollection()\n\n>>> zope.interface.verify.verifyObject(ICity, City())\nTraceback (most recent call last):\nBrokenImplementation: An object has failed to implement interface \nThe cultural_institutions attribute was not provided.\n\nBut the special variant in gocept.reference works for collections, too:\n\n>>> gocept.reference.verify.verifyObject(ICity, City())\nTrue\n\n=================\nzope.schema field\n=================\n\nTo comply with ``zope.schema``, ``gocept.reference`` has an own `set` field\nwhich has the internally used `InstrumentedSet` class as `type`.\n\nFor demonstration purposes we create an interface which uses both\n``gocept.reference.field.Set`` and ``zope.schema.Set``:\n\n>>> import gocept.reference\n>>> import gocept.reference.field\n>>> import zope.annotation.interfaces\n>>> import zope.interface\n>>> import zope.schema\n>>> import zope.schema.vocabulary\n>>> dumb_vocab = zope.schema.vocabulary.SimpleVocabulary.fromItems(())\n>>> class ICollector(zope.interface.Interface):\n... gr_items = gocept.reference.field.Set(\n... title=u'collected items using gocept.reference.field',\n... value_type=zope.schema.Choice(title=u'items', vocabulary=dumb_vocab)\n... )\n... zs_items = zope.schema.Set(\n... title=u'collected items using zope.schema',\n... value_type=zope.schema.Choice(title=u'items', vocabulary=dumb_vocab)\n... )\n\n>>> class Collector(object):\n... zope.interface.implements(\n... ICollector, zope.annotation.interfaces.IAttributeAnnotatable)\n... gr_items = gocept.reference.ReferenceCollection()\n... zs_items = gocept.reference.ReferenceCollection()\n\n>>> collector = Collector()\n>>> collector.gr_items = set()\n>>> collector.gr_items\nInstrumentedSet([])\n>>> collector.zs_items = set()\n>>> collector.zs_items\nInstrumentedSet([])\n\n``gocept.reference.field.Set`` validates both ``set`` and\n``InstrumentedSet`` and correctly, but raises an exception if\nsomething else is validated:\n\n>>> ICollector['gr_items'].bind(collector).validate(collector.gr_items) is None\nTrue\n>>> ICollector['gr_items'].bind(collector).validate(set([])) is None\nTrue\n>>> ICollector['gr_items'].bind(collector).validate([])\nTraceback (most recent call last):\nWrongType: ([], (,\n ))\n\nWhile ``zope.schema.Set`` fails at ``InstrumentedSet`` as expected:\n\n>>> ICollector['zs_items'].bind(collector).validate(collector.zs_items)\nTraceback (most recent call last):\nWrongType: (InstrumentedSet([]), , 'zs_items')\n>>> ICollector['zs_items'].bind(collector).validate(set([])) is None\nTrue\n>>> ICollector['zs_items'].bind(collector).validate([])\nTraceback (most recent call last):\nWrongType: ([], , 'zs_items')\n\n\n=======\nChanges\n=======\n\n0.9.4 (2017-05-15)\n==================\n\n- Version 0.9.3 did not include all files. Fixing this by adding a\n MANIFEST.in.\n\n\n0.9.3 (2017-05-14)\n==================\n\n- Use `pytest` as test runner.\n\n\n0.9.2 (2015-08-05)\n==================\n\n- Move repos to https://bitbucket.org/gocept/gocept.reference\n\n\n0.9.1 (2011-02-02)\n==================\n\n- Bug fixed: reference descriptors could not find out their attribute names\n when read from the class.\n\n- Bug fixed: the algorithm for digging up the attribute name of a reference\n descriptor on a class would not handle inherited references.\n\n\n0.9.0 (2010-09-18)\n==================\n\n- Depending on ``zope.generations`` instead of ``zope.app.generations``.\n\n\n0.8.0 (2010-08-20)\n==================\n\n- Updated tests to work with `zope.schema` 3.6.\n\n- Removed unused parameter of ``InstrumentedSet.__init__``.\n\n- Avoid ``sets`` module as it got deprecated in Python 2.6.\n\n\n0.7.2 (2009-06-30)\n==================\n\n- Fixed generation added in previous version.\n\n\n0.7.1 (2009-04-28)\n==================\n\n- Fixed reference counting for reference collections by keeping a usage\n counter for InstrumentedSets.\n\n- Added a tool that rebuilds all reference counts. Added a database generation\n that uses this tool to set up the new usage counts for InstrumentedSets.\n\n\n0.7.0 (2009-04-06)\n==================\n\n- Require newer ``zope.app.generations`` version to get rid of\n dependency on ``zope.app.zopeappgenerations``.\n\n\n0.6.2 (2009-03-27)\n==================\n\n- Validation of ``gocept.reference.field.Set`` now allows both\n ``InstrumentedSet`` and ``set`` in field validation, as both\n variants occur.\n\n\n0.6.1 (2009-03-27)\n==================\n\n- ``zope.app.form`` breaks encapsulation of the fields by using the\n ``_type`` attribute to convert form values to field values. Using\n ``InstrumentedSet`` as ``_type`` was a bad idea, as only the\n reference collection knows how to instantiate an\n ``InstrumentedSet``. Now the trick is done on validation where the\n ``_type`` gets set to ``InstrumentedSet`` temporarily.\n\n\n0.6 (2009-03-26)\n================\n\n- Take advantage of the simpler zope package dependencies achieved at the Grok\n cave sprint in January 2009.\n\n- Added zope.schema field ``gocept.reference.field.Set`` which has the\n internally used InstrumentedSet as field type, so validation does\n not fail.\n\n- gocept.reference 0.5.2 had a consistency bug: Causing a TypeError by\n trying to assign a non-collection to a ReferenceCollection attribute\n would break integrity enforcement for that attribute while keeping\n its previously assigned value.\n\n\n0.5.2 (2008-10-16)\n==================\n\n- Fixed: When upgrading gocept.reference to version 0.5.1, a\n duplication error was raised.\n\n\n0.5.1 (2008-10-10)\n==================\n\n- Made sure that the reference manager is installed using\n zope.app.generations before other packages depending on\n gocept.reference.\n\n0.5 (2008-09-11)\n================\n\n- Added specialized variant of zope.interface.verify.verifyObject\n which can handle references and reference collections correctly.\n\n\n0.4 (2008-09-08)\n================\n\n- Moved InstrumentedSet to use BTree data structures for better performance.\n\n- Added `update` method to InstrumentedSet.\n\n- Updated documentation.\n\n\n0.3 (2008-04-22)\n================\n\n- Added a `set` implementation for referencing collections of objects.\n\n0.2 (2007-12-21)\n================\n\n- Extended the API for `IReferenceTarget.is_referenced` to allow specifying\n whether to query for references recursively or only on a specific object.\n By default the query is recursive.\n\n- Fixed bug in the event handler for enforcing ensured constraints: referenced\n objects could be deleted if they were deleted together with a parent\n location.\n\n0.1 (2007-12-20)\n================\n\nInitial release.", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://bitbucket.org/gocept/gocept.reference", "keywords": "zodb zope3 intrinsic reference", "license": "ZPL 2.1", "maintainer": "", "maintainer_email": "", "name": "gocept.reference", "package_url": "https://pypi.org/project/gocept.reference/", "platform": "", "project_url": "https://pypi.org/project/gocept.reference/", "project_urls": { "Homepage": "https://bitbucket.org/gocept/gocept.reference" }, "release_url": "https://pypi.org/project/gocept.reference/0.9.4/", "requires_dist": null, "requires_python": "", "summary": "Intrinsic references for Zope/ZODB applications.", "version": "0.9.4" }, "last_serial": 2876164, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "a49da4963349a768306405c43b77ad7b", "sha256": "f206d4ea12ec273dbac6fcd46a723c829c5a149755bdcc4f735e3b5745d25f07" }, "downloads": -1, "filename": "gocept.reference-0.1.tar.gz", "has_sig": false, "md5_digest": "a49da4963349a768306405c43b77ad7b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12056, "upload_time": "2007-12-21T09:42:52", "url": "https://files.pythonhosted.org/packages/c0/ef/889932bfd4bc08c21a94ea387fa291dcf9432b3e344bbf5f20be8a9dc14f/gocept.reference-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "5c59e65de22db81debe95ba135480fa6", "sha256": "65dbeb2f20b082e099ce5ab5956ec5e98ad9ad96e07f0c5d6456e551bd6b7f6c" }, "downloads": -1, "filename": "gocept.reference-0.2.tar.gz", "has_sig": false, "md5_digest": "5c59e65de22db81debe95ba135480fa6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12827, "upload_time": "2007-12-21T14:10:21", "url": "https://files.pythonhosted.org/packages/7a/f3/e1a5d25c8e3efb2e455f18e1ddf17531c81c6d7ba7af74dd79fe64471a13/gocept.reference-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "f67d433c88f9f3622730af0dbc42a34d", "sha256": "67cffc11a1a6cae2823308fa50ecc9bf5f6693bb7439acf49df42df8d62cd446" }, "downloads": -1, "filename": "gocept.reference-0.3.tar.gz", "has_sig": false, "md5_digest": "f67d433c88f9f3622730af0dbc42a34d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14913, "upload_time": "2008-04-22T07:40:42", "url": "https://files.pythonhosted.org/packages/99/3c/72819388a9751c9ee94ee170e1de97491f16812762f626685321bdd0d353/gocept.reference-0.3.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "0f321fb4705e1e188d6c0a3971a98c61", "sha256": "9d5430aab8cab02be5ec415b9c6e106f9ee0e1aa754224ce1cd0777d4e0f2b48" }, "downloads": -1, "filename": "gocept.reference-0.4.tar.gz", "has_sig": false, "md5_digest": "0f321fb4705e1e188d6c0a3971a98c61", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21611, "upload_time": "2008-09-08T08:23:25", "url": "https://files.pythonhosted.org/packages/13/d3/cc8fe00d2027c6e8847f61b32f9c7d5844f4c36e2ef7a4ca0c151f1ea536/gocept.reference-0.4.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "817f9524b19e63c233c80d5b0a112a06", "sha256": "5badb511ae9f8b6e64025023b683749d9669bf45a7346fbfa1eba25e194d378d" }, "downloads": -1, "filename": "gocept.reference-0.5.tar.gz", "has_sig": false, "md5_digest": "817f9524b19e63c233c80d5b0a112a06", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24430, "upload_time": "2008-09-11T09:57:35", "url": "https://files.pythonhosted.org/packages/36/14/9c9d06a219fde6ff7567b9c06c671477aef7854430c6183ee594fa910c27/gocept.reference-0.5.tar.gz" } ], "0.5.1": [ { "comment_text": "", "digests": { "md5": "deea262d24836ca4503a003b3c1b9b20", "sha256": "af7f2897d891a2c0bce24f8cb81a7939a417eb27b7880253566c25dae9e3d143" }, "downloads": -1, "filename": "gocept.reference-0.5.1.tar.gz", "has_sig": false, "md5_digest": "deea262d24836ca4503a003b3c1b9b20", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24677, "upload_time": "2008-10-10T12:45:27", "url": "https://files.pythonhosted.org/packages/ef/16/253312fc81ae356a94f25188b1fd3a65938d76fd25f7c2761f502296cdca/gocept.reference-0.5.1.tar.gz" } ], "0.5.2": [ { "comment_text": "", "digests": { "md5": "1b373f417a85a2a4972cb91c56a25a54", "sha256": "51e2bfdf5e542205199809bae220ce785966fd9c4c1e2d23e214e27246decc31" }, "downloads": -1, "filename": "gocept.reference-0.5.2.tar.gz", "has_sig": false, "md5_digest": "1b373f417a85a2a4972cb91c56a25a54", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24724, "upload_time": "2008-10-16T08:13:45", "url": "https://files.pythonhosted.org/packages/48/60/17e079dda55794a0c5afc63700a5f38438308280268d7a1850e6e8839c83/gocept.reference-0.5.2.tar.gz" } ], "0.6": [ { "comment_text": "", "digests": { "md5": "e1ebc0a803b38a0bde99bcf3a6708720", "sha256": "f11cca199312ad364c8daad1317ca1e04edf2ddce04db83f144e024fc8ae4a76" }, "downloads": -1, "filename": "gocept.reference-0.6.tar.gz", "has_sig": false, "md5_digest": "e1ebc0a803b38a0bde99bcf3a6708720", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 28999, "upload_time": "2009-03-26T14:45:11", "url": "https://files.pythonhosted.org/packages/ac/85/6b5a161887ab2b725ae4f189768b6a844adf912039609da3e5dff91a2749/gocept.reference-0.6.tar.gz" } ], "0.6.1": [ { "comment_text": "", "digests": { "md5": "b4bb9cd251e95e1c48998fcacff281eb", "sha256": "3eea9fe39422c66d8c1377468fce9df2522a91424651798f9fdaace69e6f5366" }, "downloads": -1, "filename": "gocept.reference-0.6.1.tar.gz", "has_sig": false, "md5_digest": "b4bb9cd251e95e1c48998fcacff281eb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29681, "upload_time": "2009-03-27T15:35:25", "url": "https://files.pythonhosted.org/packages/5b/72/b3342b1b5c2dc9c5fd8d6d86eeda7a72290094e294036f5140f3410733d4/gocept.reference-0.6.1.tar.gz" } ], "0.6.2": [ { "comment_text": "", "digests": { "md5": "9c073f284366d2de4cfe9d6d717aeba3", "sha256": "c4e90eb16ce1315753f048507a194ae8749df2174a4c162ca5c3570d2b358304" }, "downloads": -1, "filename": "gocept.reference-0.6.2.tar.gz", "has_sig": false, "md5_digest": "9c073f284366d2de4cfe9d6d717aeba3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30215, "upload_time": "2009-03-27T16:15:35", "url": "https://files.pythonhosted.org/packages/38/f1/c99bc3723ea1eced1675d60442da95e1442fef29dc9d47d0bb66d28176e7/gocept.reference-0.6.2.tar.gz" } ], "0.7.0": [ { "comment_text": "", "digests": { "md5": "94b46126f5c474463baffe0b76d66104", "sha256": "43f4e6c05998becb5701a61d6fb4f980ad2d5443e4520ed5a32388b8d3de0e69" }, "downloads": -1, "filename": "gocept.reference-0.7.0.tar.gz", "has_sig": false, "md5_digest": "94b46126f5c474463baffe0b76d66104", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30363, "upload_time": "2009-04-06T10:02:44", "url": "https://files.pythonhosted.org/packages/70/7e/f229f834ba8adbe85b8bdecbf56030025f5128a44cf2e7ae80570e1848bc/gocept.reference-0.7.0.tar.gz" } ], "0.7.1": [ { "comment_text": "", "digests": { "md5": "feb62c8b5e9c92d26f6291fb0bf2442d", "sha256": "e245ad955c15d9afb02dbe3c819386075673e175f10acf903c58f2327f996854" }, "downloads": -1, "filename": "gocept.reference-0.7.1.tar.gz", "has_sig": true, "md5_digest": "feb62c8b5e9c92d26f6291fb0bf2442d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31434, "upload_time": "2009-04-28T21:02:56", "url": "https://files.pythonhosted.org/packages/3e/dd/c4dd35c8d4bd0452efdccdd52a07e330126061955c6700f0313867db37a1/gocept.reference-0.7.1.tar.gz" } ], "0.7.2": [ { "comment_text": "", "digests": { "md5": "b1d5f2be92d9eff68f3e3e57111ddd9a", "sha256": "a2e64f23388010f8a070cd93e78074155bb7c80ba67919c76d92f5ac0e65287e" }, "downloads": -1, "filename": "gocept.reference-0.7.2.tar.gz", "has_sig": false, "md5_digest": "b1d5f2be92d9eff68f3e3e57111ddd9a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33561, "upload_time": "2009-06-30T23:48:57", "url": "https://files.pythonhosted.org/packages/61/37/58db017e265203bd4dbb717f981773d01f194d70c2d0a192c8d2feef5898/gocept.reference-0.7.2.tar.gz" } ], "0.8.0": [ { "comment_text": "", "digests": { "md5": "e8564aff1524d9a5aa5767b50689c085", "sha256": "a0c01a889ba4011edf98b472cf5256c7a65761a426e81048c419bd8232ac7443" }, "downloads": -1, "filename": "gocept.reference-0.8.0.tar.gz", "has_sig": false, "md5_digest": "e8564aff1524d9a5aa5767b50689c085", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33364, "upload_time": "2010-08-20T16:05:09", "url": "https://files.pythonhosted.org/packages/6e/55/c4bb396f6c7e90fb04320019e82f077d039afbf74cedef75e9591dee2109/gocept.reference-0.8.0.tar.gz" } ], "0.9.0": [ { "comment_text": "", "digests": { "md5": "da0230d48c25f1998b211ae8e707bf47", "sha256": "cb2d8faf49c411419bcb13ec9f3d9d2905b4bf6a913af4efa7d0d07e41cea71a" }, "downloads": -1, "filename": "gocept.reference-0.9.0.tar.gz", "has_sig": false, "md5_digest": "da0230d48c25f1998b211ae8e707bf47", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33424, "upload_time": "2010-09-18T14:16:52", "url": "https://files.pythonhosted.org/packages/0a/d6/1271cb19989001e6699e6aa89d34cf54d108e092f6d50d42617090b6fa63/gocept.reference-0.9.0.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "597dd31dde9c1c73e1b95d2a0b588692", "sha256": "8e277b7875cbd7bed5ccf779f593b982a7719b8d07dd18cad0b2a2c26d8e5fab" }, "downloads": -1, "filename": "gocept.reference-0.9.1.tar.gz", "has_sig": false, "md5_digest": "597dd31dde9c1c73e1b95d2a0b588692", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32721, "upload_time": "2011-02-02T09:09:41", "url": "https://files.pythonhosted.org/packages/a7/31/db31ea10d6d6d334a26cae506da948fcb618ca37c9d9ef79ff7752abd330/gocept.reference-0.9.1.tar.gz" } ], "0.9.2": [ { "comment_text": "", "digests": { "md5": "ad1bd57b02f45d66fbce1758a7322328", "sha256": "6788cb5990567eef418b92929c9da8fb424b44250ddf42b89d1455a3084b9214" }, "downloads": -1, "filename": "gocept.reference-0.9.2.tar.gz", "has_sig": false, "md5_digest": "ad1bd57b02f45d66fbce1758a7322328", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41408, "upload_time": "2015-08-05T07:29:47", "url": "https://files.pythonhosted.org/packages/c2/f8/0b58ae1944559b65e80113b88a4e070486f71dcab3935fc09d957bfd930e/gocept.reference-0.9.2.tar.gz" } ], "0.9.3": [ { "comment_text": "", "digests": { "md5": "b96b697535156b9b2fe122b87f4b4c67", "sha256": "98f6b8d4619704bb657fd331928700ae778e79c5fafcf55f323bc3e90b61b501" }, "downloads": -1, "filename": "gocept.reference-0.9.3.tar.gz", "has_sig": false, "md5_digest": "b96b697535156b9b2fe122b87f4b4c67", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26185, "upload_time": "2017-05-14T19:12:23", "url": "https://files.pythonhosted.org/packages/f9/3d/9c059a7632a974df1598a25cd60d3016b20a249b799dd5831d068bec5caa/gocept.reference-0.9.3.tar.gz" } ], "0.9.4": [ { "comment_text": "", "digests": { "md5": "01485eacaa9dd0b54816e3856a3d73dc", "sha256": "734ae251b661076883a5eb7bd46465e3533bae5c3fc9360e4b37b8fe223db396" }, "downloads": -1, "filename": "gocept.reference-0.9.4.tar.gz", "has_sig": false, "md5_digest": "01485eacaa9dd0b54816e3856a3d73dc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 39001, "upload_time": "2017-05-15T18:53:06", "url": "https://files.pythonhosted.org/packages/ca/ff/846aa08921319510d50801af0857ed4630171c1f817b450ee9e2c998d4f0/gocept.reference-0.9.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "01485eacaa9dd0b54816e3856a3d73dc", "sha256": "734ae251b661076883a5eb7bd46465e3533bae5c3fc9360e4b37b8fe223db396" }, "downloads": -1, "filename": "gocept.reference-0.9.4.tar.gz", "has_sig": false, "md5_digest": "01485eacaa9dd0b54816e3856a3d73dc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 39001, "upload_time": "2017-05-15T18:53:06", "url": "https://files.pythonhosted.org/packages/ca/ff/846aa08921319510d50801af0857ed4630171c1f817b450ee9e2c998d4f0/gocept.reference-0.9.4.tar.gz" } ] }