~therp-nl/anybox.recipe.openerp/jbaudoux-relative_paths_resolve_conflict

« back to all changes in this revision

Viewing changes to anybox/recipe/openerp/runtime/session.py

[MRG] Update with target branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
"""Utilities to start a server process."""
2
2
import warnings
3
3
import sys
 
4
import os
4
5
import logging
 
6
from distutils.version import Version
 
7
 
 
8
 
5
9
try:
6
10
    import openerp
7
11
except ImportError:
8
12
    warnings.warn("This must be imported with a buildout openerp recipe "
9
13
                  "driven sys.path", RuntimeWarning)
10
14
else:
11
 
    from openerp.cli import server as startup
 
15
    try:
 
16
        from openerp.cli import server as startup
 
17
    except ImportError:
 
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
 
22
 
13
23
from optparse import OptionParser  # we support python >= 2.6
14
24
 
15
25
logger = logging.getLogger(__name__)
16
26
 
 
27
DEFAULT_VERSION_PARAMETER = 'buildout.db_version'
 
28
 
 
29
DEFAULT_VERSION_FILE = 'VERSION.txt'
 
30
 
 
31
 
 
32
class OpenERPVersion(Version):
 
33
    """OpenERP idea of version, wrapped in a class.
 
34
 
 
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.
 
38
    """
 
39
 
 
40
    def parse(self, incoming):
 
41
        if isinstance(incoming, OpenERPVersion):
 
42
            self.vstring = incoming.vstring
 
43
            self.components = incoming.components
 
44
        else:
 
45
            self.vstring = incoming
 
46
            self.components = parse_version(incoming)
 
47
 
 
48
    def __str__(self):
 
49
        return self.vstring
 
50
 
 
51
    def __repr__(self):
 
52
        return 'OpenERPVersion(%r)' % str(self)
 
53
 
 
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
 
59
 
 
60
        other = self.__class__(other)
 
61
        return cmp(self.components, other.components)
 
62
 
17
63
 
18
64
class Session(object):
19
 
    """A class to represent the server object.
20
 
 
21
 
    you should have exactly one per process.
22
 
 
23
 
    Before actual use, call the ``bootstrap`` method.
24
 
    Then you have useful attributes/methods behaving like unit test classes:
25
 
 
26
 
    self.cr: a cursor
27
 
    self.uid: user id
 
65
    """A class to give server-level access to one database.
 
66
 
 
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)
 
71
 
 
72
    Before actual use, call :meth:`open`.
 
73
    Then you'll have useful attributes and methods reminiscent of the unit test
 
74
    classes:
 
75
 
 
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`
 
81
 
 
82
    Example application code::
 
83
 
 
84
       session.open(db_name="my_db")
 
85
       admin = session.registry('res_users').browse(session.cr, session.uid, 1)
 
86
       (...)
 
87
       session.cr.commit()
 
88
       session.close()
 
89
 
 
90
    Transaction management is up to user code
 
91
 
 
92
    Upgrade scripts writers should check the version handling properties:
 
93
 
 
94
    * :meth:`db_version`
 
95
    * :meth:`package_version`
 
96
 
 
97
    Instantiation is done by passing the path to OpenERP main
 
98
    configuration file and the path of the buildout directory.
 
99
 
 
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`
 
107
    for details.
 
108
 
 
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
 
111
    source script.
 
112
 
 
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).
28
116
    """
29
117
 
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
 
121
 
31
122
        self._registry = self.cr = None
32
 
        config.parse_config(['-c', conffile])
 
123
        if parse_config:
 
124
            config.parse_config(['-c', conffile])
33
125
 
34
126
    def ready(self):
35
127
        return self._registry is not None
36
128
 
37
 
    def open(self, db=None):
 
129
    def open(self, db=None, with_demo=False):
 
130
        """Load the database
 
131
 
 
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.
 
135
 
 
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:
 
143
 
 
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
 
148
 
 
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.
 
153
        """
38
154
        if db is None:
39
155
            db = config['db_name']
40
156
        if not db:
41
 
            db = ''  # default to OpenERP/psycopg2/lipbq default behaviour
 
157
            db = ''  # expected value expected by OpenERP to start defaulting.
 
158
 
 
159
        cnx = openerp.sql_db.db_connect(db)
 
160
        cr = cnx.cursor()
 
161
        self.is_initialization = not(openerp.modules.db.is_initialized(cr))
 
162
        cr.close()
 
163
 
42
164
        startup.check_root_user()
43
165
        startup.check_postgres_user()
44
166
        openerp.netsvc.init_logger()
 
167
 
 
168
        saved_without_demo = config['without_demo']
 
169
        if with_demo is None:
 
170
            with_demo = config['without_demo']
 
171
 
 
172
        config['without_demo'] = not with_demo
 
173
        self.with_demo = with_demo
 
174
 
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
 
180
 
 
181
    # A later version might read that from buildout configuration.
 
182
    _version_parameter_name = DEFAULT_VERSION_PARAMETER
 
183
 
 
184
    @property
 
185
    def version_file_path(self):
 
186
        """Absolute path of the flat file storing the package version.
 
187
 
 
188
        For now this is not configurable, a later version might read it
 
189
        from buildout configuration.
 
190
        """
 
191
        return os.path.join(self.buildout_dir, DEFAULT_VERSION_FILE)
 
192
 
 
193
    def parse_version_string(self, vstring):
 
194
        """Stable method for downstream code needing to instantiate a version.
 
195
 
 
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.
 
200
        """
 
201
        return OpenERPVersion(vstring)
 
202
 
 
203
    @property
 
204
    def db_version(self):
 
205
        """Settable property for version stored in DB of the whole buildout.
 
206
 
 
207
        This can be thought as the latest version to which the DB has been
 
208
        upgraded to.
 
209
        A simple caching system to avoid querying the DB multiple times is
 
210
        implemented.
 
211
        """
 
212
        db_version = getattr(self, '_db_version', None)
 
213
        if db_version is not None:
 
214
            return db_version
 
215
 
 
216
        db_version = self.registry('ir.config_parameter').get_param(
 
217
            self.cr, self.uid, self._version_parameter_name)
 
218
        if not db_version:
 
219
            # as usual OpenERP thinks its simpler to use False as None
 
220
            # restoring sanity ASAP
 
221
            db_version = None
 
222
        else:
 
223
            db_version = OpenERPVersion(db_version)
 
224
        self._db_version = db_version
 
225
        return db_version
 
226
 
 
227
    @db_version.setter
 
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)
 
232
 
 
233
    @property
 
234
    def package_version(self):
 
235
        """Property reading the version file from buildout directory.
 
236
 
 
237
        Comments introduced with a hash are accepted.
 
238
        Only the first significant line is taken into account.
 
239
        """
 
240
        pkg_version = getattr(self, '_pkg_version', None)
 
241
        if pkg_version is not None:
 
242
            return pkg_version
 
243
 
 
244
        try:
 
245
            with open(self.version_file_path) as f:
 
246
                for line in f:
 
247
                    line = line.split('#', 1)[0].strip()
 
248
                    if not line:
 
249
                        continue
 
250
                    self._pkg_version = OpenERPVersion(line)
 
251
                    return self._pkg_version
 
252
        except IOError:
 
253
            logger.info("No version file could be read, "
 
254
                        "package version considered to be None")
 
255
 
 
256
    def update_modules_list(self):
 
257
        """Update the list of available OpenERP modules, like the UI allows to.
 
258
 
 
259
        This is necessary prior to install of any new module.
 
260
        """
 
261
        self.registry('ir.module.module').update_list(self.cr, self.uid)
48
262
 
49
263
    def init_cursor(self):
50
264
        self.cr = self._registry.db.cursor()
51
265
 
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)
55
269
 
56
270
    def rollback(self):
57
271
        self.cr.rollback()
58
272
 
59
273
    def close(self):
 
274
        """Close the cursor and forget about the current database.
 
275
 
 
276
        The session is thus ready to open another database.
 
277
        """
 
278
        dbname = self.cr.dbname
60
279
        self.cr.close()
 
280
        openerp.modules.registry.RegistryManager.delete(dbname)
 
281
 
 
282
    def update_modules(self, modules, db=None):
 
283
        """Update the prescribed modules in the database.
 
284
 
 
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.
 
295
        """
 
296
        if db is None:
 
297
            if self.cr is None:
 
298
                raise ValueError("update_modules needs either the session to "
 
299
                                 "be opened or an explicit database name")
 
300
            db = self.cr.dbname
 
301
 
 
302
        if self.cr is not None:
 
303
            self.close()
 
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()
 
309
        self.init_cursor()
 
310
 
 
311
    def install_modules(self, modules, db=None, update_modules_list=True,
 
312
                        open_with_demo=False):
 
313
        """Install the modules in the database.
 
314
 
 
315
        Has the side effect of closing the current cursor, committing if and
 
316
        only if the list of modules is updated.
 
317
 
 
318
        Demo data loading is handled consistently with the decision taken
 
319
        by :meth:`open`.
 
320
 
 
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
 
331
                               :meth:`open`.
 
332
        """
 
333
        already_open = self.cr is not None
 
334
        if db is None:
 
335
            if not already_open:
 
336
                raise ValueError("install_modules needs either the session to "
 
337
                                 "be opened or an explicit database name")
 
338
            db = self.cr.dbname
 
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)
 
342
 
 
343
        if update_modules_list:
 
344
            self.update_modules_list()
 
345
            self.cr.commit()
 
346
 
 
347
        if self.cr is not None:
 
348
            self.close()
 
349
        saved_without_demo = config['without_demo']
 
350
 
 
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
 
360
        self.init_cursor()
61
361
 
62
362
    def handle_command_line_options(self, to_handle):
63
363
        """Handle prescribed command line options and eat them.
64
364
 
65
 
        Anything before first occurrence of '--' is ours and removed from
66
 
        sys.argv.
 
365
        Anything before first occurrence of ``--`` on the command-line is taken
 
366
        into account and removed from ``sys.argv``.
67
367
 
68
368
        Help messages:
69
369
 
118
418
                logger.info("No database specified, using the one specified "
119
419
                            "in buildout configuration.")
120
420
            self.open(db=options.db_name)
121
 
 
122
 
_imported_addons = set()
123
 
 
124
 
 
125
 
def already_imported(module_name):
126
 
    name = module_name.rsplit('.', 1)[-1]
127
 
    if name in _imported_addons:
128
 
        return True
129
 
    _imported_addons.add(name)
130
 
    return False
131
 
 
132
 
 
133
 
def clear_import_registry():
134
 
    _imported_addons.clear()