Gentoo Archives: gentoo-portage-dev

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

Replies