Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Bottle applications from the same source files #603

Closed
muflone opened this issue Mar 21, 2014 · 9 comments
Closed

Multiple Bottle applications from the same source files #603

muflone opened this issue Mar 21, 2014 · 9 comments

Comments

@muflone
Copy link
Contributor

muflone commented Mar 21, 2014

Hello

Is it possible to create multiple Bottle applications from the same source module?

In my case I've an application that works with a database which depends on the source URL or the request.

In main.py:

app = bottle.app()
new_app = bottle.load_app('archive.app:setup("customer1")')
app.mount('/archive/customer1', new_app)
print new_app

new_app = bottle.load_app('archive.app:setup("customer2")')
app.mount('/archive/customer2', new_app)
print new_app

new_app = bottle.load_app('archive.app:setup("customer3")')
app.mount('/archive/customer3', new_app)
print new_app

In archive/app.py:

app = bottle.app()
def setup(customer):
  """Initial setup, called during the application mount"""
  app.customer = customer
  return app

@app.route('/list')
def serve():
  return my_results()

The documentation about load_app tells that a new application is created but in my case
all the three new_app objects results to be the same (because the source files are the same) and therefore all the use of app.customer results in the same customer3 value.

Is there a way to run multiple applications based on the same source files?

@RonRothman
Copy link

Try this:

app1 = bottle.Bottle()
app2 = bottle.Bottle()  # will be distinct from app1

instead of using Bottle.app().

@avelino
Copy link
Member

avelino commented Mar 23, 2014

Bottle appliction example: https://github.com/avelino/mining/blob/master/manage.py#L37:L40

@muflone
Copy link
Contributor Author

muflone commented Mar 23, 2014

@RonRothman
no differences using bottle.Bottle() instead of bottle.app.

The problem is related to the way python handle imports. The module is loaded only once in the namespace.

@muflone
Copy link
Contributor Author

muflone commented Mar 23, 2014

@avelino thank you but that's completely unrelated to the issue, such example has a different module for each mounted application

@muflone
Copy link
Contributor Author

muflone commented Mar 23, 2014

What is needed is a way to create bottle instances from a class. While this is easy to do it's unclear to me how to handle the routes. Decorators are clearly unusable as they work at the module level and cannot access to instance objects.

Maybe adding the routes to each instance from inside the class could work.

@RonRothman
Copy link

I guess I'm misunderstanding this: "multiple Bottle applications from the same source application."

bottle.Bottle() acts differently from bottle.app(), in what I thought was precisely the way that you were concerned with:

import bottle

a = bottle.app()
b = bottle.app()

print a, b, a is b
# <bottle.Bottle object at 0x1005fd8d0> <bottle.Bottle object at 0x1005fd8d0> True

c = bottle.Bottle()
d = bottle.Bottle()
# <bottle.Bottle object at 0x1005ca550> <bottle.Bottle object at 0x101223990> False

print c, d, c is d

Note that the latter method produces multiple distinct objects (apps), whereas the former does not. Hope that helps.

@muflone
Copy link
Contributor Author

muflone commented Mar 24, 2014

@RonRothman your understanding of the issue is correct but unfortunately Bottle (or better Python in general) don't act as expected.

Please consider the following sources:
main.py

import bottle

first_app = bottle.app()
new_app1 = bottle.load_app('app:setup("customer1")')
first_app.mount('/archive/customer1', new_app1)

new_app2 = bottle.load_app('app:setup("customer2")')
first_app.mount('/archive/customer2', new_app2)

new_app3 = bottle.load_app('app:setup("customer3")')
first_app.mount('/archive/customer3', new_app3)

print new_app1, new_app1.customer
print new_app2, new_app2.customer
print new_app3, new_app3.customer

first_app.run()

app.py

import bottle

print 'module imported'
app = bottle.Bottle()

def setup(customer):
  """Initial setup, called during the application mount"""
  app.customer = customer
  return app

@app.route('/list')
def serve():
  return my_results()

Executing main.py produces the following output:

module imported
<bottle.Bottle object at 0x7f06df676f10> customer3
<bottle.Bottle object at 0x7f06df676f10> customer3
<bottle.Bottle object at 0x7f06df676f10> customer3
Bottle v0.13-dev server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8080/

This means that load_app method has loaded the module only once (only one 'module imported' line was printed) and every object returned by the function in the app module refers always to the same object as you can see from both the same objects address and the last customer assignment repetead three times.

@RonRothman
Copy link

Ah, thanks, that sample code is helpful. But I'm afraid that I see it acting precisely as expected. On the one hand, you say you want multiple (distinct) apps; but on the other hand, you're reusing the one app you've created (in app.py) instead of returning new apps.

If you want distinct apps, have setup return distinct apps:

def setup(customer):
  """Initial setup, called during the application mount"""
  app = bottle.Bottle()
  app.customer = customer

  @app.route('/list')
  def serve():
    return my_results()

  return app

Here's the output this generates:

<app.MyApp object at 0x101223ad0> customer1
<app.MyApp object at 0x1005bdb90> customer2
<app.MyApp object at 0x1005bdf90> customer3

Does that do the trick?


As an aside, you could also subclass bottle.Bottle:

import bottle

class MyApp(bottle.Bottle):
    def __init__(self, customer):
        super(MyApp, self).__init__(self)
        self.customer = customer

        @self.route('/list')
        def serve():
            return my_results()

def setup(customer):
    """Initial setup, called during the application mount"""
    return MyApp(customer)

@muflone
Copy link
Contributor Author

muflone commented Mar 28, 2014

Hi

sorry for the delay but I was a bit busy but I can now confirm you that the solution works pretty fine.
I wasn't aware that I could use the route decorators inside a class by simply putting them inside the same method before the app object is returned.

Thank you @RonRothman

Closing the bug now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants