[ADD] oe: provides sane (unfucked) command-line interface.
The implementation is far from perfect. Some improvements are waiting in its previous location: lp:~openerp/openerp-command. Some docs are provided, see doc/openerp-command.rst and doc/adding-command.rst. bzr revid: vmt@openerp.com-20130111134657-im2f3uqjluyo4pm6
This commit is contained in:
parent
3478333bca
commit
3da57500c2
|
@ -0,0 +1,34 @@
|
|||
.. _adding-command:
|
||||
|
||||
Adding a new command
|
||||
====================
|
||||
|
||||
``oe`` uses the argparse_ library to implement commands. Each
|
||||
command lives in its own ``openerpcommand/<command>.py`` file.
|
||||
|
||||
.. _argparse: http://docs.python.org/2.7/library/argparse.html
|
||||
|
||||
To create a new command, probably the most simple way to get started is to
|
||||
copy/paste an existing command, say ``openerpcommand/initialize.py`` to
|
||||
``openerpcommand/foo.py``. In the newly created file, the important bits
|
||||
are the ``run(args)`` and ``add_parser(subparsers)`` functions.
|
||||
|
||||
``add_parser``'s responsability is to create a (sub-)parser for the command,
|
||||
i.e. describe the different options and flags. The last thing it does is to set
|
||||
``run`` as the function to call when the command is invoked.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
> def add_parser(subparsers):
|
||||
> parser = subparsers.add_parser('<command-name>',
|
||||
> description='...')
|
||||
> parser.add_argument(...)
|
||||
> ...
|
||||
> parser.set_defaults(run=run)
|
||||
|
||||
``run(args)`` actually implements the command. It should be kept as simple as
|
||||
possible and delegate most of its work to small functions (probably placed at
|
||||
the top of the new file). In other words, its responsability is mainly to
|
||||
deal with the presence/absence/pre-processing of ``argparse``'s arguments.
|
||||
|
||||
Finally, the module must be added to ``openerpcommand/__init__.py``.
|
|
@ -18,6 +18,15 @@ OpenERP Server
|
|||
06_misc
|
||||
09_deployment
|
||||
|
||||
OpenERP Command
|
||||
'''''''''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
openerp-command.rst
|
||||
adding-command.rst
|
||||
|
||||
OpenERP Server API
|
||||
''''''''''''''''''
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
.. _openerp-command:
|
||||
|
||||
OpenERP Command
|
||||
===============
|
||||
|
||||
The ``oe`` script provides a set of command-line tools around the OpenERP
|
||||
framework.
|
||||
|
||||
Using OpenERP Command
|
||||
---------------------
|
||||
|
||||
In contrast to the previous ``openerp-server`` script, ``oe`` defines a few
|
||||
sub-commands, each with its own set of flags and options. You can get some
|
||||
information for any of them with
|
||||
|
||||
::
|
||||
|
||||
> oe <sub-command> --help
|
||||
|
||||
For instance::
|
||||
|
||||
> oe run-tests --help
|
||||
|
||||
Some ``oe`` options can be provided via environment variables. For instance::
|
||||
|
||||
> export OPENERP_DATABASE=trunk
|
||||
> export OPENERP_HOST=127.0.0.1
|
||||
> export OPENERP_PORT=8069
|
||||
|
||||
Depending on your needs, you can group all of the above in one single script;
|
||||
for instance here is a, say, ``test-trunk-view-validation.sh`` file::
|
||||
|
||||
COMMAND_REPO=/home/thu/repos/command/trunk/
|
||||
SERVER_REPO=/home/thu/repos/server/trunk
|
||||
|
||||
export PYTHONPATH=$SERVER_REPO:$COMMAND_REPO
|
||||
export PATH=$SERVER_REPO:$COMMAND_REPO:$PATH
|
||||
export OPENERP_DATABASE=trunk
|
||||
export OPENERP_HOST=127.0.0.1
|
||||
export OPENERP_PORT=8069
|
||||
|
||||
# The -d ignored is actually needed by `oe` even though `test_view_validation`
|
||||
# itself does not need it.
|
||||
oe run-tests -d ignored -m openerp.test_view_validation
|
||||
|
||||
Adding new commands
|
||||
-------------------
|
||||
|
||||
See the :doc:`adding-command` page.
|
||||
|
||||
Bash completion
|
||||
---------------
|
||||
|
||||
A preliminary ``oe-bash-completion`` file is provided. After sourcing it,
|
||||
|
||||
::
|
||||
|
||||
> . oe-bash-completion
|
||||
|
||||
completion (using the TAB character) in Bash should be working.
|
|
@ -0,0 +1,5 @@
|
|||
#! /usr/bin/env python2
|
||||
|
||||
if __name__ == '__main__':
|
||||
import openerpcommand.main
|
||||
openerpcommand.main.run()
|
|
@ -0,0 +1,89 @@
|
|||
_oe()
|
||||
{
|
||||
local cur prev opts
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
cmd="${COMP_WORDS[0]}"
|
||||
subcmd=""
|
||||
if [[ ${COMP_CWORD} > 0 ]] ; then
|
||||
subcmd="${COMP_WORDS[1]}"
|
||||
fi
|
||||
|
||||
# oe
|
||||
|
||||
opts="initialize model read run-tests scaffold update \
|
||||
call open show consume-nothing consume-memory leak-memory \
|
||||
consume-cpu bench-read bench-fields-view-get bench-dummy bench-login \
|
||||
bench-sale-mrp --help"
|
||||
|
||||
if [[ ${prev} == oe && ${cur} != -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# oe call
|
||||
|
||||
opts="--database --user --password --host --port --help"
|
||||
|
||||
if [[ ${subcmd} == call ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# oe initialize
|
||||
|
||||
opts="--database --addons --all-modules --exclude --no-create --help"
|
||||
|
||||
if [[ ${subcmd} == initialize ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# oe model
|
||||
|
||||
opts="--database --model --field --verbose --help"
|
||||
|
||||
if [[ ${subcmd} == model ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# oe read
|
||||
|
||||
opts="--database --model --id --field --verbose --short --help"
|
||||
|
||||
if [[ ${subcmd} == read ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# oe run-tests
|
||||
|
||||
opts="--database --addons --module --dry-run --help"
|
||||
|
||||
if [[ ${subcmd} == run-tests ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# oe scaffold
|
||||
|
||||
opts="--help"
|
||||
|
||||
if [[ ${subcmd} == scaffold ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# fallback for unimplemented completion
|
||||
|
||||
opts="--help"
|
||||
|
||||
if [[ true ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
complete -F _oe oe
|
|
@ -0,0 +1,61 @@
|
|||
import argparse
|
||||
import textwrap
|
||||
|
||||
from .call import Call
|
||||
from .client import Open, Show, ConsumeNothing, ConsumeMemory, LeakMemory, ConsumeCPU
|
||||
from .benchmarks import Bench, BenchRead, BenchFieldsViewGet, BenchDummy, BenchLogin
|
||||
from .bench_sale_mrp import BenchSaleMrp
|
||||
from . import common
|
||||
|
||||
from . import conf # Not really server-side (in the `for` below).
|
||||
from . import drop
|
||||
from . import initialize
|
||||
from . import model
|
||||
from . import module
|
||||
from . import read
|
||||
from . import run_tests
|
||||
from . import scaffold
|
||||
from . import uninstall
|
||||
from . import update
|
||||
|
||||
command_list_server = (conf, drop, initialize, model, module, read, run_tests,
|
||||
scaffold, uninstall, update, )
|
||||
|
||||
command_list_client = (Call, Open, Show, ConsumeNothing, ConsumeMemory,
|
||||
LeakMemory, ConsumeCPU, Bench, BenchRead,
|
||||
BenchFieldsViewGet, BenchDummy, BenchLogin,
|
||||
BenchSaleMrp, )
|
||||
|
||||
def main_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
usage=argparse.SUPPRESS,
|
||||
description=textwrap.fill(textwrap.dedent("""\
|
||||
OpenERP Command provides a set of command-line tools around
|
||||
the OpenERP framework: openobject-server. All the tools are
|
||||
sub-commands of a single oe executable.""")),
|
||||
epilog="""Use <command> --help to get information about the command.""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
description = []
|
||||
for x in command_list_server:
|
||||
description.append(x.__name__[len(__package__)+1:])
|
||||
if x.__doc__:
|
||||
description.extend([
|
||||
":\n",
|
||||
textwrap.fill(str(x.__doc__).strip(),
|
||||
subsequent_indent=' ',
|
||||
initial_indent=' '),
|
||||
])
|
||||
description.append("\n\n")
|
||||
subparsers = parser.add_subparsers(
|
||||
title="Available commands",
|
||||
help=argparse.SUPPRESS,
|
||||
description="".join(description[:-1]),
|
||||
)
|
||||
# Server-side commands.
|
||||
for x in command_list_server:
|
||||
x.add_parser(subparsers)
|
||||
# Client-side commands. TODO one per .py file.
|
||||
for x in command_list_client:
|
||||
x(subparsers)
|
||||
return parser
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Nothing here, the module provides only data.
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'bench_sale_mrp',
|
||||
'version': '0.1',
|
||||
'category': 'Benchmarks',
|
||||
'description': """Prepare some data to run a benchmark.""",
|
||||
'author': 'OpenERP SA',
|
||||
'maintainer': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base', 'sale_mrp'],
|
||||
'data': ['data.yml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,41 @@
|
|||
-
|
||||
This is a subset of `sale_mrp/test/sale_mrp.yml`.
|
||||
-
|
||||
I define a product category `Mobile Products Sellable`.
|
||||
-
|
||||
!record {model: product.category, id: my_product_category_0}:
|
||||
name: Mobile Products Sellable
|
||||
-
|
||||
I define a product `Slider Mobile`
|
||||
-
|
||||
!record {model: product.product, id: my_slider_mobile_0}:
|
||||
categ_id: my_product_category_0
|
||||
cost_method: standard
|
||||
list_price: 200.0
|
||||
mes_type: fixed
|
||||
name: Slider Mobile
|
||||
procure_method: make_to_order
|
||||
seller_delay: '1'
|
||||
seller_ids:
|
||||
- delay: 1
|
||||
name: base.res_partner_agrolait
|
||||
min_qty: 2.0
|
||||
qty: 5.0
|
||||
standard_price: 189.0
|
||||
supply_method: produce
|
||||
type: product
|
||||
uom_id: product.product_uom_unit
|
||||
uom_po_id: product.product_uom_unit
|
||||
-
|
||||
I create a Bill of Material for the `Slider Mobile` product.
|
||||
-
|
||||
!record {model: mrp.bom, id: mrp_bom_slidermobile0}:
|
||||
company_id: base.main_company
|
||||
name: Slider Mobile
|
||||
product_efficiency: 1.0
|
||||
product_id: my_slider_mobile_0
|
||||
product_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
product_uos_qty: 0.0
|
||||
sequence: 0.0
|
||||
type: normal
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Benchmark based on the `sale_mrp` addons (in `sale_mrp/test/sale_mrp.yml`).
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from .benchmarks import Bench
|
||||
|
||||
class BenchSaleMrp(Bench):
|
||||
"""\
|
||||
Similar to `sale_mrp/test/sale_mrp.yml`.
|
||||
|
||||
This benchmarks the OpenERP server `sale_mrp` module by creating and
|
||||
confirming a sale order. As it creates data in the server, it is necessary
|
||||
to ensure unique names for the newly created data. You can use the --seed
|
||||
argument to give a lower bound to those names. (The number of generated
|
||||
names is --jobs * --samples.)
|
||||
"""
|
||||
|
||||
command_name = 'bench-sale-mrp'
|
||||
bench_name = '`sale_mrp/test/sale_mrp.yml`'
|
||||
|
||||
def measure_once(self, i):
|
||||
if self.worker >= 0:
|
||||
i = int(self.args.seed) + i + (self.worker * int(self.args.samples))
|
||||
else:
|
||||
i = int(self.args.seed) + i
|
||||
|
||||
# Resolve a few external-ids (this has little impact on the running
|
||||
# time of the whole method).
|
||||
product_uom_unit = self.execute('ir.model.data', 'get_object_reference', 'product', 'product_uom_unit')[1]
|
||||
my_slider_mobile_0 = self.execute('ir.model.data', 'get_object_reference', 'bench_sale_mrp', 'my_slider_mobile_0')[1]
|
||||
res_partner_4 = self.execute('ir.model.data', 'get_object_reference', 'base', 'res_partner_4')[1]
|
||||
res_partner_address_7 = self.execute('ir.model.data', 'get_object_reference', 'base', 'res_partner_address_7')[1]
|
||||
list0 = self.execute('ir.model.data', 'get_object_reference', 'product', 'list0')[1]
|
||||
shop = self.execute('ir.model.data', 'get_object_reference', 'sale', 'shop')[1]
|
||||
|
||||
# Create a sale order for the product `Slider Mobile`.
|
||||
data = {
|
||||
'client_order_ref': 'ref_xxx_' + str(i).rjust(6, '0'),
|
||||
'date_order': time.strftime('%Y-%m-%d'),
|
||||
'invoice_quantity': 'order',
|
||||
'name': 'sale_order_ref_xxx_' + str(i).rjust(6, '0'),
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'Slider Mobile',
|
||||
'price_unit': 2,
|
||||
'product_uom': product_uom_unit,
|
||||
'product_uom_qty': 5.0,
|
||||
'state': 'draft',
|
||||
'delay': 7.0,
|
||||
'product_id': my_slider_mobile_0,
|
||||
'product_uos_qty': 5,
|
||||
'type': 'make_to_order',
|
||||
})],
|
||||
'order_policy': 'manual',
|
||||
'partner_id': res_partner_4,
|
||||
'partner_invoice_id': res_partner_address_7,
|
||||
'partner_order_id': res_partner_address_7,
|
||||
'partner_shipping_id': res_partner_address_7,
|
||||
'picking_policy': 'direct',
|
||||
'pricelist_id': list0,
|
||||
'shop_id': shop,
|
||||
}
|
||||
sale_order_id = self.execute('sale.order', 'create', data, {})
|
||||
|
||||
# Confirm the sale order.
|
||||
self.object_proxy.exec_workflow(self.database, self.uid, self.password, 'sale.order', 'order_confirm', sale_order_id, {})
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
"""
|
||||
Define a base class for client-side benchmarking.
|
||||
"""
|
||||
import hashlib
|
||||
import multiprocessing
|
||||
import sys
|
||||
import time
|
||||
|
||||
from .client import Client
|
||||
|
||||
class Bench(Client):
|
||||
"""
|
||||
Base class for concurrent benchmarks. The measure_once() method must be
|
||||
overriden.
|
||||
|
||||
Each sub-benchmark will be run in its own process then a report is done
|
||||
with all the results (shared with the main process using a
|
||||
`multiprocessing.Array`).
|
||||
"""
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(Bench, self).__init__(subparsers)
|
||||
self.parser.add_argument('-n', '--samples', metavar='INT',
|
||||
default=100, help='number of measurements to take')
|
||||
# TODO if -n <int>s is given (instead of -n <int>), run the
|
||||
# benchmark for <int> seconds and return the number of iterations.
|
||||
self.parser.add_argument('-o', '--output', metavar='PATH',
|
||||
required=True, help='path to save the generated report')
|
||||
self.parser.add_argument('--append', action='store_true',
|
||||
default=False, help='append the report to an existing file')
|
||||
self.parser.add_argument('-j', '--jobs', metavar='JOBS',
|
||||
default=1, help='number of concurrent workers')
|
||||
self.parser.add_argument('--seed', metavar='SEED',
|
||||
default=0, help='a value to ensure different runs can create unique data')
|
||||
self.worker = -1
|
||||
|
||||
def work(self, iarr=None):
|
||||
if iarr:
|
||||
# If an array is given, it means we are a worker process...
|
||||
self.work_slave(iarr)
|
||||
else:
|
||||
# ... else we are the main process and we will spawn workers,
|
||||
# passing them an array.
|
||||
self.work_master()
|
||||
|
||||
def work_master(self):
|
||||
N = int(self.args.samples)
|
||||
self.arrs = [(i, multiprocessing.Array('f', range(N)))
|
||||
for i in xrange(int(self.args.jobs))]
|
||||
ps = [multiprocessing.Process(target=self.run, args=(arr,))
|
||||
for arr in self.arrs]
|
||||
[p.start() for p in ps]
|
||||
[p.join() for p in ps]
|
||||
|
||||
self.report_html()
|
||||
|
||||
def work_slave(self, iarr):
|
||||
j, arr = iarr
|
||||
self.worker = j
|
||||
N = int(self.args.samples)
|
||||
total_t0 = time.time()
|
||||
for i in xrange(N):
|
||||
t0 = time.time()
|
||||
self.measure_once(i)
|
||||
t1 = time.time()
|
||||
arr[i] = t1 - t0
|
||||
print >> sys.stdout, '\r%s' % ('|' * (i * 60 / N)),
|
||||
print >> sys.stdout, '%s %s%%' % \
|
||||
(' ' * (60 - (i * 60 / N)), int(float(i+1)/N*100)),
|
||||
sys.stdout.flush()
|
||||
total_t1 = time.time()
|
||||
print '\nDone in %ss.' % (total_t1 - total_t0)
|
||||
|
||||
def report_html(self):
|
||||
series = []
|
||||
for arr in self.arrs:
|
||||
serie = """{
|
||||
data: %s,
|
||||
points: { show: true }
|
||||
}""" % ([[x, i] for i, x in enumerate(arr)],)
|
||||
series.append(serie)
|
||||
chart_id = hashlib.md5(" ".join(sys.argv)).hexdigest()
|
||||
HEADER = """<!doctype html>
|
||||
<title>Benchmarks</title>
|
||||
<meta charset=utf-8>
|
||||
<script type="text/javascript" src="js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="js/jquery.flot.js"></script>
|
||||
"""
|
||||
|
||||
CONTENT = """<h1>%s</h1>
|
||||
%s
|
||||
<div id='chart_%s' style='width:400px;height:300px;'>...</div>
|
||||
<script type="text/javascript">
|
||||
$.plot($("#chart_%s"), [%s],
|
||||
{yaxis: { ticks: false }});
|
||||
</script>""" % (self.bench_name, ' '.join(sys.argv), chart_id, chart_id,
|
||||
','.join(series))
|
||||
if self.args.append:
|
||||
with open(self.args.output, 'a') as f:
|
||||
f.write(CONTENT,)
|
||||
else:
|
||||
with open(self.args.output, 'w') as f:
|
||||
f.write(HEADER + CONTENT,)
|
||||
|
||||
def measure_once(self, i):
|
||||
"""
|
||||
The `measure_once` method is called --jobs times. A `i` argument is
|
||||
supplied to allow to create unique values for each execution (e.g. to
|
||||
supply fresh identifiers to a `create` method.
|
||||
"""
|
||||
pass
|
||||
|
||||
class BenchRead(Bench):
|
||||
"""Read a record repeatedly."""
|
||||
|
||||
command_name = 'bench-read'
|
||||
bench_name = 'res.users.read(1)'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(BenchRead, self).__init__(subparsers)
|
||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
||||
required=True, help='the model')
|
||||
self.parser.add_argument('-i', '--id', metavar='RECORDID',
|
||||
required=True, help='the record id')
|
||||
|
||||
def measure_once(self, i):
|
||||
self.execute(self.args.model, 'read', [self.args.id], [])
|
||||
|
||||
class BenchFieldsViewGet(Bench):
|
||||
"""Read a record's fields and view architecture repeatedly."""
|
||||
|
||||
command_name = 'bench-view'
|
||||
bench_name = 'res.users.fields_view_get(1)'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(BenchFieldsViewGet, self).__init__(subparsers)
|
||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
||||
required=True, help='the model')
|
||||
self.parser.add_argument('-i', '--id', metavar='RECORDID',
|
||||
required=True, help='the record id')
|
||||
|
||||
def measure_once(self, i):
|
||||
self.execute(self.args.model, 'fields_view_get', self.args.id)
|
||||
|
||||
class BenchDummy(Bench):
|
||||
"""Dummy (call test.limits.model.consume_nothing())."""
|
||||
|
||||
command_name = 'bench-dummy'
|
||||
bench_name = 'test.limits.model.consume_nothing()'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(BenchDummy, self).__init__(subparsers)
|
||||
self.parser.add_argument('-a', '--args', metavar='ARGS',
|
||||
default='', help='some arguments to serialize')
|
||||
|
||||
def measure_once(self, i):
|
||||
self.execute('test.limits.model', 'consume_nothing')
|
||||
|
||||
class BenchLogin(Bench):
|
||||
"""Login (update res_users.date)."""
|
||||
|
||||
command_name = 'bench-login'
|
||||
bench_name = 'res.users.login(1)'
|
||||
|
||||
def measure_once(self, i):
|
||||
self.common_proxy.login(self.database, self.user, self.password)
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
Call an arbitrary model's method.
|
||||
"""
|
||||
import ast
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
import time
|
||||
import xmlrpclib
|
||||
|
||||
import client
|
||||
|
||||
class Call(client.Client):
|
||||
"""\
|
||||
Call an arbitrary model's method.
|
||||
|
||||
Example:
|
||||
> oe call res.users.read '[1, 3]' '[]' -u 1 -p admin
|
||||
"""
|
||||
# TODO The above docstring is completely borked in the
|
||||
# --help message.
|
||||
|
||||
command_name = 'call'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(Call, self).__init__(subparsers)
|
||||
self.parser.add_argument('call', metavar='MODEL.METHOD',
|
||||
help='the model and the method to call, using the '
|
||||
'<model>.<method> format.')
|
||||
self.parser.add_argument('args', metavar='ARGUMENT',
|
||||
nargs='+',
|
||||
help='the argument for the method call, must be '
|
||||
'`ast.literal_eval` compatible. Can be repeated.')
|
||||
|
||||
def work(self):
|
||||
try:
|
||||
model, method = self.args.call.rsplit('.', 1)
|
||||
except:
|
||||
print "Invalid syntax `%s` must have the form <model>.<method>."
|
||||
sys.exit(1)
|
||||
args = tuple(map(ast.literal_eval, self.args.args)) if self.args.args else ()
|
||||
x = self.execute(model, method, *args)
|
||||
pprint.pprint(x, indent=4)
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
"""
|
||||
Define a few common arguments for client-side command-line tools.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import xmlrpclib
|
||||
|
||||
import common
|
||||
|
||||
class Client(common.Command):
|
||||
"""
|
||||
Base class for XML-RPC command-line clients. It must be inherited and the
|
||||
work() method overriden.
|
||||
"""
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(Client, self).__init__(subparsers)
|
||||
required_or_default = common.required_or_default
|
||||
self.parser.add_argument('-H', '--host', metavar='HOST',
|
||||
**required_or_default('HOST', 'the server host'))
|
||||
self.parser.add_argument('-P', '--port', metavar='PORT',
|
||||
**required_or_default('PORT', 'the server port'))
|
||||
|
||||
def execute(self, *args):
|
||||
return self.object_proxy.execute(self.database, self.uid, self.password, *args)
|
||||
|
||||
def initialize(self):
|
||||
self.host = self.args.host
|
||||
self.port = int(self.args.port)
|
||||
self.database = self.args.database
|
||||
self.user = self.args.user
|
||||
self.password = self.args.password
|
||||
|
||||
self.url = 'http://%s:%d/xmlrpc/' % (self.host, self.port)
|
||||
self.common_proxy = xmlrpclib.ServerProxy(self.url + 'common')
|
||||
self.object_proxy = xmlrpclib.ServerProxy(self.url + 'object')
|
||||
|
||||
try:
|
||||
self.uid = int(self.user)
|
||||
except ValueError, e:
|
||||
self.uid = self.common_proxy.login(self.database, self.user, self.password)
|
||||
|
||||
def run(self, *args):
|
||||
self.initialize()
|
||||
self.work(*args)
|
||||
|
||||
def work(self, *args):
|
||||
pass
|
||||
|
||||
class Open(Client):
|
||||
"""Get the web client's URL to view a specific model."""
|
||||
|
||||
command_name = 'open'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(Open, self).__init__(subparsers)
|
||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
||||
required=True, help='the view type')
|
||||
self.parser.add_argument('-v', '--view-mode', metavar='VIEWMODE',
|
||||
default='tree', help='the view mode')
|
||||
|
||||
def work(self):
|
||||
ids = self.execute('ir.actions.act_window', 'search', [
|
||||
('res_model', '=', self.args.model),
|
||||
('view_mode', 'like', self.args.view_mode),
|
||||
])
|
||||
xs = self.execute('ir.actions.act_window', 'read', ids, [])
|
||||
for x in xs:
|
||||
print x['id'], x['name']
|
||||
d = {}
|
||||
d['host'] = self.host
|
||||
d['port'] = self.port
|
||||
d['action_id'] = x['id']
|
||||
print " http://%(host)s:%(port)s/web/webclient/home#action_id=%(action_id)s" % d
|
||||
|
||||
class Show(Client):
|
||||
"""Display a record."""
|
||||
|
||||
command_name = 'show'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(Show, self).__init__(subparsers)
|
||||
self.parser.add_argument('-m', '--model', metavar='MODEL',
|
||||
required=True, help='the model')
|
||||
self.parser.add_argument('-i', '--id', metavar='RECORDID',
|
||||
required=True, help='the record id')
|
||||
|
||||
def work(self):
|
||||
xs = self.execute(self.args.model, 'read', [self.args.id], [])
|
||||
if xs:
|
||||
x = xs[0]
|
||||
print x['name']
|
||||
else:
|
||||
print "Record not found."
|
||||
|
||||
class ConsumeNothing(Client):
|
||||
"""Call test.limits.model.consume_nothing()."""
|
||||
|
||||
command_name = 'consume-nothing'
|
||||
|
||||
def work(self):
|
||||
xs = self.execute('test.limits.model', 'consume_nothing')
|
||||
|
||||
class ConsumeMemory(Client):
|
||||
"""Call test.limits.model.consume_memory()."""
|
||||
|
||||
command_name = 'consume-memory'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(ConsumeMemory, self).__init__(subparsers)
|
||||
self.parser.add_argument('--size', metavar='SIZE',
|
||||
required=True, help='size of the list to allocate')
|
||||
|
||||
def work(self):
|
||||
xs = self.execute('test.limits.model', 'consume_memory', int(self.args.size))
|
||||
|
||||
class LeakMemory(ConsumeMemory):
|
||||
"""Call test.limits.model.leak_memory()."""
|
||||
|
||||
command_name = 'leak-memory'
|
||||
|
||||
def work(self):
|
||||
xs = self.execute('test.limits.model', 'leak_memory', int(self.args.size))
|
||||
|
||||
class ConsumeCPU(Client):
|
||||
"""Call test.limits.model.consume_cpu_time()."""
|
||||
|
||||
command_name = 'consume-cpu'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
super(ConsumeCPU, self).__init__(subparsers)
|
||||
self.parser.add_argument('--seconds', metavar='INT',
|
||||
required=True, help='how much CPU time to consume')
|
||||
|
||||
def work(self):
|
||||
xs = self.execute('test.limits.model', 'consume_cpu_time', int(self.args.seconds))
|
|
@ -0,0 +1,88 @@
|
|||
"""
|
||||
Define a few common arguments for server-side command-line tools.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
def add_addons_argument(parser):
|
||||
"""
|
||||
Add a common --addons argument to a parser.
|
||||
"""
|
||||
parser.add_argument('--addons', metavar='ADDONS',
|
||||
**required_or_default('ADDONS',
|
||||
'colon-separated list of paths to addons'))
|
||||
|
||||
def get_addons_from_paths(paths, exclude):
|
||||
"""
|
||||
Build a list of available modules from a list of addons paths.
|
||||
"""
|
||||
exclude = exclude or []
|
||||
module_names = []
|
||||
for p in paths:
|
||||
if os.path.exists(p):
|
||||
names = list(set(os.listdir(p)))
|
||||
names = filter(lambda a: not (a.startswith('.') or a in exclude), names)
|
||||
module_names.extend(names)
|
||||
else:
|
||||
print "The addons path `%s` doesn't exist." % p
|
||||
sys.exit(1)
|
||||
return module_names
|
||||
|
||||
def required_or_default(name, h):
|
||||
"""
|
||||
Helper to define `argparse` arguments. If the name is the environment,
|
||||
the argument is optional and draw its value from the environment if not
|
||||
supplied on the command-line. If it is not in the environment, make it
|
||||
a mandatory argument.
|
||||
"""
|
||||
if os.environ.get('OPENERP_' + name.upper()):
|
||||
d = {'default': os.environ['OPENERP_' + name.upper()]}
|
||||
else:
|
||||
d = {'required': True}
|
||||
d['help'] = h + '. The environment variable OPENERP_' + \
|
||||
name.upper() + ' can be used instead.'
|
||||
return d
|
||||
|
||||
class Command(object):
|
||||
"""
|
||||
Base class to create command-line tools. It must be inherited and the
|
||||
run() method overriden.
|
||||
"""
|
||||
|
||||
command_name = 'stand-alone'
|
||||
|
||||
def __init__(self, subparsers=None):
|
||||
if subparsers:
|
||||
self.parser = parser = subparsers.add_parser(self.command_name,
|
||||
description=self.__class__.__doc__)
|
||||
else:
|
||||
self.parser = parser = argparse.ArgumentParser(
|
||||
description=self.__class__.__doc__)
|
||||
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
||||
**required_or_default('DATABASE', 'the database to connect to'))
|
||||
parser.add_argument('-u', '--user', metavar='USER',
|
||||
**required_or_default('USER', 'the user login or ID. When using '
|
||||
'RPC, providing an ID avoid the login() step'))
|
||||
parser.add_argument('-p', '--password', metavar='PASSWORD',
|
||||
**required_or_default('PASSWORD', 'the user password')) # TODO read it from the command line or from file.
|
||||
|
||||
parser.set_defaults(run=self.run_with_args)
|
||||
|
||||
def run_with_args(self, args):
|
||||
self.args = args
|
||||
self.run()
|
||||
|
||||
def run(self):
|
||||
print 'Stub Command.run().'
|
||||
|
||||
@classmethod
|
||||
def stand_alone(cls):
|
||||
"""
|
||||
A single Command object is a complete command-line program. See
|
||||
`openerp-command/stand-alone` for an example.
|
||||
"""
|
||||
command = cls()
|
||||
args = command.parser.parse_args()
|
||||
args.run(args)
|
|
@ -0,0 +1,25 @@
|
|||
"""
|
||||
Display the currently used configuration. The configuration for any
|
||||
sub-command is normally given by options. But some options can be specified
|
||||
using environment variables. This sub-command shows those variables.
|
||||
A `set` sub-command should be provided when the configuration is in a real
|
||||
configuration file instead of environment variables.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
def run(args):
|
||||
for x in ('database', 'addons', 'host', 'port'):
|
||||
x_ = ('openerp_' + x).upper()
|
||||
if x_ in os.environ:
|
||||
print '%s: %s' % (x, os.environ[x_])
|
||||
else:
|
||||
print '%s: <not set>' % (x, )
|
||||
os.environ['OPENERP_DATABASE'] = 'yeah'
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('conf',
|
||||
description='Display the currently used configuration.')
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,45 @@
|
|||
"""
|
||||
Drop a database.
|
||||
"""
|
||||
|
||||
import common
|
||||
|
||||
# TODO turn template1 in a parameter
|
||||
# This should be exposed from openerp (currently in
|
||||
# openerp/service/web_services.py).
|
||||
def drop_database(database_name):
|
||||
import openerp
|
||||
db = openerp.sql_db.db_connect('template1')
|
||||
cr = db.cursor()
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
try:
|
||||
# TODO option for doing this.
|
||||
# Try to terminate all other connections that might prevent
|
||||
# dropping the database
|
||||
try:
|
||||
cr.execute("""SELECT pg_terminate_backend(procpid)
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = %s AND
|
||||
procpid != pg_backend_pid()""",
|
||||
(database_name,))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
cr.execute('DROP DATABASE "%s"' % database_name)
|
||||
except Exception, e:
|
||||
print "Can't drop %s" % (database_name,)
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
def run(args):
|
||||
assert args.database
|
||||
drop_database(args.database)
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('drop',
|
||||
description='Drop a database.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
||||
**common.required_or_default('DATABASE', 'the database to create'))
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,103 @@
|
|||
"""
|
||||
Install OpenERP on a new (by default) database.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import common
|
||||
|
||||
def install_openerp(database_name, create_database_flag, module_names, install_demo_data):
|
||||
import openerp
|
||||
config = openerp.tools.config
|
||||
|
||||
if create_database_flag:
|
||||
create_database(database_name)
|
||||
|
||||
config['init'] = dict.fromkeys(module_names, 1)
|
||||
|
||||
# Install the import hook, to import openerp.addons.<module>.
|
||||
openerp.modules.module.initialize_sys_path()
|
||||
if hasattr(openerp.modules.loading, 'open_openerp_namespace'):
|
||||
openerp.modules.loading.open_openerp_namespace()
|
||||
|
||||
registry = openerp.modules.registry.RegistryManager.get(
|
||||
database_name, update_module=True, force_demo=install_demo_data)
|
||||
|
||||
return registry
|
||||
|
||||
# TODO turn template1 in a parameter
|
||||
# This should be exposed from openerp (currently in
|
||||
# openerp/service/web_services.py).
|
||||
def create_database(database_name):
|
||||
import openerp
|
||||
db = openerp.sql_db.db_connect('template1')
|
||||
cr = db.cursor() # TODO `with db as cr:`
|
||||
try:
|
||||
cr.autocommit(True)
|
||||
cr.execute("""CREATE DATABASE "%s"
|
||||
ENCODING 'unicode' TEMPLATE "template1" """ \
|
||||
% (database_name,))
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
def run(args):
|
||||
assert args.database
|
||||
assert not (args.module and args.all_modules)
|
||||
|
||||
import openerp
|
||||
|
||||
config = openerp.tools.config
|
||||
|
||||
if args.tests:
|
||||
config['log_handler'] = [':TEST']
|
||||
config['test_enable'] = True
|
||||
config['without_demo'] = False
|
||||
else:
|
||||
config['log_handler'] = [':CRITICAL']
|
||||
config['test_enable'] = False
|
||||
config['without_demo'] = True
|
||||
|
||||
if args.addons:
|
||||
args.addons = args.addons.split(':')
|
||||
else:
|
||||
args.addons = []
|
||||
config['addons_path'] = ','.join(args.addons)
|
||||
|
||||
if args.all_modules:
|
||||
module_names = common.get_addons_from_paths(args.addons, args.exclude)
|
||||
elif args.module:
|
||||
module_names = args.module
|
||||
else:
|
||||
module_names = ['base']
|
||||
|
||||
openerp.netsvc.init_logger()
|
||||
registry = install_openerp(args.database, not args.no_create, module_names, not config['without_demo'])
|
||||
|
||||
# The `_assertion_report` attribute was added on the registry during the
|
||||
# OpenERP 7.0 development.
|
||||
if hasattr(registry, '_assertion_report'):
|
||||
sys.exit(1 if registry._assertion_report.failures else 0)
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('initialize',
|
||||
description='Create and initialize a new OpenERP database.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
||||
**common.required_or_default('DATABASE', 'the database to create'))
|
||||
common.add_addons_argument(parser)
|
||||
parser.add_argument('--module', metavar='MODULE', action='append',
|
||||
help='specify a module to install'
|
||||
' (this option can be repeated)')
|
||||
parser.add_argument('--all-modules', action='store_true',
|
||||
help='install all visible modules (not compatible with --module)')
|
||||
parser.add_argument('--no-create', action='store_true',
|
||||
help='do not create the database, only initialize it')
|
||||
parser.add_argument('--exclude', metavar='MODULE', action='append',
|
||||
help='exclude a module from installation'
|
||||
' (this option can be repeated)')
|
||||
parser.add_argument('--tests', action='store_true',
|
||||
help='run the tests as modules are installed'
|
||||
' (use the `run-tests` command to choose specific'
|
||||
' tests to run against an existing database).'
|
||||
' Demo data are installed.')
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,7 @@
|
|||
import openerpcommand
|
||||
|
||||
def run():
|
||||
""" Main entry point for the openerp-command tool."""
|
||||
parser = openerpcommand.main_parser()
|
||||
args = parser.parse_args()
|
||||
args.run(args)
|
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
Display information about a given model.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
def run(args):
|
||||
assert args.database
|
||||
assert args.model
|
||||
import openerp
|
||||
openerp.tools.config['log_level'] = 100
|
||||
openerp.netsvc.init_logger()
|
||||
registry = openerp.modules.registry.RegistryManager.get(
|
||||
args.database, update_module=False)
|
||||
model = registry.get(args.model)
|
||||
longest_k = 1
|
||||
longest_string = 1
|
||||
columns = model._columns
|
||||
|
||||
if args.field and args.field not in columns:
|
||||
print "No such field."
|
||||
sys.exit(1)
|
||||
|
||||
if args.field:
|
||||
columns = { args.field: columns[args.field] }
|
||||
else:
|
||||
print "Fields (model `%s`, database `%s`):" % (args.model, args.database)
|
||||
|
||||
for k, v in columns.items():
|
||||
longest_k = len(k) if longest_k < len(k) else longest_k
|
||||
longest_string = len(v.string) \
|
||||
if longest_string < len(v.string) else longest_string
|
||||
for k, v in sorted(columns.items()):
|
||||
attr = []
|
||||
if v.required:
|
||||
attr.append("Required")
|
||||
if v.readonly:
|
||||
attr.append("Read-only")
|
||||
attr = '/'.join(attr)
|
||||
attr = '(' + attr + ')' if attr else attr
|
||||
if args.verbose:
|
||||
print v.string, '-- ' + k + ', ' + v._type, attr
|
||||
else:
|
||||
print k.ljust(longest_k + 2), v._type, attr
|
||||
if args.verbose and v.help:
|
||||
print textwrap.fill(v.help, initial_indent=' ', subsequent_indent=' ')
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('model',
|
||||
description='Display information about a given model for an existing database.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE', required=True,
|
||||
help='the database to connect to')
|
||||
parser.add_argument('-m', '--model', metavar='MODEL', required=True,
|
||||
help='the model for which information should be displayed')
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='display more information')
|
||||
parser.add_argument('-f', '--field', metavar='FIELD',
|
||||
help='display information only for this particular field')
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Show module information for a given database or from the file-system.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from . import common
|
||||
|
||||
# TODO provide a --rpc flag to use XML-RPC (with a specific username) instead
|
||||
# of server-side library.
|
||||
def run(args):
|
||||
assert args.database
|
||||
import openerp
|
||||
|
||||
config = openerp.tools.config
|
||||
config['log_handler'] = [':CRITICAL']
|
||||
if args.addons:
|
||||
args.addons = args.addons.split(':')
|
||||
else:
|
||||
args.addons = []
|
||||
config['addons_path'] = ','.join(args.addons)
|
||||
openerp.netsvc.init_logger()
|
||||
|
||||
if args.filesystem:
|
||||
module_names = common.get_addons_from_paths(args.addons, [])
|
||||
print "Modules (addons path %s):" % (', '.join(args.addons),)
|
||||
for x in sorted(module_names):
|
||||
print x
|
||||
else:
|
||||
registry = openerp.modules.registry.RegistryManager.get(
|
||||
args.database, update_module=False)
|
||||
|
||||
xs = []
|
||||
ir_module_module = registry.get('ir.module.module')
|
||||
cr = registry.db.cursor() # TODO context manager
|
||||
try:
|
||||
ids = ir_module_module.search(cr, openerp.SUPERUSER_ID, [], {})
|
||||
xs = ir_module_module.read(cr, openerp.SUPERUSER_ID, ids, [], {})
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
if xs:
|
||||
print "Modules (database `%s`):" % (args.database,)
|
||||
for x in xs:
|
||||
if args.short:
|
||||
print '%3d %s' % (x['id'], x['name'])
|
||||
else:
|
||||
print '%3d %s %s' % (x['id'], x['name'], {'installed': '(installed)'}.get(x['state'], ''))
|
||||
else:
|
||||
print "No module found (database `%s`)." % (args.database,)
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('module',
|
||||
description='Display modules known from a given database or on file-system.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
||||
**common.required_or_default('DATABASE', 'the database to modify'))
|
||||
common.add_addons_argument(parser)
|
||||
parser.add_argument('-m', '--module', metavar='MODULE', required=False,
|
||||
help='the module for which information should be shown')
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='display more information')
|
||||
parser.add_argument('--short', action='store_true',
|
||||
help='display less information')
|
||||
parser.add_argument('-f', '--filesystem', action='store_true',
|
||||
help='display module in the addons path, not in db')
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
Read a record.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
# TODO provide a --rpc flag to use XML-RPC (with a specific username) instead
|
||||
# of server-side library.
|
||||
def run(args):
|
||||
assert args.database
|
||||
assert args.model
|
||||
import openerp
|
||||
config = openerp.tools.config
|
||||
config['log_handler'] = [':CRITICAL']
|
||||
openerp.netsvc.init_logger()
|
||||
registry = openerp.modules.registry.RegistryManager.get(
|
||||
args.database, update_module=False)
|
||||
model = registry.get(args.model)
|
||||
cr = registry.db.cursor() # TODO context manager
|
||||
field_names = [args.field] if args.field else []
|
||||
if args.short:
|
||||
# ignore --field
|
||||
field_names = ['name']
|
||||
try:
|
||||
xs = model.read(cr, 1, args.id, field_names, {})
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
if xs:
|
||||
print "Records (model `%s`, database `%s`):" % (args.model, args.database)
|
||||
x = xs[0]
|
||||
if args.short:
|
||||
print str(x['id']) + '.', x['name']
|
||||
else:
|
||||
longest_k = 1
|
||||
for k, v in x.items():
|
||||
longest_k = len(k) if longest_k < len(k) else longest_k
|
||||
for k, v in sorted(x.items()):
|
||||
print (k + ':').ljust(longest_k + 2), v
|
||||
else:
|
||||
print "Record not found."
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('read',
|
||||
description='Display a record.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE', required=True,
|
||||
help='the database to connect to')
|
||||
parser.add_argument('-m', '--model', metavar='MODEL', required=True,
|
||||
help='the model for which a record should be read')
|
||||
parser.add_argument('-i', '--id', metavar='RECORDID', required=True,
|
||||
help='the record id')
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='display more information')
|
||||
parser.add_argument('--short', action='store_true',
|
||||
help='display less information')
|
||||
parser.add_argument('-f', '--field', metavar='FIELD',
|
||||
help='display information only for this particular field')
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,202 @@
|
|||
"""
|
||||
Execute the unittest2 tests available in OpenERP addons.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
|
||||
import common
|
||||
|
||||
def get_test_modules(module, submodule, explode):
|
||||
"""
|
||||
Return a list of submodules containing tests.
|
||||
`submodule` can be:
|
||||
- None
|
||||
- the name of a submodule
|
||||
- '__fast_suite__'
|
||||
- '__sanity_checks__'
|
||||
"""
|
||||
# Turn command-line module, submodule into importable names.
|
||||
if module is None:
|
||||
pass
|
||||
elif module == 'openerp':
|
||||
module = 'openerp.tests'
|
||||
else:
|
||||
module = 'openerp.addons.' + module + '.tests'
|
||||
|
||||
# Try to import the module
|
||||
try:
|
||||
__import__(module)
|
||||
except Exception, e:
|
||||
if explode:
|
||||
print 'Can not `import %s`.' % module
|
||||
import logging
|
||||
logging.exception('')
|
||||
sys.exit(1)
|
||||
else:
|
||||
if str(e) == 'No module named tests':
|
||||
# It seems the module has no `tests` sub-module, no problem.
|
||||
pass
|
||||
else:
|
||||
print 'Can not `import %s`.' % module
|
||||
return []
|
||||
|
||||
# Discover available test sub-modules.
|
||||
m = sys.modules[module]
|
||||
submodule_names = sorted([x for x in dir(m) \
|
||||
if x.startswith('test_') and \
|
||||
isinstance(getattr(m, x), types.ModuleType)])
|
||||
submodules = [getattr(m, x) for x in submodule_names]
|
||||
|
||||
def show_submodules_and_exit():
|
||||
if submodule_names:
|
||||
print 'Available submodules are:'
|
||||
for x in submodule_names:
|
||||
print ' ', x
|
||||
sys.exit(1)
|
||||
|
||||
if submodule is None:
|
||||
# Use auto-discovered sub-modules.
|
||||
ms = submodules
|
||||
elif submodule == '__fast_suite__':
|
||||
# Obtain the explicit test sub-modules list.
|
||||
ms = getattr(sys.modules[module], 'fast_suite', None)
|
||||
# `suite` was used before the 6.1 release instead of `fast_suite`.
|
||||
ms = ms if ms else getattr(sys.modules[module], 'suite', None)
|
||||
if ms is None:
|
||||
if explode:
|
||||
print 'The module `%s` has no defined test suite.' % (module,)
|
||||
show_submodules_and_exit()
|
||||
else:
|
||||
ms = []
|
||||
elif submodule == '__sanity_checks__':
|
||||
ms = getattr(sys.modules[module], 'checks', None)
|
||||
if ms is None:
|
||||
if explode:
|
||||
print 'The module `%s` has no defined sanity checks.' % (module,)
|
||||
show_submodules_and_exit()
|
||||
else:
|
||||
ms = []
|
||||
else:
|
||||
# Pick the command-line-specified test sub-module.
|
||||
m = getattr(sys.modules[module], submodule, None)
|
||||
ms = [m]
|
||||
|
||||
if m is None:
|
||||
if explode:
|
||||
print 'The module `%s` has no submodule named `%s`.' % \
|
||||
(module, submodule)
|
||||
show_submodules_and_exit()
|
||||
else:
|
||||
ms = []
|
||||
|
||||
return ms
|
||||
|
||||
def run(args):
|
||||
import unittest2
|
||||
|
||||
import openerp
|
||||
|
||||
config = openerp.tools.config
|
||||
config['db_name'] = args.database
|
||||
if args.port:
|
||||
config['xmlrpc_port'] = int(args.port)
|
||||
config['admin_passwd'] = 'admin'
|
||||
config['db_password'] = 'a2aevl8w' # TODO from .openerpserverrc
|
||||
config['addons_path'] = args.addons.replace(':',',')
|
||||
if args.addons:
|
||||
args.addons = args.addons.split(':')
|
||||
else:
|
||||
args.addons = []
|
||||
if args.sanity_checks and args.fast_suite:
|
||||
print 'Only at most one of `--sanity-checks` and `--fast-suite` ' \
|
||||
'can be specified.'
|
||||
sys.exit(1)
|
||||
|
||||
import logging
|
||||
openerp.netsvc.init_alternative_logger()
|
||||
logging.getLogger('openerp').setLevel(logging.CRITICAL)
|
||||
|
||||
# Install the import hook, to import openerp.addons.<module>.
|
||||
openerp.modules.module.initialize_sys_path()
|
||||
openerp.modules.loading.open_openerp_namespace()
|
||||
|
||||
# Extract module, submodule from the command-line args.
|
||||
if args.module is None:
|
||||
module, submodule = None, None
|
||||
else:
|
||||
splitted = args.module.split('.')
|
||||
if len(splitted) == 1:
|
||||
module, submodule = splitted[0], None
|
||||
elif len(splitted) == 2:
|
||||
module, submodule = splitted
|
||||
else:
|
||||
print 'The `module` argument must have the form ' \
|
||||
'`module[.submodule]`.'
|
||||
sys.exit(1)
|
||||
|
||||
# Import the necessary modules and get the corresponding suite.
|
||||
if module is None:
|
||||
# TODO
|
||||
modules = common.get_addons_from_paths(args.addons, []) # TODO openerp.addons.base is not included ?
|
||||
test_modules = []
|
||||
for module in ['openerp'] + modules:
|
||||
if args.fast_suite:
|
||||
submodule = '__fast_suite__'
|
||||
if args.sanity_checks:
|
||||
submodule = '__sanity_checks__'
|
||||
test_modules.extend(get_test_modules(module,
|
||||
submodule, explode=False))
|
||||
else:
|
||||
if submodule and args.fast_suite:
|
||||
print "Submodule name `%s` given, ignoring `--fast-suite`." % (submodule,)
|
||||
if submodule and args.sanity_checks:
|
||||
print "Submodule name `%s` given, ignoring `--sanity-checks`." % (submodule,)
|
||||
if not submodule and args.fast_suite:
|
||||
submodule = '__fast_suite__'
|
||||
if not submodule and args.sanity_checks:
|
||||
submodule = '__sanity_checks__'
|
||||
test_modules = get_test_modules(module,
|
||||
submodule, explode=True)
|
||||
|
||||
# Run the test suite.
|
||||
if not args.dry_run:
|
||||
suite = unittest2.TestSuite()
|
||||
for test_module in test_modules:
|
||||
suite.addTests(unittest2.TestLoader().loadTestsFromModule(test_module))
|
||||
r = unittest2.TextTestRunner(verbosity=2).run(suite)
|
||||
if r.errors or r.failures:
|
||||
sys.exit(1)
|
||||
else:
|
||||
print 'Test modules:'
|
||||
for test_module in test_modules:
|
||||
print ' ', test_module.__name__
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('run-tests',
|
||||
description='Run the OpenERP server and/or addons tests.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE', required=True,
|
||||
help='the database to test. Depending on the test suites, the '
|
||||
'database must already exist or not.')
|
||||
parser.add_argument('-p', '--port', metavar='PORT',
|
||||
help='the port used for WML-RPC tests')
|
||||
common.add_addons_argument(parser)
|
||||
parser.add_argument('-m', '--module', metavar='MODULE',
|
||||
default=None,
|
||||
help='the module to test in `module[.submodule]` notation. '
|
||||
'Use `openerp` for the core OpenERP tests. '
|
||||
'Leave empty to run every declared tests. '
|
||||
'Give a module but no submodule to run all the module\'s declared '
|
||||
'tests. If both the module and the submodule are given, '
|
||||
'the sub-module can be run even if it is not declared in the module.')
|
||||
parser.add_argument('--fast-suite', action='store_true',
|
||||
help='run only the tests explicitely declared in the fast suite (this '
|
||||
'makes sense only with the bare `module` notation or no module at '
|
||||
'all).')
|
||||
parser.add_argument('--sanity-checks', action='store_true',
|
||||
help='run only the sanity check tests')
|
||||
parser.add_argument('--dry-run', action='store_true',
|
||||
help='do not run the tests')
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,77 @@
|
|||
"""
|
||||
Generate an OpenERP module skeleton.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def run(args):
|
||||
assert args.module
|
||||
module = args.module
|
||||
|
||||
if os.path.exists(module):
|
||||
print "The path `%s` already exists."
|
||||
sys.exit(1)
|
||||
|
||||
os.mkdir(module)
|
||||
os.mkdir(os.path.join(module, 'models'))
|
||||
with open(os.path.join(module, '__openerp__.py'), 'w') as h:
|
||||
h.write(MANIFEST)
|
||||
with open(os.path.join(module, '__init__.py'), 'w') as h:
|
||||
h.write(INIT_PY)
|
||||
with open(os.path.join(module, 'models', '__init__.py'), 'w') as h:
|
||||
h.write(MODELS_PY % (module,))
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('scaffold',
|
||||
description='Generate an OpenERP module skeleton.')
|
||||
parser.add_argument('module', metavar='MODULE',
|
||||
help='the name of the generated module')
|
||||
|
||||
parser.set_defaults(run=run)
|
||||
|
||||
MANIFEST = """\
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': '<Module name>',
|
||||
'version': '0.0',
|
||||
'category': '<Category>',
|
||||
'description': '''
|
||||
<Long description>
|
||||
''',
|
||||
'author': '<author>',
|
||||
'maintainer': '<maintainer>',
|
||||
'website': 'http://<website>',
|
||||
# Add any module that are necessary for this module to correctly work in
|
||||
# the `depends` list.
|
||||
'depends': ['base'],
|
||||
'data': [
|
||||
],
|
||||
'test': [
|
||||
],
|
||||
# Set to False if you want to prevent the module to be known by OpenERP
|
||||
# (and thus appearing in the list of modules).
|
||||
'installable': True,
|
||||
# Set to True if you want the module to be automatically whenever all its
|
||||
# dependencies are installed.
|
||||
'auto_install': False,
|
||||
}
|
||||
"""
|
||||
|
||||
INIT_PY = """\
|
||||
# -*- coding: utf-8 -*-
|
||||
import models
|
||||
"""
|
||||
|
||||
MODELS_PY = """\
|
||||
# -*- coding: utf-8 -*-
|
||||
import openerp
|
||||
|
||||
# Define a new model.
|
||||
class my_model(openerp.osv.osv.Model):
|
||||
|
||||
_name = '%s.my_model'
|
||||
|
||||
_columns = {
|
||||
}
|
||||
"""
|
|
@ -0,0 +1,67 @@
|
|||
"""
|
||||
Install OpenERP on a new (by default) database.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import common
|
||||
|
||||
# TODO turn template1 in a parameter
|
||||
# This should be exposed from openerp (currently in
|
||||
# openerp/service/web_services.py).
|
||||
def create_database(database_name):
|
||||
import openerp
|
||||
db = openerp.sql_db.db_connect('template1')
|
||||
cr = db.cursor() # TODO `with db as cr:`
|
||||
try:
|
||||
cr.autocommit(True)
|
||||
cr.execute("""CREATE DATABASE "%s"
|
||||
ENCODING 'unicode' TEMPLATE "template1" """ \
|
||||
% (database_name,))
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
def run(args):
|
||||
assert args.database
|
||||
assert args.module
|
||||
|
||||
import openerp
|
||||
|
||||
config = openerp.tools.config
|
||||
config['log_handler'] = [':CRITICAL']
|
||||
if args.addons:
|
||||
args.addons = args.addons.split(':')
|
||||
else:
|
||||
args.addons = []
|
||||
config['addons_path'] = ','.join(args.addons)
|
||||
openerp.netsvc.init_logger()
|
||||
|
||||
# Install the import hook, to import openerp.addons.<module>.
|
||||
openerp.modules.module.initialize_sys_path()
|
||||
openerp.modules.loading.open_openerp_namespace()
|
||||
|
||||
registry = openerp.modules.registry.RegistryManager.get(
|
||||
args.database, update_module=False)
|
||||
|
||||
ir_module_module = registry.get('ir.module.module')
|
||||
cr = registry.db.cursor() # TODO context manager
|
||||
try:
|
||||
ids = ir_module_module.search(cr, openerp.SUPERUSER_ID, [('name', 'in', args.module), ('state', '=', 'installed')], {})
|
||||
if len(ids) == len(args.module):
|
||||
ir_module_module.button_immediate_uninstall(cr, openerp.SUPERUSER_ID, ids, {})
|
||||
else:
|
||||
print "At least one module not found (database `%s`)." % (args.database,)
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('uninstall',
|
||||
description='Uninstall some modules from an OpenERP database.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE',
|
||||
**common.required_or_default('DATABASE', 'the database to modify'))
|
||||
common.add_addons_argument(parser)
|
||||
parser.add_argument('--module', metavar='MODULE', action='append',
|
||||
help='specify a module to uninstall'
|
||||
' (this option can be repeated)')
|
||||
|
||||
parser.set_defaults(run=run)
|
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
Update an existing OpenERP database.
|
||||
"""
|
||||
|
||||
def run(args):
|
||||
assert args.database
|
||||
import openerp
|
||||
config = openerp.tools.config
|
||||
config['update']['all'] = 1
|
||||
openerp.modules.registry.RegistryManager.get(
|
||||
args.database, update_module=True)
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('update',
|
||||
description='Update an existing OpenERP database.')
|
||||
parser.add_argument('-d', '--database', metavar='DATABASE', required=True,
|
||||
help='the database to update')
|
||||
|
||||
parser.set_defaults(run=run)
|
Loading…
Reference in New Issue