Merge ~cjwatson/lazr.delegates:no-py2 into lazr.delegates: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)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+413588@code.launchpad.net

Commit message

Drop Python 2 support

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) wrote (last edit ):

LGTM with two minor nitpicks

review: Approve
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
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8691251..5d1fb12 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,3 +16,8 @@ repos:
16 rev: ee781d3ce0ddf835267764f27f4ffdd2dd21fa2716 rev: ee781d3ce0ddf835267764f27f4ffdd2dd21fa27
17 hooks:17 hooks:
18 - id: woke-from-source18 - id: woke-from-source
19- repo: https://github.com/asottile/pyupgrade
20 rev: v2.31.0
21 hooks:
22 - id: pyupgrade
23 args: [--keep-percent-format, --py3-plus]
diff --git a/NEWS.rst b/NEWS.rst
index af21504..80e1684 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -2,12 +2,12 @@
2NEWS for lazr.delegates2NEWS for lazr.delegates
3=======================3=======================
44
52.0.552.1.0
6=====6=====
7- Officially add support for Python 3.6, 3.7, 3.8, 3.9, and 3.10.7- Officially add support for Python 3.6, 3.7, 3.8, 3.9, and 3.10.
8- Drop support for Python 3.2, 3.3, and 3.4.8- Drop support for Python 2, 3.2, 3.3, and 3.4.
9- Test using ``zope.testrunner`` rather than ``nose``.9- Test using ``zope.testrunner`` rather than ``nose``.
10- Combine coverage for Python 2 and 3.10- Bring coverage to 100%.
1111
1212
132.0.4 (2017-10-20)132.0.4 (2017-10-20)
diff --git a/setup.py b/setup.py
index abe2368..9474d1d 100755
--- a/setup.py
+++ b/setup.py
@@ -49,8 +49,6 @@ delegating behavior.
49 "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",49 "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
50 "Operating System :: OS Independent",50 "Operating System :: OS Independent",
51 'Programming Language :: Python',51 'Programming Language :: Python',
52 'Programming Language :: Python :: 2',
53 'Programming Language :: Python :: 2.7',
54 'Programming Language :: Python :: 3',52 'Programming Language :: Python :: 3',
55 'Programming Language :: Python :: 3.5',53 'Programming Language :: Python :: 3.5',
56 'Programming Language :: Python :: 3.6',54 'Programming Language :: Python :: 3.6',
@@ -59,7 +57,8 @@ delegating behavior.
59 'Programming Language :: Python :: 3.9',57 'Programming Language :: Python :: 3.9',
60 'Programming Language :: Python :: 3.10',58 'Programming Language :: Python :: 3.10',
61 ],59 ],
62 extras_require={60 extras_require={
63 "docs": ["Sphinx"],61 "docs": ["Sphinx"],
64 },62 },
63 python_requires=">=3.5",
65 )64 )
diff --git a/src/lazr/delegates/__init__.py b/src/lazr/delegates/__init__.py
index b0b15de..eb56e29 100644
--- a/src/lazr/delegates/__init__.py
+++ b/src/lazr/delegates/__init__.py
@@ -17,6 +17,7 @@
17"""Decorator helpers that simplify class composition."""17"""Decorator helpers that simplify class composition."""
1818
19__all__ = [19__all__ = [
20 'Passthrough',
20 'delegate_to',21 'delegate_to',
21 ]22 ]
2223
@@ -24,14 +25,7 @@ __all__ = [
24from lazr.delegates._version import __version__25from lazr.delegates._version import __version__
25__version__26__version__
2627
27from lazr.delegates._passthrough import Passthrough28from lazr.delegates._delegates import (
2829 Passthrough,
29# The class decorator syntax is different in Python 2 vs. Python 3.30 delegate_to,
30import sys31 )
31if sys.version_info[0] == 2:
32 from lazr.delegates._python2 import delegate_to
33 # The legacy API is only compatible with Python 2.
34 from lazr.delegates._delegates import delegates
35 __all__.append('delegates')
36else:
37 from lazr.delegates._python3 import delegate_to
diff --git a/src/lazr/delegates/_delegates.py b/src/lazr/delegates/_delegates.py
index ae09920..eb7c1b3 100644
--- a/src/lazr/delegates/_delegates.py
+++ b/src/lazr/delegates/_delegates.py
@@ -1,4 +1,4 @@
1# Copyright 2008-2015 Canonical Ltd. All rights reserved.1# Copyright 2008-2022 Canonical Ltd. All rights reserved.
2#2#
3# This file is part of lazr.delegates.3# This file is part of lazr.delegates.
4#4#
@@ -16,34 +16,22 @@
1616
17"""Decorator helpers that simplify class composition."""17"""Decorator helpers that simplify class composition."""
1818
19from __future__ import absolute_import, print_function, unicode_literals
20
21
22__metaclass__ = type
23__all__ = [19__all__ = [
24 'delegates',20 'Passthrough',
21 'delegate_to',
25 ]22 ]
2623
27
28import sys
29from types import ClassType
30
31from zope.interface.advice import addClassAdvisor
32from zope.interface import classImplements24from zope.interface import classImplements
33from zope.interface.interfaces import IInterface
3425
35from lazr.delegates._passthrough import Passthrough
3626
3727def delegate_to(*interfaces, context='context'):
38def delegates(interface_spec, context='context'):
39 """Make an adapter into a decorator.28 """Make an adapter into a decorator.
4029
41 Use like:30 Use like:
4231
32 @implementer(IRosettaProject)
33 @delegate_to(IProject)
43 class RosettaProject:34 class RosettaProject:
44 implements(IRosettaProject)
45 delegates(IProject)
46
47 def __init__(self, context):35 def __init__(self, context):
48 self.context = context36 self.context = context
4937
@@ -53,10 +41,9 @@ def delegates(interface_spec, context='context'):
53 If you want to use a different name than "context" then you can explicitly41 If you want to use a different name than "context" then you can explicitly
54 say so:42 say so:
5543
44 @implementer(IRosettaProject)
45 @delegate_to(IProject, context='project')
56 class RosettaProject:46 class RosettaProject:
57 implements(IRosettaProject)
58 delegates(IProject, context='project')
59
60 def __init__(self, project):47 def __init__(self, project):
61 self.project = project48 self.project = project
6249
@@ -67,45 +54,51 @@ def delegates(interface_spec, context='context'):
6754
68 The minimal decorator looks like this:55 The minimal decorator looks like this:
6956
57 @delegate_to(IProject)
70 class RosettaProject:58 class RosettaProject:
71 delegates(IProject)
72
73 def __init__(self, context):59 def __init__(self, context):
74 self.context = context60 self.context = context
75
76 """61 """
77 # pylint: disable-msg=W021262 if len(interfaces) == 0:
78 frame = sys._getframe(1)63 raise TypeError('At least one interface is required')
79 locals = frame.f_locals64 def _decorator(cls):
8065 missing = object()
81 # Try to make sure we were called from a class def66 for interface in interfaces:
82 if (locals is frame.f_globals) or ('__module__' not in locals):67 classImplements(cls, interface)
83 raise TypeError(68 for name in interface:
84 "delegates() can be used only from a class definition.")69 if getattr(cls, name, missing) is missing:
8570 setattr(cls, name, Passthrough(name, context))
86 locals['__delegates_advice_data__'] = interface_spec, context71 return cls
87 addClassAdvisor(_delegates_advice, depth=2)72 return _decorator
8873
8974
90def _delegates_advice(cls):75class Passthrough:
91 """Add a Passthrough class for each missing interface attribute.76 """Call the delegated class for the decorator class.
9277
93 This function connects the decorator class to the delegate class.78 If the ``adaptation`` argument is not None, it should be a callable. It
94 Only new-style classes are supported.79 will be called with the context, and should return an object that will
80 have the delegated attribute. The ``adaptation`` argument is expected to
81 be used with an interface, to adapt the context.
95 """82 """
96 interface_spec, contextvar = cls.__dict__['__delegates_advice_data__']83 def __init__(self, name, contextvar, adaptation=None):
97 del cls.__delegates_advice_data__84 self.name = name
98 if isinstance(cls, ClassType):85 self.contextvar = contextvar
99 raise TypeError(86 self.adaptation = adaptation
100 'Cannot use delegates() on a classic class: %s.' % cls)87
101 if IInterface.providedBy(interface_spec):88 def __get__(self, inst, cls=None):
102 interfaces = [interface_spec]89 if inst is None:
103 else:90 return self
104 interfaces = interface_spec91 else:
105 not_found = object()92 context = getattr(inst, self.contextvar)
106 for interface in interfaces:93 if self.adaptation is not None:
107 classImplements(cls, interface)94 context = self.adaptation(context)
108 for name in interface:95 return getattr(context, self.name)
109 if getattr(cls, name, not_found) is not_found:96
110 setattr(cls, name, Passthrough(name, contextvar))97 def __set__(self, inst, value):
111 return cls98 context = getattr(inst, self.contextvar)
99 if self.adaptation is not None:
100 context = self.adaptation(context)
101 setattr(context, self.name, value)
102
103 def __delete__(self, inst):
104 raise NotImplementedError
diff --git a/src/lazr/delegates/_passthrough.py b/src/lazr/delegates/_passthrough.py
112deleted file mode 100644105deleted file mode 100644
index 144f689..0000000
--- a/src/lazr/delegates/_passthrough.py
+++ /dev/null
@@ -1,55 +0,0 @@
1# Copyright 2008-2015 Canonical Ltd. All rights reserved.
2#
3# This file is part of lazr.delegates.
4#
5# lazr.delegates is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# lazr.delegates is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12# License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
16
17from __future__ import absolute_import, print_function, unicode_literals
18
19
20__metaclass__ = type
21__all__ = [
22 'Passthrough',
23 ]
24
25
26class Passthrough:
27 """Call the delegated class for the decorator class.
28
29 If the ``adaptation`` argument is not None, it should be a callable. It
30 will be called with the context, and should return an object that will
31 have the delegated attribute. The ``adaptation`` argument is expected to
32 be used with an interface, to adapt the context.
33 """
34 def __init__(self, name, contextvar, adaptation=None):
35 self.name = name
36 self.contextvar = contextvar
37 self.adaptation = adaptation
38
39 def __get__(self, inst, cls=None):
40 if inst is None:
41 return self
42 else:
43 context = getattr(inst, self.contextvar)
44 if self.adaptation is not None:
45 context = self.adaptation(context)
46 return getattr(context, self.name)
47
48 def __set__(self, inst, value):
49 context = getattr(inst, self.contextvar)
50 if self.adaptation is not None:
51 context = self.adaptation(context)
52 setattr(context, self.name, value)
53
54 def __delete__(self, inst):
55 raise NotImplementedError
diff --git a/src/lazr/delegates/_python2.py b/src/lazr/delegates/_python2.py
56deleted file mode 1006440deleted file mode 100644
index 2ce472a..0000000
--- a/src/lazr/delegates/_python2.py
+++ /dev/null
@@ -1,38 +0,0 @@
1# Copyright 2014-2015 Canonical Ltd. All rights reserved.
2#
3# This file is part of lazr.delegates.
4#
5# lazr.delegates is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# lazr.delegates is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12# License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
16
17"""Class decorator definition for Python 2."""
18
19from zope.interface import classImplements
20
21from lazr.delegates._passthrough import Passthrough
22
23
24def delegate_to(*interfaces, **kws):
25 context = kws.pop('context', 'context')
26 if len(kws) > 0:
27 raise TypeError('Too many arguments')
28 if len(interfaces) == 0:
29 raise TypeError('At least one interface is required')
30 def _decorator(cls):
31 missing = object()
32 for interface in interfaces:
33 classImplements(cls, interface)
34 for name in interface:
35 if getattr(cls, name, missing) is missing:
36 setattr(cls, name, Passthrough(name, context))
37 return cls
38 return _decorator
diff --git a/src/lazr/delegates/_python3.py b/src/lazr/delegates/_python3.py
39deleted file mode 1006440deleted file mode 100644
index f9430f5..0000000
--- a/src/lazr/delegates/_python3.py
+++ /dev/null
@@ -1,38 +0,0 @@
1# Copyright 2014-2015 Canonical Ltd. All rights reserved.
2#
3# This file is part of lazr.delegates.
4#
5# lazr.delegates is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# lazr.delegates is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12# License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
16
17"""Class decorator definition for Python 3.
18
19This syntax is not legal in Python 2.
20"""
21
22from zope.interface import classImplements
23
24from lazr.delegates._passthrough import Passthrough
25
26
27def delegate_to(*interfaces, context='context'):
28 if len(interfaces) == 0:
29 raise TypeError('At least one interface is required')
30 def _decorator(cls):
31 missing = object()
32 for interface in interfaces:
33 classImplements(cls, interface)
34 for name in interface:
35 if getattr(cls, name, missing) is missing:
36 setattr(cls, name, Passthrough(name, context))
37 return cls
38 return _decorator
diff --git a/src/lazr/delegates/docs/conf.py b/src/lazr/delegates/docs/conf.py
index fed69d1..adc0c50 100644
--- a/src/lazr/delegates/docs/conf.py
+++ b/src/lazr/delegates/docs/conf.py
@@ -1,5 +1,3 @@
1# -*- coding: utf-8 -*-
2#
3# lazr.delegates documentation build configuration file, created by1# lazr.delegates documentation build configuration file, created by
4# sphinx-quickstart on Mon Jan 7 10:37:37 2013.2# sphinx-quickstart on Mon Jan 7 10:37:37 2013.
5#3#
diff --git a/src/lazr/delegates/tests/test_api.py b/src/lazr/delegates/tests/test_api.py
index 5ecea47..4f6f200 100644
--- a/src/lazr/delegates/tests/test_api.py
+++ b/src/lazr/delegates/tests/test_api.py
@@ -25,11 +25,4 @@ class TestAPI(unittest.TestCase):
25 """Test various corner cases in the API."""25 """Test various corner cases in the API."""
2626
27 def test_no_interfaces(self):27 def test_no_interfaces(self):
28 try:28 self.assertRaises(TypeError, delegate_to)
29 @delegate_to()
30 class SomeClass(object):
31 pass
32 except TypeError:
33 pass
34 else:
35 self.fail('TypeError expected')
diff --git a/src/lazr/delegates/tests/test_docs.py b/src/lazr/delegates/tests/test_docs.py
index 7ca6971..9e19ce4 100644
--- a/src/lazr/delegates/tests/test_docs.py
+++ b/src/lazr/delegates/tests/test_docs.py
@@ -16,9 +16,6 @@
1616
17"""Test harness for doctests."""17"""Test harness for doctests."""
1818
19from __future__ import absolute_import, print_function, unicode_literals
20
21__metaclass__ = type
22__all__ = []19__all__ = []
2320
24import atexit21import atexit
@@ -50,17 +47,11 @@ def load_tests(loader, tests, pattern):
50 )47 )
51 )48 )
52 atexit.register(cleanup_resources)49 atexit.register(cleanup_resources)
53 globs = {
54 "absolute_import": absolute_import,
55 "print_function": print_function,
56 "unicode_literals": unicode_literals,
57 }
58 tests.addTest(50 tests.addTest(
59 doctest.DocFileSuite(51 doctest.DocFileSuite(
60 *doctest_files,52 *doctest_files,
61 module_relative=False,53 module_relative=False,
62 optionflags=DOCTEST_FLAGS,54 optionflags=DOCTEST_FLAGS,
63 globs=globs,
64 encoding="UTF-8"55 encoding="UTF-8"
65 )56 )
66 )57 )
diff --git a/src/lazr/delegates/tests/test_passthrough.py b/src/lazr/delegates/tests/test_passthrough.py
index 009ed73..0905e3b 100644
--- a/src/lazr/delegates/tests/test_passthrough.py
+++ b/src/lazr/delegates/tests/test_passthrough.py
@@ -58,13 +58,13 @@ class TestPassthrough(unittest.TestCase):
58 # provided, should be a zope.interface.Interface subclass (although in58 # provided, should be a zope.interface.Interface subclass (although in
59 # practice any callable will do) to which the instance is adapted59 # practice any callable will do) to which the instance is adapted
60 # before getting/setting the delegated attribute.60 # before getting/setting the delegated attribute.
61 class HasNoFoo(object):61 class HasNoFoo:
62 _foo = 162 _foo = 1
63 no_foo = HasNoFoo()63 no_foo = HasNoFoo()
64 # ... but IHasFooAdapter uses HasNoFoo._foo to provide its own .foo,64 # ... but IHasFooAdapter uses HasNoFoo._foo to provide its own .foo,
65 # so it works like an adapter for HasNoFoo into some interface that65 # so it works like an adapter for HasNoFoo into some interface that
66 # provides a 'foo' attribute.66 # provides a 'foo' attribute.
67 class IHasFooAdapter(object):67 class IHasFooAdapter:
68 def __init__(self, inst):68 def __init__(self, inst):
69 self.inst = inst69 self.inst = inst
70 @property70 @property
@@ -74,7 +74,7 @@ class TestPassthrough(unittest.TestCase):
74 def foo(self, value):74 def foo(self, value):
75 self.inst._foo = value75 self.inst._foo = value
7676
77 class Example(object):77 class Example:
78 context = no_foo78 context = no_foo
7979
80 p = Passthrough('foo', 'context', adaptation=IHasFooAdapter)80 p = Passthrough('foo', 'context', adaptation=IHasFooAdapter)
diff --git a/src/lazr/delegates/tests/test_python2.py b/src/lazr/delegates/tests/test_python2.py
81deleted file mode 10064481deleted file mode 100644
index ad3de7e..0000000
--- a/src/lazr/delegates/tests/test_python2.py
+++ /dev/null
@@ -1,159 +0,0 @@
1# Copyright 2013-2015 Canonical Ltd. All rights reserved.
2#
3# This file is part of lazr.delegates.
4#
5# lazr.delegates is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# lazr.delegates is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12# License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
16
17"""Test the legacy API, which only works in Python 2.
18
19All of these tests are copied almost verbatim from the old README.rst.
20"""
21
22
23from __future__ import absolute_import, print_function, unicode_literals
24
25
26# Don't enable the following or we can't test classic class failures.
27#__metaclass__ = type
28__all__ = [
29 'TestLegacyAPI',
30 ]
31
32
33import sys
34import unittest
35
36from zope.interface import Attribute, Interface, implements, providedBy
37
38from lazr.delegates import delegate_to
39if sys.version_info[0] == 2:
40 from lazr.delegates import delegates
41
42
43class IFoo0(Interface):
44 spoo = Attribute('attribute in IFoo0')
45
46class IFoo(IFoo0):
47 def bar():
48 'some method'
49 baz = Attribute('some attribute')
50
51class BaseFoo0:
52 spoo = 'some spoo'
53
54class BaseFoo(BaseFoo0):
55 def bar(self):
56 return 'bar'
57 baz = 'hi baz!'
58
59class IOther(Interface):
60 another = Attribute('another attribute')
61
62class BaseOtherFoo(BaseFoo):
63 another = 'yes, another'
64
65
66@unittest.skipIf(sys.version_info[0] > 2, "only relevant in Python 2")
67class TestLegacyAPI(unittest.TestCase):
68 def test_basic_usage(self):
69 class SomeClass(object):
70 delegates(IFoo)
71 def __init__(self, context):
72 self.context = context
73
74 f = BaseFoo()
75 s = SomeClass(f)
76 self.assertEqual(s.bar(), 'bar')
77 self.assertEqual(s.baz, 'hi baz!')
78 self.assertEqual(s.spoo, 'some spoo')
79 self.assertTrue(IFoo.providedBy(s))
80
81 def test_keyword_context(self):
82 class SomeOtherClass(object):
83 delegates(IFoo, context='myfoo')
84 def __init__(self, foo):
85 self.myfoo = foo
86 spoo = 'spoo from SomeOtherClass'
87
88 f = BaseFoo()
89 s = SomeOtherClass(f)
90 self.assertEqual(s.bar(), 'bar')
91 self.assertEqual(s.baz, 'hi baz!')
92 self.assertEqual(s.spoo, 'spoo from SomeOtherClass')
93
94 s.baz = 'fish'
95 self.assertEqual(s.baz, 'fish')
96 self.assertEqual(f.baz, 'fish')
97
98 def test_classic_is_error(self):
99 try:
100 class SomeClassicClass:
101 delegates(IFoo)
102 except TypeError:
103 pass
104 else:
105 self.fail('TypeError expected')
106
107 def test_use_outside_class_is_error(self):
108 self.assertRaises(TypeError, delegates, IFoo)
109
110 def test_multiple_interfaces(self):
111 class SomeOtherClass(object):
112 delegates([IFoo, IOther])
113
114 s = SomeOtherClass()
115 s.context = BaseOtherFoo()
116 self.assertEqual(s.another, 'yes, another')
117 self.assertEqual(s.baz, 'hi baz!')
118 self.assertEqual(s.spoo, 'some spoo')
119 self.assertTrue(IFoo.providedBy(s))
120 self.assertTrue(IOther.providedBy(s))
121
122 def test_decorate_existing_object(self):
123 class MoreFoo(BaseFoo, BaseOtherFoo):
124 implements([IFoo, IOther])
125
126 foo = MoreFoo()
127
128 class WithExtraTeapot(object):
129 delegates(providedBy(foo))
130 teapot = 'i am a teapot'
131
132 foo_with_teapot = WithExtraTeapot()
133 foo_with_teapot.context = foo
134
135 self.assertEqual(foo_with_teapot.baz, 'hi baz!')
136 self.assertEqual(foo_with_teapot.another, 'yes, another')
137 self.assertEqual(foo_with_teapot.teapot, 'i am a teapot')
138 self.assertTrue(IFoo.providedBy(foo_with_teapot))
139 self.assertTrue(IOther.providedBy(foo_with_teapot))
140
141
142@unittest.skipIf(sys.version_info[0] > 2, "only relevant in Python 2")
143class TestNewAPI(unittest.TestCase):
144 """Test corner cases in Python 2.
145
146 Most of the new API is tested in the doctest. The implementation of the
147 new API is different between Python 2 and Python 3, so test these corner
148 cases.
149 """
150 def test_type_error(self):
151 # Too many arguments to @delegate_to() raises a TypeError.
152 try:
153 @delegate_to(IFoo0, context='myfoo', other='bogus')
154 class SomeClass(object):
155 pass
156 except TypeError:
157 pass
158 else:
159 self.fail('TypeError expected')
diff --git a/tox.ini b/tox.ini
index 7bdfc27..451caed 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,6 @@
1[tox]1[tox]
2envlist =2envlist =
3 lint3 lint
4 py27
5 py354 py35
6 py365 py36
7 py376 py37
@@ -13,11 +12,9 @@ envlist =
1312
14[testenv]13[testenv]
15deps =14deps =
16 .
17 zope.testrunner15 zope.testrunner
18 coverage
19commands =16commands =
20 coverage run -m zope.testrunner --test-path src --tests-pattern ^tests {posargs}17 zope-testrunner --test-path src --tests-pattern ^tests {posargs}
2118
22[testenv:lint]19[testenv:lint]
23basepython = python3.820basepython = python3.8
@@ -36,22 +33,19 @@ deps =
36 .[docs]33 .[docs]
3734
38[testenv:coverage]35[testenv:coverage]
39description = Run coverage either via `tox` or `tox -e py27,py38,coverage`
40basepython = python336basepython = python3
41skip_install = true
42depends =
43 py27
44 py38
45deps =37deps =
46 coverage38 coverage
39 zope.testrunner
47commands =40commands =
48 coverage combine41 coverage erase
49 coverage report -m --fail-under=9742 coverage run -m zope.testrunner --test-path src --tests-pattern ^tests {posargs}
43 coverage html
44 coverage report -m --fail-under=100
5045
51[coverage:run]46[coverage:run]
52source = lazr.delegates47source = lazr.delegates
53omit = */docs/conf.py48omit = */docs/conf.py
54parallel = true
5549
56[coverage:paths]50[coverage:paths]
57source =51source =

Subscribers

People subscribed via source and target branches

to all changes: