1
1
"""Utilities to start a server process."""
6
from distutils.version import Version
8
12
warnings.warn("This must be imported with a buildout openerp recipe "
9
13
"driven sys.path", RuntimeWarning)
11
from openerp.cli import server as startup
16
from openerp.cli import server as startup
18
from .backports.cli import server as startup
12
19
from openerp.tools import config
20
from openerp import SUPERUSER_ID
21
from openerp.tools.parse_version import parse_version
13
23
from optparse import OptionParser # we support python >= 2.6
15
25
logger = logging.getLogger(__name__)
27
DEFAULT_VERSION_PARAMETER = 'buildout.db_version'
29
DEFAULT_VERSION_FILE = 'VERSION.txt'
32
class OpenERPVersion(Version):
33
"""OpenERP idea of version, wrapped in a class.
35
This is based on :meth:`openerp.tools.parse_version`, and
36
Provides straight-ahead comparison with tuples of integers, or
37
distutils Version classes.
40
def parse(self, incoming):
41
if isinstance(incoming, OpenERPVersion):
42
self.vstring = incoming.vstring
43
self.components = incoming.components
45
self.vstring = incoming
46
self.components = parse_version(incoming)
52
return 'OpenERPVersion(%r)' % str(self)
54
def __cmp__(self, other):
55
if isinstance(other, tuple):
56
other = '.'.join(str(s) for s in other)
57
elif not isinstance(other, self.__class__):
58
other = str(other) # Works with distutils' Version classes
60
other = self.__class__(other)
61
return cmp(self.components, other.components)
18
64
class Session(object):
19
"""A class to represent the server object.
21
you should have exactly one per process.
23
Before actual use, call the ``bootstrap`` method.
24
Then you have useful attributes/methods behaving like unit test classes:
65
"""A class to give server-level access to one database.
67
There should be exactly one instance of this class per process.
68
It can be used for any kind of script involving OpenERP API, and provides
69
facilities for upgrade scripts (see also
70
:mod:anybox.recipe.openerp.runtime.upgrade)
72
Before actual use, call :meth:`open`.
73
Then you'll have useful attributes and methods reminiscent of the unit test
76
* :attr:`cr`: a cursor
77
* :attr:`uid`: user id
78
* :attr:`registry`: access to model objects
79
* :attr:`is_initialization`: True if and only if the database was not
80
initialized before the call to :meth:`open`
82
Example application code::
84
session.open(db_name="my_db")
85
admin = session.registry('res_users').browse(session.cr, session.uid, 1)
90
Transaction management is up to user code
92
Upgrade scripts writers should check the version handling properties:
95
* :meth:`package_version`
97
Instantiation is done by passing the path to OpenERP main
98
configuration file and the path of the buildout directory.
100
Usually, instantiation code is written by the recipe in the body of the
101
executable "OpenERP scripts" it produces.
102
Script writers provide a callable that takes a
103
:class:`.Session` object argument and declare it as a console script entry
104
point in their distribution.
105
End users can reference such entry points in their buildout configurations
106
to have buildout produce the actual executable. See :doc:`/scripts`
109
Upgrade scripts are a special case of that process, in which the entry
110
point is actually provided by the recipe and rewraps a user-level
113
Later versions of the recipe may find a way to pass the whole buildout
114
configuration (recall that this is to be used in a separate process in
115
which the buildout configuration has not been parsed).
30
def __init__(self, conffile):
118
def __init__(self, conffile, buildout_dir, parse_config=True):
119
self.buildout_dir = buildout_dir
120
self.openerp_config_file = conffile
31
122
self._registry = self.cr = None
32
config.parse_config(['-c', conffile])
124
config.parse_config(['-c', conffile])
35
127
return self._registry is not None
37
def open(self, db=None):
129
def open(self, db=None, with_demo=False):
132
Loading an empty database in OpenERP has the side effect of installing
133
the ``base`` module. Whether to loading demo data or not has therefore
134
to be decided right away.
136
:param db: database name. If not specified, the same cascading of
137
defaults as OpenERP mainstream will be applied:
138
configuration file, psycopg2/lipq defaults.
139
:param with_demo: controls the loading of demo data for all
140
module installations triggered by this call to
141
:meth:`open` and further uses of :meth:`load_modules`
142
on this :class:`Session` instance:
144
* if ``True``, demo data will uniformly be loaded
145
* if ``False``, no demo data will be loaded
146
* if ``None``, demo data will be loaded according to
147
the value of ``without_demo`` in configuration
149
In all cases, the behaviour will stay consistent
150
until the next call of ``open()``, but the
151
implementation does not protect against any race
152
conditions in OpenERP internals.
39
155
db = config['db_name']
41
db = '' # default to OpenERP/psycopg2/lipbq default behaviour
157
db = '' # expected value expected by OpenERP to start defaulting.
159
cnx = openerp.sql_db.db_connect(db)
161
self.is_initialization = not(openerp.modules.db.is_initialized(cr))
42
164
startup.check_root_user()
43
165
startup.check_postgres_user()
44
166
openerp.netsvc.init_logger()
168
saved_without_demo = config['without_demo']
169
if with_demo is None:
170
with_demo = config['without_demo']
172
config['without_demo'] = not with_demo
173
self.with_demo = with_demo
45
175
self._registry = openerp.modules.registry.RegistryManager.get(
46
176
db, update_module=False)
177
config['without_demo'] = saved_without_demo
47
178
self.init_cursor()
179
self.uid = SUPERUSER_ID
181
# A later version might read that from buildout configuration.
182
_version_parameter_name = DEFAULT_VERSION_PARAMETER
185
def version_file_path(self):
186
"""Absolute path of the flat file storing the package version.
188
For now this is not configurable, a later version might read it
189
from buildout configuration.
191
return os.path.join(self.buildout_dir, DEFAULT_VERSION_FILE)
193
def parse_version_string(self, vstring):
194
"""Stable method for downstream code needing to instantiate a version.
196
This method returns an appropriate version instance, without
197
any dependency on where to import the class from. Especially useful
198
for applications whose life started before this set of utilities has
199
been used : this helps building an usable default.
201
return OpenERPVersion(vstring)
204
def db_version(self):
205
"""Settable property for version stored in DB of the whole buildout.
207
This can be thought as the latest version to which the DB has been
209
A simple caching system to avoid querying the DB multiple times is
212
db_version = getattr(self, '_db_version', None)
213
if db_version is not None:
216
db_version = self.registry('ir.config_parameter').get_param(
217
self.cr, self.uid, self._version_parameter_name)
219
# as usual OpenERP thinks its simpler to use False as None
220
# restoring sanity ASAP
223
db_version = OpenERPVersion(db_version)
224
self._db_version = db_version
228
def db_version(self, version):
229
self.registry('ir.config_parameter').set_param(
230
self.cr, self.uid, self._version_parameter_name, str(version))
231
self._db_version = OpenERPVersion(version)
234
def package_version(self):
235
"""Property reading the version file from buildout directory.
237
Comments introduced with a hash are accepted.
238
Only the first significant line is taken into account.
240
pkg_version = getattr(self, '_pkg_version', None)
241
if pkg_version is not None:
245
with open(self.version_file_path) as f:
247
line = line.split('#', 1)[0].strip()
250
self._pkg_version = OpenERPVersion(line)
251
return self._pkg_version
253
logger.info("No version file could be read, "
254
"package version considered to be None")
256
def update_modules_list(self):
257
"""Update the list of available OpenERP modules, like the UI allows to.
259
This is necessary prior to install of any new module.
261
self.registry('ir.module.module').update_list(self.cr, self.uid)
49
263
def init_cursor(self):
50
264
self.cr = self._registry.db.cursor()
52
266
def registry(self, model):
53
"""Return the model object."""
267
"""Lookup model by name and return a ready-to-work instance."""
54
268
return self._registry.get(model)
56
270
def rollback(self):
57
271
self.cr.rollback()
274
"""Close the cursor and forget about the current database.
276
The session is thus ready to open another database.
278
dbname = self.cr.dbname
280
openerp.modules.registry.RegistryManager.delete(dbname)
282
def update_modules(self, modules, db=None):
283
"""Update the prescribed modules in the database.
285
:param db: Database name. If not specified, it is assumed to have
286
already been opened with :meth:`open`, e.g, for a prior
287
read of :meth:`db_version`.
288
If it is specified, then the session in particular opens
289
that db and will use it afterwards whether another one
290
was already opened or not.
291
:param modules: any iterable of module names.
292
Not installed modules will be ignored
293
The special name ``'all'`` triggers the update of
294
all installed modules.
298
raise ValueError("update_modules needs either the session to "
299
"be opened or an explicit database name")
302
if self.cr is not None:
304
for module in modules:
305
config['update'][module] = 1
306
self._registry = openerp.modules.registry.RegistryManager.get(
307
db, update_module=True)
308
config['update'].clear()
311
def install_modules(self, modules, db=None, update_modules_list=True,
312
open_with_demo=False):
313
"""Install the modules in the database.
315
Has the side effect of closing the current cursor, committing if and
316
only if the list of modules is updated.
318
Demo data loading is handled consistently with the decision taken
321
:param db: Database name. If not specified, it is assumed to have
322
already been opened with :meth:`open`, e.g, for a prior
323
read of :meth:`db_version`.
324
If it is specified, then the session in particular opens
325
that db and will use it afterwards whether another one
326
was already opened or not.
327
:param modules: any iterable of module names.
328
:param update_modules_list: if True, will update the module lists
329
*and commit* before the install begins.
330
:param open_with_demo: if ``db`` is not None, will be passed to
333
already_open = self.cr is not None
336
raise ValueError("install_modules needs either the session to "
337
"be opened or an explicit database name")
339
elif update_modules_list and not (
340
already_open and self.cr.dbname == db):
341
self.open(db=db, with_demo=open_with_demo)
343
if update_modules_list:
344
self.update_modules_list()
347
if self.cr is not None:
349
saved_without_demo = config['without_demo']
351
# with update_modules_list=False, an explicitely named DB would not
352
# have gone through open() yet.
353
config['without_demo'] = not getattr(self, 'with_demo', open_with_demo)
354
for module in modules:
355
config['init'][module] = 1
356
self._registry = openerp.modules.registry.RegistryManager.get(
357
db, update_module=True, force_demo=self.with_demo)
358
config['init'].clear()
359
config['without_demo'] = saved_without_demo
62
362
def handle_command_line_options(self, to_handle):
63
363
"""Handle prescribed command line options and eat them.
65
Anything before first occurrence of '--' is ours and removed from
365
Anything before first occurrence of ``--`` on the command-line is taken
366
into account and removed from ``sys.argv``.