Showing
20 changed files
with
243 additions
and
130 deletions
1 | -"""SNAKES is the Net Algebra Kit for Editors and Simulators | 1 | +"""SNAKES library is organised into three parts: |
2 | - | 2 | + |
3 | -SNAKES is a Python library allowing to model all sorts of Petri nets | 3 | + * the core library is package `snakes` and its modules, among which |
4 | -and to execute them. It is very general as most Petri nets annotations | 4 | + `snakes.nets` is the one to work with Petri nets while the others |
5 | -can be arbitrary Python expressions while most values can be arbitrary | 5 | + can be seen as auxiliary modules |
6 | -Python objects. | 6 | + * the plugin system and the plugins themselves reside into package |
7 | - | 7 | + `snakes.plugins` |
8 | -SNAKES can be further extended with plugins, several ones being | 8 | + * auxiliary tools are kept into other sub-packages: `snakes.lang` |
9 | -already provided, in particular two plugins implement the Petri nets | 9 | + for all the material related to parsing Python and other |
10 | -compositions defined for the Petri Box Calculus and its successors. | 10 | + languages, `snakes.utils` for various utilities like the ABCD |
11 | + compiler | ||
11 | 12 | ||
12 | @author: Franck Pommereau | 13 | @author: Franck Pommereau |
13 | @organization: University of Evry/Paris-Saclay | 14 | @organization: University of Evry/Paris-Saclay |
14 | @copyright: (C) 2005-2013 Franck Pommereau | 15 | @copyright: (C) 2005-2013 Franck Pommereau |
15 | @license: GNU Lesser General Public Licence (aka. GNU LGPL), see the | 16 | @license: GNU Lesser General Public Licence (aka. GNU LGPL), see the |
16 | - file `doc/COPYING` in the distribution or visit [the GNU web | 17 | + file `doc/COPYING` in the distribution or visit the [GNU web |
17 | site](http://www.gnu.org/licenses/licenses.html#LGPL) | 18 | site](http://www.gnu.org/licenses/licenses.html#LGPL) |
18 | @contact: franck.pommereau@ibisc.univ-evry.fr | 19 | @contact: franck.pommereau@ibisc.univ-evry.fr |
19 | """ | 20 | """ |
... | @@ -21,8 +22,13 @@ compositions defined for the Petri Box Calculus and its successors. | ... | @@ -21,8 +22,13 @@ compositions defined for the Petri Box Calculus and its successors. |
21 | version = "0.9.16" | 22 | version = "0.9.16" |
22 | defaultencoding = "utf-8" | 23 | defaultencoding = "utf-8" |
23 | 24 | ||
25 | +"""## Module `snakes` | ||
26 | + | ||
27 | +This module only provides the exceptions used throughout SNAKES. | ||
28 | +""" | ||
29 | + | ||
24 | class SnakesError (Exception) : | 30 | class SnakesError (Exception) : |
25 | - "An error in SNAKES" | 31 | + "Generic error in SNAKES" |
26 | pass | 32 | pass |
27 | 33 | ||
28 | class ConstraintError (SnakesError) : | 34 | class ConstraintError (SnakesError) : | ... | ... |
This diff is collapsed. Click to expand it.
1 | -"""Hashable mutable objets. | 1 | +"""This module proposes hashable version of the mutable containers |
2 | - | ||
3 | -This module proposes hashable version of the mutable containers | ||
4 | `list`, `dict` and `set`, called respectively `hlist`, `hdict` and | 2 | `list`, `dict` and `set`, called respectively `hlist`, `hdict` and |
5 | -`hset`. After one object has been hashed, it becomes not mutable and | 3 | +`hset`. |
6 | -raises a ValueError if a method which changes the object (let call a | 4 | + |
7 | -_mutation_ such a method) is invoqued. The object can be then un- | 5 | +After one object has been hashed, it becomes not mutable and raises a |
8 | -hashed by calling `unhash` on it so that it becomes mutable again. | 6 | +`ValueError` if a method which changes the object (let call a |
7 | +_mutation_ such a method) is invoqued. The object can be then | ||
8 | +un-hashed by calling `unhash` on it so that it becomes mutable again. | ||
9 | Notice that this may cause troubles if the object is stored in a set | 9 | Notice that this may cause troubles if the object is stored in a set |
10 | or dict which uses its hashcode to locate it. Notice also that | 10 | or dict which uses its hashcode to locate it. Notice also that |
11 | hashable containers cannot be hashed if they contain non hashable | 11 | hashable containers cannot be hashed if they contain non hashable |
... | @@ -53,8 +53,9 @@ from operator import xor | ... | @@ -53,8 +53,9 @@ from operator import xor |
53 | from snakes.compat import * | 53 | from snakes.compat import * |
54 | 54 | ||
55 | def unhash (obj) : | 55 | def unhash (obj) : |
56 | - """Make the object hashable again. | 56 | + """Make the object mutable again. This should be used with |
57 | - | 57 | + caution, especially if the object is stored in a dict or a set. |
58 | + | ||
58 | >>> l = hlist(range(3)) | 59 | >>> l = hlist(range(3)) |
59 | >>> _ = hash(l) | 60 | >>> _ = hash(l) |
60 | >>> l.append(3) | 61 | >>> l.append(3) |
... | @@ -63,7 +64,7 @@ def unhash (obj) : | ... | @@ -63,7 +64,7 @@ def unhash (obj) : |
63 | ValueError: hashed 'hlist' object is not mutable | 64 | ValueError: hashed 'hlist' object is not mutable |
64 | >>> unhash(l) | 65 | >>> unhash(l) |
65 | >>> l.append(3) | 66 | >>> l.append(3) |
66 | - | 67 | + |
67 | @param obj: any object | 68 | @param obj: any object |
68 | @type obj: `object` | 69 | @type obj: `object` |
69 | """ | 70 | """ |
... | @@ -72,6 +73,7 @@ def unhash (obj) : | ... | @@ -72,6 +73,7 @@ def unhash (obj) : |
72 | except : | 73 | except : |
73 | pass | 74 | pass |
74 | 75 | ||
76 | +# apidoc skip | ||
75 | def hashable (cls) : | 77 | def hashable (cls) : |
76 | """Wrap methods in a class in order to make it hashable. | 78 | """Wrap methods in a class in order to make it hashable. |
77 | """ | 79 | """ |
... | @@ -122,8 +124,8 @@ def hashable (cls) : | ... | @@ -122,8 +124,8 @@ def hashable (cls) : |
122 | return cls | 124 | return cls |
123 | 125 | ||
124 | class hlist (list) : | 126 | class hlist (list) : |
125 | - """Hashable lists. | 127 | + """Hashable lists. They support all standard methods from `list`. |
126 | - | 128 | + |
127 | >>> l = hlist(range(5)) | 129 | >>> l = hlist(range(5)) |
128 | >>> l | 130 | >>> l |
129 | hlist([0, 1, 2, 3, 4]) | 131 | hlist([0, 1, 2, 3, 4]) |
... | @@ -138,6 +140,7 @@ class hlist (list) : | ... | @@ -138,6 +140,7 @@ class hlist (list) : |
138 | >>> unhash(l) | 140 | >>> unhash(l) |
139 | >>> l.append(6) | 141 | >>> l.append(6) |
140 | """ | 142 | """ |
143 | + # apidoc stop | ||
141 | def __add__ (self, other) : | 144 | def __add__ (self, other) : |
142 | return self.__class__(list.__add__(self, other)) | 145 | return self.__class__(list.__add__(self, other)) |
143 | def __delitem__ (self, item) : | 146 | def __delitem__ (self, item) : |
... | @@ -200,8 +203,9 @@ class hlist (list) : | ... | @@ -200,8 +203,9 @@ class hlist (list) : |
200 | hlist = hashable(hlist) | 203 | hlist = hashable(hlist) |
201 | 204 | ||
202 | class hdict (dict) : | 205 | class hdict (dict) : |
203 | - """Hashable dictionnaries. | 206 | + """Hashable dictionnaries. They support all standard methods from |
204 | - | 207 | + `dict`. |
208 | + | ||
205 | >>> l = hlist(range(5)) | 209 | >>> l = hlist(range(5)) |
206 | >>> d = hdict([(l, 0)]) | 210 | >>> d = hdict([(l, 0)]) |
207 | >>> d | 211 | >>> d |
... | @@ -229,6 +233,7 @@ class hdict (dict) : | ... | @@ -229,6 +233,7 @@ class hdict (dict) : |
229 | >>> d.pop(l) | 233 | >>> d.pop(l) |
230 | 0 | 234 | 0 |
231 | """ | 235 | """ |
236 | + # apidoc stop | ||
232 | def __delitem__ (self, key) : | 237 | def __delitem__ (self, key) : |
233 | self.__mutable__() | 238 | self.__mutable__() |
234 | dict.__delitem__(self, key) | 239 | dict.__delitem__(self, key) |
... | @@ -271,8 +276,8 @@ class hdict (dict) : | ... | @@ -271,8 +276,8 @@ class hdict (dict) : |
271 | hdict = hashable(hdict) | 276 | hdict = hashable(hdict) |
272 | 277 | ||
273 | class hset (set) : | 278 | class hset (set) : |
274 | - """Hashable sets. | 279 | + """Hashable sets. They support all standard methods from `set`. |
275 | - | 280 | + |
276 | >>> s = hset() | 281 | >>> s = hset() |
277 | >>> l = hlist(range(5)) | 282 | >>> l = hlist(range(5)) |
278 | >>> s.add(l) | 283 | >>> s.add(l) |
... | @@ -298,6 +303,7 @@ class hset (set) : | ... | @@ -298,6 +303,7 @@ class hset (set) : |
298 | >>> unhash(s) | 303 | >>> unhash(s) |
299 | >>> s.discard(l) | 304 | >>> s.discard(l) |
300 | """ | 305 | """ |
306 | + # apidoc stop | ||
301 | def __and__ (self, other) : | 307 | def __and__ (self, other) : |
302 | return self.__class__(set.__and__(self, other)) | 308 | return self.__class__(set.__and__(self, other)) |
303 | def __hash__ (self) : | 309 | def __hash__ (self) : | ... | ... |
1 | +"""This package is dedicated to parse and work with various languages, | ||
2 | +in particular Python itself and ABCD. These are mainly utilities for | ||
3 | +internal use in SNAKES, however, they may be of general interest | ||
4 | +independently of SNAKES. | ||
5 | + | ||
6 | +@todo: add documentation about how to use parsing and similar services | ||
7 | +""" | ||
8 | + | ||
1 | import sys | 9 | import sys |
2 | if sys.version_info[:2] in ((2, 6), (2, 7)) : | 10 | if sys.version_info[:2] in ((2, 6), (2, 7)) : |
3 | import ast | 11 | import ast |
... | @@ -14,9 +22,34 @@ else : | ... | @@ -14,9 +22,34 @@ else : |
14 | 22 | ||
15 | sys.modules["snkast"] = ast | 23 | sys.modules["snkast"] = ast |
16 | 24 | ||
25 | +"""### Module `ast` ### | ||
26 | + | ||
27 | +The module `ast` exported by `snakes.lang` is similar to Python's | ||
28 | +standard `ast` module (starting from version 2.6) but is available and | ||
29 | +uniform on every implementation of Python supported by SNAKES: CPython | ||
30 | +from 2.5, Jython and PyPy. (In general, these modules are available in | ||
31 | +these implementation - except CPython 2.5 - but with slight | ||
32 | +differences, so using `snakes.lang.ast` can be seen as a portable | ||
33 | +implementation.) | ||
34 | + | ||
35 | +Notice that this module is _not_ available for direct import but is | ||
36 | +exposed as a member of `snakes.lang`. Moreover, when `snakes.lang` is | ||
37 | +loaded, this module `ast` is also loaded as `snkast` in `sys.modules`, | ||
38 | +this allows to have both versions from Python and SNAKES | ||
39 | +simultaneously. | ||
40 | + | ||
41 | +>>> import snakes.lang.ast as ast | ||
42 | +ImportError ... | ||
43 | + ... | ||
44 | +ImportError: No module named ast | ||
45 | +>>> from snakes.lang import ast | ||
46 | +>>> import snkast | ||
47 | +""" | ||
48 | + | ||
17 | from . import unparse as _unparse | 49 | from . import unparse as _unparse |
18 | from snakes.compat import * | 50 | from snakes.compat import * |
19 | 51 | ||
52 | +# apidoc skip | ||
20 | class Names (ast.NodeVisitor) : | 53 | class Names (ast.NodeVisitor) : |
21 | def __init__ (self) : | 54 | def __init__ (self) : |
22 | ast.NodeVisitor.__init__(self) | 55 | ast.NodeVisitor.__init__(self) |
... | @@ -25,16 +58,24 @@ class Names (ast.NodeVisitor) : | ... | @@ -25,16 +58,24 @@ class Names (ast.NodeVisitor) : |
25 | self.names.add(node.id) | 58 | self.names.add(node.id) |
26 | 59 | ||
27 | def getvars (expr) : | 60 | def getvars (expr) : |
28 | - """ | 61 | + """Return the set of variables (or names in general) involved in a |
62 | + Python expression. | ||
63 | + | ||
29 | >>> list(sorted(getvars('x+y<z'))) | 64 | >>> list(sorted(getvars('x+y<z'))) |
30 | ['x', 'y', 'z'] | 65 | ['x', 'y', 'z'] |
31 | >>> list(sorted(getvars('x+y<z+f(3,t)'))) | 66 | >>> list(sorted(getvars('x+y<z+f(3,t)'))) |
32 | ['f', 't', 'x', 'y', 'z'] | 67 | ['f', 't', 'x', 'y', 'z'] |
68 | + | ||
69 | + @param expr: a Python expression | ||
70 | + @type expr: `str` | ||
71 | + @return: the set of variable names as strings | ||
72 | + @rtype: `set` | ||
33 | """ | 73 | """ |
34 | names = Names() | 74 | names = Names() |
35 | names.visit(ast.parse(expr)) | 75 | names.visit(ast.parse(expr)) |
36 | return names.names - set(['None', 'True', 'False']) | 76 | return names.names - set(['None', 'True', 'False']) |
37 | 77 | ||
78 | +# apidoc skip | ||
38 | class Unparser(_unparse.Unparser) : | 79 | class Unparser(_unparse.Unparser) : |
39 | boolops = {"And": 'and', "Or": 'or'} | 80 | boolops = {"And": 'and', "Or": 'or'} |
40 | def _Interactive (self, tree) : | 81 | def _Interactive (self, tree) : |
... | @@ -58,11 +99,13 @@ class Unparser(_unparse.Unparser) : | ... | @@ -58,11 +99,13 @@ class Unparser(_unparse.Unparser) : |
58 | self.dispatch(tree.body) | 99 | self.dispatch(tree.body) |
59 | self.leave() | 100 | self.leave() |
60 | 101 | ||
102 | +# apidoc skip | ||
61 | def unparse (st) : | 103 | def unparse (st) : |
62 | output = io.StringIO() | 104 | output = io.StringIO() |
63 | Unparser(st, output) | 105 | Unparser(st, output) |
64 | return output.getvalue().strip() | 106 | return output.getvalue().strip() |
65 | 107 | ||
108 | +# apidoc skip | ||
66 | class Renamer (ast.NodeTransformer) : | 109 | class Renamer (ast.NodeTransformer) : |
67 | def __init__ (self, map_names) : | 110 | def __init__ (self, map_names) : |
68 | ast.NodeTransformer.__init__(self) | 111 | ast.NodeTransformer.__init__(self) |
... | @@ -96,7 +139,8 @@ class Renamer (ast.NodeTransformer) : | ... | @@ -96,7 +139,8 @@ class Renamer (ast.NodeTransformer) : |
96 | ctx=ast.Load()), node) | 139 | ctx=ast.Load()), node) |
97 | 140 | ||
98 | def rename (expr, map={}, **ren) : | 141 | def rename (expr, map={}, **ren) : |
99 | - """ | 142 | + """Rename variables (ie, names) in a Python expression |
143 | + | ||
100 | >>> rename('x+y<z', x='t') | 144 | >>> rename('x+y<z', x='t') |
101 | '((t + y) < z)' | 145 | '((t + y) < z)' |
102 | >>> rename('x+y<z+f(3,t)', f='g', t='z', z='t') | 146 | >>> rename('x+y<z+f(3,t)', f='g', t='z', z='t') |
... | @@ -105,12 +149,22 @@ def rename (expr, map={}, **ren) : | ... | @@ -105,12 +149,22 @@ def rename (expr, map={}, **ren) : |
105 | '[(x + y) for x in range(3)]' | 149 | '[(x + y) for x in range(3)]' |
106 | >>> rename('[x+y for x in range(3)]', y='z') | 150 | >>> rename('[x+y for x in range(3)]', y='z') |
107 | '[(x + z) for x in range(3)]' | 151 | '[(x + z) for x in range(3)]' |
152 | + | ||
153 | + @param expr: a Python expression | ||
154 | + @type expr: `str` | ||
155 | + @param map: a mapping from old to new names (`str` to `str`) | ||
156 | + @type map: `dict` | ||
157 | + @param ren: additional mapping of old to new names | ||
158 | + @type ren: `str` | ||
159 | + @return: the new expression | ||
160 | + @rtype: `str` | ||
108 | """ | 161 | """ |
109 | map_names = dict(map) | 162 | map_names = dict(map) |
110 | map_names.update(ren) | 163 | map_names.update(ren) |
111 | transf = Renamer(map_names) | 164 | transf = Renamer(map_names) |
112 | return unparse(transf.visit(ast.parse(expr))) | 165 | return unparse(transf.visit(ast.parse(expr))) |
113 | 166 | ||
167 | +# apidoc skip | ||
114 | class Binder (Renamer) : | 168 | class Binder (Renamer) : |
115 | def visit_Name (self, node) : | 169 | def visit_Name (self, node) : |
116 | if node.id in self.map[-1] : | 170 | if node.id in self.map[-1] : |
... | @@ -119,7 +173,10 @@ class Binder (Renamer) : | ... | @@ -119,7 +173,10 @@ class Binder (Renamer) : |
119 | return node | 173 | return node |
120 | 174 | ||
121 | def bind (expr, map={}, **ren) : | 175 | def bind (expr, map={}, **ren) : |
122 | - """ | 176 | + """Replace variables (ie, names) in an expression with other |
177 | + expressions. The replacements should be provided as `ast` nodes, | ||
178 | + and so could be arbitrary expression. | ||
179 | + | ||
123 | >>> bind('x+y<z', x=ast.Num(n=2)) | 180 | >>> bind('x+y<z', x=ast.Num(n=2)) |
124 | '((2 + y) < z)' | 181 | '((2 + y) < z)' |
125 | >>> bind('x+y<z', y=ast.Num(n=2)) | 182 | >>> bind('x+y<z', y=ast.Num(n=2)) |
... | @@ -128,6 +185,15 @@ def bind (expr, map={}, **ren) : | ... | @@ -128,6 +185,15 @@ def bind (expr, map={}, **ren) : |
128 | '[(x + y) for x in range(3)]' | 185 | '[(x + y) for x in range(3)]' |
129 | >>> bind('[x+y for x in range(3)]', y=ast.Num(n=2)) | 186 | >>> bind('[x+y for x in range(3)]', y=ast.Num(n=2)) |
130 | '[(x + 2) for x in range(3)]' | 187 | '[(x + 2) for x in range(3)]' |
188 | + | ||
189 | + @param expr: a Python expression | ||
190 | + @type expr: `str` | ||
191 | + @param map: a mapping from old to new names (`str` to `ast.AST`) | ||
192 | + @type map: `dict` | ||
193 | + @param ren: additional mapping of old to new names | ||
194 | + @type ren: `ast.AST` | ||
195 | + @return: the new expression | ||
196 | + @rtype: `str` | ||
131 | """ | 197 | """ |
132 | map_names = dict(map) | 198 | map_names = dict(map) |
133 | map_names.update(ren) | 199 | map_names.update(ren) | ... | ... |
This diff is collapsed. Click to expand it.
1 | -"""A plugins system. | 1 | +"""This package implements SNAKES plugin system. SNAKES plugins |
2 | +themselves are available as modules within the package. | ||
3 | + | ||
4 | +Examples below are based on plugin `hello` that is distributed with | ||
5 | +SNAKES to be used as an exemple of how to build a plugin. It extends | ||
6 | +class `PetriNet` adding a method `hello` that says hello displaying | ||
7 | +the name of the net. | ||
8 | + | ||
9 | +## Loading plugins ## | ||
2 | 10 | ||
3 | The first example shows how to load a plugin: we load | 11 | The first example shows how to load a plugin: we load |
4 | `snakes.plugins.hello` and plug it into `snakes.nets`, which results | 12 | `snakes.plugins.hello` and plug it into `snakes.nets`, which results |
5 | -in a new module that actually `snakes.nets` extended by | 13 | +in a new module that is actually `snakes.nets` extended by |
6 | `snakes.plugins.hello`. | 14 | `snakes.plugins.hello`. |
7 | 15 | ||
8 | >>> import snakes.plugins as plugins | 16 | >>> import snakes.plugins as plugins |
... | @@ -18,11 +26,6 @@ The next example shows how to simulate the effect of `import module`: | ... | @@ -18,11 +26,6 @@ The next example shows how to simulate the effect of `import module`: |
18 | we give to `load` a thrid argument that is the name of the created | 26 | we give to `load` a thrid argument that is the name of the created |
19 | module, from which it becomes possible to import names or `*`. | 27 | module, from which it becomes possible to import names or `*`. |
20 | 28 | ||
21 | -**Warning:** this feature will not work `load` is not called from the | ||
22 | -module where we then do the `from ... import ...`. This is exactly the | ||
23 | -same when, from a module `foo` that you load a module `bar`: if `bar` | ||
24 | -loads other modules they will not be imported in `foo`. | ||
25 | - | ||
26 | >>> plugins.load('hello', 'snakes.nets', 'another_version') | 29 | >>> plugins.load('hello', 'snakes.nets', 'another_version') |
27 | <module ...> | 30 | <module ...> |
28 | >>> from another_version import PetriNet | 31 | >>> from another_version import PetriNet |
... | @@ -33,12 +36,23 @@ Hello from another net | ... | @@ -33,12 +36,23 @@ Hello from another net |
33 | >>> n.hello() | 36 | >>> n.hello() |
34 | Hi, this is yet another net! | 37 | Hi, this is yet another net! |
35 | 38 | ||
36 | -How to define a plugin is explained in the example `hello`. | 39 | +The last example shows how to load several plugins at once, instead of |
40 | +giving one plugin name, we just need to give a list of plugin names. | ||
41 | + | ||
42 | +>>> plugins.load(['hello', 'pos'], 'snakes.nets', 'mynets') | ||
43 | +<module ...> | ||
44 | +>>> from mynets import PetriNet | ||
45 | +>>> n = PetriNet('a net') | ||
46 | +>>> n.hello() # works thanks to plugin `hello` | ||
47 | +Hello from a net | ||
48 | +>>> n.bbox() # works thanks to plugin `pos` | ||
49 | +((0, 0), (0, 0)) | ||
37 | """ | 50 | """ |
38 | 51 | ||
39 | import imp, sys, inspect | 52 | import imp, sys, inspect |
40 | from functools import wraps | 53 | from functools import wraps |
41 | 54 | ||
55 | +# apidoc skip | ||
42 | def update (module, objects) : | 56 | def update (module, objects) : |
43 | """Update a module content | 57 | """Update a module content |
44 | """ | 58 | """ |
... | @@ -54,48 +68,17 @@ def update (module, objects) : | ... | @@ -54,48 +68,17 @@ def update (module, objects) : |
54 | else : | 68 | else : |
55 | raise ValueError("cannot plug '%r'" % obj) | 69 | raise ValueError("cannot plug '%r'" % obj) |
56 | 70 | ||
57 | -def build (name, module, *objects) : | ||
58 | - """Builds an extended module. | ||
59 | - | ||
60 | - The parameter `module` is exactly that taken by the function | ||
61 | - `extend` of a plugin. This list argument `objects` holds all the | ||
62 | - objects, constructed in `extend`, that are extensions of objects | ||
63 | - from `module`. The resulting value should be returned by `extend`. | ||
64 | - | ||
65 | - @param name: the name of the constructed module | ||
66 | - @type name: `str` | ||
67 | - @param module: the extended module | ||
68 | - @type module: `module` | ||
69 | - @param objects: the sub-objects | ||
70 | - @type objects: each is a class object | ||
71 | - @return: the new module | ||
72 | - @rtype: `module` | ||
73 | - """ | ||
74 | - result = imp.new_module(name) | ||
75 | - result.__dict__.update(module.__dict__) | ||
76 | - update(result, objects) | ||
77 | - result.__plugins__ = (module.__dict__.get("__plugins__", | ||
78 | - (module.__name__,)) | ||
79 | - + (name,)) | ||
80 | - for obj in objects : | ||
81 | - if inspect.isclass(obj) : | ||
82 | - obj.__plugins__ = result.__plugins__ | ||
83 | - return result | ||
84 | - | ||
85 | def load (plugins, base, name=None) : | 71 | def load (plugins, base, name=None) : |
86 | - """Load plugins. | 72 | + """Load plugins, `plugins` can be a single plugin name, a module |
87 | - | 73 | + or a list of such values. If `name` is not `None`, the extended |
88 | - `plugins` can be a single plugin name or module or a list of such | 74 | + module is loaded as `name` in `sys.modules` as well as in the |
89 | - values. If `name` is not `None`, the extended module is loaded ad | 75 | + global environment from which `load` was called. |
90 | - `name` in `sys.modules` as well as in the global environment from | 76 | + |
91 | - which `load` was called. | ||
92 | - | ||
93 | @param plugins: the module that implements the plugin, or its | 77 | @param plugins: the module that implements the plugin, or its |
94 | - name, or a collection of such values | 78 | + name, or a collection (eg, list, tuple) of such values |
95 | - @type plugins: `str` or `module`, or a `list`/`tuple`/... of such | 79 | + @type plugins: `object` |
96 | - values | ||
97 | @param base: the module being extended or its name | 80 | @param base: the module being extended or its name |
98 | - @type base: `str` or `module` | 81 | + @type base: `object` |
99 | @param name: the name of the created module | 82 | @param name: the name of the created module |
100 | @type name: `str` | 83 | @type name: `str` |
101 | @return: the extended module | 84 | @return: the extended module |
... | @@ -125,17 +108,45 @@ def load (plugins, base, name=None) : | ... | @@ -125,17 +108,45 @@ def load (plugins, base, name=None) : |
125 | inspect.stack()[1][0].f_globals[name] = result | 108 | inspect.stack()[1][0].f_globals[name] = result |
126 | return result | 109 | return result |
127 | 110 | ||
111 | +"""## Creating plugins ### | ||
112 | + | ||
113 | +We show now how to develop a plugin that allows instances of | ||
114 | +`PetriNet` to say hello: a new method `PetriNet.hello` is added and | ||
115 | +the constructor `PetriNet.__init__` is added a keyword argument | ||
116 | +`hello` for the message to print when calling method `hello`. | ||
117 | + | ||
118 | +Defining a plugins required to write a module with a function called | ||
119 | +`extend` that takes as its single argument the module to be extended. | ||
120 | +Inside this function, extensions of the classes in the module are | ||
121 | +defined as normal sub-classes. Function `extend` returns the extended | ||
122 | +classes. A decorator called `plugin` must be used, it also allows to | ||
123 | +resolve plugin dependencies and conflicts. | ||
124 | +""" | ||
125 | + | ||
126 | +# apidoc include "hello.py" lang="python" | ||
127 | + | ||
128 | +"""Note that, when extending an existing method like `__init__` above, | ||
129 | +we have to take care that you may be working on an already extended | ||
130 | +class, consequently, we cannot know how its arguments have been | ||
131 | +changed already. So, we must always use those from the unextended | ||
132 | +method plus `**args`. Then, we remove from the latter what your plugin | ||
133 | +needs and pass the remaining to the method of the base class if we | ||
134 | +need to call it (which is usually the case). """ | ||
135 | + | ||
128 | def plugin (base, depends=[], conflicts=[]) : | 136 | def plugin (base, depends=[], conflicts=[]) : |
129 | """Decorator for extension functions | 137 | """Decorator for extension functions |
130 | - | 138 | + |
131 | - @param base: name of base module (usually 'snakes.nets') | 139 | + @param base: name of base module (usually 'snakes.nets') that the |
132 | - @type base: str | 140 | + plugin extends |
133 | - @param depends: list of plugins on which this one depends | 141 | + @type base: `str` |
134 | - @type depends: list of str | 142 | + @param depends: list of plugin names (as `str`) this one depends |
135 | - @param conflicts: list of plugins with which this one conflicts | 143 | + on, prefix `snakes.plugins.` may be omitted |
136 | - @type conflicts: list of str | 144 | + @type depends: `list` |
145 | + @param conflicts: list of plugin names with which this one | ||
146 | + conflicts | ||
147 | + @type conflicts: `list` | ||
137 | @return: the appropriate decorator | 148 | @return: the appropriate decorator |
138 | - @rtype: function | 149 | + @rtype: `decorator` |
139 | """ | 150 | """ |
140 | def wrapper (fun) : | 151 | def wrapper (fun) : |
141 | @wraps(fun) | 152 | @wraps(fun) |
... | @@ -164,9 +175,39 @@ def plugin (base, depends=[], conflicts=[]) : | ... | @@ -164,9 +175,39 @@ def plugin (base, depends=[], conflicts=[]) : |
164 | return extend | 175 | return extend |
165 | return wrapper | 176 | return wrapper |
166 | 177 | ||
178 | +# apidoc skip | ||
167 | def new_instance (cls, obj) : | 179 | def new_instance (cls, obj) : |
168 | """Create a copy of `obj` which is an instance of `cls` | 180 | """Create a copy of `obj` which is an instance of `cls` |
169 | """ | 181 | """ |
170 | result = object.__new__(cls) | 182 | result = object.__new__(cls) |
171 | result.__dict__.update(obj.__dict__) | 183 | result.__dict__.update(obj.__dict__) |
172 | return result | 184 | return result |
185 | + | ||
186 | +# apidoc skip | ||
187 | +def build (name, module, *objects) : | ||
188 | + """Builds an extended module. | ||
189 | + | ||
190 | + The parameter `module` is exactly that taken by the function | ||
191 | + `extend` of a plugin. This list argument `objects` holds all the | ||
192 | + objects, constructed in `extend`, that are extensions of objects | ||
193 | + from `module`. The resulting value should be returned by `extend`. | ||
194 | + | ||
195 | + @param name: the name of the constructed module | ||
196 | + @type name: `str` | ||
197 | + @param module: the extended module | ||
198 | + @type module: `module` | ||
199 | + @param objects: the sub-objects | ||
200 | + @type objects: each is a class object | ||
201 | + @return: the new module | ||
202 | + @rtype: `module` | ||
203 | + """ | ||
204 | + result = imp.new_module(name) | ||
205 | + result.__dict__.update(module.__dict__) | ||
206 | + update(result, objects) | ||
207 | + result.__plugins__ = (module.__dict__.get("__plugins__", | ||
208 | + (module.__name__,)) | ||
209 | + + (name,)) | ||
210 | + for obj in objects : | ||
211 | + if inspect.isclass(obj) : | ||
212 | + obj.__plugins__ = result.__plugins__ | ||
213 | + return result | ... | ... |
... | @@ -26,6 +26,8 @@ | ... | @@ -26,6 +26,8 @@ |
26 | >>> n.layout() | 26 | >>> n.layout() |
27 | >>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str)) | 27 | >>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str)) |
28 | False | 28 | False |
29 | + | ||
30 | +@todo: revise documentation | ||
29 | """ | 31 | """ |
30 | 32 | ||
31 | import os, os.path, subprocess, collections | 33 | import os, os.path, subprocess, collections | ... | ... |
1 | -"""An example plugin that allows instances class `PetriNet` to say hello. | 1 | +"""An example plugin that allows instances of `PetriNet` to |
2 | - | 2 | +say hello. The source code can be used as a starting |
3 | -A new method `hello` is added. The constructor is added a keyword | 3 | +example.""" |
4 | -argument `hello` that must be the `str` to print when calling `hello`, | ||
5 | -with one `%s` that will be replaced by the name of the net when | ||
6 | -`hello` is called. | ||
7 | - | ||
8 | -Defining a plugins need writing a module with a single function called | ||
9 | -`extend` that takes a single argument that is the module to be | ||
10 | -extended. | ||
11 | - | ||
12 | -Inside the function, extensions of the classes in the module are | ||
13 | -defined as normal sub-classes. | ||
14 | - | ||
15 | -The function `extend` should return the extended module created by | ||
16 | -`snakes.plugins.build` that takes as arguments: the name of the | ||
17 | -extended module, the module taken as argument and the sub-classes | ||
18 | -defined (expected as a list argument `*args` in no special order). | ||
19 | - | ||
20 | -If the plugin depends on other plugins, for instance `foo` and `bar`, | ||
21 | -the function `extend` should be decorated by `@depends('foo', 'bar')`. | ||
22 | - | ||
23 | -Read the source code of this module to have an example | ||
24 | -""" | ||
25 | 4 | ||
26 | import snakes.plugins | 5 | import snakes.plugins |
27 | 6 | ||
28 | @snakes.plugins.plugin("snakes.nets") | 7 | @snakes.plugins.plugin("snakes.nets") |
29 | def extend (module) : | 8 | def extend (module) : |
30 | - """Extends `module` | 9 | + "Extends `module`" |
31 | - """ | ||
32 | class PetriNet (module.PetriNet) : | 10 | class PetriNet (module.PetriNet) : |
33 | - """Extension of the class `PetriNet` in `module` | 11 | + "Extension of the class `PetriNet` in `module`" |
34 | - """ | ||
35 | def __init__ (self, name, **args) : | 12 | def __init__ (self, name, **args) : |
36 | - """When extending an existing method, take care that you may | 13 | + """Add new keyword argument `hello` |
37 | - be working on an already extended class, so you so not | 14 | + |
38 | - know how its arguments have been changed. So, always use | ||
39 | - those from the unextended class plus `**args`, remove from | ||
40 | - it what your plugin needs and pass it to the method of the | ||
41 | - extended class if you need to call it. | ||
42 | - | ||
43 | >>> PetriNet('N').hello() | 15 | >>> PetriNet('N').hello() |
44 | Hello from N | 16 | Hello from N |
45 | >>> PetriNet('N', hello='Hi! This is %s...').hello() | 17 | >>> PetriNet('N', hello='Hi! This is %s...').hello() |
46 | Hi! This is N... | 18 | Hi! This is N... |
47 | - | 19 | + |
48 | @param args: plugin options | 20 | @param args: plugin options |
49 | - @keyword hello: the message to print, with `%s` where the | 21 | + @keyword hello: the message to print, with |
50 | - net name should appear. | 22 | + `%s` where the net name should appear. |
51 | @type hello: `str` | 23 | @type hello: `str` |
52 | """ | 24 | """ |
53 | self._hello = args.pop("hello", "Hello from %s") | 25 | self._hello = args.pop("hello", "Hello from %s") |
54 | module.PetriNet.__init__(self, name, **args) | 26 | module.PetriNet.__init__(self, name, **args) |
55 | def hello (self) : | 27 | def hello (self) : |
56 | - """A new method `hello` | 28 | + "Ask the net to say hello" |
57 | - | ||
58 | - >>> n = PetriNet('N') | ||
59 | - >>> n.hello() | ||
60 | - Hello from N | ||
61 | - """ | ||
62 | print(self._hello % self.name) | 29 | print(self._hello % self.name) |
63 | return PetriNet | 30 | return PetriNet | ... | ... |
... | @@ -42,6 +42,8 @@ Position(1, 3) | ... | @@ -42,6 +42,8 @@ Position(1, 3) |
42 | >>> n.transpose() | 42 | >>> n.transpose() |
43 | >>> n.node('t01').pos | 43 | >>> n.node('t01').pos |
44 | Position(-3, 1) | 44 | Position(-3, 1) |
45 | + | ||
46 | +@todo: revise documentation | ||
45 | """ | 47 | """ |
46 | 48 | ||
47 | from snakes import SnakesError | 49 | from snakes import SnakesError | ... | ... |
1 | +""" | ||
2 | +@todo: revise (actually make) documentation | ||
3 | +""" | ||
4 | + | ||
1 | from snakes.plugins import plugin | 5 | from snakes.plugins import plugin |
2 | from snakes.pnml import Tree, loads, dumps | 6 | from snakes.pnml import Tree, loads, dumps |
3 | import imp, sys, socket, traceback, operator | 7 | import imp, sys, socket, traceback, operator | ... | ... |
... | @@ -12,6 +12,8 @@ Several status are defined by default: `entry`, `internal`, `exit`, | ... | @@ -12,6 +12,8 @@ Several status are defined by default: `entry`, `internal`, `exit`, |
12 | >>> n.add_place(Place('p1'), status=status.entry) | 12 | >>> n.add_place(Place('p1'), status=status.entry) |
13 | >>> n.place('p1') | 13 | >>> n.place('p1') |
14 | Place('p1', MultiSet([]), tAll, status=Status('entry')) | 14 | Place('p1', MultiSet([]), tAll, status=Status('entry')) |
15 | + | ||
16 | +@todo: revise documentation | ||
15 | """ | 17 | """ |
16 | 18 | ||
17 | import operator, weakref | 19 | import operator, weakref | ... | ... |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
... | @@ -60,6 +60,8 @@ TypeError: ... | ... | @@ -60,6 +60,8 @@ TypeError: ... |
60 | Traceback (most recent call last): | 60 | Traceback (most recent call last): |
61 | ... | 61 | ... |
62 | TypeError: ... | 62 | TypeError: ... |
63 | + | ||
64 | +@todo: revise documentation | ||
63 | """ | 65 | """ |
64 | 66 | ||
65 | import inspect, sys | 67 | import inspect, sys | ... | ... |
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment