{ "info": { "author": "Lovely Systems", "author_email": "office@lovelysystems.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Zope3", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP" ], "description": "=======\nTagging\n=======\n\nA tagging engine allows you to assign tags to any type of object by an user. A\ntag is a simple string.\n\n >>> from lovely import tag\n\nTagging Engine\n--------------\n\nThe tagging engine provides the capabilities to manipulate and and query\ntagged items.\n\n >>> engine = tag.TaggingEngine()\n >>> engine\n \n\nThe first step is to associate tags with an item for a user. Items are\nreferenced by their intId, the user is a system-wide unique string and\nthe tags is a simple list of strings.\n\nBefore updating the engine we need to ensure that persistent objects can be\nadapted to key references:\n\n >>> import zope.component\n >>> from zope.app.keyreference import testing\n\n >>> zope.component.provideAdapter(testing.SimpleKeyReference)\n\n\nInstead providing a separate API for adding and updating tags, both actions\nare done via the ``update()`` method. Think of it as updating the tagging\nengine.\n\n >>> engine.update(1, u'srichter', [u'USA', u'personal'])\n >>> engine.update(2, u'srichter', [u'austria', u'lovely'])\n >>> engine.update(3, u'jodok', [u'Austria', u'personal'])\n >>> engine.update(2, u'jodok', [u'austria', u'lovely', u'work'])\n\nNext you can ask the engine several questions.\n\nQuerying for Tags\n~~~~~~~~~~~~~~~~~\n\nA common request is to ask for tags based on items and users. First, you can\nask for all tags for a particular item:\n\n >>> sorted(engine.getTags(items=(1,)))\n [u'USA', u'personal']\n\nNote: The query methods return sets.\n\n >>> type(engine.getTags())\n \n\nThe method always returns the normalized tag strings. You can also specify\nseveral items:\n\n >>> sorted(engine.getTags(items=(1, 2)))\n [u'USA', u'austria', u'lovely', u'personal', u'work']\n\nYou can also ask for tags of a user:\n\n >>> sorted(engine.getTags(users=(u'srichter',)))\n [u'USA', u'austria', u'lovely', u'personal']\n\nAgain, you can specify multiple users:\n\n >>> sorted(engine.getTags(users=(u'srichter', u'jodok')))\n [u'Austria', u'USA', u'austria', u'lovely', u'personal', u'work']\n\nFinally, you can also specify a combination of both:\n\n >>> sorted(engine.getTags(items=(1,), users=(u'srichter',)))\n [u'USA', u'personal']\n >>> sorted(engine.getTags(items=(1, 2), users=(u'srichter',)))\n [u'USA', u'austria', u'lovely', u'personal']\n >>> sorted(engine.getTags(items=(3,), users=(u'srichter',)))\n []\n\nYou can also query all tags by not specifying items or users:\n\n >>> sorted(engine.getTags())\n [u'Austria', u'USA', u'austria', u'lovely', u'personal', u'work']\n\n\nQuerying for Items\n~~~~~~~~~~~~~~~~~~\n\nThis method allows to look for items. For example, we would like to find all\nitems that have the \"personal\" tag:\n\n >>> sorted(engine.getItems(tags=(u'personal',)))\n [1, 3]\n\nNote: The query methods return sets.\n\n >>> type(engine.getItems())\n \n\nFurthermore, you can query for all items of a particular user:\n\n >>> sorted(engine.getItems(users=(u'srichter',)))\n [1, 2]\n >>> sorted(engine.getItems(users=(u'srichter', u'jodok')))\n [1, 2, 3]\n\nFinally, you can combine tag and user specifications:\n\n >>> sorted(engine.getItems(\n ... tags=(u'personal',), users=(u'srichter', u'jodok')))\n [1, 3]\n\nYou can also query all items by not specifying tags or users:\n\n >>> sorted(engine.getItems())\n [1, 2, 3]\n\n\nQuerying for Users\n~~~~~~~~~~~~~~~~~~\n\nSimilar to the two methods above, you can query for users. First we are\nlooking for all users specifying a particular tag.\n\n >>> sorted(engine.getUsers(tags=(u'personal',)))\n [u'jodok', u'srichter']\n >>> sorted(engine.getUsers(tags=(u'Austria',)))\n [u'jodok']\n\nNote: The query methods return sets.\n\n >>> type(engine.getUsers())\n \n\nNext you can also find all items that that have been tagged by a user:\n\n >>> sorted(engine.getUsers(items=(1,)))\n [u'srichter']\n >>> sorted(engine.getUsers(items=(2,)))\n [u'jodok', u'srichter']\n\nAs before you can combine the two criteria as well:\n\n >>> sorted(engine.getUsers(tags=(u'USA',), items=(1,)))\n [u'srichter']\n >>> sorted(engine.getUsers(tags=(u'personal',), items=(1, 3)))\n [u'jodok', u'srichter']\n\nYou can also query all users by not specifying tags or items:\n\n >>> sorted(engine.getUsers())\n [u'jodok', u'srichter']\n\n\nQuerying for Tagobjects\n~~~~~~~~~~~~~~~~~~~~~~~\n\nSometimes it is usefull to have the actual tag objects directly. These\ntag objects can be queried by tagnames, users and items.\n\n >>> sorted(engine.getTagObjects(tags=(u'personal',)))\n [,\n ]\n >>> sorted(engine.getTagObjects(tags=(u'personal',),\n ... users=(u'srichter',)))\n []\n >>> sorted(engine.getTagObjects(tags=(u'personal',),\n ... items=(3,)))\n []\n\nWe can also search fr\n\nTagging Statistics\n------------------\n\n >>> from lovely.tag.interfaces import ITaggingStatistics\n >>> ITaggingStatistics.providedBy(engine)\n True\n >>> engine.tagCount\n 6\n >>> engine.itemCount\n 3\n >>> engine.userCount\n 2\n\n\nCombining Queries\n-----------------\n\nSince those query methods return sets, you can easily combine them:\n\n >>> users1 = engine.getUsers(items=(1,))\n >>> users2 = engine.getUsers(items=(2,))\n >>> sorted(users1.intersection(users2))\n [u'srichter']\n\n\nChanging and deleting Entries\n-----------------------------\n\n\"srichter\" moved from USA to Germany:\n\n >>> engine.update(1, u'srichter', [u'Germany', u'personal'])\n >>> sorted(engine.getTags(items=(1,), users=(u'srichter',)))\n [u'Germany', u'personal']\n\n\nWe delete entries by passing an empty list to the update method:\n\n >>> engine.update(1, u'srichter', [])\n >>> sorted(engine.getTags(items=(1,)))\n []\n >>> sorted(engine.getTags())\n [u'Austria', u'austria', u'lovely', u'personal', u'work']\n >>> sorted(engine.getItems())\n [2, 3]\n\nNow let's delete the tags of the second item. We want to be sure that\n\"srichter\" can't be found anymore:\n\n >>> engine.update(2, u'srichter', [])\n >>> sorted(engine.getUsers())\n [u'jodok']\n\nIn order to delete entries globaly use the delete method described below.\n\nTag Object\n----------\n\nInternally, the tagging engine uses the ``Tag`` class to store all data about\none particular item, user and tag names pair.\n\n >>> from lovely.tag.tag import Tag\n\nThe ``Tag`` object is initialized with the three pieces information mentioned\nabove.\n\n >>> sample = Tag(1, u'user', u'tag1')\n >>> sample\n \n\nYou can also think of those three items as the unique key of the\ntag. Additionally to those three attributes, a creation date is also\nspecified:\n\n >>> sample.item\n 1\n >>> sample.user\n u'user'\n >>> sample.name\n u'tag1'\n >>> sample.timestamp\n datetime.datetime(...)\n\n\nTaggable Objects\n----------------\n\nTheoretically all objects are taggable. But this might not be desirable. Thus\nobjects must provide the ``ITaggable`` interface to be taggable.\n\n >>> import zope.interface\n\n >>> class Image(object):\n ... zope.interface.implements(tag.interfaces.ITaggable)\n >>> image = Image()\n\n >>> class File(object):\n ... pass\n >>> file = File()\n\nTaggable objects can then be adapted to the ``ITagging`` interface. For this\nto work we have to register the adapter:\n\n >>> zope.component.provideAdapter(tag.Tagging)\n\nBefore we can now use the tagging object, we need to register our tagging\nengine as well as the integer id generator as a utility:\n\n >>> zope.component.provideUtility(engine, tag.interfaces.ITaggingEngine)\n\n >>> from zope.app import intid\n >>> intIds = intid.IntIds()\n >>> zope.component.provideUtility(intIds, intid.interfaces.IIntIds)\n\nAdapting the file to be tagged should fail:\n\n >>> tag.interfaces.ITagging(file)\n Traceback (most recent call last):\n ...\n TypeError: ('Could not adapt', , )\n\nBut images can be tagged:\n\n >>> tagging = tag.interfaces.ITagging(image)\n\nAt first there are no tags for the image:\n\n >>> sorted(tagging.getTags())\n []\n\nLet's now have \"srichter\" and \"jodok\" add a few tags:\n\n >>> tagging.update(u'srichter', [u'home', u'USA'])\n >>> tagging.update(u'jodok', [u'vacation', u'USA'])\n\n >>> sorted(tagging.getTags())\n [u'USA', u'home', u'vacation']\n\nOf course, you can also ask just for the tags by \"srichter\":\n\n >>> sorted(tagging.getTags(users=[u'srichter']))\n [u'USA', u'home']\n\nFurther you can request to see all users that have tagged the image:\n\n >>> sorted(tagging.getUsers())\n [u'jodok', u'srichter']\n\nor all users that have specified a particular tag:\n\n >>> sorted(tagging.getUsers(tags=(u'home',)))\n [u'srichter']\n >>> sorted(tagging.getUsers(tags=(u'USA',)))\n [u'jodok', u'srichter']\n\nUsing Named Tagging Engines\n---------------------------\n\n >>> class INamedTagging(tag.interfaces.ITagging):\n ... pass\n >>> class NamedTagging(tag.Tagging):\n ... zope.interface.implements(INamedTagging)\n ... zope.component.adapts(tag.interfaces.ITaggable)\n ... engineName = 'IAmNamed'\n >>> zope.component.provideAdapter(NamedTagging,\n ... (tag.interfaces.ITaggable,),\n ... INamedTagging)\n\n >>> namedTagging = INamedTagging(image)\n >>> namedTagging.tags = ['named1', 'named2']\n >>> namedTagging.update(u'jukart', [u'works', u'hard'])\n Traceback (most recent call last):\n ...\n ComponentLookupError: (, 'IAmNamed')\n\nWe have no named tagging engine registered yet. Let's see what happens if we\nupdate with an empty list of tags.\n\n >>> namedTagging.update(u'jukart', [])\n\nIf we update without tags it is possible that we do this because an object has\nbeen deleted. This is usually done in an event handler for ObjectRemovedEvent.\nIf we would raise an exeption in this case it is not possible to delete a site.\n\nNow we register a named tagging engine.\n\n >>> namedEngine = tag.TaggingEngine()\n >>> zope.component.provideUtility(namedEngine, tag.interfaces.ITaggingEngine,\n ... name='IAmNamed')\n\n >>> namedTagging = INamedTagging(image)\n >>> namedTagging.tags = ['named1', 'named2']\n >>> sorted(namedTagging.getTags())\n []\n >>> namedTagging.update(u'jukart', [u'works', u'hard'])\n >>> sorted(namedTagging.getTags())\n [u'hard', u'works']\n\nThe new tags are not in the unnamed tagging engine.\n\n >>> sorted(tagging.getTags())\n [u'USA', u'home', u'vacation']\n\n\nIUserTagging\n------------\n\nThere is also an adapter for ITaggable objects which provides a simple\ntag attribute which accepts a list of tags defined for the ITaggable\nby the current principal.\n\n >>> zope.component.provideAdapter(tag.UserTagging)\n >>> userTagging = tag.interfaces.IUserTagging(image)\n >>> userTagging.tags\n Traceback (most recent call last):\n ...\n ValueError: User not found\n\nWe get a ValueError because we have no interaction in this test, and\ntherefore the implementation cannot find the principal. We have to\ncreate a principal and a participation.\n\n >>> from zope.security.testing import Principal, Participation\n >>> from zope.security import management\n >>> p = Principal(u'srichter')\n >>> participation = Participation(p)\n >>> management.endInteraction()\n >>> management.newInteraction(participation)\n >>> sorted(userTagging.tags)\n [u'USA', u'home']\n >>> userTagging.tags = [u'zope3', u'guru']\n >>> sorted(userTagging.tags)\n [u'guru', u'zope3']\n\nTag Clouds\n----------\n\nAll portals like Flickr, del.icio.us use tagging and generate tag clouds.\nTag clouds contain tags and their frequency.\n\nThe ``getCloud`` method returns a set of tuples in the form of\n('tag', frequency). It takes the same arguments as getTags.\n\n >>> type(engine.getCloud())\n \n\nNow let's add some tags to generate clouds later:\n\n >>> engine.update(3, u'michael', [u'Austria', u'Bizau'])\n >>> engine.update(2, u'michael', [u'lovely', u'USA'])\n >>> engine.update(1, u'jodok', [u'USA',])\n\nThe most common use-case is to generate a global tag cloud.\n\n >>> sorted(engine.getCloud())\n [(u'Austria', 2), (u'Bizau', 1), (u'USA', 3), (u'austria', 1),\n (u'guru', 1), (u'lovely', 2), (u'personal', 1), (u'vacation', 1),\n (u'work', 1), (u'zope3', 1)]\n\nOf course you can generate clouds on item basis. You can't pass a tuple of\nitems, only a single one is allowed:\n\n >>> sorted(engine.getCloud(items=[1]))\n [(u'USA', 1)]\n\nThe same applies to queries by user:\n\n >>> sorted(engine.getCloud(users=[u'srichter']))\n [(u'guru', 1), (u'zope3', 1)]\n\nOr more users, and a few items.\n\n >>> sorted(engine.getCloud(items=[1, 2, 3], users=[u'srichter', u'jodok']))\n [(u'Austria', 1), (u'USA', 1), (u'austria', 1),\n (u'lovely', 1), (u'personal', 1), (u'work', 1)]\n\nRe-updating tags for same user does not affect cloud weight\n\n >>> engine.update(1, u'jodok', [u'USA',])\n >>> sorted(engine.getCloud(items=[1, 2, 3], users=[u'srichter', u'jodok']))\n [(u'Austria', 1), (u'USA', 1), (u'austria', 1),\n (u'lovely', 1), (u'personal', 1), (u'work', 1)]\n\n\nRe-updating tags for same user does not affect cloud weight\n\n >>> engine.update(1, u'jodok', [u'USA',])\n >>> sorted(engine.getCloud(items=[1, 2, 3], users=[u'srichter', u'jodok']))\n [(u'Austria', 1), (u'USA', 1), (u'austria', 1),\n (u'lovely', 1), (u'personal', 1), (u'work', 1)]\n\n\nRelated Tags\n------------\n\nAn advanced feature of the tagging engine is to find all tags that are related\nto a given tag.\n\n >>> sorted(engine.getRelatedTags(u'austria'))\n [u'lovely', u'work']\n\nBy default the method only searches for the first degree related tags. You can\nalso search for other degrees:\n\n >>> engine.update(4, u'jodok', [u'lovely', u'dornbirn', u'personal'])\n >>> sorted(engine.getRelatedTags(u'austria', degree=2))\n [u'USA', u'dornbirn', u'lovely', u'personal', u'work']\n\n >>> engine.update(4, u'jodok', [u'lovely', u'dornbirn', u'personal'])\n >>> sorted(engine.getRelatedTags(u'austria', degree=3))\n [u'Austria', u'USA', u'dornbirn', u'lovely', u'personal',\n u'vacation', u'work']\n\n\nRelated Items\n-------------\n\nAnother advanced feature is to provide related items.\n\nWe set up a new engine for this test. Items are related if they have at least\none tag in common.\n\n >>> relatedEngine = tag.TaggingEngine()\n >>> relatedEngine.update(1, u'srichter', [u'USA', u'personal', u'zope'])\n >>> relatedEngine.update(2, u'srichter', [u'austria', u'lovely'])\n >>> relatedEngine.update(3, u'jodok', [u'Austria', u'personal'])\n >>> relatedEngine.update(2, u'jodok', [u'austria', u'lovely', u'work'])\n >>> relatedEngine.update(4, u'jukart', [u'austria', u'Austria', u'lovely', u'work'])\n >>> relatedEngine.update(5, u'jim', [u'USA', u'zope'])\n\nWe get tuples with the related item and the number of tags in common.\n\n >>> relatedEngine.getRelatedItems(1)\n [(5, 2), (3, 1)]\n >>> relatedEngine.getRelatedItems(5)\n [(1, 2)]\n >>> relatedEngine.getRelatedItems(2)\n [(4, 3)]\n\n\nRelated Users\n-------------\n\nWe can also get related users. Users are related if they have at least one tag\nin common.\n\n >>> relatedEngine.getRelatedUsers(u'jim')\n [(u'srichter', 2)]\n >>> relatedEngine.getRelatedUsers(u'jodok')\n [(u'jukart', 4), (u'srichter', 3)]\n\n\nFrequency Of Tags\n-----------------\n\nIf we have a list of tags we can ask for the frequencies of the tags.\n\n >>> sorted(engine.getFrequency([u'Austria', u'USA']))\n [(u'Austria', 2), (u'USA', 3)]\n\nWe get a frequency of 0 if we ask for a tag which is not in the engine.\n\n >>> sorted(engine.getFrequency([u'Austria', u'jukart', u'USA']))\n [(u'Austria', 2), (u'USA', 3), (u'jukart', 0)]\n\n\nRemoval of Tag objects\n----------------------\n\n\nWhen an object is unregistered from the intids utility it will be\nremoved from each engine. Let us see how much items we have so far.\n\n >>> len(engine.getItems())\n 5\n >>> len(namedEngine.getItems())\n 1\n\nWe can use the delete method of the tagging engine to delete tag\nobjects by defining the user, item or a tag name.\n\n >>> u'austria' in engine.getTags()\n True\n >>> engine.delete(tag=u'austria')\n >>> u'austria' in engine.getTags()\n False\n\nIf we delete tags for a user, the tags still exists for other users.\n\n >>> sorted(engine.getTags(users=(u'jodok',)))\n [u'Austria', u'USA', u'dornbirn', u'lovely',\n u'personal', u'vacation', u'work']\n >>> engine.delete(user=u'jodok')\n >>> sorted(engine.getTags(users=(u'jodok',)))\n []\n >>> sorted(engine.getTags())\n [u'Austria', u'Bizau', u'USA', u'guru', u'lovely', u'zope3']\n\nThis is also possible with items.\n\n >>> sorted(engine.getTags(items=(3,)))\n [u'Austria', u'Bizau']\n\nLet us add a tag tag from the item to another item to show the behaviour.\n\n >>> engine.update(2, u'srichter', [u'Austria'])\n >>> engine.delete(item=3)\n >>> sorted(engine.getTags(items=(3,)))\n []\n\nThe 'Austria' tag is still there.\n\n >>> sorted(engine.getTags())\n [u'Austria', u'USA', u'guru', u'lovely', u'zope3']\n\nLet us setup the handler and events.\n\n >>> from zope.component import eventtesting\n >>> from zope import event\n >>> from lovely.tag.engine import removeItemSubscriber\n >>> from zope.app.intid.interfaces import IntIdRemovedEvent\n >>> from zope.app.intid import removeIntIdSubscriber\n >>> zope.component.provideHandler(removeItemSubscriber)\n\nIf we now fire the intid remove event with our image object, it should\nget removed in both engines.\n\n >>> len(namedEngine.getItems())\n 1\n >>> len(engine.getItems())\n 2\n >>> removeIntIdSubscriber(image, None)\n >>> len(namedEngine.getItems())\n 0\n >>> len(engine.getItems())\n 1\n\n\nRemoving Stale Items\n--------------------\n\nYou can remove stale items from the tagging engine. Stale means that\nthe item is not available anymore by the intids utility.\n\nBecause we removed any objects with intids before, we have an empty\nintid utility.\n\n >>> sorted(intIds.refs.keys())\n []\n\nBut above we defined an item with an id that does not exist. So this\nis a stale item.\n\n >>> sorted(engine.getItems())\n [2]\n\nLet us add our image object again.\n\n >>> tagging = tag.interfaces.ITagging(image)\n >>> tagging.update(u'srichter', [u'newtag'])\n\nThis is our first and only entry in the intid util\n\n >>> intIds.refs.keys()[0] in engine.getItems()\n True\n\nOur stale entry is 2. The intids of the items deleted are returned.\n\n >>> 2 in engine.getItems()\n True\n >>> engine.cleanStaleItems()\n [2]\n\nWe now only have our real image item.\n\n >>> 2 in engine.getItems()\n False\n >>> len(engine.getItems())\n 1\n >>> sorted(engine.getItems())[0] == intIds.refs.keys()[0]\n True\n\n\nRenaming Tags\n-------------\n\nIt is also possible to rename tags globally in the engine.\n\n >>> tagging.update(u'srichter', [u'tagtorename', u'usa'])\n >>> tagging.update(u'jukart', [\n ... u'tagtorename', u'someothertag', u'renamedtag'])\n >>> engine.update(123, 'jukart', [u'tagtorename'])\n >>> sorted(engine.getTags())\n [u'renamedtag', u'someothertag', u'tagtorename', u'usa']\n >>> sorted(engine.getTags(users=[u'jukart']))\n [u'renamedtag', u'someothertag', u'tagtorename']\n >>> len(sorted(engine.getItems(tags=[u'tagtorename'])))\n 2\n >>> len(sorted(engine.getItems(tags=[u'renamedtag'])))\n 1\n >>> sorted(engine.getTags(users=[u'srichter']))\n [u'tagtorename', u'usa']\n\nThe rename method returns the number of renamed tag objects.\n\n >>> engine.rename(u'tagtorename', u'renamedtag')\n 3\n >>> sorted(engine.getTags())\n [u'renamedtag', u'someothertag', u'usa']\n\nTags are joined if the new name already exists.\n\n >>> sorted(engine.getTags(users=[u'jukart']))\n [u'renamedtag', u'someothertag']\n >>> sorted(engine.getTags(users=[u'srichter']))\n [u'renamedtag', u'usa']\n >>> len(sorted(engine.getItems(tags=[u'tagtorename'])))\n 0\n >>> len(sorted(engine.getItems(tags=[u'renamedtag'])))\n 2\n\nNormalizing Tags\n----------------\n\nIt is also possible to normalize tags with a callable ojbect which\nreturns a new name for any given name.\nlower case.\n\n >>> engine.update(123, 'jukart', [u'RenamedTag', u'USA'])\n >>> sorted(engine.getTags())\n [u'RenamedTag', u'USA', u'renamedtag', u'someothertag', u'usa']\n\nLet us normalize all tags to lowercase by using the lower function\nfrom the string module.\n\n >>> import string\n\nThe normalize method returns the number of tag objects affected.\n\n >>> engine.normalize(string.lower)\n 2\n >>> sorted(engine.getTags())\n [u'renamedtag', u'someothertag', u'usa']\n\nThe normalize method also accepts a python dotted name, which will be\nresolved to a global object.\n\n >>> engine.normalize('string.upper')\n 7\n >>> sorted(engine.getTags())\n [u'RENAMEDTAG', u'SOMEOTHERTAG', u'USA']\n\n\n=======\nCHANGES\n=======\n\n1.1.1 (2009-11-18)\n------------------\n\n- Renamed a conflicting 'zmi_views' menu entry. There were two\n entries entitled 'Manage'. The CSVExportView became 'CSV Export'.\n [trollfot]\n\n\n1.1.0 (2009-11-18)\n------------------\n\n- Fixed an important error on the tag update handling : tags were\n registered more than once if an update was made for the same user,\n item and tag. The issue came from the set comparison method, that\n uses the hash and not a classical cmp method. We had to introduce a\n comparison basis we called \"brain\" in order to get the expected\n behavior. A test was added to emphasis this behavior. [trollfot]\n\n\n1.0.0 (2009-07-24)\n------------------\n\n- Fixed tests to conform to latest packages.\n\n- Cleanup release boilerplate.\n\n\n0.3.0b2 (2007-07-18)\n--------------------\n\n- in case of a not matching query lovely.tag will not longer return\n None but an empty IFTreeSet. (this is caused because zope.app.catalog\n will ignore None which results in a boolean or operation)\n\n\n0.3.0b1 (2007-06-13)\n--------------------\n\n- use iobtree for tag persistence instead of persistent list and intid\n util, should be much faster now with big numbers of tags. (this is a\n new database generation)", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pypi.python.org/pypi/lovely.tag", "keywords": "zope3 lovely tag cloud", "license": "ZPL 2.1", "maintainer": null, "maintainer_email": null, "name": "lovely.tag", "package_url": "https://pypi.org/project/lovely.tag/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/lovely.tag/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://pypi.python.org/pypi/lovely.tag" }, "release_url": "https://pypi.org/project/lovely.tag/1.1.1/", "requires_dist": null, "requires_python": null, "summary": "A tagging engine for Zope 3", "version": "1.1.1" }, "last_serial": 683106, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "0072fa11fc5d87906f5e050207b98718", "sha256": "382b06beb847728fd3cb03de9438105e5e8e52f83f64be0fd90c5fedccf86bde" }, "downloads": -1, "filename": "lovely.tag-1.0.0.tar.gz", "has_sig": false, "md5_digest": "0072fa11fc5d87906f5e050207b98718", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 219898, "upload_time": "2009-07-24T20:56:41", "url": "https://files.pythonhosted.org/packages/9d/86/1b52c6c892df2d3772ae22c6f2815f0fcaf6db9d9486d2a578e2000dc3de/lovely.tag-1.0.0.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "c8dabee891f5cbc5f32dce5aca8a046d", "sha256": "8763e27c566740331594060275db1fb8c5c4e5e954f27892e91ee3fe0d4626bb" }, "downloads": -1, "filename": "lovely.tag-1.1.0.tar.gz", "has_sig": false, "md5_digest": "c8dabee891f5cbc5f32dce5aca8a046d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 216368, "upload_time": "2009-11-18T14:40:17", "url": "https://files.pythonhosted.org/packages/60/37/ef4ee305884f760153daf83cfdf467717cb210296f5a0945ca779701ec68/lovely.tag-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "b1e87d4ad0c3bc4b49fe865bb47ce086", "sha256": "47e9a1f1b105e78083cb40be1d76665a516669ae900d29e7503ba792bbbf963a" }, "downloads": -1, "filename": "lovely.tag-1.1.1.tar.gz", "has_sig": false, "md5_digest": "b1e87d4ad0c3bc4b49fe865bb47ce086", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 216610, "upload_time": "2009-11-18T17:29:14", "url": "https://files.pythonhosted.org/packages/4a/0f/999f71a74dac72e9266eb411dfae2334c4a9fc4c939ebec37a2399b233ba/lovely.tag-1.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "b1e87d4ad0c3bc4b49fe865bb47ce086", "sha256": "47e9a1f1b105e78083cb40be1d76665a516669ae900d29e7503ba792bbbf963a" }, "downloads": -1, "filename": "lovely.tag-1.1.1.tar.gz", "has_sig": false, "md5_digest": "b1e87d4ad0c3bc4b49fe865bb47ce086", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 216610, "upload_time": "2009-11-18T17:29:14", "url": "https://files.pythonhosted.org/packages/4a/0f/999f71a74dac72e9266eb411dfae2334c4a9fc4c939ebec37a2399b233ba/lovely.tag-1.1.1.tar.gz" } ] }