Merge ~cjwatson/lazr.delegates:no-py2 into lazr.delegates:main
- Git
- lp:~cjwatson/lazr.delegates
- no-py2
- Merge into main
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | edccc841995e77e9dbd61464b30b74ae63cb8dcd |
Proposed branch: | ~cjwatson/lazr.delegates:no-py2 |
Merge into: | lazr.delegates:main |
Diff against target: |
696 lines (+78/-270) 11 files modified
.pre-commit-config.yaml (+5/-0) NEWS.rst (+3/-3) dev/null (+0/-159) setup.py (+4/-5) src/lazr/delegates/__init__.py (+5/-11) src/lazr/delegates/_delegates.py (+51/-58) src/lazr/delegates/docs/conf.py (+0/-2) src/lazr/delegates/tests/test_api.py (+1/-8) src/lazr/delegates/tests/test_docs.py (+0/-9) src/lazr/delegates/tests/test_passthrough.py (+3/-3) tox.ini (+6/-12) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jürgen Gmach | Approve | ||
Review via email: mp+413588@code.launchpad.net |
Commit message
Drop Python 2 support
Description of the change
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
Revision history for this message
Jürgen Gmach (jugmac00) wrote : | # |
Looks good! Thank you for the update.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml |
2 | index 8691251..5d1fb12 100644 |
3 | --- a/.pre-commit-config.yaml |
4 | +++ b/.pre-commit-config.yaml |
5 | @@ -16,3 +16,8 @@ repos: |
6 | rev: ee781d3ce0ddf835267764f27f4ffdd2dd21fa27 |
7 | hooks: |
8 | - id: woke-from-source |
9 | +- repo: https://github.com/asottile/pyupgrade |
10 | + rev: v2.31.0 |
11 | + hooks: |
12 | + - id: pyupgrade |
13 | + args: [--keep-percent-format, --py3-plus] |
14 | diff --git a/NEWS.rst b/NEWS.rst |
15 | index af21504..80e1684 100644 |
16 | --- a/NEWS.rst |
17 | +++ b/NEWS.rst |
18 | @@ -2,12 +2,12 @@ |
19 | NEWS for lazr.delegates |
20 | ======================= |
21 | |
22 | -2.0.5 |
23 | +2.1.0 |
24 | ===== |
25 | - Officially add support for Python 3.6, 3.7, 3.8, 3.9, and 3.10. |
26 | -- Drop support for Python 3.2, 3.3, and 3.4. |
27 | +- Drop support for Python 2, 3.2, 3.3, and 3.4. |
28 | - Test using ``zope.testrunner`` rather than ``nose``. |
29 | -- Combine coverage for Python 2 and 3. |
30 | +- Bring coverage to 100%. |
31 | |
32 | |
33 | 2.0.4 (2017-10-20) |
34 | diff --git a/setup.py b/setup.py |
35 | index abe2368..9474d1d 100755 |
36 | --- a/setup.py |
37 | +++ b/setup.py |
38 | @@ -49,8 +49,6 @@ delegating behavior. |
39 | "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", |
40 | "Operating System :: OS Independent", |
41 | 'Programming Language :: Python', |
42 | - 'Programming Language :: Python :: 2', |
43 | - 'Programming Language :: Python :: 2.7', |
44 | 'Programming Language :: Python :: 3', |
45 | 'Programming Language :: Python :: 3.5', |
46 | 'Programming Language :: Python :: 3.6', |
47 | @@ -59,7 +57,8 @@ delegating behavior. |
48 | 'Programming Language :: Python :: 3.9', |
49 | 'Programming Language :: Python :: 3.10', |
50 | ], |
51 | - extras_require={ |
52 | - "docs": ["Sphinx"], |
53 | - }, |
54 | + extras_require={ |
55 | + "docs": ["Sphinx"], |
56 | + }, |
57 | + python_requires=">=3.5", |
58 | ) |
59 | diff --git a/src/lazr/delegates/__init__.py b/src/lazr/delegates/__init__.py |
60 | index b0b15de..eb56e29 100644 |
61 | --- a/src/lazr/delegates/__init__.py |
62 | +++ b/src/lazr/delegates/__init__.py |
63 | @@ -17,6 +17,7 @@ |
64 | """Decorator helpers that simplify class composition.""" |
65 | |
66 | __all__ = [ |
67 | + 'Passthrough', |
68 | 'delegate_to', |
69 | ] |
70 | |
71 | @@ -24,14 +25,7 @@ __all__ = [ |
72 | from lazr.delegates._version import __version__ |
73 | __version__ |
74 | |
75 | -from lazr.delegates._passthrough import Passthrough |
76 | - |
77 | -# The class decorator syntax is different in Python 2 vs. Python 3. |
78 | -import sys |
79 | -if sys.version_info[0] == 2: |
80 | - from lazr.delegates._python2 import delegate_to |
81 | - # The legacy API is only compatible with Python 2. |
82 | - from lazr.delegates._delegates import delegates |
83 | - __all__.append('delegates') |
84 | -else: |
85 | - from lazr.delegates._python3 import delegate_to |
86 | +from lazr.delegates._delegates import ( |
87 | + Passthrough, |
88 | + delegate_to, |
89 | + ) |
90 | diff --git a/src/lazr/delegates/_delegates.py b/src/lazr/delegates/_delegates.py |
91 | index ae09920..eb7c1b3 100644 |
92 | --- a/src/lazr/delegates/_delegates.py |
93 | +++ b/src/lazr/delegates/_delegates.py |
94 | @@ -1,4 +1,4 @@ |
95 | -# Copyright 2008-2015 Canonical Ltd. All rights reserved. |
96 | +# Copyright 2008-2022 Canonical Ltd. All rights reserved. |
97 | # |
98 | # This file is part of lazr.delegates. |
99 | # |
100 | @@ -16,34 +16,22 @@ |
101 | |
102 | """Decorator helpers that simplify class composition.""" |
103 | |
104 | -from __future__ import absolute_import, print_function, unicode_literals |
105 | - |
106 | - |
107 | -__metaclass__ = type |
108 | __all__ = [ |
109 | - 'delegates', |
110 | + 'Passthrough', |
111 | + 'delegate_to', |
112 | ] |
113 | |
114 | - |
115 | -import sys |
116 | -from types import ClassType |
117 | - |
118 | -from zope.interface.advice import addClassAdvisor |
119 | from zope.interface import classImplements |
120 | -from zope.interface.interfaces import IInterface |
121 | |
122 | -from lazr.delegates._passthrough import Passthrough |
123 | |
124 | - |
125 | -def delegates(interface_spec, context='context'): |
126 | +def delegate_to(*interfaces, context='context'): |
127 | """Make an adapter into a decorator. |
128 | |
129 | Use like: |
130 | |
131 | + @implementer(IRosettaProject) |
132 | + @delegate_to(IProject) |
133 | class RosettaProject: |
134 | - implements(IRosettaProject) |
135 | - delegates(IProject) |
136 | - |
137 | def __init__(self, context): |
138 | self.context = context |
139 | |
140 | @@ -53,10 +41,9 @@ def delegates(interface_spec, context='context'): |
141 | If you want to use a different name than "context" then you can explicitly |
142 | say so: |
143 | |
144 | + @implementer(IRosettaProject) |
145 | + @delegate_to(IProject, context='project') |
146 | class RosettaProject: |
147 | - implements(IRosettaProject) |
148 | - delegates(IProject, context='project') |
149 | - |
150 | def __init__(self, project): |
151 | self.project = project |
152 | |
153 | @@ -67,45 +54,51 @@ def delegates(interface_spec, context='context'): |
154 | |
155 | The minimal decorator looks like this: |
156 | |
157 | + @delegate_to(IProject) |
158 | class RosettaProject: |
159 | - delegates(IProject) |
160 | - |
161 | def __init__(self, context): |
162 | self.context = context |
163 | - |
164 | """ |
165 | - # pylint: disable-msg=W0212 |
166 | - frame = sys._getframe(1) |
167 | - locals = frame.f_locals |
168 | - |
169 | - # Try to make sure we were called from a class def |
170 | - if (locals is frame.f_globals) or ('__module__' not in locals): |
171 | - raise TypeError( |
172 | - "delegates() can be used only from a class definition.") |
173 | - |
174 | - locals['__delegates_advice_data__'] = interface_spec, context |
175 | - addClassAdvisor(_delegates_advice, depth=2) |
176 | - |
177 | - |
178 | -def _delegates_advice(cls): |
179 | - """Add a Passthrough class for each missing interface attribute. |
180 | - |
181 | - This function connects the decorator class to the delegate class. |
182 | - Only new-style classes are supported. |
183 | + if len(interfaces) == 0: |
184 | + raise TypeError('At least one interface is required') |
185 | + def _decorator(cls): |
186 | + missing = object() |
187 | + for interface in interfaces: |
188 | + classImplements(cls, interface) |
189 | + for name in interface: |
190 | + if getattr(cls, name, missing) is missing: |
191 | + setattr(cls, name, Passthrough(name, context)) |
192 | + return cls |
193 | + return _decorator |
194 | + |
195 | + |
196 | +class Passthrough: |
197 | + """Call the delegated class for the decorator class. |
198 | + |
199 | + If the ``adaptation`` argument is not None, it should be a callable. It |
200 | + will be called with the context, and should return an object that will |
201 | + have the delegated attribute. The ``adaptation`` argument is expected to |
202 | + be used with an interface, to adapt the context. |
203 | """ |
204 | - interface_spec, contextvar = cls.__dict__['__delegates_advice_data__'] |
205 | - del cls.__delegates_advice_data__ |
206 | - if isinstance(cls, ClassType): |
207 | - raise TypeError( |
208 | - 'Cannot use delegates() on a classic class: %s.' % cls) |
209 | - if IInterface.providedBy(interface_spec): |
210 | - interfaces = [interface_spec] |
211 | - else: |
212 | - interfaces = interface_spec |
213 | - not_found = object() |
214 | - for interface in interfaces: |
215 | - classImplements(cls, interface) |
216 | - for name in interface: |
217 | - if getattr(cls, name, not_found) is not_found: |
218 | - setattr(cls, name, Passthrough(name, contextvar)) |
219 | - return cls |
220 | + def __init__(self, name, contextvar, adaptation=None): |
221 | + self.name = name |
222 | + self.contextvar = contextvar |
223 | + self.adaptation = adaptation |
224 | + |
225 | + def __get__(self, inst, cls=None): |
226 | + if inst is None: |
227 | + return self |
228 | + else: |
229 | + context = getattr(inst, self.contextvar) |
230 | + if self.adaptation is not None: |
231 | + context = self.adaptation(context) |
232 | + return getattr(context, self.name) |
233 | + |
234 | + def __set__(self, inst, value): |
235 | + context = getattr(inst, self.contextvar) |
236 | + if self.adaptation is not None: |
237 | + context = self.adaptation(context) |
238 | + setattr(context, self.name, value) |
239 | + |
240 | + def __delete__(self, inst): |
241 | + raise NotImplementedError |
242 | diff --git a/src/lazr/delegates/_passthrough.py b/src/lazr/delegates/_passthrough.py |
243 | deleted file mode 100644 |
244 | index 144f689..0000000 |
245 | --- a/src/lazr/delegates/_passthrough.py |
246 | +++ /dev/null |
247 | @@ -1,55 +0,0 @@ |
248 | -# Copyright 2008-2015 Canonical Ltd. All rights reserved. |
249 | -# |
250 | -# This file is part of lazr.delegates. |
251 | -# |
252 | -# lazr.delegates is free software: you can redistribute it and/or modify it |
253 | -# under the terms of the GNU Lesser General Public License as published by |
254 | -# the Free Software Foundation, version 3 of the License. |
255 | -# |
256 | -# lazr.delegates is distributed in the hope that it will be useful, but |
257 | -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
258 | -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
259 | -# License for more details. |
260 | -# |
261 | -# You should have received a copy of the GNU Lesser General Public License |
262 | -# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. |
263 | - |
264 | -from __future__ import absolute_import, print_function, unicode_literals |
265 | - |
266 | - |
267 | -__metaclass__ = type |
268 | -__all__ = [ |
269 | - 'Passthrough', |
270 | - ] |
271 | - |
272 | - |
273 | -class Passthrough: |
274 | - """Call the delegated class for the decorator class. |
275 | - |
276 | - If the ``adaptation`` argument is not None, it should be a callable. It |
277 | - will be called with the context, and should return an object that will |
278 | - have the delegated attribute. The ``adaptation`` argument is expected to |
279 | - be used with an interface, to adapt the context. |
280 | - """ |
281 | - def __init__(self, name, contextvar, adaptation=None): |
282 | - self.name = name |
283 | - self.contextvar = contextvar |
284 | - self.adaptation = adaptation |
285 | - |
286 | - def __get__(self, inst, cls=None): |
287 | - if inst is None: |
288 | - return self |
289 | - else: |
290 | - context = getattr(inst, self.contextvar) |
291 | - if self.adaptation is not None: |
292 | - context = self.adaptation(context) |
293 | - return getattr(context, self.name) |
294 | - |
295 | - def __set__(self, inst, value): |
296 | - context = getattr(inst, self.contextvar) |
297 | - if self.adaptation is not None: |
298 | - context = self.adaptation(context) |
299 | - setattr(context, self.name, value) |
300 | - |
301 | - def __delete__(self, inst): |
302 | - raise NotImplementedError |
303 | diff --git a/src/lazr/delegates/_python2.py b/src/lazr/delegates/_python2.py |
304 | deleted file mode 100644 |
305 | index 2ce472a..0000000 |
306 | --- a/src/lazr/delegates/_python2.py |
307 | +++ /dev/null |
308 | @@ -1,38 +0,0 @@ |
309 | -# Copyright 2014-2015 Canonical Ltd. All rights reserved. |
310 | -# |
311 | -# This file is part of lazr.delegates. |
312 | -# |
313 | -# lazr.delegates is free software: you can redistribute it and/or modify it |
314 | -# under the terms of the GNU Lesser General Public License as published by |
315 | -# the Free Software Foundation, version 3 of the License. |
316 | -# |
317 | -# lazr.delegates is distributed in the hope that it will be useful, but |
318 | -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
319 | -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
320 | -# License for more details. |
321 | -# |
322 | -# You should have received a copy of the GNU Lesser General Public License |
323 | -# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. |
324 | - |
325 | -"""Class decorator definition for Python 2.""" |
326 | - |
327 | -from zope.interface import classImplements |
328 | - |
329 | -from lazr.delegates._passthrough import Passthrough |
330 | - |
331 | - |
332 | -def delegate_to(*interfaces, **kws): |
333 | - context = kws.pop('context', 'context') |
334 | - if len(kws) > 0: |
335 | - raise TypeError('Too many arguments') |
336 | - if len(interfaces) == 0: |
337 | - raise TypeError('At least one interface is required') |
338 | - def _decorator(cls): |
339 | - missing = object() |
340 | - for interface in interfaces: |
341 | - classImplements(cls, interface) |
342 | - for name in interface: |
343 | - if getattr(cls, name, missing) is missing: |
344 | - setattr(cls, name, Passthrough(name, context)) |
345 | - return cls |
346 | - return _decorator |
347 | diff --git a/src/lazr/delegates/_python3.py b/src/lazr/delegates/_python3.py |
348 | deleted file mode 100644 |
349 | index f9430f5..0000000 |
350 | --- a/src/lazr/delegates/_python3.py |
351 | +++ /dev/null |
352 | @@ -1,38 +0,0 @@ |
353 | -# Copyright 2014-2015 Canonical Ltd. All rights reserved. |
354 | -# |
355 | -# This file is part of lazr.delegates. |
356 | -# |
357 | -# lazr.delegates is free software: you can redistribute it and/or modify it |
358 | -# under the terms of the GNU Lesser General Public License as published by |
359 | -# the Free Software Foundation, version 3 of the License. |
360 | -# |
361 | -# lazr.delegates is distributed in the hope that it will be useful, but |
362 | -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
363 | -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
364 | -# License for more details. |
365 | -# |
366 | -# You should have received a copy of the GNU Lesser General Public License |
367 | -# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. |
368 | - |
369 | -"""Class decorator definition for Python 3. |
370 | - |
371 | -This syntax is not legal in Python 2. |
372 | -""" |
373 | - |
374 | -from zope.interface import classImplements |
375 | - |
376 | -from lazr.delegates._passthrough import Passthrough |
377 | - |
378 | - |
379 | -def delegate_to(*interfaces, context='context'): |
380 | - if len(interfaces) == 0: |
381 | - raise TypeError('At least one interface is required') |
382 | - def _decorator(cls): |
383 | - missing = object() |
384 | - for interface in interfaces: |
385 | - classImplements(cls, interface) |
386 | - for name in interface: |
387 | - if getattr(cls, name, missing) is missing: |
388 | - setattr(cls, name, Passthrough(name, context)) |
389 | - return cls |
390 | - return _decorator |
391 | diff --git a/src/lazr/delegates/docs/conf.py b/src/lazr/delegates/docs/conf.py |
392 | index fed69d1..adc0c50 100644 |
393 | --- a/src/lazr/delegates/docs/conf.py |
394 | +++ b/src/lazr/delegates/docs/conf.py |
395 | @@ -1,5 +1,3 @@ |
396 | -# -*- coding: utf-8 -*- |
397 | -# |
398 | # lazr.delegates documentation build configuration file, created by |
399 | # sphinx-quickstart on Mon Jan 7 10:37:37 2013. |
400 | # |
401 | diff --git a/src/lazr/delegates/tests/test_api.py b/src/lazr/delegates/tests/test_api.py |
402 | index 5ecea47..4f6f200 100644 |
403 | --- a/src/lazr/delegates/tests/test_api.py |
404 | +++ b/src/lazr/delegates/tests/test_api.py |
405 | @@ -25,11 +25,4 @@ class TestAPI(unittest.TestCase): |
406 | """Test various corner cases in the API.""" |
407 | |
408 | def test_no_interfaces(self): |
409 | - try: |
410 | - @delegate_to() |
411 | - class SomeClass(object): |
412 | - pass |
413 | - except TypeError: |
414 | - pass |
415 | - else: |
416 | - self.fail('TypeError expected') |
417 | + self.assertRaises(TypeError, delegate_to) |
418 | diff --git a/src/lazr/delegates/tests/test_docs.py b/src/lazr/delegates/tests/test_docs.py |
419 | index 7ca6971..9e19ce4 100644 |
420 | --- a/src/lazr/delegates/tests/test_docs.py |
421 | +++ b/src/lazr/delegates/tests/test_docs.py |
422 | @@ -16,9 +16,6 @@ |
423 | |
424 | """Test harness for doctests.""" |
425 | |
426 | -from __future__ import absolute_import, print_function, unicode_literals |
427 | - |
428 | -__metaclass__ = type |
429 | __all__ = [] |
430 | |
431 | import atexit |
432 | @@ -50,17 +47,11 @@ def load_tests(loader, tests, pattern): |
433 | ) |
434 | ) |
435 | atexit.register(cleanup_resources) |
436 | - globs = { |
437 | - "absolute_import": absolute_import, |
438 | - "print_function": print_function, |
439 | - "unicode_literals": unicode_literals, |
440 | - } |
441 | tests.addTest( |
442 | doctest.DocFileSuite( |
443 | *doctest_files, |
444 | module_relative=False, |
445 | optionflags=DOCTEST_FLAGS, |
446 | - globs=globs, |
447 | encoding="UTF-8" |
448 | ) |
449 | ) |
450 | diff --git a/src/lazr/delegates/tests/test_passthrough.py b/src/lazr/delegates/tests/test_passthrough.py |
451 | index 009ed73..0905e3b 100644 |
452 | --- a/src/lazr/delegates/tests/test_passthrough.py |
453 | +++ b/src/lazr/delegates/tests/test_passthrough.py |
454 | @@ -58,13 +58,13 @@ class TestPassthrough(unittest.TestCase): |
455 | # provided, should be a zope.interface.Interface subclass (although in |
456 | # practice any callable will do) to which the instance is adapted |
457 | # before getting/setting the delegated attribute. |
458 | - class HasNoFoo(object): |
459 | + class HasNoFoo: |
460 | _foo = 1 |
461 | no_foo = HasNoFoo() |
462 | # ... but IHasFooAdapter uses HasNoFoo._foo to provide its own .foo, |
463 | # so it works like an adapter for HasNoFoo into some interface that |
464 | # provides a 'foo' attribute. |
465 | - class IHasFooAdapter(object): |
466 | + class IHasFooAdapter: |
467 | def __init__(self, inst): |
468 | self.inst = inst |
469 | @property |
470 | @@ -74,7 +74,7 @@ class TestPassthrough(unittest.TestCase): |
471 | def foo(self, value): |
472 | self.inst._foo = value |
473 | |
474 | - class Example(object): |
475 | + class Example: |
476 | context = no_foo |
477 | |
478 | p = Passthrough('foo', 'context', adaptation=IHasFooAdapter) |
479 | diff --git a/src/lazr/delegates/tests/test_python2.py b/src/lazr/delegates/tests/test_python2.py |
480 | deleted file mode 100644 |
481 | index ad3de7e..0000000 |
482 | --- a/src/lazr/delegates/tests/test_python2.py |
483 | +++ /dev/null |
484 | @@ -1,159 +0,0 @@ |
485 | -# Copyright 2013-2015 Canonical Ltd. All rights reserved. |
486 | -# |
487 | -# This file is part of lazr.delegates. |
488 | -# |
489 | -# lazr.delegates is free software: you can redistribute it and/or modify it |
490 | -# under the terms of the GNU Lesser General Public License as published by |
491 | -# the Free Software Foundation, version 3 of the License. |
492 | -# |
493 | -# lazr.delegates is distributed in the hope that it will be useful, but |
494 | -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
495 | -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
496 | -# License for more details. |
497 | -# |
498 | -# You should have received a copy of the GNU Lesser General Public License |
499 | -# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. |
500 | - |
501 | -"""Test the legacy API, which only works in Python 2. |
502 | - |
503 | -All of these tests are copied almost verbatim from the old README.rst. |
504 | -""" |
505 | - |
506 | - |
507 | -from __future__ import absolute_import, print_function, unicode_literals |
508 | - |
509 | - |
510 | -# Don't enable the following or we can't test classic class failures. |
511 | -#__metaclass__ = type |
512 | -__all__ = [ |
513 | - 'TestLegacyAPI', |
514 | - ] |
515 | - |
516 | - |
517 | -import sys |
518 | -import unittest |
519 | - |
520 | -from zope.interface import Attribute, Interface, implements, providedBy |
521 | - |
522 | -from lazr.delegates import delegate_to |
523 | -if sys.version_info[0] == 2: |
524 | - from lazr.delegates import delegates |
525 | - |
526 | - |
527 | -class IFoo0(Interface): |
528 | - spoo = Attribute('attribute in IFoo0') |
529 | - |
530 | -class IFoo(IFoo0): |
531 | - def bar(): |
532 | - 'some method' |
533 | - baz = Attribute('some attribute') |
534 | - |
535 | -class BaseFoo0: |
536 | - spoo = 'some spoo' |
537 | - |
538 | -class BaseFoo(BaseFoo0): |
539 | - def bar(self): |
540 | - return 'bar' |
541 | - baz = 'hi baz!' |
542 | - |
543 | -class IOther(Interface): |
544 | - another = Attribute('another attribute') |
545 | - |
546 | -class BaseOtherFoo(BaseFoo): |
547 | - another = 'yes, another' |
548 | - |
549 | - |
550 | -@unittest.skipIf(sys.version_info[0] > 2, "only relevant in Python 2") |
551 | -class TestLegacyAPI(unittest.TestCase): |
552 | - def test_basic_usage(self): |
553 | - class SomeClass(object): |
554 | - delegates(IFoo) |
555 | - def __init__(self, context): |
556 | - self.context = context |
557 | - |
558 | - f = BaseFoo() |
559 | - s = SomeClass(f) |
560 | - self.assertEqual(s.bar(), 'bar') |
561 | - self.assertEqual(s.baz, 'hi baz!') |
562 | - self.assertEqual(s.spoo, 'some spoo') |
563 | - self.assertTrue(IFoo.providedBy(s)) |
564 | - |
565 | - def test_keyword_context(self): |
566 | - class SomeOtherClass(object): |
567 | - delegates(IFoo, context='myfoo') |
568 | - def __init__(self, foo): |
569 | - self.myfoo = foo |
570 | - spoo = 'spoo from SomeOtherClass' |
571 | - |
572 | - f = BaseFoo() |
573 | - s = SomeOtherClass(f) |
574 | - self.assertEqual(s.bar(), 'bar') |
575 | - self.assertEqual(s.baz, 'hi baz!') |
576 | - self.assertEqual(s.spoo, 'spoo from SomeOtherClass') |
577 | - |
578 | - s.baz = 'fish' |
579 | - self.assertEqual(s.baz, 'fish') |
580 | - self.assertEqual(f.baz, 'fish') |
581 | - |
582 | - def test_classic_is_error(self): |
583 | - try: |
584 | - class SomeClassicClass: |
585 | - delegates(IFoo) |
586 | - except TypeError: |
587 | - pass |
588 | - else: |
589 | - self.fail('TypeError expected') |
590 | - |
591 | - def test_use_outside_class_is_error(self): |
592 | - self.assertRaises(TypeError, delegates, IFoo) |
593 | - |
594 | - def test_multiple_interfaces(self): |
595 | - class SomeOtherClass(object): |
596 | - delegates([IFoo, IOther]) |
597 | - |
598 | - s = SomeOtherClass() |
599 | - s.context = BaseOtherFoo() |
600 | - self.assertEqual(s.another, 'yes, another') |
601 | - self.assertEqual(s.baz, 'hi baz!') |
602 | - self.assertEqual(s.spoo, 'some spoo') |
603 | - self.assertTrue(IFoo.providedBy(s)) |
604 | - self.assertTrue(IOther.providedBy(s)) |
605 | - |
606 | - def test_decorate_existing_object(self): |
607 | - class MoreFoo(BaseFoo, BaseOtherFoo): |
608 | - implements([IFoo, IOther]) |
609 | - |
610 | - foo = MoreFoo() |
611 | - |
612 | - class WithExtraTeapot(object): |
613 | - delegates(providedBy(foo)) |
614 | - teapot = 'i am a teapot' |
615 | - |
616 | - foo_with_teapot = WithExtraTeapot() |
617 | - foo_with_teapot.context = foo |
618 | - |
619 | - self.assertEqual(foo_with_teapot.baz, 'hi baz!') |
620 | - self.assertEqual(foo_with_teapot.another, 'yes, another') |
621 | - self.assertEqual(foo_with_teapot.teapot, 'i am a teapot') |
622 | - self.assertTrue(IFoo.providedBy(foo_with_teapot)) |
623 | - self.assertTrue(IOther.providedBy(foo_with_teapot)) |
624 | - |
625 | - |
626 | -@unittest.skipIf(sys.version_info[0] > 2, "only relevant in Python 2") |
627 | -class TestNewAPI(unittest.TestCase): |
628 | - """Test corner cases in Python 2. |
629 | - |
630 | - Most of the new API is tested in the doctest. The implementation of the |
631 | - new API is different between Python 2 and Python 3, so test these corner |
632 | - cases. |
633 | - """ |
634 | - def test_type_error(self): |
635 | - # Too many arguments to @delegate_to() raises a TypeError. |
636 | - try: |
637 | - @delegate_to(IFoo0, context='myfoo', other='bogus') |
638 | - class SomeClass(object): |
639 | - pass |
640 | - except TypeError: |
641 | - pass |
642 | - else: |
643 | - self.fail('TypeError expected') |
644 | diff --git a/tox.ini b/tox.ini |
645 | index 7bdfc27..451caed 100644 |
646 | --- a/tox.ini |
647 | +++ b/tox.ini |
648 | @@ -1,7 +1,6 @@ |
649 | [tox] |
650 | envlist = |
651 | lint |
652 | - py27 |
653 | py35 |
654 | py36 |
655 | py37 |
656 | @@ -13,11 +12,9 @@ envlist = |
657 | |
658 | [testenv] |
659 | deps = |
660 | - . |
661 | zope.testrunner |
662 | - coverage |
663 | commands = |
664 | - coverage run -m zope.testrunner --test-path src --tests-pattern ^tests {posargs} |
665 | + zope-testrunner --test-path src --tests-pattern ^tests {posargs} |
666 | |
667 | [testenv:lint] |
668 | basepython = python3.8 |
669 | @@ -36,22 +33,19 @@ deps = |
670 | .[docs] |
671 | |
672 | [testenv:coverage] |
673 | -description = Run coverage either via `tox` or `tox -e py27,py38,coverage` |
674 | basepython = python3 |
675 | -skip_install = true |
676 | -depends = |
677 | - py27 |
678 | - py38 |
679 | deps = |
680 | coverage |
681 | + zope.testrunner |
682 | commands = |
683 | - coverage combine |
684 | - coverage report -m --fail-under=97 |
685 | + coverage erase |
686 | + coverage run -m zope.testrunner --test-path src --tests-pattern ^tests {posargs} |
687 | + coverage html |
688 | + coverage report -m --fail-under=100 |
689 | |
690 | [coverage:run] |
691 | source = lazr.delegates |
692 | omit = */docs/conf.py |
693 | -parallel = true |
694 | |
695 | [coverage:paths] |
696 | source = |
LGTM with two minor nitpicks