root/projects/sAsync/trunk/sasync/graph.py

Revision 3, 7.5 kB (checked in by edsuom, 1 year ago)

Import of trunk from old repo

Line 
1 # sAsync:
2 # An enhancement to the SQLAlchemy package that provides persistent
3 # dictionaries, text indexing and searching, and an access broker for
4 # conveniently managing database access, table setup, and
5 # transactions. Everything can be run in an asynchronous fashion using the
6 # Twisted framework and its deferred processing capabilities.
7 #
8 # Copyright (C) 2006 by Edwin A. Suominen, http://www.eepatents.com
9 #
10 # This program is free software; you can redistribute it and/or modify it under
11 # the terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
13 # version.
14 #
15 # This program is distributed in the hope that it will be useful, but WITHOUT
16 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 # FOR A PARTICULAR PURPOSE.  See the file COPYING for more details.
18 #
19 # You should have received a copy of the GNU General Public License along with
20 # this program; if not, write to the Free Software Foundation, Inc., 51
21 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22
23 """
24 Overview
25 ========
26
27 Provides transparently persistent versions of the four different I{NetworkX}
28 graph objects: C{Graph}, C{DiGraph}, C{XGraph}, and C{XDiGraph}.
29
30 The persistent versions of those objects work just like the regular ones from
31 the I{NetworkX} package except that they are instantiated with an active
32 SQLAlchemy C{engine} object. Then the objects' contents are read from and
33 stored to the database with which that object was created, without any fuss on
34 the user's part.
35
36
37 Usage
38 =====
39
40 This module, like the rest of sAsync, requires the use of the Twisted
41 asynchronous processing framework. Twisted is very powerful, but its
42 event-driven, non-blocking way of life takes some getting used to.
43
44 Otherwise, usage is very simple, changing just a line or two of your regular
45 L{networkx} code::
46
47   from sasync.graph import Graph
48  
49   ...
50
51   # Instantiate a persistent graph object with its own name (a required
52   # argument for filing the persisted data, not just a keyword option)
53   G = Graph('foo')
54  
55   # Start up its persistence engine and return a Deferred object that fires
56   # when it's ready
57   d = G.startup('sqlite:///graph.db')
58  
59   # Add a callback to the Deferred to proceed with using the graph object
60   # somehow
61   d.addCallback(...)
62
63   ...
64
65 When the startup deferred fires, the instantiated graph will have whatever node
66 and edge values you set it to last time you instantiated it.
67
68 """
69
70 from twisted.internet import defer
71 import networkx as NX
72 from pdict import PersistentDict
73
74
75 class _Persistent(object):
76     """
77     I am a mixin used in persistent subclasses of NetworkX C{Graph} classes for
78     both directed and undirected graphs, with or without edge weights. I rely
79     on a persistent dictionary-like-object from the I{sAsync} package to
80     achieve my behind-the-scenes persistency.
81
82     My persistent contents are common to all instances of me having the same
83     graph name, which can be defined as an option and defaults to 'No
84     Name'. For example, a second instance of me invoked with no keywords will
85     be instantiated with the same graph contents as a first one, but
86     C{Graph(name='foo')} will be a different graph. If you want different
87     instances of C{Graph}, instantiate them with different I{name} options.
88     """
89     def __init__(self, name, url, **kw):
90         self.name = name
91         self.nodeType = kw.pop('nodeType', str)
92         self.startFresh = kw.pop('startFresh', False)
93         self.engineParams = (url, kw)
94         super(_Persistent, self).__init__(name=name)
95    
96     def startup(self, startFresh=False):
97         """
98         Starts up my persistence engine.
99
100         This method replaces the stock C{adj} adjacency list attribute with an
101         instance of L{PersistentDict}, or for directed graphs, by replacing the
102         C{succ} and C{pred} adjacency list attributes with two such instances.
103
104         Important note regarding directed graph persistence
105         ===================================================
106
107         In directed graphs, we actually overwrite the C{adj} attribute instead
108         of C{succ}. That's because C{adj} is used in the C{NX.Graph} superclass
109         and C{succ} is merely set equal to it in the constructor of
110         C{NX.DiGraph}.
111
112         When C{x = y} in Python, changing some property of C{y} causes the same
113         change in C{x}. However, we are changing the attribute C{adj} to
114         reference an I{entirely new} object, not just changing its
115         properties. Thus we have to refresh the C{self.succ = self.adj} link
116         after overwriting C{adj}, giving C{succ} a reference to the I{new}
117         C{adj} object. Adding or changing I{items} of either one will show up
118         in the other, so no further hacking is required.
119
120         @param startFresh: Set this keyword C{True} to clear any
121           persisted content and start fresh. This keyword can also be
122           set in the constructor. Obviously, you should use this
123           option with care as it will B{erase} database entries!
124
125         @return: A deferred that fires when the persistence engine is ready
126           for use.
127         
128         """
129         self._uniqueCount = 0
130        
131         def ID():
132             self._uniqueCount += 1
133             thisID = "%s-%d" % (self.name, self._uniqueCount)
134             return hash(thisID)
135
136         def started(null):
137             self.succ = self.adj
138             if startFresh or self.startFresh:
139                 return self.adjacencyListOperation("clear")
140
141         dList = []
142         url, kw = self.engineParams
143         kw['nameType'] = self.nodeType
144         # Adjacency lists
145         for dictName in self.adjacencyLists:
146             dictObject = PersistentDict(ID(), url, **kw)
147             setattr(self, dictName, dictObject)
148             dList.append(dictObject.preload())
149         return defer.DeferredList(dList).addCallback(started)
150    
151     def shutdown(self):
152         """
153         Shuts down my persistence engine.
154
155         @return: A deferred that fires when the persistence engine is shut
156             down and I can safely be deleted.
157
158         """
159         return self.adjacencyListOperation("shutdown")
160
161     def deferToWrites(self):
162         """
163         Returns a deferred that fires when all outstanding lazy writes are
164         done. You should only need this if you are going to be doing something
165         with the underlying database.
166         """
167         return self.adjacencyListOperation("deferToWrites")
168    
169     def adjacencyListOperation(self, methodName):
170         """
171         Private method that performs the named I{method} of all my persistent
172         adjacency list dictionary or dictionaries.
173
174         Returns a deferredList that fires when the deferreds resulting from
175         each method call have fired.
176         """
177         dList = []
178         for dictName in self.adjacencyLists:
179             dictObject = getattr(self, dictName, None)
180             methodObject = getattr(dictObject, methodName, None)
181             if callable(methodObject):
182                 dList.append(methodObject())
183         return defer.DeferredList(dList)
184
185
186 class Graph(_Persistent, NX.Graph):
187     """
188     Persistent version of L{NX.Graph}
189     """
190     adjacencyLists = ('adj',)
191
192
193 class DiGraph(_Persistent, NX.DiGraph):
194     """
195     Persistent version of L{NX.DiGraph}
196     """
197     adjacencyLists = ('adj', 'pred')
198
199
200 class XGraph(_Persistent, NX.XGraph):
201     """
202     Persistent version of L{NX.XGraph}
203     """
204     adjacencyLists = ('adj',)
205
206
207 class XDiGraph(_Persistent, NX.XDiGraph):
208     """
209     Persistent version of L{NX.XDiGraph}
210     """
211     adjacencyLists = ('adj', 'pred')
Note: See TracBrowser for help on using the browser.