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