Merge lp:~pieter-equinox/reahl/reahl-declarative into lp:~iv/reahl/reahl-declarative
- reahl-declarative
- Merge into reahl-declarative
Status: | Merged |
---|---|
Merged at revision: | 85 |
Proposed branch: | lp:~pieter-equinox/reahl/reahl-declarative |
Merge into: | lp:~iv/reahl/reahl-declarative |
Diff against target: |
875 lines (+300/-112) (has conflicts) 17 files modified
reahl-bzrsupport/reahl/bzrsupport.py (+31/-33) reahl-bzrsupport/reahl/bzrsupport_dev/bzrtests.py (+2/-2) reahl-component/reahl/component/config.py (+48/-4) reahl-component/reahl/component/eggs.py (+1/-3) reahl-component/reahl/component/i18n.py (+2/-0) reahl-component/reahl/component/modelinterface.py (+10/-10) reahl-component/reahl/component_dev/domainaccesscontrol.py (+22/-2) reahl-dev/reahl_dev.egg-info/SOURCES.txt (+3/-1) reahl-dev/reahl_dev.egg-info/entry_points.txt (+73/-0) reahl-mailutil/reahl/mailutil/mail.py (+6/-6) reahl-stubble/ideas/stubble.py (+1/-1) reahl-stubble/reahl/stubble/intercept.py (+28/-10) reahl-stubble/reahl/stubble/stub.py (+6/-6) reahl-stubble/reahl/stubble_dev/EasterEggTests.py (+2/-2) reahl-stubble/reahl/stubble_dev/InterceptTests.py (+55/-17) reahl-tofu/reahl/tofu/files.py (+6/-12) reahl-tofu/reahl/tofu/fixture.py (+4/-3) Text conflict in reahl-dev/reahl_dev.egg-info/entry_points.txt |
To merge this branch: | bzr merge lp:~pieter-equinox/reahl/reahl-declarative |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Iwan Vosloo | Pending | ||
Review via email:
|
Commit message
Description of the change
More Python 3 compatibility.
NB: This deprecates some end-user visible functionality, see individual commits. Esp relevant is ability of stubble to call replaced() for unbound methods and replace them at a class-level. Reahl does not use this, and Python 3 does not support the current implementation, so I chose to just deprecate the feature under Py2 and not support it under Py3.
- 97. By Pieter Nagel
-
Add test for the case where SecuredDeclarat
ion.__get_ _ is used to get an unbound method - 98. By Pieter Nagel
-
Python3 compatibility for SecuredDeclaration in bound case
- 99. By Pieter Nagel
-
SecuredDeclaration works as descriptor for getting unbound method under Python 3 too
- 100. By Pieter Nagel
-
Ignore bogus sys.setdefaulte
ncoding under PY3. Ivan, please read comment in this commit and strongly consider ripping it out under PY2 as well - 101. By Pieter Nagel
-
dictionary.values() is a view under python3
- 102. By Pieter Nagel
-
Fix file handle leak exposed by Python 3
- 103. By Pieter Nagel
-
Remove dead code that fails under Py3. Reahl-component now passes tests under Py3
- 104. By Pieter Nagel
-
Fix reading text out of TemporaryFiles opend in binary mode. Use open() to open files. bzrsupport now works in Python 3
- 105. By Pieter Nagel
-
Small refactoring
Preview Diff
1 | === modified file 'reahl-bzrsupport/reahl/bzrsupport.py' |
2 | --- reahl-bzrsupport/reahl/bzrsupport.py 2014-08-16 06:54:45 +0000 |
3 | +++ reahl-bzrsupport/reahl/bzrsupport.py 2014-08-19 13:24:49 +0000 |
4 | @@ -112,49 +112,47 @@ |
5 | return None |
6 | |
7 | def uses_bzr(self): |
8 | - with TemporaryFile() as err: |
9 | - with TemporaryFile() as out: |
10 | - try: |
11 | - return_code = Executable('bzr').call(['info', self.directory], stdout=out, stderr=err) |
12 | - return return_code == 0 |
13 | - except Exception as ex: |
14 | - logging.error('Error trying to execute "bzr info %s": %s' % (self.directory, ex)) |
15 | - return False |
16 | + with open(os.devnull, 'w') as DEVNULL: |
17 | + try: |
18 | + return_code = Executable('bzr').call(['info', self.directory], stdout=DEVNULL, stderr=DEVNULL) |
19 | + return return_code == 0 |
20 | + except Exception as ex: |
21 | + logging.error('Error trying to execute "bzr info %s": %s' % (self.directory, ex)) |
22 | + return False |
23 | |
24 | def inventory(self): |
25 | - with TemporaryFile() as err: |
26 | - with TemporaryFile() as out: |
27 | + with TemporaryFile(mode='w+') as err: |
28 | + with TemporaryFile(mode='w+') as out: |
29 | bzr_args = 'inventory %s --kind=file' % self.directory |
30 | try: |
31 | return_code = Executable('bzr').call(bzr_args.split(), stdout=out, stderr=err) |
32 | out.seek(0) |
33 | err.seek(0) |
34 | if not err.read(): |
35 | - files = out.read().decode().split('\n') |
36 | + files = out.read().split('\n') |
37 | return files |
38 | except Exception as ex: |
39 | logging.error('Error trying to execute "bzr %s": %s' % (bzr_args, ex)) |
40 | return [''] |
41 | |
42 | def bzr_installed(self): |
43 | - with TemporaryFile() as err: |
44 | - with TemporaryFile() as out: |
45 | - try: |
46 | - return_code = Executable('bzr').call([], stdout=out, stderr=err, shell=True) |
47 | - return return_code == 0 |
48 | - except OSError as ex: |
49 | - if ex.errno == os.errno.ENOENT: |
50 | - return False |
51 | - else: |
52 | - logging.error('Error trying to execute "bzr": %s' % ex) |
53 | - except ExecutableNotInstalledException as ex: |
54 | - pass |
55 | - except Exception as ex: |
56 | + with open(os.devnull, 'w') as DEVNULL: |
57 | + try: |
58 | + return_code = Executable('bzr').call([], stdout=DEVNULL, stderr=DEVNULL, shell=True) |
59 | + return return_code == 0 |
60 | + except OSError as ex: |
61 | + if ex.errno == os.errno.ENOENT: |
62 | + return False |
63 | + else: |
64 | logging.error('Error trying to execute "bzr": %s' % ex) |
65 | - return False |
66 | + except ExecutableNotInstalledException as ex: |
67 | + pass |
68 | + except Exception as ex: |
69 | + logging.error('Error trying to execute "bzr": %s' % ex) |
70 | + return False |
71 | |
72 | def commit(self, message, unchanged=False): |
73 | - with file(os.devnull, 'w') as DEVNULL: |
74 | + with open(os.devnull, 'w') as DEVNULL: |
75 | args = '-m %s' % message |
76 | if unchanged: |
77 | args += ' --unchanged' |
78 | @@ -162,20 +160,20 @@ |
79 | return return_code == 0 |
80 | |
81 | def is_version_controlled(self): |
82 | - with file(os.devnull, 'w') as DEVNULL: |
83 | + with open(os.devnull, 'w') as DEVNULL: |
84 | return_code = Executable('bzr').call('info'.split(), cwd=self.directory, stdout=DEVNULL, stderr=DEVNULL) |
85 | return return_code == 0 |
86 | |
87 | def is_checked_in(self): |
88 | - with TemporaryFile() as out: |
89 | + with TemporaryFile(mode='w+') as out: |
90 | return_code = Executable('bzr').call('status'.split(), cwd=self.directory, stdout=out, stderr=out) |
91 | out.seek(0) |
92 | return return_code == 0 and not out.read() |
93 | |
94 | @property |
95 | def last_commit_time(self): |
96 | - with TemporaryFile() as out: |
97 | - with file(os.devnull, 'w') as DEVNULL: |
98 | + with TemporaryFile(mode='w+') as out: |
99 | + with open(os.devnull, 'w') as DEVNULL: |
100 | Executable('bzr').check_call('log -r -1'.split(), cwd=self.directory, stdout=out, stderr=DEVNULL) |
101 | out.seek(0) |
102 | [timestamp] = [line for line in out if line.startswith('timestamp')] |
103 | @@ -183,13 +181,13 @@ |
104 | return datetime.datetime.strptime(timestamp, 'timestamp: %a %Y-%m-%d %H:%M:%S') |
105 | |
106 | def tag(self, tag_string): |
107 | - with file(os.devnull, 'w') as DEVNULL: |
108 | + with open(os.devnull, 'w') as DEVNULL: |
109 | Executable('bzr').check_call(('tag %s' % tag_string).split(), cwd=self.directory, stdout=DEVNULL, stderr=DEVNULL) |
110 | |
111 | def get_tags(self, head_only=False): |
112 | tags = [] |
113 | - with TemporaryFile() as out: |
114 | - with file(os.devnull, 'w') as DEVNULL: |
115 | + with TemporaryFile(mode='w+') as out: |
116 | + with open(os.devnull, 'w') as DEVNULL: |
117 | head_only = ' -r -1 ' if head_only else '' |
118 | Executable('bzr').check_call(('tags'+head_only).split(), cwd=self.directory, stdout=out, stderr=DEVNULL) |
119 | out.seek(0) |
120 | |
121 | === modified file 'reahl-bzrsupport/reahl/bzrsupport_dev/bzrtests.py' |
122 | --- reahl-bzrsupport/reahl/bzrsupport_dev/bzrtests.py 2014-07-06 10:25:15 +0000 |
123 | +++ reahl-bzrsupport/reahl/bzrsupport_dev/bzrtests.py 2014-08-19 13:24:49 +0000 |
124 | @@ -33,7 +33,7 @@ |
125 | def new_bzr_directory(self, initialised=True): |
126 | bzr_directory = temp_dir() |
127 | if initialised: |
128 | - with file(os.devnull, 'w') as DEVNULL: |
129 | + with open(os.devnull, 'w') as DEVNULL: |
130 | Executable('bzr').check_call(['init'], cwd=bzr_directory.name, stdout=DEVNULL, stderr=DEVNULL) |
131 | return bzr_directory |
132 | |
133 | @@ -54,7 +54,7 @@ |
134 | bzr = Bzr(fixture.bzr_directory.name) |
135 | vassert( bzr.is_checked_in() ) |
136 | |
137 | - file(os.path.join(fixture.bzr_directory.name, 'afile'), 'w').close() |
138 | + open(os.path.join(fixture.bzr_directory.name, 'afile'), 'w').close() |
139 | vassert( not bzr.is_checked_in() ) |
140 | |
141 | @test(BzrFixture) |
142 | |
143 | === modified file 'reahl-component/reahl/component/config.py' |
144 | --- reahl-component/reahl/component/config.py 2014-07-06 10:25:15 +0000 |
145 | +++ reahl-component/reahl/component/config.py 2014-08-19 13:24:49 +0000 |
146 | @@ -19,6 +19,7 @@ |
147 | from __future__ import unicode_literals |
148 | from __future__ import print_function |
149 | import sys |
150 | +import six |
151 | import os.path |
152 | import logging |
153 | import tempfile |
154 | @@ -256,9 +257,51 @@ |
155 | self.in_production = in_production |
156 | |
157 | def configure(self, validate=True): |
158 | - #http://mail.python.org/pipermail/tutor/2005-August/040993.html |
159 | - imp.reload(sys); #read setdefaultencoding python docs - it "enables" the method again |
160 | - sys.setdefaultencoding('utf-8') |
161 | + if six.PY2: |
162 | + # NO! Doing this is WRONG. The fact that you had to hack around |
163 | + # sys.setdefaultencoding being removed should be a clue that you |
164 | + # are doing something bogus. |
165 | + # |
166 | + # The default encoding is a property of the end-user system that |
167 | + # reahl is running on, and they are free to configure it to |
168 | + # anything else. You just broke reahl on all systems not configured |
169 | + # to be utf-8. |
170 | + # |
171 | + # The best way to think of this is to think that each and every |
172 | + # external system around reahl that receives strings as a sequences |
173 | + # of bytes can have its own encoding, possibly different from all |
174 | + # others. |
175 | + # |
176 | + # So we have: |
177 | + # |
178 | + # HTTP |
179 | + # ^ |
180 | + # | (utf-8, unless otherwise specified in headers etc.) |
181 | + # v |
182 | + # Command-Line <--> Reahl <--> |
183 | + # (stdout, |
184 | + # stderr) |
185 | + # |
186 | + # sys.setdefaultencoding is intended for the command-line, so that |
187 | + # unicode you print to stdout etc. converts correctly to the end-user |
188 | + # system's chosen encoding. |
189 | + # |
190 | + # It also governs the encoding used when you open files in text mode |
191 | + # and read/write unicode out of or into them. If those files originate |
192 | + # from the end-user's system they *are* most likely encoded in that |
193 | + # system's default encoding and you should not much with that. If |
194 | + # those files originate from some other protocol, you need to |
195 | + # explicitly specify that protocol's encoding as per its spec. |
196 | + # |
197 | + # Most likely you do sys.setdefaultencoding('utf-8') so that you |
198 | + # won't need to specify the parameter to encode() every time you |
199 | + # send bytes to HTTP. But your code specifies the parameter to each |
200 | + # and every encode anyway. |
201 | + # |
202 | + |
203 | + #http://mail.python.org/pipermail/tutor/2005-August/040993.html |
204 | + imp.reload(sys); #read setdefaultencoding python docs - it "enables" the method again |
205 | + sys.setdefaultencoding('utf-8') |
206 | |
207 | self.configure_logging() |
208 | logging.getLogger(__name__).info('Using config in %s' % self.config_directory) |
209 | @@ -316,7 +359,8 @@ |
210 | file_path = os.path.join(self.config_directory, new_config.filename) |
211 | if os.path.isfile(file_path): |
212 | locals_dict = ConfigAsDict(self) |
213 | - exec(compile(open(file_path).read(), file_path, 'exec'), globals(), locals_dict) |
214 | + with open(file_path) as f: |
215 | + exec(compile(f.read(), file_path, 'exec'), globals(), locals_dict) |
216 | locals_dict.update_required(new_config.config_key) |
217 | else: |
218 | message = 'file "%s" not found, using defaults' % file_path |
219 | |
220 | === modified file 'reahl-component/reahl/component/eggs.py' |
221 | --- reahl-component/reahl/component/eggs.py 2014-08-19 06:30:42 +0000 |
222 | +++ reahl-component/reahl/component/eggs.py 2014-08-19 13:24:49 +0000 |
223 | @@ -21,7 +21,6 @@ |
224 | import os |
225 | import os.path |
226 | import logging |
227 | -import copy |
228 | |
229 | from pkg_resources import Requirement |
230 | from pkg_resources import iter_entry_points |
231 | @@ -91,7 +90,6 @@ |
232 | |
233 | def get_ordered_classes_exported_on(self, entry_point): |
234 | entry_point_dict = self.distribution.get_entry_map().get(entry_point, {}) |
235 | - eps = copy.copy(entry_point_dict.values()) |
236 | found_eps = set() |
237 | for ep in entry_point_dict.values(): |
238 | if ep in found_eps: |
239 | @@ -252,7 +250,7 @@ |
240 | for i in cls.compute_ordered_dependent_distributions(main_egg): |
241 | entry_map = i.get_entry_map('reahl.eggs') |
242 | if entry_map: |
243 | - classes = entry_map.values() |
244 | + classes = list(entry_map.values()) |
245 | assert len(classes) == 1, 'Only one eggdeb class per egg allowed' |
246 | interfaces.append(classes[0].load()(i)) |
247 | |
248 | |
249 | === modified file 'reahl-component/reahl/component/i18n.py' |
250 | --- reahl-component/reahl/component/i18n.py 2014-07-06 10:25:15 +0000 |
251 | +++ reahl-component/reahl/component/i18n.py 2014-08-19 13:24:49 +0000 |
252 | @@ -46,6 +46,8 @@ |
253 | for locale_dir in package.__path__: |
254 | if not isinstance(translation, Translations): |
255 | translation = Translations.load(dirname=locale_dir, locales=[locale], domain=domain) |
256 | + # Babel 1.3 bug under Python 3: files is a filter object, not a list like in Python 2 |
257 | + translation.files = list(translation.files) |
258 | else: |
259 | translation.merge(Translations.load(dirname=locale_dir, locales=[locale], domain=domain)) |
260 | self.translations[(locale, domain)] = translation |
261 | |
262 | === modified file 'reahl-component/reahl/component/modelinterface.py' |
263 | --- reahl-component/reahl/component/modelinterface.py 2014-08-13 17:49:28 +0000 |
264 | +++ reahl-component/reahl/component/modelinterface.py 2014-08-19 13:24:49 +0000 |
265 | @@ -1051,7 +1051,8 @@ |
266 | |
267 | |
268 | class SecuredMethod(object): |
269 | - def __init__(self, to_be_called, secured_declaration): |
270 | + def __init__(self, instance, to_be_called, secured_declaration): |
271 | + self.instance = instance |
272 | self.to_be_called = to_be_called |
273 | self.secured_declaration = secured_declaration |
274 | |
275 | @@ -1060,21 +1061,19 @@ |
276 | raise AccessRestricted() |
277 | return self.to_be_called(*args, **kwargs) |
278 | |
279 | - def check_right(self, right_to_check, called_self, *args, **kwargs): |
280 | - args_to_send = args |
281 | - if called_self: |
282 | - args_to_send = [called_self]+list(args) |
283 | + def check_right(self, right_to_check, *args, **kwargs): |
284 | if right_to_check: |
285 | + args_to_send = (args if self.instance is None |
286 | + else (self.instance,)+args) |
287 | return right_to_check(*args_to_send, **kwargs) |
288 | else: |
289 | return True |
290 | |
291 | def read_check(self, *args, **kwargs): |
292 | - return self.check_right(self.secured_declaration.read_check, six.get_method_self(self.to_be_called), *args, **kwargs) |
293 | + return self.check_right(self.secured_declaration.read_check, *args, **kwargs) |
294 | |
295 | def write_check(self, *args, **kwargs): |
296 | - return self.check_right(self.secured_declaration.write_check, six.get_method_self(self.to_be_called), *args, **kwargs) |
297 | - |
298 | + return self.check_right(self.secured_declaration.write_check, *args, **kwargs) |
299 | |
300 | |
301 | class SecuredDeclaration(object): |
302 | @@ -1130,8 +1129,9 @@ |
303 | return arg_spec.args[:positional_args_end] |
304 | |
305 | def __get__(self, instance, owner): |
306 | - method = types.MethodType(self.func, instance, owner) |
307 | - return SecuredMethod(method, self) |
308 | + method = (self.func if instance is None |
309 | + else six.create_bound_method(self.func, instance)) |
310 | + return SecuredMethod(instance, method, self) |
311 | |
312 | |
313 | secured = SecuredDeclaration #: An alias for :class:`SecuredDeclaration` |
314 | |
315 | === modified file 'reahl-component/reahl/component_dev/domainaccesscontrol.py' |
316 | --- reahl-component/reahl/component_dev/domainaccesscontrol.py 2014-07-06 10:25:15 +0000 |
317 | +++ reahl-component/reahl/component_dev/domainaccesscontrol.py 2014-08-19 13:24:49 +0000 |
318 | @@ -54,7 +54,7 @@ |
319 | |
320 | model_object = ModelObject() |
321 | |
322 | - # Case: access not allowed |
323 | + # Case: access not allowed, called via bound method |
324 | model_object.did_something = False |
325 | with expected(AccessRestricted): |
326 | model_object.do_something_write_only() |
327 | @@ -65,12 +65,32 @@ |
328 | model_object.do_something_read_only() |
329 | vassert( not model_object.did_something ) |
330 | |
331 | - # Case: access allowed |
332 | + # Case: access not allowed, called via unbound method |
333 | + model_object.did_something = False |
334 | + method = ModelObject.do_something_write_only |
335 | + with expected(AccessRestricted): |
336 | + method(model_object) |
337 | + vassert( not model_object.did_something ) |
338 | + |
339 | + model_object.did_something = False |
340 | + method = ModelObject.do_something_read_only |
341 | + with expected(AccessRestricted): |
342 | + method(model_object) |
343 | + vassert( not model_object.did_something ) |
344 | + |
345 | + # Case: access allowed, called via bound method |
346 | model_object.did_something = False |
347 | with expected(NoException): |
348 | model_object.do_something_read_and_write() |
349 | vassert( model_object.did_something ) |
350 | |
351 | + # Case: access allowed, called via unbound method |
352 | + model_object.did_something = False |
353 | + method = ModelObject.do_something_read_and_write |
354 | + with expected(NoException): |
355 | + method(model_object) |
356 | + vassert( model_object.did_something ) |
357 | + |
358 | |
359 | @test(Fixture) |
360 | def checks_must_match_signature(self, fixture): |
361 | |
362 | === modified file 'reahl-dev/reahl_dev.egg-info/SOURCES.txt' |
363 | --- reahl-dev/reahl_dev.egg-info/SOURCES.txt 2014-08-19 06:30:42 +0000 |
364 | +++ reahl-dev/reahl_dev.egg-info/SOURCES.txt 2014-08-19 13:24:49 +0000 |
365 | @@ -19,6 +19,8 @@ |
366 | reahl_dev.egg-info/SOURCES.txt |
367 | reahl_dev.egg-info/dependency_links.txt |
368 | reahl_dev.egg-info/entry_points.txt |
369 | +reahl_dev.egg-info/entry_points.txt.~1~ |
370 | reahl_dev.egg-info/namespace_packages.txt |
371 | reahl_dev.egg-info/requires.txt |
372 | -reahl_dev.egg-info/top_level.txt |
373 | \ No newline at end of file |
374 | +reahl_dev.egg-info/top_level.txt |
375 | +reahl_dev.egg-info/top_level.txt.~1~ |
376 | \ No newline at end of file |
377 | |
378 | === modified file 'reahl-dev/reahl_dev.egg-info/entry_points.txt' |
379 | --- reahl-dev/reahl_dev.egg-info/entry_points.txt 2014-08-17 12:45:35 +0000 |
380 | +++ reahl-dev/reahl_dev.egg-info/entry_points.txt 2014-08-19 13:24:49 +0000 |
381 | @@ -1,4 +1,5 @@ |
382 | [reahl.dev.xmlclasses] |
383 | +<<<<<<< TREE |
384 | NamespaceList = reahl.dev.devdomain:NamespaceList |
385 | DebianPackage = reahl.dev.devdomain:DebianPackage |
386 | ScriptExport = reahl.dev.devdomain:ScriptExport |
387 | @@ -7,7 +8,19 @@ |
388 | ThirdpartyDependency = reahl.dev.devdomain:ThirdpartyDependency |
389 | EntryPointExport = reahl.dev.devdomain:EntryPointExport |
390 | MigrationList = reahl.dev.devdomain:MigrationList |
391 | +======= |
392 | +NamespaceList = reahl.dev.devdomain:NamespaceList |
393 | +ChickenProject = reahl.dev.devdomain:ChickenProject |
394 | +ShippedFile = reahl.dev.devdomain:ShippedFile |
395 | +TranslationPackage = reahl.dev.devdomain:TranslationPackage |
396 | +CommandAlias = reahl.dev.devdomain:CommandAlias |
397 | +XMLDependencyList = reahl.dev.devdomain:XMLDependencyList |
398 | +OrderedPersistedClass = reahl.dev.devdomain:OrderedPersistedClass |
399 | +FileList = reahl.dev.devdomain:FileList |
400 | +Project = reahl.dev.devdomain:Project |
401 | +>>>>>>> MERGE-SOURCE |
402 | NamespaceEntry = reahl.dev.devdomain:NamespaceEntry |
403 | +<<<<<<< TREE |
404 | FileList = reahl.dev.devdomain:FileList |
405 | MetaInfo = reahl.dev.devdomain:MetaInfo |
406 | DebianPackageSet = reahl.dev.devdomain:DebianPackageSet |
407 | @@ -29,13 +42,68 @@ |
408 | ShippedFile = reahl.dev.devdomain:ShippedFile |
409 | XMLDependencyList = reahl.dev.devdomain:XMLDependencyList |
410 | ChickenProject = reahl.dev.devdomain:ChickenProject |
411 | +======= |
412 | +EggProject = reahl.dev.devdomain:EggProject |
413 | +ProjectTag = reahl.dev.devdomain:ProjectTag |
414 | +DebianPackageSet = reahl.dev.devdomain:DebianPackageSet |
415 | +AttachmentList = reahl.dev.devdomain:AttachmentList |
416 | +MigrationList = reahl.dev.devdomain:MigrationList |
417 | +ExtraPath = reahl.dev.devdomain:ExtraPath |
418 | +ScheduledJobSpec = reahl.dev.devdomain:ScheduledJobSpec |
419 | +MetaInfo = reahl.dev.devdomain:MetaInfo |
420 | +PersistedClassesList = reahl.dev.devdomain:PersistedClassesList |
421 | +BzrSourceControl = reahl.dev.devdomain:BzrSourceControl |
422 | +ExtrasList = reahl.dev.devdomain:ExtrasList |
423 | +Dependency = reahl.dev.devdomain:Dependency |
424 | +ExcludedPackage = reahl.dev.devdomain:ExcludedPackage |
425 | +EntryPointExport = reahl.dev.devdomain:EntryPointExport |
426 | +PythonSourcePackage = reahl.dev.devdomain:PythonSourcePackage |
427 | +HardcodedMetadata = reahl.dev.devdomain:HardcodedMetadata |
428 | +PackageIndex = reahl.dev.devdomain:PackageIndex |
429 | +ScriptExport = reahl.dev.devdomain:ScriptExport |
430 | +ConfigurationSpec = reahl.dev.devdomain:ConfigurationSpec |
431 | +SshRepository = reahl.dev.devdomain:SshRepository |
432 | +DebianPackage = reahl.dev.devdomain:DebianPackage |
433 | +ThirdpartyDependency = reahl.dev.devdomain:ThirdpartyDependency |
434 | +>>>>>>> MERGE-SOURCE |
435 | DebianPackageMetadata = reahl.dev.devdomain:DebianPackageMetadata |
436 | +<<<<<<< TREE |
437 | ScheduledJobSpec = reahl.dev.devdomain:ScheduledJobSpec |
438 | PythonSourcePackage = reahl.dev.devdomain:PythonSourcePackage |
439 | +======= |
440 | + |
441 | +[reahl.dev.commands] |
442 | +UpdateAptRepository = reahl.dev.devshell:UpdateAptRepository |
443 | +DebInstall = reahl.dev.devshell:DebInstall |
444 | +List = reahl.dev.devshell:List |
445 | +MarkReleased = reahl.dev.devshell:MarkReleased |
446 | +SubstVars = reahl.dev.devshell:SubstVars |
447 | +ServeSMTP = reahl.dev.mailtest:ServeSMTP |
448 | +Upload = reahl.dev.devshell:Upload |
449 | +Select = reahl.dev.devshell:Select |
450 | +ListSelections = reahl.dev.devshell:ListSelections |
451 | +ListMissingDependencies = reahl.dev.devshell:ListMissingDependencies |
452 | +ExtractMessages = reahl.dev.devshell:ExtractMessages |
453 | +ClearSelection = reahl.dev.devshell:ClearSelection |
454 | +Shell = reahl.dev.devshell:Shell |
455 | +AddLocale = reahl.dev.devshell:AddLocale |
456 | +Refresh = reahl.dev.devshell:Refresh |
457 | +CompileTranslations = reahl.dev.devshell:CompileTranslations |
458 | +Debianise = reahl.dev.devshell:Debianise |
459 | +Read = reahl.dev.devshell:Read |
460 | +DeleteSelection = reahl.dev.devshell:DeleteSelection |
461 | +MergeTranslations = reahl.dev.devshell:MergeTranslations |
462 | +Save = reahl.dev.devshell:Save |
463 | +Build = reahl.dev.devshell:Build |
464 | +Info = reahl.dev.devshell:Info |
465 | +ExplainLegend = reahl.dev.devshell:ExplainLegend |
466 | +Setup = reahl.dev.devshell:Setup |
467 | +>>>>>>> MERGE-SOURCE |
468 | |
469 | [console_scripts] |
470 | reahl = reahl.dev.devshell:WorkspaceCommandline.execute_one |
471 | |
472 | +<<<<<<< TREE |
473 | [reahl.eggs] |
474 | Egg = reahl.component.eggs:ReahlEgg |
475 | |
476 | @@ -66,3 +134,8 @@ |
477 | CompileTranslations = reahl.dev.devshell:CompileTranslations |
478 | Select = reahl.dev.devshell:Select |
479 | |
480 | +======= |
481 | +[reahl.eggs] |
482 | +Egg = reahl.component.eggs:ReahlEgg |
483 | + |
484 | +>>>>>>> MERGE-SOURCE |
485 | |
486 | === modified file 'reahl-mailutil/reahl/mailutil/mail.py' |
487 | --- reahl-mailutil/reahl/mailutil/mail.py 2014-07-06 10:25:15 +0000 |
488 | +++ reahl-mailutil/reahl/mailutil/mail.py 2014-08-19 13:24:49 +0000 |
489 | @@ -22,8 +22,8 @@ |
490 | import re |
491 | import smtplib |
492 | import logging |
493 | -import email.MIMEMultipart |
494 | -import email.MIMEText |
495 | +from six.moves import email_mime_multipart as MIMEMultipart |
496 | +from six.moves import email_mime_text as MIMEText |
497 | |
498 | from reahl.component.context import ExecutionContext |
499 | from reahl.mailutil.rst import RestructuredText |
500 | @@ -50,7 +50,7 @@ |
501 | self.to_addresses = to_addresses |
502 | self.subject = subject |
503 | self.rst_text = rst_message |
504 | - self.message_root = email.MIMEMultipart.MIMEMultipart('related') |
505 | + self.message_root = MIMEMultipart.MIMEMultipart('related') |
506 | self.message_root['Subject'] = subject |
507 | self.message_root['From'] = from_address |
508 | self.message_root['To'] = ", ".join(to_addresses) |
509 | @@ -58,14 +58,14 @@ |
510 | |
511 | # Encapsulate the plain and HTML versions of the message body in an |
512 | # 'alternative' part, so message agents can decide which they want to display. |
513 | - self.message_alternative = email.MIMEMultipart.MIMEMultipart('alternative') |
514 | + self.message_alternative = MIMEMultipart.MIMEMultipart('alternative') |
515 | self.message_root.attach(self.message_alternative) |
516 | |
517 | - message_text = email.MIMEText.MIMEText(rst_message.encode(charset), 'plain', charset) |
518 | + message_text = MIMEText.MIMEText(rst_message.encode(charset), 'plain', charset) |
519 | self.message_alternative.attach(message_text) |
520 | |
521 | rst = RestructuredText(rst_message) |
522 | - message_text = email.MIMEText.MIMEText(rst.as_HTML_fragment().encode(charset), 'html', charset) |
523 | + message_text = MIMEText.MIMEText(rst.as_HTML_fragment().encode(charset), 'html', charset) |
524 | self.message_alternative.attach(message_text) |
525 | |
526 | def as_string(self): |
527 | |
528 | === modified file 'reahl-stubble/ideas/stubble.py' |
529 | --- reahl-stubble/ideas/stubble.py 2014-08-16 11:03:09 +0000 |
530 | +++ reahl-stubble/ideas/stubble.py 2014-08-19 13:24:49 +0000 |
531 | @@ -67,7 +67,7 @@ |
532 | stub_args = inspect.getargspec(self.stub) |
533 | assert real_args == stub_args, 'argument specification mismatch' |
534 | |
535 | - return types.MethodType(self.stub, instance) |
536 | + return six.create_bound_method(self.stub, instance) |
537 | |
538 | def __set__(self, instance, value): |
539 | assert None, 'cannot set stub methods' |
540 | |
541 | === modified file 'reahl-stubble/reahl/stubble/intercept.py' |
542 | --- reahl-stubble/reahl/stubble/intercept.py 2014-08-16 11:42:52 +0000 |
543 | +++ reahl-stubble/reahl/stubble/intercept.py 2014-08-19 13:24:49 +0000 |
544 | @@ -17,8 +17,11 @@ |
545 | |
546 | from __future__ import unicode_literals |
547 | from __future__ import print_function |
548 | +import warnings |
549 | import sys |
550 | import io |
551 | +import six |
552 | +import inspect |
553 | from contextlib import contextmanager |
554 | |
555 | from reahl.stubble.stub import StubClass |
556 | @@ -67,8 +70,8 @@ |
557 | self.kwargs = kwargs #: The dictionary with keyword arguments passed during the call |
558 | |
559 | |
560 | -class CallMonitor(object): |
561 | - """The CallMonitor is a context manager which records calls to a single method of an object or class. |
562 | +class CallMonitorBase(object): |
563 | + """The CallMonitorBase is a context manager which records calls to a single method of an object or class. |
564 | The calls are recorded, but the original method is also executed. |
565 | |
566 | For example: |
567 | @@ -89,9 +92,9 @@ |
568 | assert monitor.calls[0].kwargs == {} |
569 | assert monitor.calls[0].return_value == 'something' |
570 | """ |
571 | - def __init__(self, method): |
572 | - self.obj = method.__self__ |
573 | - self.method_name = method.__func__.__name__ |
574 | + def __init__(self, obj, method): |
575 | + self.obj = obj |
576 | + self.method_name = method.__name__ |
577 | self.calls = [] #: A list of :class:`MonitoredCalls` made, one for each call made, in the order they were made |
578 | self.original_method = None |
579 | |
580 | @@ -114,12 +117,18 @@ |
581 | setattr(self.obj, self.method_name, self.original_method) |
582 | |
583 | |
584 | -class InitMonitor(CallMonitor): |
585 | +class CallMonitor(CallMonitorBase): |
586 | + """A :class:`CallMonitorBase` used to intercept calls to bound methods |
587 | + of a class.""" |
588 | + def __init__(self, method): |
589 | + super(CallMonitor, self).__init__(six.get_method_self(method), method) |
590 | + |
591 | + |
592 | +class InitMonitor(CallMonitorBase): |
593 | """A :class:`CallMonitor` used to intercept calls to the __init__ method |
594 | of a class.""" |
595 | def __init__(self, monitored_class): |
596 | - super(InitMonitor, self).__init__(monitored_class.__init__) |
597 | - self.obj = monitored_class |
598 | + super(InitMonitor, self).__init__(monitored_class, monitored_class.__init__) |
599 | |
600 | @property |
601 | def monitored_class(self): |
602 | @@ -168,8 +177,17 @@ |
603 | assert s.foo(2) == 'yyy' |
604 | """ |
605 | StubClass.signatures_match(method, replacement, ignore_self=True) |
606 | - target = method.im_self or method.im_class |
607 | - method_name = method.im_func.__name__ |
608 | + if inspect.isfunction(method): |
609 | + raise ValueError('%s should be a method' % method) |
610 | + method_self = six.get_method_self(method) |
611 | + if inspect.ismethod(method) and method_self is not None: |
612 | + target = method_self |
613 | + else: |
614 | + warnings.warn( |
615 | + 'Stubbing by passing in unbound methods is deprecated.', |
616 | + DeprecationWarning) |
617 | + target = method.im_class |
618 | + method_name = six.get_method_function(method).__name__ |
619 | saved_method = getattr(target, method_name) |
620 | try: |
621 | setattr(target, method_name, replacement) |
622 | |
623 | === modified file 'reahl-stubble/reahl/stubble/stub.py' |
624 | --- reahl-stubble/reahl/stubble/stub.py 2014-08-16 11:15:20 +0000 |
625 | +++ reahl-stubble/reahl/stubble/stub.py 2014-08-19 13:24:49 +0000 |
626 | @@ -16,16 +16,16 @@ |
627 | |
628 | from __future__ import unicode_literals |
629 | from __future__ import print_function |
630 | -import six |
631 | import os |
632 | import os.path |
633 | -import pkg_resources |
634 | import inspect |
635 | -import types |
636 | - |
637 | import collections |
638 | from functools import reduce |
639 | |
640 | +import six |
641 | +import pkg_resources |
642 | + |
643 | + |
644 | class StubClass(object): |
645 | def __init__(self, orig, check_attributes_also=False): |
646 | self.orig = orig |
647 | @@ -125,7 +125,7 @@ |
648 | return os.path.join(base, *resource_name.split('/')) |
649 | |
650 | def _get(self, path): |
651 | - return file(path).read() |
652 | + return open(path).read() |
653 | |
654 | |
655 | #------------------------------------------------[ Delegate ] |
656 | @@ -212,7 +212,7 @@ |
657 | if instance is None: |
658 | return self |
659 | else: |
660 | - return types.MethodType(self.value, instance) |
661 | + return six.create_bound_method(self.value, instance) |
662 | else: |
663 | return self.value |
664 | |
665 | |
666 | === modified file 'reahl-stubble/reahl/stubble_dev/EasterEggTests.py' |
667 | --- reahl-stubble/reahl/stubble_dev/EasterEggTests.py 2014-07-06 10:25:15 +0000 |
668 | +++ reahl-stubble/reahl/stubble_dev/EasterEggTests.py 2014-08-19 13:24:49 +0000 |
669 | @@ -70,7 +70,7 @@ |
670 | |
671 | @istest |
672 | def test_resource_api(self): |
673 | - test_file = NamedTemporaryFile() |
674 | + test_file = NamedTemporaryFile(mode='w+') |
675 | dirname, file_name = os.path.split(test_file.name) |
676 | |
677 | self.stub_egg.set_module_path(dirname) |
678 | @@ -80,7 +80,7 @@ |
679 | |
680 | contents = 'asdd ' |
681 | test_file.write(contents) |
682 | - test_file.seek(0) |
683 | + test_file.flush() |
684 | |
685 | as_string = pkg_resources.resource_string(self.stub_egg.as_requirement(), file_name) |
686 | assert as_string == contents |
687 | |
688 | === modified file 'reahl-stubble/reahl/stubble_dev/InterceptTests.py' |
689 | --- reahl-stubble/reahl/stubble_dev/InterceptTests.py 2014-08-14 08:49:20 +0000 |
690 | +++ reahl-stubble/reahl/stubble_dev/InterceptTests.py 2014-08-19 13:24:49 +0000 |
691 | @@ -18,6 +18,8 @@ |
692 | from __future__ import print_function |
693 | |
694 | import io |
695 | +import six |
696 | +import warnings |
697 | from nose.tools import istest, assert_raises |
698 | import tempfile |
699 | |
700 | @@ -35,13 +37,13 @@ |
701 | return y |
702 | |
703 | s = SomethingElse() |
704 | - original_method = s.foo.im_func |
705 | + original_method = six.get_method_function(s.foo) |
706 | |
707 | with CallMonitor(s.foo) as monitor: |
708 | assert s.foo(1, y='a') == 'a' |
709 | assert s.foo(2) == None |
710 | |
711 | - assert s.foo.im_func is original_method |
712 | + assert six.get_method_function(s.foo) is original_method |
713 | assert s.n == 2 |
714 | assert monitor.times_called == 2 |
715 | assert monitor.calls[0].args == (1,) |
716 | @@ -106,26 +108,65 @@ |
717 | s = SomethingElse() |
718 | def replacement(n, y=None): |
719 | return y |
720 | - original_method = s.foo.im_func |
721 | + original_method = six.get_method_function(s.foo) |
722 | |
723 | with replaced(s.foo, replacement): |
724 | assert s.foo(1, y='a') == 'a' |
725 | assert s.foo(2) == None |
726 | |
727 | - assert s.foo.im_func is original_method |
728 | + assert six.get_method_function(s.foo) is original_method |
729 | |
730 | # Case: unbound method |
731 | + """Python 3 does not support the concept of unbound methods, they are |
732 | + just plain functions without an im_class pointing back to their class. |
733 | + See https://docs.python.org/3/whatsnew/3.0.html#operators-and-special-methods, |
734 | + and https://mail.python.org/pipermail/python-dev/2005-January/050625.html |
735 | + for the rationale. |
736 | + |
737 | + If stubble wishes to support them under Python 3, the signature of |
738 | + stubble.replaced will need to change to take the class as a parameter. |
739 | + But since reahl itself does not use this feature, we just deprecate |
740 | + it under Python 2 and make it illegal under Python 3. |
741 | + |
742 | + Note that we are only talking about instance methods here, not class |
743 | + methods. Instance methods always require an instance to be called on, so |
744 | + there should always be an instance they can be stubbed on, i.e. by using |
745 | + replaced(instance.method, ...) instead of replaced(someclass.method, ...). |
746 | + """ |
747 | + |
748 | s = SomethingElse() |
749 | def replacement(self, n, y=None): |
750 | return y |
751 | - original_method = SomethingElse.foo.im_func |
752 | - |
753 | - with replaced(SomethingElse.foo, replacement): |
754 | - assert s.foo(1, y='a') == 'a' |
755 | - assert s.foo(2) == None |
756 | - assert SomethingElse.foo.im_func is not original_method |
757 | - |
758 | - assert SomethingElse.foo.im_func is original_method |
759 | + if six.PY2: |
760 | + original_method = six.get_method_function(SomethingElse.foo) |
761 | + |
762 | + with warnings.catch_warnings(record=True) as raised_warnings: |
763 | + warnings.simplefilter("always") |
764 | + with replaced(SomethingElse.foo, replacement): |
765 | + assert s.foo(1, y='a') == 'a' |
766 | + assert s.foo(2) == None |
767 | + assert six.get_method_function(SomethingElse.foo) is not original_method |
768 | + assert six.get_method_function(SomethingElse.foo) is original_method |
769 | + |
770 | + [deprecation] = raised_warnings |
771 | + assert issubclass(deprecation.category, DeprecationWarning) |
772 | + else: |
773 | + with assert_raises(ValueError): |
774 | + with replaced(SomethingElse.foo, replacement): |
775 | + pass |
776 | + |
777 | + @istest |
778 | + def test_replacing_functions_is_disallowed(self): |
779 | + """Functions can not be replaced, only methods can.""" |
780 | + |
781 | + def function1(): |
782 | + pass |
783 | + def function2(): |
784 | + pass |
785 | + |
786 | + with assert_raises(ValueError): |
787 | + with replaced(function1, function2): |
788 | + pass |
789 | |
790 | @istest |
791 | def test_replaced_signature_should_match(self): |
792 | @@ -137,16 +178,13 @@ |
793 | |
794 | |
795 | s = SomethingElse() |
796 | - original_method = s.foo.im_func |
797 | |
798 | def replacement(): |
799 | - return y |
800 | + pass |
801 | |
802 | - def with_statement(): |
803 | + with assert_raises(AssertionError): |
804 | with replaced(s.foo, replacement): |
805 | pass |
806 | - |
807 | - assert_raises(AssertionError, with_statement) |
808 | |
809 | @istest |
810 | def test_stubbed_sysout(self): |
811 | |
812 | === modified file 'reahl-tofu/reahl/tofu/files.py' |
813 | --- reahl-tofu/reahl/tofu/files.py 2014-07-28 08:03:21 +0000 |
814 | +++ reahl-tofu/reahl/tofu/files.py 2014-08-19 13:24:49 +0000 |
815 | @@ -18,7 +18,6 @@ |
816 | from __future__ import print_function |
817 | import tempfile |
818 | import os |
819 | -import io |
820 | import sys |
821 | import os.path |
822 | from contextlib import contextmanager |
823 | @@ -28,20 +27,15 @@ |
824 | 'temp_dir', 'added_sys_path', 'preserved_sys_modules'] |
825 | |
826 | |
827 | -class AutomaticallyDeletedFile(io.FileIO): |
828 | +class AutomaticallyDeletedFile: |
829 | def __init__(self, name, contents=''): |
830 | - super(AutomaticallyDeletedFile, self).__init__(name, 'w+b') |
831 | - self.write(contents) |
832 | - self.seek(0) |
833 | - def rm(self): |
834 | + self.name = name |
835 | + with open(name, 'w') as f: |
836 | + f.write(contents) |
837 | + |
838 | + def __del__(self): |
839 | if os.path.exists(self.name): |
840 | os.remove(self.name) |
841 | - def __del__(self): |
842 | - self.close() |
843 | - try: |
844 | - self.rm() |
845 | - except AttributeError: |
846 | - pass |
847 | |
848 | def file_with(name, contents): |
849 | """Creates a file with the given `name` and `contents`. The file will be deleted |
850 | |
851 | === modified file 'reahl-tofu/reahl/tofu/fixture.py' |
852 | --- reahl-tofu/reahl/tofu/fixture.py 2014-08-16 11:03:09 +0000 |
853 | +++ reahl-tofu/reahl/tofu/fixture.py 2014-08-19 13:24:49 +0000 |
854 | @@ -17,9 +17,10 @@ |
855 | |
856 | from __future__ import unicode_literals |
857 | from __future__ import print_function |
858 | +import sys |
859 | + |
860 | import six |
861 | -import sys |
862 | -import types |
863 | + |
864 | |
865 | #--------------------------------------------------[ MarkingDecorator ] |
866 | class MarkingDecorator(object): |
867 | @@ -38,7 +39,7 @@ |
868 | if instance is None: |
869 | return self |
870 | else: |
871 | - return types.MethodType(self.function, instance) |
872 | + return six.create_bound_method(self.function, instance) |
873 | |
874 | @property |
875 | def name(self): |