The web framework for inventors

Overview

Emmett

Emmett is a full-stack Python web framework designed with simplicity in mind.

The aim of Emmett is to be clearly understandable, easy to be learned and to be used, so you can focus completely on your product's features:

from emmett import App, request, response
from emmett.orm import Database, Model, Field
from emmett.tools import service, requires

class Task(Model):
    name = Field.string()
    is_completed = Field.bool(default=False)

app = App(__name__)
app.config.db.uri = "postgres://user:[email protected]/foo"
db = Database(app)
db.define_models(Task)
app.pipeline = [db.pipe]

def is_authenticated():
    return request.headers["Api-Key"] == "foobar"
    
def not_authorized():
    response.status = 401
    return {'error': 'not authorized'}

@app.route(methods='get')
@service.json
@requires(is_authenticated, otherwise=not_authorized)
async def todo():
    page = request.query_params.page or 1
    tasks = Task.where(
        lambda t: t.is_completed == False
    ).select(paginate=(page, 20))
    return {'tasks': tasks}

pip version Tests Status

Documentation

The documentation is available at https://emmett.sh/docs. The sources are available under the docs folder.

Examples

The bloggy example described in the Tutorial is available under the examples folder.

Status of the project

Emmett is production ready and is compatible with Python 3.7 and above versions.

Emmett follows a semantic versioning for its releases, with a {major}.{minor}.{patch} scheme for versions numbers, where:

  • major versions might introduce breaking changes
  • minor versions usually introduce new features and might introduce deprecations
  • patch versions only introduce bug fixes

Deprecations are kept in place for at least 3 minor versions, and the drop is always communicated in the upgrade guide.

How can I help?

We would be very glad if you contributed to the project in one or all of these ways:

  • Talking about Emmett with friends and on the web
  • Adding issues and features requests here on GitHub
  • Participating in discussions about new features and issues here on GitHub
  • Improving the documentation
  • Forking the project and writing beautiful code

License

Emmmett is released under the BSD License.

However, due to original license limitations, some components are included in Emmett under their original licenses. Please check the LICENSE file for more details.

Issues
  • Possible bug in stream_dbfile

    Possible bug in stream_dbfile

    Hi @gi0baro, I do not know where the problem may be. I will try to put the maximum information about what is happening.

    Given a model with an upload field, an image in my case.

    In development mode works fine. I upload the image and I can show it on some page, but in production (nginx + gunicorn) the following happens:

    • The image is uploaded well to the server
    • The image fails to display on the page

    I did this test:

    Create a tunnel to the server to access port 8000 and start the development server from weppy.

    [develop]

    1. I uploaded the image and I could see it without problems

    [nginx] 2) Try to view image uploaded in (1) and fail. 3) I uploaded another image but could not see it

    [develop] 4) I can see the image uploaded in (3)

    The download function is this:

    @app.route("/download/<str:filename>")
    def download(filename):
        stream_dbfile(db, filename)
    
    

    If I copy the url of the image and paste it in another tab, sometimes it gives: "Internal Server Error" and other times malformed image

    There is no error in the nginx log.

    If you need any further proof, please let me know.

    Jose

    invalid 
    opened by josejachuf 19
  • Next major release

    Next major release

    So, I think during this year a couple of minor releases (1.1 and 1.2) hopefully will take place, and they probably will focus on optimization – rewriting the router to gain performance is one of these – and small enhancements on the existing features.

    The key-fact of this discussion is regarding the next major release (2.0) which I really hope would be ready for the end of the year or the start of the next. Here are some ideas I got in the last months.

    Things I really want into 2.0

    Drop the Python 2 support With the status of Python 3 and the ecosystem, and the official drop of the support in 2020, I can't see any good reason to keep all the compatibility code inside weppy. Definitely moving to Python 3 will really simplify the internal code and avoid slow downs caused by compatibility layers.

    Rewrite and separate the ORM Some people already asked me to separate the ORM into a separate project, and I think that's a good idea. But I really want to take advantage of this to rewrite the bottom layer and stop depending from the pydal library. This will surely bring some advantages, like generally improving the performance of the ORM, and the ability to extend it with new features quickly (hstore and jsonb support on postgres, for example). Even if I am one of the core contributors of pydal, moving the weppy ORM layer into a separated project will definitely avoid me the pain of keeping really old and awful code just for keeping backward compatibility and to remove a lot of codes weppy doesn't really use or need. Surely rewriting this layer will cause some disadvantages, like some incompatibilities with weppy 1.0 or loosing the compatibility with some DBMS – even if today I don't really supports them, and they actually doesn't work really well 'cause nobody supports them in pydal – but I really feel bad when I think the difficulty of adding new features to the current layer. What I can say right now is that I want to keep support for sqlite, mysql, postgresql, mssql and mongo, but the list might be expanded depending on the workload. We'll see.

    Support for sockets/channels I don't really want to switch the framework when I need them, so they will definitely be in 2.0. I don't actually know if the next major version will be totally async compatible, 'cause right now weppy abuses quite a lot of thread locals, but I'll see in the next month how to deal with that.

    Things nice to have in 2.0

    • Enhancements to the templates system: would be nice to make it even more flexible
    • Support for isomorphism/frontend components: seems like a trend right know, and it's really interesting to me (can lead to frontend validation with forms and things like that), but I need to explore this more accurately.
    • Commands to generate scaffolds: can be a good point to generate different applications scaffolds for new adopters

    This is almost everything I have in my plans. But I am totally open to suggestions, feature requests, feedbacks, whatever. Feel free to add your 2 cents to the discussion.

    discussion 
    opened by gi0baro 18
  • URL validation causes weird bug in general validation

    URL validation causes weird bug in general validation

    Try these examples:

    The following URLs fail on URL validation of forms: www.domain.tld domain.tld http://domain.tld http://www.domain.tld http://www.domain.tld/

    This works: http://domain.tld/

    After I tried this again - the working URL wasn't accepted a second time. So this check is inconsistent and weirdly at the same time when this URL validation passes, all other form errors aren't shown like all other fields (which are empty in this situation) are okay even if I force them to be filled in.

    There must be some bug in the form validation.

    opened by GiantCrocodile 16
  • From weppy to emmett

    From weppy to emmett

    Hi @gi0baro

    Emmet: 2.0.0a5 Emmet-REST: 1.0.0a1

    I am trying to run a weppy app with the new emmett and emmett_rest. After fixing the imports the app compiles and starts the server. But for any request return an error. The app is exactly as I took it working from weppy.

    ERROR: Application exception: Traceback (most recent call last): File "/home/jose/emmett/venv38/lib/python3.8/site-packages/emmett/asgi/handlers.py", line 282, in dynamic_handler http = await self.router.dispatch() File "/home/jose/emmett/venv38/lib/python3.8/site-packages/emmett/routing/router.py", line 217, in dispatch http_cls, output = await route.dispatcher.dispatch(request, reqargs) File "/home/jose/emmett/venv38/lib/python3.8/site-packages/emmett/routing/dispatchers.py", line 75, in dispatch await self._parallel_flow(self.flow_open) File "/home/jose/emmett/venv38/lib/python3.8/site-packages/emmett/routing/dispatchers.py", line 26, in _parallel_flow tasks = [asyncio.create_task(method()) for method in flow] File "/home/jose/emmett/venv38/lib/python3.8/site-packages/emmett/routing/dispatchers.py", line 26, in tasks = [asyncio.create_task(method()) for method in flow] File "/usr/lib/python3.8/asyncio/tasks.py", line 382, in create_task task = loop.create_task(coro) File "uvloop/loop.pyx", line 1396, in uvloop.loop.Loop.create_task TypeError: a coroutine was expected, got None

    What should I consider to do the w2e migration?

    question 
    opened by josejachuf 12
  • Temporarily return to rapidjson

    Temporarily return to rapidjson

    I have a problem that is not directly from emmett but with the installation of orjson.

    All the server infrastructure I use is freebsd (+ jails). The orjson installation with pip does not use binaries, it is compiled. To compile you need to install some dependencies, including maturin. In the maturin installation an error occurs (I reported it) and therefore I cannot install orjson.

    I've been seeing this [1]

    What I want to do is temporarily modify serializers.py [2] and go back to rapidjson. This is apparently the only place where orjson is referenced. The query is if this change is enough and nothing will break.

    [1] https://github.com/emmett-framework/emmett/commit/2878fbdfd0ddedbee40ed863d78fb9c6525fc93e [2] https://github.com/emmett-framework/emmett/commit/5bbbae35c8517fd147e9b76ec4486432c347ef9e

    question 
    opened by josejachuf 11
  • About signed urls

    About signed urls

    @gi0baro, Based on bloggy, I added signature.py. (Attached)

    Modifications to bloggy.py:

    from signature import SignPipe, SignInjector

    app.injectors = [SignInjector()]

    @app.route("/post/<int:pid>", pipeline=[SignPipe()])
    def one(pid):
       ...
    
    @app.route("/new")
    @requires(lambda: auth.has_membership('admin'), url('index'))
    def new_post():
        form = Post.form()
        if form.accepted:
            redirect(url('one', form.params.id, sign=SignInjector.sign))
        return dict(form=form)
    
    

    index.html

    {{extend 'layout.html'}}
    
    <a href="{{=url('new_post')}}">Create a new post</a>
    <ul class="posts">
    {{for post in posts:}}
        <li>
            <h2>{{=post.title}}</h2>
            <a href="{{=url('one', post.id, sign=sign)}}">Read more</a>
            <hr />
        </li>
    {{pass}}
    {{if not posts:}}
        <li><em>No posts here so far.</em></li>
    {{pass}}
    </ul>
    

    As you can see only the one function requires that it be signed.

    It happens that after entering a signed link ["Read more"] other links are signed, for example new_post. It is as if the _signature parameter appears in each url, so when the signature is created, the url already contains another signature and it is obvious that validation will always fail.

    I found a solution modifying weppy / espose / url by removing the _signature parameter if it exists in params like this:

    # add signature
        if '_signature' in params:
            params.pop('_signature')
    
        if sign:
            params['_signature'] = sign(
                path, args, params, anchor, scheme, host, language)
    

    Can you apply this change to the repository?

    bloggy.zip

    opened by josejachuf 11
  • regex and russian language in utf-8 template

    regex and russian language in utf-8 template

    Hi, @gi0baro !

    I'm cannot find nothing about regular expressions in templates, and when i create template with utf-8 encoding and write russian text, i cannot see a valid text on template rendering. Does weppy framework supports these features?

    Sorry, if i ask stupid questions, and my english.

    Thank you.

    opened by hloe0xff 11
  • multiple route and one function  work random,404

    multiple route and one function work random,404

    Here is my simple app server,use two route /api/v1/book/isbn/<str:isbn> and /api/v1/book/<int:bookid>

    when I visit http://127.0.0.1:8001/api/v1/book/isbn/1231 It works 50% work at #return 1 ,50% 404, I dont know why,It has run inside the book function already。

    This is my code:

    #coding=utf-8
    from weppy import request
    from weppy import App
    from weppy.tools import service
    
    app = App(__name__)
    
    import random
    
    @app.route('/api/v1/book/isbn/<str:isbn>')
    @app.route('/api/v1/book/<int:bookid>')
    @service.json
    def book(isbn=None,bookid=None):
    
        if random.random()>0.5:
        #return 1
            return isbn
        #return 2
        if isbn != None:
            data = {'isbn':isbn}
        if bookid != None:
            bookid = int(bookid)
            data = {'id':bookid}
    
        return data
    
    if __name__ == '__main__':
        app.debug = True
        app.run(host='0.0.0.0', port=8001)
    
    opened by alingse 9
  • Auth issue: User isn't denied if deleted after session is created

    Auth issue: User isn't denied if deleted after session is created

    If you are authorized and while you are logged in your account gets deleted from the database, there is no logout/error happening. You can still browse /account/profile etc.

    I deleted the entry directly in the database and not with weppy's DAL. I don't know if this issue can occur if you do it the normal productive way.

    discussion 
    opened by GiantCrocodile 9
  • Auth: True for read is ignored on form_profile_rw

    Auth: True for read is ignored on form_profile_rw

    When I set form_profile_rw for field username to (True, False) in my User(AuthUserBasic) model the username field isn't displayed. It looks like read-access is broken and only writing works because if I set it to (True, True) or (False, True) I can see a textfield to type in.

    Expected behaviour is explained here: http://weppy.org/docs/0.5/dal#the-models-setup-helper

    enhancement feature request 
    opened by GiantCrocodile 9
  • Add Python 3.10 support

    Add Python 3.10 support

    null

    enhancement 
    opened by gi0baro 0
  • Bug with 'in': {'dbset': ...} validation

    Bug with 'in': {'dbset': ...} validation

    I have this validation

    'f1': {'in': {'dbset': lambda db: db.where(db.A.f2 == session.f2)}}

    In another way:

    def val_f2(db):
        print(session.f2)
        return db.where(db.A.f2 == session.f2)
        
    'f1': {'in': {'dbset': lambda db: val_f2(db)}}
    

    The problem I have is that lambda runs only once and after that first time does not come to run, then it is always calling db.where from the first condition. I can check it with the print() that only runs once

    Jose

    ORM bug::investigation-needed 
    opened by josejachuf 5
  • compute does not work with emmett relations

    compute does not work with emmett relations

    With this table definition (I can post the complete sample code if it would be helpful):

    class Tmodel(Model):
        has_many('tas', {'amodels': {'via': 'tas'}})
        has_many('tbs', {'bmodels': {'via': 'tbs'}})
    
        a = Field()
        # total_avg_cost = Field.decimal(10, 2, auto_validation=False)
    
        @rowmethod('total_avg_cost')
        def get_avg_cost(self, row):
            avg_list = [lst.my_avg() for lst in row.bmodels.select(db.Tb.ALL) if lst.my_avg()]
            return sum(avg_list) / len(avg_list) if avg_list else 0
    

    Tmodel.first().total_avg_cost() works as a rowmethod (returns 0 when no related records).

    But when using @compute instead, Tmodel.create(a='foo') fails to create the record, with an 'Invalid Value' error.

    If setting auto_validation=False on the total_avg_cost field, the record is created with total_avg_cost=None instead of the computed 0 (when there are no related records).

    enhancement docs ORM 
    opened by Triquetra 4
  • Introduce relations facilities on forms

    Introduce relations facilities on forms

    Aim: make it easy to generate forms containing also related models' fields

    See https://github.com/emmett-framework/emmett/discussions/324 and https://github.com/emmett-framework/emmett/discussions/350

    enhancement nicetohave 
    opened by gi0baro 0
  • Custom model field

    Custom model field

    Hi @gi0baro

    Need information with examples)))

    opened by bronte2k7 3
  • Customizing forms

    Customizing forms

    Hi @gi0baro

    Need information with examples)))

    opened by bronte2k7 3
  • Introduce facilities for large applications

    Introduce facilities for large applications

    Some might be:

    • application composition
    • re-utilizable ORM models
    • ORM migrations' namespaces
    • advanced control of AppModule lifecycle
    • routes abstract classes to facilitate code recycling/extension
    enhancement nicetohave 
    opened by gi0baro 1
  • Use Emmett ORM separately from the App (in a Notebook)

    Use Emmett ORM separately from the App (in a Notebook)

    Hey @gi0baro ,

    I wanted to know use Emmett ORM separately from the App.

    Ideally, I would use a Notebook where I would declare the DB and the Models, attach the Models to the DB, and test different queries.

    Is it possible ?

    Thank you! Pierre

    feature request 
    opened by delarroqua 4
  • Oauth/openid support

    Oauth/openid support

    Could you add openid/oauth2 sso support to emmett please? ;)

    feature request 
    opened by Kkeller83 2
  • Provide scaffolding utilities

    Provide scaffolding utilities

    Would be nice to have a scaffold command for projects generation in CLI.

    nicetohave 
    opened by gi0baro 0
Releases(v2.3.1)
  • v2.3.1(Aug 19, 2021)

  • v2.3.0(Aug 12, 2021)

    Changes since 2.2:

    • Minor enhancements on request flow
    • Added ORM support for PostgreSQL json/jsonb fields and operators
    • Added widget_radio to forms.FormStyle
    • Added dict values support for in validations
    • Use optional emmett-crypto package for cryptographic functions
    • Deprecated security.secure_dumps and security.secure_loads in favour of new crypto package
    • Add on_delete option to belongs_to and refers_to
    • Add --dry-run option to migrations up and down commands
    Source code(tar.gz)
    Source code(zip)
  • v2.2.5(Aug 8, 2021)

    Patch release

    Changes since 2.2.4:

    • Fix regression bug preventing correct serialisation/deserialisation with tools.service module pipes and websockets
    Source code(tar.gz)
    Source code(zip)
  • v2.2.4(Jul 27, 2021)

    Patch release

    Changes since 2.2.3:

    • Fix ORM migrations engine producing wrong SQL instructions in columns foreign keys constraints on some adapters
    • Bump bundled jQuery to 3.5.x to fix several vulnerabilities
    Source code(tar.gz)
    Source code(zip)
  • v2.2.3(Jun 21, 2021)

  • v2.2.2(Jun 1, 2021)

  • v2.2.1(Apr 26, 2021)

  • v2.2.0(Mar 9, 2021)

    Changes since 2.1:

    • Slightly refactored request flow
    • Added App.command_group decorator
    • Added additional arguments acceptance in AppModule
    • Added static paths customisation for AppModule
    • Added workers options to serve command
    • Changed default logging level to info in serve command
    • Changed default SameSite policy for session cookies to Lax
    • Added official Python 3.9 support
    Source code(tar.gz)
    Source code(zip)
  • v2.1.4(Nov 9, 2020)

  • v2.1.3(Nov 6, 2020)

  • v2.1.2(Oct 31, 2020)

  • v2.1.1(Oct 28, 2020)

  • v2.1.0(Oct 17, 2020)

    Changes since 2.0:

    • Added type hints on all major interfaces
    • Deprecated App.run
    • Deprecated string signals in favour of extensions.Signals
    • Removed libs.sanitizer
    • Use orjson for JSON serialization
    • Refactored request flow internals
    • Added namespaces to templater
    • Added SameSite support to session cookies
    • Added HTTP/2 support
    • Added Request.push_promise
    • Added Python 3.9 support (beta)
    Source code(tar.gz)
    Source code(zip)
  • v2.0.2(Aug 30, 2020)

  • v2.0.1(Aug 11, 2020)

  • v2.0.0(May 3, 2020)

    Changes since 1.3:

    • Dropped Python 2 support, requiring Python 3.7 minimum version
    • Changed package name to emmett
    • Moved from WSGI to ASGI
    • Moved to async syntax
    • Moved globals module to ctx
    • Added output optional param to route definition
    • Introduced develop and serve commands in place of run
    • Pipeline open and close flows are now handled concurrently
    • Rewritten router, optimized request flow
    • Introduced websockets support
    • Added after_loop signal
    • Decoupled templating engine
    • Added Request.files
    • Added request_max_content_length to App.config
    • Added request_body_timeout to App.config
    • Added async support in cache module
    • Decoupled internationalization engine
    • Added runtime migration utils in ORM
    • Added response_timeout to App.config
    • Use default text/plain Content-Type header in responses
    • Added namespace to Injector class
    • Added Python 3.8 support
    Source code(tar.gz)
    Source code(zip)
  • v1.3.4(Apr 23, 2020)

  • v2.0.0b2(Apr 3, 2020)

  • v2.0.0b1(Mar 15, 2020)

  • v2.0.0a5(Feb 23, 2020)

  • v2.0.0a4(Feb 11, 2020)

  • v2.0.0a3(Jan 15, 2020)

  • v2.0.0a2(Jan 14, 2020)

  • v2.0.0a1(Jan 13, 2020)

  • v1.3.3(Dec 29, 2019)

  • v1.3.2(Nov 22, 2019)

  • v1.3.1(Dec 10, 2018)

  • v1.3(Oct 28, 2018)

    Changes since 1.2:

    • Minor bugfixes
    • Added proper support for 'big' id and reference fields to ORM
    • Added url_prefix global routing paramater to App
    • Allow to specify different templates_encoding for templates loading in application config
    • Added migrations_folder variable to database configuration to support migrations with multiple databases
    • Added support for Python 3.7
    • Refactored join and including options handling in ORM
    • Added cast method to ORM Field
    • Added cast options to has_many decorator
    • Optimized pipeline flow
    • Optimized router match code
    Source code(tar.gz)
    Source code(zip)
  • v1.2.11(Jun 2, 2018)

  • v1.2.10(Mar 1, 2018)

Owner
Emmett
Emmett
The web framework for inventors

Emmett is a full-stack Python web framework designed with simplicity in mind. The aim of Emmett is to be clearly understandable, easy to be learned an

Emmett 716 Oct 17, 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 11.8k Oct 23, 2021
Serverless Python

Zappa - Serverless Python About Installation and Configuration Running the Initial Setup / Settings Basic Usage Initial Deployments Updates Rollback S

Rich Jones 11.8k Oct 22, 2021
Serverless Python

Zappa - Serverless Python About Installation and Configuration Running the Initial Setup / Settings Basic Usage Initial Deployments Updates Rollback S

Rich Jones 11.7k Feb 17, 2021
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 74 Oct 8, 2021
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.4k Oct 23, 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.7k Oct 23, 2021
A familiar HTTP Service Framework for Python.

Responder: a familiar HTTP Service Framework for Python Powered by Starlette. That async declaration is optional. View documentation. This gets you a

Taoufik 3.6k Oct 15, 2021
Django Ninja - Fast Django REST Framework

Django Ninja is a web framework for building APIs with Django and Python 3.6+ type hints.

Vitaliy Kucheryaviy 1.6k Oct 23, 2021
Async Python 3.6+ web server/framework | Build fast. Run fast.

Sanic | Build fast. Run fast. Build Docs Package Support Stats Sanic is a Python 3.6+ web server and web framework that's written to go fast. It allow

Sanic Community Organization 15.5k Oct 21, 2021
Async Python 3.6+ web server/framework | Build fast. Run fast.

Sanic | Build fast. Run fast. Build Docs Package Support Stats Sanic is a Python 3.6+ web server and web framework that's written to go fast. It allow

Sanic Community Organization 15.5k Oct 21, 2021
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.2k Oct 22, 2021
Appier is an object-oriented Python web framework built for super fast app development.

Joyful Python Web App development Appier is an object-oriented Python web framework built for super fast app development. It's as lightweight as possi

Hive Solutions 116 Oct 2, 2021
FastAPI framework, high performance, easy to learn, fast to code, ready for production

FastAPI framework, high performance, easy to learn, fast to code, ready for production Documentation: https://fastapi.tiangolo.com Source Code: https:

Sebastián Ramírez 37.2k Oct 23, 2021
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.6k Oct 15, 2021
An easy-to-use high-performance asynchronous web framework.

An easy-to-use high-performance asynchronous web framework.

Aber 242 Oct 14, 2021
An easy-to-use high-performance asynchronous web framework.

中文 | English 一个易用的高性能异步 web 框架。 Index.py 文档 Index.py 实现了 ASGI3 接口,并使用 Radix Tree 进行路由查找。是最快的 Python web 框架之一。一切特性都服务于快速开发高性能的 Web 服务。 大量正确的类型注释 灵活且高效的

Index.py 243 Oct 15, 2021
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.3k Oct 12, 2021
A micro web-framework using asyncio coroutines and chained middleware.

Growler master ' dev Growler is a web framework built atop asyncio, the asynchronous library described in PEP 3156 and added to the standard library i

null 686 Aug 12, 2021