Merge lp:~barry/lazr.delegates/lp1096513 into lp:lazr.delegates
- lp1096513
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 7 |
Proposed branch: | lp:~barry/lazr.delegates/lp1096513 |
Merge into: | lp:lazr.delegates |
Diff against target: |
2350 lines (+1248/-848) 28 files modified
.bzrignore (+2/-0) HACKING.rst (+16/-2) MANIFEST.in (+3/-4) _bootstrap/COPYRIGHT.txt (+0/-9) _bootstrap/LICENSE.txt (+0/-54) _bootstrap/bootstrap.py (+0/-77) buildout.cfg (+0/-31) distribute_setup.py (+546/-0) ez_setup.py (+0/-241) lazr/__init__.py (+12/-8) lazr/delegates/NEWS.rst (+6/-0) lazr/delegates/__init__.py (+15/-6) lazr/delegates/_delegates.py (+9/-37) lazr/delegates/_passthrough.py (+55/-0) lazr/delegates/_python2.py (+38/-0) lazr/delegates/_python3.py (+38/-0) lazr/delegates/docs/fixture.py (+34/-0) lazr/delegates/docs/usage.rst (+136/-0) lazr/delegates/docs/usage_fixture.py (+27/-0) lazr/delegates/tests/__init__.py (+0/-16) lazr/delegates/tests/test_api.py (+35/-0) lazr/delegates/tests/test_passthrough.py (+85/-0) lazr/delegates/tests/test_python2.py (+166/-0) lazr/delegates/version.txt (+1/-1) setup.cfg (+9/-0) setup.py (+15/-19) src/lazr/delegates/README.txt (+0/-292) src/lazr/delegates/tests/test_docs.py (+0/-51) |
To merge this branch: | bzr merge lp:~barry/lazr.delegates/lp1096513 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella | Approve | ||
Curtis Hovey | Pending | ||
Gary Poster | Pending | ||
Review via email: mp+142052@code.launchpad.net |
Commit message
Description of the change
See bug for API changes and more details.
- 19. By Barry Warsaw
-
Update the MANIFEST.
Barry Warsaw (barry) wrote : | # |
Gavin Panella (allenap) wrote : | # |
Looks good. Some minor points. I did a fairly quick once-over
sanity-check rather than an in-depth review, so you might want to ask
someone else to take a look if you think it warrants it.
[1]
+def delegate_
+ context = kws.pop('context', 'context')
+ if len(kws) > 0:
+ raise TypeError('Too many arguments')
+ def _decorator(cls):
+ if len(interfaces) == 0:
+ raise TypeError('At least one interface is required')
I think it would make more sense to check interfaces at the same time
as kws, i.e. in delegate_to, rather than _decorator.
In both _python2.py and python3.py.
[2]
--- lazr/delegates/
+++ lazr/delegates/
@@ -0,0 +1,34 @@
+# Copyright 2009-2013 Canonical Ltd. All rights reserved.
+#
+# This file is part of lazr.smtptest
s/smtptest/
In lazr/delegates/
[3]
+The ``@delegation`` decorator makes a class implement zero or more interfaces
s/delegation/
[4]
=== added file 'lazr/delegates
...
+__all__ = [
+ 'globs',
+ ]
+
+
+from lazr.delegates.
What does this module do?
[5]
+class TestAPI(
+ """Test various corner cases in the API."""
+
+ def test_no_
+ try:
+ @delegate_to()
+ class SomeClass(object):
+ pass
+ except TypeError:
+ pass
+ else:
+ raise AssertionError(
assertRaises would be more concise, but I can see that there's some
clarity here from using decorator syntax. Use self.fail() instead of
`raise AssertionError`?
- 20. By Barry Warsaw
-
Fixes based on Gavin Panella's review:
* delgate_to(): Check the length of *interfaces in the wrapper instead of in
the embedded decorator.
* Fix typos.
* Use self.fail() instead of raising an AssertionError.
Barry Warsaw (barry) wrote : | # |
On Jan 08, 2013, at 12:28 PM, Gavin Panella wrote:
>> BTW, what did you think of @delegate_to as the choice of name?
>
>It's good, and I can't think of anything better.
>
>...
>> Would you take a quick look at the lazr.smtptest mp?
>> https:/
>
>I was too late :)
Yeah, it's futile to try to beat Curtis. :)
Thanks!
Gavin Panella (allenap) wrote : | # |
> BTW, what did you think of @delegate_to as the choice of name?
It's good, and I can't think of anything better.
...
> Would you take a quick look at the lazr.smtptest mp?
> https:/
I was too late :)
Preview Diff
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2009-03-24 18:52:52 +0000 | |||
3 | +++ .bzrignore 2013-01-07 15:18:26 +0000 | |||
4 | @@ -9,3 +9,5 @@ | |||
5 | 9 | build | 9 | build |
6 | 10 | *.egg | 10 | *.egg |
7 | 11 | dist | 11 | dist |
8 | 12 | __pycache__ | ||
9 | 13 | .coverage | ||
10 | 12 | 14 | ||
11 | === renamed file 'HACKING.txt' => 'HACKING.rst' | |||
12 | --- HACKING.txt 2009-03-24 19:51:29 +0000 | |||
13 | +++ HACKING.rst 2013-01-07 15:18:26 +0000 | |||
14 | @@ -13,8 +13,6 @@ | |||
15 | 13 | You should have received a copy of the GNU Lesser General Public License | 13 | You should have received a copy of the GNU Lesser General Public License |
16 | 14 | along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | 14 | along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. |
17 | 15 | 15 | ||
18 | 16 | This project uses zc.buildout for development. | ||
19 | 17 | |||
20 | 18 | ============ | 16 | ============ |
21 | 19 | Introduction | 17 | Introduction |
22 | 20 | ============ | 18 | ============ |
23 | @@ -39,3 +37,19 @@ | |||
24 | 39 | or send a message to: | 37 | or send a message to: |
25 | 40 | 38 | ||
26 | 41 | lazr-developers@lists.launchpad.net | 39 | lazr-developers@lists.launchpad.net |
27 | 40 | |||
28 | 41 | |||
29 | 42 | Running the tests | ||
30 | 43 | ================= | ||
31 | 44 | |||
32 | 45 | The tests suite requires nose_ and is compatible with both Python 2 and | ||
33 | 46 | Python 3. To run the full test suite:: | ||
34 | 47 | |||
35 | 48 | $ python setup.py nosetests | ||
36 | 49 | |||
37 | 50 | Where ``python`` is the Python executable to use for the tests. E.g. this | ||
38 | 51 | might be ``python3`` for Python 3, or the Python executable from a | ||
39 | 52 | virtualenv_. | ||
40 | 53 | |||
41 | 54 | .. _nose: https://nose.readthedocs.org/en/latest/ | ||
42 | 55 | .. _virtualenv: http://www.virtualenv.org/en/latest/ | ||
43 | 42 | 56 | ||
44 | === modified file 'MANIFEST.in' | |||
45 | --- MANIFEST.in 2009-08-29 17:51:36 +0000 | |||
46 | +++ MANIFEST.in 2013-01-07 15:18:26 +0000 | |||
47 | @@ -1,4 +1,3 @@ | |||
52 | 1 | include ez_setup.py | 1 | include distribute_setup.py |
53 | 2 | recursive-include src *.txt *.pt *.zcml *.xsd | 2 | recursive-include lazr *.txt *.pt *.zcml *.xsd |
54 | 3 | exclude MANIFEST.in buildout.cfg bootstrap.py .bzrignore | 3 | exclude MANIFEST.in .bzrignore |
51 | 4 | prune _bootstrap | ||
55 | 5 | 4 | ||
56 | === renamed file 'README.txt' => 'README.rst' | |||
57 | === removed directory '_bootstrap' | |||
58 | === removed file '_bootstrap/COPYRIGHT.txt' | |||
59 | --- _bootstrap/COPYRIGHT.txt 2009-03-24 18:52:52 +0000 | |||
60 | +++ _bootstrap/COPYRIGHT.txt 1970-01-01 00:00:00 +0000 | |||
61 | @@ -1,9 +0,0 @@ | |||
62 | 1 | Copyright (c) 2004-2009 Zope Corporation and Contributors. | ||
63 | 2 | All Rights Reserved. | ||
64 | 3 | |||
65 | 4 | This software is subject to the provisions of the Zope Public License, | ||
66 | 5 | Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
67 | 6 | THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
68 | 7 | WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
69 | 8 | WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
70 | 9 | FOR A PARTICULAR PURPOSE. | ||
71 | 10 | 0 | ||
72 | === removed file '_bootstrap/LICENSE.txt' | |||
73 | --- _bootstrap/LICENSE.txt 2009-03-24 18:52:52 +0000 | |||
74 | +++ _bootstrap/LICENSE.txt 1970-01-01 00:00:00 +0000 | |||
75 | @@ -1,54 +0,0 @@ | |||
76 | 1 | Zope Public License (ZPL) Version 2.1 | ||
77 | 2 | ------------------------------------- | ||
78 | 3 | |||
79 | 4 | A copyright notice accompanies this license document that | ||
80 | 5 | identifies the copyright holders. | ||
81 | 6 | |||
82 | 7 | This license has been certified as open source. It has also | ||
83 | 8 | been designated as GPL compatible by the Free Software | ||
84 | 9 | Foundation (FSF). | ||
85 | 10 | |||
86 | 11 | Redistribution and use in source and binary forms, with or | ||
87 | 12 | without modification, are permitted provided that the | ||
88 | 13 | following conditions are met: | ||
89 | 14 | |||
90 | 15 | 1. Redistributions in source code must retain the | ||
91 | 16 | accompanying copyright notice, this list of conditions, | ||
92 | 17 | and the following disclaimer. | ||
93 | 18 | |||
94 | 19 | 2. Redistributions in binary form must reproduce the accompanying | ||
95 | 20 | copyright notice, this list of conditions, and the | ||
96 | 21 | following disclaimer in the documentation and/or other | ||
97 | 22 | materials provided with the distribution. | ||
98 | 23 | |||
99 | 24 | 3. Names of the copyright holders must not be used to | ||
100 | 25 | endorse or promote products derived from this software | ||
101 | 26 | without prior written permission from the copyright | ||
102 | 27 | holders. | ||
103 | 28 | |||
104 | 29 | 4. The right to distribute this software or to use it for | ||
105 | 30 | any purpose does not give you the right to use | ||
106 | 31 | Servicemarks (sm) or Trademarks (tm) of the copyright | ||
107 | 32 | holders. Use of them is covered by separate agreement | ||
108 | 33 | with the copyright holders. | ||
109 | 34 | |||
110 | 35 | 5. If any files are modified, you must cause the modified | ||
111 | 36 | files to carry prominent notices stating that you changed | ||
112 | 37 | the files and the date of any change. | ||
113 | 38 | |||
114 | 39 | Disclaimer | ||
115 | 40 | |||
116 | 41 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' | ||
117 | 42 | AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT | ||
118 | 43 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY | ||
119 | 44 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN | ||
120 | 45 | NO EVENT SHALL THE COPYRIGHT HOLDERS BE | ||
121 | 46 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | ||
122 | 47 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
123 | 48 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
124 | 49 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
125 | 50 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
126 | 51 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||
127 | 52 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
128 | 53 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | ||
129 | 54 | DAMAGE. | ||
130 | 55 | \ No newline at end of file | 0 | \ No newline at end of file |
131 | 56 | 1 | ||
132 | === removed file '_bootstrap/bootstrap.py' | |||
133 | --- _bootstrap/bootstrap.py 2009-03-24 18:52:52 +0000 | |||
134 | +++ _bootstrap/bootstrap.py 1970-01-01 00:00:00 +0000 | |||
135 | @@ -1,77 +0,0 @@ | |||
136 | 1 | ############################################################################## | ||
137 | 2 | # | ||
138 | 3 | # Copyright (c) 2006 Zope Corporation and Contributors. | ||
139 | 4 | # All Rights Reserved. | ||
140 | 5 | # | ||
141 | 6 | # This software is subject to the provisions of the Zope Public License, | ||
142 | 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
143 | 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
144 | 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
145 | 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
146 | 11 | # FOR A PARTICULAR PURPOSE. | ||
147 | 12 | # | ||
148 | 13 | ############################################################################## | ||
149 | 14 | """Bootstrap a buildout-based project | ||
150 | 15 | |||
151 | 16 | Simply run this script in a directory containing a buildout.cfg. | ||
152 | 17 | The script accepts buildout command-line options, so you can | ||
153 | 18 | use the -c option to specify an alternate configuration file. | ||
154 | 19 | |||
155 | 20 | $Id$ | ||
156 | 21 | """ | ||
157 | 22 | |||
158 | 23 | import os, shutil, sys, tempfile, urllib2 | ||
159 | 24 | |||
160 | 25 | tmpeggs = tempfile.mkdtemp() | ||
161 | 26 | |||
162 | 27 | is_jython = sys.platform.startswith('java') | ||
163 | 28 | |||
164 | 29 | try: | ||
165 | 30 | import pkg_resources | ||
166 | 31 | except ImportError: | ||
167 | 32 | ez = {} | ||
168 | 33 | exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' | ||
169 | 34 | ).read() in ez | ||
170 | 35 | ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) | ||
171 | 36 | |||
172 | 37 | import pkg_resources | ||
173 | 38 | |||
174 | 39 | if sys.platform == 'win32': | ||
175 | 40 | def quote(c): | ||
176 | 41 | if ' ' in c: | ||
177 | 42 | return '"%s"' % c # work around spawn lamosity on windows | ||
178 | 43 | else: | ||
179 | 44 | return c | ||
180 | 45 | else: | ||
181 | 46 | def quote (c): | ||
182 | 47 | return c | ||
183 | 48 | |||
184 | 49 | cmd = 'from setuptools.command.easy_install import main; main()' | ||
185 | 50 | ws = pkg_resources.working_set | ||
186 | 51 | |||
187 | 52 | if is_jython: | ||
188 | 53 | import subprocess | ||
189 | 54 | |||
190 | 55 | assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', | ||
191 | 56 | quote(tmpeggs), 'zc.buildout'], | ||
192 | 57 | env=dict(os.environ, | ||
193 | 58 | PYTHONPATH= | ||
194 | 59 | ws.find(pkg_resources.Requirement.parse('setuptools')).location | ||
195 | 60 | ), | ||
196 | 61 | ).wait() == 0 | ||
197 | 62 | |||
198 | 63 | else: | ||
199 | 64 | assert os.spawnle( | ||
200 | 65 | os.P_WAIT, sys.executable, quote (sys.executable), | ||
201 | 66 | '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout', | ||
202 | 67 | dict(os.environ, | ||
203 | 68 | PYTHONPATH= | ||
204 | 69 | ws.find(pkg_resources.Requirement.parse('setuptools')).location | ||
205 | 70 | ), | ||
206 | 71 | ) == 0 | ||
207 | 72 | |||
208 | 73 | ws.add_entry(tmpeggs) | ||
209 | 74 | ws.require('zc.buildout') | ||
210 | 75 | import zc.buildout.buildout | ||
211 | 76 | zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) | ||
212 | 77 | shutil.rmtree(tmpeggs) | ||
213 | 78 | 0 | ||
214 | === removed symlink 'bootstrap.py' | |||
215 | === target was u'_bootstrap/bootstrap.py' | |||
216 | === removed file 'buildout.cfg' | |||
217 | --- buildout.cfg 2009-03-24 19:51:29 +0000 | |||
218 | +++ buildout.cfg 1970-01-01 00:00:00 +0000 | |||
219 | @@ -1,31 +0,0 @@ | |||
220 | 1 | [buildout] | ||
221 | 2 | parts = | ||
222 | 3 | interpreter | ||
223 | 4 | test | ||
224 | 5 | docs | ||
225 | 6 | tags | ||
226 | 7 | unzip = true | ||
227 | 8 | |||
228 | 9 | develop = . | ||
229 | 10 | |||
230 | 11 | [test] | ||
231 | 12 | recipe = zc.recipe.testrunner | ||
232 | 13 | eggs = lazr.delegates | ||
233 | 14 | defaults = '--tests-pattern ^tests --exit-with-status --suite-name additional_tests'.split() | ||
234 | 15 | |||
235 | 16 | [docs] | ||
236 | 17 | recipe = z3c.recipe.sphinxdoc | ||
237 | 18 | eggs = lazr.delegates [docs] | ||
238 | 19 | index-doc = README | ||
239 | 20 | default.css = | ||
240 | 21 | layout.html = | ||
241 | 22 | |||
242 | 23 | [interpreter] | ||
243 | 24 | recipe = zc.recipe.egg | ||
244 | 25 | interpreter = py | ||
245 | 26 | eggs = lazr.delegates | ||
246 | 27 | docutils | ||
247 | 28 | |||
248 | 29 | [tags] | ||
249 | 30 | recipe = z3c.recipe.tag:tags | ||
250 | 31 | eggs = lazr.delegates | ||
251 | 32 | 0 | ||
252 | === added file 'distribute_setup.py' | |||
253 | --- distribute_setup.py 1970-01-01 00:00:00 +0000 | |||
254 | +++ distribute_setup.py 2013-01-07 15:18:26 +0000 | |||
255 | @@ -0,0 +1,546 @@ | |||
256 | 1 | #!python | ||
257 | 2 | """Bootstrap distribute installation | ||
258 | 3 | |||
259 | 4 | If you want to use setuptools in your package's setup.py, just include this | ||
260 | 5 | file in the same directory with it, and add this to the top of your setup.py:: | ||
261 | 6 | |||
262 | 7 | from distribute_setup import use_setuptools | ||
263 | 8 | use_setuptools() | ||
264 | 9 | |||
265 | 10 | If you want to require a specific version of setuptools, set a download | ||
266 | 11 | mirror, or use an alternate download directory, you can do so by supplying | ||
267 | 12 | the appropriate options to ``use_setuptools()``. | ||
268 | 13 | |||
269 | 14 | This file can also be run as a script to install or upgrade setuptools. | ||
270 | 15 | """ | ||
271 | 16 | import os | ||
272 | 17 | import shutil | ||
273 | 18 | import sys | ||
274 | 19 | import time | ||
275 | 20 | import fnmatch | ||
276 | 21 | import tempfile | ||
277 | 22 | import tarfile | ||
278 | 23 | import optparse | ||
279 | 24 | |||
280 | 25 | from distutils import log | ||
281 | 26 | |||
282 | 27 | try: | ||
283 | 28 | from site import USER_SITE | ||
284 | 29 | except ImportError: | ||
285 | 30 | USER_SITE = None | ||
286 | 31 | |||
287 | 32 | try: | ||
288 | 33 | import subprocess | ||
289 | 34 | |||
290 | 35 | def _python_cmd(*args): | ||
291 | 36 | args = (sys.executable,) + args | ||
292 | 37 | return subprocess.call(args) == 0 | ||
293 | 38 | |||
294 | 39 | except ImportError: | ||
295 | 40 | # will be used for python 2.3 | ||
296 | 41 | def _python_cmd(*args): | ||
297 | 42 | args = (sys.executable,) + args | ||
298 | 43 | # quoting arguments if windows | ||
299 | 44 | if sys.platform == 'win32': | ||
300 | 45 | def quote(arg): | ||
301 | 46 | if ' ' in arg: | ||
302 | 47 | return '"%s"' % arg | ||
303 | 48 | return arg | ||
304 | 49 | args = [quote(arg) for arg in args] | ||
305 | 50 | return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 | ||
306 | 51 | |||
307 | 52 | DEFAULT_VERSION = "0.6.34" | ||
308 | 53 | DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" | ||
309 | 54 | SETUPTOOLS_FAKED_VERSION = "0.6c11" | ||
310 | 55 | |||
311 | 56 | SETUPTOOLS_PKG_INFO = """\ | ||
312 | 57 | Metadata-Version: 1.0 | ||
313 | 58 | Name: setuptools | ||
314 | 59 | Version: %s | ||
315 | 60 | Summary: xxxx | ||
316 | 61 | Home-page: xxx | ||
317 | 62 | Author: xxx | ||
318 | 63 | Author-email: xxx | ||
319 | 64 | License: xxx | ||
320 | 65 | Description: xxx | ||
321 | 66 | """ % SETUPTOOLS_FAKED_VERSION | ||
322 | 67 | |||
323 | 68 | |||
324 | 69 | def _install(tarball, install_args=()): | ||
325 | 70 | # extracting the tarball | ||
326 | 71 | tmpdir = tempfile.mkdtemp() | ||
327 | 72 | log.warn('Extracting in %s', tmpdir) | ||
328 | 73 | old_wd = os.getcwd() | ||
329 | 74 | try: | ||
330 | 75 | os.chdir(tmpdir) | ||
331 | 76 | tar = tarfile.open(tarball) | ||
332 | 77 | _extractall(tar) | ||
333 | 78 | tar.close() | ||
334 | 79 | |||
335 | 80 | # going in the directory | ||
336 | 81 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) | ||
337 | 82 | os.chdir(subdir) | ||
338 | 83 | log.warn('Now working in %s', subdir) | ||
339 | 84 | |||
340 | 85 | # installing | ||
341 | 86 | log.warn('Installing Distribute') | ||
342 | 87 | if not _python_cmd('setup.py', 'install', *install_args): | ||
343 | 88 | log.warn('Something went wrong during the installation.') | ||
344 | 89 | log.warn('See the error message above.') | ||
345 | 90 | # exitcode will be 2 | ||
346 | 91 | return 2 | ||
347 | 92 | finally: | ||
348 | 93 | os.chdir(old_wd) | ||
349 | 94 | shutil.rmtree(tmpdir) | ||
350 | 95 | |||
351 | 96 | |||
352 | 97 | def _build_egg(egg, tarball, to_dir): | ||
353 | 98 | # extracting the tarball | ||
354 | 99 | tmpdir = tempfile.mkdtemp() | ||
355 | 100 | log.warn('Extracting in %s', tmpdir) | ||
356 | 101 | old_wd = os.getcwd() | ||
357 | 102 | try: | ||
358 | 103 | os.chdir(tmpdir) | ||
359 | 104 | tar = tarfile.open(tarball) | ||
360 | 105 | _extractall(tar) | ||
361 | 106 | tar.close() | ||
362 | 107 | |||
363 | 108 | # going in the directory | ||
364 | 109 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) | ||
365 | 110 | os.chdir(subdir) | ||
366 | 111 | log.warn('Now working in %s', subdir) | ||
367 | 112 | |||
368 | 113 | # building an egg | ||
369 | 114 | log.warn('Building a Distribute egg in %s', to_dir) | ||
370 | 115 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) | ||
371 | 116 | |||
372 | 117 | finally: | ||
373 | 118 | os.chdir(old_wd) | ||
374 | 119 | shutil.rmtree(tmpdir) | ||
375 | 120 | # returning the result | ||
376 | 121 | log.warn(egg) | ||
377 | 122 | if not os.path.exists(egg): | ||
378 | 123 | raise IOError('Could not build the egg.') | ||
379 | 124 | |||
380 | 125 | |||
381 | 126 | def _do_download(version, download_base, to_dir, download_delay): | ||
382 | 127 | egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' | ||
383 | 128 | % (version, sys.version_info[0], sys.version_info[1])) | ||
384 | 129 | if not os.path.exists(egg): | ||
385 | 130 | tarball = download_setuptools(version, download_base, | ||
386 | 131 | to_dir, download_delay) | ||
387 | 132 | _build_egg(egg, tarball, to_dir) | ||
388 | 133 | sys.path.insert(0, egg) | ||
389 | 134 | import setuptools | ||
390 | 135 | setuptools.bootstrap_install_from = egg | ||
391 | 136 | |||
392 | 137 | |||
393 | 138 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | ||
394 | 139 | to_dir=os.curdir, download_delay=15, no_fake=True): | ||
395 | 140 | # making sure we use the absolute path | ||
396 | 141 | to_dir = os.path.abspath(to_dir) | ||
397 | 142 | was_imported = 'pkg_resources' in sys.modules or \ | ||
398 | 143 | 'setuptools' in sys.modules | ||
399 | 144 | try: | ||
400 | 145 | try: | ||
401 | 146 | import pkg_resources | ||
402 | 147 | if not hasattr(pkg_resources, '_distribute'): | ||
403 | 148 | if not no_fake: | ||
404 | 149 | _fake_setuptools() | ||
405 | 150 | raise ImportError | ||
406 | 151 | except ImportError: | ||
407 | 152 | return _do_download(version, download_base, to_dir, download_delay) | ||
408 | 153 | try: | ||
409 | 154 | pkg_resources.require("distribute>=" + version) | ||
410 | 155 | return | ||
411 | 156 | except pkg_resources.VersionConflict: | ||
412 | 157 | e = sys.exc_info()[1] | ||
413 | 158 | if was_imported: | ||
414 | 159 | sys.stderr.write( | ||
415 | 160 | "The required version of distribute (>=%s) is not available,\n" | ||
416 | 161 | "and can't be installed while this script is running. Please\n" | ||
417 | 162 | "install a more recent version first, using\n" | ||
418 | 163 | "'easy_install -U distribute'." | ||
419 | 164 | "\n\n(Currently using %r)\n" % (version, e.args[0])) | ||
420 | 165 | sys.exit(2) | ||
421 | 166 | else: | ||
422 | 167 | del pkg_resources, sys.modules['pkg_resources'] # reload ok | ||
423 | 168 | return _do_download(version, download_base, to_dir, | ||
424 | 169 | download_delay) | ||
425 | 170 | except pkg_resources.DistributionNotFound: | ||
426 | 171 | return _do_download(version, download_base, to_dir, | ||
427 | 172 | download_delay) | ||
428 | 173 | finally: | ||
429 | 174 | if not no_fake: | ||
430 | 175 | _create_fake_setuptools_pkg_info(to_dir) | ||
431 | 176 | |||
432 | 177 | |||
433 | 178 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | ||
434 | 179 | to_dir=os.curdir, delay=15): | ||
435 | 180 | """Download distribute from a specified location and return its filename | ||
436 | 181 | |||
437 | 182 | `version` should be a valid distribute version number that is available | ||
438 | 183 | as an egg for download under the `download_base` URL (which should end | ||
439 | 184 | with a '/'). `to_dir` is the directory where the egg will be downloaded. | ||
440 | 185 | `delay` is the number of seconds to pause before an actual download | ||
441 | 186 | attempt. | ||
442 | 187 | """ | ||
443 | 188 | # making sure we use the absolute path | ||
444 | 189 | to_dir = os.path.abspath(to_dir) | ||
445 | 190 | try: | ||
446 | 191 | from urllib.request import urlopen | ||
447 | 192 | except ImportError: | ||
448 | 193 | from urllib2 import urlopen | ||
449 | 194 | tgz_name = "distribute-%s.tar.gz" % version | ||
450 | 195 | url = download_base + tgz_name | ||
451 | 196 | saveto = os.path.join(to_dir, tgz_name) | ||
452 | 197 | src = dst = None | ||
453 | 198 | if not os.path.exists(saveto): # Avoid repeated downloads | ||
454 | 199 | try: | ||
455 | 200 | log.warn("Downloading %s", url) | ||
456 | 201 | src = urlopen(url) | ||
457 | 202 | # Read/write all in one block, so we don't create a corrupt file | ||
458 | 203 | # if the download is interrupted. | ||
459 | 204 | data = src.read() | ||
460 | 205 | dst = open(saveto, "wb") | ||
461 | 206 | dst.write(data) | ||
462 | 207 | finally: | ||
463 | 208 | if src: | ||
464 | 209 | src.close() | ||
465 | 210 | if dst: | ||
466 | 211 | dst.close() | ||
467 | 212 | return os.path.realpath(saveto) | ||
468 | 213 | |||
469 | 214 | |||
470 | 215 | def _no_sandbox(function): | ||
471 | 216 | def __no_sandbox(*args, **kw): | ||
472 | 217 | try: | ||
473 | 218 | from setuptools.sandbox import DirectorySandbox | ||
474 | 219 | if not hasattr(DirectorySandbox, '_old'): | ||
475 | 220 | def violation(*args): | ||
476 | 221 | pass | ||
477 | 222 | DirectorySandbox._old = DirectorySandbox._violation | ||
478 | 223 | DirectorySandbox._violation = violation | ||
479 | 224 | patched = True | ||
480 | 225 | else: | ||
481 | 226 | patched = False | ||
482 | 227 | except ImportError: | ||
483 | 228 | patched = False | ||
484 | 229 | |||
485 | 230 | try: | ||
486 | 231 | return function(*args, **kw) | ||
487 | 232 | finally: | ||
488 | 233 | if patched: | ||
489 | 234 | DirectorySandbox._violation = DirectorySandbox._old | ||
490 | 235 | del DirectorySandbox._old | ||
491 | 236 | |||
492 | 237 | return __no_sandbox | ||
493 | 238 | |||
494 | 239 | |||
495 | 240 | def _patch_file(path, content): | ||
496 | 241 | """Will backup the file then patch it""" | ||
497 | 242 | f = open(path) | ||
498 | 243 | existing_content = f.read() | ||
499 | 244 | f.close() | ||
500 | 245 | if existing_content == content: | ||
501 | 246 | # already patched | ||
502 | 247 | log.warn('Already patched.') | ||
503 | 248 | return False | ||
504 | 249 | log.warn('Patching...') | ||
505 | 250 | _rename_path(path) | ||
506 | 251 | f = open(path, 'w') | ||
507 | 252 | try: | ||
508 | 253 | f.write(content) | ||
509 | 254 | finally: | ||
510 | 255 | f.close() | ||
511 | 256 | return True | ||
512 | 257 | |||
513 | 258 | _patch_file = _no_sandbox(_patch_file) | ||
514 | 259 | |||
515 | 260 | |||
516 | 261 | def _same_content(path, content): | ||
517 | 262 | f = open(path) | ||
518 | 263 | existing_content = f.read() | ||
519 | 264 | f.close() | ||
520 | 265 | return existing_content == content | ||
521 | 266 | |||
522 | 267 | |||
523 | 268 | def _rename_path(path): | ||
524 | 269 | new_name = path + '.OLD.%s' % time.time() | ||
525 | 270 | log.warn('Renaming %s to %s', path, new_name) | ||
526 | 271 | os.rename(path, new_name) | ||
527 | 272 | return new_name | ||
528 | 273 | |||
529 | 274 | |||
530 | 275 | def _remove_flat_installation(placeholder): | ||
531 | 276 | if not os.path.isdir(placeholder): | ||
532 | 277 | log.warn('Unkown installation at %s', placeholder) | ||
533 | 278 | return False | ||
534 | 279 | found = False | ||
535 | 280 | for file in os.listdir(placeholder): | ||
536 | 281 | if fnmatch.fnmatch(file, 'setuptools*.egg-info'): | ||
537 | 282 | found = True | ||
538 | 283 | break | ||
539 | 284 | if not found: | ||
540 | 285 | log.warn('Could not locate setuptools*.egg-info') | ||
541 | 286 | return | ||
542 | 287 | |||
543 | 288 | log.warn('Moving elements out of the way...') | ||
544 | 289 | pkg_info = os.path.join(placeholder, file) | ||
545 | 290 | if os.path.isdir(pkg_info): | ||
546 | 291 | patched = _patch_egg_dir(pkg_info) | ||
547 | 292 | else: | ||
548 | 293 | patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) | ||
549 | 294 | |||
550 | 295 | if not patched: | ||
551 | 296 | log.warn('%s already patched.', pkg_info) | ||
552 | 297 | return False | ||
553 | 298 | # now let's move the files out of the way | ||
554 | 299 | for element in ('setuptools', 'pkg_resources.py', 'site.py'): | ||
555 | 300 | element = os.path.join(placeholder, element) | ||
556 | 301 | if os.path.exists(element): | ||
557 | 302 | _rename_path(element) | ||
558 | 303 | else: | ||
559 | 304 | log.warn('Could not find the %s element of the ' | ||
560 | 305 | 'Setuptools distribution', element) | ||
561 | 306 | return True | ||
562 | 307 | |||
563 | 308 | _remove_flat_installation = _no_sandbox(_remove_flat_installation) | ||
564 | 309 | |||
565 | 310 | |||
566 | 311 | def _after_install(dist): | ||
567 | 312 | log.warn('After install bootstrap.') | ||
568 | 313 | placeholder = dist.get_command_obj('install').install_purelib | ||
569 | 314 | _create_fake_setuptools_pkg_info(placeholder) | ||
570 | 315 | |||
571 | 316 | |||
572 | 317 | def _create_fake_setuptools_pkg_info(placeholder): | ||
573 | 318 | if not placeholder or not os.path.exists(placeholder): | ||
574 | 319 | log.warn('Could not find the install location') | ||
575 | 320 | return | ||
576 | 321 | pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) | ||
577 | 322 | setuptools_file = 'setuptools-%s-py%s.egg-info' % \ | ||
578 | 323 | (SETUPTOOLS_FAKED_VERSION, pyver) | ||
579 | 324 | pkg_info = os.path.join(placeholder, setuptools_file) | ||
580 | 325 | if os.path.exists(pkg_info): | ||
581 | 326 | log.warn('%s already exists', pkg_info) | ||
582 | 327 | return | ||
583 | 328 | |||
584 | 329 | log.warn('Creating %s', pkg_info) | ||
585 | 330 | try: | ||
586 | 331 | f = open(pkg_info, 'w') | ||
587 | 332 | except EnvironmentError: | ||
588 | 333 | log.warn("Don't have permissions to write %s, skipping", pkg_info) | ||
589 | 334 | return | ||
590 | 335 | try: | ||
591 | 336 | f.write(SETUPTOOLS_PKG_INFO) | ||
592 | 337 | finally: | ||
593 | 338 | f.close() | ||
594 | 339 | |||
595 | 340 | pth_file = os.path.join(placeholder, 'setuptools.pth') | ||
596 | 341 | log.warn('Creating %s', pth_file) | ||
597 | 342 | f = open(pth_file, 'w') | ||
598 | 343 | try: | ||
599 | 344 | f.write(os.path.join(os.curdir, setuptools_file)) | ||
600 | 345 | finally: | ||
601 | 346 | f.close() | ||
602 | 347 | |||
603 | 348 | _create_fake_setuptools_pkg_info = _no_sandbox( | ||
604 | 349 | _create_fake_setuptools_pkg_info | ||
605 | 350 | ) | ||
606 | 351 | |||
607 | 352 | |||
608 | 353 | def _patch_egg_dir(path): | ||
609 | 354 | # let's check if it's already patched | ||
610 | 355 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') | ||
611 | 356 | if os.path.exists(pkg_info): | ||
612 | 357 | if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): | ||
613 | 358 | log.warn('%s already patched.', pkg_info) | ||
614 | 359 | return False | ||
615 | 360 | _rename_path(path) | ||
616 | 361 | os.mkdir(path) | ||
617 | 362 | os.mkdir(os.path.join(path, 'EGG-INFO')) | ||
618 | 363 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') | ||
619 | 364 | f = open(pkg_info, 'w') | ||
620 | 365 | try: | ||
621 | 366 | f.write(SETUPTOOLS_PKG_INFO) | ||
622 | 367 | finally: | ||
623 | 368 | f.close() | ||
624 | 369 | return True | ||
625 | 370 | |||
626 | 371 | _patch_egg_dir = _no_sandbox(_patch_egg_dir) | ||
627 | 372 | |||
628 | 373 | |||
629 | 374 | def _before_install(): | ||
630 | 375 | log.warn('Before install bootstrap.') | ||
631 | 376 | _fake_setuptools() | ||
632 | 377 | |||
633 | 378 | |||
634 | 379 | def _under_prefix(location): | ||
635 | 380 | if 'install' not in sys.argv: | ||
636 | 381 | return True | ||
637 | 382 | args = sys.argv[sys.argv.index('install') + 1:] | ||
638 | 383 | for index, arg in enumerate(args): | ||
639 | 384 | for option in ('--root', '--prefix'): | ||
640 | 385 | if arg.startswith('%s=' % option): | ||
641 | 386 | top_dir = arg.split('root=')[-1] | ||
642 | 387 | return location.startswith(top_dir) | ||
643 | 388 | elif arg == option: | ||
644 | 389 | if len(args) > index: | ||
645 | 390 | top_dir = args[index + 1] | ||
646 | 391 | return location.startswith(top_dir) | ||
647 | 392 | if arg == '--user' and USER_SITE is not None: | ||
648 | 393 | return location.startswith(USER_SITE) | ||
649 | 394 | return True | ||
650 | 395 | |||
651 | 396 | |||
652 | 397 | def _fake_setuptools(): | ||
653 | 398 | log.warn('Scanning installed packages') | ||
654 | 399 | try: | ||
655 | 400 | import pkg_resources | ||
656 | 401 | except ImportError: | ||
657 | 402 | # we're cool | ||
658 | 403 | log.warn('Setuptools or Distribute does not seem to be installed.') | ||
659 | 404 | return | ||
660 | 405 | ws = pkg_resources.working_set | ||
661 | 406 | try: | ||
662 | 407 | setuptools_dist = ws.find( | ||
663 | 408 | pkg_resources.Requirement.parse('setuptools', replacement=False) | ||
664 | 409 | ) | ||
665 | 410 | except TypeError: | ||
666 | 411 | # old distribute API | ||
667 | 412 | setuptools_dist = ws.find( | ||
668 | 413 | pkg_resources.Requirement.parse('setuptools') | ||
669 | 414 | ) | ||
670 | 415 | |||
671 | 416 | if setuptools_dist is None: | ||
672 | 417 | log.warn('No setuptools distribution found') | ||
673 | 418 | return | ||
674 | 419 | # detecting if it was already faked | ||
675 | 420 | setuptools_location = setuptools_dist.location | ||
676 | 421 | log.warn('Setuptools installation detected at %s', setuptools_location) | ||
677 | 422 | |||
678 | 423 | # if --root or --preix was provided, and if | ||
679 | 424 | # setuptools is not located in them, we don't patch it | ||
680 | 425 | if not _under_prefix(setuptools_location): | ||
681 | 426 | log.warn('Not patching, --root or --prefix is installing Distribute' | ||
682 | 427 | ' in another location') | ||
683 | 428 | return | ||
684 | 429 | |||
685 | 430 | # let's see if its an egg | ||
686 | 431 | if not setuptools_location.endswith('.egg'): | ||
687 | 432 | log.warn('Non-egg installation') | ||
688 | 433 | res = _remove_flat_installation(setuptools_location) | ||
689 | 434 | if not res: | ||
690 | 435 | return | ||
691 | 436 | else: | ||
692 | 437 | log.warn('Egg installation') | ||
693 | 438 | pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') | ||
694 | 439 | if (os.path.exists(pkg_info) and | ||
695 | 440 | _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): | ||
696 | 441 | log.warn('Already patched.') | ||
697 | 442 | return | ||
698 | 443 | log.warn('Patching...') | ||
699 | 444 | # let's create a fake egg replacing setuptools one | ||
700 | 445 | res = _patch_egg_dir(setuptools_location) | ||
701 | 446 | if not res: | ||
702 | 447 | return | ||
703 | 448 | log.warn('Patching complete.') | ||
704 | 449 | _relaunch() | ||
705 | 450 | |||
706 | 451 | |||
707 | 452 | def _relaunch(): | ||
708 | 453 | log.warn('Relaunching...') | ||
709 | 454 | # we have to relaunch the process | ||
710 | 455 | # pip marker to avoid a relaunch bug | ||
711 | 456 | _cmd1 = ['-c', 'install', '--single-version-externally-managed'] | ||
712 | 457 | _cmd2 = ['-c', 'install', '--record'] | ||
713 | 458 | if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: | ||
714 | 459 | sys.argv[0] = 'setup.py' | ||
715 | 460 | args = [sys.executable] + sys.argv | ||
716 | 461 | sys.exit(subprocess.call(args)) | ||
717 | 462 | |||
718 | 463 | |||
719 | 464 | def _extractall(self, path=".", members=None): | ||
720 | 465 | """Extract all members from the archive to the current working | ||
721 | 466 | directory and set owner, modification time and permissions on | ||
722 | 467 | directories afterwards. `path' specifies a different directory | ||
723 | 468 | to extract to. `members' is optional and must be a subset of the | ||
724 | 469 | list returned by getmembers(). | ||
725 | 470 | """ | ||
726 | 471 | import copy | ||
727 | 472 | import operator | ||
728 | 473 | from tarfile import ExtractError | ||
729 | 474 | directories = [] | ||
730 | 475 | |||
731 | 476 | if members is None: | ||
732 | 477 | members = self | ||
733 | 478 | |||
734 | 479 | for tarinfo in members: | ||
735 | 480 | if tarinfo.isdir(): | ||
736 | 481 | # Extract directories with a safe mode. | ||
737 | 482 | directories.append(tarinfo) | ||
738 | 483 | tarinfo = copy.copy(tarinfo) | ||
739 | 484 | tarinfo.mode = 448 # decimal for oct 0700 | ||
740 | 485 | self.extract(tarinfo, path) | ||
741 | 486 | |||
742 | 487 | # Reverse sort directories. | ||
743 | 488 | if sys.version_info < (2, 4): | ||
744 | 489 | def sorter(dir1, dir2): | ||
745 | 490 | return cmp(dir1.name, dir2.name) | ||
746 | 491 | directories.sort(sorter) | ||
747 | 492 | directories.reverse() | ||
748 | 493 | else: | ||
749 | 494 | directories.sort(key=operator.attrgetter('name'), reverse=True) | ||
750 | 495 | |||
751 | 496 | # Set correct owner, mtime and filemode on directories. | ||
752 | 497 | for tarinfo in directories: | ||
753 | 498 | dirpath = os.path.join(path, tarinfo.name) | ||
754 | 499 | try: | ||
755 | 500 | self.chown(tarinfo, dirpath) | ||
756 | 501 | self.utime(tarinfo, dirpath) | ||
757 | 502 | self.chmod(tarinfo, dirpath) | ||
758 | 503 | except ExtractError: | ||
759 | 504 | e = sys.exc_info()[1] | ||
760 | 505 | if self.errorlevel > 1: | ||
761 | 506 | raise | ||
762 | 507 | else: | ||
763 | 508 | self._dbg(1, "tarfile: %s" % e) | ||
764 | 509 | |||
765 | 510 | |||
766 | 511 | def _build_install_args(options): | ||
767 | 512 | """ | ||
768 | 513 | Build the arguments to 'python setup.py install' on the distribute package | ||
769 | 514 | """ | ||
770 | 515 | install_args = [] | ||
771 | 516 | if options.user_install: | ||
772 | 517 | if sys.version_info < (2, 6): | ||
773 | 518 | log.warn("--user requires Python 2.6 or later") | ||
774 | 519 | raise SystemExit(1) | ||
775 | 520 | install_args.append('--user') | ||
776 | 521 | return install_args | ||
777 | 522 | |||
778 | 523 | def _parse_args(): | ||
779 | 524 | """ | ||
780 | 525 | Parse the command line for options | ||
781 | 526 | """ | ||
782 | 527 | parser = optparse.OptionParser() | ||
783 | 528 | parser.add_option( | ||
784 | 529 | '--user', dest='user_install', action='store_true', default=False, | ||
785 | 530 | help='install in user site package (requires Python 2.6 or later)') | ||
786 | 531 | parser.add_option( | ||
787 | 532 | '--download-base', dest='download_base', metavar="URL", | ||
788 | 533 | default=DEFAULT_URL, | ||
789 | 534 | help='alternative URL from where to download the distribute package') | ||
790 | 535 | options, args = parser.parse_args() | ||
791 | 536 | # positional arguments are ignored | ||
792 | 537 | return options | ||
793 | 538 | |||
794 | 539 | def main(version=DEFAULT_VERSION): | ||
795 | 540 | """Install or upgrade setuptools and EasyInstall""" | ||
796 | 541 | options = _parse_args() | ||
797 | 542 | tarball = download_setuptools(download_base=options.download_base) | ||
798 | 543 | return _install(tarball, _build_install_args(options)) | ||
799 | 544 | |||
800 | 545 | if __name__ == '__main__': | ||
801 | 546 | sys.exit(main()) | ||
802 | 0 | 547 | ||
803 | === removed file 'ez_setup.py' | |||
804 | --- ez_setup.py 2009-03-24 18:52:52 +0000 | |||
805 | +++ ez_setup.py 1970-01-01 00:00:00 +0000 | |||
806 | @@ -1,241 +0,0 @@ | |||
807 | 1 | #!python | ||
808 | 2 | """Bootstrap setuptools installation | ||
809 | 3 | |||
810 | 4 | If you want to use setuptools in your package's setup.py, just include this | ||
811 | 5 | file in the same directory with it, and add this to the top of your setup.py:: | ||
812 | 6 | |||
813 | 7 | from ez_setup import use_setuptools | ||
814 | 8 | use_setuptools() | ||
815 | 9 | |||
816 | 10 | If you want to require a specific version of setuptools, set a download | ||
817 | 11 | mirror, or use an alternate download directory, you can do so by supplying | ||
818 | 12 | the appropriate options to ``use_setuptools()``. | ||
819 | 13 | |||
820 | 14 | This file can also be run as a script to install or upgrade setuptools. | ||
821 | 15 | """ | ||
822 | 16 | import sys | ||
823 | 17 | DEFAULT_VERSION = "0.6c8" | ||
824 | 18 | DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] | ||
825 | 19 | |||
826 | 20 | md5_data = { | ||
827 | 21 | 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', | ||
828 | 22 | 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', | ||
829 | 23 | 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', | ||
830 | 24 | 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', | ||
831 | 25 | 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', | ||
832 | 26 | 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', | ||
833 | 27 | 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', | ||
834 | 28 | 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', | ||
835 | 29 | 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', | ||
836 | 30 | 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', | ||
837 | 31 | 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', | ||
838 | 32 | 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', | ||
839 | 33 | 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', | ||
840 | 34 | 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', | ||
841 | 35 | 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', | ||
842 | 36 | 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', | ||
843 | 37 | 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', | ||
844 | 38 | 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', | ||
845 | 39 | 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', | ||
846 | 40 | 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', | ||
847 | 41 | 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', | ||
848 | 42 | 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', | ||
849 | 43 | 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', | ||
850 | 44 | 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', | ||
851 | 45 | 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', | ||
852 | 46 | 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', | ||
853 | 47 | 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', | ||
854 | 48 | 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', | ||
855 | 49 | 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', | ||
856 | 50 | 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', | ||
857 | 51 | } | ||
858 | 52 | |||
859 | 53 | import sys, os | ||
860 | 54 | |||
861 | 55 | def _validate_md5(egg_name, data): | ||
862 | 56 | if egg_name in md5_data: | ||
863 | 57 | from md5 import md5 | ||
864 | 58 | digest = md5(data).hexdigest() | ||
865 | 59 | if digest != md5_data[egg_name]: | ||
866 | 60 | print >>sys.stderr, ( | ||
867 | 61 | "md5 validation of %s failed! (Possible download problem?)" | ||
868 | 62 | % egg_name | ||
869 | 63 | ) | ||
870 | 64 | sys.exit(2) | ||
871 | 65 | return data | ||
872 | 66 | |||
873 | 67 | |||
874 | 68 | def use_setuptools( | ||
875 | 69 | version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, | ||
876 | 70 | download_delay=15, min_version=None | ||
877 | 71 | ): | ||
878 | 72 | """Automatically find/download setuptools and make it available on sys.path | ||
879 | 73 | |||
880 | 74 | `version` should be a valid setuptools version number that is available | ||
881 | 75 | as an egg for download under the `download_base` URL (which should end with | ||
882 | 76 | a '/'). `to_dir` is the directory where setuptools will be downloaded, if | ||
883 | 77 | it is not already available. If `download_delay` is specified, it should | ||
884 | 78 | be the number of seconds that will be paused before initiating a download, | ||
885 | 79 | should one be required. If an older version of setuptools is installed, | ||
886 | 80 | this routine will print a message to ``sys.stderr`` and raise SystemExit in | ||
887 | 81 | an attempt to abort the calling script. | ||
888 | 82 | """ | ||
889 | 83 | # Work around a hack in the ez_setup.py file from simplejson==1.7.3. | ||
890 | 84 | if min_version: | ||
891 | 85 | version = min_version | ||
892 | 86 | |||
893 | 87 | was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules | ||
894 | 88 | def do_download(): | ||
895 | 89 | egg = download_setuptools(version, download_base, to_dir, download_delay) | ||
896 | 90 | sys.path.insert(0, egg) | ||
897 | 91 | import setuptools; setuptools.bootstrap_install_from = egg | ||
898 | 92 | try: | ||
899 | 93 | import pkg_resources | ||
900 | 94 | except ImportError: | ||
901 | 95 | return do_download() | ||
902 | 96 | try: | ||
903 | 97 | pkg_resources.require("setuptools>="+version); return | ||
904 | 98 | except pkg_resources.VersionConflict, e: | ||
905 | 99 | if was_imported: | ||
906 | 100 | print >>sys.stderr, ( | ||
907 | 101 | "The required version of setuptools (>=%s) is not available, and\n" | ||
908 | 102 | "can't be installed while this script is running. Please install\n" | ||
909 | 103 | " a more recent version first, using 'easy_install -U setuptools'." | ||
910 | 104 | "\n\n(Currently using %r)" | ||
911 | 105 | ) % (version, e.args[0]) | ||
912 | 106 | sys.exit(2) | ||
913 | 107 | else: | ||
914 | 108 | del pkg_resources, sys.modules['pkg_resources'] # reload ok | ||
915 | 109 | return do_download() | ||
916 | 110 | except pkg_resources.DistributionNotFound: | ||
917 | 111 | return do_download() | ||
918 | 112 | |||
919 | 113 | def download_setuptools( | ||
920 | 114 | version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, | ||
921 | 115 | delay = 15 | ||
922 | 116 | ): | ||
923 | 117 | """Download setuptools from a specified location and return its filename | ||
924 | 118 | |||
925 | 119 | `version` should be a valid setuptools version number that is available | ||
926 | 120 | as an egg for download under the `download_base` URL (which should end | ||
927 | 121 | with a '/'). `to_dir` is the directory where the egg will be downloaded. | ||
928 | 122 | `delay` is the number of seconds to pause before an actual download attempt. | ||
929 | 123 | """ | ||
930 | 124 | import urllib2, shutil | ||
931 | 125 | egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) | ||
932 | 126 | url = download_base + egg_name | ||
933 | 127 | saveto = os.path.join(to_dir, egg_name) | ||
934 | 128 | src = dst = None | ||
935 | 129 | if not os.path.exists(saveto): # Avoid repeated downloads | ||
936 | 130 | try: | ||
937 | 131 | from distutils import log | ||
938 | 132 | if delay: | ||
939 | 133 | log.warn(""" | ||
940 | 134 | --------------------------------------------------------------------------- | ||
941 | 135 | This script requires setuptools version %s to run (even to display | ||
942 | 136 | help). I will attempt to download it for you (from | ||
943 | 137 | %s), but | ||
944 | 138 | you may need to enable firewall access for this script first. | ||
945 | 139 | I will start the download in %d seconds. | ||
946 | 140 | |||
947 | 141 | (Note: if this machine does not have network access, please obtain the file | ||
948 | 142 | |||
949 | 143 | %s | ||
950 | 144 | |||
951 | 145 | and place it in this directory before rerunning this script.) | ||
952 | 146 | ---------------------------------------------------------------------------""", | ||
953 | 147 | version, download_base, delay, url | ||
954 | 148 | ); from time import sleep; sleep(delay) | ||
955 | 149 | log.warn("Downloading %s", url) | ||
956 | 150 | src = urllib2.urlopen(url) | ||
957 | 151 | # Read/write all in one block, so we don't create a corrupt file | ||
958 | 152 | # if the download is interrupted. | ||
959 | 153 | data = _validate_md5(egg_name, src.read()) | ||
960 | 154 | dst = open(saveto,"wb"); dst.write(data) | ||
961 | 155 | finally: | ||
962 | 156 | if src: src.close() | ||
963 | 157 | if dst: dst.close() | ||
964 | 158 | return os.path.realpath(saveto) | ||
965 | 159 | |||
966 | 160 | def main(argv, version=DEFAULT_VERSION): | ||
967 | 161 | """Install or upgrade setuptools and EasyInstall""" | ||
968 | 162 | try: | ||
969 | 163 | import setuptools | ||
970 | 164 | except ImportError: | ||
971 | 165 | egg = None | ||
972 | 166 | try: | ||
973 | 167 | egg = download_setuptools(version, delay=0) | ||
974 | 168 | sys.path.insert(0,egg) | ||
975 | 169 | from setuptools.command.easy_install import main | ||
976 | 170 | return main(list(argv)+[egg]) # we're done here | ||
977 | 171 | finally: | ||
978 | 172 | if egg and os.path.exists(egg): | ||
979 | 173 | os.unlink(egg) | ||
980 | 174 | else: | ||
981 | 175 | if setuptools.__version__ == '0.0.1': | ||
982 | 176 | print >>sys.stderr, ( | ||
983 | 177 | "You have an obsolete version of setuptools installed. Please\n" | ||
984 | 178 | "remove it from your system entirely before rerunning this script." | ||
985 | 179 | ) | ||
986 | 180 | sys.exit(2) | ||
987 | 181 | |||
988 | 182 | req = "setuptools>="+version | ||
989 | 183 | import pkg_resources | ||
990 | 184 | try: | ||
991 | 185 | pkg_resources.require(req) | ||
992 | 186 | except pkg_resources.VersionConflict: | ||
993 | 187 | try: | ||
994 | 188 | from setuptools.command.easy_install import main | ||
995 | 189 | except ImportError: | ||
996 | 190 | from easy_install import main | ||
997 | 191 | main(list(argv)+[download_setuptools(delay=0)]) | ||
998 | 192 | sys.exit(0) # try to force an exit | ||
999 | 193 | else: | ||
1000 | 194 | if argv: | ||
1001 | 195 | from setuptools.command.easy_install import main | ||
1002 | 196 | main(argv) | ||
1003 | 197 | else: | ||
1004 | 198 | print "Setuptools version",version,"or greater has been installed." | ||
1005 | 199 | print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' | ||
1006 | 200 | |||
1007 | 201 | def update_md5(filenames): | ||
1008 | 202 | """Update our built-in md5 registry""" | ||
1009 | 203 | |||
1010 | 204 | import re | ||
1011 | 205 | from md5 import md5 | ||
1012 | 206 | |||
1013 | 207 | for name in filenames: | ||
1014 | 208 | base = os.path.basename(name) | ||
1015 | 209 | f = open(name,'rb') | ||
1016 | 210 | md5_data[base] = md5(f.read()).hexdigest() | ||
1017 | 211 | f.close() | ||
1018 | 212 | |||
1019 | 213 | data = [" %r: %r,\n" % it for it in md5_data.items()] | ||
1020 | 214 | data.sort() | ||
1021 | 215 | repl = "".join(data) | ||
1022 | 216 | |||
1023 | 217 | import inspect | ||
1024 | 218 | srcfile = inspect.getsourcefile(sys.modules[__name__]) | ||
1025 | 219 | f = open(srcfile, 'rb'); src = f.read(); f.close() | ||
1026 | 220 | |||
1027 | 221 | match = re.search("\nmd5_data = {\n([^}]+)}", src) | ||
1028 | 222 | if not match: | ||
1029 | 223 | print >>sys.stderr, "Internal error!" | ||
1030 | 224 | sys.exit(2) | ||
1031 | 225 | |||
1032 | 226 | src = src[:match.start(1)] + repl + src[match.end(1):] | ||
1033 | 227 | f = open(srcfile,'w') | ||
1034 | 228 | f.write(src) | ||
1035 | 229 | f.close() | ||
1036 | 230 | |||
1037 | 231 | |||
1038 | 232 | if __name__=='__main__': | ||
1039 | 233 | if len(sys.argv)>2 and sys.argv[1]=='--md5update': | ||
1040 | 234 | update_md5(sys.argv[2:]) | ||
1041 | 235 | else: | ||
1042 | 236 | main(sys.argv[1:]) | ||
1043 | 237 | |||
1044 | 238 | |||
1045 | 239 | |||
1046 | 240 | |||
1047 | 241 | |||
1048 | 242 | 0 | ||
1049 | === renamed directory 'src/lazr' => 'lazr' | |||
1050 | === modified file 'lazr/__init__.py' | |||
1051 | --- src/lazr/__init__.py 2009-03-24 19:51:29 +0000 | |||
1052 | +++ lazr/__init__.py 2013-01-07 15:18:26 +0000 | |||
1053 | @@ -1,4 +1,4 @@ | |||
1055 | 1 | # Copyright 2008 Canonical Ltd. All rights reserved. | 1 | # Copyright 2008-2013 Canonical Ltd. All rights reserved. |
1056 | 2 | # | 2 | # |
1057 | 3 | # This file is part of lazr.delegates. | 3 | # This file is part of lazr.delegates. |
1058 | 4 | # | 4 | # |
1059 | @@ -14,10 +14,14 @@ | |||
1060 | 14 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
1061 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. |
1062 | 16 | 16 | ||
1070 | 17 | # this is a namespace package | 17 | # This is a namespace package, however under >= Python 3.3, let it be a true |
1071 | 18 | try: | 18 | # namespace package (i.e. this cruft isn't necessary). |
1072 | 19 | import pkg_resources | 19 | import sys |
1073 | 20 | pkg_resources.declare_namespace(__name__) | 20 | |
1074 | 21 | except ImportError: | 21 | if sys.hexversion < 0x30300f0: |
1075 | 22 | import pkgutil | 22 | try: |
1076 | 23 | __path__ = pkgutil.extend_path(__path__, __name__) | 23 | import pkg_resources |
1077 | 24 | pkg_resources.declare_namespace(__name__) | ||
1078 | 25 | except ImportError: | ||
1079 | 26 | import pkgutil | ||
1080 | 27 | __path__ = pkgutil.extend_path(__path__, __name__) | ||
1081 | 24 | 28 | ||
1082 | === renamed file 'src/lazr/delegates/NEWS.txt' => 'lazr/delegates/NEWS.rst' | |||
1083 | --- src/lazr/delegates/NEWS.txt 2010-07-16 13:23:43 +0000 | |||
1084 | +++ lazr/delegates/NEWS.rst 2013-01-07 15:18:26 +0000 | |||
1085 | @@ -2,6 +2,12 @@ | |||
1086 | 2 | NEWS for lazr.delegates | 2 | NEWS for lazr.delegates |
1087 | 3 | ======================= | 3 | ======================= |
1088 | 4 | 4 | ||
1089 | 5 | 2.0 (2013-01-06) | ||
1090 | 6 | ================ | ||
1091 | 7 | |||
1092 | 8 | - Port to Python 3, which requires the use of the ``@delegate_to`` class | ||
1093 | 9 | decorator instead of the ``delegates()`` function call. | ||
1094 | 10 | |||
1095 | 5 | 1.2.0 (2010-07-16) | 11 | 1.2.0 (2010-07-16) |
1096 | 6 | ================== | 12 | ================== |
1097 | 7 | 13 | ||
1098 | 8 | 14 | ||
1099 | === modified file 'lazr/delegates/__init__.py' | |||
1100 | --- src/lazr/delegates/__init__.py 2009-08-29 17:51:36 +0000 | |||
1101 | +++ lazr/delegates/__init__.py 2013-01-07 15:18:26 +0000 | |||
1102 | @@ -1,4 +1,4 @@ | |||
1104 | 1 | # Copyright 2008 Canonical Ltd. All rights reserved. | 1 | # Copyright 2008-2013 Canonical Ltd. All rights reserved. |
1105 | 2 | # | 2 | # |
1106 | 3 | # This file is part of lazr.delegates. | 3 | # This file is part of lazr.delegates. |
1107 | 4 | # | 4 | # |
1108 | @@ -16,12 +16,21 @@ | |||
1109 | 16 | 16 | ||
1110 | 17 | """Decorator helpers that simplify class composition.""" | 17 | """Decorator helpers that simplify class composition.""" |
1111 | 18 | 18 | ||
1112 | 19 | __all__ = [ | ||
1113 | 20 | 'delegate_to', | ||
1114 | 21 | ] | ||
1115 | 22 | |||
1116 | 23 | |||
1117 | 19 | import pkg_resources | 24 | import pkg_resources |
1118 | 20 | __version__ = pkg_resources.resource_string( | 25 | __version__ = pkg_resources.resource_string( |
1119 | 21 | "lazr.delegates", "version.txt").strip() | 26 | "lazr.delegates", "version.txt").strip() |
1120 | 22 | 27 | ||
1126 | 23 | # While we generally frown on "*" imports, this, combined with the fact we | 28 | # The class decorator syntax is different in Python 2 vs. Python 3. |
1127 | 24 | # only test code from this module, means that we can verify what has been | 29 | import sys |
1128 | 25 | # exported. | 30 | if sys.version_info[0] == 2: |
1129 | 26 | from lazr.delegates._delegates import * | 31 | from lazr.delegates._python2 import delegate_to |
1130 | 27 | from lazr.delegates._delegates import __all__ | 32 | # The legacy API is only compatible with Python 2. |
1131 | 33 | from lazr.delegates._delegates import delegates | ||
1132 | 34 | __all__.append('delegates') | ||
1133 | 35 | else: | ||
1134 | 36 | from lazr.delegates._python3 import delegate_to | ||
1135 | 28 | 37 | ||
1136 | === modified file 'lazr/delegates/_delegates.py' | |||
1137 | --- src/lazr/delegates/_delegates.py 2010-07-16 13:14:16 +0000 | |||
1138 | +++ lazr/delegates/_delegates.py 2013-01-07 15:18:26 +0000 | |||
1139 | @@ -1,4 +1,4 @@ | |||
1141 | 1 | # Copyright 2008 Canonical Ltd. All rights reserved. | 1 | # Copyright 2008-2013 Canonical Ltd. All rights reserved. |
1142 | 2 | # | 2 | # |
1143 | 3 | # This file is part of lazr.delegates. | 3 | # This file is part of lazr.delegates. |
1144 | 4 | # | 4 | # |
1145 | @@ -16,11 +16,13 @@ | |||
1146 | 16 | 16 | ||
1147 | 17 | """Decorator helpers that simplify class composition.""" | 17 | """Decorator helpers that simplify class composition.""" |
1148 | 18 | 18 | ||
1149 | 19 | from __future__ import absolute_import, print_function, unicode_literals | ||
1150 | 20 | |||
1151 | 19 | 21 | ||
1152 | 20 | __metaclass__ = type | 22 | __metaclass__ = type |
1156 | 21 | 23 | __all__ = [ | |
1157 | 22 | 24 | 'delegates', | |
1158 | 23 | __all__ = ['delegates', 'Passthrough'] | 25 | ] |
1159 | 24 | 26 | ||
1160 | 25 | 27 | ||
1161 | 26 | import sys | 28 | import sys |
1162 | @@ -30,6 +32,8 @@ | |||
1163 | 30 | from zope.interface import classImplements | 32 | from zope.interface import classImplements |
1164 | 31 | from zope.interface.interfaces import IInterface | 33 | from zope.interface.interfaces import IInterface |
1165 | 32 | 34 | ||
1166 | 35 | from lazr.delegates._passthrough import Passthrough | ||
1167 | 36 | |||
1168 | 33 | 37 | ||
1169 | 34 | def delegates(interface_spec, context='context'): | 38 | def delegates(interface_spec, context='context'): |
1170 | 35 | """Make an adapter into a decorator. | 39 | """Make an adapter into a decorator. |
1171 | @@ -91,7 +95,7 @@ | |||
1172 | 91 | """ | 95 | """ |
1173 | 92 | interface_spec, contextvar = cls.__dict__['__delegates_advice_data__'] | 96 | interface_spec, contextvar = cls.__dict__['__delegates_advice_data__'] |
1174 | 93 | del cls.__delegates_advice_data__ | 97 | del cls.__delegates_advice_data__ |
1176 | 94 | if type(cls) is ClassType: | 98 | if isinstance(cls, ClassType): |
1177 | 95 | raise TypeError( | 99 | raise TypeError( |
1178 | 96 | 'Cannot use delegates() on a classic class: %s.' % cls) | 100 | 'Cannot use delegates() on a classic class: %s.' % cls) |
1179 | 97 | if IInterface.providedBy(interface_spec): | 101 | if IInterface.providedBy(interface_spec): |
1180 | @@ -105,35 +109,3 @@ | |||
1181 | 105 | if getattr(cls, name, not_found) is not_found: | 109 | if getattr(cls, name, not_found) is not_found: |
1182 | 106 | setattr(cls, name, Passthrough(name, contextvar)) | 110 | setattr(cls, name, Passthrough(name, contextvar)) |
1183 | 107 | return cls | 111 | return cls |
1184 | 108 | |||
1185 | 109 | |||
1186 | 110 | class Passthrough: | ||
1187 | 111 | """Call the delegated class for the decorator class. | ||
1188 | 112 | |||
1189 | 113 | If the ``adaptation`` argument is not None, it should be a callable. It | ||
1190 | 114 | will be called with the context, and should return an object that will | ||
1191 | 115 | have the delegated attribute. The ``adaptation`` argument is expected to | ||
1192 | 116 | be used with an interface, to adapt the context. | ||
1193 | 117 | """ | ||
1194 | 118 | def __init__(self, name, contextvar, adaptation=None): | ||
1195 | 119 | self.name = name | ||
1196 | 120 | self.contextvar = contextvar | ||
1197 | 121 | self.adaptation = adaptation | ||
1198 | 122 | |||
1199 | 123 | def __get__(self, inst, cls=None): | ||
1200 | 124 | if inst is None: | ||
1201 | 125 | return self | ||
1202 | 126 | else: | ||
1203 | 127 | context = getattr(inst, self.contextvar) | ||
1204 | 128 | if self.adaptation is not None: | ||
1205 | 129 | context = self.adaptation(context) | ||
1206 | 130 | return getattr(context, self.name) | ||
1207 | 131 | |||
1208 | 132 | def __set__(self, inst, value): | ||
1209 | 133 | context = getattr(inst, self.contextvar) | ||
1210 | 134 | if self.adaptation is not None: | ||
1211 | 135 | context = self.adaptation(context) | ||
1212 | 136 | setattr(context, self.name, value) | ||
1213 | 137 | |||
1214 | 138 | def __delete__(self, inst): | ||
1215 | 139 | raise NotImplementedError | ||
1216 | 140 | 112 | ||
1217 | === added file 'lazr/delegates/_passthrough.py' | |||
1218 | --- lazr/delegates/_passthrough.py 1970-01-01 00:00:00 +0000 | |||
1219 | +++ lazr/delegates/_passthrough.py 2013-01-07 15:18:26 +0000 | |||
1220 | @@ -0,0 +1,55 @@ | |||
1221 | 1 | # Copyright 2008-2013 Canonical Ltd. All rights reserved. | ||
1222 | 2 | # | ||
1223 | 3 | # This file is part of lazr.delegates. | ||
1224 | 4 | # | ||
1225 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1226 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1227 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1228 | 8 | # | ||
1229 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
1230 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
1231 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1232 | 12 | # License for more details. | ||
1233 | 13 | # | ||
1234 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1235 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1236 | 16 | |||
1237 | 17 | from __future__ import absolute_import, print_function, unicode_literals | ||
1238 | 18 | |||
1239 | 19 | |||
1240 | 20 | __metaclass__ = type | ||
1241 | 21 | __all__ = [ | ||
1242 | 22 | 'Passthrough', | ||
1243 | 23 | ] | ||
1244 | 24 | |||
1245 | 25 | |||
1246 | 26 | class Passthrough: | ||
1247 | 27 | """Call the delegated class for the decorator class. | ||
1248 | 28 | |||
1249 | 29 | If the ``adaptation`` argument is not None, it should be a callable. It | ||
1250 | 30 | will be called with the context, and should return an object that will | ||
1251 | 31 | have the delegated attribute. The ``adaptation`` argument is expected to | ||
1252 | 32 | be used with an interface, to adapt the context. | ||
1253 | 33 | """ | ||
1254 | 34 | def __init__(self, name, contextvar, adaptation=None): | ||
1255 | 35 | self.name = name | ||
1256 | 36 | self.contextvar = contextvar | ||
1257 | 37 | self.adaptation = adaptation | ||
1258 | 38 | |||
1259 | 39 | def __get__(self, inst, cls=None): | ||
1260 | 40 | if inst is None: | ||
1261 | 41 | return self | ||
1262 | 42 | else: | ||
1263 | 43 | context = getattr(inst, self.contextvar) | ||
1264 | 44 | if self.adaptation is not None: | ||
1265 | 45 | context = self.adaptation(context) | ||
1266 | 46 | return getattr(context, self.name) | ||
1267 | 47 | |||
1268 | 48 | def __set__(self, inst, value): | ||
1269 | 49 | context = getattr(inst, self.contextvar) | ||
1270 | 50 | if self.adaptation is not None: | ||
1271 | 51 | context = self.adaptation(context) | ||
1272 | 52 | setattr(context, self.name, value) | ||
1273 | 53 | |||
1274 | 54 | def __delete__(self, inst): | ||
1275 | 55 | raise NotImplementedError | ||
1276 | 0 | 56 | ||
1277 | === added file 'lazr/delegates/_python2.py' | |||
1278 | --- lazr/delegates/_python2.py 1970-01-01 00:00:00 +0000 | |||
1279 | +++ lazr/delegates/_python2.py 2013-01-07 15:18:26 +0000 | |||
1280 | @@ -0,0 +1,38 @@ | |||
1281 | 1 | # Copyright 2013 Canonical Ltd. All rights reserved. | ||
1282 | 2 | # | ||
1283 | 3 | # This file is part of lazr.delegates. | ||
1284 | 4 | # | ||
1285 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1286 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1287 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1288 | 8 | # | ||
1289 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
1290 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
1291 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1292 | 12 | # License for more details. | ||
1293 | 13 | # | ||
1294 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1295 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1296 | 16 | |||
1297 | 17 | """Class decorator definition for Python 2.""" | ||
1298 | 18 | |||
1299 | 19 | from zope.interface import classImplements | ||
1300 | 20 | |||
1301 | 21 | from lazr.delegates._passthrough import Passthrough | ||
1302 | 22 | |||
1303 | 23 | |||
1304 | 24 | def delegate_to(*interfaces, **kws): | ||
1305 | 25 | context = kws.pop('context', 'context') | ||
1306 | 26 | if len(kws) > 0: | ||
1307 | 27 | raise TypeError('Too many arguments') | ||
1308 | 28 | if len(interfaces) == 0: | ||
1309 | 29 | raise TypeError('At least one interface is required') | ||
1310 | 30 | def _decorator(cls): | ||
1311 | 31 | missing = object() | ||
1312 | 32 | for interface in interfaces: | ||
1313 | 33 | classImplements(cls, interface) | ||
1314 | 34 | for name in interface: | ||
1315 | 35 | if getattr(cls, name, missing) is missing: | ||
1316 | 36 | setattr(cls, name, Passthrough(name, context)) | ||
1317 | 37 | return cls | ||
1318 | 38 | return _decorator | ||
1319 | 0 | 39 | ||
1320 | === added file 'lazr/delegates/_python3.py' | |||
1321 | --- lazr/delegates/_python3.py 1970-01-01 00:00:00 +0000 | |||
1322 | +++ lazr/delegates/_python3.py 2013-01-07 15:18:26 +0000 | |||
1323 | @@ -0,0 +1,38 @@ | |||
1324 | 1 | # Copyright 2013 Canonical Ltd. All rights reserved. | ||
1325 | 2 | # | ||
1326 | 3 | # This file is part of lazr.delegates. | ||
1327 | 4 | # | ||
1328 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1329 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1330 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1331 | 8 | # | ||
1332 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
1333 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
1334 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1335 | 12 | # License for more details. | ||
1336 | 13 | # | ||
1337 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1338 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1339 | 16 | |||
1340 | 17 | """Class decorator definition for Python 3. | ||
1341 | 18 | |||
1342 | 19 | This syntax is not legal in Python 2. | ||
1343 | 20 | """ | ||
1344 | 21 | |||
1345 | 22 | from zope.interface import classImplements | ||
1346 | 23 | |||
1347 | 24 | from lazr.delegates._passthrough import Passthrough | ||
1348 | 25 | |||
1349 | 26 | |||
1350 | 27 | def delegate_to(*interfaces, context='context'): | ||
1351 | 28 | if len(interfaces) == 0: | ||
1352 | 29 | raise TypeError('At least one interface is required') | ||
1353 | 30 | def _decorator(cls): | ||
1354 | 31 | missing = object() | ||
1355 | 32 | for interface in interfaces: | ||
1356 | 33 | classImplements(cls, interface) | ||
1357 | 34 | for name in interface: | ||
1358 | 35 | if getattr(cls, name, missing) is missing: | ||
1359 | 36 | setattr(cls, name, Passthrough(name, context)) | ||
1360 | 37 | return cls | ||
1361 | 38 | return _decorator | ||
1362 | 0 | 39 | ||
1363 | === added directory 'lazr/delegates/docs' | |||
1364 | === added file 'lazr/delegates/docs/__init__.py' | |||
1365 | === added file 'lazr/delegates/docs/fixture.py' | |||
1366 | --- lazr/delegates/docs/fixture.py 1970-01-01 00:00:00 +0000 | |||
1367 | +++ lazr/delegates/docs/fixture.py 2013-01-07 15:18:26 +0000 | |||
1368 | @@ -0,0 +1,34 @@ | |||
1369 | 1 | # Copyright 2009-2013 Canonical Ltd. All rights reserved. | ||
1370 | 2 | # | ||
1371 | 3 | # This file is part of lazr.delegates | ||
1372 | 4 | # | ||
1373 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1374 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1375 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1376 | 8 | # | ||
1377 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but WITHOUT | ||
1378 | 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
1379 | 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1380 | 12 | # License for more details. | ||
1381 | 13 | # | ||
1382 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1383 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1384 | 16 | |||
1385 | 17 | """Doctest fixtures for running under nose.""" | ||
1386 | 18 | |||
1387 | 19 | from __future__ import absolute_import, print_function, unicode_literals | ||
1388 | 20 | |||
1389 | 21 | __metaclass__ = type | ||
1390 | 22 | __all__ = [ | ||
1391 | 23 | 'globs', | ||
1392 | 24 | ] | ||
1393 | 25 | |||
1394 | 26 | |||
1395 | 27 | def globs(globs): | ||
1396 | 28 | """Set up globals for doctests.""" | ||
1397 | 29 | # Enable future statements to make Python 2 act more like Python 3. | ||
1398 | 30 | globs['absolute_import'] = absolute_import | ||
1399 | 31 | globs['print_function'] = print_function | ||
1400 | 32 | globs['unicode_literals'] = unicode_literals | ||
1401 | 33 | # Provide a convenient way to clean things up at the end of the test. | ||
1402 | 34 | return globs | ||
1403 | 0 | 35 | ||
1404 | === added file 'lazr/delegates/docs/usage.rst' | |||
1405 | --- lazr/delegates/docs/usage.rst 1970-01-01 00:00:00 +0000 | |||
1406 | +++ lazr/delegates/docs/usage.rst 2013-01-07 15:18:26 +0000 | |||
1407 | @@ -0,0 +1,136 @@ | |||
1408 | 1 | ============== | ||
1409 | 2 | lazr.delegates | ||
1410 | 3 | ============== | ||
1411 | 4 | |||
1412 | 5 | The ``lazr.delegates`` package makes it easy to write objects that delegate | ||
1413 | 6 | behavior to another object. The new object adds some property or behavior on | ||
1414 | 7 | to the other object, while still providing the underlying interface, and | ||
1415 | 8 | delegating behavior. | ||
1416 | 9 | |||
1417 | 10 | |||
1418 | 11 | Usage | ||
1419 | 12 | ===== | ||
1420 | 13 | |||
1421 | 14 | The ``@delegate_to`` class decorator makes a class implement zero or more | ||
1422 | 15 | interfaces by delegating the implementation to another object. In the case of | ||
1423 | 16 | a class providing an adapter, that object will be the *context*, but it can | ||
1424 | 17 | really be any object stored in an attribute. So while the interfaces use an | ||
1425 | 18 | inheritance mechanism, the classes use a composition mechanism. | ||
1426 | 19 | |||
1427 | 20 | For example, we can define two interfaces ``IFoo0`` and ``IFoo1`` where the | ||
1428 | 21 | latter inherits from the former. The first interface defines an attribute. | ||
1429 | 22 | |||
1430 | 23 | >>> from zope.interface import Interface, Attribute | ||
1431 | 24 | >>> class IFoo0(Interface): | ||
1432 | 25 | ... one = Attribute('attribute in IFoo0') | ||
1433 | 26 | |||
1434 | 27 | The second (i.e. derived) interface defines a method and an attribute. | ||
1435 | 28 | |||
1436 | 29 | >>> class IFoo1(IFoo0): | ||
1437 | 30 | ... def bar(): | ||
1438 | 31 | ... """A method in IFoo1""" | ||
1439 | 32 | ... baz = Attribute('attribute in IFoo1') | ||
1440 | 33 | |||
1441 | 34 | We also define two classes that mirror the interfaces, and do something | ||
1442 | 35 | interesting. | ||
1443 | 36 | :: | ||
1444 | 37 | |||
1445 | 38 | >>> class Foo0: | ||
1446 | 39 | ... one = 'one' | ||
1447 | 40 | |||
1448 | 41 | >>> class Foo1(Foo0): | ||
1449 | 42 | ... def bar(self): | ||
1450 | 43 | ... return 'bar' | ||
1451 | 44 | ... baz = 'I am baz' | ||
1452 | 45 | |||
1453 | 46 | Finally, to tie everything together, we can define a class that delegates the | ||
1454 | 47 | implementation of ``IFoo1`` to an attribute on the instance. By default, | ||
1455 | 48 | ``self.context`` is used as the delegate attribute. | ||
1456 | 49 | |||
1457 | 50 | >>> from lazr.delegates import delegate_to | ||
1458 | 51 | >>> @delegate_to(IFoo1) | ||
1459 | 52 | ... class SomeClass: | ||
1460 | 53 | ... def __init__(self, context): | ||
1461 | 54 | ... self.context = context | ||
1462 | 55 | |||
1463 | 56 | When the class doing the delegation is instantiated, an instance of the class | ||
1464 | 57 | implementing the interface is passed in. | ||
1465 | 58 | |||
1466 | 59 | >>> delegate = Foo1() | ||
1467 | 60 | >>> s = SomeClass(delegate) | ||
1468 | 61 | |||
1469 | 62 | Now, the ``bar()`` method comes from ``Foo1``. | ||
1470 | 63 | |||
1471 | 64 | >>> print(s.bar()) | ||
1472 | 65 | bar | ||
1473 | 66 | |||
1474 | 67 | The ``baz`` attribute also comes from ``Foo1``. | ||
1475 | 68 | |||
1476 | 69 | >>> print(s.baz) | ||
1477 | 70 | I am baz | ||
1478 | 71 | |||
1479 | 72 | The ``one`` attribute comes from ``Foo0``. | ||
1480 | 73 | |||
1481 | 74 | >>> print(s.one) | ||
1482 | 75 | one | ||
1483 | 76 | |||
1484 | 77 | Even though the interface of ``SomeClass`` is defined through the delegate, | ||
1485 | 78 | the interface is still provided by the instance. | ||
1486 | 79 | |||
1487 | 80 | >>> IFoo1.providedBy(s) | ||
1488 | 81 | True | ||
1489 | 82 | |||
1490 | 83 | |||
1491 | 84 | Custom context | ||
1492 | 85 | -------------- | ||
1493 | 86 | |||
1494 | 87 | The ``@delegate_to`` decorator takes an optional keyword argument to customize | ||
1495 | 88 | the attribute containing the object to delegate to. | ||
1496 | 89 | |||
1497 | 90 | >>> @delegate_to(IFoo1, context='myfoo') | ||
1498 | 91 | ... class SomeOtherClass: | ||
1499 | 92 | ... def __init__(self, foo): | ||
1500 | 93 | ... self.myfoo = foo | ||
1501 | 94 | |||
1502 | 95 | The attributes and methods are still delegated correctly. | ||
1503 | 96 | |||
1504 | 97 | >>> s = SomeOtherClass(delegate) | ||
1505 | 98 | >>> print(s.bar()) | ||
1506 | 99 | bar | ||
1507 | 100 | >>> print(s.baz) | ||
1508 | 101 | I am baz | ||
1509 | 102 | |||
1510 | 103 | |||
1511 | 104 | Multiple interfaces | ||
1512 | 105 | =================== | ||
1513 | 106 | |||
1514 | 107 | The ``@delegate_to`` decorator accepts more than one interface. Note however, | ||
1515 | 108 | that the context attribute must implement all of the named interfaces. | ||
1516 | 109 | |||
1517 | 110 | >>> class IFoo2(Interface): | ||
1518 | 111 | ... another = Attribute('another attribute') | ||
1519 | 112 | |||
1520 | 113 | Here is a class that implements the interface. It inherits from the | ||
1521 | 114 | implementation class that provides the ``IFoo0`` interface. Thus does this | ||
1522 | 115 | class implement both interfaces. | ||
1523 | 116 | |||
1524 | 117 | >>> class Foo2(Foo0): | ||
1525 | 118 | ... another = 'I am another foo' | ||
1526 | 119 | |||
1527 | 120 | Again, we tie it all together. | ||
1528 | 121 | |||
1529 | 122 | >>> @delegate_to(IFoo0, IFoo2) | ||
1530 | 123 | ... class SomeOtherClass: | ||
1531 | 124 | ... def __init__(self, context): | ||
1532 | 125 | ... self.context = context | ||
1533 | 126 | |||
1534 | 127 | Now, the instance of this class has all the expected attributes, and provides | ||
1535 | 128 | the expected interfaces. | ||
1536 | 129 | |||
1537 | 130 | >>> s = SomeOtherClass(Foo2()) | ||
1538 | 131 | >>> print(s.another) | ||
1539 | 132 | I am another foo | ||
1540 | 133 | >>> IFoo0.providedBy(s) | ||
1541 | 134 | True | ||
1542 | 135 | >>> IFoo2.providedBy(s) | ||
1543 | 136 | True | ||
1544 | 0 | 137 | ||
1545 | === added file 'lazr/delegates/docs/usage_fixture.py' | |||
1546 | --- lazr/delegates/docs/usage_fixture.py 1970-01-01 00:00:00 +0000 | |||
1547 | +++ lazr/delegates/docs/usage_fixture.py 2013-01-07 15:18:26 +0000 | |||
1548 | @@ -0,0 +1,27 @@ | |||
1549 | 1 | # Copyright 2009-2013 Canonical Ltd. All rights reserved. | ||
1550 | 2 | # | ||
1551 | 3 | # This file is part of lazr.delegates | ||
1552 | 4 | # | ||
1553 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1554 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1555 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1556 | 8 | # | ||
1557 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but WITHOUT | ||
1558 | 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
1559 | 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1560 | 12 | # License for more details. | ||
1561 | 13 | # | ||
1562 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1563 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1564 | 16 | |||
1565 | 17 | """Doctest fixtures for running under nose.""" | ||
1566 | 18 | |||
1567 | 19 | from __future__ import absolute_import, print_function, unicode_literals | ||
1568 | 20 | |||
1569 | 21 | __metaclass__ = type | ||
1570 | 22 | __all__ = [ | ||
1571 | 23 | 'globs', | ||
1572 | 24 | ] | ||
1573 | 25 | |||
1574 | 26 | |||
1575 | 27 | from lazr.delegates.docs.fixture import globs | ||
1576 | 0 | 28 | ||
1577 | === modified file 'lazr/delegates/tests/__init__.py' | |||
1578 | --- src/lazr/delegates/tests/__init__.py 2009-03-24 19:51:29 +0000 | |||
1579 | +++ lazr/delegates/tests/__init__.py 2013-01-07 15:18:26 +0000 | |||
1580 | @@ -1,16 +0,0 @@ | |||
1581 | 1 | # Copyright 2008 Canonical Ltd. All rights reserved. | ||
1582 | 2 | # | ||
1583 | 3 | # This file is part of lazr.delegates. | ||
1584 | 4 | # | ||
1585 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1586 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1587 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1588 | 8 | # | ||
1589 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
1590 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
1591 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1592 | 12 | # License for more details. | ||
1593 | 13 | # | ||
1594 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1595 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1596 | 16 | """Tests for lazr.delegates""" | ||
1597 | 17 | 0 | ||
1598 | === added file 'lazr/delegates/tests/test_api.py' | |||
1599 | --- lazr/delegates/tests/test_api.py 1970-01-01 00:00:00 +0000 | |||
1600 | +++ lazr/delegates/tests/test_api.py 2013-01-07 15:18:26 +0000 | |||
1601 | @@ -0,0 +1,35 @@ | |||
1602 | 1 | # Copyright 2013 Canonical Ltd. All rights reserved. | ||
1603 | 2 | # | ||
1604 | 3 | # This file is part of lazr.delegates. | ||
1605 | 4 | # | ||
1606 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1607 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1608 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1609 | 8 | # | ||
1610 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
1611 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
1612 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1613 | 12 | # License for more details. | ||
1614 | 13 | # | ||
1615 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1616 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1617 | 16 | |||
1618 | 17 | """Test the new API.""" | ||
1619 | 18 | |||
1620 | 19 | import unittest | ||
1621 | 20 | |||
1622 | 21 | from lazr.delegates import delegate_to | ||
1623 | 22 | |||
1624 | 23 | |||
1625 | 24 | class TestAPI(unittest.TestCase): | ||
1626 | 25 | """Test various corner cases in the API.""" | ||
1627 | 26 | |||
1628 | 27 | def test_no_interfaces(self): | ||
1629 | 28 | try: | ||
1630 | 29 | @delegate_to() | ||
1631 | 30 | class SomeClass(object): | ||
1632 | 31 | pass | ||
1633 | 32 | except TypeError: | ||
1634 | 33 | pass | ||
1635 | 34 | else: | ||
1636 | 35 | self.fail('TypeError expected') | ||
1637 | 0 | 36 | ||
1638 | === added file 'lazr/delegates/tests/test_passthrough.py' | |||
1639 | --- lazr/delegates/tests/test_passthrough.py 1970-01-01 00:00:00 +0000 | |||
1640 | +++ lazr/delegates/tests/test_passthrough.py 2013-01-07 15:18:26 +0000 | |||
1641 | @@ -0,0 +1,85 @@ | |||
1642 | 1 | # Copyright 2013 Canonical Ltd. All rights reserved. | ||
1643 | 2 | # | ||
1644 | 3 | # This file is part of lazr.delegates. | ||
1645 | 4 | # | ||
1646 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1647 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1648 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1649 | 8 | # | ||
1650 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
1651 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
1652 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1653 | 12 | # License for more details. | ||
1654 | 13 | # | ||
1655 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1656 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1657 | 16 | |||
1658 | 17 | """Test the Passthrough implementation.""" | ||
1659 | 18 | |||
1660 | 19 | import unittest | ||
1661 | 20 | |||
1662 | 21 | from lazr.delegates._passthrough import Passthrough | ||
1663 | 22 | |||
1664 | 23 | |||
1665 | 24 | class Base: | ||
1666 | 25 | foo = 'foo from Base' | ||
1667 | 26 | |||
1668 | 27 | @classmethod | ||
1669 | 28 | def clsmethod(cls): | ||
1670 | 29 | return cls.__name__ | ||
1671 | 30 | |||
1672 | 31 | |||
1673 | 32 | class TestPassthrough(unittest.TestCase): | ||
1674 | 33 | def setUp(self): | ||
1675 | 34 | self.p = Passthrough('foo', 'mycontext') | ||
1676 | 35 | self.p2 = Passthrough('clsmethod', 'mycontext') | ||
1677 | 36 | |||
1678 | 37 | self.base = Base() | ||
1679 | 38 | class Adapter: | ||
1680 | 39 | mycontext = self.base | ||
1681 | 40 | self.Adapter = Adapter | ||
1682 | 41 | self.adapter = Adapter() | ||
1683 | 42 | |||
1684 | 43 | def test_get(self): | ||
1685 | 44 | self.assertEqual(self.p.__get__(self.adapter), 'foo from Base') | ||
1686 | 45 | self.assertTrue(self.p.__get__(None, self.Adapter) is self.p) | ||
1687 | 46 | self.assertEqual(self.p2.__get__(self.adapter)(), 'Base') | ||
1688 | 47 | |||
1689 | 48 | def test_set(self): | ||
1690 | 49 | self.p.__set__(self.adapter, 'new value') | ||
1691 | 50 | self.assertEqual(self.base.foo, 'new value') | ||
1692 | 51 | |||
1693 | 52 | def test_no_delete(self): | ||
1694 | 53 | self.assertRaises(NotImplementedError, | ||
1695 | 54 | self.p.__delete__, self.adapter) | ||
1696 | 55 | |||
1697 | 56 | def test_adaptation(self): | ||
1698 | 57 | # Passthrough's third argument (adaptation) is optional and, when | ||
1699 | 58 | # provided, should be a zope.interface.Interface subclass (although in | ||
1700 | 59 | # practice any callable will do) to which the instance is adapted | ||
1701 | 60 | # before getting/setting the delegated attribute. | ||
1702 | 61 | class HasNoFoo(object): | ||
1703 | 62 | _foo = 1 | ||
1704 | 63 | no_foo = HasNoFoo() | ||
1705 | 64 | # ... but IHasFooAdapter uses HasNoFoo._foo to provide its own .foo, | ||
1706 | 65 | # so it works like an adapter for HasNoFoo into some interface that | ||
1707 | 66 | # provides a 'foo' attribute. | ||
1708 | 67 | class IHasFooAdapter(object): | ||
1709 | 68 | def __init__(self, inst): | ||
1710 | 69 | self.inst = inst | ||
1711 | 70 | @property | ||
1712 | 71 | def foo(self): | ||
1713 | 72 | return self.inst._foo | ||
1714 | 73 | @foo.setter | ||
1715 | 74 | def foo(self, value): | ||
1716 | 75 | self.inst._foo = value | ||
1717 | 76 | |||
1718 | 77 | class Example(object): | ||
1719 | 78 | context = no_foo | ||
1720 | 79 | |||
1721 | 80 | p = Passthrough('foo', 'context', adaptation=IHasFooAdapter) | ||
1722 | 81 | e = Example() | ||
1723 | 82 | |||
1724 | 83 | self.assertEqual(p.__get__(e), 1) | ||
1725 | 84 | p.__set__(e, 2) | ||
1726 | 85 | self.assertEqual(p.__get__(e), 2) | ||
1727 | 0 | 86 | ||
1728 | === added file 'lazr/delegates/tests/test_python2.py' | |||
1729 | --- lazr/delegates/tests/test_python2.py 1970-01-01 00:00:00 +0000 | |||
1730 | +++ lazr/delegates/tests/test_python2.py 2013-01-07 15:18:26 +0000 | |||
1731 | @@ -0,0 +1,166 @@ | |||
1732 | 1 | # Copyright 2013 Canonical Ltd. All rights reserved. | ||
1733 | 2 | # | ||
1734 | 3 | # This file is part of lazr.delegates. | ||
1735 | 4 | # | ||
1736 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
1737 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
1738 | 7 | # the Free Software Foundation, version 3 of the License. | ||
1739 | 8 | # | ||
1740 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
1741 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
1742 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
1743 | 12 | # License for more details. | ||
1744 | 13 | # | ||
1745 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1746 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
1747 | 16 | |||
1748 | 17 | """Test the legacy API, which only works in Python 2. | ||
1749 | 18 | |||
1750 | 19 | All of these tests are copied almost verbatim from the old README.rst. | ||
1751 | 20 | """ | ||
1752 | 21 | |||
1753 | 22 | |||
1754 | 23 | from __future__ import absolute_import, print_function, unicode_literals | ||
1755 | 24 | |||
1756 | 25 | |||
1757 | 26 | # Don't enable the following or we can't test classic class failures. | ||
1758 | 27 | #__metaclass__ = type | ||
1759 | 28 | __all__ = [ | ||
1760 | 29 | 'TestLegacyAPI', | ||
1761 | 30 | ] | ||
1762 | 31 | |||
1763 | 32 | |||
1764 | 33 | import sys | ||
1765 | 34 | import unittest | ||
1766 | 35 | |||
1767 | 36 | from zope.interface import Attribute, Interface, implements, providedBy | ||
1768 | 37 | |||
1769 | 38 | from lazr.delegates import delegate_to | ||
1770 | 39 | if sys.version_info[0] == 2: | ||
1771 | 40 | from lazr.delegates import delegates | ||
1772 | 41 | |||
1773 | 42 | |||
1774 | 43 | class IFoo0(Interface): | ||
1775 | 44 | spoo = Attribute('attribute in IFoo0') | ||
1776 | 45 | |||
1777 | 46 | class IFoo(IFoo0): | ||
1778 | 47 | def bar(): | ||
1779 | 48 | 'some method' | ||
1780 | 49 | baz = Attribute('some attribute') | ||
1781 | 50 | |||
1782 | 51 | class BaseFoo0: | ||
1783 | 52 | spoo = 'some spoo' | ||
1784 | 53 | |||
1785 | 54 | class BaseFoo(BaseFoo0): | ||
1786 | 55 | def bar(self): | ||
1787 | 56 | return 'bar' | ||
1788 | 57 | baz = 'hi baz!' | ||
1789 | 58 | |||
1790 | 59 | class IOther(Interface): | ||
1791 | 60 | another = Attribute('another attribute') | ||
1792 | 61 | |||
1793 | 62 | class BaseOtherFoo(BaseFoo): | ||
1794 | 63 | another = 'yes, another' | ||
1795 | 64 | |||
1796 | 65 | |||
1797 | 66 | # Python 2.6 doesn't have skips. | ||
1798 | 67 | def skip_python3(cls): | ||
1799 | 68 | if sys.version_info[0] > 2: | ||
1800 | 69 | return None | ||
1801 | 70 | return cls | ||
1802 | 71 | |||
1803 | 72 | |||
1804 | 73 | @skip_python3 | ||
1805 | 74 | class TestLegacyAPI(unittest.TestCase): | ||
1806 | 75 | def test_basic_usage(self): | ||
1807 | 76 | class SomeClass(object): | ||
1808 | 77 | delegates(IFoo) | ||
1809 | 78 | def __init__(self, context): | ||
1810 | 79 | self.context = context | ||
1811 | 80 | |||
1812 | 81 | f = BaseFoo() | ||
1813 | 82 | s = SomeClass(f) | ||
1814 | 83 | self.assertEqual(s.bar(), 'bar') | ||
1815 | 84 | self.assertEqual(s.baz, 'hi baz!') | ||
1816 | 85 | self.assertEqual(s.spoo, 'some spoo') | ||
1817 | 86 | self.assertTrue(IFoo.providedBy(s)) | ||
1818 | 87 | |||
1819 | 88 | def test_keyword_context(self): | ||
1820 | 89 | class SomeOtherClass(object): | ||
1821 | 90 | delegates(IFoo, context='myfoo') | ||
1822 | 91 | def __init__(self, foo): | ||
1823 | 92 | self.myfoo = foo | ||
1824 | 93 | spoo = 'spoo from SomeOtherClass' | ||
1825 | 94 | |||
1826 | 95 | f = BaseFoo() | ||
1827 | 96 | s = SomeOtherClass(f) | ||
1828 | 97 | self.assertEqual(s.bar(), 'bar') | ||
1829 | 98 | self.assertEqual(s.baz, 'hi baz!') | ||
1830 | 99 | self.assertEqual(s.spoo, 'spoo from SomeOtherClass') | ||
1831 | 100 | |||
1832 | 101 | s.baz = 'fish' | ||
1833 | 102 | self.assertEqual(s.baz, 'fish') | ||
1834 | 103 | self.assertEqual(f.baz, 'fish') | ||
1835 | 104 | |||
1836 | 105 | def test_classic_is_error(self): | ||
1837 | 106 | try: | ||
1838 | 107 | class SomeClassicClass: | ||
1839 | 108 | delegates(IFoo) | ||
1840 | 109 | except TypeError: | ||
1841 | 110 | pass | ||
1842 | 111 | else: | ||
1843 | 112 | self.fail('TypeError expected') | ||
1844 | 113 | |||
1845 | 114 | def test_use_outside_class_is_error(self): | ||
1846 | 115 | self.assertRaises(TypeError, delegates, IFoo) | ||
1847 | 116 | |||
1848 | 117 | def test_multiple_interfaces(self): | ||
1849 | 118 | class SomeOtherClass(object): | ||
1850 | 119 | delegates([IFoo, IOther]) | ||
1851 | 120 | |||
1852 | 121 | s = SomeOtherClass() | ||
1853 | 122 | s.context = BaseOtherFoo() | ||
1854 | 123 | self.assertEqual(s.another, 'yes, another') | ||
1855 | 124 | self.assertEqual(s.baz, 'hi baz!') | ||
1856 | 125 | self.assertEqual(s.spoo, 'some spoo') | ||
1857 | 126 | self.assertTrue(IFoo.providedBy(s)) | ||
1858 | 127 | self.assertTrue(IOther.providedBy(s)) | ||
1859 | 128 | |||
1860 | 129 | def test_decorate_existing_object(self): | ||
1861 | 130 | class MoreFoo(BaseFoo, BaseOtherFoo): | ||
1862 | 131 | implements([IFoo, IOther]) | ||
1863 | 132 | |||
1864 | 133 | foo = MoreFoo() | ||
1865 | 134 | |||
1866 | 135 | class WithExtraTeapot(object): | ||
1867 | 136 | delegates(providedBy(foo)) | ||
1868 | 137 | teapot = 'i am a teapot' | ||
1869 | 138 | |||
1870 | 139 | foo_with_teapot = WithExtraTeapot() | ||
1871 | 140 | foo_with_teapot.context = foo | ||
1872 | 141 | |||
1873 | 142 | self.assertEqual(foo_with_teapot.baz, 'hi baz!') | ||
1874 | 143 | self.assertEqual(foo_with_teapot.another, 'yes, another') | ||
1875 | 144 | self.assertEqual(foo_with_teapot.teapot, 'i am a teapot') | ||
1876 | 145 | self.assertTrue(IFoo.providedBy(foo_with_teapot)) | ||
1877 | 146 | self.assertTrue(IOther.providedBy(foo_with_teapot)) | ||
1878 | 147 | |||
1879 | 148 | |||
1880 | 149 | @skip_python3 | ||
1881 | 150 | class TestNewAPI(unittest.TestCase): | ||
1882 | 151 | """Test corner cases in Python 2. | ||
1883 | 152 | |||
1884 | 153 | Most of the new API is tested in the doctest. The implementation of the | ||
1885 | 154 | new API is different between Python 2 and Python 3, so test these corner | ||
1886 | 155 | cases. | ||
1887 | 156 | """ | ||
1888 | 157 | def test_type_error(self): | ||
1889 | 158 | # Too many arguments to @delegate_to() raises a TypeError. | ||
1890 | 159 | try: | ||
1891 | 160 | @delegate_to(IFoo0, context='myfoo', other='bogus') | ||
1892 | 161 | class SomeClass(object): | ||
1893 | 162 | pass | ||
1894 | 163 | except TypeError: | ||
1895 | 164 | pass | ||
1896 | 165 | else: | ||
1897 | 166 | self.fail('TypeError expected') | ||
1898 | 0 | 167 | ||
1899 | === modified file 'lazr/delegates/version.txt' | |||
1900 | --- src/lazr/delegates/version.txt 2010-07-16 13:23:43 +0000 | |||
1901 | +++ lazr/delegates/version.txt 2013-01-07 15:18:26 +0000 | |||
1902 | @@ -1,1 +1,1 @@ | |||
1904 | 1 | 1.2.0 | 1 | 2.0 |
1905 | 2 | 2 | ||
1906 | === added file 'setup.cfg' | |||
1907 | --- setup.cfg 1970-01-01 00:00:00 +0000 | |||
1908 | +++ setup.cfg 2013-01-07 15:18:26 +0000 | |||
1909 | @@ -0,0 +1,9 @@ | |||
1910 | 1 | [nosetests] | ||
1911 | 2 | verbosity=3 | ||
1912 | 3 | with-coverage=1 | ||
1913 | 4 | with-doctest=1 | ||
1914 | 5 | doctest-extension=.rst | ||
1915 | 6 | doctest-options=+ELLIPSIS,+NORMALIZE_WHITESPACE,+REPORT_NDIFF | ||
1916 | 7 | doctest-fixtures=_fixture | ||
1917 | 8 | cover-package=lazr.delegates | ||
1918 | 9 | pdb=1 | ||
1919 | 0 | 10 | ||
1920 | === modified file 'setup.py' | |||
1921 | --- setup.py 2009-08-29 17:51:36 +0000 | |||
1922 | +++ setup.py 2013-01-07 15:18:26 +0000 | |||
1923 | @@ -1,6 +1,4 @@ | |||
1927 | 1 | #!/usr/bin/env python | 1 | # Copyright 2008-2013 Canonical Ltd. All rights reserved. |
1925 | 2 | |||
1926 | 3 | # Copyright 2008-2009 Canonical Ltd. All rights reserved. | ||
1928 | 4 | # | 2 | # |
1929 | 5 | # This file is part of lazr.delegates. | 3 | # This file is part of lazr.delegates. |
1930 | 6 | # | 4 | # |
1931 | @@ -16,17 +14,16 @@ | |||
1932 | 16 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
1933 | 17 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. |
1934 | 18 | 16 | ||
1937 | 19 | import ez_setup | 17 | import distribute_setup |
1938 | 20 | ez_setup.use_setuptools() | 18 | distribute_setup.use_setuptools() |
1939 | 21 | 19 | ||
1940 | 22 | import sys | ||
1941 | 23 | from setuptools import setup, find_packages | 20 | from setuptools import setup, find_packages |
1942 | 24 | 21 | ||
1943 | 25 | # generic helpers primarily for the long_description | 22 | # generic helpers primarily for the long_description |
1944 | 26 | def generate(*docname_or_string): | 23 | def generate(*docname_or_string): |
1945 | 27 | res = [] | 24 | res = [] |
1946 | 28 | for value in docname_or_string: | 25 | for value in docname_or_string: |
1948 | 29 | if value.endswith('.txt'): | 26 | if value.endswith('.rst'): |
1949 | 30 | f = open(value) | 27 | f = open(value) |
1950 | 31 | value = f.read() | 28 | value = f.read() |
1951 | 32 | f.close() | 29 | f.close() |
1952 | @@ -36,22 +33,21 @@ | |||
1953 | 36 | return '\n'.join(res) | 33 | return '\n'.join(res) |
1954 | 37 | # end generic helpers | 34 | # end generic helpers |
1955 | 38 | 35 | ||
1957 | 39 | __version__ = open("src/lazr/delegates/version.txt").read().strip() | 36 | __version__ = open("lazr/delegates/version.txt").read().strip() |
1958 | 40 | 37 | ||
1959 | 41 | setup( | 38 | setup( |
1960 | 42 | name='lazr.delegates', | 39 | name='lazr.delegates', |
1961 | 43 | version=__version__, | 40 | version=__version__, |
1962 | 44 | namespace_packages=['lazr'], | 41 | namespace_packages=['lazr'], |
1965 | 45 | packages=find_packages('src'), | 42 | packages=find_packages(), |
1964 | 46 | package_dir={'':'src'}, | ||
1966 | 47 | include_package_data=True, | 43 | include_package_data=True, |
1967 | 48 | zip_safe=False, | 44 | zip_safe=False, |
1968 | 49 | maintainer='LAZR Developers', | 45 | maintainer='LAZR Developers', |
1969 | 50 | maintainer_email='lazr-developers@lists.launchpad.net', | 46 | maintainer_email='lazr-developers@lists.launchpad.net', |
1971 | 51 | description=open('README.txt').readline().strip(), | 47 | description=open('README.rst').readline().strip(), |
1972 | 52 | long_description=generate( | 48 | long_description=generate( |
1975 | 53 | 'src/lazr/delegates/README.txt', | 49 | 'lazr/delegates/docs/usage.rst', |
1976 | 54 | 'src/lazr/delegates/NEWS.txt'), | 50 | 'lazr/delegates/NEWS.rst'), |
1977 | 55 | license='LGPL v3', | 51 | license='LGPL v3', |
1978 | 56 | install_requires=[ | 52 | install_requires=[ |
1979 | 57 | 'setuptools', | 53 | 'setuptools', |
1980 | @@ -64,10 +60,10 @@ | |||
1981 | 64 | "Intended Audience :: Developers", | 60 | "Intended Audience :: Developers", |
1982 | 65 | "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", | 61 | "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", |
1983 | 66 | "Operating System :: OS Independent", | 62 | "Operating System :: OS Independent", |
1990 | 67 | "Programming Language :: Python"], | 63 | 'Programming Language :: Python', |
1991 | 68 | extras_require=dict( | 64 | 'Programming Language :: Python :: 2.6', |
1992 | 69 | docs=['Sphinx', | 65 | 'Programming Language :: Python :: 2.7', |
1993 | 70 | 'z3c.recipe.sphinxdoc'] | 66 | 'Programming Language :: Python :: 3', |
1994 | 71 | ), | 67 | ], |
1995 | 72 | test_suite='lazr.delegates.tests', | 68 | test_suite='nose.collector', |
1996 | 73 | ) | 69 | ) |
1997 | 74 | 70 | ||
1998 | === removed directory 'src' | |||
1999 | === removed file 'src/lazr/delegates/README.txt' | |||
2000 | --- src/lazr/delegates/README.txt 2010-07-16 13:14:16 +0000 | |||
2001 | +++ src/lazr/delegates/README.txt 1970-01-01 00:00:00 +0000 | |||
2002 | @@ -1,292 +0,0 @@ | |||
2003 | 1 | .. | ||
2004 | 2 | This file is part of lazr.delegates. | ||
2005 | 3 | |||
2006 | 4 | lazr.delegates is free software: you can redistribute it and/or modify it | ||
2007 | 5 | under the terms of the GNU Lesser General Public License as published by | ||
2008 | 6 | the Free Software Foundation, version 3 of the License. | ||
2009 | 7 | |||
2010 | 8 | lazr.delegates is distributed in the hope that it will be useful, but | ||
2011 | 9 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
2012 | 10 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
2013 | 11 | License for more details. | ||
2014 | 12 | |||
2015 | 13 | You should have received a copy of the GNU Lesser General Public License | ||
2016 | 14 | along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
2017 | 15 | |||
2018 | 16 | The ``lazr.delegates`` Package | ||
2019 | 17 | ****************************** | ||
2020 | 18 | |||
2021 | 19 | The ``lazr.delegates`` package makes it easy to write objects that delegate | ||
2022 | 20 | behavior to another object. The new object adds some property or behavior on | ||
2023 | 21 | to the other object, while still providing the underlying interface, and | ||
2024 | 22 | delegating behavior. | ||
2025 | 23 | |||
2026 | 24 | ===== | ||
2027 | 25 | Usage | ||
2028 | 26 | ===== | ||
2029 | 27 | |||
2030 | 28 | The ``delegates`` function makes a class implement zero or more | ||
2031 | 29 | interfaces by delegating the implementation to another object. In the | ||
2032 | 30 | case of a class providing an adapter, that object will be the 'context', | ||
2033 | 31 | but it can really be any object stored in an attribute. So while the | ||
2034 | 32 | interfaces use an inheritance mechanism, the classes use a composition | ||
2035 | 33 | mechanism. | ||
2036 | 34 | |||
2037 | 35 | For example we can define two interfaces IFoo0 <- IFoo... | ||
2038 | 36 | |||
2039 | 37 | >>> from lazr.delegates import delegates | ||
2040 | 38 | >>> from zope.interface import Interface, Attribute | ||
2041 | 39 | >>> class IFoo0(Interface): | ||
2042 | 40 | ... spoo = Attribute('attribute in IFoo0') | ||
2043 | 41 | |||
2044 | 42 | >>> class IFoo(IFoo0): | ||
2045 | 43 | ... def bar(): | ||
2046 | 44 | ... "some method" | ||
2047 | 45 | ... baz = Attribute("some attribute") | ||
2048 | 46 | |||
2049 | 47 | And two classes (BaseFoo0 <- BaseFoo) that do something interesting. | ||
2050 | 48 | |||
2051 | 49 | >>> class BaseFoo0: | ||
2052 | 50 | ... spoo = 'some spoo' | ||
2053 | 51 | |||
2054 | 52 | >>> class BaseFoo(BaseFoo0): | ||
2055 | 53 | ... def bar(self): | ||
2056 | 54 | ... return 'bar' | ||
2057 | 55 | ... baz = 'hi baz!' | ||
2058 | 56 | |||
2059 | 57 | SomeClass can implement IFoo by delegating to an instance of BaseFoo | ||
2060 | 58 | stored in the 'context' attribute. Note that ``delegates`` takes the | ||
2061 | 59 | interface as the argument. By default, 'context' is the attribute | ||
2062 | 60 | containing the object to which the interface implementation is | ||
2063 | 61 | delegated. | ||
2064 | 62 | |||
2065 | 63 | >>> class SomeClass(object): | ||
2066 | 64 | ... delegates(IFoo) | ||
2067 | 65 | ... def __init__(self, context): | ||
2068 | 66 | ... self.context = context | ||
2069 | 67 | |||
2070 | 68 | >>> f = BaseFoo() | ||
2071 | 69 | >>> s = SomeClass(f) | ||
2072 | 70 | >>> s.bar() | ||
2073 | 71 | 'bar' | ||
2074 | 72 | |||
2075 | 73 | >>> s.baz | ||
2076 | 74 | 'hi baz!' | ||
2077 | 75 | |||
2078 | 76 | >>> s.spoo | ||
2079 | 77 | 'some spoo' | ||
2080 | 78 | |||
2081 | 79 | >>> IFoo.providedBy(s) | ||
2082 | 80 | True | ||
2083 | 81 | |||
2084 | 82 | The ``delegates()`` function takes an optional keyword argument to change | ||
2085 | 83 | attribute containing the object to delegate to. So an existing class, | ||
2086 | 84 | such as SomeOtherClass, can declare the name of the attribute to which to | ||
2087 | 85 | delegate. | ||
2088 | 86 | |||
2089 | 87 | >>> class SomeOtherClass(object): | ||
2090 | 88 | ... delegates(IFoo, context='myfoo') | ||
2091 | 89 | ... def __init__(self, foo): | ||
2092 | 90 | ... self.myfoo = foo | ||
2093 | 91 | ... spoo = 'spoo from SomeOtherClass' | ||
2094 | 92 | |||
2095 | 93 | >>> f = BaseFoo() | ||
2096 | 94 | >>> s = SomeOtherClass(f) | ||
2097 | 95 | >>> s.bar() | ||
2098 | 96 | 'bar' | ||
2099 | 97 | |||
2100 | 98 | >>> s.baz | ||
2101 | 99 | 'hi baz!' | ||
2102 | 100 | |||
2103 | 101 | >>> s.spoo | ||
2104 | 102 | 'spoo from SomeOtherClass' | ||
2105 | 103 | |||
2106 | 104 | >>> s.baz = 'fish' | ||
2107 | 105 | >>> s.baz | ||
2108 | 106 | 'fish' | ||
2109 | 107 | |||
2110 | 108 | >>> f.baz | ||
2111 | 109 | 'fish' | ||
2112 | 110 | |||
2113 | 111 | The ``delegates()`` function can only be used in new-style classes. An | ||
2114 | 112 | error is raised when a classic-style class is modified to implement an | ||
2115 | 113 | interface. | ||
2116 | 114 | |||
2117 | 115 | >>> class SomeClassicClass: | ||
2118 | 116 | ... delegates(IFoo) | ||
2119 | 117 | Traceback (most recent call last): | ||
2120 | 118 | ... | ||
2121 | 119 | TypeError: Cannot use delegates() on a classic | ||
2122 | 120 | class: __builtin__.SomeClassicClass. | ||
2123 | 121 | |||
2124 | 122 | The ``delegates()`` function cannot be used out side of a class definition, | ||
2125 | 123 | such as in a module or in a function. | ||
2126 | 124 | |||
2127 | 125 | >>> delegates(IFoo) | ||
2128 | 126 | Traceback (most recent call last): | ||
2129 | 127 | ... | ||
2130 | 128 | TypeError: delegates() can be used only from a class definition. | ||
2131 | 129 | |||
2132 | 130 | Multiple interfaces can be specified by passing an iterable to | ||
2133 | 131 | delegates(). | ||
2134 | 132 | |||
2135 | 133 | >>> class IOther(Interface): | ||
2136 | 134 | ... another = Attribute("another attribute") | ||
2137 | 135 | |||
2138 | 136 | >>> class BaseOtherFoo(BaseFoo): | ||
2139 | 137 | ... another = 'yes, another' | ||
2140 | 138 | |||
2141 | 139 | >>> class SomeOtherClass(object): | ||
2142 | 140 | ... delegates([IFoo, IOther]) | ||
2143 | 141 | |||
2144 | 142 | >>> s = SomeOtherClass() | ||
2145 | 143 | >>> s.context = BaseOtherFoo() | ||
2146 | 144 | >>> s.another | ||
2147 | 145 | 'yes, another' | ||
2148 | 146 | |||
2149 | 147 | >>> s.baz | ||
2150 | 148 | 'hi baz!' | ||
2151 | 149 | |||
2152 | 150 | >>> s.spoo | ||
2153 | 151 | 'some spoo' | ||
2154 | 152 | |||
2155 | 153 | >>> IFoo.providedBy(s) | ||
2156 | 154 | True | ||
2157 | 155 | |||
2158 | 156 | >>> IOther.providedBy(s) | ||
2159 | 157 | True | ||
2160 | 158 | |||
2161 | 159 | This can be convenient when decorating an existing object. | ||
2162 | 160 | |||
2163 | 161 | >>> from zope.interface import implements | ||
2164 | 162 | >>> class MoreFoo(BaseFoo, BaseOtherFoo): | ||
2165 | 163 | ... implements(IFoo, IOther) | ||
2166 | 164 | |||
2167 | 165 | >>> foo = MoreFoo() | ||
2168 | 166 | |||
2169 | 167 | >>> from zope.interface import providedBy | ||
2170 | 168 | >>> class WithExtraTeapot(object): | ||
2171 | 169 | ... delegates(providedBy(foo)) | ||
2172 | 170 | ... teapot = 'i am a teapot' | ||
2173 | 171 | |||
2174 | 172 | >>> foo_with_teapot = WithExtraTeapot() | ||
2175 | 173 | >>> foo_with_teapot.context = foo | ||
2176 | 174 | |||
2177 | 175 | >>> foo_with_teapot.baz | ||
2178 | 176 | 'hi baz!' | ||
2179 | 177 | |||
2180 | 178 | >>> foo_with_teapot.another | ||
2181 | 179 | 'yes, another' | ||
2182 | 180 | |||
2183 | 181 | >>> foo_with_teapot.teapot | ||
2184 | 182 | 'i am a teapot' | ||
2185 | 183 | |||
2186 | 184 | >>> IFoo.providedBy(foo_with_teapot) | ||
2187 | 185 | True | ||
2188 | 186 | |||
2189 | 187 | >>> IOther.providedBy(foo_with_teapot) | ||
2190 | 188 | True | ||
2191 | 189 | |||
2192 | 190 | ============== | ||
2193 | 191 | Implementation | ||
2194 | 192 | ============== | ||
2195 | 193 | |||
2196 | 194 | The Passthrough class is the implementation machinery of ``delegates()``. It | ||
2197 | 195 | uses the descriptor protocol to implement the delegation behaviour provided by | ||
2198 | 196 | ``delegates()``. It takes at least two arguments: the name of the attribute | ||
2199 | 197 | that is delegated, and the name of the attribute containing the object to | ||
2200 | 198 | which to delegate. | ||
2201 | 199 | |||
2202 | 200 | To illustrate, p and p2 are two Passthrough instances that use the | ||
2203 | 201 | instance assigned to 'mycontext' to call the 'foo' attribute and | ||
2204 | 202 | the 'clsmethod' method. | ||
2205 | 203 | |||
2206 | 204 | >>> from lazr.delegates import Passthrough | ||
2207 | 205 | >>> p = Passthrough('foo', 'mycontext') | ||
2208 | 206 | >>> p2 = Passthrough('clsmethod', 'mycontext') | ||
2209 | 207 | |||
2210 | 208 | Base is a class the implements both 'foo' and 'clsmethod'. | ||
2211 | 209 | |||
2212 | 210 | >>> class Base: | ||
2213 | 211 | ... foo = 'foo from Base' | ||
2214 | 212 | ... def clsmethod(cls): | ||
2215 | 213 | ... return str(cls) | ||
2216 | 214 | ... clsmethod = classmethod(clsmethod) | ||
2217 | 215 | |||
2218 | 216 | Adapter is a class that has an instance of Base assigned to the | ||
2219 | 217 | attribute 'mycontext'. | ||
2220 | 218 | |||
2221 | 219 | >>> base = Base() | ||
2222 | 220 | |||
2223 | 221 | >>> class Adapter: | ||
2224 | 222 | ... mycontext = base | ||
2225 | 223 | |||
2226 | 224 | >>> adapter = Adapter() | ||
2227 | 225 | |||
2228 | 226 | The Passthrough instances can get and set their prescribed attributes | ||
2229 | 227 | when passed an instance of adapter. | ||
2230 | 228 | |||
2231 | 229 | >>> p.__get__(adapter) | ||
2232 | 230 | 'foo from Base' | ||
2233 | 231 | |||
2234 | 232 | >>> p.__get__(None, Adapter) is p | ||
2235 | 233 | True | ||
2236 | 234 | |||
2237 | 235 | >>> p2.__get__(adapter)() | ||
2238 | 236 | '__builtin__.Base' | ||
2239 | 237 | |||
2240 | 238 | >>> p.__set__(adapter, 'new value') | ||
2241 | 239 | >>> base.foo | ||
2242 | 240 | 'new value' | ||
2243 | 241 | |||
2244 | 242 | Passthrough does not implement __delete__. An error is raised if | ||
2245 | 243 | it is called. | ||
2246 | 244 | |||
2247 | 245 | >>> p.__delete__(adapter) | ||
2248 | 246 | Traceback (most recent call last): | ||
2249 | 247 | ... | ||
2250 | 248 | NotImplementedError | ||
2251 | 249 | |||
2252 | 250 | Passthrough's third argument (adaptation) is optional and, when provided, | ||
2253 | 251 | should be a zope.interface.Interface subclass (although in practice any | ||
2254 | 252 | callable will do) to which the instance is adapted before getting/setting the | ||
2255 | 253 | delegated attribute. | ||
2256 | 254 | |||
2257 | 255 | # HasNoFoo does not have a .foo attribute... | ||
2258 | 256 | >>> class HasNoFoo(object): | ||
2259 | 257 | ... _foo = 1 | ||
2260 | 258 | >>> no_foo = HasNoFoo() | ||
2261 | 259 | |||
2262 | 260 | # ... but IHasFooAdapter uses HasNoFoo._foo to provide its own .foo, so it | ||
2263 | 261 | # works like an adapter for HasNoFoo into some interface that provides | ||
2264 | 262 | # a 'foo' attribute. | ||
2265 | 263 | >>> class IHasFooAdapter(object): | ||
2266 | 264 | ... def __init__(self, inst): | ||
2267 | 265 | ... self.inst = inst | ||
2268 | 266 | ... def _get_foo(self): | ||
2269 | 267 | ... return self.inst._foo | ||
2270 | 268 | ... def _set_foo(self, value): | ||
2271 | 269 | ... self.inst._foo = value | ||
2272 | 270 | ... foo = property(_get_foo, _set_foo) | ||
2273 | 271 | |||
2274 | 272 | >>> class Example(object): | ||
2275 | 273 | ... context = no_foo | ||
2276 | 274 | |||
2277 | 275 | >>> p = Passthrough('foo', 'context', adaptation=IHasFooAdapter) | ||
2278 | 276 | >>> e = Example() | ||
2279 | 277 | >>> p.__get__(e) | ||
2280 | 278 | 1 | ||
2281 | 279 | >>> p.__set__(e, 2) | ||
2282 | 280 | >>> p.__get__(e) | ||
2283 | 281 | 2 | ||
2284 | 282 | |||
2285 | 283 | |||
2286 | 284 | =============== | ||
2287 | 285 | Other Documents | ||
2288 | 286 | =============== | ||
2289 | 287 | |||
2290 | 288 | .. toctree:: | ||
2291 | 289 | :glob: | ||
2292 | 290 | |||
2293 | 291 | * | ||
2294 | 292 | docs/* | ||
2295 | 293 | 0 | ||
2296 | === removed file 'src/lazr/delegates/tests/test_docs.py' | |||
2297 | --- src/lazr/delegates/tests/test_docs.py 2009-03-24 19:51:29 +0000 | |||
2298 | +++ src/lazr/delegates/tests/test_docs.py 1970-01-01 00:00:00 +0000 | |||
2299 | @@ -1,51 +0,0 @@ | |||
2300 | 1 | # Copyright 2008-2009 Canonical Ltd. All rights reserved. | ||
2301 | 2 | # | ||
2302 | 3 | # This file is part of lazr.delegates | ||
2303 | 4 | # | ||
2304 | 5 | # lazr.delegates is free software: you can redistribute it and/or modify it | ||
2305 | 6 | # under the terms of the GNU Lesser General Public License as published by | ||
2306 | 7 | # the Free Software Foundation, version 3 of the License. | ||
2307 | 8 | # | ||
2308 | 9 | # lazr.delegates is distributed in the hope that it will be useful, but | ||
2309 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
2310 | 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
2311 | 12 | # License for more details. | ||
2312 | 13 | # | ||
2313 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2314 | 15 | # along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>. | ||
2315 | 16 | "Test harness for doctests." | ||
2316 | 17 | |||
2317 | 18 | # pylint: disable-msg=E0611,W0142 | ||
2318 | 19 | |||
2319 | 20 | __metaclass__ = type | ||
2320 | 21 | __all__ = [ | ||
2321 | 22 | 'additional_tests', | ||
2322 | 23 | ] | ||
2323 | 24 | |||
2324 | 25 | import atexit | ||
2325 | 26 | import doctest | ||
2326 | 27 | import os | ||
2327 | 28 | from pkg_resources import ( | ||
2328 | 29 | resource_filename, resource_exists, resource_listdir, cleanup_resources) | ||
2329 | 30 | import unittest | ||
2330 | 31 | |||
2331 | 32 | DOCTEST_FLAGS = ( | ||
2332 | 33 | doctest.ELLIPSIS | | ||
2333 | 34 | doctest.NORMALIZE_WHITESPACE | | ||
2334 | 35 | doctest.REPORT_NDIFF) | ||
2335 | 36 | |||
2336 | 37 | |||
2337 | 38 | def additional_tests(): | ||
2338 | 39 | "Run the doc tests (README.txt and docs/*, if any exist)" | ||
2339 | 40 | doctest_files = [ | ||
2340 | 41 | os.path.abspath(resource_filename('lazr.delegates', 'README.txt'))] | ||
2341 | 42 | if resource_exists('lazr.delegates', 'docs'): | ||
2342 | 43 | for name in resource_listdir('lazr.delegates', 'docs'): | ||
2343 | 44 | if name.endswith('.txt'): | ||
2344 | 45 | doctest_files.append( | ||
2345 | 46 | os.path.abspath( | ||
2346 | 47 | resource_filename('lazr.delegates', 'docs/%s' % name))) | ||
2347 | 48 | kwargs = dict(module_relative=False, optionflags=DOCTEST_FLAGS) | ||
2348 | 49 | atexit.register(cleanup_resources) | ||
2349 | 50 | return unittest.TestSuite(( | ||
2350 | 51 | doctest.DocFileSuite(*doctest_files, **kwargs))) |
On Jan 07, 2013, at 11:02 AM, Gavin Panella wrote:
>Review: Approve
>
>Looks good. Some minor points. I did a fairly quick once-over
>sanity-check rather than an in-depth review, so you might want to ask
>someone else to take a look if you think it warrants it.
Thanks for the review! You caught some great things.
I'll see if Gary or Curtis wants to take a look, otherwise I'll JFDI :).
BTW, what did you think of @delegate_to as the choice of name?
>+def delegate_ to(*interfaces, **kws):
>+ context = kws.pop('context', 'context')
>+ if len(kws) > 0:
>+ raise TypeError('Too many arguments')
>+ def _decorator(cls):
>+ if len(interfaces) == 0:
>+ raise TypeError('At least one interface is required')
>
>I think it would make more sense to check interfaces at the same time
>as kws, i.e. in delegate_to, rather than _decorator.
>
>In both _python2.py and python3.py.
Good idea, fixed.
>--- lazr/delegates/ docs/fixture. py 1970-01-01 00:00:00 +0000 docs/fixture. py 2013-01-07 10:44:28 +0000 delegates/ docs/usage_ fixture. py too.
>+++ lazr/delegates/
>@@ -0,0 +1,34 @@
>+# Copyright 2009-2013 Canonical Ltd. All rights reserved.
>+#
>+# This file is part of lazr.smtptest
>
>s/smtptest/
>
>In lazr/delegates/
Fixed everywhere.
>+The ``@delegation`` decorator makes a class implement zero or more interfaces delegate_ to/
>
>s/delegation/
Fixed.
>=== added file 'lazr/delegates /docs/usage_ fixture. py' docs.fixture import globs
>...
>+__all__ = [
>+ 'globs',
>+ ]
>+
>+
>+from lazr.delegates.
>
>What does this module do?
It's a nosetests artifact. AFAICT, under nose (which is generally pretty
awesome) the only way to add a fixture for a doctest is to provide a suffix
which is appended to the doctest base name. So, given "_fixture" as the
suffix, for usage.rst it will only search for usage_fixture.py.
In lazr.delegates case, that's not a big deal, but I copied this over from doc.fixture, and then import what's needed into
lazr.smtptest (which I'm porting to Python 3 in a very similar way) and it has
more than one doctest. So the idea was to put the common fixtures in a shared
module, e.g. lazr.delegates.
the nose-required module name.
BTW, I opened a bug on nose for making this nicer.
https:/ /github. com/nose- devs/nose/ issues/ 594
>+class TestAPI( unittest. TestCase) : interfaces( self): 'TypeError expected')
>+ """Test various corner cases in the API."""
>+
>+ def test_no_
>+ try:
>+ @delegate_to()
>+ class SomeClass(object):
>+ pass
>+ except TypeError:
>+ pass
>+ else:
>+ raise AssertionError(
>
>assertRaises would be more concise, but I can see that there's some
>clarity here from using decorator syntax. Use self.fail() instead of
>`raise AssertionError`?
Yeah, I did it this way to use the syntax that you'd see more commonly in the
code. Good idea about self.fail() though - fixed!
Update pushed, and again, thanks!
Would you take a quick look at the lazr.smtptest mp? /code.launchpad .net/~barry/ lazr.smtptest/ lp1096475
https:/