Merge lp:~tvansteenburgh/juju-deployer/python3-support into lp:juju-deployer
- python3-support
- Merge into trunk
Proposed by
Tim Van Steenburgh
Status: | Merged |
---|---|
Merged at revision: | 174 |
Proposed branch: | lp:~tvansteenburgh/juju-deployer/python3-support |
Merge into: | lp:juju-deployer |
Diff against target: |
3879 lines (+261/-2524) 43 files modified
.bzrignore (+1/-0) HACKING (+3/-2) Makefile (+1/-9) deployer/action/diff.py (+8/-5) deployer/action/export.py (+1/-0) deployer/action/importer.py (+4/-1) deployer/charm.py (+4/-2) deployer/cli.py (+5/-3) deployer/config.py (+10/-8) deployer/deployment.py (+20/-17) deployer/env/__init__.py (+0/-3) deployer/env/base.py (+2/-1) deployer/env/go.py (+5/-4) deployer/env/mem.py (+5/-3) deployer/env/py.py (+1/-3) deployer/env/watchers.py (+2/-1) deployer/errors.py (+4/-2) deployer/feedback.py (+5/-0) deployer/guiserver.py (+1/-0) deployer/relation.py (+3/-4) deployer/service.py (+14/-10) deployer/tests/base.py (+5/-2) deployer/tests/mock.py (+0/-2367) deployer/tests/test_base.py (+4/-2) deployer/tests/test_charm.py (+2/-1) deployer/tests/test_config.py (+20/-12) deployer/tests/test_constraints.py (+8/-5) deployer/tests/test_deployment.py (+19/-11) deployer/tests/test_diff.py (+8/-5) deployer/tests/test_goenv.py (+2/-1) deployer/tests/test_guienv.py (+1/-0) deployer/tests/test_guiserver.py (+1/-0) deployer/tests/test_importer.py (+10/-7) deployer/tests/test_pyenv.py (+4/-2) deployer/tests/test_service.py (+1/-0) deployer/tests/test_utils.py (+13/-10) deployer/tests/test_watchers.py (+16/-7) deployer/utils.py (+11/-5) deployer/vcs.py (+6/-8) doc/conf.py (+1/-0) setup.py (+6/-1) test-requirements.txt (+4/-0) tox.ini (+20/-0) |
To merge this branch: | bzr merge lp:~tvansteenburgh/juju-deployer/python3-support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
juju-deployers | Pending | ||
Review via email:
|
Commit message
Description of the change
Python 3 support.
All tests passing on python 2.7 and python 3.5 on Xenial (76% coverage). I successfully deployed a bundle using py2.7 and py3.5 on both juju-1.25.5 and juju-2.0-beta6.
These are the configurations I had readily available. If anyone can test others (trusty, py3.4, and especially juju-2.0-beta7), that would be helpful.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2015-02-20 19:18:58 +0000 |
3 | +++ .bzrignore 2016-05-08 03:36:27 +0000 |
4 | @@ -12,3 +12,4 @@ |
5 | tmp |
6 | report/ |
7 | .coverage |
8 | +.tox/ |
9 | |
10 | === modified file 'HACKING' |
11 | --- HACKING 2016-04-04 18:22:59 +0000 |
12 | +++ HACKING 2016-05-08 03:36:27 +0000 |
13 | @@ -13,9 +13,10 @@ |
14 | Running unit tests |
15 | ------------------ |
16 | |
17 | -Tests are compatible with nose and can be run using, e.g.: |
18 | +Tests can be run using tox, e.g.: |
19 | |
20 | - $ nosetests -s --verbosity=2 deployer/tests |
21 | + $ tox # run tests for all supported python versions |
22 | + $ tox -e py27 # run tests on python2.7 |
23 | |
24 | |
25 | Running live environment tests |
26 | |
27 | === modified file 'Makefile' |
28 | --- Makefile 2014-08-26 22:34:07 +0000 |
29 | +++ Makefile 2016-05-08 03:36:27 +0000 |
30 | @@ -1,10 +1,2 @@ |
31 | test: |
32 | - nosetests -s --verbosity=2 deployer/tests |
33 | - |
34 | -freeze: |
35 | - pip install -d tools/dist -r requirements.txt |
36 | - |
37 | -cover: |
38 | - pip install coverage nose |
39 | - nosetests --cover-html-dir=report --cover-html --with-coverage --cover-package=deployer |
40 | - gnome-open report/index.html |
41 | + tox |
42 | |
43 | === modified file 'deployer/action/diff.py' |
44 | --- deployer/action/diff.py 2015-01-27 15:14:00 +0000 |
45 | +++ deployer/action/diff.py 2016-05-08 03:36:27 +0000 |
46 | @@ -1,9 +1,12 @@ |
47 | +from __future__ import absolute_import |
48 | +from __future__ import print_function |
49 | import logging |
50 | import time |
51 | |
52 | from .base import BaseAction |
53 | from ..relation import EndpointPair |
54 | from ..utils import parse_constraints, yaml_dump |
55 | +import six |
56 | |
57 | |
58 | class Diff(BaseAction): |
59 | @@ -22,7 +25,7 @@ |
60 | """ |
61 | rels = set() |
62 | for svc_name in self.env_status['services']: |
63 | - if not svc_name in self.env_status['services']: |
64 | + if svc_name not in self.env_status['services']: |
65 | self.env_state['services'][svc_name] = 'missing' |
66 | self.env_state['services'].setdefault(svc_name, {})[ |
67 | 'options'] = self.env.get_config(svc_name) |
68 | @@ -115,7 +118,7 @@ |
69 | self.deployment.get_charm_for(cs)) |
70 | if not mod: |
71 | continue |
72 | - if not 'modified' in delta: |
73 | + if 'modified' not in delta: |
74 | delta['modified'] = {} |
75 | delta['modified'][cs] = mod |
76 | return delta |
77 | @@ -125,7 +128,7 @@ |
78 | d_sc = parse_constraints(d_s.get('constraints', '')) |
79 | # 'tags' is a special case, as it can be multi-valued: convert to list |
80 | # if cfg one is a string |
81 | - if isinstance(d_sc.get('tags'), basestring): |
82 | + if isinstance(d_sc.get('tags'), six.string_types): |
83 | d_sc['tags'] = [d_sc['tags']] |
84 | if d_sc != e_s['constraints']: |
85 | mod['env-constraints'] = e_s['constraints'] |
86 | @@ -133,7 +136,7 @@ |
87 | for k, v in d_s.get('options', {}).items(): |
88 | # Deploy options not known to the env may originate |
89 | # from charm version delta or be an invalid config. |
90 | - if not k in e_s['options']: |
91 | + if k not in e_s['options']: |
92 | continue |
93 | e_v = e_s['options'].get(k, {}).get('value') |
94 | if e_v != v: |
95 | @@ -155,4 +158,4 @@ |
96 | def run(self): |
97 | diff = self.do_diff() |
98 | if diff: |
99 | - print yaml_dump(diff) |
100 | + print(yaml_dump(diff)) |
101 | |
102 | === modified file 'deployer/action/export.py' |
103 | --- deployer/action/export.py 2013-07-22 15:29:31 +0000 |
104 | +++ deployer/action/export.py 2016-05-08 03:36:27 +0000 |
105 | @@ -1,3 +1,4 @@ |
106 | +from __future__ import absolute_import |
107 | import logging |
108 | |
109 | from .base import BaseAction |
110 | |
111 | === modified file 'deployer/action/importer.py' |
112 | --- deployer/action/importer.py 2016-05-03 16:03:18 +0000 |
113 | +++ deployer/action/importer.py 2016-05-08 03:36:27 +0000 |
114 | @@ -1,9 +1,12 @@ |
115 | +from __future__ import absolute_import |
116 | import logging |
117 | import time |
118 | |
119 | from .base import BaseAction |
120 | from ..env import watchers |
121 | from ..utils import ErrorExit |
122 | +from six.moves import map |
123 | +from six.moves import range |
124 | |
125 | |
126 | class Importer(BaseAction): |
127 | @@ -65,7 +68,7 @@ |
128 | |
129 | def machine_exists(self, id): |
130 | """Checks if the given id exists on the current environment.""" |
131 | - return str(id) in map(str, self.env.status().get('machines', {})) |
132 | + return str(id) in list(map(str, self.env.status().get('machines', {}))) |
133 | |
134 | def create_machines(self): |
135 | """Create machines as specified in the machine spec in the bundle. |
136 | |
137 | === modified file 'deployer/charm.py' |
138 | --- deployer/charm.py 2016-05-03 16:03:18 +0000 |
139 | +++ deployer/charm.py 2016-05-08 03:36:27 +0000 |
140 | @@ -1,8 +1,10 @@ |
141 | +from __future__ import absolute_import |
142 | import logging |
143 | import os |
144 | -import urllib2 |
145 | import shutil |
146 | |
147 | +from six.moves.urllib.request import urlopen |
148 | + |
149 | from .vcs import Git, Bzr |
150 | from .utils import ( |
151 | _check_call, |
152 | @@ -210,7 +212,7 @@ |
153 | |
154 | store_url = "%s/charm/%s" % (STORE_URL, qualified_url[3:]) |
155 | with temp_file() as fh: |
156 | - ufh = urllib2.urlopen(store_url) |
157 | + ufh = urlopen(store_url) |
158 | shutil.copyfileobj(ufh, fh) |
159 | fh.flush() |
160 | extract_zip(fh.name, self.path) |
161 | |
162 | === modified file 'deployer/cli.py' |
163 | --- deployer/cli.py 2016-03-31 02:48:32 +0000 |
164 | +++ deployer/cli.py 2016-05-08 03:36:27 +0000 |
165 | @@ -7,6 +7,8 @@ |
166 | """ |
167 | |
168 | |
169 | +from __future__ import absolute_import |
170 | +from __future__ import print_function |
171 | import argparse |
172 | import errno |
173 | import logging |
174 | @@ -220,14 +222,14 @@ |
175 | |
176 | # Just list the available deployments |
177 | if options.list_deploys: |
178 | - print("\n".join(sorted(config.keys()))) |
179 | + print(("\n".join(sorted(config.keys())))) |
180 | sys.exit(0) |
181 | |
182 | # Do something to a deployment |
183 | if not options.deployment: |
184 | # If there's only one option then use it. |
185 | - if len(config.keys()) == 1: |
186 | - options.deployment = config.keys()[0] |
187 | + if len(list(config.keys())) == 1: |
188 | + options.deployment = list(config.keys())[0] |
189 | log.info("Using deployment %s", options.deployment) |
190 | else: |
191 | log.error( |
192 | |
193 | === modified file 'deployer/config.py' |
194 | --- deployer/config.py 2015-08-05 15:39:40 +0000 |
195 | +++ deployer/config.py 2016-05-08 03:36:27 +0000 |
196 | @@ -1,12 +1,14 @@ |
197 | +from __future__ import absolute_import |
198 | from os.path import abspath, isabs, join, dirname |
199 | |
200 | import logging |
201 | import os |
202 | import tempfile |
203 | import shutil |
204 | -import urllib2 |
205 | -import urlparse |
206 | |
207 | +import six |
208 | +from six.moves.urllib.request import urlopen |
209 | +from six.moves.urllib.parse import urlparse |
210 | |
211 | from .deployment import Deployment |
212 | from .utils import ErrorExit, yaml_load, path_exists, dict_merge |
213 | @@ -23,14 +25,14 @@ |
214 | self.data = {} |
215 | self.yaml = {} |
216 | self.include_dirs = [] |
217 | - self.urlopen = urllib2.urlopen |
218 | + self.urlopen = urlopen |
219 | self.load() |
220 | |
221 | def _yaml_load(self, config_file): |
222 | if config_file in self.yaml: |
223 | return self.yaml[config_file] |
224 | |
225 | - if urlparse.urlparse(config_file).scheme: |
226 | + if urlparse(config_file).scheme: |
227 | response = self.urlopen(config_file) |
228 | if response.getcode() == 200: |
229 | temp = tempfile.NamedTemporaryFile(delete=True) |
230 | @@ -44,7 +46,7 @@ |
231 | with open(config_file) as fh: |
232 | try: |
233 | yaml_result = yaml_load(fh.read()) |
234 | - except Exception, e: |
235 | + except Exception as e: |
236 | self.log.warning( |
237 | "Couldn't load config file @ %r, error: %s:%s", |
238 | config_file, type(e), e) |
239 | @@ -66,7 +68,7 @@ |
240 | def get(self, key): |
241 | if key not in self.data: |
242 | self.log.warning("Deployment %r not found. Available %s", |
243 | - key, ", ".join(self.keys())) |
244 | + key, ", ".join(list(self.keys()))) |
245 | raise ErrorExit() |
246 | deploy_data = self.data[key] |
247 | if self.version < 4: |
248 | @@ -94,7 +96,7 @@ |
249 | |
250 | def _inherits(self, d): |
251 | parents = d.get('inherits', ()) |
252 | - if isinstance(parents, basestring): |
253 | + if isinstance(parents, six.string_types): |
254 | parents = [parents] |
255 | return parents |
256 | |
257 | @@ -115,7 +117,7 @@ |
258 | d = self._yaml_load(config_file) |
259 | |
260 | incs = d.get('include-configs') or d.get('include-config') |
261 | - if isinstance(incs, basestring): |
262 | + if isinstance(incs, six.string_types): |
263 | inc_fs = [incs] |
264 | else: |
265 | inc_fs = incs |
266 | |
267 | === modified file 'deployer/deployment.py' |
268 | --- deployer/deployment.py 2015-08-06 12:04:16 +0000 |
269 | +++ deployer/deployment.py 2016-05-08 03:36:27 +0000 |
270 | @@ -1,10 +1,14 @@ |
271 | +from __future__ import absolute_import |
272 | from base64 import b64encode |
273 | +from functools import cmp_to_key |
274 | |
275 | import logging |
276 | import pprint |
277 | import os |
278 | import yaml |
279 | |
280 | +import six |
281 | + |
282 | from .charm import Charm |
283 | from .feedback import Feedback |
284 | from .service import Service, ServiceUnitPlacementV3, ServiceUnitPlacementV4 |
285 | @@ -50,7 +54,7 @@ |
286 | # Sort unplaced units first, then sort by name for placed units. |
287 | services.sort(key=lambda svc: (bool(svc.unit_placement), svc.name)) |
288 | else: |
289 | - services.sort(self._machines_placement_sort) |
290 | + services.sort(key=cmp_to_key(self._machines_placement_sort)) |
291 | return services |
292 | |
293 | def set_machines(self, machines): |
294 | @@ -78,7 +82,7 @@ |
295 | |
296 | def get_service_names(self): |
297 | """Return a sequence of service names for this deployment.""" |
298 | - return self.data.get('services', {}).keys() |
299 | + return list(self.data.get('services', {}).keys()) |
300 | |
301 | @staticmethod |
302 | def _machines_placement_sort(svc_a, svc_b): |
303 | @@ -90,6 +94,9 @@ |
304 | whether or not the service has a unit placement, and then finally |
305 | based on the name of the service. |
306 | """ |
307 | + def cmp_(a, b): |
308 | + return (a > b) - (a < b) |
309 | + |
310 | if svc_a.unit_placement: |
311 | if svc_b.unit_placement: |
312 | # Check for colocation. This naively assumes that there is no |
313 | @@ -99,14 +106,14 @@ |
314 | if x_in_y(svc_a, svc_b): |
315 | return -1 |
316 | # If no colocation exists, simply compare names. |
317 | - return cmp(svc_a.name, svc_b.name) |
318 | + return cmp_(svc_a.name, svc_b.name) |
319 | return 1 |
320 | if svc_b.unit_placement: |
321 | return -1 |
322 | - return cmp(svc_a.name, svc_b.name) |
323 | + return cmp_(svc_a.name, svc_b.name) |
324 | |
325 | def get_unit_placement(self, svc, status): |
326 | - if isinstance(svc, (str, unicode)): |
327 | + if isinstance(svc, (str, six.text_type)): |
328 | svc = self.get_service(svc) |
329 | if self.version == 3: |
330 | return ServiceUnitPlacementV3(svc, self, status) |
331 | @@ -124,7 +131,6 @@ |
332 | def check(a, b): |
333 | k = tuple(sorted([a, b])) |
334 | if k in seen: |
335 | - #self.log.warning(" Skipping duplicate relation %r" % (k,)) |
336 | return |
337 | seen.add(k) |
338 | return True |
339 | @@ -154,8 +160,6 @@ |
340 | for r in rels[k]: |
341 | if check(*r): |
342 | yield r |
343 | - #self.log.debug( |
344 | - # "Found relations %s\n %s" % (" ".join(map(str, seen)))) |
345 | |
346 | def get_charms(self): |
347 | for k, v in self.data.get('services', {}).items(): |
348 | @@ -204,7 +208,7 @@ |
349 | key, value = o.split('=', 1) |
350 | overrides[key] = value |
351 | |
352 | - for k, v in overrides.iteritems(): |
353 | + for k, v in six.iteritems(overrides): |
354 | found = False |
355 | for svc_name, svc_data in self.data['services'].items(): |
356 | charm = self.get_charm_for(svc_name) |
357 | @@ -225,7 +229,7 @@ |
358 | # against defined services |
359 | feedback = Feedback() |
360 | for svc_name, svc_data in self.data.get('services', {}).items(): |
361 | - if not 'options' in svc_data: |
362 | + if 'options' not in svc_data: |
363 | continue |
364 | charm = self.get_charm_for(svc_name) |
365 | config = charm.config |
366 | @@ -235,7 +239,7 @@ |
367 | if svc_options is None: |
368 | svc_options = {} |
369 | for k, v in svc_options.items(): |
370 | - if not k in config: |
371 | + if k not in config: |
372 | feedback.error( |
373 | "Invalid config charm %s %s=%s" % (charm.name, k, v)) |
374 | continue |
375 | @@ -252,18 +256,17 @@ |
376 | def _resolve_include(self, svc_name, k, v): |
377 | feedback = Feedback() |
378 | for include_type in ["file", "base64"]: |
379 | - if (not isinstance(v, basestring) |
380 | - or not v.startswith( |
381 | - "include-%s://" % include_type)): |
382 | + if (not isinstance(v, six.string_types) or |
383 | + not v.startswith("include-%s://" % include_type)): |
384 | continue |
385 | include, fname = v.split("://", 1) |
386 | ip = resolve_include(fname, self.include_dirs) |
387 | if ip is None: |
388 | feedback.error( |
389 | "Invalid config %s.%s include not found %s" % ( |
390 | - svc_name, k, v)) |
391 | + svc_name, k, v)) |
392 | continue |
393 | - with open(ip) as fh: |
394 | + with open(ip, 'rb') as fh: |
395 | v = fh.read() |
396 | if include_type == "base64": |
397 | v = b64encode(v) |
398 | @@ -277,7 +280,7 @@ |
399 | feedback = Feedback() |
400 | for e_a, e_b in self.get_relations(): |
401 | for ep in [Endpoint(e_a), Endpoint(e_b)]: |
402 | - if not ep.service in services: |
403 | + if ep.service not in services: |
404 | feedback.error( |
405 | ("Invalid relation in config," |
406 | " service %s not found, rel %s") % ( |
407 | |
408 | === modified file 'deployer/env/__init__.py' |
409 | --- deployer/env/__init__.py 2014-02-20 01:14:53 +0000 |
410 | +++ deployer/env/__init__.py 2016-05-08 03:36:27 +0000 |
411 | @@ -1,4 +1,3 @@ |
412 | -# |
413 | from .go import GoEnvironment |
414 | from .py import PyEnvironment |
415 | from ..utils import _check_call |
416 | @@ -10,5 +9,3 @@ |
417 | if result is None: |
418 | return PyEnvironment(name, options) |
419 | return GoEnvironment(name, options) |
420 | - |
421 | - |
422 | |
423 | === modified file 'deployer/env/base.py' |
424 | --- deployer/env/base.py 2016-05-05 17:39:13 +0000 |
425 | +++ deployer/env/base.py 2016-05-08 03:36:27 +0000 |
426 | @@ -1,3 +1,4 @@ |
427 | +from __future__ import absolute_import |
428 | import logging |
429 | |
430 | from ..utils import ( |
431 | @@ -66,7 +67,7 @@ |
432 | params = self._named_env(["juju", "deploy"]) |
433 | with temp_file() as fh: |
434 | if config: |
435 | - fh.write(yaml_dump({name: config})) |
436 | + fh.write(yaml_dump({name: config}).encode()) |
437 | fh.flush() |
438 | params.extend(["--config", fh.name]) |
439 | if constraints: |
440 | |
441 | === modified file 'deployer/env/go.py' |
442 | --- deployer/env/go.py 2016-03-10 14:14:54 +0000 |
443 | +++ deployer/env/go.py 2016-05-08 03:36:27 +0000 |
444 | @@ -1,3 +1,5 @@ |
445 | +from __future__ import absolute_import |
446 | +from functools import cmp_to_key |
447 | import time |
448 | |
449 | from .base import BaseEnvironment |
450 | @@ -56,7 +58,6 @@ |
451 | if self.client: |
452 | self.client.close() |
453 | |
454 | - |
455 | def connect(self): |
456 | self.log.debug("Connecting to environment...") |
457 | if self.juju_version == 1: |
458 | @@ -130,7 +131,7 @@ |
459 | return |
460 | |
461 | # containers before machines, container hosts post wait. |
462 | - machines = status['machines'].keys() |
463 | + machines = list(status['machines'].keys()) |
464 | |
465 | container_hosts = set() |
466 | containers = set() |
467 | @@ -145,9 +146,9 @@ |
468 | return -1 |
469 | if m == y: |
470 | return 1 |
471 | - return cmp(x, y) |
472 | + return (x > y) - (x < y) |
473 | |
474 | - machines.sort(machine_sort) |
475 | + machines.sort(key=cmp_to_key(machine_sort)) |
476 | |
477 | for mid in machines: |
478 | self._terminate_machine(mid, container_hosts, force=force) |
479 | |
480 | === modified file 'deployer/env/mem.py' |
481 | --- deployer/env/mem.py 2014-04-21 22:49:05 +0000 |
482 | +++ deployer/env/mem.py 2016-05-08 03:36:27 +0000 |
483 | @@ -1,6 +1,8 @@ |
484 | +from __future__ import absolute_import |
485 | from deployer.utils import parse_constraints |
486 | from jujuclient import (UnitErrors, |
487 | EnvError) |
488 | +from six.moves import range |
489 | |
490 | |
491 | class MemoryEnvironment(object): |
492 | @@ -24,7 +26,7 @@ |
493 | """Add units |
494 | """ |
495 | next_num = self._services_data[svc_name]['next_unit_num'] |
496 | - for idx in xrange(next_num, next_num + num): |
497 | + for idx in range(next_num, next_num + num): |
498 | self._services[svc_name]['units'].append( |
499 | '{}/{}'.format(svc_name, idx)) |
500 | self._services_data[svc_name]['next_unit_num'] = \ |
501 | @@ -44,7 +46,7 @@ |
502 | def _get_service(self, svc_name): |
503 | """ Get service by name (as returned by status()) |
504 | """ |
505 | - if not svc_name in self._services: |
506 | + if svc_name not in self._services: |
507 | raise EnvError("Invalid service name") |
508 | return self._services[svc_name] |
509 | |
510 | @@ -55,7 +57,7 @@ |
511 | def destroy_service(self, svc_name): |
512 | """ Destroy a service |
513 | """ |
514 | - if not svc_name in self._services: |
515 | + if svc_name not in self._services: |
516 | raise EnvError("Invalid service name") |
517 | del self._services[svc_name] |
518 | |
519 | |
520 | === modified file 'deployer/env/py.py' |
521 | --- deployer/env/py.py 2014-02-18 12:16:46 +0000 |
522 | +++ deployer/env/py.py 2016-05-08 03:36:27 +0000 |
523 | @@ -1,8 +1,6 @@ |
524 | +from __future__ import absolute_import |
525 | import time |
526 | |
527 | -from deployer.errors import UnitErrors |
528 | -from deployer.utils import ErrorExit |
529 | - |
530 | from .base import BaseEnvironment |
531 | |
532 | |
533 | |
534 | === modified file 'deployer/env/watchers.py' |
535 | --- deployer/env/watchers.py 2016-03-31 20:08:08 +0000 |
536 | +++ deployer/env/watchers.py 2016-05-08 03:36:27 +0000 |
537 | @@ -1,8 +1,9 @@ |
538 | """A collection of juju-core environment watchers.""" |
539 | |
540 | +from __future__ import absolute_import |
541 | from jujuclient import WatchWrapper |
542 | |
543 | -from ..utils import ErrorExit, get_juju_major_version |
544 | +from ..utils import ErrorExit |
545 | |
546 | # _status_map provides a translation of Juju 2 status codes to the closest |
547 | # Juju 1 equivalent. Only defines codes that need translation. |
548 | |
549 | === modified file 'deployer/errors.py' |
550 | --- deployer/errors.py 2013-07-22 15:29:31 +0000 |
551 | +++ deployer/errors.py 2016-05-08 03:36:27 +0000 |
552 | @@ -1,2 +1,4 @@ |
553 | -# TODO make deployer specific exceptions, also move errorexit from utils to here. |
554 | -from jujuclient import UnitErrors, EnvError |
555 | +# TODO make deployer specific exceptions, |
556 | +# also move errorexit from utils to here. |
557 | +from __future__ import absolute_import |
558 | +from jujuclient import UnitErrors, EnvError # noqa |
559 | |
560 | === modified file 'deployer/feedback.py' |
561 | --- deployer/feedback.py 2013-11-20 05:08:15 +0000 |
562 | +++ deployer/feedback.py 2016-05-08 03:36:27 +0000 |
563 | @@ -21,6 +21,11 @@ |
564 | return iter(self.messages) |
565 | |
566 | def __nonzero__(self): |
567 | + # py2 |
568 | + return bool(self.messages) |
569 | + |
570 | + def __bool__(self): |
571 | + # py3 |
572 | return bool(self.messages) |
573 | |
574 | def get_errors(self): |
575 | |
576 | === modified file 'deployer/guiserver.py' |
577 | --- deployer/guiserver.py 2015-08-10 13:12:20 +0000 |
578 | +++ deployer/guiserver.py 2016-05-08 03:36:27 +0000 |
579 | @@ -9,6 +9,7 @@ |
580 | <https://code.launchpad.net/~juju-gui/charms/precise/juju-gui/trunk>. |
581 | """ |
582 | |
583 | +from __future__ import absolute_import |
584 | import os |
585 | |
586 | from deployer.action.importer import Importer |
587 | |
588 | === modified file 'deployer/relation.py' |
589 | --- deployer/relation.py 2013-07-22 15:29:31 +0000 |
590 | +++ deployer/relation.py 2016-05-08 03:36:27 +0000 |
591 | @@ -1,3 +1,4 @@ |
592 | +from __future__ import absolute_import |
593 | import yaml |
594 | |
595 | |
596 | @@ -23,13 +24,11 @@ |
597 | def __eq__(self, ep_pair): |
598 | if not isinstance(ep_pair, EndpointPair): |
599 | return False |
600 | - return (ep_pair.ep_x.service in self |
601 | - and |
602 | + return (ep_pair.ep_x.service in self and |
603 | ep_pair.ep_y.service in self) |
604 | |
605 | def __contains__(self, svc_name): |
606 | - return (svc_name == self.ep_x.service |
607 | - or |
608 | + return (svc_name == self.ep_x.service or |
609 | svc_name == self.ep_y.service) |
610 | |
611 | def __hash__(self): |
612 | |
613 | === modified file 'deployer/service.py' |
614 | --- deployer/service.py 2015-12-11 16:09:53 +0000 |
615 | +++ deployer/service.py 2016-05-08 03:36:27 +0000 |
616 | @@ -1,6 +1,8 @@ |
617 | +from __future__ import absolute_import |
618 | import itertools |
619 | |
620 | -from feedback import Feedback |
621 | +from .feedback import Feedback |
622 | +from six.moves import map |
623 | |
624 | |
625 | class Service(object): |
626 | @@ -43,7 +45,7 @@ |
627 | value = self.svc_data.get('force-machine') |
628 | if value is not None and not isinstance(value, list): |
629 | value = [value] |
630 | - return value and map(str, value) or [] |
631 | + return value and list(map(str, value)) or [] |
632 | |
633 | @property |
634 | def expose(self): |
635 | @@ -93,7 +95,7 @@ |
636 | "Cannot solve, falling back to default placement", |
637 | svc.name, placement, u_idx) |
638 | return None |
639 | - unit_names = svc_units.keys() |
640 | + unit_names = list(svc_units.keys()) |
641 | unit_names.sort() |
642 | machine = svc_units[unit_names[int(u_idx)]].get('machine') |
643 | if not machine: |
644 | @@ -125,7 +127,7 @@ |
645 | |
646 | if not isinstance(unit_placement, list): |
647 | unit_placement = [unit_placement] |
648 | - unit_placement = map(str, unit_placement) |
649 | + unit_placement = list(map(str, unit_placement)) |
650 | |
651 | services = dict([(s.name, s) for s in self.deployment.get_services()]) |
652 | machines = self.deployment.get_machines() |
653 | @@ -134,15 +136,15 @@ |
654 | container, p, u_idx = self._parse_placement(p) |
655 | if container and container not in ('lxc', 'kvm'): |
656 | feedback.error( |
657 | - "Invalid container type:%s service: %s placement: %s" \ |
658 | - % (container, self.service.name, unit_placement[idx])) |
659 | + "Invalid container type:%s service: %s placement: %s" |
660 | + % (container, self.service.name, unit_placement[idx])) |
661 | if u_idx: |
662 | if p in ('maas', 'zone'): |
663 | continue |
664 | if not u_idx.isdigit(): |
665 | feedback.error( |
666 | "Invalid service:%s placement: %s" % ( |
667 | - self.service.name, unit_placement[idx])) |
668 | + self.service.name, unit_placement[idx])) |
669 | if p.isdigit(): |
670 | if p == '0' or p in machines or self.arbitrary_machines: |
671 | continue |
672 | @@ -237,7 +239,8 @@ |
673 | |
674 | self.service.svc_data['to'] = ( |
675 | unit_mapping + |
676 | - list(itertools.repeat(unit_mapping[-1], unit_count - len(unit_mapping))) |
677 | + list(itertools.repeat( |
678 | + unit_mapping[-1], unit_count - len(unit_mapping))) |
679 | ) |
680 | unit_mapping = self.service.unit_placement |
681 | |
682 | @@ -288,7 +291,7 @@ |
683 | |
684 | if not isinstance(unit_placement, (list, tuple)): |
685 | unit_placement = [unit_placement] |
686 | - unit_placement = map(str, unit_placement) |
687 | + unit_placement = list(map(str, unit_placement)) |
688 | |
689 | services = dict([(s.name, s) for s in self.deployment.get_services()]) |
690 | machines = self.deployment.get_machines() |
691 | @@ -349,7 +352,8 @@ |
692 | # Generate a name for this machine to be used in the |
693 | # machines_map used later; as a quick path forward, simply use |
694 | # the unit's name. |
695 | - new_machines.append('{}/{}'.format(self.service.name, unit.next())) |
696 | + new_machines.append('{}/{}'.format( |
697 | + self.service.name, next(unit))) |
698 | return new_machines |
699 | |
700 | def get(self, unit_number): |
701 | |
702 | === modified file 'deployer/tests/base.py' |
703 | --- deployer/tests/base.py 2015-08-06 13:02:14 +0000 |
704 | +++ deployer/tests/base.py 2016-05-08 03:36:27 +0000 |
705 | @@ -1,9 +1,9 @@ |
706 | +from __future__ import absolute_import |
707 | import inspect |
708 | import logging |
709 | import os |
710 | import unittest |
711 | import shutil |
712 | -import StringIO |
713 | import tempfile |
714 | |
715 | import mock |
716 | @@ -11,6 +11,9 @@ |
717 | import deployer |
718 | from deployer.config import ConfigStack |
719 | |
720 | +from six import StringIO |
721 | +from six.moves import range |
722 | + |
723 | |
724 | # Skip during launchpad recipe package builds (DEB_BUILD_ARCH) or if explicitly |
725 | # requested with 'TEST_OFFLINE=1' |
726 | @@ -48,7 +51,7 @@ |
727 | def capture_logging(self, name="", level=logging.INFO, |
728 | log_file=None, formatter=None): |
729 | if log_file is None: |
730 | - log_file = StringIO.StringIO() |
731 | + log_file = StringIO() |
732 | log_handler = logging.StreamHandler(log_file) |
733 | if formatter: |
734 | log_handler.setFormatter(formatter) |
735 | |
736 | === removed file 'deployer/tests/mock.py' |
737 | --- deployer/tests/mock.py 2013-11-22 20:43:40 +0000 |
738 | +++ deployer/tests/mock.py 1970-01-01 00:00:00 +0000 |
739 | @@ -1,2367 +0,0 @@ |
740 | -# mock.py |
741 | -# Test tools for mocking and patching. |
742 | -# Copyright (C) 2007-2012 Michael Foord & the mock team |
743 | -# E-mail: fuzzyman AT voidspace DOT org DOT uk |
744 | - |
745 | -# mock 1.0 |
746 | -# http://www.voidspace.org.uk/python/mock/ |
747 | - |
748 | -# Released subject to the BSD License |
749 | -# Please see http://www.voidspace.org.uk/python/license.shtml |
750 | - |
751 | -# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml |
752 | -# Comments, suggestions and bug reports welcome. |
753 | - |
754 | - |
755 | -__all__ = ( |
756 | - 'Mock', |
757 | - 'MagicMock', |
758 | - 'patch', |
759 | - 'sentinel', |
760 | - 'DEFAULT', |
761 | - 'ANY', |
762 | - 'call', |
763 | - 'create_autospec', |
764 | - 'FILTER_DIR', |
765 | - 'NonCallableMock', |
766 | - 'NonCallableMagicMock', |
767 | - 'mock_open', |
768 | - 'PropertyMock', |
769 | -) |
770 | - |
771 | - |
772 | -__version__ = '1.0.1' |
773 | - |
774 | - |
775 | -import pprint |
776 | -import sys |
777 | - |
778 | -try: |
779 | - import inspect |
780 | -except ImportError: |
781 | - # for alternative platforms that |
782 | - # may not have inspect |
783 | - inspect = None |
784 | - |
785 | -try: |
786 | - from functools import wraps as original_wraps |
787 | -except ImportError: |
788 | - # Python 2.4 compatibility |
789 | - def wraps(original): |
790 | - def inner(f): |
791 | - f.__name__ = original.__name__ |
792 | - f.__doc__ = original.__doc__ |
793 | - f.__module__ = original.__module__ |
794 | - f.__wrapped__ = original |
795 | - return f |
796 | - return inner |
797 | -else: |
798 | - if sys.version_info[:2] >= (3, 3): |
799 | - wraps = original_wraps |
800 | - else: |
801 | - def wraps(func): |
802 | - def inner(f): |
803 | - f = original_wraps(func)(f) |
804 | - f.__wrapped__ = func |
805 | - return f |
806 | - return inner |
807 | - |
808 | -try: |
809 | - unicode |
810 | -except NameError: |
811 | - # Python 3 |
812 | - basestring = unicode = str |
813 | - |
814 | -try: |
815 | - long |
816 | -except NameError: |
817 | - # Python 3 |
818 | - long = int |
819 | - |
820 | -try: |
821 | - BaseException |
822 | -except NameError: |
823 | - # Python 2.4 compatibility |
824 | - BaseException = Exception |
825 | - |
826 | -try: |
827 | - next |
828 | -except NameError: |
829 | - def next(obj): |
830 | - return obj.next() |
831 | - |
832 | - |
833 | -BaseExceptions = (BaseException,) |
834 | -if 'java' in sys.platform: |
835 | - # jython |
836 | - import java |
837 | - BaseExceptions = (BaseException, java.lang.Throwable) |
838 | - |
839 | -try: |
840 | - _isidentifier = str.isidentifier |
841 | -except AttributeError: |
842 | - # Python 2.X |
843 | - import keyword |
844 | - import re |
845 | - regex = re.compile(r'^[a-z_][a-z0-9_]*$', re.I) |
846 | - def _isidentifier(string): |
847 | - if string in keyword.kwlist: |
848 | - return False |
849 | - return regex.match(string) |
850 | - |
851 | - |
852 | -inPy3k = sys.version_info[0] == 3 |
853 | - |
854 | -# Needed to work around Python 3 bug where use of "super" interferes with |
855 | -# defining __class__ as a descriptor |
856 | -_super = super |
857 | - |
858 | -self = 'im_self' |
859 | -builtin = '__builtin__' |
860 | -if inPy3k: |
861 | - self = '__self__' |
862 | - builtin = 'builtins' |
863 | - |
864 | -FILTER_DIR = True |
865 | - |
866 | - |
867 | -def _is_instance_mock(obj): |
868 | - # can't use isinstance on Mock objects because they override __class__ |
869 | - # The base class for all mocks is NonCallableMock |
870 | - return issubclass(type(obj), NonCallableMock) |
871 | - |
872 | - |
873 | -def _is_exception(obj): |
874 | - return ( |
875 | - isinstance(obj, BaseExceptions) or |
876 | - isinstance(obj, ClassTypes) and issubclass(obj, BaseExceptions) |
877 | - ) |
878 | - |
879 | - |
880 | -class _slotted(object): |
881 | - __slots__ = ['a'] |
882 | - |
883 | - |
884 | -DescriptorTypes = ( |
885 | - type(_slotted.a), |
886 | - property, |
887 | -) |
888 | - |
889 | - |
890 | -def _getsignature(func, skipfirst, instance=False): |
891 | - if inspect is None: |
892 | - raise ImportError('inspect module not available') |
893 | - |
894 | - if isinstance(func, ClassTypes) and not instance: |
895 | - try: |
896 | - func = func.__init__ |
897 | - except AttributeError: |
898 | - return |
899 | - skipfirst = True |
900 | - elif not isinstance(func, FunctionTypes): |
901 | - # for classes where instance is True we end up here too |
902 | - try: |
903 | - func = func.__call__ |
904 | - except AttributeError: |
905 | - return |
906 | - |
907 | - if inPy3k: |
908 | - try: |
909 | - argspec = inspect.getfullargspec(func) |
910 | - except TypeError: |
911 | - # C function / method, possibly inherited object().__init__ |
912 | - return |
913 | - regargs, varargs, varkw, defaults, kwonly, kwonlydef, ann = argspec |
914 | - else: |
915 | - try: |
916 | - regargs, varargs, varkwargs, defaults = inspect.getargspec(func) |
917 | - except TypeError: |
918 | - # C function / method, possibly inherited object().__init__ |
919 | - return |
920 | - |
921 | - # instance methods and classmethods need to lose the self argument |
922 | - if getattr(func, self, None) is not None: |
923 | - regargs = regargs[1:] |
924 | - if skipfirst: |
925 | - # this condition and the above one are never both True - why? |
926 | - regargs = regargs[1:] |
927 | - |
928 | - if inPy3k: |
929 | - signature = inspect.formatargspec( |
930 | - regargs, varargs, varkw, defaults, |
931 | - kwonly, kwonlydef, ann, formatvalue=lambda value: "") |
932 | - else: |
933 | - signature = inspect.formatargspec( |
934 | - regargs, varargs, varkwargs, defaults, |
935 | - formatvalue=lambda value: "") |
936 | - return signature[1:-1], func |
937 | - |
938 | - |
939 | -def _check_signature(func, mock, skipfirst, instance=False): |
940 | - if not _callable(func): |
941 | - return |
942 | - |
943 | - result = _getsignature(func, skipfirst, instance) |
944 | - if result is None: |
945 | - return |
946 | - signature, func = result |
947 | - |
948 | - # can't use self because "self" is common as an argument name |
949 | - # unfortunately even not in the first place |
950 | - src = "lambda _mock_self, %s: None" % signature |
951 | - checksig = eval(src, {}) |
952 | - _copy_func_details(func, checksig) |
953 | - type(mock)._mock_check_sig = checksig |
954 | - |
955 | - |
956 | -def _copy_func_details(func, funcopy): |
957 | - funcopy.__name__ = func.__name__ |
958 | - funcopy.__doc__ = func.__doc__ |
959 | - #funcopy.__dict__.update(func.__dict__) |
960 | - funcopy.__module__ = func.__module__ |
961 | - if not inPy3k: |
962 | - funcopy.func_defaults = func.func_defaults |
963 | - return |
964 | - funcopy.__defaults__ = func.__defaults__ |
965 | - funcopy.__kwdefaults__ = func.__kwdefaults__ |
966 | - |
967 | - |
968 | -def _callable(obj): |
969 | - if isinstance(obj, ClassTypes): |
970 | - return True |
971 | - if getattr(obj, '__call__', None) is not None: |
972 | - return True |
973 | - return False |
974 | - |
975 | - |
976 | -def _is_list(obj): |
977 | - # checks for list or tuples |
978 | - # XXXX badly named! |
979 | - return type(obj) in (list, tuple) |
980 | - |
981 | - |
982 | -def _instance_callable(obj): |
983 | - """Given an object, return True if the object is callable. |
984 | - For classes, return True if instances would be callable.""" |
985 | - if not isinstance(obj, ClassTypes): |
986 | - # already an instance |
987 | - return getattr(obj, '__call__', None) is not None |
988 | - |
989 | - klass = obj |
990 | - # uses __bases__ instead of __mro__ so that we work with old style classes |
991 | - if klass.__dict__.get('__call__') is not None: |
992 | - return True |
993 | - |
994 | - for base in klass.__bases__: |
995 | - if _instance_callable(base): |
996 | - return True |
997 | - return False |
998 | - |
999 | - |
1000 | -def _set_signature(mock, original, instance=False): |
1001 | - # creates a function with signature (*args, **kwargs) that delegates to a |
1002 | - # mock. It still does signature checking by calling a lambda with the same |
1003 | - # signature as the original. |
1004 | - if not _callable(original): |
1005 | - return |
1006 | - |
1007 | - skipfirst = isinstance(original, ClassTypes) |
1008 | - result = _getsignature(original, skipfirst, instance) |
1009 | - if result is None: |
1010 | - # was a C function (e.g. object().__init__ ) that can't be mocked |
1011 | - return |
1012 | - |
1013 | - signature, func = result |
1014 | - |
1015 | - src = "lambda %s: None" % signature |
1016 | - checksig = eval(src, {}) |
1017 | - _copy_func_details(func, checksig) |
1018 | - |
1019 | - name = original.__name__ |
1020 | - if not _isidentifier(name): |
1021 | - name = 'funcopy' |
1022 | - context = {'_checksig_': checksig, 'mock': mock} |
1023 | - src = """def %s(*args, **kwargs): |
1024 | - _checksig_(*args, **kwargs) |
1025 | - return mock(*args, **kwargs)""" % name |
1026 | - exec (src, context) |
1027 | - funcopy = context[name] |
1028 | - _setup_func(funcopy, mock) |
1029 | - return funcopy |
1030 | - |
1031 | - |
1032 | -def _setup_func(funcopy, mock): |
1033 | - funcopy.mock = mock |
1034 | - |
1035 | - # can't use isinstance with mocks |
1036 | - if not _is_instance_mock(mock): |
1037 | - return |
1038 | - |
1039 | - def assert_called_with(*args, **kwargs): |
1040 | - return mock.assert_called_with(*args, **kwargs) |
1041 | - def assert_called_once_with(*args, **kwargs): |
1042 | - return mock.assert_called_once_with(*args, **kwargs) |
1043 | - def assert_has_calls(*args, **kwargs): |
1044 | - return mock.assert_has_calls(*args, **kwargs) |
1045 | - def assert_any_call(*args, **kwargs): |
1046 | - return mock.assert_any_call(*args, **kwargs) |
1047 | - def reset_mock(): |
1048 | - funcopy.method_calls = _CallList() |
1049 | - funcopy.mock_calls = _CallList() |
1050 | - mock.reset_mock() |
1051 | - ret = funcopy.return_value |
1052 | - if _is_instance_mock(ret) and not ret is mock: |
1053 | - ret.reset_mock() |
1054 | - |
1055 | - funcopy.called = False |
1056 | - funcopy.call_count = 0 |
1057 | - funcopy.call_args = None |
1058 | - funcopy.call_args_list = _CallList() |
1059 | - funcopy.method_calls = _CallList() |
1060 | - funcopy.mock_calls = _CallList() |
1061 | - |
1062 | - funcopy.return_value = mock.return_value |
1063 | - funcopy.side_effect = mock.side_effect |
1064 | - funcopy._mock_children = mock._mock_children |
1065 | - |
1066 | - funcopy.assert_called_with = assert_called_with |
1067 | - funcopy.assert_called_once_with = assert_called_once_with |
1068 | - funcopy.assert_has_calls = assert_has_calls |
1069 | - funcopy.assert_any_call = assert_any_call |
1070 | - funcopy.reset_mock = reset_mock |
1071 | - |
1072 | - mock._mock_delegate = funcopy |
1073 | - |
1074 | - |
1075 | -def _is_magic(name): |
1076 | - return '__%s__' % name[2:-2] == name |
1077 | - |
1078 | - |
1079 | -class _SentinelObject(object): |
1080 | - "A unique, named, sentinel object." |
1081 | - def __init__(self, name): |
1082 | - self.name = name |
1083 | - |
1084 | - def __repr__(self): |
1085 | - return 'sentinel.%s' % self.name |
1086 | - |
1087 | - |
1088 | -class _Sentinel(object): |
1089 | - """Access attributes to return a named object, usable as a sentinel.""" |
1090 | - def __init__(self): |
1091 | - self._sentinels = {} |
1092 | - |
1093 | - def __getattr__(self, name): |
1094 | - if name == '__bases__': |
1095 | - # Without this help(mock) raises an exception |
1096 | - raise AttributeError |
1097 | - return self._sentinels.setdefault(name, _SentinelObject(name)) |
1098 | - |
1099 | - |
1100 | -sentinel = _Sentinel() |
1101 | - |
1102 | -DEFAULT = sentinel.DEFAULT |
1103 | -_missing = sentinel.MISSING |
1104 | -_deleted = sentinel.DELETED |
1105 | - |
1106 | - |
1107 | -class OldStyleClass: |
1108 | - pass |
1109 | -ClassType = type(OldStyleClass) |
1110 | - |
1111 | - |
1112 | -def _copy(value): |
1113 | - if type(value) in (dict, list, tuple, set): |
1114 | - return type(value)(value) |
1115 | - return value |
1116 | - |
1117 | - |
1118 | -ClassTypes = (type,) |
1119 | -if not inPy3k: |
1120 | - ClassTypes = (type, ClassType) |
1121 | - |
1122 | -_allowed_names = set( |
1123 | - [ |
1124 | - 'return_value', '_mock_return_value', 'side_effect', |
1125 | - '_mock_side_effect', '_mock_parent', '_mock_new_parent', |
1126 | - '_mock_name', '_mock_new_name' |
1127 | - ] |
1128 | -) |
1129 | - |
1130 | - |
1131 | -def _delegating_property(name): |
1132 | - _allowed_names.add(name) |
1133 | - _the_name = '_mock_' + name |
1134 | - def _get(self, name=name, _the_name=_the_name): |
1135 | - sig = self._mock_delegate |
1136 | - if sig is None: |
1137 | - return getattr(self, _the_name) |
1138 | - return getattr(sig, name) |
1139 | - def _set(self, value, name=name, _the_name=_the_name): |
1140 | - sig = self._mock_delegate |
1141 | - if sig is None: |
1142 | - self.__dict__[_the_name] = value |
1143 | - else: |
1144 | - setattr(sig, name, value) |
1145 | - |
1146 | - return property(_get, _set) |
1147 | - |
1148 | - |
1149 | - |
1150 | -class _CallList(list): |
1151 | - |
1152 | - def __contains__(self, value): |
1153 | - if not isinstance(value, list): |
1154 | - return list.__contains__(self, value) |
1155 | - len_value = len(value) |
1156 | - len_self = len(self) |
1157 | - if len_value > len_self: |
1158 | - return False |
1159 | - |
1160 | - for i in range(0, len_self - len_value + 1): |
1161 | - sub_list = self[i:i+len_value] |
1162 | - if sub_list == value: |
1163 | - return True |
1164 | - return False |
1165 | - |
1166 | - def __repr__(self): |
1167 | - return pprint.pformat(list(self)) |
1168 | - |
1169 | - |
1170 | -def _check_and_set_parent(parent, value, name, new_name): |
1171 | - if not _is_instance_mock(value): |
1172 | - return False |
1173 | - if ((value._mock_name or value._mock_new_name) or |
1174 | - (value._mock_parent is not None) or |
1175 | - (value._mock_new_parent is not None)): |
1176 | - return False |
1177 | - |
1178 | - _parent = parent |
1179 | - while _parent is not None: |
1180 | - # setting a mock (value) as a child or return value of itself |
1181 | - # should not modify the mock |
1182 | - if _parent is value: |
1183 | - return False |
1184 | - _parent = _parent._mock_new_parent |
1185 | - |
1186 | - if new_name: |
1187 | - value._mock_new_parent = parent |
1188 | - value._mock_new_name = new_name |
1189 | - if name: |
1190 | - value._mock_parent = parent |
1191 | - value._mock_name = name |
1192 | - return True |
1193 | - |
1194 | - |
1195 | - |
1196 | -class Base(object): |
1197 | - _mock_return_value = DEFAULT |
1198 | - _mock_side_effect = None |
1199 | - def __init__(self, *args, **kwargs): |
1200 | - pass |
1201 | - |
1202 | - |
1203 | - |
1204 | -class NonCallableMock(Base): |
1205 | - """A non-callable version of `Mock`""" |
1206 | - |
1207 | - def __new__(cls, *args, **kw): |
1208 | - # every instance has its own class |
1209 | - # so we can create magic methods on the |
1210 | - # class without stomping on other mocks |
1211 | - new = type(cls.__name__, (cls,), {'__doc__': cls.__doc__}) |
1212 | - instance = object.__new__(new) |
1213 | - return instance |
1214 | - |
1215 | - |
1216 | - def __init__( |
1217 | - self, spec=None, wraps=None, name=None, spec_set=None, |
1218 | - parent=None, _spec_state=None, _new_name='', _new_parent=None, |
1219 | - **kwargs |
1220 | - ): |
1221 | - if _new_parent is None: |
1222 | - _new_parent = parent |
1223 | - |
1224 | - __dict__ = self.__dict__ |
1225 | - __dict__['_mock_parent'] = parent |
1226 | - __dict__['_mock_name'] = name |
1227 | - __dict__['_mock_new_name'] = _new_name |
1228 | - __dict__['_mock_new_parent'] = _new_parent |
1229 | - |
1230 | - if spec_set is not None: |
1231 | - spec = spec_set |
1232 | - spec_set = True |
1233 | - |
1234 | - self._mock_add_spec(spec, spec_set) |
1235 | - |
1236 | - __dict__['_mock_children'] = {} |
1237 | - __dict__['_mock_wraps'] = wraps |
1238 | - __dict__['_mock_delegate'] = None |
1239 | - |
1240 | - __dict__['_mock_called'] = False |
1241 | - __dict__['_mock_call_args'] = None |
1242 | - __dict__['_mock_call_count'] = 0 |
1243 | - __dict__['_mock_call_args_list'] = _CallList() |
1244 | - __dict__['_mock_mock_calls'] = _CallList() |
1245 | - |
1246 | - __dict__['method_calls'] = _CallList() |
1247 | - |
1248 | - if kwargs: |
1249 | - self.configure_mock(**kwargs) |
1250 | - |
1251 | - _super(NonCallableMock, self).__init__( |
1252 | - spec, wraps, name, spec_set, parent, |
1253 | - _spec_state |
1254 | - ) |
1255 | - |
1256 | - |
1257 | - def attach_mock(self, mock, attribute): |
1258 | - """ |
1259 | - Attach a mock as an attribute of this one, replacing its name and |
1260 | - parent. Calls to the attached mock will be recorded in the |
1261 | - `method_calls` and `mock_calls` attributes of this one.""" |
1262 | - mock._mock_parent = None |
1263 | - mock._mock_new_parent = None |
1264 | - mock._mock_name = '' |
1265 | - mock._mock_new_name = None |
1266 | - |
1267 | - setattr(self, attribute, mock) |
1268 | - |
1269 | - |
1270 | - def mock_add_spec(self, spec, spec_set=False): |
1271 | - """Add a spec to a mock. `spec` can either be an object or a |
1272 | - list of strings. Only attributes on the `spec` can be fetched as |
1273 | - attributes from the mock. |
1274 | - |
1275 | - If `spec_set` is True then only attributes on the spec can be set.""" |
1276 | - self._mock_add_spec(spec, spec_set) |
1277 | - |
1278 | - |
1279 | - def _mock_add_spec(self, spec, spec_set): |
1280 | - _spec_class = None |
1281 | - |
1282 | - if spec is not None and not _is_list(spec): |
1283 | - if isinstance(spec, ClassTypes): |
1284 | - _spec_class = spec |
1285 | - else: |
1286 | - _spec_class = _get_class(spec) |
1287 | - |
1288 | - spec = dir(spec) |
1289 | - |
1290 | - __dict__ = self.__dict__ |
1291 | - __dict__['_spec_class'] = _spec_class |
1292 | - __dict__['_spec_set'] = spec_set |
1293 | - __dict__['_mock_methods'] = spec |
1294 | - |
1295 | - |
1296 | - def __get_return_value(self): |
1297 | - ret = self._mock_return_value |
1298 | - if self._mock_delegate is not None: |
1299 | - ret = self._mock_delegate.return_value |
1300 | - |
1301 | - if ret is DEFAULT: |
1302 | - ret = self._get_child_mock( |
1303 | - _new_parent=self, _new_name='()' |
1304 | - ) |
1305 | - self.return_value = ret |
1306 | - return ret |
1307 | - |
1308 | - |
1309 | - def __set_return_value(self, value): |
1310 | - if self._mock_delegate is not None: |
1311 | - self._mock_delegate.return_value = value |
1312 | - else: |
1313 | - self._mock_return_value = value |
1314 | - _check_and_set_parent(self, value, None, '()') |
1315 | - |
1316 | - __return_value_doc = "The value to be returned when the mock is called." |
1317 | - return_value = property(__get_return_value, __set_return_value, |
1318 | - __return_value_doc) |
1319 | - |
1320 | - |
1321 | - @property |
1322 | - def __class__(self): |
1323 | - if self._spec_class is None: |
1324 | - return type(self) |
1325 | - return self._spec_class |
1326 | - |
1327 | - called = _delegating_property('called') |
1328 | - call_count = _delegating_property('call_count') |
1329 | - call_args = _delegating_property('call_args') |
1330 | - call_args_list = _delegating_property('call_args_list') |
1331 | - mock_calls = _delegating_property('mock_calls') |
1332 | - |
1333 | - |
1334 | - def __get_side_effect(self): |
1335 | - sig = self._mock_delegate |
1336 | - if sig is None: |
1337 | - return self._mock_side_effect |
1338 | - return sig.side_effect |
1339 | - |
1340 | - def __set_side_effect(self, value): |
1341 | - value = _try_iter(value) |
1342 | - sig = self._mock_delegate |
1343 | - if sig is None: |
1344 | - self._mock_side_effect = value |
1345 | - else: |
1346 | - sig.side_effect = value |
1347 | - |
1348 | - side_effect = property(__get_side_effect, __set_side_effect) |
1349 | - |
1350 | - |
1351 | - def reset_mock(self): |
1352 | - "Restore the mock object to its initial state." |
1353 | - self.called = False |
1354 | - self.call_args = None |
1355 | - self.call_count = 0 |
1356 | - self.mock_calls = _CallList() |
1357 | - self.call_args_list = _CallList() |
1358 | - self.method_calls = _CallList() |
1359 | - |
1360 | - for child in self._mock_children.values(): |
1361 | - if isinstance(child, _SpecState): |
1362 | - continue |
1363 | - child.reset_mock() |
1364 | - |
1365 | - ret = self._mock_return_value |
1366 | - if _is_instance_mock(ret) and ret is not self: |
1367 | - ret.reset_mock() |
1368 | - |
1369 | - |
1370 | - def configure_mock(self, **kwargs): |
1371 | - """Set attributes on the mock through keyword arguments. |
1372 | - |
1373 | - Attributes plus return values and side effects can be set on child |
1374 | - mocks using standard dot notation and unpacking a dictionary in the |
1375 | - method call: |
1376 | - |
1377 | - >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} |
1378 | - >>> mock.configure_mock(**attrs)""" |
1379 | - for arg, val in sorted(kwargs.items(), |
1380 | - # we sort on the number of dots so that |
1381 | - # attributes are set before we set attributes on |
1382 | - # attributes |
1383 | - key=lambda entry: entry[0].count('.')): |
1384 | - args = arg.split('.') |
1385 | - final = args.pop() |
1386 | - obj = self |
1387 | - for entry in args: |
1388 | - obj = getattr(obj, entry) |
1389 | - setattr(obj, final, val) |
1390 | - |
1391 | - |
1392 | - def __getattr__(self, name): |
1393 | - if name == '_mock_methods': |
1394 | - raise AttributeError(name) |
1395 | - elif self._mock_methods is not None: |
1396 | - if name not in self._mock_methods or name in _all_magics: |
1397 | - raise AttributeError("Mock object has no attribute %r" % name) |
1398 | - elif _is_magic(name): |
1399 | - raise AttributeError(name) |
1400 | - |
1401 | - result = self._mock_children.get(name) |
1402 | - if result is _deleted: |
1403 | - raise AttributeError(name) |
1404 | - elif result is None: |
1405 | - wraps = None |
1406 | - if self._mock_wraps is not None: |
1407 | - # XXXX should we get the attribute without triggering code |
1408 | - # execution? |
1409 | - wraps = getattr(self._mock_wraps, name) |
1410 | - |
1411 | - result = self._get_child_mock( |
1412 | - parent=self, name=name, wraps=wraps, _new_name=name, |
1413 | - _new_parent=self |
1414 | - ) |
1415 | - self._mock_children[name] = result |
1416 | - |
1417 | - elif isinstance(result, _SpecState): |
1418 | - result = create_autospec( |
1419 | - result.spec, result.spec_set, result.instance, |
1420 | - result.parent, result.name |
1421 | - ) |
1422 | - self._mock_children[name] = result |
1423 | - |
1424 | - return result |
1425 | - |
1426 | - |
1427 | - def __repr__(self): |
1428 | - _name_list = [self._mock_new_name] |
1429 | - _parent = self._mock_new_parent |
1430 | - last = self |
1431 | - |
1432 | - dot = '.' |
1433 | - if _name_list == ['()']: |
1434 | - dot = '' |
1435 | - seen = set() |
1436 | - while _parent is not None: |
1437 | - last = _parent |
1438 | - |
1439 | - _name_list.append(_parent._mock_new_name + dot) |
1440 | - dot = '.' |
1441 | - if _parent._mock_new_name == '()': |
1442 | - dot = '' |
1443 | - |
1444 | - _parent = _parent._mock_new_parent |
1445 | - |
1446 | - # use ids here so as not to call __hash__ on the mocks |
1447 | - if id(_parent) in seen: |
1448 | - break |
1449 | - seen.add(id(_parent)) |
1450 | - |
1451 | - _name_list = list(reversed(_name_list)) |
1452 | - _first = last._mock_name or 'mock' |
1453 | - if len(_name_list) > 1: |
1454 | - if _name_list[1] not in ('()', '().'): |
1455 | - _first += '.' |
1456 | - _name_list[0] = _first |
1457 | - name = ''.join(_name_list) |
1458 | - |
1459 | - name_string = '' |
1460 | - if name not in ('mock', 'mock.'): |
1461 | - name_string = ' name=%r' % name |
1462 | - |
1463 | - spec_string = '' |
1464 | - if self._spec_class is not None: |
1465 | - spec_string = ' spec=%r' |
1466 | - if self._spec_set: |
1467 | - spec_string = ' spec_set=%r' |
1468 | - spec_string = spec_string % self._spec_class.__name__ |
1469 | - return "<%s%s%s id='%s'>" % ( |
1470 | - type(self).__name__, |
1471 | - name_string, |
1472 | - spec_string, |
1473 | - id(self) |
1474 | - ) |
1475 | - |
1476 | - |
1477 | - def __dir__(self): |
1478 | - """Filter the output of `dir(mock)` to only useful members. |
1479 | - XXXX |
1480 | - """ |
1481 | - extras = self._mock_methods or [] |
1482 | - from_type = dir(type(self)) |
1483 | - from_dict = list(self.__dict__) |
1484 | - |
1485 | - if FILTER_DIR: |
1486 | - from_type = [e for e in from_type if not e.startswith('_')] |
1487 | - from_dict = [e for e in from_dict if not e.startswith('_') or |
1488 | - _is_magic(e)] |
1489 | - return sorted(set(extras + from_type + from_dict + |
1490 | - list(self._mock_children))) |
1491 | - |
1492 | - |
1493 | - def __setattr__(self, name, value): |
1494 | - if name in _allowed_names: |
1495 | - # property setters go through here |
1496 | - return object.__setattr__(self, name, value) |
1497 | - elif (self._spec_set and self._mock_methods is not None and |
1498 | - name not in self._mock_methods and |
1499 | - name not in self.__dict__): |
1500 | - raise AttributeError("Mock object has no attribute '%s'" % name) |
1501 | - elif name in _unsupported_magics: |
1502 | - msg = 'Attempting to set unsupported magic method %r.' % name |
1503 | - raise AttributeError(msg) |
1504 | - elif name in _all_magics: |
1505 | - if self._mock_methods is not None and name not in self._mock_methods: |
1506 | - raise AttributeError("Mock object has no attribute '%s'" % name) |
1507 | - |
1508 | - if not _is_instance_mock(value): |
1509 | - setattr(type(self), name, _get_method(name, value)) |
1510 | - original = value |
1511 | - value = lambda *args, **kw: original(self, *args, **kw) |
1512 | - else: |
1513 | - # only set _new_name and not name so that mock_calls is tracked |
1514 | - # but not method calls |
1515 | - _check_and_set_parent(self, value, None, name) |
1516 | - setattr(type(self), name, value) |
1517 | - self._mock_children[name] = value |
1518 | - elif name == '__class__': |
1519 | - self._spec_class = value |
1520 | - return |
1521 | - else: |
1522 | - if _check_and_set_parent(self, value, name, name): |
1523 | - self._mock_children[name] = value |
1524 | - return object.__setattr__(self, name, value) |
1525 | - |
1526 | - |
1527 | - def __delattr__(self, name): |
1528 | - if name in _all_magics and name in type(self).__dict__: |
1529 | - delattr(type(self), name) |
1530 | - if name not in self.__dict__: |
1531 | - # for magic methods that are still MagicProxy objects and |
1532 | - # not set on the instance itself |
1533 | - return |
1534 | - |
1535 | - if name in self.__dict__: |
1536 | - object.__delattr__(self, name) |
1537 | - |
1538 | - obj = self._mock_children.get(name, _missing) |
1539 | - if obj is _deleted: |
1540 | - raise AttributeError(name) |
1541 | - if obj is not _missing: |
1542 | - del self._mock_children[name] |
1543 | - self._mock_children[name] = _deleted |
1544 | - |
1545 | - |
1546 | - |
1547 | - def _format_mock_call_signature(self, args, kwargs): |
1548 | - name = self._mock_name or 'mock' |
1549 | - return _format_call_signature(name, args, kwargs) |
1550 | - |
1551 | - |
1552 | - def _format_mock_failure_message(self, args, kwargs): |
1553 | - message = 'Expected call: %s\nActual call: %s' |
1554 | - expected_string = self._format_mock_call_signature(args, kwargs) |
1555 | - call_args = self.call_args |
1556 | - if len(call_args) == 3: |
1557 | - call_args = call_args[1:] |
1558 | - actual_string = self._format_mock_call_signature(*call_args) |
1559 | - return message % (expected_string, actual_string) |
1560 | - |
1561 | - |
1562 | - def assert_called_with(_mock_self, *args, **kwargs): |
1563 | - """assert that the mock was called with the specified arguments. |
1564 | - |
1565 | - Raises an AssertionError if the args and keyword args passed in are |
1566 | - different to the last call to the mock.""" |
1567 | - self = _mock_self |
1568 | - if self.call_args is None: |
1569 | - expected = self._format_mock_call_signature(args, kwargs) |
1570 | - raise AssertionError('Expected call: %s\nNot called' % (expected,)) |
1571 | - |
1572 | - if self.call_args != (args, kwargs): |
1573 | - msg = self._format_mock_failure_message(args, kwargs) |
1574 | - raise AssertionError(msg) |
1575 | - |
1576 | - |
1577 | - def assert_called_once_with(_mock_self, *args, **kwargs): |
1578 | - """assert that the mock was called exactly once and with the specified |
1579 | - arguments.""" |
1580 | - self = _mock_self |
1581 | - if not self.call_count == 1: |
1582 | - msg = ("Expected to be called once. Called %s times." % |
1583 | - self.call_count) |
1584 | - raise AssertionError(msg) |
1585 | - return self.assert_called_with(*args, **kwargs) |
1586 | - |
1587 | - |
1588 | - def assert_has_calls(self, calls, any_order=False): |
1589 | - """assert the mock has been called with the specified calls. |
1590 | - The `mock_calls` list is checked for the calls. |
1591 | - |
1592 | - If `any_order` is False (the default) then the calls must be |
1593 | - sequential. There can be extra calls before or after the |
1594 | - specified calls. |
1595 | - |
1596 | - If `any_order` is True then the calls can be in any order, but |
1597 | - they must all appear in `mock_calls`.""" |
1598 | - if not any_order: |
1599 | - if calls not in self.mock_calls: |
1600 | - raise AssertionError( |
1601 | - 'Calls not found.\nExpected: %r\n' |
1602 | - 'Actual: %r' % (calls, self.mock_calls) |
1603 | - ) |
1604 | - return |
1605 | - |
1606 | - all_calls = list(self.mock_calls) |
1607 | - |
1608 | - not_found = [] |
1609 | - for kall in calls: |
1610 | - try: |
1611 | - all_calls.remove(kall) |
1612 | - except ValueError: |
1613 | - not_found.append(kall) |
1614 | - if not_found: |
1615 | - raise AssertionError( |
1616 | - '%r not all found in call list' % (tuple(not_found),) |
1617 | - ) |
1618 | - |
1619 | - |
1620 | - def assert_any_call(self, *args, **kwargs): |
1621 | - """assert the mock has been called with the specified arguments. |
1622 | - |
1623 | - The assert passes if the mock has *ever* been called, unlike |
1624 | - `assert_called_with` and `assert_called_once_with` that only pass if |
1625 | - the call is the most recent one.""" |
1626 | - kall = call(*args, **kwargs) |
1627 | - if kall not in self.call_args_list: |
1628 | - expected_string = self._format_mock_call_signature(args, kwargs) |
1629 | - raise AssertionError( |
1630 | - '%s call not found' % expected_string |
1631 | - ) |
1632 | - |
1633 | - |
1634 | - def _get_child_mock(self, **kw): |
1635 | - """Create the child mocks for attributes and return value. |
1636 | - By default child mocks will be the same type as the parent. |
1637 | - Subclasses of Mock may want to override this to customize the way |
1638 | - child mocks are made. |
1639 | - |
1640 | - For non-callable mocks the callable variant will be used (rather than |
1641 | - any custom subclass).""" |
1642 | - _type = type(self) |
1643 | - if not issubclass(_type, CallableMixin): |
1644 | - if issubclass(_type, NonCallableMagicMock): |
1645 | - klass = MagicMock |
1646 | - elif issubclass(_type, NonCallableMock) : |
1647 | - klass = Mock |
1648 | - else: |
1649 | - klass = _type.__mro__[1] |
1650 | - return klass(**kw) |
1651 | - |
1652 | - |
1653 | - |
1654 | -def _try_iter(obj): |
1655 | - if obj is None: |
1656 | - return obj |
1657 | - if _is_exception(obj): |
1658 | - return obj |
1659 | - if _callable(obj): |
1660 | - return obj |
1661 | - try: |
1662 | - return iter(obj) |
1663 | - except TypeError: |
1664 | - # XXXX backwards compatibility |
1665 | - # but this will blow up on first call - so maybe we should fail early? |
1666 | - return obj |
1667 | - |
1668 | - |
1669 | - |
1670 | -class CallableMixin(Base): |
1671 | - |
1672 | - def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, |
1673 | - wraps=None, name=None, spec_set=None, parent=None, |
1674 | - _spec_state=None, _new_name='', _new_parent=None, **kwargs): |
1675 | - self.__dict__['_mock_return_value'] = return_value |
1676 | - |
1677 | - _super(CallableMixin, self).__init__( |
1678 | - spec, wraps, name, spec_set, parent, |
1679 | - _spec_state, _new_name, _new_parent, **kwargs |
1680 | - ) |
1681 | - |
1682 | - self.side_effect = side_effect |
1683 | - |
1684 | - |
1685 | - def _mock_check_sig(self, *args, **kwargs): |
1686 | - # stub method that can be replaced with one with a specific signature |
1687 | - pass |
1688 | - |
1689 | - |
1690 | - def __call__(_mock_self, *args, **kwargs): |
1691 | - # can't use self in-case a function / method we are mocking uses self |
1692 | - # in the signature |
1693 | - _mock_self._mock_check_sig(*args, **kwargs) |
1694 | - return _mock_self._mock_call(*args, **kwargs) |
1695 | - |
1696 | - |
1697 | - def _mock_call(_mock_self, *args, **kwargs): |
1698 | - self = _mock_self |
1699 | - self.called = True |
1700 | - self.call_count += 1 |
1701 | - self.call_args = _Call((args, kwargs), two=True) |
1702 | - self.call_args_list.append(_Call((args, kwargs), two=True)) |
1703 | - |
1704 | - _new_name = self._mock_new_name |
1705 | - _new_parent = self._mock_new_parent |
1706 | - self.mock_calls.append(_Call(('', args, kwargs))) |
1707 | - |
1708 | - seen = set() |
1709 | - skip_next_dot = _new_name == '()' |
1710 | - do_method_calls = self._mock_parent is not None |
1711 | - name = self._mock_name |
1712 | - while _new_parent is not None: |
1713 | - this_mock_call = _Call((_new_name, args, kwargs)) |
1714 | - if _new_parent._mock_new_name: |
1715 | - dot = '.' |
1716 | - if skip_next_dot: |
1717 | - dot = '' |
1718 | - |
1719 | - skip_next_dot = False |
1720 | - if _new_parent._mock_new_name == '()': |
1721 | - skip_next_dot = True |
1722 | - |
1723 | - _new_name = _new_parent._mock_new_name + dot + _new_name |
1724 | - |
1725 | - if do_method_calls: |
1726 | - if _new_name == name: |
1727 | - this_method_call = this_mock_call |
1728 | - else: |
1729 | - this_method_call = _Call((name, args, kwargs)) |
1730 | - _new_parent.method_calls.append(this_method_call) |
1731 | - |
1732 | - do_method_calls = _new_parent._mock_parent is not None |
1733 | - if do_method_calls: |
1734 | - name = _new_parent._mock_name + '.' + name |
1735 | - |
1736 | - _new_parent.mock_calls.append(this_mock_call) |
1737 | - _new_parent = _new_parent._mock_new_parent |
1738 | - |
1739 | - # use ids here so as not to call __hash__ on the mocks |
1740 | - _new_parent_id = id(_new_parent) |
1741 | - if _new_parent_id in seen: |
1742 | - break |
1743 | - seen.add(_new_parent_id) |
1744 | - |
1745 | - ret_val = DEFAULT |
1746 | - effect = self.side_effect |
1747 | - if effect is not None: |
1748 | - if _is_exception(effect): |
1749 | - raise effect |
1750 | - |
1751 | - if not _callable(effect): |
1752 | - result = next(effect) |
1753 | - if _is_exception(result): |
1754 | - raise result |
1755 | - return result |
1756 | - |
1757 | - ret_val = effect(*args, **kwargs) |
1758 | - if ret_val is DEFAULT: |
1759 | - ret_val = self.return_value |
1760 | - |
1761 | - if (self._mock_wraps is not None and |
1762 | - self._mock_return_value is DEFAULT): |
1763 | - return self._mock_wraps(*args, **kwargs) |
1764 | - if ret_val is DEFAULT: |
1765 | - ret_val = self.return_value |
1766 | - return ret_val |
1767 | - |
1768 | - |
1769 | - |
1770 | -class Mock(CallableMixin, NonCallableMock): |
1771 | - """ |
1772 | - Create a new `Mock` object. `Mock` takes several optional arguments |
1773 | - that specify the behaviour of the Mock object: |
1774 | - |
1775 | - * `spec`: This can be either a list of strings or an existing object (a |
1776 | - class or instance) that acts as the specification for the mock object. If |
1777 | - you pass in an object then a list of strings is formed by calling dir on |
1778 | - the object (excluding unsupported magic attributes and methods). Accessing |
1779 | - any attribute not in this list will raise an `AttributeError`. |
1780 | - |
1781 | - If `spec` is an object (rather than a list of strings) then |
1782 | - `mock.__class__` returns the class of the spec object. This allows mocks |
1783 | - to pass `isinstance` tests. |
1784 | - |
1785 | - * `spec_set`: A stricter variant of `spec`. If used, attempting to *set* |
1786 | - or get an attribute on the mock that isn't on the object passed as |
1787 | - `spec_set` will raise an `AttributeError`. |
1788 | - |
1789 | - * `side_effect`: A function to be called whenever the Mock is called. See |
1790 | - the `side_effect` attribute. Useful for raising exceptions or |
1791 | - dynamically changing return values. The function is called with the same |
1792 | - arguments as the mock, and unless it returns `DEFAULT`, the return |
1793 | - value of this function is used as the return value. |
1794 | - |
1795 | - Alternatively `side_effect` can be an exception class or instance. In |
1796 | - this case the exception will be raised when the mock is called. |
1797 | - |
1798 | - If `side_effect` is an iterable then each call to the mock will return |
1799 | - the next value from the iterable. If any of the members of the iterable |
1800 | - are exceptions they will be raised instead of returned. |
1801 | - |
1802 | - * `return_value`: The value returned when the mock is called. By default |
1803 | - this is a new Mock (created on first access). See the |
1804 | - `return_value` attribute. |
1805 | - |
1806 | - * `wraps`: Item for the mock object to wrap. If `wraps` is not None then |
1807 | - calling the Mock will pass the call through to the wrapped object |
1808 | - (returning the real result). Attribute access on the mock will return a |
1809 | - Mock object that wraps the corresponding attribute of the wrapped object |
1810 | - (so attempting to access an attribute that doesn't exist will raise an |
1811 | - `AttributeError`). |
1812 | - |
1813 | - If the mock has an explicit `return_value` set then calls are not passed |
1814 | - to the wrapped object and the `return_value` is returned instead. |
1815 | - |
1816 | - * `name`: If the mock has a name then it will be used in the repr of the |
1817 | - mock. This can be useful for debugging. The name is propagated to child |
1818 | - mocks. |
1819 | - |
1820 | - Mocks can also be called with arbitrary keyword arguments. These will be |
1821 | - used to set attributes on the mock after it is created. |
1822 | - """ |
1823 | - |
1824 | - |
1825 | - |
1826 | -def _dot_lookup(thing, comp, import_path): |
1827 | - try: |
1828 | - return getattr(thing, comp) |
1829 | - except AttributeError: |
1830 | - __import__(import_path) |
1831 | - return getattr(thing, comp) |
1832 | - |
1833 | - |
1834 | -def _importer(target): |
1835 | - components = target.split('.') |
1836 | - import_path = components.pop(0) |
1837 | - thing = __import__(import_path) |
1838 | - |
1839 | - for comp in components: |
1840 | - import_path += ".%s" % comp |
1841 | - thing = _dot_lookup(thing, comp, import_path) |
1842 | - return thing |
1843 | - |
1844 | - |
1845 | -def _is_started(patcher): |
1846 | - # XXXX horrible |
1847 | - return hasattr(patcher, 'is_local') |
1848 | - |
1849 | - |
1850 | -class _patch(object): |
1851 | - |
1852 | - attribute_name = None |
1853 | - _active_patches = set() |
1854 | - |
1855 | - def __init__( |
1856 | - self, getter, attribute, new, spec, create, |
1857 | - spec_set, autospec, new_callable, kwargs |
1858 | - ): |
1859 | - if new_callable is not None: |
1860 | - if new is not DEFAULT: |
1861 | - raise ValueError( |
1862 | - "Cannot use 'new' and 'new_callable' together" |
1863 | - ) |
1864 | - if autospec is not None: |
1865 | - raise ValueError( |
1866 | - "Cannot use 'autospec' and 'new_callable' together" |
1867 | - ) |
1868 | - |
1869 | - self.getter = getter |
1870 | - self.attribute = attribute |
1871 | - self.new = new |
1872 | - self.new_callable = new_callable |
1873 | - self.spec = spec |
1874 | - self.create = create |
1875 | - self.has_local = False |
1876 | - self.spec_set = spec_set |
1877 | - self.autospec = autospec |
1878 | - self.kwargs = kwargs |
1879 | - self.additional_patchers = [] |
1880 | - |
1881 | - |
1882 | - def copy(self): |
1883 | - patcher = _patch( |
1884 | - self.getter, self.attribute, self.new, self.spec, |
1885 | - self.create, self.spec_set, |
1886 | - self.autospec, self.new_callable, self.kwargs |
1887 | - ) |
1888 | - patcher.attribute_name = self.attribute_name |
1889 | - patcher.additional_patchers = [ |
1890 | - p.copy() for p in self.additional_patchers |
1891 | - ] |
1892 | - return patcher |
1893 | - |
1894 | - |
1895 | - def __call__(self, func): |
1896 | - if isinstance(func, ClassTypes): |
1897 | - return self.decorate_class(func) |
1898 | - return self.decorate_callable(func) |
1899 | - |
1900 | - |
1901 | - def decorate_class(self, klass): |
1902 | - for attr in dir(klass): |
1903 | - if not attr.startswith(patch.TEST_PREFIX): |
1904 | - continue |
1905 | - |
1906 | - attr_value = getattr(klass, attr) |
1907 | - if not hasattr(attr_value, "__call__"): |
1908 | - continue |
1909 | - |
1910 | - patcher = self.copy() |
1911 | - setattr(klass, attr, patcher(attr_value)) |
1912 | - return klass |
1913 | - |
1914 | - |
1915 | - def decorate_callable(self, func): |
1916 | - if hasattr(func, 'patchings'): |
1917 | - func.patchings.append(self) |
1918 | - return func |
1919 | - |
1920 | - @wraps(func) |
1921 | - def patched(*args, **keywargs): |
1922 | - # don't use a with here (backwards compatability with Python 2.4) |
1923 | - extra_args = [] |
1924 | - entered_patchers = [] |
1925 | - |
1926 | - # can't use try...except...finally because of Python 2.4 |
1927 | - # compatibility |
1928 | - exc_info = tuple() |
1929 | - try: |
1930 | - try: |
1931 | - for patching in patched.patchings: |
1932 | - arg = patching.__enter__() |
1933 | - entered_patchers.append(patching) |
1934 | - if patching.attribute_name is not None: |
1935 | - keywargs.update(arg) |
1936 | - elif patching.new is DEFAULT: |
1937 | - extra_args.append(arg) |
1938 | - |
1939 | - args += tuple(extra_args) |
1940 | - return func(*args, **keywargs) |
1941 | - except: |
1942 | - if (patching not in entered_patchers and |
1943 | - _is_started(patching)): |
1944 | - # the patcher may have been started, but an exception |
1945 | - # raised whilst entering one of its additional_patchers |
1946 | - entered_patchers.append(patching) |
1947 | - # Pass the exception to __exit__ |
1948 | - exc_info = sys.exc_info() |
1949 | - # re-raise the exception |
1950 | - raise |
1951 | - finally: |
1952 | - for patching in reversed(entered_patchers): |
1953 | - patching.__exit__(*exc_info) |
1954 | - |
1955 | - patched.patchings = [self] |
1956 | - if hasattr(func, 'func_code'): |
1957 | - # not in Python 3 |
1958 | - patched.compat_co_firstlineno = getattr( |
1959 | - func, "compat_co_firstlineno", |
1960 | - func.func_code.co_firstlineno |
1961 | - ) |
1962 | - return patched |
1963 | - |
1964 | - |
1965 | - def get_original(self): |
1966 | - target = self.getter() |
1967 | - name = self.attribute |
1968 | - |
1969 | - original = DEFAULT |
1970 | - local = False |
1971 | - |
1972 | - try: |
1973 | - original = target.__dict__[name] |
1974 | - except (AttributeError, KeyError): |
1975 | - original = getattr(target, name, DEFAULT) |
1976 | - else: |
1977 | - local = True |
1978 | - |
1979 | - if not self.create and original is DEFAULT: |
1980 | - raise AttributeError( |
1981 | - "%s does not have the attribute %r" % (target, name) |
1982 | - ) |
1983 | - return original, local |
1984 | - |
1985 | - |
1986 | - def __enter__(self): |
1987 | - """Perform the patch.""" |
1988 | - new, spec, spec_set = self.new, self.spec, self.spec_set |
1989 | - autospec, kwargs = self.autospec, self.kwargs |
1990 | - new_callable = self.new_callable |
1991 | - self.target = self.getter() |
1992 | - |
1993 | - # normalise False to None |
1994 | - if spec is False: |
1995 | - spec = None |
1996 | - if spec_set is False: |
1997 | - spec_set = None |
1998 | - if autospec is False: |
1999 | - autospec = None |
2000 | - |
2001 | - if spec is not None and autospec is not None: |
2002 | - raise TypeError("Can't specify spec and autospec") |
2003 | - if ((spec is not None or autospec is not None) and |
2004 | - spec_set not in (True, None)): |
2005 | - raise TypeError("Can't provide explicit spec_set *and* spec or autospec") |
2006 | - |
2007 | - original, local = self.get_original() |
2008 | - |
2009 | - if new is DEFAULT and autospec is None: |
2010 | - inherit = False |
2011 | - if spec is True: |
2012 | - # set spec to the object we are replacing |
2013 | - spec = original |
2014 | - if spec_set is True: |
2015 | - spec_set = original |
2016 | - spec = None |
2017 | - elif spec is not None: |
2018 | - if spec_set is True: |
2019 | - spec_set = spec |
2020 | - spec = None |
2021 | - elif spec_set is True: |
2022 | - spec_set = original |
2023 | - |
2024 | - if spec is not None or spec_set is not None: |
2025 | - if original is DEFAULT: |
2026 | - raise TypeError("Can't use 'spec' with create=True") |
2027 | - if isinstance(original, ClassTypes): |
2028 | - # If we're patching out a class and there is a spec |
2029 | - inherit = True |
2030 | - |
2031 | - Klass = MagicMock |
2032 | - _kwargs = {} |
2033 | - if new_callable is not None: |
2034 | - Klass = new_callable |
2035 | - elif spec is not None or spec_set is not None: |
2036 | - this_spec = spec |
2037 | - if spec_set is not None: |
2038 | - this_spec = spec_set |
2039 | - if _is_list(this_spec): |
2040 | - not_callable = '__call__' not in this_spec |
2041 | - else: |
2042 | - not_callable = not _callable(this_spec) |
2043 | - if not_callable: |
2044 | - Klass = NonCallableMagicMock |
2045 | - |
2046 | - if spec is not None: |
2047 | - _kwargs['spec'] = spec |
2048 | - if spec_set is not None: |
2049 | - _kwargs['spec_set'] = spec_set |
2050 | - |
2051 | - # add a name to mocks |
2052 | - if (isinstance(Klass, type) and |
2053 | - issubclass(Klass, NonCallableMock) and self.attribute): |
2054 | - _kwargs['name'] = self.attribute |
2055 | - |
2056 | - _kwargs.update(kwargs) |
2057 | - new = Klass(**_kwargs) |
2058 | - |
2059 | - if inherit and _is_instance_mock(new): |
2060 | - # we can only tell if the instance should be callable if the |
2061 | - # spec is not a list |
2062 | - this_spec = spec |
2063 | - if spec_set is not None: |
2064 | - this_spec = spec_set |
2065 | - if (not _is_list(this_spec) and not |
2066 | - _instance_callable(this_spec)): |
2067 | - Klass = NonCallableMagicMock |
2068 | - |
2069 | - _kwargs.pop('name') |
2070 | - new.return_value = Klass(_new_parent=new, _new_name='()', |
2071 | - **_kwargs) |
2072 | - elif autospec is not None: |
2073 | - # spec is ignored, new *must* be default, spec_set is treated |
2074 | - # as a boolean. Should we check spec is not None and that spec_set |
2075 | - # is a bool? |
2076 | - if new is not DEFAULT: |
2077 | - raise TypeError( |
2078 | - "autospec creates the mock for you. Can't specify " |
2079 | - "autospec and new." |
2080 | - ) |
2081 | - if original is DEFAULT: |
2082 | - raise TypeError("Can't use 'autospec' with create=True") |
2083 | - spec_set = bool(spec_set) |
2084 | - if autospec is True: |
2085 | - autospec = original |
2086 | - |
2087 | - new = create_autospec(autospec, spec_set=spec_set, |
2088 | - _name=self.attribute, **kwargs) |
2089 | - elif kwargs: |
2090 | - # can't set keyword args when we aren't creating the mock |
2091 | - # XXXX If new is a Mock we could call new.configure_mock(**kwargs) |
2092 | - raise TypeError("Can't pass kwargs to a mock we aren't creating") |
2093 | - |
2094 | - new_attr = new |
2095 | - |
2096 | - self.temp_original = original |
2097 | - self.is_local = local |
2098 | - setattr(self.target, self.attribute, new_attr) |
2099 | - if self.attribute_name is not None: |
2100 | - extra_args = {} |
2101 | - if self.new is DEFAULT: |
2102 | - extra_args[self.attribute_name] = new |
2103 | - for patching in self.additional_patchers: |
2104 | - arg = patching.__enter__() |
2105 | - if patching.new is DEFAULT: |
2106 | - extra_args.update(arg) |
2107 | - return extra_args |
2108 | - |
2109 | - return new |
2110 | - |
2111 | - |
2112 | - def __exit__(self, *exc_info): |
2113 | - """Undo the patch.""" |
2114 | - if not _is_started(self): |
2115 | - raise RuntimeError('stop called on unstarted patcher') |
2116 | - |
2117 | - if self.is_local and self.temp_original is not DEFAULT: |
2118 | - setattr(self.target, self.attribute, self.temp_original) |
2119 | - else: |
2120 | - delattr(self.target, self.attribute) |
2121 | - if not self.create and not hasattr(self.target, self.attribute): |
2122 | - # needed for proxy objects like django settings |
2123 | - setattr(self.target, self.attribute, self.temp_original) |
2124 | - |
2125 | - del self.temp_original |
2126 | - del self.is_local |
2127 | - del self.target |
2128 | - for patcher in reversed(self.additional_patchers): |
2129 | - if _is_started(patcher): |
2130 | - patcher.__exit__(*exc_info) |
2131 | - |
2132 | - |
2133 | - def start(self): |
2134 | - """Activate a patch, returning any created mock.""" |
2135 | - result = self.__enter__() |
2136 | - self._active_patches.add(self) |
2137 | - return result |
2138 | - |
2139 | - |
2140 | - def stop(self): |
2141 | - """Stop an active patch.""" |
2142 | - self._active_patches.discard(self) |
2143 | - return self.__exit__() |
2144 | - |
2145 | - |
2146 | - |
2147 | -def _get_target(target): |
2148 | - try: |
2149 | - target, attribute = target.rsplit('.', 1) |
2150 | - except (TypeError, ValueError): |
2151 | - raise TypeError("Need a valid target to patch. You supplied: %r" % |
2152 | - (target,)) |
2153 | - getter = lambda: _importer(target) |
2154 | - return getter, attribute |
2155 | - |
2156 | - |
2157 | -def _patch_object( |
2158 | - target, attribute, new=DEFAULT, spec=None, |
2159 | - create=False, spec_set=None, autospec=None, |
2160 | - new_callable=None, **kwargs |
2161 | - ): |
2162 | - """ |
2163 | - patch.object(target, attribute, new=DEFAULT, spec=None, create=False, |
2164 | - spec_set=None, autospec=None, new_callable=None, **kwargs) |
2165 | - |
2166 | - patch the named member (`attribute`) on an object (`target`) with a mock |
2167 | - object. |
2168 | - |
2169 | - `patch.object` can be used as a decorator, class decorator or a context |
2170 | - manager. Arguments `new`, `spec`, `create`, `spec_set`, |
2171 | - `autospec` and `new_callable` have the same meaning as for `patch`. Like |
2172 | - `patch`, `patch.object` takes arbitrary keyword arguments for configuring |
2173 | - the mock object it creates. |
2174 | - |
2175 | - When used as a class decorator `patch.object` honours `patch.TEST_PREFIX` |
2176 | - for choosing which methods to wrap. |
2177 | - """ |
2178 | - getter = lambda: target |
2179 | - return _patch( |
2180 | - getter, attribute, new, spec, create, |
2181 | - spec_set, autospec, new_callable, kwargs |
2182 | - ) |
2183 | - |
2184 | - |
2185 | -def _patch_multiple(target, spec=None, create=False, spec_set=None, |
2186 | - autospec=None, new_callable=None, **kwargs): |
2187 | - """Perform multiple patches in a single call. It takes the object to be |
2188 | - patched (either as an object or a string to fetch the object by importing) |
2189 | - and keyword arguments for the patches:: |
2190 | - |
2191 | - with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'): |
2192 | - ... |
2193 | - |
2194 | - Use `DEFAULT` as the value if you want `patch.multiple` to create |
2195 | - mocks for you. In this case the created mocks are passed into a decorated |
2196 | - function by keyword, and a dictionary is returned when `patch.multiple` is |
2197 | - used as a context manager. |
2198 | - |
2199 | - `patch.multiple` can be used as a decorator, class decorator or a context |
2200 | - manager. The arguments `spec`, `spec_set`, `create`, |
2201 | - `autospec` and `new_callable` have the same meaning as for `patch`. These |
2202 | - arguments will be applied to *all* patches done by `patch.multiple`. |
2203 | - |
2204 | - When used as a class decorator `patch.multiple` honours `patch.TEST_PREFIX` |
2205 | - for choosing which methods to wrap. |
2206 | - """ |
2207 | - if type(target) in (unicode, str): |
2208 | - getter = lambda: _importer(target) |
2209 | - else: |
2210 | - getter = lambda: target |
2211 | - |
2212 | - if not kwargs: |
2213 | - raise ValueError( |
2214 | - 'Must supply at least one keyword argument with patch.multiple' |
2215 | - ) |
2216 | - # need to wrap in a list for python 3, where items is a view |
2217 | - items = list(kwargs.items()) |
2218 | - attribute, new = items[0] |
2219 | - patcher = _patch( |
2220 | - getter, attribute, new, spec, create, spec_set, |
2221 | - autospec, new_callable, {} |
2222 | - ) |
2223 | - patcher.attribute_name = attribute |
2224 | - for attribute, new in items[1:]: |
2225 | - this_patcher = _patch( |
2226 | - getter, attribute, new, spec, create, spec_set, |
2227 | - autospec, new_callable, {} |
2228 | - ) |
2229 | - this_patcher.attribute_name = attribute |
2230 | - patcher.additional_patchers.append(this_patcher) |
2231 | - return patcher |
2232 | - |
2233 | - |
2234 | -def patch( |
2235 | - target, new=DEFAULT, spec=None, create=False, |
2236 | - spec_set=None, autospec=None, new_callable=None, **kwargs |
2237 | - ): |
2238 | - """ |
2239 | - `patch` acts as a function decorator, class decorator or a context |
2240 | - manager. Inside the body of the function or with statement, the `target` |
2241 | - is patched with a `new` object. When the function/with statement exits |
2242 | - the patch is undone. |
2243 | - |
2244 | - If `new` is omitted, then the target is replaced with a |
2245 | - `MagicMock`. If `patch` is used as a decorator and `new` is |
2246 | - omitted, the created mock is passed in as an extra argument to the |
2247 | - decorated function. If `patch` is used as a context manager the created |
2248 | - mock is returned by the context manager. |
2249 | - |
2250 | - `target` should be a string in the form `'package.module.ClassName'`. The |
2251 | - `target` is imported and the specified object replaced with the `new` |
2252 | - object, so the `target` must be importable from the environment you are |
2253 | - calling `patch` from. The target is imported when the decorated function |
2254 | - is executed, not at decoration time. |
2255 | - |
2256 | - The `spec` and `spec_set` keyword arguments are passed to the `MagicMock` |
2257 | - if patch is creating one for you. |
2258 | - |
2259 | - In addition you can pass `spec=True` or `spec_set=True`, which causes |
2260 | - patch to pass in the object being mocked as the spec/spec_set object. |
2261 | - |
2262 | - `new_callable` allows you to specify a different class, or callable object, |
2263 | - that will be called to create the `new` object. By default `MagicMock` is |
2264 | - used. |
2265 | - |
2266 | - A more powerful form of `spec` is `autospec`. If you set `autospec=True` |
2267 | - then the mock with be created with a spec from the object being replaced. |
2268 | - All attributes of the mock will also have the spec of the corresponding |
2269 | - attribute of the object being replaced. Methods and functions being |
2270 | - mocked will have their arguments checked and will raise a `TypeError` if |
2271 | - they are called with the wrong signature. For mocks replacing a class, |
2272 | - their return value (the 'instance') will have the same spec as the class. |
2273 | - |
2274 | - Instead of `autospec=True` you can pass `autospec=some_object` to use an |
2275 | - arbitrary object as the spec instead of the one being replaced. |
2276 | - |
2277 | - By default `patch` will fail to replace attributes that don't exist. If |
2278 | - you pass in `create=True`, and the attribute doesn't exist, patch will |
2279 | - create the attribute for you when the patched function is called, and |
2280 | - delete it again afterwards. This is useful for writing tests against |
2281 | - attributes that your production code creates at runtime. It is off by by |
2282 | - default because it can be dangerous. With it switched on you can write |
2283 | - passing tests against APIs that don't actually exist! |
2284 | - |
2285 | - Patch can be used as a `TestCase` class decorator. It works by |
2286 | - decorating each test method in the class. This reduces the boilerplate |
2287 | - code when your test methods share a common patchings set. `patch` finds |
2288 | - tests by looking for method names that start with `patch.TEST_PREFIX`. |
2289 | - By default this is `test`, which matches the way `unittest` finds tests. |
2290 | - You can specify an alternative prefix by setting `patch.TEST_PREFIX`. |
2291 | - |
2292 | - Patch can be used as a context manager, with the with statement. Here the |
2293 | - patching applies to the indented block after the with statement. If you |
2294 | - use "as" then the patched object will be bound to the name after the |
2295 | - "as"; very useful if `patch` is creating a mock object for you. |
2296 | - |
2297 | - `patch` takes arbitrary keyword arguments. These will be passed to |
2298 | - the `Mock` (or `new_callable`) on construction. |
2299 | - |
2300 | - `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are |
2301 | - available for alternate use-cases. |
2302 | - """ |
2303 | - getter, attribute = _get_target(target) |
2304 | - return _patch( |
2305 | - getter, attribute, new, spec, create, |
2306 | - spec_set, autospec, new_callable, kwargs |
2307 | - ) |
2308 | - |
2309 | - |
2310 | -class _patch_dict(object): |
2311 | - """ |
2312 | - Patch a dictionary, or dictionary like object, and restore the dictionary |
2313 | - to its original state after the test. |
2314 | - |
2315 | - `in_dict` can be a dictionary or a mapping like container. If it is a |
2316 | - mapping then it must at least support getting, setting and deleting items |
2317 | - plus iterating over keys. |
2318 | - |
2319 | - `in_dict` can also be a string specifying the name of the dictionary, which |
2320 | - will then be fetched by importing it. |
2321 | - |
2322 | - `values` can be a dictionary of values to set in the dictionary. `values` |
2323 | - can also be an iterable of `(key, value)` pairs. |
2324 | - |
2325 | - If `clear` is True then the dictionary will be cleared before the new |
2326 | - values are set. |
2327 | - |
2328 | - `patch.dict` can also be called with arbitrary keyword arguments to set |
2329 | - values in the dictionary:: |
2330 | - |
2331 | - with patch.dict('sys.modules', mymodule=Mock(), other_module=Mock()): |
2332 | - ... |
2333 | - |
2334 | - `patch.dict` can be used as a context manager, decorator or class |
2335 | - decorator. When used as a class decorator `patch.dict` honours |
2336 | - `patch.TEST_PREFIX` for choosing which methods to wrap. |
2337 | - """ |
2338 | - |
2339 | - def __init__(self, in_dict, values=(), clear=False, **kwargs): |
2340 | - if isinstance(in_dict, basestring): |
2341 | - in_dict = _importer(in_dict) |
2342 | - self.in_dict = in_dict |
2343 | - # support any argument supported by dict(...) constructor |
2344 | - self.values = dict(values) |
2345 | - self.values.update(kwargs) |
2346 | - self.clear = clear |
2347 | - self._original = None |
2348 | - |
2349 | - |
2350 | - def __call__(self, f): |
2351 | - if isinstance(f, ClassTypes): |
2352 | - return self.decorate_class(f) |
2353 | - @wraps(f) |
2354 | - def _inner(*args, **kw): |
2355 | - self._patch_dict() |
2356 | - try: |
2357 | - return f(*args, **kw) |
2358 | - finally: |
2359 | - self._unpatch_dict() |
2360 | - |
2361 | - return _inner |
2362 | - |
2363 | - |
2364 | - def decorate_class(self, klass): |
2365 | - for attr in dir(klass): |
2366 | - attr_value = getattr(klass, attr) |
2367 | - if (attr.startswith(patch.TEST_PREFIX) and |
2368 | - hasattr(attr_value, "__call__")): |
2369 | - decorator = _patch_dict(self.in_dict, self.values, self.clear) |
2370 | - decorated = decorator(attr_value) |
2371 | - setattr(klass, attr, decorated) |
2372 | - return klass |
2373 | - |
2374 | - |
2375 | - def __enter__(self): |
2376 | - """Patch the dict.""" |
2377 | - self._patch_dict() |
2378 | - |
2379 | - |
2380 | - def _patch_dict(self): |
2381 | - values = self.values |
2382 | - in_dict = self.in_dict |
2383 | - clear = self.clear |
2384 | - |
2385 | - try: |
2386 | - original = in_dict.copy() |
2387 | - except AttributeError: |
2388 | - # dict like object with no copy method |
2389 | - # must support iteration over keys |
2390 | - original = {} |
2391 | - for key in in_dict: |
2392 | - original[key] = in_dict[key] |
2393 | - self._original = original |
2394 | - |
2395 | - if clear: |
2396 | - _clear_dict(in_dict) |
2397 | - |
2398 | - try: |
2399 | - in_dict.update(values) |
2400 | - except AttributeError: |
2401 | - # dict like object with no update method |
2402 | - for key in values: |
2403 | - in_dict[key] = values[key] |
2404 | - |
2405 | - |
2406 | - def _unpatch_dict(self): |
2407 | - in_dict = self.in_dict |
2408 | - original = self._original |
2409 | - |
2410 | - _clear_dict(in_dict) |
2411 | - |
2412 | - try: |
2413 | - in_dict.update(original) |
2414 | - except AttributeError: |
2415 | - for key in original: |
2416 | - in_dict[key] = original[key] |
2417 | - |
2418 | - |
2419 | - def __exit__(self, *args): |
2420 | - """Unpatch the dict.""" |
2421 | - self._unpatch_dict() |
2422 | - return False |
2423 | - |
2424 | - start = __enter__ |
2425 | - stop = __exit__ |
2426 | - |
2427 | - |
2428 | -def _clear_dict(in_dict): |
2429 | - try: |
2430 | - in_dict.clear() |
2431 | - except AttributeError: |
2432 | - keys = list(in_dict) |
2433 | - for key in keys: |
2434 | - del in_dict[key] |
2435 | - |
2436 | - |
2437 | -def _patch_stopall(): |
2438 | - """Stop all active patches.""" |
2439 | - for patch in list(_patch._active_patches): |
2440 | - patch.stop() |
2441 | - |
2442 | - |
2443 | -patch.object = _patch_object |
2444 | -patch.dict = _patch_dict |
2445 | -patch.multiple = _patch_multiple |
2446 | -patch.stopall = _patch_stopall |
2447 | -patch.TEST_PREFIX = 'test' |
2448 | - |
2449 | -magic_methods = ( |
2450 | - "lt le gt ge eq ne " |
2451 | - "getitem setitem delitem " |
2452 | - "len contains iter " |
2453 | - "hash str sizeof " |
2454 | - "enter exit " |
2455 | - "divmod neg pos abs invert " |
2456 | - "complex int float index " |
2457 | - "trunc floor ceil " |
2458 | -) |
2459 | - |
2460 | -numerics = "add sub mul div floordiv mod lshift rshift and xor or pow " |
2461 | -inplace = ' '.join('i%s' % n for n in numerics.split()) |
2462 | -right = ' '.join('r%s' % n for n in numerics.split()) |
2463 | -extra = '' |
2464 | -if inPy3k: |
2465 | - extra = 'bool next ' |
2466 | -else: |
2467 | - extra = 'unicode long nonzero oct hex truediv rtruediv ' |
2468 | - |
2469 | -# not including __prepare__, __instancecheck__, __subclasscheck__ |
2470 | -# (as they are metaclass methods) |
2471 | -# __del__ is not supported at all as it causes problems if it exists |
2472 | - |
2473 | -_non_defaults = set('__%s__' % method for method in [ |
2474 | - 'cmp', 'getslice', 'setslice', 'coerce', 'subclasses', |
2475 | - 'format', 'get', 'set', 'delete', 'reversed', |
2476 | - 'missing', 'reduce', 'reduce_ex', 'getinitargs', |
2477 | - 'getnewargs', 'getstate', 'setstate', 'getformat', |
2478 | - 'setformat', 'repr', 'dir' |
2479 | -]) |
2480 | - |
2481 | - |
2482 | -def _get_method(name, func): |
2483 | - "Turns a callable object (like a mock) into a real function" |
2484 | - def method(self, *args, **kw): |
2485 | - return func(self, *args, **kw) |
2486 | - method.__name__ = name |
2487 | - return method |
2488 | - |
2489 | - |
2490 | -_magics = set( |
2491 | - '__%s__' % method for method in |
2492 | - ' '.join([magic_methods, numerics, inplace, right, extra]).split() |
2493 | -) |
2494 | - |
2495 | -_all_magics = _magics | _non_defaults |
2496 | - |
2497 | -_unsupported_magics = set([ |
2498 | - '__getattr__', '__setattr__', |
2499 | - '__init__', '__new__', '__prepare__' |
2500 | - '__instancecheck__', '__subclasscheck__', |
2501 | - '__del__' |
2502 | -]) |
2503 | - |
2504 | -_calculate_return_value = { |
2505 | - '__hash__': lambda self: object.__hash__(self), |
2506 | - '__str__': lambda self: object.__str__(self), |
2507 | - '__sizeof__': lambda self: object.__sizeof__(self), |
2508 | - '__unicode__': lambda self: unicode(object.__str__(self)), |
2509 | -} |
2510 | - |
2511 | -_return_values = { |
2512 | - '__lt__': NotImplemented, |
2513 | - '__gt__': NotImplemented, |
2514 | - '__le__': NotImplemented, |
2515 | - '__ge__': NotImplemented, |
2516 | - '__int__': 1, |
2517 | - '__contains__': False, |
2518 | - '__len__': 0, |
2519 | - '__exit__': False, |
2520 | - '__complex__': 1j, |
2521 | - '__float__': 1.0, |
2522 | - '__bool__': True, |
2523 | - '__nonzero__': True, |
2524 | - '__oct__': '1', |
2525 | - '__hex__': '0x1', |
2526 | - '__long__': long(1), |
2527 | - '__index__': 1, |
2528 | -} |
2529 | - |
2530 | - |
2531 | -def _get_eq(self): |
2532 | - def __eq__(other): |
2533 | - ret_val = self.__eq__._mock_return_value |
2534 | - if ret_val is not DEFAULT: |
2535 | - return ret_val |
2536 | - return self is other |
2537 | - return __eq__ |
2538 | - |
2539 | -def _get_ne(self): |
2540 | - def __ne__(other): |
2541 | - if self.__ne__._mock_return_value is not DEFAULT: |
2542 | - return DEFAULT |
2543 | - return self is not other |
2544 | - return __ne__ |
2545 | - |
2546 | -def _get_iter(self): |
2547 | - def __iter__(): |
2548 | - ret_val = self.__iter__._mock_return_value |
2549 | - if ret_val is DEFAULT: |
2550 | - return iter([]) |
2551 | - # if ret_val was already an iterator, then calling iter on it should |
2552 | - # return the iterator unchanged |
2553 | - return iter(ret_val) |
2554 | - return __iter__ |
2555 | - |
2556 | -_side_effect_methods = { |
2557 | - '__eq__': _get_eq, |
2558 | - '__ne__': _get_ne, |
2559 | - '__iter__': _get_iter, |
2560 | -} |
2561 | - |
2562 | - |
2563 | - |
2564 | -def _set_return_value(mock, method, name): |
2565 | - fixed = _return_values.get(name, DEFAULT) |
2566 | - if fixed is not DEFAULT: |
2567 | - method.return_value = fixed |
2568 | - return |
2569 | - |
2570 | - return_calulator = _calculate_return_value.get(name) |
2571 | - if return_calulator is not None: |
2572 | - try: |
2573 | - return_value = return_calulator(mock) |
2574 | - except AttributeError: |
2575 | - # XXXX why do we return AttributeError here? |
2576 | - # set it as a side_effect instead? |
2577 | - return_value = AttributeError(name) |
2578 | - method.return_value = return_value |
2579 | - return |
2580 | - |
2581 | - side_effector = _side_effect_methods.get(name) |
2582 | - if side_effector is not None: |
2583 | - method.side_effect = side_effector(mock) |
2584 | - |
2585 | - |
2586 | - |
2587 | -class MagicMixin(object): |
2588 | - def __init__(self, *args, **kw): |
2589 | - _super(MagicMixin, self).__init__(*args, **kw) |
2590 | - self._mock_set_magics() |
2591 | - |
2592 | - |
2593 | - def _mock_set_magics(self): |
2594 | - these_magics = _magics |
2595 | - |
2596 | - if self._mock_methods is not None: |
2597 | - these_magics = _magics.intersection(self._mock_methods) |
2598 | - |
2599 | - remove_magics = set() |
2600 | - remove_magics = _magics - these_magics |
2601 | - |
2602 | - for entry in remove_magics: |
2603 | - if entry in type(self).__dict__: |
2604 | - # remove unneeded magic methods |
2605 | - delattr(self, entry) |
2606 | - |
2607 | - # don't overwrite existing attributes if called a second time |
2608 | - these_magics = these_magics - set(type(self).__dict__) |
2609 | - |
2610 | - _type = type(self) |
2611 | - for entry in these_magics: |
2612 | - setattr(_type, entry, MagicProxy(entry, self)) |
2613 | - |
2614 | - |
2615 | - |
2616 | -class NonCallableMagicMock(MagicMixin, NonCallableMock): |
2617 | - """A version of `MagicMock` that isn't callable.""" |
2618 | - def mock_add_spec(self, spec, spec_set=False): |
2619 | - """Add a spec to a mock. `spec` can either be an object or a |
2620 | - list of strings. Only attributes on the `spec` can be fetched as |
2621 | - attributes from the mock. |
2622 | - |
2623 | - If `spec_set` is True then only attributes on the spec can be set.""" |
2624 | - self._mock_add_spec(spec, spec_set) |
2625 | - self._mock_set_magics() |
2626 | - |
2627 | - |
2628 | - |
2629 | -class MagicMock(MagicMixin, Mock): |
2630 | - """ |
2631 | - MagicMock is a subclass of Mock with default implementations |
2632 | - of most of the magic methods. You can use MagicMock without having to |
2633 | - configure the magic methods yourself. |
2634 | - |
2635 | - If you use the `spec` or `spec_set` arguments then *only* magic |
2636 | - methods that exist in the spec will be created. |
2637 | - |
2638 | - Attributes and the return value of a `MagicMock` will also be `MagicMocks`. |
2639 | - """ |
2640 | - def mock_add_spec(self, spec, spec_set=False): |
2641 | - """Add a spec to a mock. `spec` can either be an object or a |
2642 | - list of strings. Only attributes on the `spec` can be fetched as |
2643 | - attributes from the mock. |
2644 | - |
2645 | - If `spec_set` is True then only attributes on the spec can be set.""" |
2646 | - self._mock_add_spec(spec, spec_set) |
2647 | - self._mock_set_magics() |
2648 | - |
2649 | - |
2650 | - |
2651 | -class MagicProxy(object): |
2652 | - def __init__(self, name, parent): |
2653 | - self.name = name |
2654 | - self.parent = parent |
2655 | - |
2656 | - def __call__(self, *args, **kwargs): |
2657 | - m = self.create_mock() |
2658 | - return m(*args, **kwargs) |
2659 | - |
2660 | - def create_mock(self): |
2661 | - entry = self.name |
2662 | - parent = self.parent |
2663 | - m = parent._get_child_mock(name=entry, _new_name=entry, |
2664 | - _new_parent=parent) |
2665 | - setattr(parent, entry, m) |
2666 | - _set_return_value(parent, m, entry) |
2667 | - return m |
2668 | - |
2669 | - def __get__(self, obj, _type=None): |
2670 | - return self.create_mock() |
2671 | - |
2672 | - |
2673 | - |
2674 | -class _ANY(object): |
2675 | - "A helper object that compares equal to everything." |
2676 | - |
2677 | - def __eq__(self, other): |
2678 | - return True |
2679 | - |
2680 | - def __ne__(self, other): |
2681 | - return False |
2682 | - |
2683 | - def __repr__(self): |
2684 | - return '<ANY>' |
2685 | - |
2686 | -ANY = _ANY() |
2687 | - |
2688 | - |
2689 | - |
2690 | -def _format_call_signature(name, args, kwargs): |
2691 | - message = '%s(%%s)' % name |
2692 | - formatted_args = '' |
2693 | - args_string = ', '.join([repr(arg) for arg in args]) |
2694 | - kwargs_string = ', '.join([ |
2695 | - '%s=%r' % (key, value) for key, value in kwargs.items() |
2696 | - ]) |
2697 | - if args_string: |
2698 | - formatted_args = args_string |
2699 | - if kwargs_string: |
2700 | - if formatted_args: |
2701 | - formatted_args += ', ' |
2702 | - formatted_args += kwargs_string |
2703 | - |
2704 | - return message % formatted_args |
2705 | - |
2706 | - |
2707 | - |
2708 | -class _Call(tuple): |
2709 | - """ |
2710 | - A tuple for holding the results of a call to a mock, either in the form |
2711 | - `(args, kwargs)` or `(name, args, kwargs)`. |
2712 | - |
2713 | - If args or kwargs are empty then a call tuple will compare equal to |
2714 | - a tuple without those values. This makes comparisons less verbose:: |
2715 | - |
2716 | - _Call(('name', (), {})) == ('name',) |
2717 | - _Call(('name', (1,), {})) == ('name', (1,)) |
2718 | - _Call(((), {'a': 'b'})) == ({'a': 'b'},) |
2719 | - |
2720 | - The `_Call` object provides a useful shortcut for comparing with call:: |
2721 | - |
2722 | - _Call(((1, 2), {'a': 3})) == call(1, 2, a=3) |
2723 | - _Call(('foo', (1, 2), {'a': 3})) == call.foo(1, 2, a=3) |
2724 | - |
2725 | - If the _Call has no name then it will match any name. |
2726 | - """ |
2727 | - def __new__(cls, value=(), name=None, parent=None, two=False, |
2728 | - from_kall=True): |
2729 | - name = '' |
2730 | - args = () |
2731 | - kwargs = {} |
2732 | - _len = len(value) |
2733 | - if _len == 3: |
2734 | - name, args, kwargs = value |
2735 | - elif _len == 2: |
2736 | - first, second = value |
2737 | - if isinstance(first, basestring): |
2738 | - name = first |
2739 | - if isinstance(second, tuple): |
2740 | - args = second |
2741 | - else: |
2742 | - kwargs = second |
2743 | - else: |
2744 | - args, kwargs = first, second |
2745 | - elif _len == 1: |
2746 | - value, = value |
2747 | - if isinstance(value, basestring): |
2748 | - name = value |
2749 | - elif isinstance(value, tuple): |
2750 | - args = value |
2751 | - else: |
2752 | - kwargs = value |
2753 | - |
2754 | - if two: |
2755 | - return tuple.__new__(cls, (args, kwargs)) |
2756 | - |
2757 | - return tuple.__new__(cls, (name, args, kwargs)) |
2758 | - |
2759 | - |
2760 | - def __init__(self, value=(), name=None, parent=None, two=False, |
2761 | - from_kall=True): |
2762 | - self.name = name |
2763 | - self.parent = parent |
2764 | - self.from_kall = from_kall |
2765 | - |
2766 | - |
2767 | - def __eq__(self, other): |
2768 | - if other is ANY: |
2769 | - return True |
2770 | - try: |
2771 | - len_other = len(other) |
2772 | - except TypeError: |
2773 | - return False |
2774 | - |
2775 | - self_name = '' |
2776 | - if len(self) == 2: |
2777 | - self_args, self_kwargs = self |
2778 | - else: |
2779 | - self_name, self_args, self_kwargs = self |
2780 | - |
2781 | - other_name = '' |
2782 | - if len_other == 0: |
2783 | - other_args, other_kwargs = (), {} |
2784 | - elif len_other == 3: |
2785 | - other_name, other_args, other_kwargs = other |
2786 | - elif len_other == 1: |
2787 | - value, = other |
2788 | - if isinstance(value, tuple): |
2789 | - other_args = value |
2790 | - other_kwargs = {} |
2791 | - elif isinstance(value, basestring): |
2792 | - other_name = value |
2793 | - other_args, other_kwargs = (), {} |
2794 | - else: |
2795 | - other_args = () |
2796 | - other_kwargs = value |
2797 | - else: |
2798 | - # len 2 |
2799 | - # could be (name, args) or (name, kwargs) or (args, kwargs) |
2800 | - first, second = other |
2801 | - if isinstance(first, basestring): |
2802 | - other_name = first |
2803 | - if isinstance(second, tuple): |
2804 | - other_args, other_kwargs = second, {} |
2805 | - else: |
2806 | - other_args, other_kwargs = (), second |
2807 | - else: |
2808 | - other_args, other_kwargs = first, second |
2809 | - |
2810 | - if self_name and other_name != self_name: |
2811 | - return False |
2812 | - |
2813 | - # this order is important for ANY to work! |
2814 | - return (other_args, other_kwargs) == (self_args, self_kwargs) |
2815 | - |
2816 | - |
2817 | - def __ne__(self, other): |
2818 | - return not self.__eq__(other) |
2819 | - |
2820 | - |
2821 | - def __call__(self, *args, **kwargs): |
2822 | - if self.name is None: |
2823 | - return _Call(('', args, kwargs), name='()') |
2824 | - |
2825 | - name = self.name + '()' |
2826 | - return _Call((self.name, args, kwargs), name=name, parent=self) |
2827 | - |
2828 | - |
2829 | - def __getattr__(self, attr): |
2830 | - if self.name is None: |
2831 | - return _Call(name=attr, from_kall=False) |
2832 | - name = '%s.%s' % (self.name, attr) |
2833 | - return _Call(name=name, parent=self, from_kall=False) |
2834 | - |
2835 | - |
2836 | - def __repr__(self): |
2837 | - if not self.from_kall: |
2838 | - name = self.name or 'call' |
2839 | - if name.startswith('()'): |
2840 | - name = 'call%s' % name |
2841 | - return name |
2842 | - |
2843 | - if len(self) == 2: |
2844 | - name = 'call' |
2845 | - args, kwargs = self |
2846 | - else: |
2847 | - name, args, kwargs = self |
2848 | - if not name: |
2849 | - name = 'call' |
2850 | - elif not name.startswith('()'): |
2851 | - name = 'call.%s' % name |
2852 | - else: |
2853 | - name = 'call%s' % name |
2854 | - return _format_call_signature(name, args, kwargs) |
2855 | - |
2856 | - |
2857 | - def call_list(self): |
2858 | - """For a call object that represents multiple calls, `call_list` |
2859 | - returns a list of all the intermediate calls as well as the |
2860 | - final call.""" |
2861 | - vals = [] |
2862 | - thing = self |
2863 | - while thing is not None: |
2864 | - if thing.from_kall: |
2865 | - vals.append(thing) |
2866 | - thing = thing.parent |
2867 | - return _CallList(reversed(vals)) |
2868 | - |
2869 | - |
2870 | -call = _Call(from_kall=False) |
2871 | - |
2872 | - |
2873 | - |
2874 | -def create_autospec(spec, spec_set=False, instance=False, _parent=None, |
2875 | - _name=None, **kwargs): |
2876 | - """Create a mock object using another object as a spec. Attributes on the |
2877 | - mock will use the corresponding attribute on the `spec` object as their |
2878 | - spec. |
2879 | - |
2880 | - Functions or methods being mocked will have their arguments checked |
2881 | - to check that they are called with the correct signature. |
2882 | - |
2883 | - If `spec_set` is True then attempting to set attributes that don't exist |
2884 | - on the spec object will raise an `AttributeError`. |
2885 | - |
2886 | - If a class is used as a spec then the return value of the mock (the |
2887 | - instance of the class) will have the same spec. You can use a class as the |
2888 | - spec for an instance object by passing `instance=True`. The returned mock |
2889 | - will only be callable if instances of the mock are callable. |
2890 | - |
2891 | - `create_autospec` also takes arbitrary keyword arguments that are passed to |
2892 | - the constructor of the created mock.""" |
2893 | - if _is_list(spec): |
2894 | - # can't pass a list instance to the mock constructor as it will be |
2895 | - # interpreted as a list of strings |
2896 | - spec = type(spec) |
2897 | - |
2898 | - is_type = isinstance(spec, ClassTypes) |
2899 | - |
2900 | - _kwargs = {'spec': spec} |
2901 | - if spec_set: |
2902 | - _kwargs = {'spec_set': spec} |
2903 | - elif spec is None: |
2904 | - # None we mock with a normal mock without a spec |
2905 | - _kwargs = {} |
2906 | - |
2907 | - _kwargs.update(kwargs) |
2908 | - |
2909 | - Klass = MagicMock |
2910 | - if type(spec) in DescriptorTypes: |
2911 | - # descriptors don't have a spec |
2912 | - # because we don't know what type they return |
2913 | - _kwargs = {} |
2914 | - elif not _callable(spec): |
2915 | - Klass = NonCallableMagicMock |
2916 | - elif is_type and instance and not _instance_callable(spec): |
2917 | - Klass = NonCallableMagicMock |
2918 | - |
2919 | - _new_name = _name |
2920 | - if _parent is None: |
2921 | - # for a top level object no _new_name should be set |
2922 | - _new_name = '' |
2923 | - |
2924 | - mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name, |
2925 | - name=_name, **_kwargs) |
2926 | - |
2927 | - if isinstance(spec, FunctionTypes): |
2928 | - # should only happen at the top level because we don't |
2929 | - # recurse for functions |
2930 | - mock = _set_signature(mock, spec) |
2931 | - else: |
2932 | - _check_signature(spec, mock, is_type, instance) |
2933 | - |
2934 | - if _parent is not None and not instance: |
2935 | - _parent._mock_children[_name] = mock |
2936 | - |
2937 | - if is_type and not instance and 'return_value' not in kwargs: |
2938 | - mock.return_value = create_autospec(spec, spec_set, instance=True, |
2939 | - _name='()', _parent=mock) |
2940 | - |
2941 | - for entry in dir(spec): |
2942 | - if _is_magic(entry): |
2943 | - # MagicMock already does the useful magic methods for us |
2944 | - continue |
2945 | - |
2946 | - if isinstance(spec, FunctionTypes) and entry in FunctionAttributes: |
2947 | - # allow a mock to actually be a function |
2948 | - continue |
2949 | - |
2950 | - # XXXX do we need a better way of getting attributes without |
2951 | - # triggering code execution (?) Probably not - we need the actual |
2952 | - # object to mock it so we would rather trigger a property than mock |
2953 | - # the property descriptor. Likewise we want to mock out dynamically |
2954 | - # provided attributes. |
2955 | - # XXXX what about attributes that raise exceptions other than |
2956 | - # AttributeError on being fetched? |
2957 | - # we could be resilient against it, or catch and propagate the |
2958 | - # exception when the attribute is fetched from the mock |
2959 | - try: |
2960 | - original = getattr(spec, entry) |
2961 | - except AttributeError: |
2962 | - continue |
2963 | - |
2964 | - kwargs = {'spec': original} |
2965 | - if spec_set: |
2966 | - kwargs = {'spec_set': original} |
2967 | - |
2968 | - if not isinstance(original, FunctionTypes): |
2969 | - new = _SpecState(original, spec_set, mock, entry, instance) |
2970 | - mock._mock_children[entry] = new |
2971 | - else: |
2972 | - parent = mock |
2973 | - if isinstance(spec, FunctionTypes): |
2974 | - parent = mock.mock |
2975 | - |
2976 | - new = MagicMock(parent=parent, name=entry, _new_name=entry, |
2977 | - _new_parent=parent, **kwargs) |
2978 | - mock._mock_children[entry] = new |
2979 | - skipfirst = _must_skip(spec, entry, is_type) |
2980 | - _check_signature(original, new, skipfirst=skipfirst) |
2981 | - |
2982 | - # so functions created with _set_signature become instance attributes, |
2983 | - # *plus* their underlying mock exists in _mock_children of the parent |
2984 | - # mock. Adding to _mock_children may be unnecessary where we are also |
2985 | - # setting as an instance attribute? |
2986 | - if isinstance(new, FunctionTypes): |
2987 | - setattr(mock, entry, new) |
2988 | - |
2989 | - return mock |
2990 | - |
2991 | - |
2992 | -def _must_skip(spec, entry, is_type): |
2993 | - if not isinstance(spec, ClassTypes): |
2994 | - if entry in getattr(spec, '__dict__', {}): |
2995 | - # instance attribute - shouldn't skip |
2996 | - return False |
2997 | - spec = spec.__class__ |
2998 | - if not hasattr(spec, '__mro__'): |
2999 | - # old style class: can't have descriptors anyway |
3000 | - return is_type |
3001 | - |
3002 | - for klass in spec.__mro__: |
3003 | - result = klass.__dict__.get(entry, DEFAULT) |
3004 | - if result is DEFAULT: |
3005 | - continue |
3006 | - if isinstance(result, (staticmethod, classmethod)): |
3007 | - return False |
3008 | - return is_type |
3009 | - |
3010 | - # shouldn't get here unless function is a dynamically provided attribute |
3011 | - # XXXX untested behaviour |
3012 | - return is_type |
3013 | - |
3014 | - |
3015 | -def _get_class(obj): |
3016 | - try: |
3017 | - return obj.__class__ |
3018 | - except AttributeError: |
3019 | - # in Python 2, _sre.SRE_Pattern objects have no __class__ |
3020 | - return type(obj) |
3021 | - |
3022 | - |
3023 | -class _SpecState(object): |
3024 | - |
3025 | - def __init__(self, spec, spec_set=False, parent=None, |
3026 | - name=None, ids=None, instance=False): |
3027 | - self.spec = spec |
3028 | - self.ids = ids |
3029 | - self.spec_set = spec_set |
3030 | - self.parent = parent |
3031 | - self.instance = instance |
3032 | - self.name = name |
3033 | - |
3034 | - |
3035 | -FunctionTypes = ( |
3036 | - # python function |
3037 | - type(create_autospec), |
3038 | - # instance method |
3039 | - type(ANY.__eq__), |
3040 | - # unbound method |
3041 | - type(_ANY.__eq__), |
3042 | -) |
3043 | - |
3044 | -FunctionAttributes = set([ |
3045 | - 'func_closure', |
3046 | - 'func_code', |
3047 | - 'func_defaults', |
3048 | - 'func_dict', |
3049 | - 'func_doc', |
3050 | - 'func_globals', |
3051 | - 'func_name', |
3052 | -]) |
3053 | - |
3054 | - |
3055 | -file_spec = None |
3056 | - |
3057 | - |
3058 | -def mock_open(mock=None, read_data=''): |
3059 | - """ |
3060 | - A helper function to create a mock to replace the use of `open`. It works |
3061 | - for `open` called directly or used as a context manager. |
3062 | - |
3063 | - The `mock` argument is the mock object to configure. If `None` (the |
3064 | - default) then a `MagicMock` will be created for you, with the API limited |
3065 | - to methods or attributes available on standard file handles. |
3066 | - |
3067 | - `read_data` is a string for the `read` method of the file handle to return. |
3068 | - This is an empty string by default. |
3069 | - """ |
3070 | - global file_spec |
3071 | - if file_spec is None: |
3072 | - # set on first use |
3073 | - if inPy3k: |
3074 | - import _io |
3075 | - file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) |
3076 | - else: |
3077 | - file_spec = file |
3078 | - |
3079 | - if mock is None: |
3080 | - mock = MagicMock(name='open', spec=open) |
3081 | - |
3082 | - handle = MagicMock(spec=file_spec) |
3083 | - handle.write.return_value = None |
3084 | - handle.__enter__.return_value = handle |
3085 | - handle.read.return_value = read_data |
3086 | - |
3087 | - mock.return_value = handle |
3088 | - return mock |
3089 | - |
3090 | - |
3091 | -class PropertyMock(Mock): |
3092 | - """ |
3093 | - A mock intended to be used as a property, or other descriptor, on a class. |
3094 | - `PropertyMock` provides `__get__` and `__set__` methods so you can specify |
3095 | - a return value when it is fetched. |
3096 | - |
3097 | - Fetching a `PropertyMock` instance from an object calls the mock, with |
3098 | - no args. Setting it calls the mock with the value being set. |
3099 | - """ |
3100 | - def _get_child_mock(self, **kwargs): |
3101 | - return MagicMock(**kwargs) |
3102 | - |
3103 | - def __get__(self, obj, obj_type): |
3104 | - return self() |
3105 | - def __set__(self, obj, val): |
3106 | - self(val) |
3107 | |
3108 | === modified file 'deployer/tests/test_base.py' |
3109 | --- deployer/tests/test_base.py 2013-07-22 15:29:31 +0000 |
3110 | +++ deployer/tests/test_base.py 2016-05-08 03:36:27 +0000 |
3111 | @@ -1,17 +1,19 @@ |
3112 | +from __future__ import absolute_import |
3113 | import os |
3114 | import shutil |
3115 | import unittest |
3116 | -import StringIO |
3117 | import logging |
3118 | import tempfile |
3119 | |
3120 | +from six import StringIO |
3121 | + |
3122 | |
3123 | class Base(unittest.TestCase): |
3124 | |
3125 | def capture_logging(self, name="", level=logging.INFO, |
3126 | log_file=None, formatter=None): |
3127 | if log_file is None: |
3128 | - log_file = StringIO.StringIO() |
3129 | + log_file = StringIO() |
3130 | log_handler = logging.StreamHandler(log_file) |
3131 | if formatter: |
3132 | log_handler.setFormatter(formatter) |
3133 | |
3134 | === modified file 'deployer/tests/test_charm.py' |
3135 | --- deployer/tests/test_charm.py 2016-03-10 17:59:21 +0000 |
3136 | +++ deployer/tests/test_charm.py 2016-05-08 03:36:27 +0000 |
3137 | @@ -1,3 +1,4 @@ |
3138 | +from __future__ import absolute_import |
3139 | import os |
3140 | import logging |
3141 | import subprocess |
3142 | @@ -313,5 +314,5 @@ |
3143 | Charm.from_service( |
3144 | "scratch", self.repo_path, "precise", params) |
3145 | self.fail("should have failed, vcs ambigious") |
3146 | - except ValueError, e: |
3147 | + except ValueError as e: |
3148 | self.assertIn("Could not determine vcs backend", str(e)) |
3149 | |
3150 | === modified file 'deployer/tests/test_config.py' |
3151 | --- deployer/tests/test_config.py 2015-03-04 19:27:31 +0000 |
3152 | +++ deployer/tests/test_config.py 2016-05-08 03:36:27 +0000 |
3153 | @@ -1,9 +1,12 @@ |
3154 | +from __future__ import absolute_import |
3155 | import logging |
3156 | import mock |
3157 | import os |
3158 | import tempfile |
3159 | import yaml |
3160 | |
3161 | +import six |
3162 | + |
3163 | from deployer.deployment import Deployment |
3164 | from deployer.config import ConfigStack |
3165 | from deployer.utils import ErrorExit |
3166 | @@ -21,7 +24,7 @@ |
3167 | config = ConfigStack(['configs/ostack-testing-sample.cfg']) |
3168 | config.load() |
3169 | self.assertEqual( |
3170 | - config.keys(), |
3171 | + list(config.keys()), |
3172 | [u'openstack-precise-ec2', |
3173 | u'openstack-precise-ec2-trunk', |
3174 | u'openstack-ubuntu-testing']) |
3175 | @@ -36,7 +39,7 @@ |
3176 | os.path.join(self.test_data_dir, "stack-inherits.cfg")]) |
3177 | config.load() |
3178 | self.assertEqual( |
3179 | - config.keys(), |
3180 | + list(config.keys()), |
3181 | [u'my-files-frontend-dev', u'wordpress']) |
3182 | deployment = config.get("wordpress") |
3183 | self.assertTrue(deployment) |
3184 | @@ -47,11 +50,11 @@ |
3185 | os.path.join(self.test_data_dir, 'v4', 'simple.yaml')]) |
3186 | config.load() |
3187 | self.assertEqual( |
3188 | - config.keys(), |
3189 | + list(config.keys()), |
3190 | [os.path.join(self.test_data_dir, 'v4', 'simple.yaml')]) |
3191 | with mock.patch('deployer.config.ConfigStack._resolve_inherited') \ |
3192 | as mock_resolve: |
3193 | - deployment = config.get(config.keys()[0]) |
3194 | + deployment = config.get(list(config.keys())[0]) |
3195 | self.assertTrue(deployment) |
3196 | self.assertFalse(mock_resolve.called) |
3197 | self.assertEqual(config.version, 4) |
3198 | @@ -62,7 +65,7 @@ |
3199 | config.load() |
3200 | # ensure picked up stacks from both files |
3201 | self.assertEqual( |
3202 | - config.keys(), |
3203 | + list(config.keys()), |
3204 | [u'my-files-frontend-dev', u'wordpress']) |
3205 | |
3206 | # ensure inheritance was adhered to during cross-file load |
3207 | @@ -104,7 +107,7 @@ |
3208 | for key in ['include-config', 'include-configs']: |
3209 | test_conf[key] = includes |
3210 | with tempfile.NamedTemporaryFile() as tmp_cfg: |
3211 | - tmp_cfg.write(yaml.dump(test_conf)) |
3212 | + tmp_cfg.write(yaml.dump(test_conf).encode()) |
3213 | tmp_cfg.flush() |
3214 | config = ConfigStack([tmp_cfg.name]) |
3215 | self._test_multiple_inheritance(config) |
3216 | @@ -141,7 +144,9 @@ |
3217 | ex_rels = [('quantum-gateway', 'nova-cloud-controller'), |
3218 | ('quantum-gateway', 'mysql'), |
3219 | ('nova-cloud-controller', 'mysql')] |
3220 | - self.assertEquals(ex_rels, list(deployment.get_relations())) |
3221 | + self.assertEquals( |
3222 | + sorted(ex_rels), |
3223 | + sorted(list(deployment.get_relations()))) |
3224 | |
3225 | def test_config_series_override(self): |
3226 | config = ConfigStack(['configs/wiki.yaml'], 'trusty') |
3227 | @@ -166,18 +171,19 @@ |
3228 | config = ConfigStack([]) |
3229 | config.config_files = [CONFIG_URL] |
3230 | |
3231 | - class FauxResponse(file): |
3232 | + class FauxResponse(six.BytesIO): |
3233 | def getcode(self): |
3234 | return 200 |
3235 | |
3236 | def faux_urlopen(url): |
3237 | self.assertEqual(url, CONFIG_URL) |
3238 | - return FauxResponse('configs/ostack-testing-sample.cfg') |
3239 | + with open('configs/ostack-testing-sample.cfg', 'rb') as f: |
3240 | + return FauxResponse(f.read()) |
3241 | |
3242 | config.urlopen = faux_urlopen |
3243 | config.load() |
3244 | self.assertEqual( |
3245 | - config.keys(), |
3246 | + list(config.keys()), |
3247 | [u'openstack-precise-ec2', |
3248 | u'openstack-precise-ec2-trunk', |
3249 | u'openstack-ubuntu-testing']) |
3250 | @@ -193,12 +199,14 @@ |
3251 | config = ConfigStack([]) |
3252 | config.config_files = [CONFIG_URL] |
3253 | |
3254 | - class FauxResponse(file): |
3255 | + class FauxResponse(six.BytesIO): |
3256 | def getcode(self): |
3257 | return 400 |
3258 | |
3259 | def faux_urlopen(url): |
3260 | self.assertEqual(url, CONFIG_URL) |
3261 | - return FauxResponse('configs/ostack-testing-sample.cfg') |
3262 | + with open('configs/ostack-testing-sample.cfg', 'rb') as f: |
3263 | + return FauxResponse(f.read()) |
3264 | + |
3265 | config.urlopen = faux_urlopen |
3266 | self.assertRaises(ErrorExit, config.load) |
3267 | |
3268 | === modified file 'deployer/tests/test_constraints.py' |
3269 | --- deployer/tests/test_constraints.py 2015-09-03 14:25:56 +0000 |
3270 | +++ deployer/tests/test_constraints.py 2016-05-08 03:36:27 +0000 |
3271 | @@ -1,3 +1,4 @@ |
3272 | +from __future__ import absolute_import |
3273 | from deployer.service import Service |
3274 | from .base import Base |
3275 | from ..utils import parse_constraints |
3276 | @@ -94,9 +95,10 @@ |
3277 | 'mem': '1E', |
3278 | } |
3279 | with self.assertRaises(ValueError) as exc: |
3280 | - result = parse_constraints(value) |
3281 | - self.assertEqual('Constraint mem has invalid value 1E', |
3282 | - exc.exception.message) |
3283 | + parse_constraints(value) |
3284 | + self.assertEqual( |
3285 | + 'Constraint mem has invalid value 1E', |
3286 | + exc.exception.args[0]) |
3287 | |
3288 | def test_other_numeric_constraints_have_no_units(self): |
3289 | # If any other numeric constraint gets a units specifier an error is |
3290 | @@ -108,8 +110,9 @@ |
3291 | } |
3292 | with self.assertRaises(ValueError) as exc: |
3293 | parse_constraints(value) |
3294 | - self.assertEqual('Constraint {} has invalid value 1T'.format(k), |
3295 | - exc.exception.message) |
3296 | + self.assertEqual( |
3297 | + 'Constraint {} has invalid value 1T'.format(k), |
3298 | + exc.exception.args[0]) |
3299 | |
3300 | def test_non_numerics_are_not_converted(self): |
3301 | # Constraints that expect strings are not affected by the parsing. |
3302 | |
3303 | === modified file 'deployer/tests/test_deployment.py' |
3304 | --- deployer/tests/test_deployment.py 2015-08-06 12:04:16 +0000 |
3305 | +++ deployer/tests/test_deployment.py 2016-05-08 03:36:27 +0000 |
3306 | @@ -1,7 +1,9 @@ |
3307 | +from __future__ import absolute_import |
3308 | import base64 |
3309 | -import StringIO |
3310 | import os |
3311 | |
3312 | +from six import StringIO |
3313 | + |
3314 | from deployer.deployment import Deployment |
3315 | from deployer.utils import setup_logging, ErrorExit |
3316 | |
3317 | @@ -22,7 +24,7 @@ |
3318 | |
3319 | def setUp(self): |
3320 | self.output = setup_logging( |
3321 | - debug=True, verbose=True, stream=StringIO.StringIO()) |
3322 | + debug=True, verbose=True, stream=StringIO()) |
3323 | |
3324 | def get_named_deployment_and_fetch_v3(self, file_name, stack_name): |
3325 | deployment = self.get_named_deployment_v3(file_name, stack_name) |
3326 | @@ -46,7 +48,8 @@ |
3327 | |
3328 | @skip_if_offline |
3329 | def test_deployer(self): |
3330 | - d = self.get_named_deployment_and_fetch_v3('blog.yaml', 'wordpress-prod') |
3331 | + d = self.get_named_deployment_and_fetch_v3( |
3332 | + 'blog.yaml', 'wordpress-prod') |
3333 | services = d.get_services() |
3334 | self.assertTrue([s for s in services if s.name == "newrelic"]) |
3335 | |
3336 | @@ -63,7 +66,7 @@ |
3337 | self.assertEqual(d.get_service('newrelic').config, {'key': 'abc'}) |
3338 | self.assertEqual( |
3339 | base64.b64decode(d.get_service('blog').config['wp-content']), |
3340 | - "HelloWorld") |
3341 | + b"HelloWorld") |
3342 | |
3343 | # TODO verify include-file |
3344 | |
3345 | @@ -82,7 +85,8 @@ |
3346 | |
3347 | @skip_if_offline |
3348 | def test_validate_placement_sorting(self): |
3349 | - d = self.get_named_deployment_and_fetch_v3("stack-placement.yaml", "stack") |
3350 | + d = self.get_named_deployment_and_fetch_v3( |
3351 | + "stack-placement.yaml", "stack") |
3352 | services = d.get_services() |
3353 | self.assertEqual(services[0].name, 'nova-compute') |
3354 | try: |
3355 | @@ -164,7 +168,8 @@ |
3356 | |
3357 | @skip_if_offline |
3358 | def test_validate_invalid_placement_nested(self): |
3359 | - d = self.get_named_deployment_and_fetch_v3("stack-placement-invalid.yaml", "stack") |
3360 | + d = self.get_named_deployment_and_fetch_v3( |
3361 | + "stack-placement-invalid.yaml", "stack") |
3362 | services = d.get_services() |
3363 | self.assertEqual(services[0].name, 'nova-compute') |
3364 | try: |
3365 | @@ -438,7 +443,8 @@ |
3366 | d.set_machines(machines) |
3367 | |
3368 | placement = d.get_unit_placement('mysql', status) |
3369 | - self.assertEqual(placement.get_new_machines_for_containers(), |
3370 | + self.assertEqual( |
3371 | + placement.get_new_machines_for_containers(), |
3372 | ['mysql/0']) |
3373 | self.assertEqual(placement.get(0), 'lxc:2') |
3374 | |
3375 | @@ -450,8 +456,8 @@ |
3376 | "nginx": {"consumes": ["wordpress"]}}} |
3377 | d = Deployment("foo", data, include_dirs=()) |
3378 | self.assertEqual( |
3379 | - [('nginx', 'wordpress'), ('wordpress', 'mysql')], |
3380 | - list(d.get_relations())) |
3381 | + sorted([('nginx', 'wordpress'), ('wordpress', 'mysql')]), |
3382 | + sorted(list(d.get_relations()))) |
3383 | |
3384 | def test_multiple_relations_weighted(self): |
3385 | data = { |
3386 | @@ -477,10 +483,12 @@ |
3387 | |
3388 | def test_getting_service_names(self): |
3389 | # It is possible to retrieve the service names. |
3390 | - deployment = self.get_named_deployment_v3("stack-placement.yaml", "stack") |
3391 | + deployment = self.get_named_deployment_v3( |
3392 | + "stack-placement.yaml", "stack") |
3393 | service_names = deployment.get_service_names() |
3394 | expected_service_names = [ |
3395 | - 'ceph', 'mysql', 'nova-compute', 'quantum', 'semper', 'verity', 'lxc-service'] |
3396 | + 'ceph', 'mysql', 'nova-compute', 'quantum', |
3397 | + 'semper', 'verity', 'lxc-service'] |
3398 | self.assertEqual(set(expected_service_names), set(service_names)) |
3399 | |
3400 | def test_resolve_config_handles_empty_options(self): |
3401 | |
3402 | === modified file 'deployer/tests/test_diff.py' |
3403 | --- deployer/tests/test_diff.py 2015-03-17 17:34:57 +0000 |
3404 | +++ deployer/tests/test_diff.py 2016-05-08 03:36:27 +0000 |
3405 | @@ -1,11 +1,13 @@ |
3406 | """ Unittest for juju-deployer diff action (--diff) """ |
3407 | # pylint: disable=C0103 |
3408 | -import StringIO |
3409 | +from __future__ import absolute_import |
3410 | import os |
3411 | import shutil |
3412 | import tempfile |
3413 | import unittest |
3414 | |
3415 | +from six import StringIO |
3416 | + |
3417 | from deployer.config import ConfigStack |
3418 | from deployer.env.mem import MemoryEnvironment |
3419 | from deployer.utils import setup_logging |
3420 | @@ -19,7 +21,7 @@ |
3421 | |
3422 | def setUp(self): |
3423 | self.output = setup_logging( |
3424 | - debug=True, verbose=True, stream=StringIO.StringIO()) |
3425 | + debug=True, verbose=True, stream=StringIO()) |
3426 | |
3427 | # Because fetch_charms is expensive, do it once for all tests |
3428 | @classmethod |
3429 | @@ -113,6 +115,7 @@ |
3430 | env = MemoryEnvironment(dpl.name, dpl) |
3431 | env.destroy_service('haproxy') |
3432 | diff = Diff(env, dpl, {}).do_diff() |
3433 | - self.assertTrue(str(diff['relations']['missing'][0]).find('haproxy') |
3434 | - != -1) |
3435 | - self.assertTrue(diff['services']['missing'].keys() == ['haproxy']) |
3436 | + self.assertTrue( |
3437 | + str(diff['relations']['missing'][0]).find('haproxy') != -1) |
3438 | + self.assertTrue( |
3439 | + list(diff['services']['missing'].keys()) == ['haproxy']) |
3440 | |
3441 | === modified file 'deployer/tests/test_goenv.py' |
3442 | --- deployer/tests/test_goenv.py 2016-04-01 02:22:26 +0000 |
3443 | +++ deployer/tests/test_goenv.py 2016-05-08 03:36:27 +0000 |
3444 | @@ -1,3 +1,4 @@ |
3445 | +from __future__ import absolute_import |
3446 | import logging |
3447 | import os |
3448 | import time |
3449 | @@ -44,7 +45,7 @@ |
3450 | self.assertFalse(status.get('services')) |
3451 | # Destroy everything.. consistent baseline |
3452 | self.env.reset( |
3453 | - terminate_machines=len(status['machines'].keys()) > 1, |
3454 | + terminate_machines=len(list(status['machines'].keys())) > 1, |
3455 | terminate_delay=240) |
3456 | |
3457 | def tearDown(self): |
3458 | |
3459 | === modified file 'deployer/tests/test_guienv.py' |
3460 | --- deployer/tests/test_guienv.py 2014-12-17 13:06:49 +0000 |
3461 | +++ deployer/tests/test_guienv.py 2016-05-08 03:36:27 +0000 |
3462 | @@ -1,5 +1,6 @@ |
3463 | """Tests for the GUIEnvironment.""" |
3464 | |
3465 | +from __future__ import absolute_import |
3466 | import unittest |
3467 | import mock |
3468 | |
3469 | |
3470 | === modified file 'deployer/tests/test_guiserver.py' |
3471 | --- deployer/tests/test_guiserver.py 2016-05-03 16:03:18 +0000 |
3472 | +++ deployer/tests/test_guiserver.py 2016-05-08 03:36:27 +0000 |
3473 | @@ -1,5 +1,6 @@ |
3474 | """Tests for the GUI server bundles deployment support.""" |
3475 | |
3476 | +from __future__ import absolute_import |
3477 | from contextlib import contextmanager |
3478 | import os |
3479 | import shutil |
3480 | |
3481 | === modified file 'deployer/tests/test_importer.py' |
3482 | --- deployer/tests/test_importer.py 2016-05-03 16:03:18 +0000 |
3483 | +++ deployer/tests/test_importer.py 2016-05-08 03:36:27 +0000 |
3484 | @@ -1,3 +1,4 @@ |
3485 | +from __future__ import absolute_import |
3486 | import os |
3487 | |
3488 | import mock |
3489 | @@ -5,7 +6,7 @@ |
3490 | from deployer.config import ConfigStack |
3491 | from deployer.action.importer import Importer |
3492 | |
3493 | -from base import ( |
3494 | +from .base import ( |
3495 | Base, |
3496 | patch_env_status, |
3497 | skip_if_offline, |
3498 | @@ -50,6 +51,7 @@ |
3499 | @skip_if_offline |
3500 | @mock.patch('deployer.action.importer.time') |
3501 | def test_importer(self, mock_time): |
3502 | + mock_time.time.return_value = 0 |
3503 | # Trying to track down where this comes from http://pad.lv/1243827 |
3504 | stack = ConfigStack(self.options.configs) |
3505 | deploy = stack.get('wiki') |
3506 | @@ -69,6 +71,7 @@ |
3507 | @skip_if_offline |
3508 | @mock.patch('deployer.action.importer.time') |
3509 | def test_importer_no_relations(self, mock_time): |
3510 | + mock_time.time.return_value = 0 |
3511 | self.options.no_relations = True |
3512 | stack = ConfigStack(self.options.configs) |
3513 | deploy = stack.get('wiki') |
3514 | @@ -81,6 +84,7 @@ |
3515 | @skip_if_offline |
3516 | @mock.patch('deployer.action.importer.time') |
3517 | def test_importer_add_machine_series(self, mock_time): |
3518 | + mock_time.time.return_value = 0 |
3519 | self.options.configs = [ |
3520 | os.path.join(self.test_data_dir, 'v4', 'series.yaml')] |
3521 | stack = ConfigStack(self.options.configs) |
3522 | @@ -91,16 +95,15 @@ |
3523 | importer.run() |
3524 | |
3525 | self.assertEqual(env.add_machine.call_count, 2) |
3526 | - self.assertEqual( |
3527 | - env.add_machine.call_args_list[0][1], |
3528 | - {'series': 'precise', 'constraints': 'mem=512M'}) |
3529 | - self.assertEqual( |
3530 | - env.add_machine.call_args_list[1][1], |
3531 | - {'series': 'trusty', 'constraints': 'mem=512M'}) |
3532 | + env.add_machine.assert_has_calls([ |
3533 | + mock.call(series='precise', constraints='mem=512M'), |
3534 | + mock.call(series='trusty', constraints='mem=512M'), |
3535 | + ], any_order=True) |
3536 | |
3537 | @skip_if_offline |
3538 | @mock.patch('deployer.action.importer.time') |
3539 | def test_importer_existing_machine(self, mock_time): |
3540 | + mock_time.time.return_value = 0 |
3541 | self.options.configs = [ |
3542 | os.path.join(self.test_data_dir, 'v4', |
3543 | 'container-existing-machine.yaml')] |
3544 | |
3545 | === modified file 'deployer/tests/test_pyenv.py' |
3546 | --- deployer/tests/test_pyenv.py 2014-02-22 23:11:02 +0000 |
3547 | +++ deployer/tests/test_pyenv.py 2016-05-08 03:36:27 +0000 |
3548 | @@ -1,4 +1,6 @@ |
3549 | -import StringIO |
3550 | +from __future__ import absolute_import |
3551 | + |
3552 | +from six import StringIO |
3553 | |
3554 | from .base import Base |
3555 | from deployer.env import watchers |
3556 | @@ -21,7 +23,7 @@ |
3557 | |
3558 | def setUp(self): |
3559 | self.output = setup_logging( |
3560 | - debug=True, verbose=True, stream=StringIO.StringIO()) |
3561 | + debug=True, verbose=True, stream=StringIO()) |
3562 | |
3563 | def test_wait_for_units_error_no_exit(self): |
3564 | env = FakePyEnvironment( |
3565 | |
3566 | === modified file 'deployer/tests/test_service.py' |
3567 | --- deployer/tests/test_service.py 2013-07-22 15:29:31 +0000 |
3568 | +++ deployer/tests/test_service.py 2016-05-08 03:36:27 +0000 |
3569 | @@ -1,3 +1,4 @@ |
3570 | +from __future__ import absolute_import |
3571 | from deployer.service import Service |
3572 | from .base import Base |
3573 | |
3574 | |
3575 | === modified file 'deployer/tests/test_utils.py' |
3576 | --- deployer/tests/test_utils.py 2016-01-11 13:35:47 +0000 |
3577 | +++ deployer/tests/test_utils.py 2016-05-08 03:36:27 +0000 |
3578 | @@ -1,5 +1,5 @@ |
3579 | +from __future__ import absolute_import |
3580 | import os |
3581 | -from StringIO import StringIO |
3582 | from subprocess import CalledProcessError |
3583 | |
3584 | from mock import ( |
3585 | @@ -7,6 +7,8 @@ |
3586 | patch, |
3587 | ) |
3588 | |
3589 | +from six import BytesIO |
3590 | + |
3591 | from .base import Base |
3592 | from deployer.utils import ( |
3593 | _check_call, |
3594 | @@ -73,7 +75,7 @@ |
3595 | self.assertRaises( |
3596 | OSError, _check_call, params=[cmd], log=MagicMock()) |
3597 | output = _check_call(params=[cmd], log=MagicMock(), shell=True) |
3598 | - self.assertEqual(output, "foo\n") |
3599 | + self.assertEqual(output, b"foo\n") |
3600 | |
3601 | |
3602 | class TestMkdir(Base): |
3603 | @@ -117,7 +119,7 @@ |
3604 | # Errors are correctly re-raised. |
3605 | path = os.path.join(self.playground, 'foo') |
3606 | os.chmod(self.playground, 0000) |
3607 | - self.addCleanup(os.chmod, self.playground, 0700) |
3608 | + self.addCleanup(os.chmod, self.playground, 0o700) |
3609 | with self.assertRaises(OSError): |
3610 | mkdir(os.path.join(path)) |
3611 | self.assertFalse(os.path.exists(path)) |
3612 | @@ -143,14 +145,14 @@ |
3613 | self.assertFalse(_is_qualified_charm_url(url)) |
3614 | |
3615 | def test_get_qualified_url(self): |
3616 | - fake_json = """ |
3617 | + fake_json = b""" |
3618 | {"cs:precise/mysql": |
3619 | {"revision":333} |
3620 | } |
3621 | """ |
3622 | |
3623 | def mocked_urlopen(url): |
3624 | - return StringIO(fake_json) |
3625 | + return BytesIO(fake_json) |
3626 | |
3627 | path = 'deployer.utils.urlopen' |
3628 | with patch(path, mocked_urlopen): |
3629 | @@ -164,10 +166,11 @@ |
3630 | with patch('deployer.utils.urlopen', mocked_urlopen): |
3631 | with self.assertRaises(DeploymentError) as exc: |
3632 | get_qualified_charm_url('cs:precise/mysql') |
3633 | - expected = ('HTTP Error 404: ' |
3634 | - 'Bad Earl (https://api.jujucharms.com/charmstore/charm-info' |
3635 | - '?charms=cs:precise/mysql)') |
3636 | - self.assertEqual([expected], exc.exception.message) |
3637 | + expected = ( |
3638 | + 'HTTP Error 404: ' |
3639 | + 'Bad Earl (https://api.jujucharms.com/charmstore/charm-info' |
3640 | + '?charms=cs:precise/mysql)') |
3641 | + self.assertEqual([expected], exc.exception.args[0]) |
3642 | |
3643 | def test_get_qualified_url_raise_exception_on_URLError(self): |
3644 | def mocked_urlopen(url): |
3645 | @@ -179,4 +182,4 @@ |
3646 | expected = ('<urlopen error Hinky URL> ' |
3647 | '(https://api.jujucharms.com/charmstore/charm-info' |
3648 | '?charms=cs:precise/mysql)') |
3649 | - self.assertEqual([expected], exc.exception.message) |
3650 | + self.assertEqual([expected], exc.exception.args[0]) |
3651 | |
3652 | === modified file 'deployer/tests/test_watchers.py' |
3653 | --- deployer/tests/test_watchers.py 2014-03-06 21:17:14 +0000 |
3654 | +++ deployer/tests/test_watchers.py 2016-05-08 03:36:27 +0000 |
3655 | @@ -1,5 +1,7 @@ |
3656 | """Tests juju-core environment watchers.""" |
3657 | |
3658 | +from __future__ import absolute_import |
3659 | +import operator |
3660 | import unittest |
3661 | |
3662 | import mock |
3663 | @@ -80,14 +82,21 @@ |
3664 | # The errors handler has been called once for each changeset containing |
3665 | # errors. |
3666 | self.assertEqual(2, on_errors.call_count) |
3667 | - on_errors.assert_has_calls([ |
3668 | - mock.call([ |
3669 | + # Because of the implementation, we can't guarantee the order of the |
3670 | + # list in the first call to on_errors here: |
3671 | + sort_key = operator.itemgetter('Name') |
3672 | + self.assertEqual( |
3673 | + sorted([ |
3674 | {'Status': 'error', 'Name': 'django/42', 'Service': 'django'}, |
3675 | {'Status': 'error', 'Name': 'haproxy/1', 'Service': 'haproxy'} |
3676 | - ]), |
3677 | - mock.call([ |
3678 | - {'Status': 'error', 'Name': 'django/0', 'Service': 'django'}]), |
3679 | - ]) |
3680 | + ], key=sort_key), |
3681 | + # [0][0][0] = first call, positional args, first positional arg |
3682 | + sorted(on_errors.call_args_list[0][0][0], key=sort_key) |
3683 | + ) |
3684 | + self.assertEqual( |
3685 | + [{'Status': 'error', 'Name': 'django/0', 'Service': 'django'}], |
3686 | + on_errors.call_args_list[1][0][0] |
3687 | + ) |
3688 | |
3689 | def test_specific_services(self): |
3690 | # It is possible to only watch units belonging to specific services. |
3691 | @@ -164,4 +173,4 @@ |
3692 | callback = watchers.raise_on_errors(ValueError) |
3693 | with self.assertRaises(ValueError) as cm: |
3694 | callback('bad wolf') |
3695 | - self.assertEqual('bad wolf', bytes(cm.exception)) |
3696 | + self.assertEqual('bad wolf', cm.exception.args[0]) |
3697 | |
3698 | === modified file 'deployer/utils.py' |
3699 | --- deployer/utils.py 2016-05-03 17:28:13 +0000 |
3700 | +++ deployer/utils.py 2016-05-08 03:36:27 +0000 |
3701 | @@ -1,3 +1,4 @@ |
3702 | +from __future__ import absolute_import |
3703 | from copy import deepcopy |
3704 | from contextlib import contextmanager |
3705 | |
3706 | @@ -20,12 +21,15 @@ |
3707 | import subprocess |
3708 | import time |
3709 | import tempfile |
3710 | -from urllib2 import ( |
3711 | +from six.moves.urllib.error import ( |
3712 | HTTPError, |
3713 | URLError, |
3714 | +) |
3715 | +from six.moves.urllib.request import ( |
3716 | urlopen, |
3717 | ) |
3718 | import zipfile |
3719 | +import six |
3720 | |
3721 | try: |
3722 | from yaml import CSafeLoader, CSafeDumper |
3723 | @@ -71,7 +75,7 @@ |
3724 | node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni) |
3725 | return node |
3726 | |
3727 | -yaml.add_representer(unicode, _unicode_representer) |
3728 | +yaml.add_representer(six.text_type, _unicode_representer) |
3729 | |
3730 | |
3731 | DEFAULT_LOGGING = """ |
3732 | @@ -250,12 +254,14 @@ |
3733 | |
3734 | |
3735 | _juju_major_version = None |
3736 | + |
3737 | + |
3738 | def get_juju_major_version(): |
3739 | global _juju_major_version |
3740 | if _juju_major_version is None: |
3741 | log = logging.getLogger("deployer.utils") |
3742 | _juju_major_version = int(_check_call( |
3743 | - ["juju", "--version"], log).split('.')[0]) |
3744 | + ["juju", "--version"], log).split(b'.')[0]) |
3745 | return _juju_major_version |
3746 | |
3747 | |
3748 | @@ -370,7 +376,7 @@ |
3749 | except (HTTPError, URLError) as e: |
3750 | errmsg = '{} ({})'.format(e, info_url) |
3751 | raise DeploymentError([errmsg]) |
3752 | - content = json.loads(fh.read()) |
3753 | + content = json.loads(fh.read().decode()) |
3754 | rev = content[url]['revision'] |
3755 | return "%s-%d" % (url, rev) |
3756 | |
3757 | @@ -399,7 +405,7 @@ |
3758 | else: |
3759 | # Return the juju2 controller:model combo |
3760 | log = logging.getLogger("deployer.utils") |
3761 | - return _check_call(["juju", "switch"], log).strip() |
3762 | + return _check_call(["juju", "switch"], log).strip().decode() |
3763 | |
3764 | |
3765 | def x_in_y(x, y): |
3766 | |
3767 | === modified file 'deployer/vcs.py' |
3768 | --- deployer/vcs.py 2016-03-03 14:18:30 +0000 |
3769 | +++ deployer/vcs.py 2016-05-08 03:36:27 +0000 |
3770 | @@ -1,9 +1,8 @@ |
3771 | +from __future__ import absolute_import |
3772 | import subprocess |
3773 | import os |
3774 | import re |
3775 | |
3776 | -from bzrlib.workingtree import WorkingTree |
3777 | - |
3778 | from .utils import ErrorExit |
3779 | |
3780 | |
3781 | @@ -34,10 +33,10 @@ |
3782 | stderr = subprocess.STDOUT |
3783 | output = subprocess.check_output( |
3784 | args, cwd=cwd or self.path, stderr=stderr) |
3785 | - except subprocess.CalledProcessError, e: |
3786 | + except subprocess.CalledProcessError as e: |
3787 | self.log.error(error_msg % self.get_err_msg_ctx(e)) |
3788 | raise ErrorExit() |
3789 | - return output.strip() |
3790 | + return output.strip().decode() |
3791 | |
3792 | def get_err_msg_ctx(self, e): |
3793 | return { |
3794 | @@ -99,10 +98,9 @@ |
3795 | self._call(params, self.err_branch, cwd) |
3796 | |
3797 | def is_modified(self): |
3798 | - # To replace with bzr cli, we need to be able to detect |
3799 | - # changes to a wc @ a rev or @ trunk. |
3800 | - tree = WorkingTree.open(self.path) |
3801 | - return tree.has_changes() |
3802 | + return subprocess.call( |
3803 | + ["bzr", "diff"], |
3804 | + cwd=self.path, stdout=subprocess.PIPE) != 0 |
3805 | |
3806 | |
3807 | class Git(Vcs): |
3808 | |
3809 | === modified file 'doc/conf.py' |
3810 | --- doc/conf.py 2013-05-16 03:05:55 +0000 |
3811 | +++ doc/conf.py 2016-05-08 03:36:27 +0000 |
3812 | @@ -11,6 +11,7 @@ |
3813 | # All configuration values have a default; values that are commented out |
3814 | # serve to show the default. |
3815 | |
3816 | +from __future__ import absolute_import |
3817 | import sys, os |
3818 | |
3819 | # If extensions (or modules to document with autodoc) are in another directory, |
3820 | |
3821 | === modified file 'setup.py' |
3822 | --- setup.py 2016-05-05 17:42:42 +0000 |
3823 | +++ setup.py 2016-05-08 03:36:27 +0000 |
3824 | @@ -1,3 +1,4 @@ |
3825 | +from __future__ import absolute_import |
3826 | from setuptools import setup, find_packages |
3827 | |
3828 | |
3829 | @@ -9,11 +10,15 @@ |
3830 | author="Kapil Thangavelu", |
3831 | author_email="kapil.foss@gmail.com", |
3832 | url="http://launchpad.net/juju-deployer", |
3833 | - install_requires=["jujuclient>=0.18", "PyYAML>=3.10", "bzr"], |
3834 | + install_requires=["jujuclient>=0.18", "PyYAML>=3.10", "six"], |
3835 | packages=find_packages(), |
3836 | classifiers=[ |
3837 | "Development Status :: 4 - Beta", |
3838 | "Programming Language :: Python", |
3839 | + "Programming Language :: Python :: 2", |
3840 | + "Programming Language :: Python :: 2.7", |
3841 | + "Programming Language :: Python :: 3", |
3842 | + "Programming Language :: Python :: 3.5", |
3843 | "Topic :: Internet", |
3844 | "Topic :: Software Development :: Libraries :: Python Modules", |
3845 | "Intended Audience :: System Administrators", |
3846 | |
3847 | === added file 'test-requirements.txt' |
3848 | --- test-requirements.txt 1970-01-01 00:00:00 +0000 |
3849 | +++ test-requirements.txt 2016-05-08 03:36:27 +0000 |
3850 | @@ -0,0 +1,4 @@ |
3851 | +coverage |
3852 | +flake8 |
3853 | +mock |
3854 | +nose |
3855 | |
3856 | === added file 'tox.ini' |
3857 | --- tox.ini 1970-01-01 00:00:00 +0000 |
3858 | +++ tox.ini 2016-05-08 03:36:27 +0000 |
3859 | @@ -0,0 +1,20 @@ |
3860 | +[tox] |
3861 | +minversion = 1.8 |
3862 | +envlist = py27,py35,pep8 |
3863 | + |
3864 | +[testenv] |
3865 | +usedevelop=True |
3866 | + |
3867 | +# need SSH_AUTH_SOCK for bzr calls to work |
3868 | +passenv = SSH_AUTH_SOCK |
3869 | + |
3870 | +deps = -r{toxinidir}/test-requirements.txt |
3871 | +setenv = |
3872 | + JUJU_TEST_ENV = {env:JUJU_TEST_ENV:"test"} |
3873 | + JUJU_DATA = {homedir}/.local/share/juju |
3874 | + HOME = {env:HOME} |
3875 | +commands= |
3876 | + nosetests --with-coverage --cover-package=deployer deployer/tests |
3877 | + |
3878 | +[testenv:pep8] |
3879 | +commands = flake8 deployer |