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

Why should bottlepy stick to a file? #1158

Open
52lemon opened this issue Aug 29, 2019 · 23 comments
Open

Why should bottlepy stick to a file? #1158

52lemon opened this issue Aug 29, 2019 · 23 comments
Labels
F.A.Q. Not a bug, but still kept open for visibility

Comments

@52lemon
Copy link

52lemon commented Aug 29, 2019

Why should bottlepy stick to a file?

@agalera
Copy link
Contributor

agalera commented Aug 29, 2019

it's a design decision, and you can use it simply by copying bottle.py to your directory

@defnull I think you can respond better to this.

@52lemon
Copy link
Author

52lemon commented Aug 30, 2019

This way is not very friendly for multi-person development, isn't it? I think this is why the development progress is so slow.

@JonathanHuot
Copy link
Contributor

This is part of the bottle's DNA.. see the project's headline: "It is distributed as a single file module and has no dependencies other than the Python Standard Library."

In our company, it is why we have decided to select bottle over flask & others, it is so simple to have a simple look on the single file and understand the code & have an overview of everything. No dependencies, no other files, everything is included into a single location.

Moving to multiple files is another project IMHO.

@defnull
Copy link
Member

defnull commented Aug 30, 2019

Yes, the "single file, no dependencies" approach is one of the core strengths of bottle compared to other frameworks. Bottle is often used to quickly REST-ify existing tools or implement small APIs for strictly internal services, even on embedded devices where a virtualenv would be too much of a hassle sometimes.

But there are more reasons:

  • The single-file restriction is an effective save-guard against feature creep. It helps keeping bottle small, lean, fast and easy to understand. We are able reject pull requests simply because they would add too many lines or add too much complexity, and that's nice.
  • A single file is easier to debug and understand. If something is not documented in detail or if you have a question on how bottle handles a specific situation, just have a look and read the code. You can read the entirety of bottle.py in an hour or so. Most of it is pretty easy to understand. Try that with flask/pyramid/django, where everything is buried under multiple layers of abstractions and imports.
  • And last but not least: Bottle is not flask, and that is a good thing. If you want it to be more like flask, then use flask.

For anyone interested in a bit of (biased) history: Bottle was first. It was inspired by an even smaller micro-framework called itty, which in turn was inspired by rubys sinatra. Armin was annoyed that I implemented everything myself and did not use his library werkzeug (or webob), so he published a clone of bottle with werkzeug bundled within a single file as an "april fools joke". He basically mocked the single-file-no-dependency approach I took for bottle. The "joke" took off, and he realized that there is actually a real demand for a web-framework just like bottle, so he started flask. You see it? Even the name is a mock. The tagline was "Like bottle, but classy" or something like that for a short time. Same API, even the same design errors (global request/response objects), but with werkzeug as a dependency. He then used his reach (he was pretty famous back then already) and pushed flask in the community, gained critical mass, and won. No need to sugarcoat it, bottle lost the popularity-battle pretty fast. But strangely enough, bottles userbase is still growing, slow and steady, even after more than 10 years in flasks shadow and with a really bad release circle. Bottle is simple, stable, fast and has enough features to be useful. Some people like it the way it is, and I'll keep it that way.

@oz123
Copy link
Contributor

oz123 commented Aug 30, 2019

@defnull, I am one of those bottle fans who prefers it over flask exactly for the reasons you mentioned. The only things I would like to improve are:

  1. Removal of global request
  2. Better release cycle

I realize that the first could only be done with more releases.
The second one could be achieved by engagement of more people of developers. There were others besides you, but they are all seem to be inactive.
Please consider enhancing the community by involving more people.

@agalera
Copy link
Contributor

agalera commented Aug 30, 2019

I use bottle for several reasons:

1 - be one of the fastest microframeworks
2 - I don't need to create classes and inherit from the framework to create my endpoints
3 - I can use any web server, I just need to define it when calling the run
4 - the plugin system is super simple to understand and meets all expectations
5 - template system included and works very well
6 - I can organize my code as I want, in my case, I like to have a folder with many files containing Endpoints and dynamically load it in init.py
7 - simple
8 - I can add any improvement easily, the code is understandable, without 20 layers of abstraction.
9 - The community is small but everyone collaborates.

Things that I do not like:
1 - the request is global, and this produces some problems with meinheld (there is a ticket for this), this is also a pain if you process some of the request in a thread

2 - The error responses are in html, I created a plugin to change this and that the response was a json, maybe it could be added in the core in some way. @defnull?

Things I wish I had:
1 - a simple websocket system, we currently have examples in the official documentation, but it does not seem to be 100% integrated.

That point is complex since I have tried many alternatives and no "framework" offers something simple for websockets.
I think there is a need not yet covered in python (regarding simple websockets to use)

@oz123
Copy link
Contributor

oz123 commented Aug 30, 2019

@agalera it's not easy combining wsgi with websockets. See asgi ... Django has the problem

@agalera
Copy link
Contributor

agalera commented Aug 30, 2019

@oz123 I know, and it is a great pain, for now I am using tornado for websockets and api en bottle.

I would like to have a "websocket for humans" as requests is "HTTP for humans" and for my bottle it is "wsgi for humans"

@oz123
Copy link
Contributor

oz123 commented Aug 30, 2019

@agalera have you tried this? https://trio-websocket.readthedocs.io/en/stable/

@carc1n0gen
Copy link

carc1n0gen commented Aug 30, 2019

@52lemon nothing says you must use a single file. You an split it in sub-applications

# app.py
from bottle import Bottle, run
from profile import profile

app = Bottle()


@app.route('/')
def index():
    return 'Hello'


app.mount('/profile', profile)


app.run()
# profile.py
from bottle import Bottle

profile = Bottle()


@profile.route('/')
def profile_index():
    return 'Profile'

EDIT: IT just dawned on me that this discussion is about the bottle source code itself, not bottle application code. Whoops!

@PyB1l
Copy link
Contributor

PyB1l commented Aug 30, 2019

@oz123 What's exactly the issue when it comes to global request / response? Is it about performance that stumbles in framework implementation? As mentioned earlier bottle still scores well in 2019 when it comes to performance.
If it is not about performance and it has to do with app design principles it is super easy to bypass it.

For example you can rewrite code in no time from this:

def some_view(id): headers = bottle.request.query.get('token') # do something bottle.response.add_header('WHAT', 'EVER') # return something

to

def some_view(id, request, response): headers = request.query.get('token') # do something response.add_header('WHAT', 'EVER') # return something
with a two-line Plugin.

In fact you can implement a fully Django-like API from scratch with bottle and other libraries.

I am working with bottlepy for 3 years now and it still rocks.

P.S. @defnull I think bottle is for much more than adhoc API's.

@agalera
Copy link
Contributor

agalera commented Aug 30, 2019

@PyB1l Exactly, you can make your "framework" on top of the bottle and adapt it to your needs and not have to adapt to a framework designed for general purpose

@agalera
Copy link
Contributor

agalera commented Aug 30, 2019

@oz123 It seems simple to use, I usually use it to send identical information to all customers (broadcast) so it's pretty simple.
Thanks!

@oz123
Copy link
Contributor

oz123 commented Aug 30, 2019

@PyB1l there is not performance issue.
It's just a matter of taste: explicit vs implicit. I prefer the former.
Also, would you be kind and share the two line plugin with us? I'm curious and intrigued ...

@agalera
Copy link
Contributor

agalera commented Aug 30, 2019

related to global requests:
#896
#906

@defnull
Copy link
Member

defnull commented Aug 30, 2019

We are getting a bit off-topic here, but that's fine.

@PyB1l The global request/response objects are thread-local, so normal multi-threaded WSGI servers work fine, but some asyncio or coroutine based servers implementations (e.g. gevent) break the WSGI threading model and handle more than one requests in the same thread at the same time. This caused some not-so-obvious bugs in applications in the past and is hard to protect against. It's not a bug in bottle, it is just something you need to know when choosing a server back-end. The solution is to either use a asyncio server that does not break WSGI semantics, or to monkeypatch threading.local so these objects are now task-local and no longer thread-local. Or to get a request.copy() and use that instead of the global instance.

@PyB1l
Copy link
Contributor

PyB1l commented Aug 30, 2019

@defnull First thing first Congrats for bottle! I am familiar with thread-local request/response of bottle, as well as with WSGI limitations. I was not referring to async support at all. Thread local are implemented in other frameworks too, even though it's not obvious due to DI pattern that are enforced. For example a simple plugin with the following apply method (@oz123 ) method would solve this problem (it's something that bottle-inject provide by default i think). My point is this: Thread Local Response should not mess with your taste or style. Sorry if i ran off-topic

def apply(self, callback, context):  # pragma: no cover
        """Implement bottle.py API version 2 `apply` method.
        """
        _signature = signature(callback).parameters

        def injection(*args, **kwargs):
            """Inject into callables request, response.
            """
            if 'request' in _signature:
                kwargs['request'] = bottle.request

            if 'response' in _signature:
                kwargs['response'] = bottle.response

            return callback(*args, **kwargs)

        return injection

@PyB1l
Copy link
Contributor

PyB1l commented Aug 30, 2019

BTW @oz123 I too prefer explicit coding! Makes py.test way more easy

@agalera
Copy link
Contributor

agalera commented Aug 30, 2019

that would work to have the data in your request, but I think the problem with asynchronous servers would not be solved since the process could change context before you assign the values

@S1M0NH
Copy link

S1M0NH commented Sep 8, 2019

Yes, the "single file, no dependencies" approach is one of the core strengths of bottle compared to other frameworks. Bottle is often used to quickly REST-ify existing tools or implement small APIs for strictly internal services, even on embedded devices where a virtualenv would be too much of a hassle sometimes.

But there are more reasons:

  • The single-file restriction is an effective save-guard against feature creep. It helps keeping bottle small, lean, fast and easy to understand. We are able reject pull requests simply because they would add too many lines or add too much complexity, and that's nice.
  • A single file is easier to debug and understand. If something is not documented in detail or if you have a question on how bottle handles a specific situation, just have a look and read the code. You can read the entirety of bottle.py in an hour or so. Most of it is pretty easy to understand. Try that with flask/pyramid/django, where everything is buried under multiple layers of abstractions and imports.
  • And last but not least: Bottle is not flask, and that is a good thing. If you want it to be more like flask, then use flask.

For anyone interested in a bit of (biased) history: Bottle was first. It was inspired by an even smaller micro-framework called itty, which in turn was inspired by rubys sinatra. Armin was annoyed that I implemented everything myself and did not use his library werkzeug (or webob), so he published a clone of bottle with werkzeug bundled within a single file as an "april fools joke". He basically mocked the single-file-no-dependency approach I took for bottle. The "joke" took off, and he realized that there is actually a real demand for a web-framework just like bottle, so he started flask. You see it? Even the name is a mock. The tagline was "Like bottle, but classy" or something like that for a short time. Same API, even the same design errors (global request/response objects), but with werkzeug as a dependency. He then used his reach (he was pretty famous back then already) and pushed flask in the community, gained critical mass, and won. No need to sugarcoat it, bottle lost the popularity-battle pretty fast. But strangely enough, bottles userbase is still growing, slow and steady, even after more than 10 years in flasks shadow and with a really bad release circle. Bottle is simple, stable, fast and has enough features to be useful. Some people like it the way it is, and I'll keep it that way.

You should put that history on the website. I knew that bottle came before flask, but not that itty.py inspired bottle.

@nhumrich
Copy link

This is the way

1 similar comment
@fgmonad
Copy link

fgmonad commented Jan 11, 2020

This is the way

@iamgodot
Copy link

Very inspiring discussion, bottle's code and docs are great for understanding (Python) web development.

About global context, ContextVar is suitable to implement upon as a universal solution of local objects regarding threads, greenlets and coroutines(I see werkzeug has switched to it). Although this is only applicable for Python 3.7 and later versions.

@defnull defnull added the F.A.Q. Not a bug, but still kept open for visibility label Sep 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
F.A.Q. Not a bug, but still kept open for visibility
Projects
None yet
Development

No branches or pull requests