Gentoo Archives: gentoo-portage-dev

From: Brian Dolbec <dolsen@g.o>
To: gentoo-portage-dev@l.g.o
Subject: Re: [gentoo-portage-dev] [PATCH] repoman: replace Fuse with Future
Date: Sat, 16 Apr 2016 22:57:20
Message-Id: 20160416155625.3befbc9b.dolsen@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH] repoman: replace Fuse with Future by Zac Medico
1 On Sat, 16 Apr 2016 13:49:40 -0700
2 Zac Medico <zmedico@g.o> wrote:
3
4 > Replace Fuse with Future, which is similar more generic. The
5 > code ends up being slightly more verbose, but more flexible.
6 > The Future class will be useful elsewhere, including the
7 > EventLoop class.
8 > ---
9 > Applies to the *repoman* branch.
10 >
11 > pym/portage/util/futures.py | 118
12 > ++++++++++++++++++++++++++++
13 > pym/repoman/fuse.py | 68 ----------------
14 > pym/repoman/main.py | 12 ++-
15 > pym/repoman/modules/scan/ebuild/ebuild.py | 10 ++-
16 > pym/repoman/modules/scan/ebuild/isebuild.py | 25 ++++--
17 > pym/repoman/modules/scan/metadata/unused.py | 10 ++-
18 > pym/repoman/scanner.py | 4 +- 7 files changed,
19 > 164 insertions(+), 83 deletions(-) create mode 100644
20 > pym/portage/util/futures.py delete mode 100644 pym/repoman/fuse.py
21 >
22 > diff --git a/pym/portage/util/futures.py b/pym/portage/util/futures.py
23 > new file mode 100644
24 > index 0000000..c648f10
25 > --- /dev/null
26 > +++ b/pym/portage/util/futures.py
27 > @@ -0,0 +1,118 @@
28 > +# Copyright 2016 Gentoo Foundation
29 > +# Distributed under the terms of the GNU General Public License v2
30 > +#
31 > +# For compatibility with python versions which do not have the
32 > +# asyncio module (Python 3.3 and earlier), this module provides a
33 > +# subset of the asyncio.futures.Futures interface.
34 > +
35 > +from __future__ import unicode_literals
36 > +
37 > +__all__ = (
38 > + 'CancelledError',
39 > + 'Future',
40 > + 'InvalidStateError',
41 > +)
42 > +
43 > +try:
44 > + from asyncio import (
45 > + CancelledError,
46 > + Future,
47 > + InvalidStateError,
48 > + )
49 > +except ImportError:
50 > +
51 > + from portage.exception import PortageException
52 > +
53 > + _PENDING = 'PENDING'
54 > + _CANCELLED = 'CANCELLED'
55 > + _FINISHED = 'FINISHED'
56 > +
57 > + class Error(PortageException):
58 > + pass
59 > +
60 > + class CancelledError(Error):
61 > + def __init__(self):
62 > + Error.__init__(self, "cancelled")
63 > +
64 > + class InvalidStateError(Error):
65 > + pass
66 > +
67 > + class Future(object):
68 > +
69 > + # Class variables serving as defaults for instance
70 > variables.
71 > + _state = _PENDING
72 > + _result = None
73 > + _exception = None
74 > +
75 > + def cancel(self):
76 > + """Cancel the future and schedule callbacks.
77 > +
78 > + If the future is already done or cancelled,
79 > return False. Otherwise,
80 > + change the future's state to cancelled,
81 > schedule the callbacks and
82 > + return True.
83 > + """
84 > + if self._state != _PENDING:
85 > + return False
86 > + self._state = _CANCELLED
87 > + return True
88 > +
89 > + def done(self):
90 > + """Return True if the future is done.
91 > +
92 > + Done means either that a result / exception
93 > are available, or that the
94 > + future was cancelled.
95 > + """
96 > + return self._state != _PENDING
97 > +
98 > + def result(self):
99 > + """Return the result this future represents.
100 > +
101 > + If the future has been cancelled, raises
102 > CancelledError. If the
103 > + future's result isn't yet available, raises
104 > InvalidStateError. If
105 > + the future is done and has an exception set,
106 > this exception is raised.
107 > + """
108 > + if self._state == _CANCELLED:
109 > + raise CancelledError()
110 > + if self._state != _FINISHED:
111 > + raise InvalidStateError('Result is
112 > not ready.')
113 > + if self._exception is not None:
114 > + raise self._exception
115 > + return self._result
116 > +
117 > + def exception(self):
118 > + """Return the exception that was set on this
119 > future. +
120 > + The exception (or None if no exception was
121 > set) is returned only if
122 > + the future is done. If the future has been
123 > cancelled, raises
124 > + CancelledError. If the future isn't done
125 > yet, raises
126 > + InvalidStateError.
127 > + """
128 > + if self._state == _CANCELLED:
129 > + raise CancelledError
130 > + if self._state != _FINISHED:
131 > + raise InvalidStateError('Exception
132 > is not set.')
133 > + return self._exception
134 > +
135 > + def set_result(self, result):
136 > + """Mark the future done and set its result.
137 > +
138 > + If the future is already done when this
139 > method is called, raises
140 > + InvalidStateError.
141 > + """
142 > + if self._state != _PENDING:
143 > + raise InvalidStateError('{}:
144 > {!r}'.format(self._state, self))
145 > + self._result = result
146 > + self._state = _FINISHED
147 > +
148 > + def set_exception(self, exception):
149 > + """Mark the future done and set an exception.
150 > +
151 > + If the future is already done when this
152 > method is called, raises
153 > + InvalidStateError.
154 > + """
155 > + if self._state != _PENDING:
156 > + raise InvalidStateError('{}:
157 > {!r}'.format(self._state, self))
158 > + if isinstance(exception, type):
159 > + exception = exception()
160 > + self._exception = exception
161 > + self._state = _FINISHED
162 > diff --git a/pym/repoman/fuse.py b/pym/repoman/fuse.py
163 > deleted file mode 100644
164 > index ac864fd..0000000
165 > --- a/pym/repoman/fuse.py
166 > +++ /dev/null
167 > @@ -1,68 +0,0 @@
168 > -
169 > -'''
170 > -fuse.py
171 > -
172 > -A tiny one-time-fuse class that uses a boolean to mimic the property
173 > of -an electrical fuse. IT's good (True) until it is popped (bad,
174 > False). -It is not resetable.
175 > -'''
176 > -
177 > -
178 > -class Fuse(object):
179 > - '''A One time fuse style boolean instance'''
180 > -
181 > - __slots__ = ('_state')
182 > -
183 > - def __init__(self):
184 > - self._state = True
185 > -
186 > - def pop(self):
187 > - '''Blow's the fuse state (makes it False)'''
188 > - self._state = False
189 > -
190 > - def __repr__(self):
191 > - '''x.__repr__() <==> repr(x)'''
192 > - return repr(self._state>0)
193 > -
194 > - def __str__(self):
195 > - '''x.__str__() <==> str(x)'''
196 > - return ['False', 'True'][self._state]
197 > -
198 > - def __bool__(self):
199 > - '''self != 0'''
200 > - return self._state != 0
201 > -
202 > - def __nonzero__(self):
203 > - '''self != 0'''
204 > - return self._state != 0
205 > -
206 > - def __abs__(self):
207 > - '''x.__abs__() <==> abs(x)'''
208 > - return [0, 1] [self._state]
209 > -
210 > - def __int__(self):
211 > - '''int(self)'''
212 > - return [0, 1][self._state]
213 > -
214 > - def __eq__(self, value):
215 > - '''Return self==value.'''
216 > - return self._state == value
217 > -
218 > - def __ne__(self, value):
219 > - '''Return self!=value.'''
220 > - return self._state != value
221 > -
222 > - def __ge__(self, value):
223 > - '''Return self>=value.'''
224 > - return self._state >= value
225 > -
226 > - def __gt__(self, value):
227 > - return self._state > value
228 > -
229 > - def __le__(self, value):
230 > - '''Return self<=value.'''
231 > - return self._state <= value
232 > -
233 > - def __lt__(self, value):
234 > - '''Return self<value.'''
235 > - return self._state < value
236 > diff --git a/pym/repoman/main.py b/pym/repoman/main.py
237 > index 2ccda99..62c3c2c 100755
238 > --- a/pym/repoman/main.py
239 > +++ b/pym/repoman/main.py
240 > @@ -22,10 +22,13 @@ import portage.repository.config
241 > from portage.output import create_color_func, nocolor
242 > from portage.output import ConsoleStyleFile, StyleWriter
243 > from portage.util import formatter
244 > +from portage.util.futures import (
245 > + Future,
246 > + InvalidStateError,
247 > +)
248 >
249 > from repoman.actions import Actions
250 > from repoman.argparser import parse_args
251 > -from repoman.fuse import Fuse
252 > from repoman.qa_data import (
253 > format_qa_output, format_qa_output_column, qahelp,
254 > qawarnings, qacats)
255 > @@ -76,7 +79,7 @@ def repoman_main(argv):
256 > # Set this to False when an extraordinary issue (generally
257 > # something other than a QA issue) makes it impossible to
258 > # commit (like if Manifest generation fails).
259 > - can_force = Fuse()
260 > + can_force = Future()
261 >
262 > portdir, portdir_overlay, mydir =
263 > utilities.FindPortdir(repoman_settings) if portdir is None:
264 > @@ -171,6 +174,11 @@ def repoman_main(argv):
265 > qa_output = qa_output.getvalue()
266 > qa_output = qa_output.splitlines(True)
267 >
268 > + try:
269 > + can_force = can_force.result()
270 > + except InvalidStateError:
271 > + can_force = True
272 > +
273 > # output the results
274 > actions = Actions(repo_settings, options, scanner,
275 > vcs_settings) if actions.inform(can_force, result):
276 > diff --git a/pym/repoman/modules/scan/ebuild/ebuild.py
277 > b/pym/repoman/modules/scan/ebuild/ebuild.py index 540411f..67eee3f
278 > 100644 --- a/pym/repoman/modules/scan/ebuild/ebuild.py
279 > +++ b/pym/repoman/modules/scan/ebuild/ebuild.py
280 > @@ -8,6 +8,7 @@ from repoman.modules.scan.scanbase import ScanBase
281 > # import our initialized portage instance
282 > from repoman._portage import portage
283 > from portage import os
284 > +from portage.util.futures import InvalidStateError
285 >
286 > pv_toolong_re = re.compile(r'[0-9]{19,}')
287 >
288 > @@ -127,15 +128,18 @@ class Ebuild(ScanBase):
289 > def pkg_invalid(self, **kwargs):
290 > '''Sets some pkg info and checks for invalid packages
291 >
292 > - @param validity_fuse: Fuse instance
293 > + @param validity_future: Future instance
294 > @returns: dictionary, including {pkg object}
295 > '''
296 > - fuse = kwargs.get('validity_fuse')
297 > + fuse = kwargs.get('validity_future')
298 > if self.pkg.invalid:
299 > for k, msgs in self.pkg.invalid.items():
300 > for msg in msgs:
301 > self.qatracker.add_error(k,
302 > "%s: %s" % (self.relative_path, msg))
303 > - fuse.pop()
304 > + try:
305 > + fuse.set_result(False)
306 > + except InvalidStateError:
307 > + pass
308 > return {'continue': True, 'pkg': self.pkg}
309 > return {'continue': False, 'pkg': self.pkg}
310 >
311 > diff --git a/pym/repoman/modules/scan/ebuild/isebuild.py
312 > b/pym/repoman/modules/scan/ebuild/isebuild.py index 514d23e..a8870c7
313 > 100644 --- a/pym/repoman/modules/scan/ebuild/isebuild.py
314 > +++ b/pym/repoman/modules/scan/ebuild/isebuild.py
315 > @@ -9,6 +9,7 @@ from _emerge.RootConfig import RootConfig
316 > from repoman._portage import portage
317 >
318 > from portage import os
319 > +from portage.util.futures import InvalidStateError
320 >
321 > from repoman.qa_data import no_exec, allvars
322 > from repoman.modules.scan.scanbase import ScanBase
323 > @@ -35,13 +36,13 @@ class IsEbuild(ScanBase):
324 > @param checkdirlist: list of files in the current
325 > package directory @param checkdir: current package directory path
326 > @param xpkg: current package directory being checked
327 > - @param validity_fuse: Fuse instance
328 > + @param validity_future: Future instance
329 > @returns: dictionary, including {pkgs, can_force}
330 > '''
331 > checkdirlist = kwargs.get('checkdirlist')
332 > checkdir = kwargs.get('checkdir')
333 > xpkg = kwargs.get('xpkg')
334 > - fuse = kwargs.get('validity_fuse')
335 > + fuse = kwargs.get('validity_future')
336 > can_force = kwargs.get('can_force')
337 > self.continue_ = False
338 > ebuildlist = []
339 > @@ -64,15 +65,24 @@ class IsEbuild(ScanBase):
340 > try:
341 > myaux = dict(zip(allvars,
342 > self.portdb.aux_get(cpv, allvars))) except KeyError:
343 > - fuse.pop()
344 > + try:
345 > +
346 > fuse.set_result(False)
347 > + except InvalidStateError:
348 > + pass
349 > self.qatracker.add_error("ebuild.syntax",
350 > os.path.join(xpkg, y)) continue
351 > except IOError:
352 > - fuse.pop()
353 > + try:
354 > +
355 > fuse.set_result(False)
356 > + except InvalidStateError:
357 > + pass
358 > self.qatracker.add_error("ebuild.output",
359 > os.path.join(xpkg, y)) continue
360 > if not
361 > portage.eapi_is_supported(myaux["EAPI"]):
362 > - fuse.pop()
363 > + try:
364 > +
365 > fuse.set_result(False)
366 > + except InvalidStateError:
367 > + pass
368 > self.qatracker.add_error("EAPI.unsupported",
369 > os.path.join(xpkg, y)) continue
370 > pkgs[pf] = Package(
371 > @@ -86,7 +96,10 @@ class IsEbuild(ScanBase):
372 > # metadata leads to false positives for
373 > several checks, and false # positives confuse users.
374 > self.continue_ = True
375 > - can_force.pop()
376 > + try:
377 > + fuse.set_result(False)
378 > + except InvalidStateError:
379 > + pass
380 >
381 > return {'continue': self.continue_, 'pkgs': pkgs}
382 >
383 > diff --git a/pym/repoman/modules/scan/metadata/unused.py
384 > b/pym/repoman/modules/scan/metadata/unused.py index 6def48f..114c1f1
385 > 100644 --- a/pym/repoman/modules/scan/metadata/unused.py
386 > +++ b/pym/repoman/modules/scan/metadata/unused.py
387 > @@ -1,4 +1,6 @@
388 >
389 > +from portage.util.futures import InvalidStateError
390 > +
391 >
392 > class UnusedCheck(object):
393 > '''Checks and reports any un-used metadata.xml use flag
394 > descriptions''' @@ -16,14 +18,18 @@ class UnusedCheck(object):
395 > @param xpkg: the pacakge being checked
396 > @param muselist: use flag list
397 > @param used_useflags: use flag list
398 > - @param validity_fuse: Fuse instance
399 > + @param validity_future: Future instance
400 > '''
401 > xpkg = kwargs.get('xpkg')
402 > muselist = kwargs.get('muselist')
403 > used_useflags = kwargs.get('used_useflags')
404 > + try:
405 > + valid_state =
406 > kwargs['validity_future'].result()
407 > + except InvalidStateError:
408 > + valid_state = True
409 > # check if there are unused local USE-descriptions
410 > in metadata.xml # (unless there are any invalids, to avoid noise)
411 > - if kwargs.get('validity_fuse'):
412 > + if valid_state:
413 > for myflag in
414 > muselist.difference(used_useflags): self.qatracker.add_error(
415 > "metadata.warning",
416 > diff --git a/pym/repoman/scanner.py b/pym/repoman/scanner.py
417 > index a9f56e9..e9a8e20 100644
418 > --- a/pym/repoman/scanner.py
419 > +++ b/pym/repoman/scanner.py
420 > @@ -9,7 +9,7 @@ import portage
421 > from portage import normalize_path
422 > from portage import os
423 > from portage.output import green
424 > -from repoman.fuse import Fuse
425 > +from portage.util.futures import Future
426 > from repoman.modules.commit import repochecks
427 > from repoman.profile import check_profiles, dev_profile_keywords,
428 > setup_profile from repoman.repos import repo_metadata
429 > @@ -232,7 +232,7 @@ class Scanner(object):
430 > 'repolevel': self.repolevel,
431 > 'catdir': catdir,
432 > 'pkgdir': pkgdir,
433 > - 'validity_fuse': Fuse()
434 > + 'validity_future': Future()
435 > }
436 > # need to set it up for ==> self.modules or
437 > some other ordered list for mod in ['Manifests', 'IsEbuild',
438 > 'KeywordChecks', 'FileChecks',
439
440
441 Yeah, it's sat long enough with me to be fine with it this way, it is
442 more flexible to handle both default states but only in the code it is
443 used in. It also can work for strings and any other data that isn't
444 normally an instance pointer.
445
446 Push it and I'll use Future() for those where it is needed in the
447 module_spec func_kwargs.
448
449 --
450 Brian Dolbec <dolsen>

Replies