add new 'link' package for general-purpose link and relation management
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3678 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
		
							parent
							
								
									e82034c31e
								
							
						
					
					
						commit
						24157bb323
					
				
					 5 changed files with 371 additions and 0 deletions
				
			
		
							
								
								
									
										55
									
								
								link/README.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								link/README.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| ============================================ | ||||
| General-purpose Link and Relation Management | ||||
| ============================================ | ||||
| 
 | ||||
|   ($Id$) | ||||
| 
 | ||||
| Basic setup | ||||
| ----------- | ||||
| 
 | ||||
|   >>> from zope import component | ||||
|   >>> from cybertools.link.tests import IntIdsStub | ||||
|   >>> intids = IntIdsStub() | ||||
|   >>> component.provideUtility(intids) | ||||
| 
 | ||||
|   >>> from cybertools.link.base import LinkManager | ||||
|   >>> links = LinkManager() | ||||
| 
 | ||||
| Create and link objects | ||||
| ----------------------- | ||||
| 
 | ||||
| We create a simple class to derive objects from it. | ||||
| 
 | ||||
|   >>> class Page(object): | ||||
|   ...     pass | ||||
| 
 | ||||
|   >>> p1 = Page() | ||||
|   >>> p2 = Page() | ||||
| 
 | ||||
| These objects have to be registered with the IntIds utility. | ||||
| 
 | ||||
|   >>> intids.register(p1) | ||||
|   0 | ||||
|   >>> intids.register(p2) | ||||
|   1 | ||||
| 
 | ||||
| Now we can create a link from p1 to p2. | ||||
| Usually the link gets a name that is related to the target. | ||||
| 
 | ||||
|   >>> l01 = links.createLink(name='p2', source=p1, target=p2) | ||||
| 
 | ||||
| Let's have a look at the newly created link and the default values of some | ||||
| of its attributes. | ||||
| 
 | ||||
|   >>> (l01.identifier, l01.source, l01.target, l01.name, l01.linkType, l01.state, | ||||
|   ...  l01.relevance, l01.order) | ||||
|   (1, 0, 1, 'p2', u'link', u'valid', 1.0, 0) | ||||
| 
 | ||||
| Query for links | ||||
| --------------- | ||||
| 
 | ||||
| We are now able to query the link manager for links, e.g. using name and | ||||
| source for finding all corresponding links on a page. | ||||
| 
 | ||||
|   >>> [l.identifier for l in links.query(name='p2', source=p1)] | ||||
|   [1] | ||||
							
								
								
									
										3
									
								
								link/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								link/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| """ | ||||
| $Id$ | ||||
| """ | ||||
							
								
								
									
										170
									
								
								link/base.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								link/base.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,170 @@ | |||
| # | ||||
| #  Copyright (c) 2010 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| A simple generic, general-purpose link management framework. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from BTrees.IFBTree import intersection, union | ||||
| from BTrees.IOBTree import IOBTree | ||||
| from persistent import Persistent | ||||
| from zope import component | ||||
| from zope.component import adapts | ||||
| from zope.index.field import FieldIndex | ||||
| from zope.interface import implements | ||||
| from zope.app.intid.interfaces import IIntIds | ||||
| 
 | ||||
| from cybertools.link.interfaces import ILinkManager, ILink | ||||
| 
 | ||||
| 
 | ||||
| class LinkManager(Persistent): | ||||
|     """ A manager (storage, registry) for link objects. | ||||
|     """ | ||||
| 
 | ||||
|     implements(ILinkManager) | ||||
| 
 | ||||
|     uid = int | ||||
| 
 | ||||
|     # initialization | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.links = IOBTree() | ||||
|         self.setupIndexes() | ||||
|         self.currentId = 0 | ||||
| 
 | ||||
|     def setupIndexes(self): | ||||
|         self.indexes = dict( | ||||
|                 name=FieldIndex(), | ||||
|                 source=FieldIndex(), | ||||
|                 target=FieldIndex()) | ||||
| 
 | ||||
|     # public methods | ||||
| 
 | ||||
|     def createLink(self, **kw): | ||||
|         if not 'name' in kw or not 'source' in kw: | ||||
|             raise ValueError("At least 'name' and 'source' attributes must " | ||||
|                              "be given.") | ||||
|         identifier = self.generateIdentifier() | ||||
|         link = Link(self, identifier) | ||||
|         link.update(**kw) | ||||
|         self.getLinks()[identifier] = link | ||||
|         self.indexLink(link) | ||||
|         return link | ||||
| 
 | ||||
|     def removeLink(self, link): | ||||
|         del self.getLinks()[link.identifier] | ||||
| 
 | ||||
|     def getLink(self, identifier): | ||||
|         return self.getLinks()[identifier] | ||||
| 
 | ||||
|     def query(self, name=None, source=None, target=None, **kw): | ||||
|         source = self.getUniqueId(source) | ||||
|         target = self.getUniqueId(target) | ||||
|         r = None | ||||
|         if name is not None: | ||||
|             r = self.indexes['name'].apply((name, name)) | ||||
|         if source is not None: | ||||
|             r = self.intersect(r, self.indexes['source'].apply((source, source))) | ||||
|         if target is not None: | ||||
|             r = self.intersect(r, self.indexes['target'].apply((target, target))) | ||||
|         if r is None: | ||||
|             raise ValueError("At least one critera of 'name', 'source', or " | ||||
|                              "'target' must be given.") | ||||
|         result = [self.getLink(id) for id in r] | ||||
|         #for k, v in kw: | ||||
|         #    result = [link for link in result if getattr(link, k) == v] | ||||
|         return sorted(result, key=lambda x: (x.order, x.name)) | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         return self.getLinks().values() | ||||
| 
 | ||||
|     # protected methods | ||||
| 
 | ||||
|     def getLinks(self): | ||||
|         return self.links | ||||
| 
 | ||||
|     def generateIdentifier(self): | ||||
|         self.currentId += 1 | ||||
|         return self.currentId | ||||
| 
 | ||||
|     def indexLink(self, link): | ||||
|         for attr, idx in self.indexes.items(): | ||||
|             value = getattr(link, attr) | ||||
|             if value is None: | ||||
|                 idx.unindex_doc(link.identifier) | ||||
|             else: | ||||
|                 idx.index_doc(link.identifier, value) | ||||
| 
 | ||||
|     def intersect(self, r1, r2): | ||||
|         return r1 is None and r2 or intersection(r1, r2) | ||||
| 
 | ||||
|     def getUniqueId(self, obj): | ||||
|         if obj is None: | ||||
|             return None | ||||
|         if isinstance(obj, self.uid): | ||||
|             return obj | ||||
|         # TODO: take external objects (referenced by URIs) in to account | ||||
|         return component.getUtility(IIntIds).getId(obj) | ||||
| 
 | ||||
|     def getObject(self, uid): | ||||
|         return component.getUtility(IIntIds).getObject(uid) | ||||
| 
 | ||||
| 
 | ||||
| class Link(Persistent): | ||||
|     """ A basic link implementation. | ||||
|     """ | ||||
| 
 | ||||
|     implements(ILink) | ||||
| 
 | ||||
|     defaults = dict(target=None, | ||||
|                     linkType=u'link', | ||||
|                     state=u'valid', | ||||
|                     relevance=1.0, | ||||
|                     order=0,) | ||||
| 
 | ||||
|     def __init__(self, manager, identifier): | ||||
|         self.manager = self.__parent__ = manager | ||||
|         self.identifier = self.__name__ = identifier | ||||
| 
 | ||||
|     def getManager(self): | ||||
|         return self.manager | ||||
| 
 | ||||
|     def getSource(self): | ||||
|         return self.getManager().getObject(self.source) | ||||
| 
 | ||||
|     def getTarget(self): | ||||
|         return self.getManager().getObject(self.target) | ||||
| 
 | ||||
|     def update(self, **kw): | ||||
|         manager = self.getManager() | ||||
|         for k in ('source', 'target'): | ||||
|             kw[k] = manager.getUniqueId(kw[k]) | ||||
|         for k, v in kw.items(): | ||||
|             setattr(self, k, v) | ||||
|         for k in manager.indexes: | ||||
|             if k in kw: | ||||
|                 manager.indexLink(self) | ||||
|                 break | ||||
| 
 | ||||
|     def __getattr__(self, attr): | ||||
|         if attr not in ILink: | ||||
|             raise AttributeError(attr) | ||||
|         value = self.defaults.get(attr, u'') | ||||
|         return value | ||||
							
								
								
									
										92
									
								
								link/interfaces.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								link/interfaces.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| # | ||||
| #  Copyright (c) 2010 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Interfaces for Wiki functionality. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import Interface, Attribute | ||||
| from zope import schema | ||||
| 
 | ||||
| 
 | ||||
| class ILinkManager(Interface): | ||||
|     """ Manages (and possibly contains) all kinds of wiki-related links. | ||||
|     """ | ||||
| 
 | ||||
|     def createLink(name, source, target, **kw): | ||||
|         """ Create, register, and return a link record. | ||||
| 
 | ||||
|             Optional attributes are given as keyword arguments. | ||||
|         """ | ||||
| 
 | ||||
|     def removeLink(link): | ||||
|         """ Remove a link. | ||||
|         """ | ||||
| 
 | ||||
|     def getLink(identifier): | ||||
|         """ Return the link record identfied by the identifier given or None if | ||||
|             not present. | ||||
|         """ | ||||
| 
 | ||||
|     def query(name=None, source=None, target=None, **kw): | ||||
|         """ Search for link records matching the criteria given. One of | ||||
|             name, source or target must be given. | ||||
|         """ | ||||
| 
 | ||||
|     def __iter__(): | ||||
|         """ Return an iterator of all links. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| class ILink(Interface): | ||||
|     """ A directed link (connection, relation) between two local or foreign | ||||
|         objects. | ||||
| 
 | ||||
|         The combination of name and source usually uniquely identfy a link. | ||||
|     """ | ||||
| 
 | ||||
|     identifier = Attribute('An internal identifier of the link record, ' | ||||
|                 'unique within the link manager.') | ||||
|     name = Attribute('The external identifier for the link, i.e. the ' | ||||
|                 'string used in the source text to address the link.') | ||||
|     source = Attribute('Identifier of the link\'s source object.') | ||||
|     target = Attribute('Identifier of the link\'s target object or - ' | ||||
|                 'for external links - the target URI.') | ||||
|     linkType = Attribute('Optional: A short string specifying the type of the ' | ||||
|                 'link, a sort of predicate; default: "link".') | ||||
|     title = Attribute('Optional: A short text, may be used as the default text for ' | ||||
|                 'the link or for the alt tag of an image.') | ||||
|     description = Attribute('Optional: some text, may be used as a title attribute.') | ||||
|     state = Attribute('Optional: A short string denoting the state of the link ' | ||||
|                 'entry; default: "valid"') | ||||
|     relevance = Attribute('Optional: A float number between 0.0 and 1.0 denoting ' | ||||
|                 'the relevance of the connection between source and target; ' | ||||
|                 'default: 1.0.') | ||||
|     order = Attribute('Optional: An integer that may be used when providing an ' | ||||
|                 'ordered listing of links; default: 0.') | ||||
|     targetFragment = Attribute('Optional: an address part leading to a ' | ||||
|                 'text anchor or the part of an image.') | ||||
|     targetParameters = Attribute('Optional: a dictionary of URI parameters ' | ||||
|                 'that will have to be appended to the link to the target object.') | ||||
|     creator = Attribute('Optional: a string denoting the creator of the record.') | ||||
| 
 | ||||
|     def getManager(): | ||||
|         """ Return the link manager this link is managed by. | ||||
|         """ | ||||
							
								
								
									
										51
									
								
								link/tests.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										51
									
								
								link/tests.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| # $Id$ | ||||
| 
 | ||||
| import unittest | ||||
| from zope.testing.doctestunit import DocFileSuite | ||||
| from zope.interface.verify import verifyClass | ||||
| from zope.interface import implements | ||||
| from zope.app.intid.interfaces import IIntIds | ||||
| 
 | ||||
| 
 | ||||
| class IntIdsStub(object): | ||||
|     """A testing stub (mock utility) for IntIds.""" | ||||
| 
 | ||||
|     implements(IIntIds) | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.objs = [] | ||||
| 
 | ||||
|     def getObject(self, uid): | ||||
|         return self.objs[uid] | ||||
| 
 | ||||
|     def register(self, ob): | ||||
|         if ob not in self.objs: | ||||
|             self.objs.append(ob) | ||||
|         return self.objs.index(ob) | ||||
| 
 | ||||
|     getId = register | ||||
|     queryId = getId | ||||
| 
 | ||||
|     def unregister(self, ob): | ||||
|         id = self.getId(ob) | ||||
|         self.objs[id] = None | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         return iter(xrange(len(self.objs))) | ||||
| 
 | ||||
| 
 | ||||
| class TestLink(unittest.TestCase): | ||||
|     "Basic tests for the link package." | ||||
| 
 | ||||
|     def testBasics(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| def test_suite(): | ||||
|     return unittest.TestSuite(( | ||||
|                 unittest.makeSuite(TestLink), | ||||
|                 DocFileSuite('README.txt'), | ||||
|             )) | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     unittest.main(defaultTest='test_suite') | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 helmutm
						helmutm