[ADD] openerp.http reference doc
authorXavier Morel <xmo@openerp.com>
Mon, 1 Sep 2014 09:54:34 +0000 (11:54 +0200)
committerXavier Morel <xmo@openerp.com>
Mon, 1 Sep 2014 12:16:14 +0000 (14:16 +0200)
* fix some docstrings so they can be autodoc'd
* intersphinx mapping (and links to) werkzeug and python

doc/conf.py
doc/reference/http.rst
openerp/http.py
openerp/tools/func.py

index 077581d..4dafec1 100644 (file)
@@ -18,7 +18,13 @@ needs_sphinx = '1.1'
 
 # 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.todo', 'sphinx.ext.autodoc', 'odoodoc', 'patchqueue']
+extensions = [
+    'sphinx.ext.todo',
+    'sphinx.ext.autodoc',
+    'sphinx.ext.intersphinx',
+    'odoodoc',
+    'patchqueue'
+]
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
@@ -152,3 +158,7 @@ html_sidebars = {
 # base URL from which the finished HTML is served.
 #html_use_opensearch = ''
 
+intersphinx_mapping = {
+    'python': ('https://docs.python.org/2/', None),
+    'werkzeug': ('http://werkzeug.pocoo.org/docs/0.9/', None),
+}
index 6459e2b..c179691 100644 (file)
@@ -12,16 +12,72 @@ Routing
 Request
 =======
 
+The request object is automatically set on :data:`openerp.http.request` at
+the start of the request
+
+.. autoclass:: openerp.http.WebRequest
+    :members:
+    :member-order: bysource
+.. autoclass:: openerp.http.HttpRequest
+    :members:
+.. autoclass:: openerp.http.JsonRequest
+    :members:
+
 Response
 ========
 
-JSON-RPC
-========
+.. autoclass:: openerp.http.Response
+    :members:
+    :member-order: bysource
+
+    .. maybe set this to document all the fine methods on Werkzeug's Response
+       object? (it works)
+       :inherited-members:
 
 .. _reference/http/controllers:
 
-Extension: controllers
-======================
+Controllers
+===========
+
+Controllers need to provide extensibility, much like
+:class:`~openerp.models.Model`, but can't use the same mechanism as the
+pre-requisites (a database with loaded modules) may not be available yet (e.g.
+no database created, or no database selected).
+
+Controllers thus provide their own extension mechanism, separate from that of
+models:
+
+Controllers are created by :ref:`inheriting <python:tut-inheritance>` from
+
+.. autoclass:: openerp.http.Controller
+
+and defining methods decorated with :func:`~openerp.http.route`::
+
+    class MyController(openerp.http.Controller):
+        @route('/some_url', auth='public')
+        def handler(self):
+            return stuff()
+
+To *override* a controller, :ref:`inherit <python:tut-inheritance>` from its
+class and override relevant methods::
+
+    class Extension(MyController):
+        @route()
+        def handler(self):
+            do_before()
+            return super(Extension, self).handler()
+
+* decorating with :func:`~openerp.http.route` is necessary to keep the method
+  (and route) visible: if the method is redefined without decorating, it
+  will be "unpublished"
+* the decorators of all methods are combined, if the overriding method's
+  decorator has no argument all previous ones will be kept, any provided
+  argument will override previously defined ones e.g.::
+
+    class Restrict(MyController):
+        @route(auth='user')
+        def handler(self):
+            return super(Restrict, self).handler()
 
-.. this should be about inheritance/extension, do web controllers still do
-   anything else nowadays?
+  will change ``/some_url`` from public authentication to user (requiring a
+  log-in)
index 6ae15f5..9cd0bfa 100644 (file)
@@ -142,7 +142,7 @@ def redirect_with_hash(url, code=303):
     return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url
 
 class WebRequest(object):
-    """ Parent class for all OpenERP Web request types, mostly deals with
+    """ Parent class for all Odoo Web request types, mostly deals with
     initialization and setup of the request object (the dispatching itself has
     to be handled by the subclasses)
 
@@ -154,60 +154,20 @@ class WebRequest(object):
         the original :class:`werkzeug.wrappers.Request` object provided to the
         request
 
-    .. attribute:: httpsession
-
-        .. deprecated:: 8.0
-
-            Use :attr:`session` instead.
-
     .. attribute:: params
 
         :class:`~collections.Mapping` of request parameters, not generally
         useful as they're provided directly to the handler method as keyword
         arguments
-
-    .. attribute:: session_id
-
-        opaque identifier for the :class:`OpenERPSession` instance of
-        the current request
-
-    .. attribute:: session
-
-        a :class:`OpenERPSession` holding the HTTP session data for the
-        current http session
-
-    .. attribute:: context
-
-        :class:`~collections.Mapping` of context values for the current
-        request
-
-    .. attribute:: db
-
-        ``str``, the name of the database linked to the current request. Can
-        be ``None`` if the current request uses the ``none`` authentication
-        in ``web`` module's controllers.
-
-    .. attribute:: uid
-
-        ``int``, the id of the user related to the current request. Can be
-        ``None`` if the current request uses the ``none`` authentication.
-
-    .. attribute:: env
-
-        an :class:`openerp.api.Environment` bound to the current
-        request's ``cr``, ``uid`` and ``context``
     """
     def __init__(self, httprequest):
         self.httprequest = httprequest
         self.httpresponse = None
         self.httpsession = httprequest.session
-        self.session = httprequest.session
-        self.session_id = httprequest.session.sid
         self.disable_db = False
         self.uid = None
         self.endpoint = None
         self.auth_method = None
-        self._cr_cm = None
         self._cr = None
 
         # prevents transaction commit, use when you catch an exception during handling
@@ -219,44 +179,49 @@ class WebRequest(object):
             threading.current_thread().dbname = self.db
         if self.session.uid:
             threading.current_thread().uid = self.session.uid
-        self.context = dict(self.session.context)
-        self.lang = self.context["lang"]
 
-    @property
-    def registry(self):
+    @lazy_property
+    def env(self):
         """
-        The registry to the database linked to this request. Can be ``None``
-        if the current request uses the ``none`` authentication.
+        The :class:`~openerp.api.Environment` bound to current request.
         """
-        return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
+        return openerp.api.Environment(self.cr, self.uid, self.context)
 
-    @property
-    def db(self):
+    @lazy_property
+    def context(self):
         """
-        The database linked to this request. Can be ``None``
-        if the current request uses the ``none`` authentication.
+        :class:`~collections.Mapping` of context values for the current
+        request
         """
-        return self.session.db if not self.disable_db else None
+        return dict(self.session.context)
+
+    @lazy_property
+    def lang(self):
+        return self.context["lang"]
+
+    @lazy_property
+    def session(self):
+        """
+        a :class:`OpenERPSession` holding the HTTP session data for the
+        current http session
+        """
+        return self.httprequest.session
 
     @property
     def cr(self):
         """
-        The cursor initialized for the current method call. If the current
-        request uses the ``none`` authentication trying to access this
-        property will raise an exception.
+        :class:`~openerp.sql_db.Cursor` initialized for the current method
+        call.
+
+        Accessing the cursor when the current request uses the ``none``
+        authentication will raise an exception.
         """
-        # some magic to lazy create the cr
+        # can not be a lazy_property because manual rollback in _call_function
+        # if already set (?)
         if not self._cr:
             self._cr = self.registry.cursor()
         return self._cr
 
-    @lazy_property
-    def env(self):
-        """
-        The Environment bound to current request.
-        """
-        return openerp.api.Environment(self.cr, self.uid, self.context)
-
     def __enter__(self):
         _request_stack.push(self)
         return self
@@ -316,6 +281,8 @@ class WebRequest(object):
 
     @property
     def debug(self):
+        """ Indicates whether the current request is in "debug" mode
+        """
         return 'debug' in self.httprequest.args
 
     @contextlib.contextmanager
@@ -323,6 +290,48 @@ class WebRequest(object):
         warnings.warn('please use request.registry and request.cr directly', DeprecationWarning)
         yield (self.registry, self.cr)
 
+    @lazy_property
+    def session_id(self):
+        """
+        opaque identifier for the :class:`OpenERPSession` instance of
+        the current request
+
+        .. deprecated:: 8.0
+
+            Use the ``id`` attribute on :attr:`.session`
+        """
+        return self.session.id
+
+    @property
+    def registry(self):
+        """
+        The registry to the database linked to this request. Can be ``None``
+        if the current request uses the ``none`` authentication.
+
+        .. deprecated:: 8.0
+
+            use :attr:`.env`
+        """
+        return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
+
+    @property
+    def db(self):
+        """
+        The database linked to this request. Can be ``None``
+        if the current request uses the ``none`` authentication.
+        """
+        return self.session.db if not self.disable_db else None
+
+    @lazy_property
+    def httpsession(self):
+        """ HTTP session data
+
+        .. deprecated:: 8.0
+
+            Use :attr:`.session` instead.
+        """
+        return self.session
+
 def route(route=None, **kw):
     """
     Decorator marking the decorated method as being a handler for
@@ -378,7 +387,15 @@ def route(route=None, **kw):
     return decorator
 
 class JsonRequest(WebRequest):
-    """ JSON-RPC2 over HTTP.
+    """ Request handler for `JSON-RPC 2
+    <http://www.jsonrpc.org/specification>`_ over HTTP
+
+    * ``method`` is ignored
+    * ``params`` must be a JSON object (not an array) and is passed as keyword
+      arguments to the handler method
+    * the handler method's result is returned as JSON-RPC ``result`` and
+      wrapped in the `JSON-RPC Response
+      <http://www.jsonrpc.org/specification#response_object>`_
 
     Sucessful request::
 
@@ -490,8 +507,6 @@ class JsonRequest(WebRequest):
             return self._json_response(error=error)
 
     def dispatch(self):
-        """ Calls the method asked for by the JSON-RPC2 or JSONP request
-        """
         if self.jsonp_handler:
             return self.jsonp_handler()
         try:
@@ -541,7 +556,23 @@ def jsonrequest(f):
     return route([base, base + "/<path:_ignored_path>"], type="json", auth="user", combine=True)(f)
 
 class HttpRequest(WebRequest):
-    """ Regular GET/POST request
+    """ Handler for the ``http`` request type.
+
+    matched routing parameters, query string parameters, form_ parameters
+    and files are passed to the handler method as keyword arguments.
+
+    In case of name conflict, routing parameters have priority.
+
+    The handler method's result can be:
+
+    * a falsy value, in which case the HTTP response will be an
+      `HTTP 204`_ (No Content)
+    * a werkzeug Response object, which is returned as-is
+    * a ``str`` or ``unicode``, will be wrapped in a Response object and
+      interpreted as HTML
+
+    .. _form: http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
+    .. _HTTP 204: http://tools.ietf.org/html/rfc7231#section-6.3.5
     """
     _request_type = "http"
 
@@ -596,7 +627,7 @@ class HttpRequest(WebRequest):
         return response
 
     def render(self, template, qcontext=None, lazy=True, **kw):
-        """ Lazy render of QWeb template.
+        """ Lazy render of a QWeb template.
 
         The actual rendering of the given template will occur at then end of
         the dispatching. Meanwhile, the template and/or qcontext can be
@@ -604,7 +635,9 @@ class HttpRequest(WebRequest):
 
         :param basestring template: template to render
         :param dict qcontext: Rendering context to use
-        :param dict lazy: Lazy rendering is processed later in wsgi response layer (default True)
+        :param bool lazy: whether the template rendering should be deferred
+                          until the last possible moment
+        :param kw: forwarded to werkzeug's Response object
         """
         response = Response(template=template, qcontext=qcontext, **kw)
         if not lazy:
@@ -612,7 +645,9 @@ class HttpRequest(WebRequest):
         return response
 
     def not_found(self, description=None):
-        """ Helper for 404 response, return its result from the method
+        """ Shortcut for a `HTTP 404
+        <http://tools.ietf.org/html/rfc7231#section-6.5.4>`_ (Not Found)
+        response
         """
         return werkzeug.exceptions.NotFound(description)
 
@@ -1073,13 +1108,20 @@ class Retry(RuntimeError):
 class Response(werkzeug.wrappers.Response):
     """ Response object passed through controller route chain.
 
-    In addition to the werkzeug.wrappers.Response parameters, this
-    classe's constructor can take the following additional parameters
+    In addition to the :class:`werkzeug.wrappers.Response` parameters, this
+    class's constructor can take the following additional parameters
     for QWeb Lazy Rendering.
 
     :param basestring template: template to render
     :param dict qcontext: Rendering context to use
-    :param int uid: User id to use for the ir.ui.view render call
+    :param int uid: User id to use for the ir.ui.view render call,
+                    ``None`` to use the request's user (the default)
+
+    these attributes are available as parameters on the Response object and
+    can be altered at any time before rendering
+
+    Also exposes all the attributes and methods of
+    :class:`werkzeug.wrappers.Response`.
     """
     default_mimetype = 'text/html'
     def __init__(self, *args, **kw):
@@ -1108,6 +1150,8 @@ class Response(werkzeug.wrappers.Response):
         return self.template is not None
 
     def render(self):
+        """ Renders the Response's template, returns the result
+        """
         view_obj = request.registry["ir.ui.view"]
         uid = self.uid or request.uid or openerp.SUPERUSER_ID
         while True:
@@ -1119,6 +1163,9 @@ class Response(werkzeug.wrappers.Response):
                 self.qcontext.update(e.updates)
 
     def flatten(self):
+        """ Forces the rendering of the response's template, sets the result
+        as response body and unsets :attr:`.template`
+        """
         self.response.append(self.render())
         self.template = None
 
index 7d04c62..baeae0b 100644 (file)
@@ -42,6 +42,10 @@ class lazy_property(object):
         setattr(obj, self.fget.__name__, value)
         return value
 
+    @property
+    def __doc__(self):
+        return self.fget.__doc__
+
     @staticmethod
     def reset_all(obj):
         """ Reset all lazy properties on the instance `obj`. """