1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
import os
import sys
import re
import subprocess
from contextlib import contextmanager
import logging
logger = logging.getLogger(__name__)
MAJOR_VERSION_RE = re.compile(r'(\d+)[.](\d*)(\w*)')
class WorkingDirectoryKeeper(object):
"""A context manager to get back the working directory as it was before.
If you want to stack working directory keepers, you need a new instance
for each stage.
"""
active = False
def __enter__(self):
if self.active:
raise RuntimeError("Already in a working directory keeper !")
self.wd = os.getcwd()
self.active = True
def __exit__(self, *exc_args):
os.chdir(self.wd)
self.active = False
working_directory_keeper = WorkingDirectoryKeeper()
@contextmanager
def use_or_open(provided, path, *open_args):
"""A context manager to use an open file if not None or open one.
Useful for code that should be unit-testable, but work on a default file if
None is passed.
"""
if provided is not None:
yield provided
else:
with open(path, *open_args) as f:
yield f
def major_version(version_string):
"""The least common denominator of OpenERP versions : two numbers.
OpenERP version numbers are a bit hard to compare if we consider nightly
releases, bzr versions etc. It's almost impossible to compare them without
an a priori knowledge of release dates and revisions.
Here are some examples::
>>> major_version('1.2.3-foo.bar')
(1, 2)
>>> major_version('6.1-20121003-233130')
(6, 1)
>>> major_version('7.0alpha')
(7, 0)
Beware, the packaging script does funny things, such as labeling current
nightlies as 6.2-date-time whereas version_info is (7, 0, 0, ALPHA)
We can in recipe code check for >= (6, 2), that's not a big issue.
Regarding OpenERP saas releases (e.g. 7.saas~1) that are short-lived stable
versions between two "X.0" LTS releases, the second version number does not
contain a numeric value. The value X.5 will be returned (e.g. 7.5)::
>>> major_version('7.saas~1')
(7, 5)
"""
m = MAJOR_VERSION_RE.match(version_string)
if m is None:
raise ValueError("Unparseable version string: %r" % version_string)
major = int(m.group(1))
minor = m.group(2)
if not minor and m.group(3).startswith('saas'):
return major, 5
try:
return major, int(minor)
except TypeError:
raise ValueError(
"Unrecognized second version segment in %r" % version_string)
def mkdirp(path):
"""Same as mkdir -p."""
if not os.path.exists(path):
parent, name = os.path.split(path)
mkdirp(parent)
os.mkdir(path)
def is_object_file(filename):
"""True if given filename is a python object file."""
return filename.endswith('.pyc') or filename.endswith('.pyo')
def clean_object_files(directory):
"""Recursively remove object files in given directory.
Also remove resulting empty directories.
"""
dirs_to_remove = []
for dirpath, dirnames, filenames in os.walk(directory, topdown=False):
to_delete = [os.path.join(dirpath, f)
for f in filenames if is_object_file(f)]
if not dirnames and len(to_delete) == len(filenames):
dirs_to_remove.append(dirpath)
for p in to_delete:
try:
os.unlink(p)
except:
logger.exception("Error attempting to unlink %r. "
"Proceeding anyway.", p)
for d in dirs_to_remove:
try:
os.rmdir(d)
except:
logger.exception("Error attempting to rmdir %r",
"Proceeding anyway.", p)
def check_output(*popenargs, **kwargs):
r"""Backport of subprocess.check_output from python 2.7.
Example (this doctest would be more readable with ELLIPSIS, but
that's good enough for today):
>>> out = check_output(["ls", "-l", "/dev/null"])
>>> out.startswith('crw-rw-rw')
True
The stdout argument is not allowed as it is used internally.
To capture standard error in the result, use stderr=STDOUT.
>>> os.environ['LANG'] = 'C' # for uniformity of error msg
>>> err = check_output(["/bin/sh", "-c",
... "ls -l non_existent_file ; exit 0"],
... stderr=subprocess.STDOUT)
>>> err.strip().endswith("No such file or directory")
True
"""
if sys.version >= (2, 7):
return subprocess.check_output(*popenargs, **kwargs)
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
# in python 2.6, CalledProcessError.__init__ does not have output kwarg
exc = subprocess.CalledProcessError(retcode, cmd)
exc.output = output
raise exc
return output
|