A micro web-framework using asyncio coroutines and chained middleware.

Overview

Growler

master
Testing Report (Master Branch) Coverage Report (Master Branch) ' Latest PyPI version
dev
Testing Report (Development Branch) Coverage Report (Development Branch)

Growler is a web framework built atop asyncio, the asynchronous library described in PEP 3156 and added to the standard library in python 3.4. It takes a cue from the Connect & Express frameworks in the nodejs ecosystem, using a single application object and series of middleware to process HTTP requests. The custom chain of middleware provides an easy way to implement complex applications.

Installation

Growler is installable via pip:

$ pip install growler

The source can be downloaded/cloned from github at http://github.com/pyGrowler/Growler.

Extras

The pip utility allows packages to provide optional requirements, so features may be installed only upon request. This meshes well with the minimal nature of the Growler project: don't install anything the user doesn't need. That being said, there are (will be) community packages that are blessed by the growler developers (after ensuring they work as expected and are well tested with each version of growler) that will be available as extras directly from the growler package.

For example, if you want to use the popular mako html template engine, you can add support easily by adding it to the list of optionals:

$ pip install growler[mako]

This will automatically install the mako-growler packge, or growler-mako, or whatever it is named - you don't care, it's right there, and it works! Very easy!

The goal here is to provide a super simple method for adding middleware packages that the user can be sure works with that version of growler (i.e. has been tested), and has the blessing of the growler developer(s).

The coolest thing would be to describe your web stack via this command, so if you want mako, coffeescript, and some postgres ORM, your install command would look like growler[mako,coffee,pgorm]; anybody could look at that string and get the birds-eye view of your project.

When multiple extras are available, they will be listed here.

Usage

The core of the framework is the growler.App class, which links the asyncio server to your project's middleware. Middeware can be any callable or coroutine. The App object creates a request and a response object when a client connects and passes the pair to this middleware chain. Note: The middleware are processed in the same order they are specified - this could cause unexpected behavior (errors) if a developer is not careful, so be careful! The middleware can manipulate the request and response, adding features or checking state. If any respond to the client, the middleware chain is finished. This stream/filter model makes it very easy to modularize and extend web applications with many features, backed by the power of python.

Example Usage

import asyncio

from growler import App
from growler.middleware import (Logger, Static, StringRenderer)

loop = asyncio.get_event_loop()

# Construct our application with name GrowlerServer
app = App('GrowlerServer', loop=loop)

# Add some growler middleware to the application
app.use(Logger())
app.use(Static(path='public'))
app.use(StringRenderer("views/"))

# Add some routes to the application
@app.get('/')
def index(req, res):
    res.render("home")

@app.get('/hello')
def hello_world(req, res):
    res.send_text("Hello World!!")

# Create the server - this automatically adds it to the asyncio event loop
Server = app.create_server(host='127.0.0.1', port=8000)

# Tell the event loop to run forever - this will listen to the server's
# socket and wake up the growler application upon each connection
loop.run_forever()

This code creates an application which is identified by 'GrowlerServer' (this name does nothing at this point), and a reference to the event loop. Requests are passed to some middleware provided by the Grower package: Logger, Static, and StringRenderer. Logger simply prints the ip address of the connecting client to stdout. Static will check a request url path against files in views/, if one of the files match, the file type is determined, proper content-type header is set, and the file content is sent. Renderer adds the 'render' method to the response object, allowing any following function to call res.render('/filename'), where filename exists in the "views" directory.

Decorators are used to add endpoints to the application, so requests with path matching '/' will call index(req, res) and requests matching '/hello' will call hello_world(req, res). Because 'app.get' is used, only HTTP GET requests will match these endpoints. Other HTTP 'verbs' (post, put, delete, etc) are available as well as 'all', which matches any method. Verb methods must match a path in full.

The 'use' method takes an optional path parameter (e.g. app.use(Static("public"), '/static')), which calls the middleware anytime the request path begins with the parameter.

The asyncio package provides a Server class which does the low-level socket handling for the developer, this is how your application should be hosted. Calling app.create_server(...) creates an asyncio Server object with the event loop given in app's constructor, and the app as the target for incomming connections; this is the recommended way to setup a server. You can't do much with the server directly, so after creation the event loop must be given control of the thread The easiest way to do this is to use loop.run_forever() after app.create_server(...). Or do it in one line with app.create_server_and_run_forever(...).

Extensions

Growler introduces the virtual namespace growler_ext to which other projects may add their own growler-specific code. Of course, these packages may be imported in the standard way, but Growler provides an autoloading feature via the growler.ext module (note the '.' in place of '_') which will automatically import any packages found in the growler_ext namespace. This not only provides a standard interface for extensions, but allows for different implementations of an interface to be chosen by the environment, rather than hard-coded in. It also can reduce the number of import statements at the beginning of the file. This specialized importer may be imported as a standalone module:

from growler import App, ext

app = App()
app.use(ext.MyGrowlerExtension())
...

or a module to import 'from':

from growler import App
from growler.ext import MyGrowlerExtension

app = App()
app.use(MyGrowlerExtension())
...

This works by replacing the 'real' ext module with an object that will import submodules in the growler_ext namespace automatically. Perhaps unfortunately, because of this there is no way I know of to allow the import growler.ext.my_extension syntax, as this skips the importer object and raises an import error. Users must use the from growler.ext import ... syntax instead.

The best practice for developers to add their middleware to growler is now to put their code in the python module growler_ext/my_extension. This will allow your code to be imported by others via from growler.ext import my_extension or the combination of from growler import ext and ext.my_extension.

An example of an extension is the indexer packge, which hosts an automatically generated index of a filesystem directory. It should implement the best practices of how to write extensions.

More

As it stands, Growler is single threaded, partially implemented, and not fully tested. Any submissions, comments, and issues are greatly appreciated, but I request that you please follow the Growler contributing guide.

The name Growler comes from the beer bottle keeping in line with the theme of giving python micro-web-frameworks fluid container names.

License

Growler is licensed under Apache 2.0.

Comments
  • Crash on request.get_body()

    Crash on request.get_body()

    Greetings. I`m trying to use some code from your examples:

    import asyncio
    
    from growler import App
    from growler.middleware import (Logger, Static, StringRenderer)
    
    loop = asyncio.get_event_loop()
    
    app = App('GrowlerServer', loop=loop)
    app.use(Logger())
    
    @app.post('/data')
    def post_test(req, res):
        print(req.get_body())
    
    Server = app.create_server(host='127.0.0.1', port=4000)
    
    loop.run_forever()
    

    but when I call this handler: curl -H "Content-Type: application/json" -X POST -d '{"somekey": "somevalue"}' http://127.0.0.1:4000/data I`m getting this errors in console:

    ERROR:growler.router:140393215106800:Event loop is running. ERROR:growler.middleware_chain:140393229054640:Event loop is running. And then app exits with code 0.

    Python - 3.5.2 asyncio (3.4.3) growler (0.7.5)

    Do you have some advice, to aboid this problem. Cause it seems, like your framework is very interesting and perfect for my purposes. But this problem ruins all idea ;)

    Thank you.

    opened by yalosev 4
  • Implement ETag

    Implement ETag

    ETags are a core feature of web servers that users should expect to work out of the box. This should be a non-middleware feature baked into either Application or HTTPRequest classes, with the option to disable or swap implementations if the user so chooses.

    enhancement help wanted 
    opened by akubera 3
  • Handle binary data uploads

    Handle binary data uploads

    I'm running into a problem with uploading binary data (longer than 32K) to a pygrowler-based service. It looks like the responder is not handling the 'EXPECT': '100-continue' header. Can this be added in? It can probably be handled similar to the aiohttp module.

    enhancement priority-high 
    opened by kyeatman74 2
  • Are views a coroutines?

    Are views a coroutines?

    Let's take a look at the documentation example:

    
    from growler import App
    
    app = App()
    
    @app.use
    def log_file(req, res):
        print("<{ip}> {req_path}".format(ip=req.ip, req_path=req.path))
    
    @app.get("/")
    def index_file(req, res):
        res.send_text("index.")
    
    app.create_server_and_run_forever(host='0.0.0.0', port='8080')
    

    Is index_file a coroutine from which I could do a yield from?

    opened by JerzySpendel 2
  • Using Uvloop in Growler

    Using Uvloop in Growler

    I recently came across Growler, it looks pretty good framework to move away from Flask that I am currently using.

    But I have seen some improvements on the loop with https://github.com/MagicStack/uvloop - the benchmark seems to show it is better event loop as compared to Asyncio Event loop. so can I replace the Event loop from the current example to use Uvloop - just wanted to know if I am missing anything.

    Asyncio Loop

    loop = asyncio.get_event_loop()
    app = App('GrowlerServer', loop=loop)
    

    Uvloop

     loop = uvloop.new_event_loop()
     app = App('GrowlerServer', loop=loop)
    

    I understand that #4 its been brought forward - but should it not be as straightforward as given above.

    opened by machbio 2
  • routes using @routerclass decorator

    routes using @routerclass decorator

    Very awesome project and i appreciate your great work. I am liking the project but the docstring based routing seems a bit unusual to me. Are you guys planning to keep it or change it to more simpler straight forward approach like other framework like falcon (another python framework to build REST API) does? For the newbie programmers, would that be confusing and harder to debug?

    opened by foodaemon 2
  • GrowlerHTTPResponder' object has no attribute 'client_query'

    GrowlerHTTPResponder' object has no attribute 'client_query'

    when i try to get get a param from a url, the app gave me this error

    ERROR:growler.router:4441216280:'GrowlerHTTPResponder' object has no attribute 'client_query' ERROR:growler.middleware_chain:4437457384:'GrowlerHTTPResponder' object has no attribute 'client_query' https://gist.github.com/leonardoo/cc9c0ae4ac0593553b4bfea239758528

    the fix is change:

    ....
        @property
        def query(self):
            return self._protocol.client_query
    

    for this:

    ....
        @property
        def query(self):
            return self._protocol.parsed_query
    
    opened by leonardoo 1
  • Example in README.md doesn't work?

    Example in README.md doesn't work?

    Thank you for creating growler, I think it looks great!

    I was trying the examples and they work fine, but I copied and pasted the example on the main README and it doesn't work with the pip version of growler (0.7.2).

    Here's the error

    File "hello.py", line 14, in <module>
        app.use(Renderer("views/", "jade"))
    TypeError: __init__() takes 2 positional arguments but 3 were given
    
    
    opened by jfmatth 1
  • Suggestion: remove growler.ext mechanism

    Suggestion: remove growler.ext mechanism

    I saw this feature in your README, and was reminded of our experience with Django and doing magical imports etc. - back in the day, your own models would end up being imported from a Django namespace, rather than your own Python modules. This was unnecessary magic that caused complications of various kinds (similar to the way that you can't do normal imports such as from growler.ext import foo). The fact that you have to mention "it doesn't work quite the way you expect" tells you that you will have trouble with it.

    The end result was that we removed this feature, along with a whole lot of unnecessary magic that required people to learn “the Django way” instead of just using "the Python way" which was perfectly adequate.

    In this case, it seems like this mechanism just adds complications - like:

    • "More than one way to do it" (you can import the middleware from the original module names or from growler.ext)
    • Requiring people to put things in certain namespaces, instead of however they want to package their code.
    • Making life hard for IDEs and other tools that want to do static analysis to provide help.

    There doesn't seem to be a reason why it is needed. If you really need a registry of plugins, that could be explicit. You could almost certainly use setuptools entrypoints for this - http://stackoverflow.com/questions/774824/explain-python-entry-points#9615473 https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points

    opened by spookylukey 2
  • Refactor pytest fixtures into a pytest-growler plugin

    Refactor pytest fixtures into a pytest-growler plugin

    I'm copying tests from growler-mako into my extension, so it's probably a good idea to eventually consolidate common fixtures into a pytest plugin at some future point.

    help wanted 
    opened by lmergner 3
  • Clarify how to use the ext namespace and RendererEngine

    Clarify how to use the ext namespace and RendererEngine

    I'm trying to build a growler-jinja extension by copying growler-jade and growler.renderer.RendererEngine. However, I'm getting an import error running from the REPL (after a pip install -e .):

    >>> from growler.ext import JinjaRenderer
    ImportError: No module named 'growler_ext.JinjaRenderer
    

    I don't fully understand setuptools namespaces, so this is partly my own ignorance.

    I also see that the api for subclassing RenderEngine has changed subtly. Does the growler-jade source reflect the current api?

    After using node's koa framework, I'm excited to try to port my Flask app to Growler.

    ubuntu 16 python 3.5.2 virtualenv 15.0.2 setuptools 26.0.0 growler 0.7.5

    opened by lmergner 4
  • Replace root logger.

    Replace root logger.

    This project is interesting to me.

    I think python's library should not use root logger like:

    import logging
     logging.warn('this is a warning')
    

    refs: http://pieces.openpolitics.com/2012/04/python-logging-best-practices/

    Would you replace root logger like:

    from logging import getLogger
    
    logger = getLogger(__name__)
    logger.warn('this is a warning')
    
    opened by c-bata 1
  • please suggest an ORM/db layer

    please suggest an ORM/db layer

    coming from HN (https://news.ycombinator.com/item?id=11634152 and https://news.ycombinator.com/item?id=11632650)

    Could you please include something like aiopg in your setup instructions - not as a requirement, but as a recommendation. Too many of us are using something like Flask with postgres and we would love to try Growler out.

    But the world of Python ORMs is problematic when it comes to asyncio compatible libraries. This includes mysql, postgresql and redis - three of the biggest ones which pretty much everyone uses.

    As someone else put it on HN - https://news.ycombinator.com/item?id=11629444

    One problem with this is that the entire ecosystem has to get on board with async. Maybe a new framework would make it compelling enough, who knows. This was/is the big issue with Tornado, IMO (and Tornado has been around for ages in framework time). Tornado is only async if the entire call stack all the way down to the http socket is async, using callbacks instead of returning values. This means that any 3rd party client library you use has to be completely written asynchronously, and none are in python. So you end up with a lot tedious work re-implementing http client libraries for Twilio or Stripe or whatever you're using. I'm curious to see where asyncio goes in python, but I'm a bit skeptical after seeing how much of a pain it was to use Tornado on a large web app. In the meantime I'll be using Gevent + Flask, which isn't perfect since it adds some magic & complexity but has the huge upside of letting you keep using all the libraries you're used to.

    opened by sandys 4
bottle.py is a fast and simple micro-framework for python web-applications.

Bottle: Python Web Framework Bottle is a fast, simple and lightweight WSGI micro web-framework for Python. It is distributed as a single file module a

Bottle Micro Web Framework 7.8k Dec 31, 2022
Fast⚡, simple and light💡weight ASGI micro🔬 web🌏-framework for Python🐍.

NanoASGI Asynchronous Python Web Framework NanoASGI is a fast ⚡ , simple and light ?? weight ASGI micro ?? web ?? -framework for Python ?? . It is dis

Kavindu Santhusa 8 Jun 16, 2022
The Python micro framework for building web applications.

Flask Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to co

The Pallets Projects 61.5k Jan 6, 2023
Bromelia-hss implements an HSS by using the Python micro framework Bromélia.

Bromélia HSS bromelia-hss is the second official implementation of a Diameter-based protocol application by using the Python micro framework Bromélia.

henriquemr 7 Nov 2, 2022
A proof-of-concept CherryPy inspired Python micro framework

Varmkorv Varmkorv is a CherryPy inspired micro framework using Werkzeug. This is just a proof of concept. You are free to use it if you like, or find

Magnus Karlsson 1 Nov 22, 2021
Asynchronous HTTP client/server framework for asyncio and Python

Async http client/server framework Key Features Supports both client and server side of HTTP protocol. Supports both client and server Web-Sockets out

aio-libs 13.2k Jan 5, 2023
WebSocket and WAMP in Python for Twisted and asyncio

Autobahn|Python WebSocket & WAMP for Python on Twisted and asyncio. Quick Links: Source Code - Documentation - WebSocket Examples - WAMP Examples Comm

Crossbar.io 2.4k Jan 6, 2023
web.py is a web framework for python that is as simple as it is powerful.

web.py is a web framework for Python that is as simple as it is powerful. Visit http://webpy.org/ for more information. The latest stable release 0.62

null 5.8k Dec 30, 2022
A very simple asynchronous wrapper that allows you to get access to the Oracle database in asyncio programs.

cx_Oracle_async A very simple asynchronous wrapper that allows you to get access to the Oracle database in asyncio programs. Easy to use , buy may not

null 36 Dec 21, 2022
Python AsyncIO data API to manage billions of resources

Introduction Please read the detailed docs This is the working project of the next generation Guillotina server based on asyncio. Dependencies Python

Plone Foundation 183 Nov 15, 2022
A public API written in Python using the Flask web framework to determine the direction of a road sign using AI

python-public-API This repository is a public API for solving the problem of the final of the AIIJC competition. The task is to create an AI for the c

Lev 1 Nov 8, 2021
Asita is a web application framework for python based on express-js framework.

Asita is a web application framework for python. It is designed to be easy to use and be more easy for javascript users to use python frameworks because it is based on express-js framework.

Mattéo 4 Nov 16, 2021
The Modern And Developer Centric Python Web Framework. Be sure to read the documentation and join the Slack channel questions: http://slack.masoniteproject.com

NOTE: Masonite 2.3 is no longer compatible with the masonite-cli tool. Please uninstall that by running pip uninstall masonite-cli. If you do not unin

Masonite 1.9k Jan 4, 2023
Free and open source full-stack enterprise framework for agile development of secure database-driven web-based applications, written and programmable in Python.

Readme web2py is a free open source full-stack framework for rapid development of fast, scalable, secure and portable database-driven web-based applic

null 2k Dec 31, 2022
Bionic is Python Framework for crafting beautiful, fast user experiences for web and is free and open source

Bionic is fast. It's powered core python without any extra dependencies. Bionic offers stateful hot reload, allowing you to make changes to your code and see the results instantly without restarting your app or losing its state.

 ⚓ 0 Mar 5, 2022
Fast, asynchronous and elegant Python web framework.

Warning: This project is being completely re-written. If you're curious about the progress, reach me on Slack. Vibora is a fast, asynchronous and eleg

vibora.io 5.7k Jan 8, 2023
Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.

Tornado Web Server Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed. By using non-blocking ne

null 20.9k Jan 1, 2023
Sierra is a lightweight Python framework for building and integrating web applications

A lightweight Python framework for building and Integrating Web Applications. Sierra is a Python3 library for building and integrating web applications with HTML and CSS using simple enough syntax. You can develop your web applications with Python, taking advantage of its functionalities and integrating them to the fullest.

null 83 Sep 23, 2022
FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins.

FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins. It is based on top of fastAPI, uvicorn, typer, and pluggy.

Adrien Delsalle 1 Nov 16, 2021