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

« back to all changes in this revision

Viewing changes to doc/scripts.rst

[MRG] Update with target branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
OpenERP Scripts
 
2
===============
 
3
 
 
4
The server recipe actually includes a general engine to install Python
 
5
code that needs access to the OpenERP API and build
 
6
configuration-aware executables.
 
7
 
 
8
As usual, it tries and do so by bridging the standard Python packaging
 
9
practice (setuptools-style console scripts, as in
 
10
``zc.recipe.egg:scripts``) and OpenERP specificities.
 
11
 
 
12
We call such scripts *OpenERP scripts* to distinguish them among the
 
13
more general concept of console scripts.
 
14
 
 
15
.. warning:: OpenERP scripts are currently supported for OpenERP ≥ 6.1 only.
 
16
 
 
17
 
 
18
Use cases
 
19
~~~~~~~~~
 
20
 
 
21
OpenERP scripts can do great in situations where an RPC script might
 
22
not be powerful enough or not practical. Some examples:
 
23
 
 
24
* specific batch jobs, especially for large databases (you get to
 
25
  control the transaction).
 
26
* introspection tools.
 
27
* general-purposes test launchers that don't have any knowledge of
 
28
  OpenERP specifics, such as ``nose``. See :ref:`command_line_options`
 
29
  for details about that.
 
30
 
 
31
OpenERP vs RPC scripts for administrative tasks
 
32
-----------------------------------------------
 
33
 
 
34
There are several Python distributions that wrap the OpenERP RPC APIs
 
35
for easy use within Python code.
 
36
 
 
37
Using an RPC script for administrative tasks usually leads to
 
38
wrap it in a shell script, with the admin password in clear text.
 
39
 
 
40
In this author's experience of applicative maintainance,
 
41
this always turns to be an
 
42
easy source of breakage that may look to be trivial at first sight but
 
43
has actually two nasty properties : it may stay unnoticed for a while,
 
44
and it lies at the interface between responsibilities.
 
45
 
 
46
In case of password change, the persons who can do it
 
47
in the database and in the system usually differ, and may not
 
48
communicate on a regular basis. In enterprise hosting environments,
 
49
you may have to explain stuff to several project managers with
 
50
different responsabilities, go through crisis management meetings,
 
51
etc. Who wants to waste hours of their life interacting with people
 
52
under stress to try and persuade them that it's only a matter of
 
53
changing an obscure password ?
 
54
 
 
55
 
 
56
OpenERP scripts vs Openerp cron jobs
 
57
------------------------------------
 
58
 
 
59
Because they are part of addons, OpenERP cron jobs also have full
 
60
unrestricted access to the internal API, and obviously don't suffer
 
61
from the password plague.
 
62
 
 
63
Some ideas to make a choice:
 
64
 
 
65
* who should control, schedule and tune execution (a system administrator or
 
66
  a functional admin)
 
67
* which one the script author finds easiest to write for
 
68
* reuse and distribution issues : OpenERP scripts are in Python
 
69
  distributions, cron jobs are in addons.
 
70
* OpenERP scripts must implement their own transaction control,
 
71
  whereas cron jobs don't bother about it but rely on the framework's
 
72
  decisions.
 
73
 
 
74
Perhaps, the best is not to choose : put the bulk of the logic in some
 
75
technical addon, it's easy to rewrap it in an OpenERP script and as a
 
76
cron job.
 
77
 
 
78
 
 
79
Declaring OpenERP Scripts
 
80
~~~~~~~~~~~~~~~~~~~~~~~~~
 
81
There are several cases, depending on the script authors
 
82
intentions. Script authors should therefore state clearly in their
 
83
documentation how to declare them.
 
84
 
 
85
Assume to fix ideas there is a Python distribution ``my.script``
 
86
that declares a console script entry point named ``my_script`` in its
 
87
``setup.py``::
 
88
 
 
89
      entry_points="""
 
90
 
 
91
      [console_scripts]
 
92
      my_script = my.script.main:run
 
93
      """
 
94
 
 
95
The first thing to do is to require that distribution using the
 
96
``eggs`` option::
 
97
 
 
98
  [my-openerp]
 
99
  (...)
 
100
  eggs = my.script
 
101
 
 
102
:ref:`How that distribution can be made available to buildout
 
103
<making_available>` is a different question.
 
104
 
 
105
Bare declararations
 
106
-------------------
 
107
The following configuration::
 
108
 
 
109
  [openerp-one]
 
110
  (...)
 
111
  openerp_scripts = my_script
 
112
 
 
113
Produces an executable ``bin/my_script-openerp-one``, that can import
 
114
OpenERP server and addons code, and in which the OpenERP configuration
 
115
related to the appropriate buildout part (here, ``openerp-one``) is
 
116
loaded in the standard ``openerp.tools.config``, for use in the
 
117
script. The script has to take care of all database management operations.
 
118
 
 
119
Optionally, it's possible to specify the name of the produced script::
 
120
 
 
121
  [openerp-one]
 
122
  (...)
 
123
  openerp_scripts = my_script=wished_name
 
124
 
 
125
That would build the script as ``bin/wished_name``.
 
126
 
 
127
This is good
 
128
enough for scripts that'd take care of many bootstrapping details, but
 
129
there is a more integrated way that script authors should be aware of:
 
130
the special ``session`` argument.
 
131
 
 
132
.. _arguments_session:
 
133
 
 
134
Arguments and session
 
135
---------------------
 
136
.. note:: new in version 1.7.0
 
137
 
 
138
An ``arguments`` parameter, similar to the one of
 
139
``zc.recipe.egg:scripts`` can be specified::
 
140
 
 
141
  [openerp-two]
 
142
  (...)
 
143
  openerp_scripts = my_script arguments=2,3
 
144
 
 
145
This is a raw string that will be used as the string of arguments for
 
146
the callable specified in the entry point, as in ``main(2,3)`` in that
 
147
example.
 
148
 
 
149
There is a special argument: ``session``, which is an object provided
 
150
by the recipe to expose OpenERP API in a convenient manner for script
 
151
authors. Check
 
152
:py:class:`anybox.recipe.openerp.runtime.session.Session` to learn
 
153
what can be done with it.
 
154
 
 
155
Scripts written for these ``session`` objects must be declared as such::
 
156
 
 
157
 [openerp-two]
 
158
 (...)
 
159
 openerp_scripts = my_script arguments=session
 
160
 
 
161
.. _command_line_options:
 
162
 
 
163
Command-line options
 
164
--------------------
 
165
 
 
166
In some cases, it is useful to do some operations, such as preloading
 
167
a database, before actual running of the script. This is intended for
 
168
scripts which have no special knowledge of OpenERP but may in turn
 
169
call some code meant for OpenERP, that'd need some preparations to
 
170
already have been performed.
 
171
 
 
172
The main use-case is unit tests launchers.
 
173
 
 
174
For these, the ``command-line-options`` modifier tells the recipe to
 
175
produce an executable that will implement some additional command-line
 
176
options parsing and perform some actions accordingly. On the
 
177
command-line ``--`` is used as a separator between those additional
 
178
options and the regular arguments expected by the script.
 
179
 
 
180
Example::
 
181
 
 
182
  [openerp-three]
 
183
  (...)
 
184
  openerp_scripts = nosetests command-line-options=-d
 
185
 
 
186
This produces a ``bin/nosetests_openerp-three``, which you can use
 
187
like this::
 
188
 
 
189
  bin/nosetests_openerp-three -d mydb -- [NOSE REGULAR OPTIONS & ARGUMENTS]
 
190
 
 
191
Currently available command-line-options:
 
192
 
 
193
:-d DB_NAME: preload the specified database
 
194
 
 
195
Writing OpenERP Scripts
 
196
~~~~~~~~~~~~~~~~~~~~~~~
 
197
 
 
198
Script authors have to:
 
199
 
 
200
* write their script as a callable within a setuptools
 
201
  distribution. Usually that'd be a function ``my_run`` at toplevel of
 
202
  a ``my/script/main.py`` file
 
203
* declare that callable in ``setup.py`` like this::
 
204
 
 
205
      entry_points="""
 
206
 
 
207
      [console_scripts]
 
208
      my_script = my.script.main:my_run
 
209
      """
 
210
* (recommended) use the
 
211
  :py:class:`anybox.recipe.openerp.runtime.session.Session` API. For
 
212
  that, let your callable accept a ``session`` argument, and tell
 
213
  users to :ref:`pass it in their buildout configuration <arguments_session>`.
 
214
 
 
215
* write the actual script! Here's a silly example, that outputs the
 
216
  total of users in the database::
 
217
 
 
218
       from argparse import ArgumentsParser
 
219
 
 
220
       def my_run(session):
 
221
           # command-line arguments handling is up to the script
 
222
           parser = ArgumentsParser()
 
223
           parser.add_argument('-d', '--database',
 
224
                               help="Database to work on", required=True)
 
225
           arguments = parser.parse_args()
 
226
 
 
227
           # loading the DB
 
228
           session.open(arguments.database)
 
229
 
 
230
           # using the models
 
231
           users = session.registry('res.users').search(
 
232
               session.cr, session.uid, [])
 
233
 
 
234
           print("There are %d users in database %r" % (
 
235
               len(users), arguments.database))
 
236
 
 
237
           # Transaction control is up to the script
 
238
           session.rollback()  # we didn't write anything, but one never knows
 
239
 
 
240
.. _making_available:
 
241
 
 
242
Making the distribution available
 
243
---------------------------------
 
244
 
 
245
In order to be used by the recipe, the distribution that holds the
 
246
script code has to be *required* with the ``eggs`` option. But how can
 
247
buildout retrieve it ? There's nothing specific to the OpenERP recipe
 
248
about that, it works in the exact same way as for the standard
 
249
``zc.recipe.eggs`` recipe.
 
250
 
 
251
We list here some possibilities, as a convenience for readers without
 
252
a more general buildout experience.
 
253
 
 
254
* provide it locally and tell buildout to "develop" it::
 
255
 
 
256
      [buildout]
 
257
      develop = my_script_distribution_path
 
258
 
 
259
  paths are interpreted relative to the buildout directory, but may be
 
260
  absolute.
 
261
 
 
262
* put it on the `Python Package Index <https://pypi.python.org>`_
 
263
* put it in a private index and use the ``index`` main buildout option
 
264
* prebuild an egg and put it in the eggs directory (can be shared
 
265
  between several buildouts).
 
266
* put a source distribution (tarball) or an egg on some HTTP server,
 
267
  and use the ``find-links`` global buildout option.
 
268
* grab it and develop it from an external VCS, using the
 
269
  `gp.vcsdevelop <https://pypi.python.org/gp.vcsdevelop>`_ buildout extension.
 
270
* use one of the other VCS-oriented buildout extensions (such as
 
271
  `mr.developer <https://pypi.python.org/pypi/mr.developer/>`_
 
272
 
 
273
.. note:: the releasing features (freeze, extract) of the recipe are
 
274
          aware of ``gp.vcsdevelop`` and will control the revision it
 
275
          uses. There's no such support of ``mr.developer`` right now.
 
276
 
 
277
.. _upgrade_scripts:
 
278
 
 
279
Upgrade scripts
 
280
~~~~~~~~~~~~~~~
 
281
.. note:: new in version 1.8.0
 
282
 
 
283
The recipe provides a toolkit for database management, including
 
284
upgrade scripts generation, to fulfill two seemingly contradictory goals:
 
285
 
 
286
* **Uniformity**: all buildout-driven
 
287
  installations have upgrade scripts with the same command-line
 
288
  arguments, similar output, and all the costly details that matter
 
289
  for industrialisation, or simply execution by a pure system
 
290
  administrator, such as success log line, proper status code, already
 
291
  taken care of. Even for one-shot delicate upgrades, repetition is
 
292
  paramount (early detection of problems through rehearsals).
 
293
* **Flexibility**: "one-size-fits all" is precisely what the recipe is
 
294
  meant to avoid. In the sensitive case of upgrades, we know that an
 
295
  guess-based approach that would work in 90% of cases is not good enough.
 
296
 
 
297
To accomodate these two needs, the installation-dependent
 
298
flexibility is given back to the user (a
 
299
project maintainer in that case) by letting her write the actual
 
300
upgrade logic in the simplest way possible. The recipe rewraps it and
 
301
produces the actual executable, with its command-line parsing, etc.
 
302
 
 
303
Project maintainers have to produce a callable using the
 
304
high-level methods of
 
305
:py:class:`anybox.recipe.openerp.runtime.session.Session`. Here's an
 
306
example::
 
307
 
 
308
   def run_upgrade(session, logger):
 
309
       db_version = session.db_version  # this is the state after
 
310
                                        # latest upgrade
 
311
       if db_version < '1.0':
 
312
          session.update_modules(['account_account'])
 
313
       else:
 
314
          logger.warn("Not upgrading account_account, as we know it "
 
315
                      "to be currently a problem with our setup. ")
 
316
       session.update_modules(['crm', 'sales'])
 
317
 
 
318
Such callables (source file and name) can be declared in the
 
319
buildout configuration with the ``upgrade_script`` option::
 
320
 
 
321
  upgrade_script = my_upgrade.py run_upgrade
 
322
 
 
323
The default is ``upgrade.py run``. The path is interpreted relative to
 
324
the buildout directory.
 
325
 
 
326
If the specified source file is not found, the recipe will initialize it
 
327
with the simplest possible one : update of all modules. That is
 
328
expected to work 90% of the time. The package manager can then modify
 
329
it according to needs, and maybe track it in version control.
 
330
 
 
331
In truth, upgrade scripts are nothing but OpenERP scripts, with the
 
332
entry point console script being provided by the recipe itself, and
 
333
in turn relaying to that user-level callable.
 
334
See :py:mod:`anybox.recipe.openerp.runtime.upgrade` for more details
 
335
on how it works.
 
336
 
 
337
Usage for instance creation
 
338
---------------------------
 
339
For projects with a fixed number of modules to install at a given
 
340
point of code history, upgrade scripts can be used to install a
 
341
fresh database::
 
342
 
 
343
  def upgrade(session, logger):
 
344
      """Create or upgrade an instance or my_project."""
 
345
      if session.is_initialization:
 
346
          logger.info("Installing modules on fresh database")
 
347
          session.install_modules(['my_module'])
 
348
          return
 
349
 
 
350
      # now upgrade logic
 
351
 
 
352
Not having a command-line argument for modules ot install in
 
353
the resulting script *is a strength*.
 
354
It means that CI robots, deployment tools
 
355
and the like will be able to install it with zero additional
 
356
configuration.
 
357
 
 
358
The default script produced by the recipe also detects initializations
 
359
and logs information on how to customize::
 
360
 
 
361
    2013-10-14 17:16:17,785 WARNING  Usage of upgrade script for initialization detected. You should consider customizing the present upgrade script to add modules install commands. The present script is at : /home/gracinet/openerp/recipe/testing-buildouts/upgrade.py (byte-compiled form)
 
362
    2013-10-14 17:16:17,786 INFO  Initialization successful. Total time: 22 seconds.
 
363
 
 
364
 
 
365
.. note:: the ``is_initialization`` attribute is new in version 1.8.1
 
366
 
 
367
 
 
368
Options of the produced executable upgrade script
 
369
-------------------------------------------------
 
370
 
 
371
Command-line parsing is done with `argparse
 
372
<http://docs.python.org/2/library/argparse.html>`_. If you have any doubt,
 
373
use ``--help`` with the version you have. Here's the current state::
 
374
 
 
375
  $ bin/upgrade_openerp -h
 
376
  usage: upgrade_openerp [-h] [--log-file LOG_FILE] [--log-level LOG_LEVEL]
 
377
                         [--console-log-level CONSOLE_LOG_LEVEL] [-q]
 
378
                         [-d DB_NAME]
 
379
 
 
380
  optional arguments:
 
381
    -h, --help            show this help message and exit
 
382
    --log-file LOG_FILE   File to log sub-operations to, relative to the current
 
383
                          working directory, supports homedir expansion ('~' on
 
384
                          POSIX systems). (default: upgrade.log)
 
385
    --log-level LOG_LEVEL
 
386
                          Main OpenERP logging level. Does not affect the
 
387
                          logging from the main upgrade script itself. (default:
 
388
                          info)
 
389
    --console-log-level CONSOLE_LOG_LEVEL
 
390
                          Level for the upgrade process console logging. This is
 
391
                          for the main upgrade script itself meaning that
 
392
                          usually only major steps should be logged (default:
 
393
                          info)
 
394
    -q, --quiet           Suppress console output from the main upgrade script
 
395
                          (lower level stages can still write) (default: False)
 
396
    -d DB_NAME, --db-name DB_NAME
 
397
                          Database name. If ommitted, the general default values
 
398
                          from OpenERP config file or libpq will apply.
 
399
    --init-load-demo-data
 
400
                          Demo data will be loaded with module installations if
 
401
                          and only if this modifier is specified (default:
 
402
                          False)
 
403
 
 
404
 
 
405
Sample output
 
406
-------------
 
407
 
 
408
Here's the output of a run of the default upgrade script::
 
409
 
 
410
  $ bin/upgrade_openerp -d testrecipe
 
411
  Starting upgrade, logging details to /home/gracinet/openerp/recipe/testing-buildouts/upgrade.log at level INFO, and major steps to console at level INFO
 
412
 
 
413
  2013-09-21 18:53:23,471 WARNING  Expected package version file '/home/gracinet/openerp/recipe/testing-buildouts/VERSION.txt' does not exist. version won't be set in database at the end of upgrade. Consider including such a version file in your project *before* version dependent logic is actually needed.
 
414
  2013-09-21 18:53:23,471 INFO  Database 'testrecipe' loaded. Actual upgrade begins.
 
415
  2013-09-21 18:53:23,471 INFO  Default upgrade procedure : updating all modules.
 
416
  2013-09-21 18:53:54,029 INFO  Upgrade successful. Total time: 32 seconds.
 
417
 
 
418
The same with a version file::
 
419
 
 
420
  $ bin/upgrade_openerp -d testrecipe
 
421
  Starting upgrade, logging details to /home/gracinet/openerp/recipe/testing-buildouts/upgrade.log at level INFO, and major steps to console at level INFO
 
422
 
 
423
  2013-09-22 19:23:17,908 INFO  Read package version: 6.6.6-final from /home/gracinet/openerp/recipe/testing-buildouts/VERSION.txt
 
424
  2013-09-22 19:23:17,908 INFO  Database 'testrecipe' loaded. Actual upgrade begins.
 
425
  2013-09-22 19:23:17,909 INFO  Default upgrade procedure : updating all modules.
 
426
  2013-09-22 19:23:48,626 INFO  setting version 6.6.6-final in database
 
427
  2013-09-22 19:23:48,635 INFO  Upgrade successful. Total time: 32 seconds.
 
428
 
 
429
 
 
430
Startup scripts
 
431
~~~~~~~~~~~~~~~
 
432
The familiar ``start_openerp``, and its less pervasing siblings
 
433
(``gunicorn_openerp``, ``test_openerp``, …) are also special cases of
 
434
OpenERP scripts.
 
435
 
 
436
What is special with them amounts to the following:
 
437
 
 
438
* the entry points are declared by the recipe itself, not by a
 
439
  third-party Python distribution.
 
440
* the recipe includes some initialization code in the final
 
441
  executable, in a way that the configuration presently could not allow.
 
442
* often, they don't use the session objects, but rewrap instead the mainline
 
443
  startup script.
 
444
 
 
445
In particular, you can control the names of the startup scripts with
 
446
the ``openerp_scripts`` option. For instance, to
 
447
replace ``bin/start_openerp`` with ``bin/oerp``, just do::
 
448
 
 
449
  [openerp]
 
450
  (...)
 
451
  openerp_scripts = openerp_starter=oerp
 
452
 
 
453
List of internal entry points
 
454
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
455
 
 
456
Here's the list of currently available internal entry points. 
 
457
 
 
458
:openerp_starter: main OpenERP startup script (dynamically added
 
459
                  behing the scenes by the recipe)
 
460
:openerp_tester: uniform script to start OpenERP, launch all tests and
 
461
                 exit. This can be achieved with the main startup
 
462
                 scripts, but options differ among OpenERP versions.
 
463
                 (also dynamically added behind the scenes).
 
464
:openerp_upgrader: entry point for the upgrade script
 
465
:openerp_cron_worker: entry point for the cron worker script that gets
 
466
                      built for gunicorn setups.
 
467
:oe: entry point declared by ``openerp-command`` and used by the recipe.
 
468
:gunicorn: entry point declared by ``gunicorn`` and used by the recipe.
 
469
 
 
470
.. note:: For these entry points, the ``command-line-options`` and
 
471
          ``arguments`` modifiers have no effect.