← Back to team overview

oerppy-hackers team mailing list archive

[Branch ~oerppy-hackers/oerppy/trunk] Rev 33: * Added a results decorator that intercepts what xmlrpclib gets back from the

 

------------------------------------------------------------
revno: 33
committer: duncan@xxxxxxxxxx
branch nick: trunk
timestamp: Thu 2011-06-02 13:03:01 -0600
message:
  * Added a results decorator that intercepts what xmlrpclib gets back from the
    OpenERP server (good for adding custom exceptions and possibly parsing raw
    data into model objects).
  * With this in place, most of the client methods were decorated with it.
  * Added a validation check for login.
  * Added several more custom exceptions and updated the base Error exception
    class to use the docstring as the message if no message was passed.
  * Added support for instantiating classes stored in the registry.
  * Fixed a nasty, hidden little but with the proxy. Turns out that you can't do
    normal existance checks against an xmlrpclib proxy instance, as that causes
    it to make a "__nonzero__" call to the server. Whatever the hell that means.
    Anyway, I had to change the check to do a comparison against a None type
    check.
modified:
  Makefile
  oerppy/addons/canonical/query.py
  oerppy/addons/canonical/scripting/export.py
  oerppy/client.py
  oerppy/exceptions.py
  oerppy/reg.py
  oerppy/service.py
  oerppy/util.py


--
lp:oerppy
https://code.launchpad.net/~oerppy-hackers/oerppy/trunk

Your team oerppy Hackers is subscribed to branch lp:oerppy.
To unsubscribe from this branch go to https://code.launchpad.net/~oerppy-hackers/oerppy/trunk/+edit-subscription
=== modified file 'Makefile'
--- Makefile	2011-06-01 18:49:40 +0000
+++ Makefile	2011-06-02 19:03:01 +0000
@@ -10,5 +10,13 @@
 commit: check
 	bzr commit --show-diff
 
+commit-msg: check
+	bzr commit --file=MSG
+
 push: commit
 	bzr push lp:oerppy
+
+push-msg: commit-msg
+	bzr push lp:oerppy
+	mv MSG MSG.backup
+	touch MSG

=== modified file 'oerppy/addons/canonical/query.py'
--- oerppy/addons/canonical/query.py	2011-06-02 17:25:28 +0000
+++ oerppy/addons/canonical/query.py	2011-06-02 19:03:01 +0000
@@ -1,3 +1,5 @@
+from datetime import date, timedelta
+
 from oerppy.addons.canonical import const
 
 
@@ -21,8 +23,8 @@
         A dispatch method that runs the query for the given report name, and
         optionally, domains.
         """
-        if not domain:
-            domain = []
+        if not domains:
+            domains = []
         if not self.user_dept:
             self.get_departments()
         if report == const.TIMESHEET_HOURS:
@@ -89,8 +91,8 @@
         Get the weekly timeentry records, highlighting the missing weeks.
         """
         # Get user Ids of timesheet users and the weeks we're interested in
-        today = datetime.date.today()
-        last_week = today - datetime.timedelta(days=-7)
+        today = date.today()
+        last_week = today - timedelta(days=-7)
         weeks = self.client.searchfields(
             'project.timeentry.week',
             [

=== modified file 'oerppy/addons/canonical/scripting/export.py'
--- oerppy/addons/canonical/scripting/export.py	2011-06-02 17:25:28 +0000
+++ oerppy/addons/canonical/scripting/export.py	2011-06-02 19:03:01 +0000
@@ -1,8 +1,10 @@
 from datetime import datetime
 
-from oerppy import config, export, service
+from oerppy import config
 from oerppy.scripting import ExportScript
 
+from oerppy.addons.canonical import const
+
 
 class MonthlyExport(ExportScript):
     """
@@ -40,6 +42,7 @@
         parser = super(WeeklyExport, self).get_option_parser()
         parser.add_option(
             "-s", "--report-style", dest="report_style", action="store",
+            default=const.TIMESHEETS_WEEKLY,
             help=("the report style to present in export (e.g., 'hours' " 
                   "or 'weekly'"))
         return parser

=== modified file 'oerppy/client.py'
--- oerppy/client.py	2011-06-02 17:25:28 +0000
+++ oerppy/client.py	2011-06-02 19:03:01 +0000
@@ -1,5 +1,7 @@
 import base64, datetime, time, xmlrpclib
 
+from oerppy import exceptions
+from oerppy.util import parse_results
 
 #   * client.Parser (parse the responses from the server and convert them to
 #     model objects; optional)
@@ -21,6 +23,8 @@
         Login to OpenERP, set the user ID, and prepare the proxies' callables.
         """
         uid = self.endpoints.common.login(self.dbname, self.credentials)
+        if uid == False:
+            raise exceptions.OpenERPPyLoginFailure()
         self.credentials.set_uid(uid)
 
         # cache the object proxy and prepare the execute callable
@@ -30,14 +34,18 @@
         # do the same for the report proxy
         self.endpoints.report.get_proxy()
         self.endpoints.report.prepare(self.dbname, self.credentials)
+        return uid
 
+    @parse_results
     def create(self, entity, record):
         """Create an entity"""
         return self.endpoints.object.execute(entity, "create", record)
 
+    @parse_results
     def search(self, entity, query):
         return self.endpoints.object.execute(entity, "search", query)
 
+    @parse_results
     def update(self, entity, record, ids):
         """Update an entity"""
         return self.endpoints.object.execute(entity, "write", ids, record)
@@ -45,6 +53,7 @@
     # preserve parity with publicly defined OpenERP API
     write = update
 
+    @parse_results
     def delete(self, entity, ids):
         """Delete an entity"""
         return self.endpoints.object.execute(entity, "unlink", ids)
@@ -52,12 +61,14 @@
     # preserve parity with publicly defined OpenERP API
     unlink = delete
 
+    @parse_results
     def read(self, entity, ids, fields):
         """
         Read an entity when provided a list of Ids
         """
         return self.endpoints.object.execute(entity, "read", ids, fields)
 
+    @parse_results
     def searchfields(self, entity, query, fields):
         """
         """
@@ -70,6 +81,7 @@
         else:
             return None
 
+    @parse_results
     def report(self, model, ids, report_name, form):
         # Get the model's records and context
         if not ids:

=== modified file 'oerppy/exceptions.py'
--- oerppy/exceptions.py	2011-06-02 17:25:28 +0000
+++ oerppy/exceptions.py	2011-06-02 19:03:01 +0000
@@ -1,5 +1,9 @@
 class OpenERPPyError(Exception):
-    pass
+
+    def __init__(self, *args, **kwargs):
+        super(OpenERPPyError, self).__init__(*args, **kwargs)
+        if not self.args:
+            self.args = (self.__doc__.strip(),)
 
 
 class OpenERPPyConfigError(OpenERPPyError):
@@ -8,3 +12,13 @@
 
 class OpenERPPyParameterError(OpenERPPyError):
     pass
+
+
+class OpenERPPyLoginFailure(OpenERPPyError):
+    """
+    Incorrect username or password provided.
+    """
+
+
+class OpenERPPyRemoteError(OpenERPPyError):
+    pass

=== modified file 'oerppy/reg.py'
--- oerppy/reg.py	2011-06-02 15:16:11 +0000
+++ oerppy/reg.py	2011-06-02 19:03:01 +0000
@@ -35,6 +35,7 @@
     def __init__(self, *args, **kwargs):
         super(AddOnRegistry, self).__init__(*args, **kwargs)
         self._order = []
+        self.are_instantiated = False
 
     def add(self, name, instance):
         super(AddOnRegistry, self).add(name, instance)
@@ -55,6 +56,19 @@
             addons.append(self.get_value(name))
         return addons
 
+    def instantiate(self, client):
+        """
+        This method updates the registry, replacing the addon classes with
+        actual instances.
+
+        Though intended for use by the service instance, anything that has
+        access to the client instance can use it.
+        """
+        class_pairs = zip(self._order, self.get_addons())
+        instance_pairs = [(name, klass(client)) for name, klass in class_pairs]
+        self.update(dict(instance_pairs))
+        self.are_instantiated = True
+
 
 # Default registry is for addons; we can change this later, if a general
 # registry is needed, and then make the addon registry a sub-reg of the general

=== modified file 'oerppy/service.py'
--- oerppy/service.py	2011-06-02 15:49:02 +0000
+++ oerppy/service.py	2011-06-02 19:03:01 +0000
@@ -78,8 +78,9 @@
         Get an XML-RPC proxy for the endpoint.
         """
         uri = self.get_uri()
-        if purge or not self._proxy:
-            self._proxy = xmlrpclib.ServerProxy(uri)
+        if purge or type(self._proxy) == type(None):
+            print uri
+            self._proxy = xmlrpclib.ServerProxy(uri, allow_none=1)
         return self._proxy
 
 
@@ -94,7 +95,7 @@
 
     def login(self, dbname, creds):
         """
-        Returns the UID of the user upon successfull login.
+        Returns the UID of the user upon successful login.
         """
         proxy = self.get_proxy()
         return proxy.login(dbname, creds.user, creds.password)
@@ -161,6 +162,8 @@
         self.credentials = Credentials(user, password)
         self.client = client.Client(
             self.endpoints, self.dbname, self.credentials)
+        # now that the client is defined, let's start up the addons
+        reg.registry.instantiate(self.client)
 
     def __getattr__(self, name):
         """
@@ -194,7 +197,7 @@
             try:
                 attrib = getattr(self.client, name)
             except AttributeError:
-                for addon in registry.get_addons():
+                for addon in reg.registry.get_addons():
                     try:
                         attrib = getattr(addon, name)
                     except AttributeError:
@@ -212,4 +215,6 @@
         this method is for you. Simply get the addon that you want, and call
         methods on the returned object like usual.
         """
-        return registry.get_addon(name)
+        if not reg.registry.are_instantiated:
+            return
+        return reg.registry.get_addon(name)

=== modified file 'oerppy/util.py'
--- oerppy/util.py	2011-06-01 22:52:15 +0000
+++ oerppy/util.py	2011-06-02 19:03:01 +0000
@@ -1,6 +1,7 @@
 import os
 import datetime
 from urlparse import urlparse, urlunparse
+import xmlrpclib
 
 from oerppy import const, exceptions
 
@@ -86,7 +87,7 @@
     return (str(scheme), str(host), port, str(path))
 
 
-def partial(func, *args, **keywords):
+def partial(func, *args, **kwargs):
     """
     By using partial function application, we're able "pre-process" functions
     whose first few arguments we know ahead of time.
@@ -98,15 +99,30 @@
     arguments remaining the same every time.
     """
     def newfunc(*fargs, **fkeywords):
-        newkeywords = keywords.copy()
+        newkeywords = kwargs.copy()
         newkeywords.update(fkeywords)
         return func(*(args + fargs), **newkeywords)
     newfunc.func = func
     newfunc.args = args
-    newfunc.keywords = keywords
+    newfunc.keywords = kwargs
     return newfunc
 
 
+def parse_results(func):
+    """
+    This is a decorator for methods that perform remote calls against the
+    OpenERP XML-RPC server. It provides the ability to intercept results and
+    have them parsed.
+    """
+    def checker(*args, **kwargs):
+        try:
+            results = func(*args, **kwargs)
+            return results
+        except xmlrpclib.Fault, error:
+            raise exceptions.OpenERPPyRemoteError(error.faultCode)
+    return checker
+
+
 def start_of_week(date, return_iso=False):
     # check to see if the date is in ISO date format
     if isinstance(date, basestring):