bottlepy / bottle

bottle.py is a fast and simple micro-framework for python web-applications.
http://bottlepy.org/
MIT License
8.37k stars 1.46k forks source link

Why should bottlepy stick to a file? #1158

Open 52lemon opened 5 years ago

52lemon commented 5 years ago

Why should bottlepy stick to a file?

agalera commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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:

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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

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

agalera commented 5 years ago

@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 commented 5 years ago

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

carc1n0gen commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

related to global requests: https://github.com/bottlepy/bottle/issues/896 https://github.com/bottlepy/bottle/issues/906

defnull commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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

agalera commented 5 years ago

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 commented 5 years ago

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 commented 4 years ago

This is the way

fgmonad commented 4 years ago

This is the way

iamgodot commented 2 years ago

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.