Hamid Elaosta <
alias....@gmail.com> writes:
(...)
> +-API/+
> +__init__.py
> |
> +-v1/+
> | |
> | +EndpointSetupDatabase.py
> | +EndpointDoSomeWork.py
> | +Endpoint1.py
> | +Endpoint2.py
> | +v1config.conf
(...)
> So, my v1 API should have, in its config the database filename, lets say
> for an SQlite DB, that I want to be a global variable within each of the
> endpoints that they can access to connect to the DB.
I guess what's still not clear to me is when - and more importantly
how - your objects are being called from outside of a CherryPy request
cycle.
> At the moment, they're all anonymously mounted in __init_.py or somewhere:
>
> cherrypy.tree.mount(Endpoint1.EndPoint1(),"/endpoint1","v1config.conf")
> cherrypy.tree.mount(Endpoint2.Endpoint2(),"/endpoint2", "v1config.conf")
> etc
For completeness, while you are handing these instances off to
CherryPy here and not maintaining any references yourself, they're
still easy to locate, as cherrypy.tree.apps maintains the mapping
between endpoint and Application instance (that CherryPy creates) and
your endpoints.
So after those two calls, the cherrypy.tree.apps dictionary will have
keys at /endpoint1 and /endpoint2, each of which points to an
Application object instance where the root attribute is your endpoint
instance.
(You can also choose to mount already created Application instances)
>
> Perhaps I should be holding on to references of Endpoint1.Endpoint1() in
> __init__ and importing v1, v2, etc.
(...)
Well, if you're not already holding such references, I'm unclear how
anything other than CherryPy is calling those endpoints? As written
above, the only way that I can see to get into that object instance is
through CherryPy as part of a request, so
cherrypy.request.app should
always be valid.
> If I wanted to mount a specific
> endpoint with its own config file though, I don't see a way to reference
> the configs from inside that endpoint without accessing the parent and
> asking it for my own config, or I have to set the app on the class after
> mounting?
I'm not positive what "parent" references here, but yes, I see no way
around tracking your own reference to the application object - or its
configuration - if you don't want to depend on a cherrypy reference
(either in cherrypy.request or cherrypy.tree). Whether you store your
own reference within your endpoints or external and pass it to the
endpoints when needed is a design question.
So yes, assuming some other code gets references to those EndPoint1/2
instances, an alternate means is needed to find the enclosing
application. I can think of several ways to do it, such as:
1. Call some sort of setup method or set an attribute on the endpoint
to configuration an application reference at start up after which
the endpoint instance will always have its own reference. This is
the "set the app on the class" you suggest above.
2. Make the endpoint objects themselves Application subclasses in which
case the config dictionary will be an instance attribute "for free".
3. Having the calling code store and then pass the application object
(or whatever specific configuration data is needed) into the
methods called on the end point. This can be done either by
tracking the applications at setup, or as previously discussed,
using cherrypy.tree to locate the endpoint, and in turn the
application at call time.
4. Pull your endpoint configuration outside of CherryPy and just make
it part of the endpoint object construction itself. E.g., if the
API is the only consumer of the database configuration, it's not
clear it must be in the CherryPy configuration to start with.
Any of these can work (and probably other options as well).
There's so many permutations of how one might handle this at a design
level that I'm not really sure even how to start. But here's one
possible sample of the first option, configuring the endpoints with
their resulting application after mounting. This keeps application
setup close to the CherryPy setup, but afterwards it's just endpoint
instance data so doesn't need to be tracked by other callers. I've
included some debugging prints so you can see the configuration
dictionaries at various points.
Given the following module:
- - - - - - - - - - - - - - - - - - - - - - - - -
import cherrypy
import time
def dump_cpy_configs():
print 'CherryPy Configs:'
print ' Global:', cherrypy.config
print ' App:', cherrypy.request.app.config
print ' Request:', cherrypy.request.config
class Nested(object):
@cherrypy.expose
def entry(self):
print '[Nested.entry]'
dump_cpy_configs()
def tick(self, app):
print '[Nested.tick]'
print ' Global:', cherrypy.config
print ' App:', app.config
class EndPoint(object):
app = None
def __init__(self, name):
self.name = name
self.nested = Nested()
@cherrypy.expose
def index(self):
print '[%s.index]' %
self.name
dump_cpy_configs()
def tick(self):
print '[%s.tick]' %
self.name
# cherrypy.request is not valid here
print ' Global:', cherrypy.config
print ' App:', self.app.config
self.nested.tick(
self.app)
if __name__ == "__main__":
cherrypy.config.update('global.conf')
cherrypy.tree.mount(EndPoint('EndPoint1'), '/endpoint1', 'v1config.conf')
cherrypy.tree.mount(EndPoint('EndPoint2'), '/endpoint2', 'v1config.conf')
# Configure each endpoint with its application
for app in cherrypy.tree.apps.values():
app.root.app = app
# Call endpoints outside of CherryPy request
last_tick = time.time()
def tick():
global last_tick
# Execute application ticks at 5s interval
cur_tick = time.time()
if cur_tick - last_tick > 5:
print 'TICK:', time.ctime(cur_tick)
last_tick = cur_tick
for app in cherrypy.tree.apps.values():
app.root.tick()
cherrypy.engine.subscribe('main', tick)
# CherryPy mainloop
cherrypy.engine.start()
cherrypy.engine.block()
- - - - - - - - - - - - - - - - - - - - - - - - -
and the following global configuration file global.conf:
- - - - - - - - - - - - - - - - - - - - - - - - -
[global]
server.socket_port = 9000
global_setting = 'global'
- - - - - - - - - - - - - - - - - - - - - - - - -
and the following application configuration file v1config.conf:
- - - - - - - - - - - - - - - - - - - - - - - - -
[database]
host='somehost'
port=5432
[/]
app_setting='app value'
[/nested]
nested_setting='value'
- - - - - - - - - - - - - - - - - - - - - - - - -
When run, this sets up URLs for:
http://localhost:9000/endpoint1/
http://localhost:9000/endpoint1/nested/entry/
http://localhost:9000/endpoint2/
http://localhost:9000/endpoint2/nested/entry/
where the /endpoint{1,2} trees are handled by independent EndPoint
instances, each with their own Nested instances.
In terms of configuration, the [global] stanza from global.conf will
always be available in cherrypy.config. (You could keep the global
stanza in the same v1config.conf if you wanted). All stanzas from
v1config.conf will be available in your application configuration
(cherrypy.app.config), while the URL stanzas (/ and /nested) will be
in the request config (cherrypy.request.config) merged with the global
config when serving a request beneath those URLs.
Since I don't know how you expect to call your endpoints independent
of CherryPy I just used an engine subscription to call a non-published
method every 5 seconds. I also opted to take advantage of the
CherryPy tree to get access to all of the applications/endpoints both
when configuring them with their own application reference and later
to call the tick methods.
If you retrieve one of the URLs above, the appropriate published
method will run, using cherrypy.request to get to the application and
configuration.
In the background, the tick entry point will be called every 5s with
access to the global configuration (since, well, it's global and works
everywhere) but using its own application reference for the
application configuration. You might also choose to configure your
endpoints with the app.config dictionary directly rather than having
to reference the application object at all.
From within a non-CherryPy entry point, if a nested object is being
called, the application needs to be supplied (such as I do here), or
you could have the endpoints configure the nested objects just as they
themselves are being configured. Sort of depends just what
configuration information the nested objects need.
Note that I believe referencing cherrypy.request from within tick()
will still technically have a value, but it'll point to an arbitrary
application.
To modify this for option 2, where the endpoint itself is also the
application object just means changing the EndPoint class to be an
Application sub-class and then to instantiate it differently (since
the application class is supplied with its mount point). So for
example:
- - - - - - - - - - - - - - - - - - - - - - - - -
# Application version of EndPoint
class EndPointApp(cherrypy.Application):
def __init__(self, name, script_name, config):
# Supply myself as the root object
cherrypy.Application.__init__(self, self, script_name, config)
self.name = name
self.nested = Nested()
@cherrypy.expose
def index(self):
print '[%s.index]' %
self.name
dump_cpy_configs()
def tick(self):
print '[%s.tick]' %
self.name
# cherrypy.request is not valid here
print ' Global:', cherrypy.config
print ' App:', self.config
self.nested.tick(self)
# Replace existing mount, and endpoint configuration with:
cherrypy.tree.mount(EndPointApp('EndPoint1', '/endpoint1', 'v1config.conf'))
cherrypy.tree.mount(EndPointApp('EndPoint2', '/endpoint2', 'v1config.conf'))
- - - - - - - - - - - - - - - - - - - - - - - - -
In this case the application configuration becomes self.config in the
endpoint instance since it itself is also the application. Of course,
this approach requires you be willing to have the endpoint be an
application-subclass (as opposed to the default behavior of being
referenced by a separate Application instance).
Hopefully this will at least give you some thoughts on how to approach
things or some code to play with further.
-- David