Gentoo Archives: gentoo-portage-dev

From: "René 'Necoro' Neumann" <lists@××××××.eu>
To: gentoo-portage-dev@l.g.o
Subject: Re: [gentoo-portage-dev] [RFC/PATCH v2] New objects cpv, pv and version to be used instead of raw strings.
Date: Thu, 24 Jul 2008 11:11:43
Message-Id: 6b1a810a0f49181932e691eb2fdb8659@mail.necoro.eu
In Reply to: [gentoo-portage-dev] [RFC/PATCH v2] New objects cpv, pv and version to be used instead of raw strings. by Ali Polatel
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