OERPLib-0.7.0/0000775000175000017500000000000012110526160012252 5ustar sebseb00000000000000OERPLib-0.7.0/AUTHORS.txt0000664000175000017500000000011512110524053014134 0ustar sebseb00000000000000Original Author --------------- Sébastien ALIX OERPLib-0.7.0/doc/0000775000175000017500000000000012110526160013017 5ustar sebseb00000000000000OERPLib-0.7.0/doc/source/0000775000175000017500000000000012110526160014317 5ustar sebseb00000000000000OERPLib-0.7.0/doc/source/ref_wizard.rst0000664000175000017500000000015312110524053017203 0ustar sebseb00000000000000oerplib.service.wizard ====================== .. autoclass:: oerplib.service.wizard.Wizard :members: OERPLib-0.7.0/doc/source/ref_rpc.rst0000664000175000017500000000010312110524053016462 0ustar sebseb00000000000000oerplib.rpc =========== .. automodule:: oerplib.rpc :members: OERPLib-0.7.0/doc/source/ref_tools.rst0000664000175000017500000000011112110524053017035 0ustar sebseb00000000000000oerplib.tools ============= .. automodule:: oerplib.tools :members: OERPLib-0.7.0/doc/source/tutorials.rst0000664000175000017500000003140312110524053017077 0ustar sebseb00000000000000.. _tutorials: Tutorials ========= First step: prepare the connection and login -------------------------------------------- You need an instance of the :class:`OERP ` class to dialog with an `OpenERP` server. Let's pretend that you want to connect as `admin` on the `db_name` database of the local `OpenERP` server (with the `XML-RPC` service which listens on port `8071`). First, prepare the connection:: >>> import oerplib >>> oerp = oerplib.OERP(server='localhost', protocol='xmlrpc', port=8071) You can also specify the default database to use with the `database` parameter:: >>> oerp = oerplib.OERP(server='localhost', database='db_name', protocol='xmlrpc', port=8071) To check databases available, use the :attr:`oerp.db ` attribute with the **list** method:: >>> oerp.db.list() ['db_name', 'db_name2', ...] The connection is ready, you are able to log in on the server with the account of your choice:: >>> user = oerp.login(user='admin', passwd='admin') Or, if no default database was specified before:: >>> user = oerp.login(user='admin', passwd='admin', database='db_name') The ``login`` method returns an object representing the user connected. It is built from the server-side model ``res.users``, and all its informations are accessible (see :ref:`browse-records` section):: >>> print(user.name) # print the full name of the user >>> print(user.company_id.name) # print the name of its company Now you are connected, you can easily execute any kind of `RPC` queries on the the `OpenERP` server (execute model methods, trigger workflow, download reports, and handle wizards). .. _tutorials-execute-queries: Execute queries --------------- The basic method to execute queries (related to the ``/object`` `RPC` service) is :func:`execute `. It takes at least two parameters (model name and the method name) following by variable parameters according to the method called. Example:: >>> order_data = oerp.execute('sale.order', 'read', [1], ['name']) This instruction will call the ``read`` method of the model ``sale.order`` with the parameters ``[1]`` (list of record IDs) and ``['name']`` (list of fields to return). However, for usual methods such as ``create``, ``read``, ``write``, ``unlink`` and ``search`` there are convenient shortcuts available (see :class:`oerplib.OERP`):: >>> partner_id = oerp.create('res.partner', {'name': 'Jacky Bob', 'lang': 'fr_FR'}) >>> partner_data = oerp.read('res.partner', [partner_id], ['name']) >>> oerp.write('res.partner', [partner_id], {'name': 'Charly Bob'}) True >>> partner_ids = oerp.search('res.partner', [('name', 'ilike', 'Bob')]) >>> oerp.unlink('res.partner', [partner_id]) True There is another way to perform all methods of a model, with the :func:`get ` method, which provide an API almost syntactically identical to the `OpenERP` server side API (see :class:`oerplib.service.osv.Model`):: >>> user_obj = oerp.get('res.users') >>> user_obj.write([1], {'name': "Dupont D."}) True >>> context = user_obj.context_get() >>> context {'lang': 'fr_FR', 'tz': False} >>> product_obj = oerp.get('product.product') >>> product_obj.name_get([3, 4]) [[3, '[PC1] PC Basic'], [4, u'[PC2] Basic+ PC (assembl\xe9 sur commande)']] If you run an `OpenERP` version `6.1` or above, the user context is automatically sent. You can disable this behaviour with the :attr:`oerplib.OERP.config` property:: >>> oerp.config['auto_context'] = False >>> product_obj.name_get([3, 4]) # Without context, lang 'en_US' by default [[3, '[PC1] Basic PC'], [4, '[PC2] Basic+ PC (assembly on order)']] .. note:: The ``auto_context`` option only affect model methods. Here is another example of how to install a module (you have to be logged as an administrator to perform this task). The ``button_immediate_install`` method used here is available since `OpenERP v6.1`:: >>> module_obj = oerp.get('ir.module.module') >>> module_id = module_obj.search([('name', '=', 'purchase')]) >>> module_obj.button_immediate_install(module_id) .. _browse-records: Browse records -------------- A great functionality of `OERPLib` is its ability to generate objects that are similar to browsable records found on the `OpenERP` server. All this is possible using the :func:`browse ` method:: # fetch one record partner = oerp.browse('res.partner', 1) # Partner ID = 1 print(partner.name) # fetch several records for partner in oerp.browse('res.partner', [1, 2]): print(partner.name) From such objects, it is possible to easily explore relationships. The related records are generated on the fly:: partner = oerp.browse('res.partner', 3) for addr in partner.address: print(addr.name) You can browse objects through a :class:`model ` too. In fact, both methods are strictly identical, :func:`oerplib.OERP.browse` is simply a shortcut to the other:: >>> partner1 = oerp.browse('res.partner', 3) >>> partner2 = oerp.get('res.partner').browse(3) >>> partner1 == partner2 True Outside relation fields, Python data types are used, like ``datetime.date`` and ``datetime.datetime``:: >>> order = oerp.browse('purchase.order', 42) >>> order.minimum_planned_date datetime.datetime(2012, 3, 10, 0, 0) >>> order.date_order datetime.date(2012, 3, 8) A list of data types used by ``browse_record`` fields are available :ref:`here `. Update data through browsable records ------------------------------------- Update data of a browsable record is workable with the :func:`write_record ` method of an :class:`OERP ` instance. Let's update the first contact's name of a partner:: >>> addr = list(partner.address)[0] # Get the first address >>> addr.name = "Caporal Jones" >>> oerp.write_record(addr) This is equivalent to:: >>> addr_id = list(partner.address)[0].id >>> oerp.write('res.partner.address', [addr_id], {'name': "Caporal Jones"}) Char, Float, Integer, Boolean, Text and Binary '''''''''''''''''''''''''''''''''''''''''''''' As see above, it's as simple as that:: >>> partner.name = "OpenERP" >>> oerp.write_record(partner) Selection ''''''''' Same as above, except there is a check about the value assigned. For instance, the field ``type`` of the ``res.partner.address`` model accept values contains in ``['default', 'invoice', 'delivery', 'contact', 'other']``:: >>> my_partner_address.type = 'default' # Ok >>> my_partner_address.type = 'foobar' # Error! Traceback (most recent call last): File "", line 1, in File "oerplib/fields.py", line 58, in setter value = self.check_value(value) File "oerplib/fields.py", line 73, in check_value field_name=self.name, ValueError: The value 'foobar' supplied doesn't match with the possible values '['default', 'invoice', 'delivery', 'contact', 'other']' for the 'type' field Many2One '''''''' You can also update a ``many2one`` field, with either an ID or a browsable record:: >>> addr.partner_id = 42 # with an ID >>> oerp.write_record(addr) >>> partner = oerp.browse('res.partner', 42) # with a browsable record >>> addr.partner_id = partner >>> oerp.write_record(addr) You can't put any ID or browsable record, a check is made on the relationship to ensure data integrity:: >>> user = oerp.browse('res.users', 1) >>> addr = oerp.browse('res.partner.address', 1) >>> addr.partner_id = user Traceback (most recent call last): File "", line 1, in File "oerplib/fields.py", line 128, in setter o_rel = self.check_value(o_rel) File "oerplib/fields.py", line 144, in check_value field_name=self.name)) ValueError: Instance of 'res.users' supplied doesn't match with the relation 'res.partner' of the 'partner_id' field. One2Many and Many2Many '''''''''''''''''''''' ``one2many`` and ``many2many`` fields can be updated by providing a list of tuple as specified in the `OpenERP` documentation:: >>> user = oerp.browse('res.users', 1) >>> user.groups_id = [(6, 0, [8, 5, 6, 4])] >>> oerp.write_record(user) Reference ''''''''' To update a ``reference`` field, you have to use either a string or a browsable record as below:: >>> helpdesk = oerp.browse('crm.helpdesk', 1) >>> helpdesk.ref = 'res.partner,1' # with a string with the format '{relation},{id}' >>> oerp.write_record(helpdesk) >>> partner = oerp.browse('res.partner', 1) >>> helpdesk.ref = partner # with a browsable record >>> oerp.write_record(helpdesk) A check is made on the relation name:: >>> helpdesk.ref = 'foo.bar,42' Traceback (most recent call last): File "", line 1, in File "oerplib/service/osv/fields.py", line 213, in __set__ value = self.check_value(value) File "oerplib/service/osv/fields.py", line 244, in check_value self._check_relation(relation) File "oerplib/service/osv/fields.py", line 225, in _check_relation field_name=self.name, ValueError: The value 'foo.bar' supplied doesn't match with the possible values '['res.partner', 'calendar.event', 'crm.meeting']' for the 'ref' field Date and Datetime ''''''''''''''''' ``date`` and ``datetime`` fields accept either string values or ``datetime.date/datetime.datetime`` objects. With ``datetime.date`` and ``datetime.datetime`` objects:: >>> order = oerp.browse('purchase.order', 42) >>> order.date_order = datetime.date(2011, 9, 20) >>> order.minimum_planned_date = datetime.datetime(2011, 9, 20, 12, 31, 24) >>> oerp.write_record(order) With formated strings:: >>> order.date_order = "2011-09-20" # %Y-%m-%d >>> order.minimum_planned_date = "2011-09-20 12:31:24" # %Y-%m-%d %H:%M:%S >>> oerp.write_record(order) As always, a wrong type will raise an exception:: >>> order.date_order = "foobar" Traceback (most recent call last): File "", line 1, in File "oerplib/fields.py", line 187, in setter value = self.check_value(value) File "oerplib/fields.py", line 203, in check_value self.pattern)) ValueError: Value not well formatted, expecting '%Y-%m-%d' format Generate reports ---------------- Another nice functionnality is the reports generation (related to the ``/report`` `RPC` service) with the :func:`report ` method. You have to supply the name of the report, the name of the model and the ID of the record related:: >>> oerp.report('sale.order', 'sale.order', 1) '/tmp/oerplib_uJ8Iho.pdf' >>> oerp.report('webkitaccount.invoice', 'account.invoice', 1) '/tmp/oerplib_r1W9jG.pdf' The method will return the path to the generated temporary report file. Manage databases ---------------- You can manage `OpenERP` databases with the :attr:`oerplib.OERP.db` property. It offers you a dynamic access to all methods of the ``/db`` RPC service in order to list, create, drop, dump, restore databases and so on. .. note:: You have not to be logged in to perform database management tasks. Instead, you have to use the "super admin" password. Prepare a connection:: >>> import oerplib >>> oerp = oerplib.OERP(server='localhost') At this point, you are able to list databases of this server:: >>> oerp.db.list() [] Let's create a new database:: >>> database_id = oerp.db.create('super_admin_passwd', 'test_db', False, 'fr_FR', 'admin') The creation process may take some time on the `OpenERP` server, and you have to wait before using the new database. The state of the creation process is returned by the :func:`get_progress ` method:: >>> database_id = oerp.db.create('super_admin_passwd', 'test_db', False, 'fr_FR', 'admin') >>> while not oerp.db.get_progress('super_admin_passwd', database_id)[0] ... pass >>> oerp.login('admin', 'admin', 'test_db') However, `OERPLib` simplifies this by providing the :func:`create_and_wait ` method:: >>> oerp.db.create_and_wait('super_admin_passwd', 'test_db', False, 'fr_FR', 'admin') [{'login': u'admin', 'password': u'admin', 'name': u'Administrator'}, {'login': u'demo', 'password': u'demo', 'name': u'Demo User'}] Some documentation about methods offered by the `OpenERP` ``/db`` RPC service is available :class:`here `. Change the timeout ------------------ By default, the timeout is set to 120 seconds for all RPC requests. If your requests need a higher timeout, you can set it through the :attr:`oerplib.OERP.config` property:: >>> oerp.config['timeout'] 120 >>> oerp.config['timeout'] = 300 # Set the timeout to 300 seconds OERPLib-0.7.0/doc/source/index.rst0000664000175000017500000000526312110524053016165 0ustar sebseb00000000000000.. OERPLib documentation master file, created by sphinx-quickstart on Thu Sep 15 10:49:22 2011. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to OERPLib's documentation! =================================== Introduction ------------ `OERPLib` is a client library to `OpenERP` server. It aims to provide an easy way to remotely pilot an `OpenERP` server. Features supported: - `XML-RPC` and `Net-RPC` protocols, - access to all methods proposed by an `OpenERP` model class (even ``browse``) with an API similar to the server-side API, - ability to use named parameters with such methods (`OpenERP` >= `6.1`), - user context automatically sent (`OpenERP` >= `6.1`), - browse records, - execute workflows, - manage databases, - reports downloading. Quick start ----------- How does it work? See below:: import oerplib # Prepare the connection to the OpenERP server oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) # Check available databases print(oerp.db.list()) # Login (the object returned is a browsable record) user = oerp.login('user', 'passwd', 'db_name') print(user.name) # name of the user connected print(user.company_id.name) # the name of its company # Simple 'raw' query user_data = oerp.execute('res.users', 'read', [user.id]) print(user_data) # Use all methods of a model class order_obj = oerp.get('sale.order') order_ids = order_obj.search([]) for order in order_obj.browse(order_ids): print(order.name) products = [line.product_id.name for line in order.order_line] print(products) # Update data through a browsable record user.name = "Brian Jones" oerp.write_record(user) For more details, see the :ref:`tutorials ` and the :ref:`API reference `. Download and install -------------------- See :ref:`download-install` section. Contents -------- .. toctree:: :maxdepth: 3 download_install tutorials reference Supported OpenERP versions -------------------------- `OERPLib` has been tested on `OpenERP` server v5.0, v6.0, v6.1 and v7.0. It should work on next versions if `OpenERP` keeps a stable API. Supported Python versions ------------------------- `OERPLib` support Python versions 2.6 and 2.7. License ------- This software is made available under the LGPLv3 license. Bugs or suggestions ------------------- Please, feel free to report bugs or suggestions in the `Bug Tracker `_! Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` OERPLib-0.7.0/doc/source/ref_common.rst0000664000175000017500000000015312110524053017173 0ustar sebseb00000000000000oerplib.service.common ====================== .. autoclass:: oerplib.service.common.Common :members: OERPLib-0.7.0/doc/source/ref_fields.rst0000664000175000017500000000257612110524053017164 0ustar sebseb00000000000000.. _fields: Browse object fields ==================== The table below presents the Python types returned by `OERPLib` for each `OpenERP` fields used by ``browse_record`` objects (see the :func:`browse ` method): ================ ============================== `OpenERP` fields Python types used in `OERPLib` ================ ============================== fields.binary basestring (str or unicode) fields.boolean bool fields.char basestring (str or unicode) fields.date `datetime `_.date fields.datetime `datetime `_.datetime fields.float float fields.integer integer fields.selection basestring (str or unicode) fields.text basestring (str or unicode) ================ ============================== Exceptions made for relation fields: ================ =========================================================== `OpenERP` fields Types used in `OERPLib` ================ =========================================================== fields.many2one ``browse_record`` instance fields.one2many generator to iterate on ``browse_record`` instances fields.many2many generator to iterate on ``browse_record`` instances fields.reference ``browse_record`` instance ================ =========================================================== OERPLib-0.7.0/doc/source/ref_osv.rst0000664000175000017500000000043012110524053016510 0ustar sebseb00000000000000oerplib.service.osv =================== oerplib.service.osv.Model ''''''''''''''''''''''''' .. autoclass:: oerplib.service.osv.Model :members: oerplib.service.osv.BrowseRecord '''''''''''''''''''''''''''''''' .. autoclass:: oerplib.service.osv.BrowseRecord :members: OERPLib-0.7.0/doc/source/ref_error.rst0000664000175000017500000000011212110524053017027 0ustar sebseb00000000000000oerplib.error ============= .. automodule:: oerplib.error :members: OERPLib-0.7.0/doc/source/ref_oerplib.rst0000664000175000017500000000053612110524053017344 0ustar sebseb00000000000000oerplib ======= .. automodule:: oerplib :members: Here's a sample session using this module:: >>> import oerplib >>> oerp = oerplib.OERP('localhost') # connect to localhost, default port >>> user = oerp.login('admin', 'admin', 'my_database') # login returns an user object >>> user.name 'Administrator' OERPLib-0.7.0/doc/source/conf.py0000664000175000017500000001576312110524053015631 0ustar sebseb00000000000000# -*- coding: utf-8 -*- # # OERPLib documentation build configuration file, created by # sphinx-quickstart on Thu Sep 15 10:49:22 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. if os.path.exists("../.."): sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'OERPLib' copyright = u'2011-Today, ABF Osiell' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.7.0' # The full version, including alpha/beta/rc tags. release = '0.7.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. #html_theme = 'default' html_theme = 'nature' html_style = 'oerplib.css' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '_static/logo_oerp_64x64.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'OERPLibdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'OERPLib.tex', u'OERPLib Documentation', u'ABF Osiell', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'oerplib', u'OERPLib Documentation', [u'ABF Osiell - Sébastien Alix'], 1) ] OERPLib-0.7.0/doc/source/download_install.rst0000664000175000017500000000276612110524053020420 0ustar sebseb00000000000000.. _download-install: Download and install instructions ================================= Python Package Index (PyPI) --------------------------- You can install OERPLib with the `easy_install` tool:: $ easy_install oerplib Or with `pip`:: $ pip install oerplib An alternative way is to download the tarball from `Python Package Index `_ page, and install manually (replace `X.Y.Z` accordingly):: $ wget http://pypi.python.org/packages/source/O/OERPLib/OERPLib-X.Y.Z.tar.gz $ tar xzvf OERPLib-X.Y.Z.tar.gz $ cd OERPLib-X.Y.Z $ python setup.py install No dependency is required. Source code ----------- The project is hosted on `Launchpad `_. To get the current development branch (the ``trunk``), just type:: $ bzr branch lp:oerplib For the last version of a stable branch (replace `X.Y` accordingly):: $ bzr branch lp:oerplib/X.Y Run tests --------- .. versionadded:: 0.4.0 Unit tests depends on `unittest2` (Python 2.3+) or `unittest` (Python 2.7). To run unit tests from the project directory, run the following command:: $ PYTHONPATH=.:$PYTHONPATH ./tests/runtests.py --help Then, set your parameters in order to indicate the `OpenERP` server on which you want to perform the tests, for instance:: $ PYTHONPATH=.:$PYTHONPATH ./tests/runtests.py --create_db --server 192.168.1.4 --version 7.0 --test_xmlrpc --xmlrpc_port 8069 The name of the database created is ``oerplib-test`` by default. OERPLib-0.7.0/doc/source/ref_db.rst0000664000175000017500000000013312110524053016266 0ustar sebseb00000000000000oerplib.service.db ================== .. autoclass:: oerplib.service.db.DB :members: OERPLib-0.7.0/doc/source/reference.rst0000664000175000017500000000031612110524053017006 0ustar sebseb00000000000000.. _reference: Reference ========= .. toctree:: :maxdepth: 2 ref_fields ref_oerplib ref_oerp ref_osv ref_common ref_db ref_wizard ref_rpc ref_tools ref_error OERPLib-0.7.0/doc/source/_static/0000775000175000017500000000000012110526160015745 5ustar sebseb00000000000000OERPLib-0.7.0/doc/source/_static/logo_oerp_64x64.png0000664000175000017500000001224412110524053021315 0ustar sebseb00000000000000PNG  IHDR@@iqsRGBbKGD pHYs  tIME 70 $IDATx[ip^U~}%VEޝZ(K:[KBڄN-[Y;L 0ЁдLR`:?  S0Ҥ%79w˖,{~ɟdv;;'Y=<ī\+wG3K+͐6h{ɫ =7:΁[yP hؚYVB Z-P)Ą@j$(PNb\m~>2(@h F,ЖC׀wxK3(7& [!43Pb*SyBOܸc%ƊY tV_^ 4qWei?[YŕT d@ .`BKFC@D82W(`F"%#I*E];YY;U+`J` `3`[EoZL%ϖUQ(Wr3 DtCP8 S&˓5v#N(:D%$#`bEI=O}m|<}q ^|jnh[!RJBjvc&5dE] e#ƣƂшqML{f#Q,Aytٓ&|B@U@%?xn 䮵sӊrɴ4ApQPu9Hxo Rch5{ccG7.4tr-gܴolhkkePq @3J>\ <Ґp E5cn^~$y(JwV%N=E53Aj h)/FH.Εc}rXW؛AB]nqzes-IxХU~}$)!-5vo~_5Ի >S 52.GHc %'AIswݼr V,whR7X`qg,> jT`;}%uWtm̿qip5ؾgxdRx}F">1 "{4;#wuۜSoؼbIaVld ($azz, .{ xjDGk"k;s!Vx'8ض<Ո1^u[4"k)TG| }醁wmbr~0gK/rrgהA ?ysAp8.O,WOg[G) Z A_h< d1ՄV@L}{Ԁ tnx`O,纄0P*}d@"{՛}.7]ajT9pq'(xTdlv&`o=R]J*:ɳ?gX+NuPaq mUW:B]TC#RrƼzy!l^cKd(e`W="idJ"Z64b@5+5JRk lDF3787$}&Ɔ}*>?^nsRZSP˕3? ;ҙ ^;)bAXqiz%Bv1 @ xzpk'Z.z%2@u4f;%Q3Ҡ}x ~՟-ut}N{s[k.Yc@dZ/ol0K1ׄ QNy޵4S[Xp3`mbB^$J=)llPrw` @uf,K`hfvn\ຩ^⏌tZ8X(7<.sJ$ $#oIŌ"(<)̣ =o,W(?YS}u\(NZՇrR%3|d#8Ip NF j='N͈Ba|}%#2\ފD.51 kE G׊9P]0m+Jg@s ?ݵxHrKy)Y'35 „CzsW^SHk%`W1/0*%8S sI$H v]keM5u6:C`X!O3 p9k Ea](;UuF`^_t^Qܝ4+KESiC{ 8 @PIP}.nYd@sUȔKkv H|1ɧlWٵXP (/#9q(??_4^o I%n64 ,ЮnM(/ EOtA%\U] Ie}ܷG-G྘zS>ȳ`  ckٮlJCG+@j2L8y^)Fe\$9XxAOs̶ kss/3;悳6]2eJIga9_eS3~To?^u[ D 7BR==Z]] o;>CM `[ӗYH?Xq2x,,RLчmZCPri~PZ5d 7ЧrKV_cFZɘDu\ͤbmDMwMi<;r(GsX z /-Ng=wϰ` |𫋀ǁU&gvwi,#Mᔼ g| p}sh2`@jҙX/MNCwJGP@pSZȤr!O|wA3M6{椫˖M^_Qє\XK,X"cWp?qzb_>J.`r|xj=VV'K7 J1pC>+lQ>OJgg<:T=so^*,$%}Qޔ3B\6f\UtEΞ_c%[Ċϼ=)'ۃ]77㙴(|VJ崘f *{ `嗔Ub3|-vz/5;%*R,oHaoUxT9pFVo>u?00lT+~o6dc5zP~V$f&#Ȼ ), *2 UQpnd(ɉd? xk0ґp>2] 1-6Yǀnns~F{R9O@IId.`$`TKR^Y̍d'= u:a0y%EPW #R;^\Y2kT>fF1*P~.zt!H:Cu/?d9[Nyp8;na^x(o :-[rBàI;pwM9|pCx` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OERPLib.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OERPLib.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end OERPLib-0.7.0/doc/Makefile0000664000175000017500000001076612110524053014470 0ustar sebseb00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OERPLib.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OERPLib.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/OERPLib" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OERPLib" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." OERPLib-0.7.0/LICENSE.txt0000664000175000017500000001674312110524053014107 0ustar sebseb00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. OERPLib-0.7.0/README.txt0000664000175000017500000000461212110525455013761 0ustar sebseb00000000000000 ======= OERPLib ======= `OERPLib` is a client library to `OpenERP` server. It aims to provide an easy way to remotely pilot an `OpenERP` server. Features supported: - `XML-RPC` and `Net-RPC` protocols, - access to all methods proposed by an `OpenERP` model class (even ``browse``) with an API similar to the server-side API, - ability to use named parameters with such methods (`OpenERP` >= `6.1`), - user context automatically sent (`OpenERP` >= `6.1`), - browse records, - execute workflows, - manage databases, - reports downloading. How does it work? See below:: import oerplib # Prepare the connection to the OpenERP server oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) # Check available databases print(oerp.db.list()) # Login (the object returned is a browsable record) user = oerp.login('user', 'passwd', 'db_name') print(user.name) # name of the user connected print(user.company_id.name) # the name of its company # Simple 'raw' query user_data = oerp.execute('res.users', 'read', [user.id]) print(user_data) # Use all methods of an OSV class order_obj = oerp.get('sale.order') order_ids = order_obj.search([]) for order in order_obj.browse(order_ids): print(order.name) products = [line.product_id.name for line in order.order_line] print(products) # Update data through a browsable record user.name = "Brian Jones" oerp.write_record(user) See the documentation for more details. Supported OpenERP versions -------------------------- `OERPLib` has been tested on `OpenERP` server v5.0, v6.0, v6.1 and v7.0. It should work on next versions if `OpenERP` keeps a stable API. Supported Python versions ------------------------- `OERPLib` support Python versions 2.6 and 2.7. Generate the documentation -------------------------- To generate the documentation, you have to install `Sphinx` documentation generator:: easy_install -U sphinx Then, you can use the ``build_doc`` option of the ``setup.py``:: python setup.py build_doc The generated documentation will be in the ``./doc/build/html`` directory. Bugs or suggestions ------------------- Please, feel free to report bugs or suggestions in the `Bug Tracker `_! Changes in this version ----------------------- Consult the ``CHANGES.txt`` file. OERPLib-0.7.0/setup.py0000664000175000017500000000422512110524053013766 0ustar sebseb00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- import os from distutils.core import setup name = 'OERPLib' version = '0.7.0' description = 'OpenERP client library which allows to easily interact with an OpenERP server.' keywords = "openerp client xml-rpc xml_rpc xmlrpc net-rpc net_rpc netrpc oerplib communication lib library python service web webservice" author = u"ABF Osiell - Sebastien Alix" author_email = 'sebastien.alix@osiell.com' url = 'http://packages.python.org/OERPLib/' download_url = 'http://pypi.python.org/packages/source/O/OERPLib/OERPLib-%s.tar.gz' % version license = 'LGPL v3' doc_build_dir = 'doc/build' doc_source_dir = 'doc/source' cmdclass = {} command_options = {} # 'build_doc' option try: from sphinx.setup_command import BuildDoc if not os.path.exists(doc_build_dir): os.mkdir(doc_build_dir) cmdclass = {'build_doc': BuildDoc} command_options = { 'build_doc': { #'project': ('setup.py', name), 'version': ('setup.py', version), 'release': ('setup.py', version), 'source_dir': ('setup.py', doc_source_dir), 'build_dir': ('setup.py', doc_build_dir), 'builder': ('setup.py', 'html'), }} except Exception: print("No Sphinx module found. You have to install Sphinx " "to be able to generate the documentation.") setup(name=name, version=version, description=description, long_description=open('README.txt').read(), keywords=keywords, author=author, author_email=author_email, url=url, download_url=download_url, packages=['oerplib', 'oerplib.rpc', 'oerplib.service', 'oerplib.service.osv'], license=license, cmdclass=cmdclass, command_options=command_options, classifiers=[ "Intended Audience :: Developers", "Programming Language :: Python", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Topic :: Software Development :: Libraries :: Python Modules", ], ) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/CHANGES.txt0000664000175000017500000001055212110524241014064 0ustar sebseb00000000000000v0.7.0, 2013-02-18: - User context is sent automatically (OpenERP >= 6.1), - Able to use named parameters with OSV methods (OpenERP >= 6.1), - Auto-detect the OpenERP server version (enable or disable some features according to the version), - Add support for 'html' type fields, - [REGRESSION] one2many and many2many descriptor attributes now return a generator to iterate on 'browse_record' instead of a list, - [REGRESSION] 'osv_name' parameter of some functions has been renamed to 'model', - 'OERP.timeout' property deprecated (use 'OERP.config["timeout"]' instead), - 'OERP.get_osv_name()' method deprecated (use 'record.__osv__["name"]' instead), - Documentation updated. Bug fixes: - Internal RPC method requests like 'read' should send a list of IDs (Bug #1064087). - Use the context when browsing relation fields of a 'browse_record' (Bug #1066554 and #1066565), v0.6.0, 2012-09-09: - Dynamic access to the '/common' RPC service ('OERP.common' attribute), - Dynamic access to the '/wizard' RPC service ('OERP.wizard' attribute), - Able to set a value to one2many and many2many fields for 'browse_record', - Support for the OpenERP 'fields.reference' added, - Support for XML-RPC over SSL added, - Timeout configuration for RPC requests, - Documentation updated. v0.5.3, 2012-06-17: Bug fixes: - For 'browse_record' objects, able to get/set a value to a many2one field which the model relation has no 'name' field (Bug #1012593). - Able to set the 'False' value to a many2one field for 'browse_record' objects (Bug #1012597). - Able to set the 'False' value to a selection field for 'browse_record' objects (Bug #1014252). v0.5.2, 2012-06-04: Bug fixes: - Able to get the 'False' value from a field of type 'date' or 'datetime' for a browse record. v0.5.1, 2012-05-09: Bug fixes: - Able to assign the 'False' value to a field of type 'char' for a browse record. v0.5.0, 2012-04-03: - Access to all methods proposed by an OSV class (even ``browse``) with an API similar to that can be found in OpenERP server, - Access to several browse records improved (no need to wait the instanciation of all records to iterate on them), - Documentation updated. v0.4.0, 2012-03-23: - Net-RPC protocol support added - Database management (via the 'OERP.db' attribute) - Browse records are no longer stored in OERPLib, each call to the 'browse', method will generate a new instance - Methods which need a user connected raise an exception if it is not the case - Browse records now store their own original data and fields updated in the '__data__' attribute - Browse record classes now store their metadata (OSV class name and columns) in the '__osv__' attribute - Dictionary interface of the 'OERP' class dropped, - 'write' and 'unlink' methods don't handle browse records anymore, 'write_record' and 'unlink_record' added for this purpose - Unit tests added - A new design for the documentation Bug fixes: - 'name' attribute of a browse record fixed (does not rely on the 'name_get' OSV method anymore) - 'OERP.report' method (previously called 'OERP.exec_report') works well, - 'None' values can now be sent via the XML-RPC protocol Other: - Project migrated from Bitbucket to Launchpad v0.3.0, 2011-10-11: - ID field of browsable objects is readonly - Unable to perform refresh/reset/write and unlink operations on locally deprecated browsable objects - String representation of browsable objects is of the form "browse_record('sale.order', 42)" (like OpenERP Server) - Implicit management of the 'name_get' method for browsable objects - 'join' parameter of the 'OERP.browse' method has been deleted - 'refresh' option of the 'OERP.browse' method is set to True by default - Update operation on One2Many field is no longer planned (setter property deleted) v0.2.0, 2011-09-30: - Updated tutorials in the documentation - Fix some exceptions raised then update data through browsable objects v0.1.2, 2011-09-29: - Fix setup.py v0.1.1, 2011-09-29: - Update documentation and README.txt - Fix setup.py script about Sphinx and download URL. v0.1.0, 2011-09-16: - Initial release. OERPLib-0.7.0/examples/0000775000175000017500000000000012110526160014070 5ustar sebseb00000000000000OERPLib-0.7.0/examples/example.py0000775000175000017500000000564112110524053016105 0ustar sebseb00000000000000#!/usr/bin/env python """A sample script to demonstrate some of functionalities of OERPLib.""" import oerplib # XMLRPC server configuration (NETRPC is also supported) SERVER = 'localhost' PROTOCOL = 'xmlrpc' PORT = 8069 # Name of the OpenERP database to use DATABASE = 'db_name' USER = 'admin' PASSWORD = 'password' try: # Login oerp = oerplib.OERP( server=SERVER, database=DATABASE, protocol=PROTOCOL, port=PORT, ) oerp.login(USER, PASSWORD) # ----------------------- # # -- Low level methods -- # # ----------------------- # # Execute - search user_ids = oerp.execute('res.users', 'search', [('id', '=', oerp.user.id)]) # Execute - read user_data = oerp.execute('res.users', 'read', user_ids[0]) # Execute - write oerp.execute('res.users', 'write', user_ids[0], {'name': u"Administrator"}) # Execute - create new_user_id = oerp.execute('res.users', 'create', {'login': u"New user"}) # ------------------------- # # -- Convenients methods -- # # ------------------------- # # Search IDs of a model that match criteria assert oerp.user.id in oerp.search('res.users', [('name', 'ilike', u"Administrator"),]) # Create a record new_user_id = oerp.create('res.users', {'login': u"new_user"}) # Read data of a record user_data = oerp.read('res.users', new_user_id) # Write a record oerp.write('res.users', [new_user_id], {'name': u"New user"}) # Delete a record oerp.unlink('res.users', new_user_id) # -------------------- # # -- Browse objects -- # # -------------------- # # Browse an object user = oerp.browse('res.users', oerp.user.id) print(user.name) print(user.company_id.name) # .. or many objects for order in oerp.browse('sale.order', [68, 69]): print(order.name) print(order.partner_id.name) for line in order.order_line: print('\t{0}'.format(line.name)) # ----------------------- # # -- Download a report -- # # ----------------------- # so_pdf_path = oerp.report('sale.order', 'sale.order', 1) inv_pdf_path = oerp.report('webkitaccount.invoice', 'account.invoice', 1) # -------------------- # # -- List databases -- # # -------------------- # # List databases print(oerp.db.list()) # Create a database in background oerp.db.create( super_admin_passwd='super_admin_passwd', database='my_new_db', demo_data=True, lang='fr_FR', admin_passwd='admin_passwd') # Create a database (process blocked until the end of the operation) oerp.db.create_and_wait( super_admin_passwd='super_admin_passwd', database='my_new_db', demo_data=True, lang='fr_FR', admin_passwd='admin_passwd') except oerplib.error.Error as e: print(e) except Exception as e: print(e) OERPLib-0.7.0/tests/0000775000175000017500000000000012110526160013414 5ustar sebseb00000000000000OERPLib-0.7.0/tests/test_timeout.py0000664000175000017500000000207312110524053016514 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import socket from args import ARGS import oerplib class TestTimeout(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) def test_reduced_timeout(self): # Set the timeout self.oerp.config['timeout'] = 0.1 # Execute a time consuming query: handle exception ids = self.oerp.search('ir.module.module', []) self.assertRaises( socket.timeout, self.oerp.write, 'ir.module.module', ids, {}) def test_increased_timeout(self): # Set the timeout self.oerp.config['timeout'] = 120 # Execute a time consuming query: no exception ids = self.oerp.search('ir.module.module', []) self.oerp.write('ir.module.module', ids, {}) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_db_create.py0000664000175000017500000000161512110524053016737 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib class TestDBCreate(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) def test_db_create(self): if ARGS.database not in self.oerp.db.list(): res = self.oerp.db.create_and_wait( ARGS.super_admin_passwd, ARGS.database, demo_data=False, lang='en_US', admin_passwd=ARGS.passwd) self.assertIsInstance(res, list) self.assertNotEqual(res, list()) self.assertEqual(res[0]['login'], 'admin') self.assertEqual(res[0]['password'], ARGS.passwd) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_db.py0000664000175000017500000000312512110524053015412 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from datetime import datetime from args import ARGS import oerplib class TestDB(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.databases = [] def test_db_list(self): res = self.oerp.db.list() self.assertIsInstance(res, list) def test_db_list_lang(self): res = self.oerp.db.list_lang() self.assertIsInstance(res, list) def test_db_dump(self): dump = self.oerp.db.dump(ARGS.super_admin_passwd, ARGS.database) self.assertIsNotNone(dump) def test_db_restore_new_database(self): dump = self.oerp.db.dump(ARGS.super_admin_passwd, ARGS.database) date = datetime.strftime(datetime.today(), '%Y-%m-%d_%Hh%Mm%S') new_database = "%s_%s" % (ARGS.database, date) self.oerp.db.restore(ARGS.super_admin_passwd, new_database, dump) self.databases.append(new_database) def test_db_restore_existing_database(self): dump = self.oerp.db.dump(ARGS.super_admin_passwd, ARGS.database) self.assertRaises( oerplib.error.RPCError, self.oerp.db.restore, ARGS.super_admin_passwd, ARGS.database, dump) def tearDown(self): for db in self.databases: try: self.oerp.db.drop(ARGS.super_admin_passwd, db) except: pass # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_execute_kw.py0000664000175000017500000000643312110524053017175 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import numbers import time from args import ARGS import oerplib class TestExecuteKw(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) # ------ # Search # ------ def test_execute_kw_search_with_good_args(self): # Check the result returned result = self.oerp.execute_kw('res.users', 'search', [[]], {}) self.assertIsInstance(result, list) self.assertIn(self.user.id, result) result = self.oerp.execute_kw( 'res.users', 'search', [[('id', '=', self.user.id)]], {'order': 'name'}) self.assertIn(self.user.id, result) self.assertEqual(result[0], self.user.id) def test_execute_kw_search_without_args(self): # Handle exception (execute a 'search' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'search') def test_execute_kw_search_with_wrong_args(self): # Handle exception (execute a 'search' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'search', False, False) # Wrong args def test_execute_kw_search_with_wrong_model(self): # Handle exception (execute a 'search' with a wrong model) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'wrong.model', # Wrong model 'search', [[]], {}) def test_execute_kw_search_with_wrong_method(self): # Handle exception (execute a 'search' with a wrong method) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'wrong_method', # Wrong method [[]], {}) # ------ # Create # ------ def test_execute_kw_create_with_good_args(self): login = "%s_%s" % ("foobar", time.time()) # Check the result returned result = self.oerp.execute_kw( 'res.users', 'create', [{'name': login, 'login': login}]) self.assertIsInstance(result, numbers.Number) # Handle exception (create another user with the same login) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'create', [{'name': login, 'login': login}]) def test_execute_kw_create_without_args(self): # Handle exception (execute a 'create' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'create') def test_execute_kw_create_with_wrong_args(self): # Handle exception (execute a 'create' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'create', True, True) # Wrong args # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_browse.py0000664000175000017500000001726712110524053016342 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import datetime from args import ARGS import oerplib from oerplib.tools import v class TestBrowse(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) def test_browse_with_one_id(self): # Check the result returned result = self.oerp.browse('res.users', self.user.id) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(self.user, result) # With context context = self.oerp.execute('res.users', 'context_get') result = self.oerp.browse('res.users', self.user.id, context) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(self.user, result) def test_browse_with_ids(self): # Iteration for result in self.oerp.browse('res.users', [self.user.id]): self.assertEqual(self.user, result) user_ids = self.oerp.search('res.users', []) for result in self.oerp.browse('res.users', user_ids): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) # With context context = self.oerp.execute('res.users', 'context_get') for result in self.oerp.browse('res.users', user_ids, context): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) def test_browse_with_id_false(self): # Check the result returned result = self.oerp.browse('res.users', False) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(False, result.id) # With context context = self.oerp.execute('res.users', 'context_get') result = self.oerp.browse('res.users', False, context) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(False, result.id) def test_browse_with_wrong_id(self): # Handle exception (execute a 'browse' with wrong ID) self.assertRaises( oerplib.error.RPCError, self.oerp.browse, 'res.users', 999999999) def test_browse_without_args(self): # Handle exception (execute a 'browse' without args) self.assertRaises( TypeError, self.oerp.browse, 'res.users') def test_browse_with_wrong_args(self): # Handle exception (execute a 'browse' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.browse, 'res.users', "wrong_arg") # Wrong arg def test_reset(self): # Check the result returned self.user.name = "Charly" self.oerp.reset(self.user) self.assertEqual(self.user.name, "Administrator") def test_refresh(self): # Check the result returned self.user.name = "Charly" self.oerp.refresh(self.user) self.assertEqual(self.user.name, "Administrator") def test_write_record_char(self): backup_name = self.user.name self.user.name = "Charly" self.oerp.write_record(self.user) self.assertEqual(self.user.name, "Charly") self.user.name = backup_name self.oerp.write_record(self.user) self.assertEqual(self.user.name, backup_name) def test_write_record_boolean(self): self.user.active = False self.user.active = True self.oerp.write_record(self.user) self.assertEqual(self.user.active, True) def test_write_record_float(self): partner = self.user.company_id.partner_id partner.credit_limit = False self.oerp.write_record(partner) self.assertEqual(partner.credit_limit, 0.0) partner.credit_limit = 0.0 self.oerp.write_record(partner) self.assertEqual(partner.credit_limit, 0.0) def test_write_record_integer(self): cur = self.oerp.browse('res.currency', 1) backup_accuracy = cur.accuracy cur.accuracy = False self.oerp.write_record(cur) self.assertEqual(cur.accuracy, 0) cur.accuracy = backup_accuracy self.oerp.write_record(cur) self.assertEqual(cur.accuracy, backup_accuracy) def test_write_record_selection(self): self.user.context_tz = False self.oerp.write_record(self.user) self.assertEqual(self.user.context_tz, False) self.user.context_tz = 'Europe/Paris' self.oerp.write_record(self.user) self.assertEqual(self.user.context_tz, 'Europe/Paris') def test_write_record_date(self): partner = self.user.company_id.partner_id partner.date = False self.oerp.write_record(partner) self.assertEqual(partner.date, False) partner.date = '2012-01-01' self.oerp.write_record(partner) self.assertEqual(partner.date, datetime.date(2012, 1, 1)) partner.date = datetime.date(2012, 1, 1) self.oerp.write_record(partner) self.assertEqual(partner.date, datetime.date(2012, 1, 1)) #def test_write_record_datetime(self): # # No common model found in every versions of OpenERP with a # # fields.datetime writable # pass def test_write_record_many2many(self): backup_groups = [grp for grp in self.user.groups_id] # False self.user.groups_id = False self.oerp.write_record(self.user) self.assertEqual(list(self.user.groups_id), backup_groups) # [] self.user.groups_id = [] self.oerp.write_record(self.user) self.assertEqual(list(self.user.groups_id), backup_groups) # [(6, 0, ...)] self.user.groups_id = [(6, 0, [grp.id for grp in backup_groups])] self.oerp.write_record(self.user) self.assertEqual(list(self.user.groups_id), backup_groups) def test_write_record_many2one(self): self.user.action_id = 1 self.oerp.write_record(self.user) self.assertEqual(self.user.action_id.id, 1) action = self.oerp.get('ir.actions.actions').browse(1) self.user.action_id = action self.oerp.write_record(self.user) self.assertEqual(self.user.action_id.id, 1) # False self.user.action_id = False self.oerp.write_record(self.user) if v(self.oerp._version) == v('5.0'): self.assertEqual(self.user.action_id.id, 1) elif v(self.oerp._version) == v('6.0'): self.assertEqual(self.user.action_id, False) elif v(self.oerp._version) == v('6.1'): self.assertEqual(self.user.action_id, False) else: self.assertEqual(self.user.action_id, False) def test_write_record_one2many(self): model_obj = self.oerp.get('ir.model') model = model_obj.browse(1) backup_access = [acc for acc in model.access_ids] # False model.access_ids = False self.oerp.write_record(model) self.assertEqual(list(model.access_ids), backup_access) # [] model.access_ids = [] self.oerp.write_record(model) self.assertEqual(list(model.access_ids), backup_access) # [(1, ID, { values })] access_id = list(model.access_ids)[0].id model.access_ids = [(1, access_id, {'name': "OERPLib-test"})] self.oerp.write_record(model) self.assertEqual(list(model.access_ids), backup_access) access = list(model.access_ids)[0] self.assertEqual(access.name, "OERPLib-test") # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_login.py0000664000175000017500000000363512110524053016143 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib from oerplib.service import osv class TestLogin(unittest.TestCase): def test_oerp_no_db_login_db(self): # OERP no database + login database oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) user = oerp.login(ARGS.user, ARGS.passwd, ARGS.database) self.assertIsNotNone(user) self.assertIsInstance(user, osv.BrowseRecord) self.assertEqual(oerp.user, user) self.assertEqual(oerp.database, ARGS.database) def test_oerp_no_db_login_no_db(self): # OERP no database + login no database => Error oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.assertRaises( oerplib.error.Error, oerp.login, ARGS.user, ARGS.passwd) def test_oerp_db_login_no_db(self): # OERP database + login no database oerp = oerplib.OERP( ARGS.server, ARGS.database, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) user = oerp.login(ARGS.user, ARGS.passwd) self.assertIsNotNone(user) self.assertIsInstance(user, osv.BrowseRecord) self.assertEqual(oerp.user, user) def test_oerp_db_login_db(self): # OERP database + login database oerp = oerplib.OERP( ARGS.server, ARGS.database, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) user = oerp.login(ARGS.user, ARGS.passwd, ARGS.database) self.assertIsNotNone(user) self.assertIsInstance(user, osv.BrowseRecord) self.assertEqual(oerp.user, user) self.assertEqual(oerp.database, ARGS.database) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_tools.py0000664000175000017500000000255512110524053016173 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS from oerplib import tools class TestTools(unittest.TestCase): def test_clean_version_numeric(self): version = tools.clean_version('6.1') self.assertEqual(version, '6.1') def test_clean_version_alphanumeric(self): version = tools.clean_version('7.0alpha-20121206-000102') self.assertEqual(version, '7.0') def test_detect_version(self): version = tools.detect_version( ARGS.server, ARGS.protocol, ARGS.port) self.assertIsInstance(version, basestring) def test_v_numeric(self): self.assertEqual(tools.v('7.0'), [7, 0]) def test_v_alphanumeric(self): self.assertEqual(tools.v('7.0alpha'), [7, 0]) def test_v_cmp(self): # [(v1, v2, is_inferior), ...] versions = [ ('7.0', '6.1', False), ('6.1', '7.0', True), ('7.0alpha', '6.1', False), ('6.1beta', '7.0', True), ('6.1beta', '5.0.16', False), ('5.0.16alpha', '6.1', True), ] for v1, v2, is_inferior in versions: result = tools.v(v1) < tools.v(v2) if is_inferior: self.assertTrue(result) else: self.assertFalse(result) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_execute.py0000664000175000017500000000627712110524053016502 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import numbers import time from args import ARGS import oerplib class TestExecute(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) # ------ # Search # ------ def test_execute_search_with_good_args(self): # Check the result returned result = self.oerp.execute('res.users', 'search', []) self.assertIsInstance(result, list) self.assertIn(self.user.id, result) result = self.oerp.execute('res.users', 'search', [('id', '=', self.user.id)]) self.assertIn(self.user.id, result) self.assertEqual(result[0], self.user.id) def test_execute_search_without_args(self): # Handle exception (execute a 'search' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'search') def test_execute_search_with_wrong_args(self): # Handle exception (execute a 'search' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'search', False) # Wrong arg def test_execute_search_with_wrong_model(self): # Handle exception (execute a 'search' with a wrong model) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'wrong.model', # Wrong model 'search', []) def test_execute_search_with_wrong_method(self): # Handle exception (execute a 'search' with a wrong method) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'wrong_method', # Wrong method []) # ------ # Create # ------ def test_execute_create_with_good_args(self): login = "%s_%s" % ("foobar", time.time()) # Check the result returned result = self.oerp.execute( 'res.users', 'create', {'name': login, 'login': login}) self.assertIsInstance(result, numbers.Number) # Handle exception (create another user with the same login) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'create', {'name': login, 'login': login}) def test_execute_create_without_args(self): # Handle exception (execute a 'create' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'create') def test_execute_create_with_wrong_args(self): # Handle exception (execute a 'create' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'create', False) # Wrong arg # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/args.py0000664000175000017500000000350012110524053014717 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- import argparse _parser = argparse.ArgumentParser(description="Run tests for OERPLib.") _parser.add_argument('--server', default='localhost', help="Host") #_parser.add_argument('--protocol', default='xmlrpc', # help="Protocol") #_parser.add_argument('--port', default='8069', # help="Port") _parser.add_argument('--test_xmlrpc', action='store_true', help="Test the XML-RPC protocol") _parser.add_argument('--xmlrpc_port', default='8069', help="Port to use with the XML-RPC protocol") _parser.add_argument('--test_netrpc', action='store_true', help="Test the NET-RPC protocol") _parser.add_argument('--netrpc_port', default='8070', help="Port to use with the NET-RPC protocol") _parser.add_argument('--version', default=None, help="OpenERP version used") _parser.add_argument('--super_admin_passwd', default='admin', help="Super administrator password") _parser.add_argument('--database', default='oerplib-test', help="Name of the database") _parser.add_argument('--user', default='admin', help="User login") _parser.add_argument('--passwd', default='admin', help="User password") _parser.add_argument('--create_db', action='store_true', help="Create a database for tests") _parser.add_argument('--drop_db', action='store_true', help="Drop the created database. " "Works only with --create_db") _parser.add_argument('--verbosity', default=2, type=int, help="Output verbosity of unittest") ARGS = _parser.parse_args() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_db_drop.py0000664000175000017500000000201012110524053016426 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib from oerplib.tools import v class TestDBDrop(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) def test_db_drop_existing_database(self): res = self.oerp.db.drop(ARGS.super_admin_passwd, ARGS.database) self.assertTrue(res) db_list = self.oerp.db.list() self.assertNotIn(ARGS.database, db_list) def test_db_drop_no_existing_database(self): if v(ARGS.version) >= v('6.1'): res = self.oerp.db.drop(ARGS.super_admin_passwd, 'fake_db_name') self.assertFalse(res) else: self.assertRaises( oerplib.error.RPCError, self.oerp.db.drop, ARGS.super_admin_passwd, 'fake_db_name') # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_osv.py0000664000175000017500000000410312110524053015631 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib class TestOSV(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) def test_model(self): # Check the result returned self.oerp.get('res.users') def test_model_method(self): # Check the result returned model = self.oerp.get('res.users') model.name_get(self.user.id) def test_model_method_without_args(self): # Handle exception (execute a 'name_get' with without args) model = self.oerp.get('res.users') self.assertRaises( oerplib.error.RPCError, model.name_get) def test_model_method_with_wrong_args(self): # Handle exception (execute a 'search' with wrong args) model = self.oerp.get('res.users') self.assertRaises( oerplib.error.RPCError, model.search, False) # Wrong arg def test_model_browse_with_one_id(self): # Check the result returned model = self.oerp.get('res.users') user = model.browse(self.user.id) self.assertEqual(user, self.user) def test_model_browse_with_ids(self): # Iteration for result in self.oerp.get('res.users').browse([self.user.id]): self.assertEqual(self.user, result) user_ids = self.oerp.search('res.users', []) for result in self.oerp.get('res.users').browse(user_ids): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) # With context context = self.oerp.execute('res.users', 'context_get') for result in self.oerp.get('res.users').browse(user_ids, context): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/test_init.py0000664000175000017500000000423012110524053015766 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib class TestInit(unittest.TestCase): def test_init1(self): # Server oerp = oerplib.OERP(ARGS.server) self.assertIsInstance(oerp, oerplib.OERP) self.assertIsNotNone(oerp) self.assertEqual(oerp.server, ARGS.server) self.assertIsNone(oerp.database) def test_init2(self): # Server + Database oerp = oerplib.OERP(ARGS.server, ARGS.database) self.assertIsInstance(oerp, oerplib.OERP) self.assertIsNotNone(oerp) self.assertEqual(oerp.server, ARGS.server) self.assertEqual(oerp.database, ARGS.database) def test_init3(self): # Server + Database + Protocol oerp = oerplib.OERP(ARGS.server, ARGS.database, ARGS.protocol) self.assertIsInstance(oerp, oerplib.OERP) self.assertIsNotNone(oerp) self.assertEqual(oerp.server, ARGS.server) self.assertEqual(oerp.database, ARGS.database) self.assertEqual(oerp.protocol, ARGS.protocol) def test_init4(self): # Server + Database + Protocol + Port oerp = oerplib.OERP(ARGS.server, ARGS.database, ARGS.protocol, ARGS.port) self.assertIsInstance(oerp, oerplib.OERP) self.assertIsNotNone(oerp) self.assertEqual(oerp.server, ARGS.server) self.assertEqual(oerp.database, ARGS.database) self.assertEqual(oerp.protocol, ARGS.protocol) self.assertEqual(oerp.port, ARGS.port) def test_init5(self): # Server + Database + Protocol + Port + Timeout oerp = oerplib.OERP(ARGS.server, ARGS.database, ARGS.protocol, ARGS.port, 42) self.assertIsInstance(oerp, oerplib.OERP) self.assertIsNotNone(oerp) self.assertEqual(oerp.server, ARGS.server) self.assertEqual(oerp.database, ARGS.database) self.assertEqual(oerp.protocol, ARGS.protocol) self.assertEqual(oerp.port, ARGS.port) self.assertEqual(oerp.config['timeout'], 42) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/tests/runtests.py0000775000175000017500000000571412110524053015666 0ustar sebseb00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS from test_tools import TestTools from test_init import TestInit from test_login import TestLogin from test_db_create import TestDBCreate from test_db import TestDB from test_db_drop import TestDBDrop from test_execute import TestExecute from test_execute_kw import TestExecuteKw from test_browse import TestBrowse from test_osv import TestOSV from test_timeout import TestTimeout from oerplib.tools import v if __name__ == '__main__': suite = unittest.TestSuite() #--------------- #- First Tests - #--------------- # 1) Test oerplib.tools loader = unittest.TestLoader().loadTestsFromTestCase(TestTools) suite.addTest(loader) # 2) Test OERP.__init__ loader = unittest.TestLoader().loadTestsFromTestCase(TestInit) suite.addTest(loader) # 3) Test OERP.db (create the database) if ARGS.create_db: loader = unittest.TestLoader().loadTestsFromTestCase(TestDBCreate) suite.addTest(loader) else: print("-- TestDBCreate skipped --") # 4) Test OERP.login loader = unittest.TestLoader().loadTestsFromTestCase(TestLogin) suite.addTest(loader) #--------- #- Tests - #--------- # Test OERP.db loader = unittest.TestLoader().loadTestsFromTestCase(TestDB) suite.addTest(loader) # Test OERP.execute and OERP.execute_kw loader = unittest.TestLoader().loadTestsFromTestCase(TestExecute) suite.addTest(loader) if ARGS.version and v(ARGS.version) >= v('6.1'): loader = unittest.TestLoader().loadTestsFromTestCase(TestExecuteKw) suite.addTest(loader) # Test OERP.browse loader = unittest.TestLoader().loadTestsFromTestCase(TestBrowse) suite.addTest(loader) # Test OERP.get loader = unittest.TestLoader().loadTestsFromTestCase(TestOSV) suite.addTest(loader) # Test socket timeout loader = unittest.TestLoader().loadTestsFromTestCase(TestTimeout) suite.addTest(loader) #--------------- #- Final Tests - #--------------- # Test OERP.db (drop the database) if ARGS.create_db and ARGS.drop_db: loader = unittest.TestLoader().loadTestsFromTestCase(TestDBDrop) suite.addTest(loader) else: print("-- TestDBDrop skipped --") # Run all tests if ARGS.test_xmlrpc: print("-- RUN (XMLRPC) --") ARGS.protocol = 'xmlrpc' ARGS.port = ARGS.xmlrpc_port unittest.TextTestRunner(verbosity=ARGS.verbosity).run(suite) if ARGS.test_netrpc: print("-- RUN (NETRPC) --") ARGS.protocol = 'netrpc' ARGS.port = ARGS.netrpc_port unittest.TextTestRunner(verbosity=ARGS.verbosity).run(suite) if not ARGS.test_xmlrpc and not ARGS.test_netrpc: print("-- NO TEST --") print("Please use '--test_xmlrpc' and/or '--test_netrpc' option.") # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/0000775000175000017500000000000012110526160013706 5ustar sebseb00000000000000OERPLib-0.7.0/oerplib/rpc/0000775000175000017500000000000012110526160014472 5ustar sebseb00000000000000OERPLib-0.7.0/oerplib/rpc/service.py0000664000175000017500000000477612110524053016521 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- import xmlrpclib from oerplib.rpc import socket_netrpc, xmlrpclib_custom, error class ServiceXMLRPC(object): def __init__(self, connector, name, url): self._connector = connector self._name = name self._url = url def __getattr__(self, method): def rpc_method(*args): try: self._sock = xmlrpclib_custom.TimeoutServerProxy( self._url, allow_none=True, timeout=self._connector.timeout) sock_method = getattr(self._sock, method, False) return sock_method(*args) #NOTE: exception raised with these kind of requests: # - execute('fake.model', 'search', []) # - execute('sale.order', 'fake_method') except xmlrpclib.Fault as exc: # faultCode: error message # faultString: OpenERP server traceback (following the OpenERP # server version used, a bad request can produce a # server traceback, or not). raise error.ConnectorError(exc.faultCode, exc.faultString) #TODO NEED TEST (when is raised this exception?) except xmlrpclib.Error as exc: raise error.ConnectorError(' - '.join(exc.args)) return rpc_method class ServiceNetRPC(object): def __init__(self, connector, name, server, port): self._connector = connector self._name = name self._server = server self._port = port def __getattr__(self, method): def rpc_method(*args): try: sock = socket_netrpc.NetRPC(timeout=self._connector.timeout) sock.connect(self._server, self._port) sock.send((self._name, method, ) + args) result = sock.receive() sock.disconnect() return result #NOTE: exception raised with these kind of requests: # - execute('fake.model', 'search', []) # - execute('sale.order', 'fake_method') except socket_netrpc.NetRPCError as exc: # faultCode: error message # faultString: OpenERP server traceback (following the OpenERP # server version used, a bad request can produce a # server traceback, or not). raise error.ConnectorError(exc.faultCode, exc.faultString) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/rpc/xmlrpclib_custom.py0000664000175000017500000000435112110524053020434 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- import xmlrpclib import httplib import socket import sys class TimeoutServerProxy(xmlrpclib.ServerProxy): """xmlrpclib.ServerProxy overload to manage the timeout of the socket.""" def __init__(self, *args, **kwargs): t = TimeoutTransport() t.timeout = kwargs.get('timeout', 120) if 'timeout' in kwargs: del kwargs['timeout'] kwargs['transport'] = t xmlrpclib.ServerProxy.__init__(self, *args, **kwargs) # Python 2.5 and 2.6 class TimeoutHTTPPy26(httplib.HTTP): def __init__(self, host='', port=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): if port == 0: port = None self._setup(self._connection_class(host, port, strict, timeout)) class TimeoutTransportPy26(xmlrpclib.Transport): def __init__(self, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *args, **kwargs): xmlrpclib.Transport.__init__(self, *args, **kwargs) self.timeout = timeout def make_connection(self, host): host, extra_headers, x509 = self.get_host_info(host) conn = TimeoutHTTPPy26(host, timeout=self.timeout) return conn # Python 2.7 class TimeoutHTTPConnectionPy27(httplib.HTTPConnection): def __init__(self, timeout, *args, **kwargs): httplib.HTTPConnection.__init__(self, *args, **kwargs) self.timeout = timeout def connect(self): httplib.HTTPConnection.connect(self) self.sock.settimeout(self.timeout) class TimeoutTransportPy27(xmlrpclib.Transport): def __init__(self, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *args, **kwargs): xmlrpclib.Transport.__init__(self, *args, **kwargs) self.timeout = timeout def make_connection(self, host): if self._connection and host == self._connection[0]: return self._connection[1] chost, self._extra_headers, x509 = self.get_host_info(host) self._connection = host, TimeoutHTTPConnectionPy27(self.timeout, chost) return self._connection[1] # Define the TimeTransport class version to use TimeoutTransport = sys.version_info <= (2, 7) \ and TimeoutTransportPy26 \ or TimeoutTransportPy27 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/rpc/error.py0000664000175000017500000000047712110524053016204 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- class ConnectorError(BaseException): """Exception raised by the ``oerplib.rpc`` package.""" def __init__(self, message, oerp_traceback=None): self.message = message self.oerp_traceback = oerp_traceback # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/rpc/connector.py0000664000175000017500000000442412110524053017041 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """Provides connectors for different protocols.""" import abc from oerplib.rpc import error, service from oerplib.tools import v class Connector(object): """Connector base class defining the interface used to interact with an OpenERP server. """ __metaclass__ = abc.ABCMeta def __init__(self, server, port, timeout, version): self.server = server try: int(port) except ValueError: txt = "The port '{0}' is invalid. An integer is required." txt = txt.format(port) raise error.ConnectorError(txt) else: self.port = port self.timeout = timeout self.version = version class ConnectorXMLRPC(Connector): """Connector class using XMLRPC protocol.""" def __init__(self, server, port, timeout, version): super(ConnectorXMLRPC, self).__init__( server, port, timeout, version) # OpenERP < 6.1 if self.version and v(self.version) < v('6.1'): self._url = 'http://{server}:{port}/xmlrpc'.format( server=self.server, port=self.port) # OpenERP >= 6.1 else: self._url = 'http://{server}:{port}/openerp/xmlrpc/1'.format( server=self.server, port=self.port) def __getattr__(self, service_name): url = self._url + '/' + service_name srv = service.ServiceXMLRPC(self, service_name, url) setattr(self, service_name, srv) return srv class ConnectorXMLRPCSSL(ConnectorXMLRPC): """Connector class using XMLRPC protocol over SSL.""" def __init__(self, server, port, timeout, version): super(ConnectorXMLRPCSSL, self).__init__( server, port, timeout, version) self._url = self._url.replace('http', 'https') class ConnectorNetRPC(Connector): """Connector class using NetRPC protocol.""" def __init__(self, server, port, timeout, version): super(ConnectorNetRPC, self).__init__( server, port, timeout, version) def __getattr__(self, service_name): srv = service.ServiceNetRPC( self, service_name, self.server, self.port) setattr(self, service_name, srv) return srv # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/rpc/__init__.py0000664000175000017500000000445512110524053016612 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module provides a `RPC` connector (via the :func:`get_connector ` method) which use the `XML-RPC` or `Net-RPC` protocol to communicate with an `OpenERP` server. Afterwards, `RPC` services (like `db`, `common` or `object`) and their methods associated can be accessed dynamically from the connector returned. Here is an example of how to use it: Get a `RPC` connector object:: >>> from oerplib import rpc >>> cnt = rpc.get_connector('localhost', 8069, 'xmlrpc') Login and retrieve ID of the user connected:: >>> uid = cnt.common.login('database', 'user', 'passwd') Execute a query:: >>> res = cnt.object.execute('database', uid, 'passwd', 'res.partner', 'read', 42) Execute a workflow query:: >>> res = cnt.object.exec_workflow('database', uid, 'passwd', 'sale.order', 'order_confirm', 4) This sub-module is used by `OERPLib` to execute all queries while abstracting the protocol used. """ from oerplib.rpc import connector, error PROTOCOLS = { 'xmlrpc': connector.ConnectorXMLRPC, 'xmlrpc+ssl': connector.ConnectorXMLRPCSSL, 'netrpc': connector.ConnectorNetRPC, } def get_connector(server, port=8069, protocol='xmlrpc', timeout=120, version=None): """Return a `RPC` connector to interact with an `OpenERP` server. Supported protocols are: - ``xmlrpc``: Standard XML-RPC protocol (default), - ``xmlrpc+ssl``: XML-RPC protocol over SSL, - ``netrpc``: Net-RPC protocol made by `OpenERP` (deprecated since `OpenERP v7.0`). If the `version` parameter is set to `None`, the last API supported will be use to send requests to `OpenERP`. Otherwise, you can force the API to use with the corresponding string version (e.g.: ``'6.0', '6.1', '7.0', ...``): >>> from oerplib import rpc >>> cnt = rpc.get_connector('localhost', 8069, 'xmlrpc', version='6.1') """ if protocol not in PROTOCOLS: txt = ("The protocol '{0}' is not supported. " "Please choose a protocol among these ones: {1}") txt = txt.format(protocol, PROTOCOLS.keys()) raise error.ConnectorError(txt) return PROTOCOLS[protocol](server, port, timeout, version) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/rpc/socket_netrpc.py0000664000175000017500000000472512110524053017716 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module contains the NetRPC class which implements the NetRPC protocol. """ import socket import pickle import cStringIO class NetRPCError(BaseException): """Exception raised by the NetRPC class when an error occured.""" def __init__(self, faultCode, faultString): self.faultCode = faultCode self.faultString = faultString self.args = (faultCode, faultString) class NetRPC(object): """Low level class for NetRPC protocol.""" def __init__(self, sock=None, timeout=120): if sock is None: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) else: self.sock = sock self.sock.settimeout(timeout) def connect(self, host, port=False): if not port: buf = host.split('//')[1] host, port = buf.split(':') self.sock.connect((host, int(port))) def disconnect(self): self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() def send(self, msg, exception=False, traceback=None): msg = pickle.dumps([msg, traceback]) size = len(msg) self.sock.send('%8d' % size) self.sock.send(exception and "1" or "0") totalsent = 0 while totalsent < size: sent = self.sock.send(msg[totalsent:]) if sent == 0: raise NetRPCError("RuntimeError", "Socket connection broken") totalsent = totalsent + sent def receive(self): buf = '' while len(buf) < 8: chunk = self.sock.recv(8 - len(buf)) if chunk == '': raise NetRPCError("RuntimeError", "Socket connection broken") buf += chunk size = int(buf) buf = self.sock.recv(1) if buf != "0": exception = buf else: exception = False msg = '' while len(msg) < size: chunk = self.sock.recv(size - len(msg)) if chunk == '': raise NetRPCError("RuntimeError", "Socket connection broken") msg = msg + chunk msgio = cStringIO.StringIO(msg) unpickler = pickle.Unpickler(msgio) unpickler.find_global = None res = unpickler.load() if isinstance(res[0], BaseException): if exception: raise NetRPCError(res[0], res[1]) raise res[0] else: return res[0] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/config.py0000664000175000017500000000205512110524053015526 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module contains the ``Config`` class which manage the configuration related to an instance of :class:`OERP ` """ import collections class Config(collections.MutableMapping): """""" def __init__(self, oerp, options): super(Config, self).__init__() self._oerp = oerp self._options = options or {} def __getitem__(self, key): return self._options[key] def __setitem__(self, key, value): """Handle ``timeout`` option to set the timeout on the connector.""" if key == 'timeout': self._oerp._connector.timeout = value self._options[key] = value def __delitem__(self, key): # TODO raise exception pass def __iter__(self): return self._options.__iter__() def __len__(self): return len(self._options) def __str__(self): return self._options.__str__() def __repr__(self): return self._options.__repr__() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/error.py0000664000175000017500000000125112110524053015407 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module contains all exceptions raised by `OERPLib` when an error occurred. """ class Error(Exception): def __init__(self, message, oerp_traceback=False): super(Error, self).__init__() self.message = message self.oerp_traceback = oerp_traceback def __str__(self): return u"{message}".format(message=self.message) class RPCError(Error): """Exception raised when an error related to a RPC query occurred.""" pass class InternalError(Error): """Exception raised when an error occurred during an internal operation.""" pass # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/0000775000175000017500000000000012110526160015346 5ustar sebseb00000000000000OERPLib-0.7.0/oerplib/service/common.py0000664000175000017500000001037412110524053017214 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """Provide the :class:`Common` class in order to manage common operations on the `OpenERP` server. """ from oerplib import rpc, error class Common(object): """.. versionadded:: 0.6 The `Common` class represents the ``/common`` RPC service. Lets you log in on `OpenERP`, and provides various utility functions. .. note:: This service have to be used through the :attr:`oerplib.OERP.common` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> oerp.common .. warning:: All methods documented below are not strictly implemented in `OERPLib` Method calls are purely dynamic, and the following documentation can be wrong if the API of `OpenERP` is changed between versions. Anyway, if you known the API used by the `OpenERP` server for the ``/common`` RPC service, it will work. .. method:: Common.login(db, login, password) >>> oerp.common.login('test_db', 'admin', 'admin_passwd') 1 :return: the user's ID .. method:: Common.authenticate(db, login, password, user_agent_env) >>> oerp.common.authenticate('test_db', 'admin', 'admin_passwd', TODO) TODO .. method:: Common.version() >>> oerp.common.version() {'protocol_version': 1, 'server_version': '6.1'} .. method:: Common.about(extended=False) Return information about the OpenERP Server. >>> oerp.common.about() '\n\nOpenERP is an ERP+CRM program for small and medium businesses.\n\nThe whole source code is distributed under the terms of the\nGNU Public Licence.\n\n(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl' >>> oerp.common.about(True) ['\n\nOpenERP is an ERP+CRM program for small and medium businesses.\n\nThe whole source code is distributed under the terms of the\nGNU Public Licence.\n\n(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl', '5.0.16'] :param: extended: if True then return version info :return: string if extended is False else tuple .. method:: Common.timezone_get(db, login, password) >>> oerp.common.timezone_get('test_db', 'admin', 'admin_passwd') 'UTC' .. method:: Common.get_server_environment() >>> print(oerp.common.get_server_environment()) Environment Information : System : Linux-2.6.32-5-686-i686-with-debian-6.0.4 OS Name : posix Distributor ID: Debian Description: Debian GNU/Linux 6.0.4 (squeeze) Release: 6.0.4 Codename: squeeze Operating System Release : 2.6.32-5-686 Operating System Version : #1 SMP Mon Mar 26 05:20:33 UTC 2012 Operating System Architecture : 32bit Operating System Locale : fr_FR.UTF8 Python Version : 2.6.6 OpenERP-Server Version : 5.0.16 Last revision No. & ID : .. method:: Common.login_message() >>> oerp.common.login_message() 'Welcome' .. method:: Common.set_loglevel(loglevel, logger=None) >>> oerp.common.set_loglevel(TODO) True .. method:: Common.get_stats() >>> print(oerp.common.get_stats()) OpenERP server: 5 threads Servers started Net-RPC: running .. method:: Common.list_http_services() >>> oerp.common.list_http_services() [] .. method:: Common.check_connectivity() >>> oerp.common.check_connectivity() True .. method:: Common.get_os_time() >>> oerp.common.get_os_time() (0.01, 0.0, 0.0, 0.0, 17873633.129999999) .. method:: Common.get_sql_count() >>> oerp.common.get_sql_count() TODO """ def __init__(self, oerp): self._oerp = oerp def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args): """Return the result of the RPC request.""" try: meth = getattr(self._oerp._connector.common, method, False) return meth(*args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/db.py0000664000175000017500000002167312110524053016315 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """Provide the :class:`DB` class in order to manage the `OpenERP` databases.""" import time from oerplib import rpc, error class DB(object): """.. versionadded:: 0.4 The `DB` class represents the database management service. It provides functionalities such as list, create, drop, dump and restore databases. .. note:: This service have to be used through the :attr:`oerplib.OERP.db` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> oerp.db .. warning:: All methods documented below are not strictly implemented in `OERPLib` (except the :func:`create_and_wait ` method). Method calls are purely dynamic, and the following documentation can be wrong if the API of `OpenERP` is changed between versions. Anyway, if you known the API used by the `OpenERP` server for the ``/db`` RPC service, it will work. .. method:: DB.list() Return a list of the `OpenERP` databases: >>> oerp.db.list() ['prod_db', 'test_db'] :return: a list of database names .. method:: DB.list_lang() Return a list of codes and names of language supported by `OpenERP`: >>> oerp.db.list_lang() [['sq_AL', u'Albanian / Shqipëri'], ['ar_AR', 'Arabic / الْعَرَبيّة'], ...] :return: a list of pairs representing languages with their codes and names .. method:: DB.server_version() Return the version of the `OpenERP Server`: >>> oerp.db.server_version() '6.1' :return: the version of the `OpenERP Server` as string .. method:: DB.dump(super_admin_passwd, database) Return a dump of `database` in `base64`: >>> binary_data = oerp.db.dump('super_admin_passwd', 'prod_db') The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: the `base64` string representation of the `database` .. method:: DB.restore(super_admin_passwd, database, binary_data) Restore in `database` a dump previously created with the :func:`dump ` method: >>> oerp.db.restore('super_admin_passwd', 'test_db', binary_data) The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. .. method:: DB.drop(super_admin_passwd, database) Drop the `database` from `OpenERP`: >>> oerp.db.drop('super_admin_passwd', 'test_db') True The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: `True` .. method:: DB.create(super_admin_passwd, database, demo_data=False, lang='en_US', admin_passwd='admin') Request the `OpenERP` server to create a new database named `database` which will have `admin_passwd` as administrator password and localized with the `lang` parameter. You have to set the flag `demo_data` to `True` in order to insert demonstration data. As the creating process may take some time, you can execute the :func:`get_progress ` method with the database ID returned to know its current state. >>> database_id = oerp.db.create('super_admin_passwd', 'test_db', False, 'fr_FR', 'my_admin_passwd') The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: the ID of the new database .. method:: DB.get_progress(super_admin_passwd, database_id) Check the state of the creating process for the database identified by the `database_id` parameter. >>> oerp.db.get_progress('super_admin_passwd', database_id) # Just after the call to the 'create' method (0, []) >>> oerp.db.get_progress('super_admin_passwd', database_id) # Once the database is fully created (1.0, [{'login': u'admin', 'password': u'admin', 'name': u'Administrator'}, {'login': u'demo', 'password': u'demo', 'name': u'Demo User'}]) The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: A tuple with the progressing state and a list of user accounts created (once the database is fully created). .. method:: DB.create_database(super_admin_passwd, database, demo_data=False, lang='en_US', admin_passwd='admin') `Available since OpenERP 6.1` Similar to :func:`create ` but blocking. >>> oerp.db.create_database('super_admin_passwd', 'test_db', False, 'fr_FR', 'my_admin_passwd') True The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: `True` .. method:: DB.duplicate_database(super_admin_passwd, original_database, database) `Available since OpenERP 7.0` Duplicate `original_database' as `database`. >>> oerp.db.duplicate_database('super_admin_passwd', 'prod_db', 'test_db') True The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: `True` .. method:: DB.rename(super_admin_passwd, old_name, new_name) Rename the `old_name` database to `new_name`. >>> oerp.db.rename('super_admin_passwd', 'test_db', 'test_db2') True The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: `True` .. method:: DB.db_exist(database) Check if connection to database is possible. >>> oerp.db.db_exist('prod_db') True :return: `True` or `False` .. method:: DB.change_admin_password(super_admin_passwd, new_passwd) Change the administrator password by `new_passwd`. >>> oerp.db.change_admin_password('super_admin_passwd', 'new_passwd') True The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: `True` """ def __init__(self, oerp): self._oerp = oerp def create_and_wait(self, super_admin_passwd, database, demo_data=False, lang='en_US', admin_passwd='admin'): """ .. note:: This method is not part of the official API of `OpenERP`. It's just a wrapper around the :func:`create ` and :func:`get_progress ` methods. For `OpenERP` in version `6.1` or above, please prefer the use of the standard :func:`create_database ` method. Like the :func:`create ` method, but waits the end of the creating process by executing the :func:`get_progress ` method regularly to check its state. >>> oerp.db.create_and_wait('super_admin_passwd', 'test_db', False, 'fr_FR', 'my_admin_passwd') [{'login': u'admin', 'password': u'my_admin_passwd', 'name': u'Administrateur'}, {'login': u'demo', 'password': u'demo', 'name': u'Demo User'}] The super administrator password `super_admin_passwd` of `OpenERP` is required to perform this action. :return: a list of user accounts created :raise: :class:`oerplib.error.RPCError` """ try: db_id = self._oerp._connector.db.create( super_admin_passwd, database, demo_data, lang, admin_passwd) progress = 0.0 attempt = 0 while progress < 1.0: result = self._oerp._connector.db.get_progress( super_admin_passwd, db_id) progress = result[0] if progress < 1.0: time.sleep(1) attempt += 1 if attempt > 300: raise error.RPCError( "Too many attempts, the operation" " has been canceled.") return result[1] except rpc.error.ConnectorError as exc: #FIXME handle the exception with the UnicodeEncodeError for # the error 'the database already exists'. #print dir(exc) raise error.RPCError(exc.message, exc.oerp_traceback) def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args): """Return the result of the RPC request.""" try: meth = getattr(self._oerp._connector.db, method, False) return meth(*args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/wizard.py0000664000175000017500000000355212110524053017224 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """Provide the :class:`Wizard` class in order to access the old-style wizards. """ from oerplib import rpc, error class Wizard(object): """.. versionadded:: 0.6 The `Wizard` class represents the ``/wizard`` RPC service which lets you access to the old-style wizards. .. note:: This service have to be used through the :attr:`oerplib.OERP.wizard` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'passwd', 'database') >>> oerp.wizard .. warning:: All methods documented below are not strictly implemented in `OERPLib` Method calls are purely dynamic, and the following documentation can be wrong if the API of `OpenERP` is changed between versions. Anyway, if you known the API used by the `OpenERP` server for the ``/wizard`` RPC service, it will work. .. method:: Wizard.create(wiz_name, datas=None) >>> oerp.wizard.create('wiz_name') 1 :return: the wizard's instance ID .. method:: Wizard.execute(wiz_id, datas, action='init', context=None) >>> oerp.wizard.execute(1, {'one_field': u"Value"}) """ def __init__(self, oerp): self._oerp = oerp def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args): """Return the result of the RPC request.""" try: meth = getattr(self._oerp._connector.wizard, method, False) return meth(self._oerp.database, self._oerp.user.id, *args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/__init__.py0000664000175000017500000000052012110524053017453 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """The `service` package provides classes to request services offers by the `OpenERP` server, such as: - '/common' (service.common), - '/db' (service.db) - '/wizard' (service.wizard) - Model, through '/object' (service.osv), """ # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/osv/0000775000175000017500000000000012110526160016155 5ustar sebseb00000000000000OERPLib-0.7.0/oerplib/service/osv/fields.py0000664000175000017500000004021012110524053017771 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module contains classes representing the fields supported by OpenObject. A field is a Python descriptor which defines getter/setter methods for its related attribute. """ import datetime from oerplib import error from oerplib.service.osv import browse def is_int(value): """Return `True` if ``value`` is an integer.""" if isinstance(value, bool): return False try: int(value) return True except ValueError: return False class BaseField(object): """Field which all other fields inherit. Manage common metadata. """ def __init__(self, osv, name, data): self.osv = osv self.name = name self.type = 'type' in data and data['type'] or False self.string = 'string' in data and data['string'] or False self.size = 'size' in data and data['size'] or False self.required = 'required' in data and data['required'] or False self.readonly = 'readonly' in data and data['readonly'] or False self.help = 'help' in data and data['help'] or False self.states = 'states' in data and data['states'] or False def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __str__(self): """Return a human readable string representation of the field.""" attrs = ['string', 'relation', 'required', 'readonly', 'size', 'domain'] attrs_rep = [] for attr in attrs: if hasattr(self, attr): value = getattr(self, attr) if value: if isinstance(value, basestring): attrs_rep.append("{0}='{1}'".format(attr, value)) else: attrs_rep.append("{0}={1}".format(attr, value)) attrs_rep = ", ".join(attrs_rep) return "{0}({1})".format(self.type, attrs_rep) def check_value(self, value): """Check the validity of a value for the field.""" #if self.readonly: # raise error.Error( # u"'{field_name}' field is readonly".format( # field_name=self.name)) if value and self.size: if not isinstance(value, basestring): raise ValueError(u"Value supplied has to be a string") if len(value) > self.size: raise ValueError( u"Lenght of the '{field_name}' is limited to {size}".format( field_name=self.name, size=self.size)) if not value and self.required: raise ValueError( u"'{field_name}' field is required".format( field_name=self.name)) return value class SelectionField(BaseField): """Represent the OpenObject 'fields.selection'""" def __init__(self, osv, name, data): super(SelectionField, self).__init__(osv, name, data) self.selection = 'selection' in data and data['selection'] or False def __get__(self, instance, owner): return instance.__data__['values'][self.name] def __set__(self, instance, value): value = self.check_value(value) instance.__data__['values'][self.name] = value instance.__data__['fields_updated'].append(self.name) def check_value(self, value): super(SelectionField, self).check_value(value) selection = [val[0] for val in self.selection] if value and value not in selection: raise ValueError( u"The value '{value}' supplied doesn't match with the possible " u"values '{selection}' for the '{field_name}' field".format( value=value, selection=selection, field_name=self.name, )) return value class Many2ManyField(BaseField): """Represent the OpenObject 'fields.many2many'""" def __init__(self, osv, name, data): super(Many2ManyField, self).__init__(osv, name, data) self.relation = 'relation' in data and data['relation'] or False self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False def __get__(self, instance, owner): """Return a generator to iterate on ``browse_record`` instances.""" ids = instance.__data__['values'][self.name] # None value => get the value on the fly if ids is None: ids = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = ids if ids: context = instance.__data__['context'].copy() context.update(self.context) return instance.__oerp__.browse(self.relation, ids, context) return iter(()) def __set__(self, instance, value): value = self.check_value(value) instance.__data__['values'][self.name] = value instance.__data__['fields_updated'].append(self.name) def check_value(self, value): if value: if not isinstance(value, list): raise ValueError( u"The value supplied has to be a list or 'False'") return super(Many2ManyField, self).check_value(value) class Many2OneField(BaseField): """Represent the OpenObject 'fields.many2one'""" def __init__(self, osv, name, data): super(Many2OneField, self).__init__(osv, name, data) self.relation = 'relation' in data and data['relation'] or False self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False def __get__(self, instance, owner): id_ = instance.__data__['values'][self.name] # None value => get the value on the fly if id_ is None: id_ = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = id_ if id_: context = instance.__data__['context'].copy() context.update(self.context) return instance.__class__.__oerp__.browse( self.relation, id_[0], context) return False def __set__(self, instance, value): if isinstance(value, browse.BrowseRecord): o_rel = value elif is_int(value): o_rel = instance.__class__.__oerp__.browse(self.relation, value) elif value in [None, False]: o_rel = False else: raise ValueError(u"Value supplied has to be an integer, " u"a browse_record object or False.") o_rel = self.check_value(o_rel) instance.__data__['values'][self.name] = o_rel and [o_rel.id, False] instance.__data__['fields_updated'].append(self.name) def check_value(self, value): super(Many2OneField, self).check_value(value) if value and value.__osv__['name'] != self.relation: raise ValueError( (u"Instance of '{model}' supplied doesn't match with the " + u"relation '{relation}' of the '{field_name}' field.").format( model=value.__osv__['name'], relation=self.relation, field_name=self.name)) return value class One2ManyField(BaseField): """Represent the OpenObject 'fields.one2many'""" def __init__(self, osv, name, data): super(One2ManyField, self).__init__(osv, name, data) self.relation = 'relation' in data and data['relation'] or False self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False def __get__(self, instance, owner): """Return a generator to iterate on ``browse_record`` instances.""" ids = instance.__data__['values'][self.name] # None value => get the value on the fly if ids is None: ids = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = ids if ids: context = instance.__data__['context'].copy() context.update(self.context) return instance.__oerp__.browse(self.relation, ids, context) return iter(()) def __set__(self, instance, value): value = self.check_value(value) instance.__data__['values'][self.name] = value instance.__data__['fields_updated'].append(self.name) def check_value(self, value): if value: if not isinstance(value, list): raise ValueError( u"The value supplied has to be a list or 'False'") return super(One2ManyField, self).check_value(value) class ReferenceField(BaseField): """.. versionadded:: 0.6 Represent the OpenObject 'fields.reference'. """ def __init__(self, osv, name, data): super(ReferenceField, self).__init__(osv, name, data) self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False self.selection = 'selection' in data and data['selection'] or False def __get__(self, instance, owner): value = instance.__data__['values'][self.name] # None value => get the value on the fly if value is None: value = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = value if value: relation, sep, o_id = value.rpartition(',') relation = relation.strip() o_id = int(o_id.strip()) if relation and o_id: context = instance.__data__['context'].copy() context.update(self.context) return instance.__class__.__oerp__.browse( relation, o_id, context) return False def __set__(self, instance, value): value = self.check_value(value) instance.__data__['values'][self.name] = value instance.__data__['fields_updated'].append(self.name) def _check_relation(self, relation): selection = [val[0] for val in self.selection] if relation not in selection: raise ValueError( (u"The value '{value}' supplied doesn't match with the possible" u" values '{selection}' for the '{field_name}' field").format( value=relation, selection=selection, field_name=self.name, )) return relation def check_value(self, value): if isinstance(value, browse.BrowseRecord): relation = value.__class__.__osv__['name'] self._check_relation(relation) value = "%s,%s" % (relation, value.id) super(ReferenceField, self).check_value(value) elif isinstance(value, basestring): super(ReferenceField, self).check_value(value) relation, sep, o_id = value.rpartition(',') relation = relation.strip() o_id = o_id.strip() #o_rel = instance.__class__.__oerp__.browse(relation, o_id) if not relation or not is_int(o_id): raise ValueError(u"String not well formatted, expecting " u"'{relation},{id}' format") self._check_relation(relation) else: raise ValueError(u"Value supplied has to be a string or" u" a browse_record object.") return value class DateField(BaseField): """Represent the OpenObject 'fields.data'""" pattern = "%Y-%m-%d" def __init__(self, osv, name, data): super(DateField, self).__init__(osv, name, data) def __get__(self, instance, owner): value = instance.__data__['values'][self.name] try: res = datetime.datetime.strptime(value, self.pattern).date() except Exception: # ValueError, TypeError res = value return res def __set__(self, instance, value): value = self.check_value(value) instance.__data__['values'][self.name] = value instance.__data__['fields_updated'].append(self.name) def check_value(self, value): super(DateField, self).check_value(value) if isinstance(value, datetime.date): value = value.strftime("%Y-%m-%d") elif isinstance(value, basestring): try: datetime.datetime.strptime(value, self.pattern) except: raise ValueError( "String not well formatted, expecting '{0}' format".format( self.pattern)) elif isinstance(value, bool): return value else: raise ValueError( "Expecting a datetime.date object or basestring") return value class DateTimeField(BaseField): """Represent the OpenObject 'fields.datetime'""" pattern = "%Y-%m-%d %H:%M:%S" def __init__(self, osv, name, data): super(DateTimeField, self).__init__(osv, name, data) def __get__(self, instance, owner): value = instance.__data__['values'][self.name] try: res = datetime.datetime.strptime(value, self.pattern) except Exception: # ValueError, TypeError res = value return res def __set__(self, instance, value): value = self.check_value(value) instance.__data__['values'][self.name] = value instance.__data__['fields_updated'].append(self.name) def check_value(self, value): super(DateTimeField, self).check_value(value) if isinstance(value, datetime.datetime): value = value.strftime("%Y-%m-%d %H:%M:%S") elif isinstance(value, basestring): try: datetime.datetime.strptime(value, self.pattern) except: raise ValueError( "Value not well formatted, expecting '{0}' format".format( self.pattern)) elif isinstance(value, bool): return value else: raise ValueError( "Expecting a datetime.datetime object or basestring") return value class ValueField(BaseField): """Represent simple OpenObject fields: - 'fields.char', - 'fields.float', - 'fields.integer', - 'fields.boolean', - 'fields.text', - 'fields.binary', """ def __init__(self, osv, name, data): super(ValueField, self).__init__(osv, name, data) def __get__(self, instance, owner): return instance.__data__['values'][self.name] def __set__(self, instance, value): value = self.check_value(value) instance.__data__['values'][self.name] = value instance.__data__['fields_updated'].append(self.name) def generate_field(osv, name, data): """Generate a well-typed field according to the data dictionary supplied (obtained via 'fields_get' XML-RPC/NET-RPC method). """ assert 'type' in data field = None if data['type'] == 'selection': field = SelectionField(osv, name, data) elif data['type'] == 'many2many': field = Many2ManyField(osv, name, data) elif data['type'] == 'many2one': field = Many2OneField(osv, name, data) elif data['type'] == 'one2many': field = One2ManyField(osv, name, data) elif data['type'] == 'reference': field = ReferenceField(osv, name, data) elif data['type'] == 'date': field = DateField(osv, name, data) elif data['type'] == 'datetime': field = DateTimeField(osv, name, data) elif data['type'] in ['char', 'float', 'integer', 'boolean', 'text', 'binary', 'html']: field = ValueField(osv, name, data) else: txt = (u"Can't instanciate the field '{field_name}', " u"'{field_type}' type unknown") raise error.InternalError( txt.format(field_name=name, field_type=data['type'])) return field # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/osv/osv.py0000664000175000017500000002244112110524053017340 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """Provide the :class:`Model` class which allow to access dynamically to all methods proposed by an model of the `OpenERP` server.""" import collections from oerplib.tools import v from oerplib import error from oerplib.service.osv import fields, browse class Model(collections.Mapping): """.. versionadded:: 0.5 Represent a data model from the `OpenERP` server. .. note:: This class have to be used through the :func:`oerplib.OERP.get` method. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'passwd', 'database') >>> user_obj = oerp.get('res.users') >>> user_obj >>> user_obj.name_get(user.id) # Use any methods from the model instance [[1, 'Administrator']] .. warning:: The only method implemented in this class is ``browse``. Except this one, method calls are purely dynamic. As long as you know the signature of the model method targeted, you will be able to use it (see the :ref:`tutorial `). """ fields_reserved = ['id', '__oerp__', '__osv__', '__data__'] def __init__(self, oerp, model): super(Model, self).__init__() self._oerp = oerp self._name = model self._browse_class = self._generate_browse_class() def _browse_generator(self, ids, context=None): """Generator used by the :func:`browse ` method. """ for o_id in ids: yield self.browse(o_id, context) def browse(self, ids, context=None): """Browse one or several records (if `ids` is a list of IDs) from `model`. The fields and values for such objects are generated dynamically. >>> oerp.get('res.partner').browse(1) browse_record(res.partner, 1) >>> [partner.name for partner in oerp.get('res.partner').browse([1, 2])] [u'Your Company', u'ASUStek'] A list of data types used by ``browse_record`` fields are available :ref:`here `. :return: a ``browse_record`` instance :return: a generator to iterate on ``browse_record`` instances :raise: :class:`oerplib.error.RPCError` """ if isinstance(ids, list): return self._browse_generator(ids, context) else: obj = self._browse_class(ids) self._refresh(obj, context) return obj #return self.browse(ids, context) def _generate_browse_class(self): """Generate a class with all its fields corresponding to the model name supplied and return them. """ # Retrieve server fields info and generate corresponding local fields fields_get = self._oerp.execute(self._name, 'fields_get') cls_name = self._name.replace('.', '_') if type(cls_name) == unicode: cls_name = cls_name.encode('utf-8') cls_fields = {} for field_name, field_data in fields_get.items(): if field_name not in Model.fields_reserved: cls_fields[field_name] = fields.generate_field( self, field_name, field_data) # Case where no field 'name' exists, we generate one (which will be # in readonly mode) in purpose to be filled with the 'name_get' method if 'name' not in cls_fields: field_data = {'type': 'text', 'string': 'Name', 'readonly': True} cls_fields['name'] = fields.generate_field(self, 'name', field_data) cls = type(cls_name, (browse.BrowseRecord,), {}) cls.__oerp__ = self._oerp cls.__osv__ = {'name': self._name, 'columns': cls_fields} slots = ['__oerp__', '__osv__', '__dict__', '__data__'] slots.extend(cls_fields.keys()) cls.__slots__ = slots return cls def _write_record(self, obj, context=None): """Send values of fields updated to the OpenERP server.""" obj_data = obj.__data__ vals = {} for field_name in obj_data['fields_updated']: if field_name in obj_data['raw_data']: field = self._browse_class.__osv__['columns'][field_name] field_value = obj.__data__['values'][field_name] # Many2One fields if isinstance(field, fields.Many2OneField): vals[field_name] = field_value and field_value[0] # All other fields else: vals[field_name] = field_value try: if v(self._oerp._version) < v('6.1'): res = self.write([obj.id], vals, context) else: res = self.write([obj.id], vals, context=context) except error.Error as exc: raise exc else: # Update raw_data dictionary # FIXME: make it optional to avoid a RPC request? self._refresh(obj, context) return res def _refresh(self, obj, context=None): """Retrieve field values from OpenERP server. May be used to restore the original values in the purpose to cancel all changes made. """ context = context or self._oerp.context obj_data = obj.__data__ obj_data['context'] = context # Get basic fields (no relational ones) basic_fields = [] for field_name, field in obj.__osv__['columns'].iteritems(): if not getattr(field, 'relation', False): basic_fields.append(field_name) else: obj_data['raw_data'][field_name] = None # Fill fields with values of the record if obj.id: if v(self._oerp._version) < v('6.1'): data = self.read([obj.id], basic_fields, context) if data: obj_data['raw_data'].update(data[0]) else: obj_data['raw_data'] = False else: data = self.read([obj.id], basic_fields, context=context) if data: obj_data['raw_data'].update(data[0]) else: obj_data['raw_data'] = False if obj_data['raw_data'] is False: raise error.RPCError( u"There is no '{model}' record with ID {obj_id}.".format( model=obj.__class__.__osv__['name'], obj_id=obj.id)) # No ID: fields filled with default values else: if v(self._oerp._version) < v('6.1'): default_get = self.default_get( obj.__osv__['columns'].keys(), context) else: default_get = self.default_get( obj.__osv__['columns'].keys(), context=context) obj_data['raw_data'] = {} for field_name in obj.__osv__['columns']: obj_data['raw_data'][field_name] = False obj_data['raw_data'].update(default_get) self._reset(obj) def _reset(self, obj): """Cancel all changes by restoring field values with original values obtained during the last refresh (object instanciation or last call to _refresh() method). """ obj_data = obj.__data__ obj_data['fields_updated'] = [] # Load fields and their values for field in self._browse_class.__osv__['columns'].values(): if field.name in obj_data['raw_data']: obj_data['values'][field.name] = \ obj_data['raw_data'][field.name] setattr(obj.__class__, field.name, field) def _unlink_record(self, obj, context=None): """Delete the object from the OpenERP server.""" if v(self._oerp._version) < v('6.1'): return self.unlink([obj.id], context) else: return self.unlink([obj.id], context=context) def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args, **kwargs): """Return the result of the RPC request.""" if v(self._oerp._version) < v('6.1'): if kwargs: raise error.RPCError( u"Named parameters are not supported by this version " u"of OpenERP") result = self._oerp.execute( self._browse_class.__osv__['name'], method, *args) else: if self._oerp.config['auto_context'] \ and 'context' not in kwargs: kwargs['context'] = self._oerp.context result = self._oerp.execute_kw( self._browse_class.__osv__['name'], method, args, kwargs) return result return rpc_method def __repr__(self): return "Model(%r)" % (self._browse_class.__osv__['name']) # ---------------------------- # # -- MutableMapping methods -- # # ---------------------------- # def __getitem__(self, obj_id): return self.browse(obj_id) def __iter__(self): ids = self.search([]) return self._browse_generator(ids) def __len__(self): return self._oerp.search(self._browse_class.__osv__['name'], count=True) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/osv/browse.py0000664000175000017500000000556112110524053020036 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module provides the BrowseRecord class.""" class BrowseRecord(object): """Base class that all browsable records inherit from. No attributes should be defined in this class (except ``_id``/``id``, ``__oerp__``, ``__osv__``, ``__data__`` and Python magic methods) in order to not be conflicted with the fields defined in the model class on the `OpenERP` server. A reference to the :class:`OERP ` object used to instanciate a ``browse_record`` is available through the ``__oerp__`` attribute:: >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'admin', 'db_name') >>> user.__oerp__ == oerp True The ``__data__`` attribute is used to store some data related to the record (it is not recommended to edit them):: >>> user.__data__ {'fields_updated': [], 'raw_data': {'action_id': False, 'active': True, 'company_id': [1, 'Your Company'], ...}, 'values': {'action_id': False, 'active': True, 'company_id': [1, 'Your Company'], ...}} In the same way, information about the model class and its columns may be obtained via the ``__osv__`` attribute:: >>> user.__osv__ {'columns': {'action_id': , 'active': , 'company_id': , ...}, 'name': 'res.users'} """ __oerp__ = None __osv__ = None def __init__(self, o_id): self._id = o_id self.__data__ = {'values': {}, 'raw_data': {}, 'fields_updated': []} @property def id(self): """ID of the record.""" return self._id def __repr__(self): return "browse_record(%r, %r)" % (self.__osv__['name'], self._id) def __getitem__(self, key): return getattr(self, key) def __int__(self): return self._id def __eq__(self, other): """Compare two browse records. Return ``True`` if their ID and model name are equals. NOTE: the comparison is made this way because their model classes can be differents objects. """ return isinstance(other, BrowseRecord) and \ self.id == other.id and \ self.__osv__['name'] == other.__osv__['name'] def __ne__(self, other): if not isinstance(other, BrowseRecord): return True return isinstance(other, BrowseRecord)\ and (self.__osv__['name'], self.id) !=\ (other.__osv__['name'], other.id) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/service/osv/__init__.py0000664000175000017500000000027212110524053020266 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- from oerplib.service.osv.browse import BrowseRecord from oerplib.service.osv.osv import Model # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/__init__.py0000664000175000017500000000107312110524053016017 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """The `oerplib` module defines the :class:`OERP` class. The :class:`OERP` class manage the client-side operations which are related to an `OpenERP` server. You can use this one to write Python programs that performs a variety of automated jobs that communicate with an `OpenERP` server. """ __author__ = 'ABF Osiell - Sebastien Alix' __email__ = 'sebastien.alix@osiell.com' __licence__ = 'LGPL v3' __version__ = '0.7.0' from oerplib.oerp import OERP from oerplib import error # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/oerp.py0000664000175000017500000004724012110524053015233 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module contains the ``OERP`` class which manage the interaction with the `OpenERP` server. """ import os import base64 import zlib import tempfile import time from oerplib import config from oerplib import rpc from oerplib import error from oerplib.tools import detect_version from oerplib.service import common, db, wizard, osv class OERP(object): """Return a new instance of the :class:`OERP` class. The optional `database` parameter specifies the default database to use when the :func:`login ` method is called. If no `database` is set, the `database` parameter of the :func:`login ` method will be mandatory. `XML-RPC` and `Net-RPC` protocols are supported. Respective values for the `protocol` parameter are ``xmlrpc``, ``xmlrpc+ssl`` and ``netrpc``. >>> import oerplib >>> oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) Since the version `0.7`, `OERPLib` will try by default to detect the `OpenERP` server version in order to adapt its requests. However, it is possible to force the version of `OpenERP` with the `version` parameter: >>> oerp = oerplib.OERP('localhost', version='6.0') :raise: :class:`oerplib.error.InternalError`, :class:`oerplib.error.RPCError` """ def __init__(self, server='localhost', database=None, protocol='xmlrpc', port=8069, timeout=120, version=None): self._server = server self._port = port self._protocol = protocol self._database = self._database_default = database self._user = None self._common = common.Common(self) self._db = db.DB(self) self._wizard = wizard.Wizard(self) self._version = version or detect_version( server, protocol, port, timeout) # Instanciate the OpenERP server connector try: self._connector = rpc.get_connector( self._server, self._port, self._protocol, timeout, self._version) except rpc.error.ConnectorError as exc: raise error.InternalError(exc.message) # Dictionary of configuration options self._config = config.Config( self, {'auto_context': True, 'timeout': timeout}) @property def config(self): """Dictionary of available configuration options. >>> oerp.config {'auto_context': True, 'timeout': 120} - ``auto_context``: if set to `True`, the user context will be sent automatically to every call of a :class:`model ` method (default: `True`): .. versionadded:: 0.7 .. note:: This option only works on `OpenERP` version `6.1` and above. >>> product_osv = oerp.get('product.product') >>> product_osv.name_get([3]) # Context sent by default ('lang': 'fr_FR' here) [[3, '[PC1] PC Basic']] >>> oerp.config['auto_context'] = False >>> product_osv.name_get([3]) # No context sent [[3, '[PC1] Basic PC']] - ``timeout``: set the maximum timeout in seconds for a RPC request (default: `120`): .. versionadded:: 0.6 >>> oerp.config['timeout'] = 300 """ return self._config # Readonly properties @property def user(self): """The browsable record of the user connected. >>> oerp.login('admin', 'admin') == oerp.user True """ return self._user @property def context(self): """The context of the user connected. >>> oerp.login('admin', 'admin') browse_record('res.users', 1) >>> oerp.context {'lang': 'fr_FR', 'tz': False} >>> oerp.context['lang'] = 'en_US' """ return self._context server = property(lambda self: self._server, doc="The server name.") port = property(lambda self: self._port, doc="The port used.") protocol = property(lambda self: self._protocol, doc="The protocol used.") database = property(lambda self: self._database, doc="The database currently used.") common = property(lambda self: self._common, doc=(""".. versionadded:: 0.6 The common service (``/common`` RPC service). See the :class:`oerplib.service.common.Common` class.""")) db = property(lambda self: self._db, doc=(""".. versionadded:: 0.4 The database management service (``/db`` RPC service). See the :class:`oerplib.service.db.DB` class.""")) wizard = property(lambda self: self._wizard, doc=(""".. versionadded:: 0.6 The wizard service (``/wizard`` RPC service). See the :class:`oerplib.service.wizard.Wizard` class.""")) # RPC Connector timeout @property def timeout(self): """.. versionadded:: 0.6 .. deprecated:: 0.7 will be deleted in the next version. Use the :attr:`OERP.config ` property instead Set the maximum timeout for a RPC request. """ return self.config['timeout'] @timeout.setter def timeout(self, timeout): self.config['timeout'] = timeout #NOTE: in the past this function was implemented as a decorator for other # methods needed to be checked, but Sphinx documentation generator is not # able to auto-document decorated methods. def _check_logged_user(self): """Check if a user is logged. Otherwise, an error is raised.""" if not self._user: raise error.Error( u"User login required.") def login(self, user='admin', passwd='admin', database=None): """Log in as the given `user` with the password `passwd` on the database `database` and return the corresponding user as a browsable record (from the ``res.users`` model). If `database` is not specified, the default one will be used instead. >>> user = oerp.login('admin', 'admin', database='db_name') >>> user.name u'Administrator' :return: the user connected as a browsable record :raise: :class:`oerplib.error.RPCError` """ # Raise an error if no database was given self._database = database or self._database_default if not self._database: raise error.Error(u"No database specified") # Get the user's ID and generate the corresponding User record try: user_id = self.common.login(self._database, user, passwd) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) else: if user_id: #NOTE: create a fake User record just to execute the # first query : browse the real User record self._user = type('FakeUser', (osv.BrowseRecord,), {'id': None, 'login': None, 'password': None}) self._user.id = user_id self._user.login = user self._user.password = passwd self._context = self.execute('res.users', 'context_get') self._user = self.browse('res.users', user_id, self._context) return self._user else: #FIXME: Raise an error? raise error.RPCError(u"Wrong login ID or password") # ------------------------- # # -- Raw XML-RPC methods -- # # ------------------------- # def execute(self, model, method, *args): """Execute the `method` of `model`. `*args` parameters varies according to the `method` used. >>> oerp.execute('res.partner', 'read', [1, 2], ['name']) [{'name': u'ASUStek', 'id': 2}, {'name': u'Your Company', 'id': 1}] :return: the result returned by the `method` called :raise: :class:`oerplib.error.RPCError` """ self._check_logged_user() # Execute the query try: return self._connector.object.execute( self._database, self._user.id, self._user.password, model, method, *args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) def execute_kw(self, model, method, args=None, kwargs=None): """Execute the `method` of `model`. `args` is a list of parameters (in the right order), and `kwargs` a dictionary (named parameters). Both varies according to the `method` used. >>> oerp.execute_kw('res.partner', 'read', [[1, 2]], {'fields': ['name']}) [{'name': u'ASUStek', 'id': 2}, {'name': u'Your Company', 'id': 1}] .. warning:: This method only works on `OpenERP` version `6.1` and above. :return: the result returned by the `method` called :raise: :class:`oerplib.error.RPCError` """ self._check_logged_user() # Execute the query args = args or [] kwargs = kwargs or {} try: return self._connector.object.execute_kw( self._database, self._user.id, self._user.password, model, method, args, kwargs) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) def exec_workflow(self, model, signal, obj_id): """Execute the workflow `signal` on the instance having the ID `obj_id` of `model`. :raise: :class:`oerplib.error.RPCError` """ #TODO NEED TEST self._check_logged_user() # Execute the workflow query try: self._connector.object.exec_workflow(self._database, self._user.id, self._user.password, model, signal, obj_id) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) def report(self, report_name, model, obj_id, report_type='pdf', context=None): """Download a report from the `OpenERP` server and return the path of the file. >>> oerp.report('sale.order', 'sale.order', 1) '/tmp/oerplib_uJ8Iho.pdf' :return: the path to the generated temporary file :raise: :class:`oerplib.error.RPCError` """ #TODO report_type: what it means exactly? self._check_logged_user() # If no context was supplied, get the default one if context is None: context = self.context # Execute the report query try: pdf_data = self._get_report_data(report_name, model, obj_id, report_type, context) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return self._print_file_data(pdf_data) def _get_report_data(self, report_name, model, obj_id, report_type='pdf', context=None): """Download binary data of a report from the `OpenERP` server.""" context = context or {} data = {'model': model, 'id': obj_id, 'report_type': report_type} try: report_id = self._connector.report.report( self._database, self.user.id, self.user.password, report_name, [obj_id], data, context) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) state = False attempt = 0 while not state: try: pdf_data = self._connector.report.report_get( self._database, self.user.id, self.user.password, report_id) except rpc.error.ConnectorError as exc: raise error.RPCError("Unknown error occurred during the " "download of the report.") state = pdf_data['state'] if not state: time.sleep(1) attempt += 1 if attempt > 200: raise error.RPCError("Download time exceeded, " "the operation has been canceled.") return pdf_data @staticmethod def _print_file_data(data): """Print data in a temporary file and return the path of this one.""" if 'result' not in data: raise error.InternalError( u"Invalid data, the operation has been canceled.") content = base64.decodestring(data['result']) if data.get('code') == 'zlib': content = zlib.decompress(content) if data['format'] in ['pdf', 'html', 'doc', 'xls', 'sxw', 'odt', 'tiff']: if data['format'] == 'html' and os.name == 'nt': data['format'] = 'doc' (file_no, file_path) = tempfile.mkstemp('.' + data['format'], 'oerplib_') with file(file_path, 'wb+') as fp: fp.write(content) os.close(file_no) return file_path # ------------------------- # # -- High Level methods -- # # ------------------------- # def browse(self, model, ids, context=None): """Browse one or several records (if `ids` is a list of IDs) from `model`. The fields and values for such objects are generated dynamically. >>> oerp.browse('res.partner', 1) browse_record(res.partner, 1) >>> [partner.name for partner in oerp.browse('res.partner', [1, 2])] [u'Your Company', u'ASUStek'] A list of data types used by ``browse_record`` fields are available :ref:`here `. :return: a ``browse_record`` instance :return: a generator to iterate on ``browse_record`` instances :raise: :class:`oerplib.error.RPCError` """ return self.get(model).browse(ids, context) def search(self, model, args=None, offset=0, limit=None, order=None, context=None, count=False): """Return a list of IDs of records matching the given criteria in `args` parameter. `args` must be of the form ``[('name', '=', 'John'), (...)]`` >>> oerp.search('res.partner', [('name', 'like', 'Agrolait')]) [3] :return: a list of IDs :raise: :class:`oerplib.error.RPCError` """ if args is None: args = [] return self.execute(model, 'search', args, offset, limit, order, context, count) def create(self, model, vals, context=None): """Create a new `model` record with values contained in the `vals` dictionary. >>> partner_id = oerp.create('res.partner', {'name': 'Jacky Bob', 'lang': 'fr_FR'}) :return: the ID of the new record. :raise: :class:`oerplib.error.RPCError` """ return self.execute(model, 'create', vals, context) def read(self, model, ids, fields=None, context=None): """Return `fields` values for each `model` record identified by `ids`. If `fields` is not specified, all fields values will be retrieved. >>> oerp.read('res.partner', [1, 2], ['name']) [{'name': u'ASUStek', 'id': 2}, {'name': u'Your Company', 'id': 1}] :return: list of dictionaries :raise: :class:`oerplib.error.RPCError` """ if fields is None: fields = [] return self.execute(model, 'read', ids, fields, context) def write(self, model, ids, vals=None, context=None): """Update `model` records identified by `ids` with the given values contained in the `vals` dictionary. >>> oerp.write('res.users', [1], {'name': u"Administrator"}) True :return: `True` :raise: :class:`oerplib.error.RPCError` """ #if ids is None: # ids = [] if vals is None: vals = {} return self.execute(model, 'write', ids, vals, context) def unlink(self, model, ids, context=None): """Delete `model` records identified by `ids`. >>> oerp.unlink('res.partner', [1]) :return: `True` :raise: :class:`oerplib.error.RPCError` """ #if ids is None: # ids = [] return self.execute(model, 'unlink', ids, context) # ---------------------- # # -- Special methods -- # # ---------------------- # def write_record(self, browse_record, context=None): """.. versionadded:: 0.4 Update the record corresponding to `browse_record` by sending its values to the `OpenERP` database (only field values which have been changed). >>> partner = oerp.browse('res.partner', 1) >>> partner.name = "Test" >>> oerp.write_record(partner) # write('res.partner', [1], {'name': "Test"}) :return: `True` :raise: :class:`oerplib.error.RPCError` """ if not isinstance(browse_record, osv.BrowseRecord): raise ValueError(u"An instance of BrowseRecord is required") return osv.Model(self, browse_record.__osv__['name'])._write_record( browse_record, context) def unlink_record(self, browse_record, context=None): """.. versionadded:: 0.4 Delete the record corresponding to `browse_record` from the `OpenERP` database. >>> partner = oerp.browse('res.partner', 1) >>> oerp.unlink_record(partner) # unlink('res.partner', [1]) :return: `True` :raise: :class:`oerplib.error.RPCError` """ if not isinstance(browse_record, osv.BrowseRecord): raise ValueError(u"An instance of BrowseRecord is required") return osv.Model(self, browse_record.__osv__['name'])._unlink_record( browse_record, context) def refresh(self, browse_record, context=None): """Restore original values on `browse_record` with data fetched on the `OpenERP` database. As a result, all changes made locally on the record are canceled. :raise: :class:`oerplib.error.RPCError` """ return osv.Model(self, browse_record.__osv__['name'])._refresh( browse_record, context) def reset(self, browse_record): """Cancel all changes made locally on the `browse_record`. No request to the server is executed to perform this operation. Therefore, values restored may be outdated. """ return osv.Model(self, browse_record.__osv__['name'])._reset( browse_record) @staticmethod def get_osv_name(browse_record): """ .. deprecated:: 0.7 use the ``__osv__`` attribute instead (see :class:`BrowseRecord `). >>> partner = oerp.browse('res.partner', 1) >>> oerp.get_osv_name(partner) 'res.partner' :return: the model name of the browsable record """ if not isinstance(browse_record, osv.BrowseRecord): raise ValueError(u"Value is not a browse_record.") return browse_record.__osv__['name'] def get(self, model): """.. versionadded:: 0.5 Return a proxy of the `model` built from the `OpenERP` server (see :class:`oerplib.service.osv.Model`). :return: an instance of :class:`oerplib.service.osv.Model` """ return osv.Model(self, model) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/oerplib/tools.py0000664000175000017500000000346112110524053015423 0ustar sebseb00000000000000# -*- coding: UTF-8 -*- """This module contains some helper functions used in ``OERPLib``.""" import re match_version = re.compile(r'[^\d.]') def clean_version(version): """Clean a version string. >>> from oerplib.tools import clean_version >>> clean_version('7.0alpha-20121206-000102') '7.0' :return: a cleaner version string """ version = match_version.sub('', version.split('-')[0]) return version def detect_version(server, protocol, port, timeout=120): """Try to detect the `OpenERP` server version. >>> from oerplib.tools import detect_version >>> detect_version('localhost', 'xmlrpc', 8069) '7.0' :return: the version as string """ from oerplib import rpc # Try to request OpenERP with the last API supported try: con = rpc.get_connector( server, port, protocol, timeout, version=None) version = con.db.server_version() except: # Try with the API of OpenERP < 6.1 try: con = rpc.get_connector( server, port, protocol, timeout, version='6.0') version = con.db.server_version() except: # No version detected? Use the magic number in order to ensure the # use of the last API supported version = '42' finally: return clean_version(version) def v(version): """Convert a version string in tuple. The tuple can be use to compare versions between them. >>> from oerplib.tools import v >>> v('7.0') [7, 0] >>> v('6.1') [6, 1] >>> v('7.0') < v('6.1') False :return: the version as tuple """ return [int(x) for x in clean_version(version).split(".")] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: OERPLib-0.7.0/PKG-INFO0000664000175000017500000000752712110526160013362 0ustar sebseb00000000000000Metadata-Version: 1.1 Name: OERPLib Version: 0.7.0 Summary: OpenERP client library which allows to easily interact with an OpenERP server. Home-page: http://packages.python.org/OERPLib/ Author: ABF Osiell - Sebastien Alix Author-email: sebastien.alix@osiell.com License: LGPL v3 Download-URL: http://pypi.python.org/packages/source/O/OERPLib/OERPLib-0.7.0.tar.gz Description: ======= OERPLib ======= `OERPLib` is a client library to `OpenERP` server. It aims to provide an easy way to remotely pilot an `OpenERP` server. Features supported: - `XML-RPC` and `Net-RPC` protocols, - access to all methods proposed by an `OpenERP` model class (even ``browse``) with an API similar to the server-side API, - ability to use named parameters with such methods (`OpenERP` >= `6.1`), - user context automatically sent (`OpenERP` >= `6.1`), - browse records, - execute workflows, - manage databases, - reports downloading. How does it work? See below:: import oerplib # Prepare the connection to the OpenERP server oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) # Check available databases print(oerp.db.list()) # Login (the object returned is a browsable record) user = oerp.login('user', 'passwd', 'db_name') print(user.name) # name of the user connected print(user.company_id.name) # the name of its company # Simple 'raw' query user_data = oerp.execute('res.users', 'read', [user.id]) print(user_data) # Use all methods of an OSV class order_obj = oerp.get('sale.order') order_ids = order_obj.search([]) for order in order_obj.browse(order_ids): print(order.name) products = [line.product_id.name for line in order.order_line] print(products) # Update data through a browsable record user.name = "Brian Jones" oerp.write_record(user) See the documentation for more details. Supported OpenERP versions -------------------------- `OERPLib` has been tested on `OpenERP` server v5.0, v6.0, v6.1 and v7.0. It should work on next versions if `OpenERP` keeps a stable API. Supported Python versions ------------------------- `OERPLib` support Python versions 2.6 and 2.7. Generate the documentation -------------------------- To generate the documentation, you have to install `Sphinx` documentation generator:: easy_install -U sphinx Then, you can use the ``build_doc`` option of the ``setup.py``:: python setup.py build_doc The generated documentation will be in the ``./doc/build/html`` directory. Bugs or suggestions ------------------- Please, feel free to report bugs or suggestions in the `Bug Tracker `_! Changes in this version ----------------------- Consult the ``CHANGES.txt`` file. Keywords: openerp client xml-rpc xml_rpc xmlrpc net-rpc net_rpc netrpc oerplib communication lib library python service web webservice Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Topic :: Software Development :: Libraries :: Python Modules