1 |
For the decorators: I'm not sure if this is overkill: But in Python2.5 |
2 |
there is the "functools.wraps"-decorator, that takes care that the wrapped |
3 |
function looks the same like the original one :)... |
4 |
|
5 |
This would look like: |
6 |
|
7 |
def decorator (f): |
8 |
@wraps(f) |
9 |
def func_proxy(...): |
10 |
# something |
11 |
return func_proxy |
12 |
|
13 |
And when we are not using 2.5, your current approach could be used (though |
14 |
enhanced a bit to copy more data of the wrapped func): |
15 |
|
16 |
def wraps (f): |
17 |
"""Emulate functools.wraps for <python-2.5""" |
18 |
def w_deco(func): |
19 |
func.__doc__ = f.__doc__ |
20 |
func.__dict__ = f.__dict__ |
21 |
func.__name__ = f.__name__ |
22 |
return func |
23 |
return w_deco |
24 |
|
25 |
On Thu, 24 Jul 2008 03:12:50 +0300, Ali Polatel <hawking@g.o> wrote: |
26 |
> Thanks for the comments. |
27 |
> |
28 |
> Version 2, updates: |
29 |
> * Use "import sys" instead of "from sys import hexversion" |
30 |
> * Use super() to call functions from parent class. |
31 |
> * Fix initialization, used to pass m.groups() to parent object which is a |
32 |
> leftover from older versions where I used to subclass list instead of |
33 |
str. |
34 |
> Removed __str__ functions and __str which are useless as well. |
35 |
> * Added readonly attributes cvs, main_version and revision to version, pv |
36 |
> and |
37 |
> cpv, package to pv, cpv and category to cpv. I think this is a better way |
38 |
> than |
39 |
> making __parts available so it's guaranteed that there'll be no unwanted |
40 |
> change |
41 |
> in internal state. |
42 |
> * version uses __init__ now. pv and cpv still use __new__ because to fit |
43 |
> code |
44 |
> easily they return None when the argument isn't a valid PV or CPV instead |
45 |
> of |
46 |
> raising TypeError. |
47 |
> |
48 |
> Please comment. |
49 |
> |
50 |
> --- |
51 |
> pym/portage/versions.py | 332 |
52 |
> +++++++++++++++++++++++++++++++++++++++++----- |
53 |
> 1 files changed, 296 insertions(+), 36 deletions(-) |
54 |
> |
55 |
> diff --git a/pym/portage/versions.py b/pym/portage/versions.py |
56 |
> index 261fa9d..507d7a1 100644 |
57 |
> --- a/pym/portage/versions.py |
58 |
> +++ b/pym/portage/versions.py |
59 |
> @@ -4,6 +4,7 @@ |
60 |
> # $Id$ |
61 |
> |
62 |
> import re |
63 |
> +import warnings |
64 |
> |
65 |
> ver_regexp = |
66 |
> |
67 |
re.compile("^(cvs\\.)?(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$") |
68 |
> suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$") |
69 |
> @@ -12,6 +13,245 @@ endversion_keys = ["pre", "p", "alpha", "beta", "rc"] |
70 |
> |
71 |
> from portage.exception import InvalidData |
72 |
> |
73 |
> +# builtin all() is new in Python-2.5 |
74 |
> +# TODO Move compatibility stuff to a new module portage.compat |
75 |
> +# and import from it like from portage.compat import all |
76 |
> +import sys |
77 |
> +if sys.hexversion < 0x02050000: |
78 |
> + def all(iterable): |
79 |
> + for i in iterable: |
80 |
> + if not bool(i): |
81 |
> + return False |
82 |
> + return True |
83 |
> + |
84 |
> +def needs_version(func): |
85 |
> + """Decorator for functions that require non-keyword arguments of type |
86 |
> version.""" |
87 |
> + def func_proxy(*args, **kwargs): |
88 |
> + if not all([isinstance(arg, version) for arg in args]): |
89 |
> + raise TypeError("Not all non-keyword arguments are of type version") |
90 |
> + return func(*args, **kwargs) |
91 |
> + func_proxy.__doc__ = func.__doc__ |
92 |
> + return func_proxy |
93 |
> + |
94 |
> +def needs_pv(func): |
95 |
> + """Decorator for functions that require non-keyword arguments of type |
96 |
> pv.""" |
97 |
> + def func_proxy(*args, **kwargs): |
98 |
> + if not all([isinstance(arg, pv) for arg in args]): |
99 |
> + raise TypeError("Not all non-keyword arguments are of type pv") |
100 |
> + return func(*args, **kwargs) |
101 |
> + func_proxy.__doc__ = func.__doc__ |
102 |
> + return func_proxy |
103 |
> + |
104 |
> +def needs_cpv(func): |
105 |
> + """Decorator for functions that require non-keyword arguments of type |
106 |
> cpv.""" |
107 |
> + def func_proxy(*args, **kwargs): |
108 |
> + if not all([isinstance(arg, cpv) for arg in args]): |
109 |
> + raise TypeError("Not all non-keyword arguments are of type cpv") |
110 |
> + return func(*args, **kwargs) |
111 |
> + func_proxy.__doc__ = func.__doc__ |
112 |
> + return func_proxy |
113 |
> + |
114 |
> +class version(str): |
115 |
> + """Represents a package version""" |
116 |
> + |
117 |
> + __hash = None |
118 |
> + __parts = () |
119 |
> + |
120 |
> + def __init__(self, value): |
121 |
> + m = ver_regexp.match(value) |
122 |
> + if m is None: |
123 |
> + raise TypeError("Syntax error in version: %s" % value) |
124 |
> + else: |
125 |
> + super(version, self).__init__(value) |
126 |
> + self.__hash = hash(m.groups()) + hash(value) |
127 |
> + self.__parts = m.groups() |
128 |
> + |
129 |
> + def __repr__(self): |
130 |
> + return "<%s object at 0x%x: %s>" % (self.__class__.__name__, |
131 |
> + id(self), self) |
132 |
> + |
133 |
> + def __hash__(self): |
134 |
> + return self.__hash |
135 |
> + |
136 |
> + def __getitem__(self, i): |
137 |
> + return self.__parts[i] |
138 |
> + |
139 |
> + def __getslice__(self, i, j): |
140 |
> + return self.__parts[i:j] |
141 |
> + |
142 |
> + def __len__(self): |
143 |
> + return len(self.__parts) |
144 |
> + |
145 |
> + @needs_version |
146 |
> + def __cmp__(self, y): |
147 |
> + return vercmp(self, y) |
148 |
> + |
149 |
> + @needs_version |
150 |
> + def __eq__(self, y): |
151 |
> + return vercmp(self, y) == 0 |
152 |
> + |
153 |
> + @needs_version |
154 |
> + def __ne__(self, y): |
155 |
> + return vercmp(self, y) != 0 |
156 |
> + |
157 |
> + @needs_version |
158 |
> + def __lt__(self, y): |
159 |
> + return vercmp(self, y) < 0 |
160 |
> + |
161 |
> + @needs_version |
162 |
> + def __le__(self, y): |
163 |
> + return vercmp(self, y) <= 0 |
164 |
> + |
165 |
> + @needs_version |
166 |
> + def __gt__(self, y): |
167 |
> + return vercmp(self, y) > 0 |
168 |
> + |
169 |
> + @needs_version |
170 |
> + def __ge__(self, y): |
171 |
> + return vercmp(self, y) >= 0 |
172 |
> + |
173 |
> + @property |
174 |
> + def cvs(self): |
175 |
> + return self.__parts[0] |
176 |
> + |
177 |
> + @property |
178 |
> + def main_version(self): |
179 |
> + mv = self.__parts[1:3] |
180 |
> + mv += self.__parts[4:6] |
181 |
> + return "".join(mv) |
182 |
> + |
183 |
> + @property |
184 |
> + def revision(self): |
185 |
> + return self.__parts[8] |
186 |
> + |
187 |
> +class pv(str): |
188 |
> + """Represents a pv""" |
189 |
> + |
190 |
> + __hash = None |
191 |
> + __parts = () |
192 |
> + |
193 |
> + def __new__(cls, value): |
194 |
> + parts = pkgsplit(value) |
195 |
> + if parts is None: |
196 |
> + # Ideally a TypeError should be raised here. |
197 |
> + # But to fit code using this easily, fail silently. |
198 |
> + return None |
199 |
> + else: |
200 |
> + new_pv = super(pv, cls).__new__(cls, value) |
201 |
> + new_pv.__hash = hash(parts) + hash(value) |
202 |
> + new_pv.__parts = (parts[0], version("-".join(parts[1:]))) |
203 |
> + |
204 |
> + return new_pv |
205 |
> + |
206 |
> + def __repr__(self): |
207 |
> + return "<%s object at 0x%x: %s>" % (self.__class__.__name__, |
208 |
> + id(self), self) |
209 |
> + |
210 |
> + def __hash__(self): |
211 |
> + return self.__hash |
212 |
> + |
213 |
> + def __getitem__(self, i): |
214 |
> + return self.__parts[i] |
215 |
> + |
216 |
> + def __getslice__(self, i, j): |
217 |
> + return self.__parts[i:j] |
218 |
> + |
219 |
> + def __len__(self): |
220 |
> + return len(self.__parts) |
221 |
> + |
222 |
> + @needs_pv |
223 |
> + def __cmp__(self, y): |
224 |
> + if self.__parts[0] != y.__parts[0]: |
225 |
> + return None |
226 |
> + else: |
227 |
> + return cmp(self[1], y[1]) |
228 |
> + |
229 |
> + @property |
230 |
> + def package(self): |
231 |
> + return self.__parts[0] |
232 |
> + |
233 |
> + @property |
234 |
> + def version(self): |
235 |
> + return self.__parts[1] |
236 |
> + |
237 |
> + @property |
238 |
> + def cvs(self): |
239 |
> + return self.__parts[1].cvs |
240 |
> + |
241 |
> + @property |
242 |
> + def main_version(self): |
243 |
> + return self.__parts[1].main_version |
244 |
> + |
245 |
> + @property |
246 |
> + def revision(self): |
247 |
> + return self.__parts[1].revision |
248 |
> + |
249 |
> +class cpv(str): |
250 |
> + """Represents a cpv""" |
251 |
> + |
252 |
> + __hash = None |
253 |
> + __parts = () |
254 |
> + |
255 |
> + def __new__(cls, value): |
256 |
> + parts = catpkgsplit(value) |
257 |
> + if parts is None: |
258 |
> + # Ideally a TypeError should be raised here. |
259 |
> + # But to fit code using this easily, fail silently. |
260 |
> + return None |
261 |
> + else: |
262 |
> + new_cpv = super(cpv, cls).__new__(cls, value) |
263 |
> + new_cpv.__hash = hash(parts) + hash(value) |
264 |
> + new_cpv.__parts = (parts[0], pv("-".join(parts[1:]))) |
265 |
> + |
266 |
> + return new_cpv |
267 |
> + |
268 |
> + def __repr__(self): |
269 |
> + return "<%s object at 0x%x: %s>" % (self.__class__.__name__, |
270 |
> + id(self), self) |
271 |
> + |
272 |
> + def __hash__(self): |
273 |
> + return self.__hash |
274 |
> + |
275 |
> + def __getitem__(self, i): |
276 |
> + return self.__parts[i] |
277 |
> + |
278 |
> + def __getslice__(self, i, j): |
279 |
> + return self.__parts[i:j] |
280 |
> + |
281 |
> + def __len__(self): |
282 |
> + return len(self.__parts) |
283 |
> + |
284 |
> + @needs_cpv |
285 |
> + def __cmp__(self, y): |
286 |
> + if self[0] != y[0]: |
287 |
> + return None |
288 |
> + else: |
289 |
> + return cmp(self[1], y[1]) |
290 |
> + |
291 |
> + @property |
292 |
> + def category(self): |
293 |
> + return self.__parts[0] |
294 |
> + |
295 |
> + @property |
296 |
> + def package(self): |
297 |
> + return self.__parts[1] |
298 |
> + |
299 |
> + @property |
300 |
> + def version(self): |
301 |
> + return self.__parts[1].version |
302 |
> + |
303 |
> + @property |
304 |
> + def cvs(self): |
305 |
> + return self.__parts[1].cvs |
306 |
> + |
307 |
> + @property |
308 |
> + def main_version(self): |
309 |
> + return self.__parts[1].main_version |
310 |
> + |
311 |
> + @property |
312 |
> + def revision(self): |
313 |
> + return self.__parts[1].revision |
314 |
> + |
315 |
> def ververify(myver, silent=1): |
316 |
> if ver_regexp.match(myver): |
317 |
> return 1 |
318 |
> @@ -45,43 +285,63 @@ def vercmp(ver1, ver2, silent=1): |
319 |
> 4. None if ver1 or ver2 are invalid (see ver_regexp in |
320 |
> portage.versions.py) |
321 |
> """ |
322 |
> |
323 |
> - if ver1 == ver2: |
324 |
> - return 0 |
325 |
> - mykey=ver1+":"+ver2 |
326 |
> - try: |
327 |
> - return vercmp_cache[mykey] |
328 |
> - except KeyError: |
329 |
> - pass |
330 |
> - match1 = ver_regexp.match(ver1) |
331 |
> - match2 = ver_regexp.match(ver2) |
332 |
> - |
333 |
> - # checking that the versions are valid |
334 |
> - if not match1 or not match1.groups(): |
335 |
> - if not silent: |
336 |
> - print "!!! syntax error in version: %s" % ver1 |
337 |
> - return None |
338 |
> - if not match2 or not match2.groups(): |
339 |
> - if not silent: |
340 |
> - print "!!! syntax error in version: %s" % ver2 |
341 |
> - return None |
342 |
> + if isinstance(ver1, version) and isinstance(ver2, version): |
343 |
> + if ver1._str == ver2._str: |
344 |
> + return 0 |
345 |
> + mykey = ver1._str+":"+ver2._str |
346 |
> + if mykey in vercmp_cache: |
347 |
> + return vercmp_cache[mykey] |
348 |
> + |
349 |
> + group1 = ver1[:] |
350 |
> + group2 = ver2[:] |
351 |
> + elif isinstance(ver1, str) and isinstance(ver2, str): |
352 |
> + ## Backwards compatibility |
353 |
> + msg = "vercmp(str,str) is deprecated use portage.version object |
354 |
> instead" |
355 |
> + warnings.warn(msg, DeprecationWarning) |
356 |
> + |
357 |
> + if ver1 == ver2: |
358 |
> + return 0 |
359 |
> + mykey=ver1+":"+ver2 |
360 |
> + try: |
361 |
> + return vercmp_cache[mykey] |
362 |
> + except KeyError: |
363 |
> + pass |
364 |
> + match1 = ver_regexp.match(ver1) |
365 |
> + match2 = ver_regexp.match(ver2) |
366 |
> + |
367 |
> + # checking that the versions are valid |
368 |
> + if not match1 or not match1.groups(): |
369 |
> + if not silent: |
370 |
> + print "!!! syntax error in version: %s" % ver1 |
371 |
> + return None |
372 |
> + if not match2 or not match2.groups(): |
373 |
> + if not silent: |
374 |
> + print "!!! syntax error in version: %s" % ver2 |
375 |
> + return None |
376 |
> + |
377 |
> + group1 = match1.groups() |
378 |
> + group2 = match2.groups() |
379 |
> + else: |
380 |
> + raise TypeError( |
381 |
> + "Arguments aren't of type str,str or version,version") |
382 |
> |
383 |
> # shortcut for cvs ebuilds (new style) |
384 |
> - if match1.group(1) and not match2.group(1): |
385 |
> + if group1[0] and not group2[0]: |
386 |
> vercmp_cache[mykey] = 1 |
387 |
> return 1 |
388 |
> - elif match2.group(1) and not match1.group(1): |
389 |
> + elif group2[0] and not group1[0]: |
390 |
> vercmp_cache[mykey] = -1 |
391 |
> return -1 |
392 |
> |
393 |
> # building lists of the version parts before the suffix |
394 |
> # first part is simple |
395 |
> - list1 = [int(match1.group(2))] |
396 |
> - list2 = [int(match2.group(2))] |
397 |
> + list1 = [int(group1[1])] |
398 |
> + list2 = [int(group2[1])] |
399 |
> |
400 |
> # this part would greatly benefit from a fixed-length version pattern |
401 |
> - if len(match1.group(3)) or len(match2.group(3)): |
402 |
> - vlist1 = match1.group(3)[1:].split(".") |
403 |
> - vlist2 = match2.group(3)[1:].split(".") |
404 |
> + if len(group1[2]) or len(group2[2]): |
405 |
> + vlist1 = group1[2][1:].split(".") |
406 |
> + vlist2 = group2[2][1:].split(".") |
407 |
> for i in range(0, max(len(vlist1), len(vlist2))): |
408 |
> # Implcit .0 is given a value of -1, so that 1.0.0 > 1.0, since it |
409 |
> # would be ambiguous if two versions that aren't literally equal |
410 |
> @@ -111,10 +371,10 @@ def vercmp(ver1, ver2, silent=1): |
411 |
> list2.append(int(vlist2[i].ljust(max_len, "0"))) |
412 |
> |
413 |
> # and now the final letter |
414 |
> - if len(match1.group(5)): |
415 |
> - list1.append(ord(match1.group(5))) |
416 |
> - if len(match2.group(5)): |
417 |
> - list2.append(ord(match2.group(5))) |
418 |
> + if len(group1[4]): |
419 |
> + list1.append(ord(group1[4])) |
420 |
> + if len(group2[4]): |
421 |
> + list2.append(ord(group2[4])) |
422 |
> |
423 |
> for i in range(0, max(len(list1), len(list2))): |
424 |
> if len(list1) <= i: |
425 |
> @@ -128,8 +388,8 @@ def vercmp(ver1, ver2, silent=1): |
426 |
> return list1[i] - list2[i] |
427 |
> |
428 |
> # main version is equal, so now compare the _suffix part |
429 |
> - list1 = match1.group(6).split("_")[1:] |
430 |
> - list2 = match2.group(6).split("_")[1:] |
431 |
> + list1 = group1[5].split("_")[1:] |
432 |
> + list2 = group2[5].split("_")[1:] |
433 |
> |
434 |
> for i in range(0, max(len(list1), len(list2))): |
435 |
> # Implicit _p0 is given a value of -1, so that 1 < 1_p0 |
436 |
> @@ -154,12 +414,12 @@ def vercmp(ver1, ver2, silent=1): |
437 |
> return r1 - r2 |
438 |
> |
439 |
> # the suffix part is equal to, so finally check the revision |
440 |
> - if match1.group(10): |
441 |
> - r1 = int(match1.group(10)) |
442 |
> + if group1[9]: |
443 |
> + r1 = int(group1[9]) |
444 |
> else: |
445 |
> r1 = 0 |
446 |
> - if match2.group(10): |
447 |
> - r2 = int(match2.group(10)) |
448 |
> + if group2[9]: |
449 |
> + r2 = int(group2[9]) |
450 |
> else: |
451 |
> r2 = 0 |
452 |
> vercmp_cache[mykey] = r1 - r2 |
453 |
> -- |
454 |
> Regards, |
455 |
> Ali Polatel |