anybox.recipe.odoo Package

base Module

class anybox.recipe.odoo.base.BaseRecipe(buildout, name, options)[source]

Bases: object

Base class for other recipes.

It implements notably fetching of the main software part plus addons.

The sources attribute is a dict storing how to fetch the main software part and the specified addons, with the following structure:

local path -> (type, location_spec, options), in which:

local path:

is either the main_software singleton (see MainSoftware) or a local path to an addons directory.


can be either

  • 'local'
  • 'downloadable'
  • one of the supported vcs

is, depending on the type, a tuple specifying how fetch is to be done:

url, or (vcs_url, vcs_revision) or None

addons options:

are typically used to specify that the addons directory is actually a subdir of the specified one.

VCS support classes (see anybox.recipe.odoo.vcs) can implemented their dedicated options

The merges attribute is a dict storing how to fetch additional changes to merge into VCS type sources:

local path -> [(type, location_spec, options), ... ]

See sources for the meaning of the various components. Note that in merges, values are a list of triples instead of only a single triple as values in sources because there can be multiple merges on the same local path.

addons_paths = ()

Try and read Odoo’s ‘requirements.txt’ and apply it.

This file appeared in the course of Odoo 8 lifetime. If not available, a warning is issued, that’s all.

Entries from the requirements file are applied if there is not already an entry in the versions section for the same project.

A more interesting behaviour would be to apply then if they don’t contradict an existing entry in the versions section, but that’s far more complicated.


Store some booleans depending on detected version.

To be refined by subclasses.

bool_opt_get(name, is_global=False)[source]

Retrieve an option and interpret it as boolean.

Factorized to improve code readability.

Parameters:is_global – if True, the option is taken from the global buildout options instead of the part taken care of by this recipe instance.

Return the name of the config file that’s been called.


Revert local modifications that have been made during installation.

These can be, e.g., forbidden by the freeze process.


Develop the specified source distribution.

Any call to zc.recipe.eggs will use that developped version. develop() launches a subprocess, to which we need to forward the paths to requirements via PYTHONPATH.

Parameters:setup_has_pil – if True, an altered version of setup that does not require PIL is produced to perform the develop, so that installation can be done with Pillow instead. Recent enough versions of OpenERP/Odoo are directly based on Pillow.
Returns:project name of the distribution that’s been “developed” This is useful for OpenERP/Odoo itself, whose project name changed within the 8.0 stable branch.

After download/analysis of ‘nightly latest’, give equivalent spec.

extract_downloads_to(target_dir, outconf_name='release.cfg')[source]

Extract anything that has been downloaded to target_dir.

This doesn’t copy intermediary buildout configurations nor local parts. In the purpose of making a self-contained and offline playable archive, these are assumed to be already taken care of.


Add implicit paths and serialize in the addons_path option.

Parameters:check_existence – if True, all the paths will be checked for existence (useful for unit tests)

Create an extension buildout freezing current revisions & versions.


Insert the standard, non-base addons bundled within Odoo git repo.

See lp:1327756

These addons are also part of the Github branch for prior versions, therefore we cannot rely on version knowledge; we check for existence instead. If not found (e.g, we are on a nightly for OpenERP <= 7), this method does nothing.

The ordering of the different paths of addons is important. When several addons at different paths have the same name, the first of them being found is used. This can be used, for instance, to replace an official addon by another one by placing a different addons’ path before the official one.

If the official addons’ path is already set in the config file (e.g. at the end), it will leave it at the end of the paths list, if it is not set, it will be placed at the beginning just after base addons’ path.

Care is taken not to break configurations that corrected this manually with a local source in the addons option.

Parameters:base_addons – the path to previously detected base addons, to properly insert right after them

Install requirements for the recipe to run.


Install egg requirements and scripts.

If some distributions are known as soft requirements, will retry without them

is_git_layout = False

True if this is the git layout, as seen from the move to GitHub.

In this layout, the standard addons other than base are in a addons directory right next to the openerp package.


Tell if the download is stale by doing a HEAD request.

Assumes the correct date had been written upon download. This is the same system as in GNU Wget 1.12. It works even if the server does not implement conditional responses such as 304


At any point in time, list the projects that have been developed.

This can work as soon as the recipe is instantiated (because the ‘buildout’ part) has already been executed. In particular, it does not rely on the workingset init done by zc.buildout.easy_install.Installer and can be used in precedence rules that need to be executed before calling the Installer indirectly via zc.recipe.eggs

Returns:list of project names

Implementation simply lists the develop eggs directory There’s probably better to be done.


HTTP download for main part of the software to self.archive_path.

main_http_caching = 'filename'

Make a path absolute if needed.

If not already absolute, it is interpreted as relative to the buildout directory.


Merge eggs option with self.requirements.

TODO refactor all this: merge requirements is not idempotent, it appends. Overall, this going back and forth between the serialized form (self.options[‘eggs’]) and the parsed version has growed too much, up to the point where it’s not natural at all.

nightly_dl_url = {'8.0': ''}

Base URLs to look for nightly versions.

The URL for 8.0 may have to be adapted once it’s released for good. This one is guessed from 7.0 and is needed by unit tests.


Parse the addons options into sources.

See BaseRecipe for the structure of sources.


Parse the merge options into merges.

See BaseRecipe for the structure of merges.


Parse revisions options and update sources.

It is assumed that sources has already been populated, and notably has a main_software entry. This allows for easy fixing of revisions in an extension buildout

See BaseRecipe for the structure of sources.


Set the main software in sources and related attributes.


Perform version checks before any attempt to install.

To be subclassed.


Ugly method to extract requirements & version from ugly

Primarily designed for 6.0, but works with 6.1 as well.


Try and read the file directly.

Used as a fallback in case reading failed, which happened in an old OpenERP version. Could become the norm, but setup is also used to list dependencies.

read_requirements_pip_after_v8(req_path, versions, develops)[source]
read_requirements_pip_before_v8(req_path, versions, develops)[source]
recipe_requirements = ()
recipe_requirements_paths = ()
release_dl_url = {}

Base URLs to look for official, released versions.

There are currently no official releases for Odoo, but the recipe has been designed at the time of OpenERP 6.0 and some parts of its code at least expect this dict to exist. Besides, official releases may reappear at some point.

requirements = ()

Peform all lookup and downloads specified in sources.

See BaseRecipe for the structure of sources.


Lookup or fetch the main software.

See MainSoftware and BaseRecipe for explanations.


Peform all VCS merges specified in merges.


Revert all sources to the revisions specified in sources.

sandboxed_tar_extract(sandbox, tarfile, first=None)[source]

Extract those members that are below the tarfile path ‘sandbox’.

The tarfile module official doc warns against attacks with .. in tar.

The option to start with a first member is useful for this case, since the recipe consumes a first member in the tar file to get the openerp main directory in parts. It is taken for granted that this first member has already been checked.

soft_requirements = ()
with_odoo_requirements_file = False

Whether attempt to use the ‘requirements.txt’ shipping with Odoo

class anybox.recipe.odoo.base.MainSoftware[source]

Bases: object

Placeholder to represent the main software instead of an addon location.

Should just have a singleton instance: main_software, whose meaning depends on the concrete recipe class using it.

For example, in anybox.recipe.odoo.server.ServerRecipe, main_software represents the OpenObject server or the Odoo standard distribution.


Parse RFC 2822-formatted http header and return a time int.

devtools Module

Provide devtools to openerp.


server Module

class anybox.recipe.odoo.server.ServerRecipe(*a, **kw)[source]

Bases: anybox.recipe.odoo.base.BaseRecipe

Recipe for server install and config


Store some booleans depending on detected version.

Also does some options normalization accordingly. Currently, there is only one Odoo version, this method will be really useful again in a while.


Prepare for installation by zc.recipe.egg

  • develop the openerp distribution and require it
  • gunicorn’s related dependencies if needed

Once ‘openerp’ is required, zc.recipe.egg will take it into account and put it in needed scripts, interpreters etc.

Historically, in anybox.recipe.openerp this used to take care of adding Pillow, which is now in Odoo’s

nightly_filenames = {'8.0': 'odoo_8.0.%s.tar.gz', 'trunk': 'odoo_9.0alpha1.%s.tar.gz'}

Name of expected nightly tarballs in base URL, by major version.

recipe_requirements = ('babel',)
release_filenames = {}
requirements = ('pychart', 'anybox.recipe.odoo')
server_wide_modules = ()
soft_requirements = ('openerp-command',)
template_upgrade_script = '/home/gracinet/odoo/recipe/release/anybox/recipe/odoo/'
with_gunicorn = False
with_upgrade = True
ws = None

testing Module

Utilities for unit tests.

class anybox.recipe.odoo.testing.FakeRepo(target_dir, url, clear_retry=False, offline=False, clear_locks=False, **options)[source]

Bases: anybox.recipe.odoo.vcs.base.BaseRepo

log = []
log_std_options = True
name = 'fakevcs'
revision = 'fakerev'
vcs_control_dir = '.fake'
class anybox.recipe.odoo.testing.PersistentRevFakeRepo(target_dir, url, clear_retry=False, offline=False, clear_locks=False, **options)[source]

Bases: anybox.recipe.odoo.testing.FakeRepo

A variant of FakeRepo that still needs the directory structure around.

Makes for a more realistic test of some conditions. In particular, reproduced launchpad #TODO

current_revisions = {}

This needs the directory to really exist and is controllable.

class anybox.recipe.odoo.testing.RecipeTestCase(methodName='runTest')[source]


A base setup for tests of recipe classes


build an egg for fake babel in buildout’s eggs directory.

Require the test case to already have a test_dir attribute (typically set on class with the dirname of the test)


build an egg of a fictive distribution for testing purposes.

Require the test case to already have a test_dir attribute (typically set on class with the dirname of the test)


Develop fictive distribution in buildout’s directory.

Require the test case to already have a test_dir attribute (typically set on class with the dirname of the test)

Parameters:require_install (bool) – if True, will be required from eggs option, and installed.
fictive_dist_name = 'FictiveDist'
fictive_name = 'fictivedist'
fictive_version = None
fill_working_set(fictive=False, babel=False)[source]
make_recipe(name='openerp', **options)[source]

Silence easy_install develop operations performed by zc.buildout.

class anybox.recipe.odoo.testing.TestingRecipe(buildout, name, options)[source]

Bases: anybox.recipe.odoo.base.BaseRecipe

A subclass with just enough few defaults for unit testing.

nightly_filenames = {'8.0': '8-0-nightly-%s.tbz'}
release_dl_url = {'8.0': 'http://release.odoo.test/src/'}
release_filenames = {'8.0': 'blob-%s.tgz'}

utils Module

class anybox.recipe.odoo.utils.WorkingDirectoryKeeper[source]

Bases: 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
anybox.recipe.odoo.utils.check_output(*popenargs, **kwargs)[source]

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')

The stdout argument is not allowed as it is used internally. To capture standard error in the result, use stderr=STDOUT.

>>> os.environ['LC_ALL'] = '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")

Recursively remove object files in given directory.

Also remove resulting empty directories.

anybox.recipe.odoo.utils.conf_ensure_section(conf, section)[source]

True if given filename is a python object file.


The least common denominator of Odoo versions : two numbers.

Odoo 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)
>>> 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 Odoo saas releases (e.g. 7.saas~1) that are short-lived stable versions between two “X.0” LTS releases, the ‘saas~’ argument before the minor version number is stripped. For instance:

>>> major_version('7.saas~3')
(7, 3)

Split a multiline option value.

This function performs stripping of whitespaces and allows comments as ConfigParser would do. Namely:

  • a line starting with a hash is a comment. This is already taken care of by zc.buildout parsing of the configuration file.

    ConfigParser does not apply this rule to the case where the hash is after some leading whitespace (e.g, line-continuation indentation) as in this example:

    bar = line1
    # this is a comment
      # this is not a comment, and will appear in 'bar' value

    Therefore this function does not have to perform anything with respect to hash-comments.

  • everything after a semicolon following a whitespace is a comment:

    bar = line1
          line2 ;this is a comment
Parameters:opt_val (basestring) – the raw option value
Returns:tuple of strings

doctests (less readable than examples above, but more authoritative):

>>> option_splitlines('line1\n  line2 ;this is a comment\n  line3')
('line1', 'line2', 'line3')
>>> option_splitlines('l1\n; inline comment from beginning\n  line3')
('l1', 'line3')
>>> option_splitlines('l1\n; inline comment from beginning\n  line3')
('l1', 'line3')
>>> option_splitlines('l1\n  ; disappears after stripping \n  line3')
('l1', 'line3')
>>> option_splitlines('line1\n\n')
>>> option_splitlines('')

The return value is guaranteed not to be a single string:

>>> option_splitlines('single')

For convenience, None is accepted:

>>> option_splitlines(None)

Same as option_splitlines() for a single line.

>>> option_strip("   hey, we have ; a comment")
'hey, we have'
>>> option_strip(None) is None

Uniformity backport of datetime.timedelta.total_seconds`()

Parameters:td – a datetime.timedelta instance
Returns:the number of seconds in tdelta

The implementation for Python < 2.7 is taken from the standard library documentation

anybox.recipe.odoo.utils.use_or_open(*args, **kwds)[source]

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.