Merge lp:~pieter-equinox/reahl/reahl-declarative into lp:~iv/reahl/reahl-declarative

Proposed by Pieter Nagel
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
Reviewer Review Type Date Requested Status
Iwan Vosloo Pending
Review via email: mp+231185@code.launchpad.net

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.

To post a comment you must log in.
97. By Pieter Nagel

Add test for the case where SecuredDeclaration.__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.setdefaultencoding 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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):

Subscribers

People subscribed via source and target branches

to all changes: