Gentoo Archives: gentoo-portage-dev

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