The shared functionality involves DB access, so as far as I can tell it cannot be abstracted into modules that would then be imported with a normal import.
You can use the DAL from modules as well. As Niphlod suggested, you can also use the scheduler to schedule and run a task in the context of another app, though that might not be as fast as you'd like, as even setting immediate=True, it could take up to "heartbeat" seconds for the worker to pick up and execute the task, and then you have to check for the results.
I can use the DAL, but can I use the "db" object defined in my models file? Or do you mean I could use the DAL if I rewrote my other app to use a "model-less" approach where the models were defined in some other manner?
import os
from gluon import *
def dal(**kwargs):
web2py_folder = current.request.env.applications_parent
db_folder = os.path.abspath(os.path.join(web2py_folder, 'applications',
'app1', 'databases'))
return DAL('sqlite://storage.sqlite', folder=db_folder, **kwargs)
def define_tables(db):
db.define_table('table1', Field('field1'))
from applications.app1.modules.db import dal, define_tables
app1_db = dal()
define_tables(app1_db)
# Now use app1_db as usual.
You can use the DAL from modules as well. As Niphlod suggested, you can also use the scheduler to schedule and run a task in the context of another app, though that might not be as fast as you'd like, as even setting immediate=True, it could take up to "heartbeat" seconds for the worker to pick up and execute the task, and then you have to check for the results.
I can use the DAL, but can I use the "db" object defined in my models file? Or do you mean I could use the DAL if I rewrote my other app to use a "model-less" approach where the models were defined in some other manner?
Yes, the point was that you can put whatever you want into modules, which means the code can be shared across apps. If you want to share the code to create a database connection object, then put that code in a module. Here is the basic idea:
/app1/modules/db.py:
import os
from gluon import *
def dal(**kwargs):
web2py_folder =db_folder =
current.request.env.applications_parent
os.path.abspath(os.path.join(web2py_folder, 'applications',
return DAL('sqlite://storage.sqlite', folder=db_folder, **kwargs)
'app1', 'databases'))
def define_tables(db):
db.define_table('table1', Field('field1'))
/app2/models/db.py:
from applications.app1.modules.db import dal, define_tables
app1_db = dal()
define_tables(app1_db)
# Now use app1_db as usual.
Of course, the module defining the database connection and tables doesn't have to be in the /modules folder of any particular application, nor does the folder specified as the "folder" argument -- that can all be centralized somewhere if desired.
In your example above, it's one model defining tables from another model. But in my case, what I have is a controller function that needs access to the db, which is defined in the model file. But I found the hard way (and later also from an old thread: https://groups.google.com/forum/#!topic/web2py-developers/5TPI9oYOZHM ) that exec_environment does not load models. So this puts me kind of back to where I was.
The simplest solution I found was to put, in the controller file, code that looks like this:
from applications.otherapp.modules.db_definer
db = db_definer.define_db()
Then every time my controller is run, it will redefine the DB, using the code set up in the other app's module to do this in a consistent way (along the lines you suggested).
What I don't like about this is that it means the DB-setup code is run multiple times on a "normal" request. That is, if I have code to set up the DB in my model file, and I visit my db-accessing controller, it runs the code in the model file to set up the DB, then runs the code again when it runs the controller file. I imagine this is not a huge penalty but it is annoying.
db = DAL(...)
def define_tables(db):
db.define_table('my_app2_table', ...)
from app2.modules.db_definer import define_tables
def myfunc():
define_tables(db)
...
I do have a couple questions about this: First, am I right that the performance impact of re-running the DAL object creation is negligible? Is it something I should be concerned about?
Second, is there anything wrong with using request.requires_https() in the top-level code of a model file?
The problem is that I don't want these internal cross-app calls to be considered "requests"; I just want them to be calls to controller functions, so anything like requires_https should be ignored (or just proxied to the "real" request which is making this sub-call to another app).
The upshot of all this appears to be that there is no real way to get web2py to give me access to the model/controller combination without an HTTP request. In web2py the models and controllers are tightly coupled to the HTTP request. I find this somewhat irritating, as to my mind in an MVC framework the models and controllers shouldn't be so closely tied to the transport mechanism. That is, I should be able to say "use this model and this controller and give me the data", without having to involve HTTP at all. It seems that, in web2py this coupling is encouraged because controller functions directly access objects like request and response to get their arguments, rather than having a separate "routing" mechanism that maps HTTP request parameters to Python functions. (This is what the "Service" function does, but it requires a level of indirection with a "call" entry point.)
In essence, the way I think of it, when a request comes in, it is routed to a controller function. That controller then runs along with its associated models. and the controller function is called. It returns some data, which is passed to a view. What I would like is the ability to do just the middle part of that: take a controller, load its models, and run a function. No HTTP. No request. No response. No network. No view. No nothing. Just the controller and the model. For now I have a way to do it, but it involves duplicating the model-controller linkage that web2py already does (by "manually" recreating the DAL object from within the controller).
In your example above, it's one model defining tables from another model. But in my case, what I have is a controller function that needs access to the db, which is defined in the model file. But I found the hard way (and later also from an old thread: https://groups.google.com/forum/#!topic/web2py-developers/5TPI9oYOZHM ) that exec_environment does not load models. So this puts me kind of back to where I was.
The simplest solution I found was to put, in the controller file, code that looks like this:
from applications.otherapp.modules.db_definer
db = db_definer.define_db()
Then every time my controller is run, it will redefine the DB, using the code set up in the other app's module to do this in a consistent way (along the lines you suggested).
What I don't like about this is that it means the DB-setup code is run multiple times on a "normal" request. That is, if I have code to set up the DB in my model file, and I visit my db-accessing controller, it runs the code in the model file to set up the DB, then runs the code again when it runs the controller file. I imagine this is not a huge penalty but it is annoying.
So, then don't do it that way. You have two options. First, if you have a controller that doesn't need any of the model definitions from your application's model files, then you should probably re-architect your application so only the models that are needed get defined when they are needed (either using the conditional models functionality or by defining all models in modules that are imported where needed).
The other option is simply to avoid re-running the DB setup code and instead use the already defined db object, passing it to the module code from the other app:
app1/models/db.py:
db = DAL(...)
app2/modules/db_definer.py:
def define_tables(db):
db.define_table('my_app2_table', ...)
app1/controllers/default.py:
from app2.modules.db_definer import define_tables
def myfunc():
define_tables(db)
...
The above assumes both apps are using the same database, but if they are not, then there is no "re-running the DB setup code" anyway, as you would be dealing with two different databases, which would each need their own setup code.
The problem is that I don't want these internal cross-app calls to be considered "requests"; I just want them to be calls to controller functions, so anything like requires_https should be ignored (or just proxied to the "real" request which is making this sub-call to another app).
It should only take effect for HTTP requests, not executions via the shell, scheduler, or cron.
The upshot of all this appears to be that there is no real way to get web2py to give me access to the model/controller combination without an HTTP request. In web2py the models and controllers are tightly coupled to the HTTP request. I find this somewhat irritating, as to my mind in an MVC framework the models and controllers shouldn't be so closely tied to the transport mechanism. That is, I should be able to say "use this model and this controller and give me the data", without having to involve HTTP at all. It seems that, in web2py this coupling is encouraged because controller functions directly access objects like request and response to get their arguments, rather than having a separate "routing" mechanism that maps HTTP request parameters to Python functions. (This is what the "Service" function does, but it requires a level of indirection with a "call" entry point.)In essence, the way I think of it, when a request comes in, it is routed to a controller function. That controller then runs along with its associated models. and the controller function is called. It returns some data, which is passed to a view. What I would like is the ability to do just the middle part of that: take a controller, load its models, and run a function. No HTTP. No request. No response. No network. No view. No nothing. Just the controller and the model. For now I have a way to do it, but it involves duplicating the model-controller linkage that web2py already does (by "manually" recreating the DAL object from within the controller).
If you need to share code across applications, I suggest you put that code into modules and then import in whatever applications need the functionality. Of course, you can also make internal HTTP requests from one application to another (or use an RPC mechanism).
What I don't like about this is that it means the DB-setup code is run multiple times on a "normal" request. That is, if I have code to set up the DB in my model file, and I visit my db-accessing controller, it runs the code in the model file to set up the DB, then runs the code again when it runs the controller file. I imagine this is not a huge penalty but it is annoying.
So, then don't do it that way. You have two options. First, if you have a controller that doesn't need any of the model definitions from your application's model files, then you should probably re-architect your application so only the models that are needed get defined when they are needed (either using the conditional models functionality or by defining all models in modules that are imported where needed).
Not sure what you mean there about the models. What you describe is the opposite of my situation. My controller does need the models. That is exactly the problem, because I want to call that controller from another controller, but to do so I need the model files to be run.
The other option is simply to avoid re-running the DB setup code and instead use the already defined db object, passing it to the module code from the other app:
app1/models/db.py:
db = DAL(...)
app2/modules/db_definer.py:
def define_tables(db):
db.define_table('my_app2_table', ...)
app1/controllers/default.py:
from app2.modules.db_definer import define_tables
def myfunc():
define_tables(db)
...
The above assumes both apps are using the same database, but if they are not, then there is no "re-running the DB setup code" anyway, as you would be dealing with two different databases, which would each need their own setup code.
What already-defined DB object?
from applications.otherapp.modules.db_definer
db = db_definer.define_db()
If I use exec_environment, model files aren't run, so there is no db object.
Anyway, your response makes me think maybe I haven't explained my situation correctly. What I have this: controllerA.py and controllerB both needs the model in modelA.py controllerB wants to call a function in controllerA. What I want is a way, from within controller B, to say "run controllerA, making sure all the models it needs are available to it, and then give me controllerA as a module object (or something equivalent), so that I can call controllerA.some_function() and get the results". All without any additional network requests. (In fact, some_function is a service, so I will pass arguments to it.).
If you need to share code across applications, I suggest you put that code into modules and then import in whatever applications need the functionality. Of course, you can also make internal HTTP requests from one application to another (or use an RPC mechanism).
How would such code access the DB? By putting the define_tables-type code at the module top-level? Or by putting it in a function and having every db-accessing function call "db = get_DB()" at the beginning?
That seems similar to what I'm doing, except that the code is in a controller instead of a module. It seems the bottom line is that there is no way for a controller to internally specify a "dependency" on particular models.
The normal web2py mechanism will run the right model files on a regular HTTP request (based on the naming conventions), but if you want to call the controller file some other way, it will have to run the model-creation code itself, even if that code has already been run in another controller. There isn't a way to say "if such-and-such tables have already been created, give me the existing db object; otherwise, create them now". Is that right?
The simplest approach is to put shared functionality into modules. Of course, if you don't know in what context a given bit of code will run, you can easily check whether particular tables have been defined and then call the table defining code if necessary.
Maybe it would help to show some actual code from two apps that you would like to share.
# db_worker.py
def call():
return service()
@service.json
def work_service(a, b=2):
return a, b, str(db)
def work_plain():
return str(db)
# boss.py
import gluon.shell
def order_service():
othermod = gluon.shell.exec_environment('applications/test_env/controllers/db_worker.py')
return "Hello", othermod.work(a=1, b=2)
def order_plain():
othermod = gluon.shell.exec_environment('applications/test_env/controllers/db_worker.py')
return "Hello", othermod.work_plain()
If I visit /test_env/db_worker/call/json/work_service?a=1&b=2, I get the right result. What I want is to be able to visit /test_env/boss/order_service and get the exact same result. Right now, I get "name 'service' is not defined" (because db.py is not run, and "service" is defined there).
Now, as near as I can tell, everything I've done above in db_worker.py is straight out of the book as basic web2py stuff. But what you seem to be saying is that, if I want to make "order" work, I have to significantly change the way I've written db.py and worker.py. Namely, I have to abandon the idea of the normal execution framework whereby model files are automatically executed and dump their contents into the namespace of controllers, and instead I have to factor out the db-creation code into a separate module, and then, instead of having it automatically executed and provided to the db_worker controller, I have to manually import and run that db-creation code from within db_worker.py. Is that correct?
If so, I understand what you're saying, but I guess it seems a bit perverse. Web2py has all this nice magic for automatically deciding which model files to run, running them, and automatically providing the names they define to the controller files. It's great and makes things very convenient and reduces boilerplate. But that all has to go out the window for one controller to access another (even within the same app)?
Is there no way to say "do everything just as you would if a request came in for it -- that is, run the model files and the controller file -- but just don't actually create a request or call any of the controller functions yet, just return me an object representing the controller file"?
Do you have a use case that cannot easily be accommodated by that pattern? If so, what do you imagine would be the API for doing what you want to do, and would it look much different from simply making an HTTP call (or possibly an RPC call) to the other app (which you can already do)?
def order_plain():
othermod = gluon.shell.exec_environment('applications/test_env/controllers/db_worker.py')
return "Hello", othermod.work_plain()
def order_plain():
work_plain_response = gluon.tools.fetch(URL('db_worker', 'work_plain', host='localhost'))
return "Hello %s" % work_plain_response
Anthonydef order_plain():
work_plain_response = LOAD('db_worker', 'work_plain')
return "Hello %s" % work_plain_response
The workflow of an HTTP request involves things like setting up the session, and some of the code (particularly controller code) may depend on attributes of the request object. If you want to make what amounts to an internal request to another app or controller, it will not be clear what elements of the current request to retain (e.g., should the current session, request headers, query string, etc. be replicated in the second call?).
Because work_service is defined as a JSON-RPC service, the proper way to call it would be via an RPC call, as explained here: http://web2py.com/books/default/chapter/29/10/services#Accessing-JSONRPC-services-from-web2py. Otherwise, there is not much point in making it an RPC service.
If I understand right, the second one generates an HTTP request. That means the data that is fetched will be serialized and deserialized. In some cases this data is sizable, so I want to avoid that overhead. There will likely also be some overhead associated with actually sending/receiving the network request; this is probably smaller but I'd still rather avoid it. As I understand it, these operations will also happen with an RPC call. That's why I keep saying that I don't want an HTTP request. I want everything to happen totally internally, within the same request, same process, same everything, nothing going over the wire.
Because work_service is defined as a JSON-RPC service, the proper way to call it would be via an RPC call, as explained here: http://web2py.com/books/default/chapter/29/10/services#Accessing-JSONRPC-services-from-web2py. Otherwise, there is not much point in making it an RPC service.
It's not an RPC service per se. The decorator is @service.json, not @service.jsonrpc.
That is also why it's not purely a matter of "common functionality that is shared across apps". Rather, there is functionality that I want to be accessible BOTH as a "real" URL endpoint AND internally between apps --- but without the request overhead in the latter case.
So I do want a controller that exposes the function. (I could of course write a "dummy" controller function that just calls a "worker" function in a module, but that is essentially what Service already gives me.)
from gluon import current
def myfunc():
return dict(message=current.request.vars.message)
import mycontroller
def index():
return getattr(mycontroller, request.args(0))()