Accessing the API from Python modules

Your models or controller may import Python modules. These are usually Python files you store in the modules directory of your app. They may need to use some of the web2py API. The way to do it is by importing them:

  1. from gluon import ...

In fact, any Python module, even if not imported by a web2py application, can import the web2py API as long as web2py is in the sys.path.

Sharing the global scope with modules using the current object

There is one caveat, though. Web2py defines some global objects (request, response, session, cache, T) that can only exist when an HTTP request is present (or is faked). Therefore, modules can access them only if they are called from an application. For this reasons they are placed into a container called current, which is a thread local object. Here is an example.

Create a module “/myapp/modules/mytest.py” that contains:

  1. from gluon import current
  2. def ip(): return current.request.client

Now from a controller in “myapp” you can do

  1. import mytest
  2. def index():
  3. return "Your ip is " + mytest.ip()

Notice a few things:

  • import mytest looks for the module first in the current app’s modules folder, then in the folders listed in sys.path. Therefore, app-level modules always take precedence over Python modules. This allows different apps to ship with different versions of their modules, without conflicts.
  • Different users can call the same action index concurrently, which calls the function in the module, and yet there is no conflict because current.request is a different object in different threads. Just be careful not to access current.request outside of functions or classes (i.e., at the top level) in the module.
  • import mytest is a shortcut for from applications.appname.modules import mytest. Using the longer syntax, it is possible to import modules from other applications.

For uniformity with normal Python behavior, by default web2py does not reload modules when changes are made. Yet this can be changed. To turn on the auto-reload feature for modules, use the track_changes function as follows (typically in a model file, before any imports):

  1. from gluon.custom_import import track_changes; track_changes(True)

From now on, every time a module is imported, the importer will check if the Python source file (.py) has changed. If it has changed, the module will be reloaded.

Do not call track_changes in the modules themselves.

Track changes only tracks changes for modules that are stored in the application. Modules that import current can access:

  • current.request
  • current.response
  • current.session
  • current.cache
  • current.T

and any other variable your application chooses to store in current. For example a model could do

  1. auth = Auth(db)
  2. from gluon import current
  3. current.auth = auth
  4. current.db = db #not needed in this case but useful

and now all modules imported can access current.auth.

current and import create a powerful mechanism to build extensible and reusable modules for your applications.

Warning! Do not use the current object in global scope in a module

Beware! Given from gluon import current, it is correct to use current.request and any of the other thread local objects but one should never assign them to global variables in the module, such as in

  1. request = current.request # WRONG! DANGER!

nor should one use current to assign class attributes:

  1. class MyClass:
  2. request = current.request # WRONG! DANGER!

This is because the thread local object must be extracted at runtime. Global variables instead are defined only once when the model is imported for the first time.

Instead, assign inside a function.

  1. from gluon import current
  2. ...
  3. def a_module_function():
  4. db = current.db # assuming you assigned current.db = db in the model db.py
  5. ...

Another caveat has to do with cache. You cannot use the cache object to decorate functions in modules, that is because it would not behave as expected. In order to cache a function f in a module you must use lazy_cache:

  1. from gluon.cache import lazy_cache
  2. @lazy_cache('key', time_expire=60, cache_model='ram')
  3. def f(a, b, c): ....

Mind that the key is user defined but must be uniquely associated to the function. If omitted web2py will automatically determine a key.