Repository: miguelgrinberg/aioflask
Branch: main
Commit: 7f447e79c81c
Files: 60
Total size: 89.0 KB
Directory structure:
gitextract_sepaq9a1/
├── .github/
│ └── workflows/
│ └── tests.yml
├── .gitignore
├── .readthedocs.yaml
├── CHANGES.md
├── LICENSE
├── README.md
├── examples/
│ ├── AsyncProgressBar/
│ │ ├── README.md
│ │ ├── progress_bar.py
│ │ └── requirements.txt
│ ├── aioflaskr/
│ │ ├── .flaskenv
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── flaskr/
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ ├── blog.py
│ │ │ ├── models.py
│ │ │ ├── static/
│ │ │ │ └── style.css
│ │ │ └── templates/
│ │ │ ├── auth/
│ │ │ │ ├── login.html
│ │ │ │ └── register.html
│ │ │ ├── base.html
│ │ │ └── blog/
│ │ │ ├── create.html
│ │ │ ├── index.html
│ │ │ └── update.html
│ │ ├── requirements.txt
│ │ └── tests/
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_auth.py
│ │ ├── test_blog.py
│ │ └── test_init.py
│ ├── g/
│ │ └── app.py
│ ├── hello_world/
│ │ ├── app.py
│ │ └── templates/
│ │ └── index.html
│ ├── login/
│ │ └── app.py
│ ├── quotes-aiohttp/
│ │ ├── README.md
│ │ └── quotes.py
│ └── quotes-requests/
│ ├── README.md
│ ├── quotes.py
│ └── quotes_app.py
├── pyproject.toml
├── setup.cfg
├── setup.py
├── src/
│ └── aioflask/
│ ├── __init__.py
│ ├── app.py
│ ├── asgi.py
│ ├── cli.py
│ ├── ctx.py
│ ├── patch.py
│ ├── patched/
│ │ ├── __init__.py
│ │ └── flask_login/
│ │ └── __init__.py
│ ├── templating.py
│ └── testing.py
├── tests/
│ ├── __init__.py
│ ├── templates/
│ │ └── template.html
│ ├── test_app.py
│ ├── test_cli.py
│ ├── test_ctx.py
│ ├── test_patch.py
│ ├── test_templating.py
│ └── utils.py
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/tests.yml
================================================
name: build
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: python -m pip install --upgrade pip wheel
- run: pip install tox tox-gh-actions
- run: tox -eflake8
tests:
name: tests
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: ['3.7', '3.8', '3.9', '3.10']
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- run: python -m pip install --upgrade pip wheel
- run: pip install tox tox-gh-actions
- run: tox
coverage:
name: coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: python -m pip install --upgrade pip wheel
- run: pip install tox tox-gh-actions codecov
- run: tox
- run: codecov
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
================================================
FILE: .readthedocs.yaml
================================================
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
python:
install:
- method: pip
path: .
extra_requirements:
- docs
================================================
FILE: CHANGES.md
================================================
# aioflask change log
**Release 0.4.0** - 2021-08-18
- Support for app factory functions with uvicorn ([commit](https://github.com/miguelgrinberg/aioflask/commit/7f51ca835a5b581b28915b9818428ea09f720081))
- Make app context async ([commit](https://github.com/miguelgrinberg/aioflask/commit/d07ea5449389ae58b286ceff389386e9481e6715))
- Make request context async ([commit](https://github.com/miguelgrinberg/aioflask/commit/4232b2819ad7cf3ed386578a679ba2cbc75f91b0))
- Make test and cli runner clients async ([commit](https://github.com/miguelgrinberg/aioflask/commit/12408cb1d6018fabac8d2749607687164fb1da50))
- Patcher for 3rd party decorators without async view support ([commit](https://github.com/miguelgrinberg/aioflask/commit/b7d4433acd153c43463bd047ddfa19b8c2087078))
- Flask-Login support ([commit #1](https://github.com/miguelgrinberg/aioflask/commit/cbe8abcc0d890bc03787b75ba3c7cb78d5333f38)) ([commit #2](https://github.com/miguelgrinberg/aioflask/commit/e0ab0e0fe1a3b51c3dc3b35abc47b21002f034c3))
- Fix handling of application context ([commit](https://github.com/miguelgrinberg/aioflask/commit/f0b14856b58bd1e85b2b054cd6b3028da0f89091)) ([commit #3](https://github.com/miguelgrinberg/aioflask/commit/a6f5a67a1d9eaa4046d075c1417f5c042dd30c38))
- More unit tests ([commit](https://github.com/miguelgrinberg/aioflask/commit/cf061caa60c9c32975db7560de6c6e8dbe746e7d)) ([commit](https://github.com/miguelgrinberg/aioflask/commit/28f6bd5d62baa8857310aedd2ba728ab3e7322b6)) ([commit](https://github.com/miguelgrinberg/aioflask/commit/0b5f9e7bb0ba98f3c291dd5aa2c2e55ebce4aa61)) ([commit](https://github.com/miguelgrinberg/aioflask/commit/d4275e15474906c30acb80acac0a41766ef1d5d7))
- Update example documentation to use `flask aiorun` ([commit](https://github.com/miguelgrinberg/aioflask/commit/634ee10b7cbc934fa70d512a66334e78ddc39b3a))
**Release 0.3.0** - 2021-06-07
- Test client support, and some more unit tests ([commit](https://github.com/miguelgrinberg/aioflask/commit/c765e12f6382d685bbec1861dac062c13d63aea3))
- Started a change log ([commit](https://github.com/miguelgrinberg/aioflask/commit/e6f4c3a87964fb2e5ea3f4464853c2a1d5ecfc29))
- Improved example code ([commit](https://github.com/miguelgrinberg/aioflask/commit/496ce73f3f0ecb1bdbdd25bd957ce08e6742c191))
- One more example ([commit](https://github.com/miguelgrinberg/aioflask/commit/7edab525809f7ba19562f67bb363a033563d6158))
**Release 0.2.0** - 2021-05-15
- Flask 2.x changes ([commit](https://github.com/miguelgrinberg/aioflask/commit/52aef31fb9a7f8fe6a54b156fe257db1300c0ca6))
- Update README.md ([commit](https://github.com/miguelgrinberg/aioflask/commit/c232561ff3e1c954c49ab362be030da854ceb8ba))
- codecov.io integration ([commit](https://github.com/miguelgrinberg/aioflask/commit/d558dfde5f0717dc6f9b6ff0cedec142ffe60335))
- github actions build ([commit](https://github.com/miguelgrinberg/aioflask/commit/c5f43dacae3d8c73ac0c55a7a58b7c9ac985195a))
**Release 0.1** - 2020-11-07
- async render_template and CLI commands ([commit](https://github.com/miguelgrinberg/aioflask/commit/2e6944c111bd581e1c0eb345ffe88cb1ec014140))
- travis builds ([commit](https://github.com/miguelgrinberg/aioflask/commit/5834c8526fffe424bccfcbe62aa03e33c81b3018))
- app.run implementation and debug mode fixes ([commit](https://github.com/miguelgrinberg/aioflask/commit/2dc8426b5e5e52309639aa31db1f845c44226259))
- add note about the experimental nature of this thing ([commit](https://github.com/miguelgrinberg/aioflask/commit/f027e5ba95cc16ed3c513525d88a197c22001784))
- initial version ([commit](https://github.com/miguelgrinberg/aioflask/commit/4f1d1a343642fa88f76a4cc064f1d7268c9d7dc7))
- Initial commit ([commit](https://github.com/miguelgrinberg/aioflask/commit/b02c360cae72d2f7dd479c93e0cd7517d4dce259))
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Miguel Grinberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# aioflask
 [](https://codecov.io/gh/miguelgrinberg/aioflask)
Flask 2.x running on asyncio!
Is there a purpose for this, now that Flask 2.0 is out with support for async
views? Yes! Flask's own support for async handlers is very limited, as the
application still runs inside a WSGI web server, which severely limits
scalability. With aioflask you get a true ASGI application, running in a 100%
async environment.
WARNING: This is an experiment at this point. Not at all production ready!
## Quick start
To use async view functions and other handlers, use the `aioflask` package
instead of `flask`.
The `aioflask.Flask` class is a subclass of `flask.Flask` that changes a few
minor things to help the application run properly under the asyncio loop. In
particular, it overrides the following aspects of the application instance:
- The `route`, `before_request`, `before_first_request`, `after_request`,
`teardown_request`, `teardown_appcontext`, `errorhandler` and `cli.command`
decorators accept coroutines as well as regular functions. The handlers all
run inside an asyncio loop, so when using regular functions, care must be
taken to not block.
- The WSGI callable entry point is replaced with an ASGI equivalent.
- The `run()` method uses uvicorn as web server.
There are also changes outside of the `Flask` class:
- The `flask aiorun` command starts an ASGI application using the uvicorn web
server.
- The `render_template()` and `render_template_string()` functions are
asynchronous and must be awaited.
- The context managers for the Flask application and request contexts are
async.
- The test client and test CLI runner use coroutines.
## Example
```python
import asyncio
from aioflask import Flask, render_template
app = Flask(__name__)
@app.route('/')
async def index():
await asyncio.sleep(1)
return await render_template('index.html')
```
================================================
FILE: examples/AsyncProgressBar/README.md
================================================
AsyncProgressBar
================
This is the *AsyncProgressBar* from Quart ported to Flask. You need to have a
Redis server running on localhost:6379 for this example to run.
================================================
FILE: examples/AsyncProgressBar/progress_bar.py
================================================
import asyncio
import random
import aioredis
import redis
from aioflask import Flask, request, url_for, jsonify
app = Flask(__name__)
sr = redis.StrictRedis(host='localhost', port=6379)
sr.execute_command('FLUSHDB')
async def some_work():
global aredis
await aredis.set('state', 'running')
work_to_do = range(1, 26)
await aredis.set('length_of_work', len(work_to_do))
for i in work_to_do:
await aredis.set('processed', i)
await asyncio.sleep(random.random())
await aredis.set('state', 'ready')
await aredis.set('percent', 100)
@app.route('/check_status/')
async def check_status():
global aredis, sr
status = dict()
try:
if await aredis.get('state') == b'running':
if await aredis.get('processed') != await aredis.get('lastProcessed'):
await aredis.set('percent', round(
int(await aredis.get('processed')) / int(await aredis.get('length_of_work')) * 100, 2))
await aredis.set('lastProcessed', str(await aredis.get('processed')))
except:
pass
try:
status['state'] = sr.get('state').decode()
status['processed'] = sr.get('processed').decode()
status['length_of_work'] = sr.get('length_of_work').decode()
status['percent_complete'] = sr.get('percent').decode()
except:
status['state'] = sr.get('state')
status['processed'] = sr.get('processed')
status['length_of_work'] = sr.get('length_of_work')
status['percent_complete'] = sr.get('percent')
status['hint'] = 'refresh me.'
return jsonify(status)
@app.route('/progress/')
async def progress():
return """
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asyncio Progress Bar Demo</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
var percent;
function checkStatus() {
$.getJSON('""" + url_for('check_status') + """', function (data) {
console.log(data);
percent = parseFloat(data.percent_complete);
update_bar(percent);
update_text(percent);
});
if (percent != 100) {
setTimeout(checkStatus, 1000);
}
}
function update_bar(val) {
if (val.length <= 0) {
val = 0;
}
$( "#progressBar" ).progressbar({
value: val
});
};
function update_text(val) {
if (val != 100) {
document.getElementById("progressData").innerHTML = " <center>"+percent+"%</center>";
} else {
document.getElementById("progressData").innerHTML = " <center>Done!</center>";
}
}
checkStatus();
</script>
</head>
<body>
<center><h2>Progress of work is shown below</h2></center>
<div id="progressBar"></div>
<div id="progressData" name="progressData"><center></center></div>
</body>
</html>"""
@app.route('/')
async def index():
return 'This is the index page. Try the following to <a href="' + url_for(
'start_work') + '">start some test work</a> with a progress indicator.'
@app.route('/start_work/')
async def start_work():
global aredis
loop = asyncio.get_event_loop()
aredis = await aioredis.create_redis('redis://localhost', loop=loop)
if await aredis.get('state') == b'running':
return "<center>Please wait for current work to finish.</center>"
else:
await aredis.set('state', 'ready')
if await aredis.get('state') == b'ready':
loop.create_task(some_work())
body = '''
<center>
work started!
</center>
<script type="text/javascript">
window.location = "''' + url_for('progress') + '''";
</script>'''
return body
if __name__ == "__main__":
app.run('localhost', port=5000, debug=True)
================================================
FILE: examples/AsyncProgressBar/requirements.txt
================================================
aioflask
aioredis==1.3.1
async-timeout==3.0.1
click==7.1.2
Flask==1.1.2
greenlet==0.4.16
greenletio
h11==0.9.0
hiredis==1.1.0
httptools==0.1.1
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
redis==3.5.3
uvicorn==0.11.6
uvloop==0.14.0
websockets==8.1
Werkzeug==1.0.1
================================================
FILE: examples/aioflaskr/.flaskenv
================================================
FLASK_APP=flaskr:create_app()
================================================
FILE: examples/aioflaskr/LICENSE
================================================
Copyright 2010 Pallets (original version)
Copyright 2021 Miguel Grinberg (this version)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: examples/aioflaskr/README.md
================================================
aioflaskr
=========
This is the "Flaskr" application from the tutorial section of the Flask
documentation, adapted to work as an asyncio application with aioflask as the
web framework and Alchemical for its database. The Flask-Login extension is
used to maintain the logged in state of the user.
Install
-------
```bash
# clone the repository
$ git clone https://github.com/miguelgrinberg/aioflask
$ cd aioflask/examples/aioflaskr
```
Create a virtualenv and activate it:
```bash
$ python3 -m venv venv
$ . venv/bin/activate
```
Or on Windows cmd:
```text
$ py -3 -m venv venv
$ venv\Scripts\activate.bat
```
Install the requirements
```bash
$ pip install -r requirements.txt
```
Run
---
```bash
flask init-db
flask aiorun
```
Open http://127.0.0.1:5000 in a browser.
Test
----
```bash
$ pip install pytest pytest-asyncio
$ python -m pytest
```
Run with coverage report:
```bash
$ pip install pytest-cov
$ python -m pytest --cov=flaskr --cov-branch --cov-report=term-missing
```
================================================
FILE: examples/aioflaskr/flaskr/__init__.py
================================================
import os
import click
from aioflask import Flask
from aioflask.cli import with_appcontext
from alchemical.aioflask import Alchemical
from aioflask.patched.flask_login import LoginManager
db = Alchemical()
login = LoginManager()
login.login_view = 'auth.login'
def create_app(test_config=None):
"""Create and configure an instance of the Flask application."""
app = Flask(__name__, instance_relative_config=True)
# some deploy systems set the database url in the environ
db_url = os.environ.get("DATABASE_URL")
if db_url is None:
# default to a sqlite database in the instance folder
db_path = os.path.join(app.instance_path, "flaskr.sqlite")
db_url = f"sqlite:///{db_path}"
# ensure the instance folder exists
os.makedirs(app.instance_path, exist_ok=True)
app.config.from_mapping(
SECRET_KEY=os.environ.get("SECRET_KEY", "dev"),
ALCHEMICAL_DATABASE_URL=db_url,
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile("config.py", silent=True)
else:
# load the test config if passed in
app.config.update(test_config)
# initialize Flask-Alchemical and the init-db command
db.init_app(app)
app.cli.add_command(init_db_command)
# initialize Flask-Login
login.init_app(app)
# apply the blueprints to the app
from flaskr import auth, blog
app.register_blueprint(auth.bp)
app.register_blueprint(blog.bp)
# make "index" point at "/", which is handled by "blog.index"
app.add_url_rule("/", endpoint="index")
return app
async def init_db():
await db.drop_all()
await db.create_all()
@click.command("init-db")
@with_appcontext
async def init_db_command():
"""Clear existing data and create new tables."""
await init_db()
click.echo("Initialized the database.")
================================================
FILE: examples/aioflaskr/flaskr/auth.py
================================================
from aioflask import Blueprint
from aioflask import flash
from aioflask import redirect
from aioflask import render_template
from aioflask import request
from aioflask import url_for
from aioflask.patched.flask_login import login_user
from aioflask.patched.flask_login import logout_user
from sqlalchemy.exc import IntegrityError
from flaskr import db, login
from flaskr.models import User
bp = Blueprint("auth", __name__, url_prefix="/auth")
@login.user_loader
async def load_user(id):
return await db.session.get(User, int(id))
@bp.route("/register", methods=("GET", "POST"))
async def register():
"""Register a new user.
Validates that the username is not already taken. Hashes the
password for security.
"""
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
error = None
if not username:
error = "Username is required."
elif not password:
error = "Password is required."
if error is None:
try:
db.session.add(User(username=username, password=password))
await db.session.commit()
except IntegrityError:
# The username was already taken, which caused the
# commit to fail. Show a validation error.
error = f"User {username} is already registered."
else:
# Success, go to the login page.
return redirect(url_for("auth.login"))
flash(error)
return await render_template("auth/register.html")
@bp.route("/login", methods=("GET", "POST"))
async def login():
"""Log in a registered user by adding the user id to the session."""
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
error = None
query = User.select().filter_by(username=username)
user = await db.session.scalar(query)
if user is None:
error = "Incorrect username."
elif not user.check_password(password):
error = "Incorrect password."
if error is None:
# store the user id in a new session and return to the index
login_user(user)
return redirect(url_for("index"))
flash(error)
return await render_template("auth/login.html")
@bp.route("/logout")
async def logout():
"""Clear the current session, including the stored user id."""
logout_user()
return redirect(url_for("index"))
================================================
FILE: examples/aioflaskr/flaskr/blog.py
================================================
from aioflask import Blueprint
from aioflask import flash
from aioflask import redirect
from aioflask import render_template
from aioflask import request
from aioflask import url_for
from werkzeug.exceptions import abort
from aioflask.patched.flask_login import current_user
from aioflask.patched.flask_login import login_required
from flaskr import db
from flaskr.models import Post
bp = Blueprint("blog", __name__)
@bp.route("/")
async def index():
"""Show all the posts, most recent first."""
posts = (await db.session.scalars(Post.select())).all()
return await render_template("blog/index.html", posts=posts)
async def get_post(id, check_author=True):
"""Get a post and its author by id.
Checks that the id exists and optionally that the current user is
the author.
:param id: id of post to get
:param check_author: require the current user to be the author
:return: the post with author information
:raise 404: if a post with the given id doesn't exist
:raise 403: if the current user isn't the author
"""
post = await db.session.get(Post, id)
if post is None:
abort(404, f"Post id {id} doesn't exist.")
if check_author and post.author != current_user:
abort(403)
return post
@bp.route("/create", methods=("GET", "POST"))
@login_required
async def create():
"""Create a new post for the current user."""
if request.method == "POST":
title = request.form["title"]
body = request.form["body"]
error = None
if not title:
error = "Title is required."
if error is not None:
flash(error)
else:
db.session.add(Post(title=title, body=body, author=current_user))
await db.session.commit()
return redirect(url_for("blog.index"))
return await render_template("blog/create.html")
@bp.route("/<int:id>/update", methods=("GET", "POST"))
@login_required
async def update(id):
"""Update a post if the current user is the author."""
post = await get_post(id)
if request.method == "POST":
title = request.form["title"]
body = request.form["body"]
error = None
if not title:
error = "Title is required."
if error is not None:
flash(error)
else:
post.title = title
post.body = body
await db.session.commit()
return redirect(url_for("blog.index"))
return await render_template("blog/update.html", post=post)
@bp.route("/<int:id>/delete", methods=("POST",))
@login_required
async def delete(id):
"""Delete a post.
Ensures that the post exists and that the logged in user is the
author of the post.
"""
post = await get_post(id)
await db.session.delete(post)
await db.session.commit()
return redirect(url_for("blog.index"))
================================================
FILE: examples/aioflaskr/flaskr/models.py
================================================
from werkzeug.security import check_password_hash
from werkzeug.security import generate_password_hash
from aioflask import url_for
from aioflask.patched.flask_login import UserMixin
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func
from sqlalchemy.orm import relationship
from flaskr import db
class User(UserMixin, db.Model):
id = Column(Integer, primary_key=True)
username = Column(String, unique=True, nullable=False)
password_hash = Column(String, nullable=False)
@property
def password(self):
raise RuntimeError('Cannot get user passwords!')
@password.setter
def password(self, value):
"""Store the password as a hash for security."""
self.password_hash = generate_password_hash(value)
def check_password(self, value):
return check_password_hash(self.password_hash, value)
class Post(db.Model):
id = Column(Integer, primary_key=True)
author_id = Column(ForeignKey(User.id), nullable=False)
created = Column(
DateTime, nullable=False, server_default=func.current_timestamp()
)
title = Column(String, nullable=False)
body = Column(String, nullable=False)
# User object backed by author_id
# lazy="joined" means the user is returned with the post in one query
author = relationship(User, lazy="joined", backref="posts")
@property
def update_url(self):
return url_for("blog.update", id=self.id)
@property
def delete_url(self):
return url_for("blog.delete", id=self.id)
================================================
FILE: examples/aioflaskr/flaskr/static/style.css
================================================
html {
font-family: sans-serif;
background: #eee;
padding: 1rem;
}
body {
max-width: 960px;
margin: 0 auto;
background: white;
}
h1, h2, h3, h4, h5, h6 {
font-family: serif;
color: #377ba8;
margin: 1rem 0;
}
a {
color: #377ba8;
}
hr {
border: none;
border-top: 1px solid lightgray;
}
nav {
background: lightgray;
display: flex;
align-items: center;
padding: 0 0.5rem;
}
nav h1 {
flex: auto;
margin: 0;
}
nav h1 a {
text-decoration: none;
padding: 0.25rem 0.5rem;
}
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
nav ul li a, nav ul li span, header .action {
display: block;
padding: 0.5rem;
}
.content {
padding: 0 1rem 1rem;
}
.content > header {
border-bottom: 1px solid lightgray;
display: flex;
align-items: flex-end;
}
.content > header h1 {
flex: auto;
margin: 1rem 0 0.25rem 0;
}
.flash {
margin: 1em 0;
padding: 1em;
background: #cae6f6;
border: 1px solid #377ba8;
}
.post > header {
display: flex;
align-items: flex-end;
font-size: 0.85em;
}
.post > header > div:first-of-type {
flex: auto;
}
.post > header h1 {
font-size: 1.5em;
margin-bottom: 0;
}
.post .about {
color: slategray;
font-style: italic;
}
.post .body {
white-space: pre-line;
}
.content:last-child {
margin-bottom: 0;
}
.content form {
margin: 1em 0;
display: flex;
flex-direction: column;
}
.content label {
font-weight: bold;
margin-bottom: 0.5em;
}
.content input, .content textarea {
margin-bottom: 1em;
}
.content textarea {
min-height: 12em;
resize: vertical;
}
input.danger {
color: #cc2f2e;
}
input[type=submit] {
align-self: start;
min-width: 10em;
}
================================================
FILE: examples/aioflaskr/flaskr/templates/auth/login.html
================================================
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% endblock %}
================================================
FILE: examples/aioflaskr/flaskr/templates/auth/register.html
================================================
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Register">
</form>
{% endblock %}
================================================
FILE: examples/aioflaskr/flaskr/templates/base.html
================================================
<!doctype html>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1><a href="{{ url_for('index') }}">Flaskr</a></h1>
<ul>
{% if current_user.is_authenticated %}
<li><span>{{ current_user.username }}</span>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</ul>
</nav>
<section class="content">
<header>
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</section>
================================================
FILE: examples/aioflaskr/flaskr/templates/blog/create.html
================================================
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
<input type="submit" value="Save">
</form>
{% endblock %}
================================================
FILE: examples/aioflaskr/flaskr/templates/blog/index.html
================================================
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Posts{% endblock %}</h1>
{% if current_user.is_authenticated %}
<a class="action" href="{{ url_for('blog.create') }}">New</a>
{% endif %}
{% endblock %}
{% block content %}
{% for post in posts %}
<article class="post">
<header>
<div>
<h1>{{ post.title }}</h1>
<div class="about">by {{ post.author.username }} on {{ post.created.strftime('%Y-%m-%d') }}</div>
</div>
{% if current_user == post.author %}
<a class="action" href="{{ post.update_url }}">Edit</a>
{% endif %}
</header>
<p class="body">{{ post.body }}</p>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}
================================================
FILE: examples/aioflaskr/flaskr/templates/blog/update.html
================================================
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] or post['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
<input type="submit" value="Save">
</form>
<hr>
<form action="{{ post.delete_url }}" method="post">
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
</form>
{% endblock %}
================================================
FILE: examples/aioflaskr/requirements.txt
================================================
aioflask
aiosqlite==0.17.0
alchemical
asgiref==3.4.1
click==8.0.1
Flask==2.0.1
Flask-Login==0.5.0
greenlet==1.1.1
greenletio
h11==0.12.0
importlib-metadata==4.6.3
itsdangerous==2.0.1
Jinja2==3.0.1
MarkupSafe==2.0.1
python-dotenv==0.19.0
SQLAlchemy==1.4.25
typing-extensions==3.10.0.0
uvicorn==0.14.0
Werkzeug==2.0.1
zipp==3.5.0
================================================
FILE: examples/aioflaskr/tests/__init__.py
================================================
================================================
FILE: examples/aioflaskr/tests/conftest.py
================================================
from datetime import datetime
import pytest
from flaskr import create_app
from flaskr import db
from flaskr import init_db
from flaskr.models import User
from flaskr.models import Post
@pytest.fixture
async def app():
"""Create and configure a new app instance for each test."""
# create the app with common test config
app = create_app({"TESTING": True,
"ALCHEMICAL_DATABASE_URL": "sqlite:///:memory:"})
# create the database and load test data
async with app.app_context():
await init_db()
user = User(username="test", password="test")
db.session.add_all(
(
user,
User(username="other", password="other"),
Post(
title="test title",
body="test\nbody",
author=user,
created=datetime(2018, 1, 1),
),
)
)
await db.session.commit()
yield app
@pytest.fixture
def client(app):
"""A test client for the app."""
return app.test_client()
@pytest.fixture
def runner(app):
"""A test runner for the app's Click commands."""
return app.test_cli_runner()
class AuthActions:
def __init__(self, client):
self._client = client
async def login(self, username="test", password="test"):
return await self._client.post(
"/auth/login", data={"username": username, "password": password}
)
async def logout(self):
return await self._client.get("/auth/logout")
@pytest.fixture
def auth(client):
return AuthActions(client)
================================================
FILE: examples/aioflaskr/tests/test_auth.py
================================================
import pytest
from flaskr import db
from flaskr.models import User
@pytest.mark.asyncio
async def test_register(client, app):
# test that viewing the page renders without template errors
assert (await client.get("/auth/register")).status_code == 200
# test that successful registration redirects to the login page
response = await client.post("/auth/register",
data={"username": "a", "password": "a"})
assert "/auth/login" == response.headers["Location"]
# test that the user was inserted into the database
async with app.app_context():
query = User.select().filter_by(username="a")
assert await db.session.scalar(query) is not None
def test_user_password(app):
user = User(username="a", password="a")
assert user.password_hash != "a"
assert user.check_password("a")
@pytest.mark.asyncio
@pytest.mark.parametrize(
("username", "password", "message"),
(
("", "", b"Username is required."),
("a", "", b"Password is required."),
("test", "test", b"already registered"),
),
)
async def test_register_validate_input(client, username, password, message):
response = await client.post(
"/auth/register", data={"username": username, "password": password}
)
assert message in response.data
@pytest.mark.asyncio
async def test_login(client, auth):
# test that viewing the page renders without template errors
assert (await client.get("/auth/login")).status_code == 200
# test that successful login redirects to the index page
response = await auth.login()
assert response.headers["Location"] == "/"
# login request set the user_id in the session
# check that the user is loaded from the session
async with client:
response = await client.get("/")
assert b"<span>test</span>" in response.data
@pytest.mark.asyncio
@pytest.mark.parametrize(
("username", "password", "message"),
(("a", "test", b"Incorrect username."),
("test", "a", b"Incorrect password.")),
)
async def test_login_validate_input(auth, username, password, message):
response = await auth.login(username, password)
assert message in response.data
@pytest.mark.asyncio
async def test_logout(client, auth):
await auth.login()
async with client:
await auth.logout()
response = await client.get("/")
assert b"Log In" in response.data
================================================
FILE: examples/aioflaskr/tests/test_blog.py
================================================
import pytest
from sqlalchemy import func
from sqlalchemy import select
from flaskr import db
from flaskr.models import User
from flaskr.models import Post
@pytest.mark.asyncio
async def test_index(client, auth):
response = await client.get("/")
assert b"Log In" in response.data
assert b"Register" in response.data
await auth.login()
response = await client.get("/")
assert b"test title" in response.data
assert b"by test on 2018-01-01" in response.data
assert b"test\nbody" in response.data
assert b'href="/1/update"' in response.data
@pytest.mark.asyncio
@pytest.mark.parametrize("path", ("/create", "/1/update", "/1/delete"))
async def test_login_required(client, path):
response = await client.post(path)
assert response.headers["Location"].startswith("/auth/login?next=")
@pytest.mark.asyncio
async def test_author_required(app, client, auth):
# change the post author to another user
async with app.app_context():
(await db.session.get(Post, 1)).author = await db.session.get(User, 2)
await db.session.commit()
await auth.login()
# current user can't modify other user's post
assert (await client.post("/1/update")).status_code == 403
assert (await client.post("/1/delete")).status_code == 403
# current user doesn't see edit link
assert b'href="/1/update"' not in (await client.get("/")).data
@pytest.mark.asyncio
@pytest.mark.parametrize("path", ("/2/update", "/2/delete"))
async def test_exists_required(client, auth, path):
await auth.login()
assert (await client.post(path)).status_code == 404
@pytest.mark.asyncio
async def test_create(client, auth, app):
await auth.login()
assert (await client.get("/create")).status_code == 200
await client.post("/create", data={"title": "created", "body": ""})
async with app.app_context():
query = select(func.count()).select_from(Post)
assert await db.session.scalar(query) == 2
@pytest.mark.asyncio
async def test_update(client, auth, app):
await auth.login()
assert (await client.get("/1/update")).status_code == 200
await client.post("/1/update", data={"title": "updated", "body": ""})
async with app.app_context():
assert (await db.session.get(Post, 1)).title == "updated"
@pytest.mark.asyncio
@pytest.mark.parametrize("path", ("/create", "/1/update"))
async def test_create_update_validate(client, auth, path):
await auth.login()
response = await client.post(path, data={"title": "", "body": ""})
assert b"Title is required." in response.data
@pytest.mark.asyncio
async def test_delete(client, auth, app):
await auth.login()
response = await client.post("/1/delete")
assert response.headers["Location"] == "/"
async with app.app_context():
assert (await db.session.get(Post, 1)) is None
================================================
FILE: examples/aioflaskr/tests/test_init.py
================================================
import pytest
from flaskr import create_app
def test_config():
"""Test create_app without passing test config."""
assert not create_app().testing
assert create_app({"TESTING": True}).testing
def test_db_url_environ(monkeypatch):
"""Test DATABASE_URL environment variable."""
monkeypatch.setenv("DATABASE_URL", "sqlite:///environ")
app = create_app()
assert app.config["ALCHEMICAL_DATABASE_URL"] == "sqlite:///environ"
@pytest.mark.asyncio
async def test_init_db_command(runner, monkeypatch):
class Recorder:
called = False
async def fake_init_db():
Recorder.called = True
monkeypatch.setattr("flaskr.init_db", fake_init_db)
result = await runner.invoke(args=["init-db"])
assert "Initialized" in result.output
assert Recorder.called
================================================
FILE: examples/g/app.py
================================================
from aioflask import Flask, g
import aiohttp
app = Flask(__name__)
@app.before_request
async def before_request():
g.session = aiohttp.ClientSession()
@app.teardown_appcontext
async def teardown_appcontext(exc):
await g.session.close()
@app.route('/')
async def index():
response = await g.session.get('https://api.quotable.io/random')
return (await response.json())['content']
================================================
FILE: examples/hello_world/app.py
================================================
from aioflask import Flask, render_template
app = Flask(__name__)
@app.route('/')
async def index():
return await render_template('index.html')
@app.cli.command()
async def hello():
"""Example async CLI handler."""
print('hello!')
================================================
FILE: examples/hello_world/templates/index.html
================================================
<!doctype html>
<html>
<head>
<title>Hello (async) world!</title>
</head>
<body>
<h1>Hello (async) world!</h1>
</body>
</html>
================================================
FILE: examples/login/app.py
================================================
from aioflask import Flask, request, redirect
from aioflask.patched.flask_login import LoginManager, login_required, UserMixin, login_user, logout_user, current_user
import aiohttp
app = Flask(__name__)
app.secret_key = 'top-secret!'
login = LoginManager(app)
login.login_view = 'login'
class User(UserMixin):
def __init__(self, user_id):
self.id = user_id
@login.user_loader
async def load_user(user_id):
return User(user_id)
@app.route('/')
@login_required
async def index():
return f'''
<html>
<body>
<p>Logged in user: {current_user.id}</p>
<form method="POST" action="/logout">
<input type="submit" value="Logout">
</form>
</body>
</html>'''
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return '''
<html>
<body>
<form method="POST" action="">
<input type="text" name="username">
<input type="submit" value="Login">
</form>
</body>
</html>'''
else:
login_user(User(request.form['username']))
return redirect(request.args.get('next', '/'))
@app.route('/logout', methods=['POST'])
def logout():
logout_user()
return redirect('/')
================================================
FILE: examples/quotes-aiohttp/README.md
================================================
Quotes
======
Returns 10 famous quotes each time the page is refreshed. Quotes are obtained
by sending concurrent HTTP requests to a Quotes API with the aiohttp
asynchronous client.
To run this example, set `FLASK_APP=quotes.py` in your environment and then use
the standard `flask aiorun` command to start the server.
================================================
FILE: examples/quotes-aiohttp/quotes.py
================================================
import asyncio
import aiohttp
from aioflask import Flask, render_template_string
app = Flask(__name__)
template = '''<!doctype html>
<html>
<head>
<title>Quotes</title>
</head>
<body>
<h1>Quotes</h1>
{% for quote in quotes %}
<p><i>"{{ quote.content }}"</i> — {{ quote.author }}</p>
{% endfor %}
</body>
</html>'''
async def get_quote(session):
response = await session.get('https://api.quotable.io/random')
return await response.json()
@app.route('/')
async def index():
async with aiohttp.ClientSession() as session:
tasks = [get_quote(session) for _ in range(10)]
quotes = await asyncio.gather(*tasks)
return await render_template_string(template, quotes=quotes)
================================================
FILE: examples/quotes-requests/README.md
================================================
Quotes
======
Returns 10 famous quotes each time the page is refreshed. Quotes are obtained
by sending concurrent HTTP requests to a Quotes API with the requests client.
This example shows how you can incorporate blocking code into your aioflask
application without blocking the asyncio loop.
To run this example, set `FLASK_APP=quotes.py` in your environment and then use
the standard `flask aiorun` command to start the server.
================================================
FILE: examples/quotes-requests/quotes.py
================================================
import greenletio
# import the application with blocking functions monkey patched
with greenletio.patch_blocking():
from quotes_app import app
================================================
FILE: examples/quotes-requests/quotes_app.py
================================================
import asyncio
from aioflask import Flask, render_template_string
from greenletio import async_
import requests
app = Flask(__name__)
template = '''<!doctype html>
<html>
<head>
<title>Quotes</title>
</head>
<body>
<h1>Quotes</h1>
{% for quote in quotes %}
<p><i>"{{ quote.content }}"</i> — {{ quote.author }}</p>
{% endfor %}
</body>
</html>'''
# this is a blocking function that is converted to asynchronous with
# greenletio's @async_ decorator. For this to work, all the low-level I/O
# functions started from this function must be asynchronous, which can be
# achieved with greenletio's monkey patching feature.
@async_
def get_quote():
response = requests.get('https://api.quotable.io/random')
return response.json()
@app.route('/')
async def index():
tasks = [get_quote() for _ in range(10)]
quotes = await asyncio.gather(*tasks)
return await render_template_string(template, quotes=quotes)
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"
================================================
FILE: setup.cfg
================================================
[metadata]
name = aioflask
version = 0.4.1.dev0
author = Miguel Grinberg
author_email = miguel.grinberg@gmail.com
description = Flask running on asyncio.
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/miguelgrinberg/aioflask
project_urls =
Bug Tracker = https://github.com/miguelgrinberg/aioflask/issues
classifiers =
Intended Audience :: Developers
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: OS Independent
[options]
zip_safe = False
include_package_data = True
package_dir =
= src
packages = find:
python_requires = >=3.6
install_requires =
greenletio
flask >= 2
uvicorn
[options.packages.find]
where = src
[options.entry_points]
flask.commands =
aiorun = aioflask.cli:run_command
[options.extras_require]
docs =
sphinx
================================================
FILE: setup.py
================================================
import setuptools
setuptools.setup()
================================================
FILE: src/aioflask/__init__.py
================================================
from flask import *
from .app import Flask
from .templating import render_template, render_template_string
from .testing import FlaskClient
================================================
FILE: src/aioflask/app.py
================================================
import asyncio
from functools import wraps
from inspect import iscoroutinefunction
import os
from flask.app import *
from flask.app import Flask as OriginalFlask
from flask import cli
from flask.globals import _app_ctx_stack, _request_ctx_stack
from flask.helpers import get_debug_flag, get_env, get_load_dotenv
from greenletio import await_
import uvicorn
from .asgi import WsgiToAsgiInstance
from .cli import show_server_banner, AppGroup
from .ctx import AppContext, RequestContext
from .testing import FlaskClient, FlaskCliRunner
class Flask(OriginalFlask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.cli = AppGroup()
self.jinja_options['enable_async'] = True
self.test_client_class = FlaskClient
self.test_cli_runner_class = FlaskCliRunner
self.async_fixed = False
def ensure_sync(self, func):
if not iscoroutinefunction(func):
return func
def wrapped(*args, **kwargs):
appctx = _app_ctx_stack.top
reqctx = _request_ctx_stack.top
async def _coro():
# app context is push internally to avoid changing reference
# counts and emitting duplicate signals
_app_ctx_stack.push(appctx)
if reqctx:
_request_ctx_stack.push(reqctx)
ret = await func(*args, **kwargs)
if reqctx:
_request_ctx_stack.pop()
_app_ctx_stack.pop()
return ret
return await_(_coro())
return wrapped
def app_context(self):
return AppContext(self)
def request_context(self, environ):
return RequestContext(self, environ)
def _fix_async(self): # pragma: no cover
self.async_fixed = True
if os.environ.get('AIOFLASK_USE_DEBUGGER') == 'true':
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
from werkzeug.debug import DebuggedApplication
self.wsgi_app = DebuggedApplication(self.wsgi_app, evalex=True)
async def asgi_app(self, scope, receive, send): # pragma: no cover
if not self.async_fixed:
self._fix_async()
return await WsgiToAsgiInstance(self.wsgi_app)(scope, receive, send)
async def __call__(self, scope, receive, send=None): # pragma: no cover
if send is None:
# we were called with two arguments, so this is likely a WSGI app
raise RuntimeError('The WSGI interface is not supported by '
'aioflask, use an ASGI web server instead.')
return await self.asgi_app(scope, receive, send)
def run(self, host=None, port=None, debug=None, load_dotenv=True,
**options):
if get_load_dotenv(load_dotenv):
cli.load_dotenv()
# if set, let env vars override previous values
if "FLASK_ENV" in os.environ:
self.env = get_env()
self.debug = get_debug_flag()
elif "FLASK_DEBUG" in os.environ:
self.debug = get_debug_flag()
# debug passed to method overrides all other sources
if debug is not None:
self.debug = bool(debug)
server_name = self.config.get("SERVER_NAME")
sn_host = sn_port = None
if server_name:
sn_host, _, sn_port = server_name.partition(":")
if not host:
if sn_host:
host = sn_host
else:
host = "127.0.0.1"
if port or port == 0:
port = int(port)
elif sn_port:
port = int(sn_port)
else:
port = 5000
options.setdefault("use_reloader", self.debug)
options.setdefault("use_debugger", self.debug)
options.setdefault("threaded", True)
options.setdefault("workers", 1)
certfile = None
keyfile = None
cert = options.get('ssl_context')
if cert is not None and len(cert) == 2:
certfile = cert[0]
keyfile = cert[1]
elif cert == 'adhoc':
raise RuntimeError(
'Aad-hoc certificates are not supported by aioflask.')
if debug:
os.environ['FLASK_DEBUG'] = 'true'
if options['use_debugger']:
os.environ['AIOFLASK_USE_DEBUGGER'] = 'true'
show_server_banner(self.env, self.debug, self.name, False)
uvicorn.run(
self.import_name + ':app',
host=host,
port=port,
reload=options['use_reloader'],
workers=options['workers'],
log_level='debug' if self.debug else 'info',
ssl_certfile=certfile,
ssl_keyfile=keyfile,
)
================================================
FILE: src/aioflask/asgi.py
================================================
import sys
from tempfile import SpooledTemporaryFile
from greenletio import async_, await_
class wsgi_to_asgi: # pragma: no cover
"""Wraps a WSGI application to make it into an ASGI application."""
def __init__(self, wsgi_application):
self.wsgi_application = wsgi_application
async def __call__(self, scope, receive, send):
"""ASGI application instantiation point.
We return a new WsgiToAsgiInstance here with the WSGI app
and the scope, ready to respond when it is __call__ed.
"""
await WsgiToAsgiInstance(self.wsgi_application)(scope, receive, send)
class WsgiToAsgiInstance: # pragma: no cover
"""Per-socket instance of a wrapped WSGI application"""
def __init__(self, wsgi_application):
self.wsgi_application = wsgi_application
self.response_started = False
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
raise ValueError("WSGI wrapper received a non-HTTP scope")
self.scope = scope
with SpooledTemporaryFile(max_size=65536) as body:
# Alright, wait for the http.request messages
while True:
message = await receive()
if message["type"] != "http.request":
raise ValueError(
"WSGI wrapper received a non-HTTP-request message")
body.write(message.get("body", b""))
if not message.get("more_body"):
break
body.seek(0)
# Wrap send so it can be called from the subthread
self.sync_send = await_(send)
# Call the WSGI app
await self.run_wsgi_app(body)
def build_environ(self, scope, body):
"""Builds a scope and request body into a WSGI environ object."""
environ = {
"REQUEST_METHOD": scope["method"],
"SCRIPT_NAME": scope.get("root_path", ""),
"PATH_INFO": scope["path"],
"QUERY_STRING": scope["query_string"].decode("ascii"),
"SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"],
"wsgi.version": (1, 0),
"wsgi.url_scheme": scope.get("scheme", "http"),
"wsgi.input": body,
"wsgi.errors": sys.stderr,
"wsgi.multithread": True,
"wsgi.multiprocess": True,
"wsgi.run_once": False,
}
# Get server name and port - required in WSGI, not in ASGI
if "server" in scope:
environ["SERVER_NAME"] = scope["server"][0]
environ["SERVER_PORT"] = str(scope["server"][1])
else:
environ["SERVER_NAME"] = "localhost"
environ["SERVER_PORT"] = "80"
if "client" in scope:
environ["REMOTE_ADDR"] = scope["client"][0]
# Go through headers and make them into environ entries
for name, value in self.scope.get("headers", []):
name = name.decode("latin1")
if name == "content-length":
corrected_name = "CONTENT_LENGTH"
elif name == "content-type":
corrected_name = "CONTENT_TYPE"
else:
corrected_name = "HTTP_%s" % name.upper().replace("-", "_")
# HTTPbis say only ASCII chars are allowed in headers, but we
# latin1 just in case
value = value.decode("latin1")
if corrected_name in environ:
value = environ[corrected_name] + "," + value
environ[corrected_name] = value
return environ
def start_response(self, status, response_headers, exc_info=None):
"""WSGI start_response callable."""
# Don't allow re-calling once response has begun
if self.response_started:
raise exc_info[1].with_traceback(exc_info[2])
# Don't allow re-calling without exc_info
if hasattr(self, "response_start") and exc_info is None:
raise ValueError(
"You cannot call start_response a second time without exc_info"
)
# Extract status code
status_code, _ = status.split(" ", 1)
status_code = int(status_code)
# Extract headers
headers = [
(name.lower().encode("ascii"), value.encode("ascii"))
for name, value in response_headers
]
# Build and send response start message.
self.response_start = {
"type": "http.response.start",
"status": status_code,
"headers": headers,
}
@async_
def run_wsgi_app(self, body):
"""WSGI app greenlet."""
# Translate the scope and incoming request body into a WSGI environ
environ = self.build_environ(self.scope, body)
# Run the WSGI app
for output in self.wsgi_application(environ, self.start_response):
# If this is the first response, include the response headers
if not self.response_started:
self.response_started = True
self.sync_send(self.response_start)
self.sync_send({"type": "http.response.body", "body": output,
"more_body": True})
# Close connection
if not self.response_started:
self.response_started = True
self.sync_send(self.response_start)
self.sync_send({"type": "http.response.body"})
================================================
FILE: src/aioflask/cli.py
================================================
from functools import wraps
from inspect import iscoroutinefunction
import os
import sys
from flask.cli import *
from flask.cli import AppGroup, ScriptInfo, update_wrapper, \
SeparatedPathType, pass_script_info, get_debug_flag, NoAppException, \
prepare_import
from flask.cli import _validate_key
from flask.globals import _app_ctx_stack
from flask.helpers import get_env
from greenletio import await_
from werkzeug.utils import import_string
import click
import uvicorn
try:
import ssl
except ImportError: # pragma: no cover
ssl = None
OriginalAppGroup = AppGroup
def _ensure_sync(func, with_appcontext=False):
if not iscoroutinefunction(func):
return func
def decorated(*args, **kwargs):
if with_appcontext:
appctx = _app_ctx_stack.top
@await_
async def _coro():
with appctx:
return await func(*args, **kwargs)
else:
@await_
async def _coro():
return await func(*args, **kwargs)
return _coro()
return decorated
def with_appcontext(f):
"""Wraps a callback so that it's guaranteed to be executed with the
script's application context. If callbacks are registered directly
to the ``app.cli`` object then they are wrapped with this function
by default unless it's disabled.
"""
@click.pass_context
def decorator(__ctx, *args, **kwargs):
with __ctx.ensure_object(ScriptInfo).load_app().app_context():
return __ctx.invoke(_ensure_sync(f, True), *args, **kwargs)
return update_wrapper(decorator, f)
class AppGroup(OriginalAppGroup):
"""This works similar to a regular click :class:`~click.Group` but it
changes the behavior of the :meth:`command` decorator so that it
automatically wraps the functions in :func:`with_appcontext`.
Not to be confused with :class:`FlaskGroup`.
"""
def command(self, *args, **kwargs):
"""This works exactly like the method of the same name on a regular
:class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
unless it's disabled by passing ``with_appcontext=False``.
"""
wrap_for_ctx = kwargs.pop("with_appcontext", True)
def decorator(f):
if wrap_for_ctx:
f = with_appcontext(f)
return click.Group.command(self, *args, **kwargs)(_ensure_sync(f))
return decorator
def show_server_banner(env, debug, app_import_path, eager_loading):
"""Show extra startup messages the first time the server is run,
ignoring the reloader.
"""
if app_import_path is not None:
message = f" * Serving Flask app {app_import_path!r}"
click.echo(message)
click.echo(f" * Environment: {env}")
if debug is not None:
click.echo(f" * Debug mode: {'on' if debug else 'off'}")
class CertParamType(click.ParamType):
"""Click option type for the ``--cert`` option. Allows either an
existing file, the string ``'adhoc'``, or an import for a
:class:`~ssl.SSLContext` object.
"""
name = "path"
def __init__(self):
self.path_type = click.Path(exists=True, dir_okay=False,
resolve_path=True)
def convert(self, value, param, ctx):
if ssl is None:
raise click.BadParameter('Using "--cert" requires Python to be '
'compiled with SSL support.',
ctx, param)
try:
return self.path_type(value, param, ctx)
except click.BadParameter:
value = click.STRING(value, param, ctx).lower()
if value == "adhoc":
raise click.BadParameter("Aad-hoc certificates are currently "
"not supported by aioflask.",
ctx, param)
return value
obj = import_string(value, silent=True)
if isinstance(obj, ssl.SSLContext):
return obj
raise
@click.command("run", short_help="Run a development server.")
@click.option("--host", "-h", default="127.0.0.1",
help="The interface to bind to.")
@click.option("--port", "-p", default=5000, help="The port to bind to.")
@click.option(
"--cert", type=CertParamType(),
help="Specify a certificate file to use HTTPS."
)
@click.option(
"--key",
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
callback=_validate_key,
expose_value=False,
help="The key file to use when specifying a certificate.",
)
@click.option(
"--reload/--no-reload",
default=None,
help="Enable or disable the reloader. By default the reloader "
"is active if debug is enabled.",
)
@click.option(
"--debugger/--no-debugger",
default=None,
help="Enable or disable the debugger. By default the debugger "
"is active if debug is enabled.",
)
@click.option(
"--eager-loading/--lazy-loading",
default=None,
help="Enable or disable eager loading. By default eager "
"loading is enabled if the reloader is disabled.",
)
@click.option(
"--with-threads/--without-threads",
default=True,
help="Enable or disable multithreading.",
)
@click.option(
"--extra-files",
default=None,
type=SeparatedPathType(),
help=(
"Extra files that trigger a reload on change. Multiple paths"
f" are separated by {os.path.pathsep!r}."
),
)
@pass_script_info
def run_command(info, host, port, reload, debugger, eager_loading,
with_threads, cert, extra_files):
"""Run a local development server.
This server is for development purposes only. It does not provide
the stability, security, or performance of production WSGI servers.
The reloader and debugger are enabled by default if
FLASK_ENV=development or FLASK_DEBUG=1.
"""
debug = get_debug_flag()
if reload is None:
reload = debug
if debugger is None:
debugger = debug
if debugger:
os.environ['AIOFLASK_USE_DEBUGGER'] = 'true'
certfile = None
keyfile = None
if cert is not None and len(cert) == 2:
certfile = cert[0]
keyfile = cert[1]
show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
app_import_path = info.app_import_path
if app_import_path is None:
for path in ('wsgi', 'app'):
if os.path.exists(path) or os.path.exists(path + '.py'):
app_import_path = path
break
if app_import_path is None:
raise NoAppException(
"Could not locate a Flask application. You did not provide "
'the "FLASK_APP" environment variable, and a "wsgi.py" or '
'"app.py" module was not found in the current directory.'
)
if app_import_path.endswith('.py'):
app_import_path = app_import_path[:-3]
factory = False
if app_import_path.endswith('()'):
# TODO: this needs to be expanded to accept arguments for the factory
# function
app_import_path = app_import_path[:-2]
factory = True
if ':' not in app_import_path:
app_import_path += ':app'
import_name, app_name = app_import_path.split(':')
import_name = prepare_import(import_name)
uvicorn.run(
import_name + ':' + app_name,
factory=factory,
host=host,
port=port,
reload=reload,
workers=1,
log_level='debug' if debug else 'info',
ssl_certfile=certfile,
ssl_keyfile=keyfile,
)
# currently not supported:
# - eager_loading
# - with_threads
# - adhoc certs
# - extra_files
================================================
FILE: src/aioflask/ctx.py
================================================
import sys
from greenletio import async_
from flask.ctx import *
from flask.ctx import AppContext as OriginalAppContext, \
RequestContext as OriginalRequestContext, _sentinel, _app_ctx_stack, \
_request_ctx_stack, appcontext_popped
class AppContext(OriginalAppContext):
async def apush(self):
"""Binds the app context to the current context."""
self.push()
async def apop(self, exc=_sentinel):
"""Pops the app context."""
try:
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel: # pragma: no cover
exc = sys.exc_info()[1]
@async_
def do_teardown_async():
_app_ctx_stack.push(self)
self.app.do_teardown_appcontext(exc)
_app_ctx_stack.pop()
await do_teardown_async()
finally:
rv = _app_ctx_stack.pop()
assert rv is self, \
f"Popped wrong app context. ({rv!r} instead of {self!r})"
appcontext_popped.send(self.app)
async def __aenter__(self):
await self.apush()
return self
async def __aexit__(self, exc_type, exc_value, tb):
await self.apop(exc_value)
class RequestContext(OriginalRequestContext):
async def apush(self):
self.push()
async def apop(self, exc=_sentinel):
app_ctx = self._implicit_app_ctx_stack.pop()
clear_request = False
try:
if not self._implicit_app_ctx_stack:
if hasattr(self, 'preserved'): # Flask < 2.2
self.preserved = False
self._preserved_exc = None
if exc is _sentinel: # pragma: no cover
exc = sys.exc_info()[1]
@async_
def do_teardown():
_request_ctx_stack.push(self)
self.app.do_teardown_request(exc)
_request_ctx_stack.pop()
await do_teardown()
request_close = getattr(self.request, "close", None)
if request_close is not None: # pragma: no branch
request_close()
clear_request = True
finally:
rv = _request_ctx_stack.pop()
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
rv.request.environ["werkzeug.request"] = None
# Get rid of the app as well if necessary.
if app_ctx is not None:
await app_ctx.apop(exc)
assert (
rv is self
), f"Popped wrong request context. ({rv!r} instead of {self!r})"
async def aauto_pop(self, exc):
if hasattr(self, 'preserved'): # Flask < 2.2
if self.request.environ.get("flask._preserve_context") or (
exc is not None and self.app.preserve_context_on_exception
): # pragma: no cover
self.preserved = True
self._preserved_exc = exc
else:
await self.apop(exc)
else:
await self.apop(exc)
async def __aenter__(self):
await self.apush()
return self
async def __aexit__(self, exc_type, exc_value, tb):
await self.aauto_pop(exc_value)
================================================
FILE: src/aioflask/patch.py
================================================
from functools import wraps
from aioflask import current_app
def patch_decorator(decorator):
def patched_decorator(f):
@wraps(f)
def ensure_sync(*a, **kw):
return current_app.ensure_sync(f)(*a, **kw)
return decorator(ensure_sync)
return patched_decorator
def patch_decorator_with_args(decorator):
def patched_decorator(*args, **kwargs):
def inner_patched_decorator(f):
@wraps(f)
def ensure_sync(*a, **kw):
return current_app.ensure_sync(f)(*a, **kw)
return decorator(*args, **kwargs)(ensure_sync)
return inner_patched_decorator
return patched_decorator
def patch_decorator_method(class_, method_name):
original_decorator = getattr(class_, method_name)
def patched_decorator_method(self, f):
@wraps(f)
def ensure_sync(*a, **kw):
return current_app.ensure_sync(f)(*a, **kw)
return original_decorator(self, ensure_sync)
return patched_decorator_method
def patch_decorator_method_with_args(class_, method_name):
original_decorator = getattr(class_, method_name)
def patched_decorator_method(self, *args, **kwargs):
def inner_patched_decorator_method(f):
@wraps(f)
def ensure_sync(*a, **kw):
return current_app.ensure_sync(f)(*a, **kw)
return original_decorator(self, *args, **kwargs)(ensure_sync)
return inner_patched_decorator_method
return patched_decorator_method
================================================
FILE: src/aioflask/patched/__init__.py
================================================
================================================
FILE: src/aioflask/patched/flask_login/__init__.py
================================================
from functools import wraps
import sys
from werkzeug.local import LocalProxy
from aioflask import current_app, g
from flask import _request_ctx_stack
from aioflask.patch import patch_decorator, patch_decorator_method
import flask_login
from flask_login import login_required, fresh_login_required, \
LoginManager as OriginalLoginManager
for symbol in flask_login.__all__:
try:
globals()[symbol] = getattr(flask_login, symbol)
except AttributeError:
pass
def _user_context_processor():
return {'current_user': _get_user()}
def _load_user():
# Obtain the current user and preserve it in the g object. Flask-Login
# saves the user in a custom attribute of the request context, but that
# doesn't work with aioflask because when a copy of the request context is
# made, custom attributes are not carried over to the copy.
current_app.login_manager._load_user()
g.flask_login_current_user = _request_ctx_stack.top.user
def _get_user():
# Return the current user. This function is linked to the current_user
# context local, but unlike the original in Flask-Login, it does not
# attempt to load the user, it just returns the user that was pre-loaded.
# This avoids the somewhat tricky complication of triggering database
# operations that need to be awaited, which would require using something
# like (await current_user)
if hasattr(g, 'flask_login_current_user'):
return g.flask_login_current_user
return current_app.login_manager.anonymous_user()
class LoginManager(OriginalLoginManager):
def init_app(self, app, add_context_processor=True):
super().init_app(app, add_context_processor=False)
if add_context_processor:
app.context_processor(_user_context_processor)
# To prevent the current_user context local from triggering I/O at a
# random time when it is first referenced (which is a big complication
# if the I/O is async and needs to be awaited), we force the user to be
# loaded before each request. This isn't a perfect solution, because
# a before request handler registered before this one will not see the
# current user.
app.before_request(_load_user)
# the decorators that register callbacks need to be patched to support
# async views
user_loader = patch_decorator_method(OriginalLoginManager, 'user_loader')
header_loader = patch_decorator_method(
OriginalLoginManager, 'header_loader')
request_loader = patch_decorator_method(
OriginalLoginManager, 'request_loader')
unauthorized_handler = patch_decorator_method(
OriginalLoginManager, 'unauthorized_handler')
needs_refresh_handler = patch_decorator_method(
OriginalLoginManager, 'needs_refresh_handler')
# patch the two login_required decorators so that they accept async views
login_required = patch_decorator(login_required)
fresh_login_required = patch_decorator(fresh_login_required)
# redefine the current_user context local
current_user = LocalProxy(_get_user)
# patch the _get_user() function in the flask_login.utils module so that any
# calls to get current_user in Flask-Login functions are redirected here
setattr(sys.modules['flask_login.utils'], '_get_user', _get_user)
================================================
FILE: src/aioflask/templating.py
================================================
from flask.templating import *
from flask.templating import _app_ctx_stack, before_render_template, \
template_rendered
async def _render(template, context, app):
"""Renders the template and fires the signal"""
before_render_template.send(app, template=template, context=context)
rv = await template.render_async(context)
template_rendered.send(app, template=template, context=context)
return rv
async def render_template(template_name_or_list, **context):
"""Renders a template from the template folder with the given
context.
:param template_name_or_list: the name of the template to be
rendered, or an iterable with template names
the first one existing will be rendered
:param context: the variables that should be available in the
context of the template.
"""
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context)
return await _render(
ctx.app.jinja_env.get_or_select_template(template_name_or_list),
context,
ctx.app,
)
async def render_template_string(source, **context):
"""Renders a template from the given template source string
with the given context. Template variables will be autoescaped.
:param source: the source code of the template to be
rendered
:param context: the variables that should be available in the
context of the template.
"""
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context)
return await _render(ctx.app.jinja_env.from_string(source), context,
ctx.app)
================================================
FILE: src/aioflask/testing.py
================================================
from flask.testing import *
from flask.testing import FlaskClient as OriginalFlaskClient, \
FlaskCliRunner as OriginalFlaskCliRunner
from flask import _request_ctx_stack
from werkzeug.test import run_wsgi_app
from greenletio import async_
class FlaskClient(OriginalFlaskClient):
def run_wsgi_app(self, environ, buffered=False):
"""Runs the wrapped WSGI app with the given environment.
:meta private:
"""
if self.cookie_jar is not None:
self.cookie_jar.inject_wsgi(environ)
rv = run_wsgi_app(self.application.wsgi_app, environ,
buffered=buffered)
if self.cookie_jar is not None:
self.cookie_jar.extract_wsgi(environ, rv[2])
return rv
async def get(self, *args, **kwargs):
return await async_(super().get)(*args, **kwargs)
async def post(self, *args, **kwargs):
return await async_(super().post)(*args, **kwargs)
async def put(self, *args, **kwargs):
return await async_(super().put)(*args, **kwargs)
async def patch(self, *args, **kwargs):
return await async_(super().patch)(*args, **kwargs)
async def delete(self, *args, **kwargs):
return await async_(super().delete)(*args, **kwargs)
async def head(self, *args, **kwargs):
return await async_(super().head)(*args, **kwargs)
async def options(self, *args, **kwargs):
return await async_(super().options)(*args, **kwargs)
async def trace(self, *args, **kwargs):
return await async_(super().trace)(*args, **kwargs)
async def __aenter__(self):
if self.preserve_context:
raise RuntimeError("Cannot nest client invocations")
self.preserve_context = True
return self
async def __aexit__(self, exc_type, exc_value, tb):
self.preserve_context = False
# Normally the request context is preserved until the next
# request in the same thread comes. When the client exits we
# want to clean up earlier. Pop request contexts until the stack
# is empty or a non-preserved one is found.
while True:
top = _request_ctx_stack.top
if top is not None and top.preserved:
await top.apop()
else:
break
class FlaskCliRunner(OriginalFlaskCliRunner):
async def invoke(self, *args, **kwargs):
return await async_(super().invoke)(*args, **kwargs)
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/templates/template.html
================================================
{{ g.x }}{{ session.y }}
================================================
FILE: tests/test_app.py
================================================
import asyncio
import os
import unittest
from unittest import mock
import aioflask
from .utils import async_test
class TestApp(unittest.TestCase):
@async_test
async def test_app(self):
app = aioflask.Flask(__name__)
@app.route('/async')
async def async_route():
await asyncio.sleep(0)
assert aioflask.current_app._get_current_object() == app
return 'async'
@app.route('/sync')
def sync_route():
assert aioflask.current_app._get_current_object() == app
return 'sync'
client = app.test_client()
response = await client.get('/async')
assert response.data == b'async'
response = await client.get('/sync')
assert response.data == b'sync'
@async_test
async def test_g(self):
app = aioflask.Flask(__name__)
app.secret_key = 'secret'
@app.before_request
async def async_before_request():
aioflask.g.asyncvar = 'async'
@app.before_request
def sync_before_request():
aioflask.g.syncvar = 'sync'
@app.route('/async')
async def async_route():
aioflask.session['a'] = 'async'
return f'{aioflask.g.asyncvar}-{aioflask.g.syncvar}'
@app.route('/sync')
async def sync_route():
aioflask.session['s'] = 'sync'
return f'{aioflask.g.asyncvar}-{aioflask.g.syncvar}'
@app.route('/session')
async def session():
return f'{aioflask.session.get("a")}-{aioflask.session.get("s")}'
@app.after_request
async def after_request(rv):
rv.data += f'/{aioflask.g.asyncvar}-{aioflask.g.syncvar}'.encode()
return rv
client = app.test_client()
response = await client.get('/session')
assert response.data == b'None-None/async-sync'
response = await client.get('/async')
assert response.data == b'async-sync/async-sync'
response = await client.get('/session')
assert response.data == b'async-None/async-sync'
response = await client.get('/sync')
assert response.data == b'async-sync/async-sync'
response = await client.get('/session')
assert response.data == b'async-sync/async-sync'
@mock.patch('aioflask.app.uvicorn')
def test_app_run(self, uvicorn):
app = aioflask.Flask(__name__)
app.run()
uvicorn.run.assert_called_with('tests.test_app:app',
host='127.0.0.1', port=5000,
reload=False, workers=1,
log_level='info', ssl_certfile=None,
ssl_keyfile=None)
app.run(host='1.2.3.4', port=3000)
uvicorn.run.assert_called_with('tests.test_app:app',
host='1.2.3.4', port=3000,
reload=False, workers=1,
log_level='info', ssl_certfile=None,
ssl_keyfile=None)
app.run(debug=True)
uvicorn.run.assert_called_with('tests.test_app:app',
host='127.0.0.1', port=5000,
reload=True, workers=1,
log_level='debug', ssl_certfile=None,
ssl_keyfile=None)
app.run(debug=True, use_reloader=False)
uvicorn.run.assert_called_with('tests.test_app:app',
host='127.0.0.1', port=5000,
reload=False, workers=1,
log_level='debug', ssl_certfile=None,
ssl_keyfile=None)
if 'FLASK_DEBUG' in os.environ:
del os.environ['FLASK_DEBUG']
if 'AIOFLASK_USE_DEBUGGER' in os.environ:
del os.environ['AIOFLASK_USE_DEBUGGER']
================================================
FILE: tests/test_cli.py
================================================
import os
import unittest
from unittest import mock
import click
from click.testing import CliRunner
import aioflask
import aioflask.cli
from .utils import async_test
class TestCli(unittest.TestCase):
@async_test
async def test_command_with_appcontext(self):
app = aioflask.Flask('testapp')
@app.cli.command(with_appcontext=True)
async def testcmd():
click.echo(aioflask.current_app.name)
result = await app.test_cli_runner().invoke(testcmd)
assert result.exit_code == 0
assert result.output == "testapp\n"
@async_test
async def test_command_without_appcontext(self):
app = aioflask.Flask('testapp')
@app.cli.command(with_appcontext=False)
async def testcmd():
click.echo(aioflask.current_app.name)
result = await app.test_cli_runner().invoke(testcmd)
assert result.exit_code == 1
assert type(result.exception) == RuntimeError
@async_test
async def test_with_appcontext(self):
@click.command()
@aioflask.cli.with_appcontext
async def testcmd():
click.echo(aioflask.current_app.name)
app = aioflask.Flask('testapp')
result = await app.test_cli_runner().invoke(testcmd)
assert result.exit_code == 0
assert result.output == "testapp\n"
@mock.patch('aioflask.cli.uvicorn')
def test_aiorun(self, uvicorn):
app = aioflask.Flask('testapp')
obj = aioflask.cli.ScriptInfo(app_import_path='app.py',
create_app=lambda: app)
result = CliRunner().invoke(aioflask.cli.run_command, obj=obj)
assert result.exit_code == 0
uvicorn.run.assert_called_with('app:app', factory=False,
host='127.0.0.1', port=5000,
reload=False, workers=1,
log_level='info', ssl_certfile=None,
ssl_keyfile=None)
result = CliRunner().invoke(aioflask.cli.run_command,
'--host 1.2.3.4 --port 3000', obj=obj)
assert result.exit_code == 0
uvicorn.run.assert_called_with('app:app', factory=False,
host='1.2.3.4', port=3000,
reload=False, workers=1,
log_level='info', ssl_certfile=None,
ssl_keyfile=None)
os.environ['FLASK_DEBUG'] = 'true'
result = CliRunner().invoke(aioflask.cli.run_command, obj=obj)
assert result.exit_code == 0
uvicorn.run.assert_called_with('app:app', factory=False,
host='127.0.0.1', port=5000,
reload=True, workers=1,
log_level='debug', ssl_certfile=None,
ssl_keyfile=None)
os.environ['FLASK_DEBUG'] = 'true'
result = CliRunner().invoke(aioflask.cli.run_command, '--no-reload',
obj=obj)
assert result.exit_code == 0
uvicorn.run.assert_called_with('app:app', factory=False,
host='127.0.0.1', port=5000,
reload=False, workers=1,
log_level='debug', ssl_certfile=None,
ssl_keyfile=None)
if 'FLASK_DEBUG' in os.environ:
del os.environ['FLASK_DEBUG']
if 'AIOFLASK_USE_DEBUGGER' in os.environ:
del os.environ['AIOFLASK_USE_DEBUGGER']
@mock.patch('aioflask.cli.uvicorn')
def test_aiorun_with_factory(self, uvicorn):
app = aioflask.Flask('testapp')
obj = aioflask.cli.ScriptInfo(app_import_path='app:create_app()',
create_app=lambda: app)
result = CliRunner().invoke(aioflask.cli.run_command, obj=obj)
assert result.exit_code == 0
uvicorn.run.assert_called_with('app:create_app', factory=True,
host='127.0.0.1', port=5000,
reload=False, workers=1,
log_level='info', ssl_certfile=None,
ssl_keyfile=None)
================================================
FILE: tests/test_ctx.py
================================================
import unittest
import pytest
import aioflask
from .utils import async_test
class TestApp(unittest.TestCase):
@async_test
async def test_app_context(self):
app = aioflask.Flask(__name__)
called_t1 = False
called_t2 = False
@app.teardown_appcontext
async def t1(exc):
nonlocal called_t1
called_t1 = True
@app.teardown_appcontext
def t2(exc):
nonlocal called_t2
called_t2 = True
async with app.app_context():
assert aioflask.current_app == app
async with app.app_context():
assert aioflask.current_app == app
assert aioflask.current_app == app
assert called_t1
assert called_t2
with pytest.raises(RuntimeError):
print(aioflask.current_app)
@async_test
async def test_req_context(self):
app = aioflask.Flask(__name__)
called_t1 = False
called_t2 = False
@app.teardown_appcontext
async def t1(exc):
nonlocal called_t1
called_t1 = True
@app.teardown_appcontext
def t2(exc):
nonlocal called_t2
called_t2 = True
async with app.test_request_context('/foo'):
assert aioflask.current_app == app
assert aioflask.request.path == '/foo'
assert called_t1
assert called_t2
async with app.app_context():
async with app.test_request_context('/bar') as reqctx:
assert aioflask.current_app == app
assert aioflask.request.path == '/bar'
async with reqctx:
assert aioflask.current_app == app
assert aioflask.request.path == '/bar'
with pytest.raises(RuntimeError):
print(aioflask.current_app)
================================================
FILE: tests/test_patch.py
================================================
import unittest
import aioflask
import aioflask.patch
from .utils import async_test
class TestPatch(unittest.TestCase):
@async_test
async def test_decorator(self):
def foo(f):
def decorator(*args, **kwargs):
return f(*args, **kwargs) + '-decorated'
return decorator
foo = aioflask.patch.patch_decorator(foo)
app = aioflask.Flask(__name__)
@app.route('/abc/<int:id>')
@foo
async def abc(id):
return str(id)
client = app.test_client()
response = await client.get('/abc/123')
assert response.data == b'123-decorated'
@async_test
async def test_decorator_with_args(self):
def foo(value):
def inner_foo(f):
def decorator(*args, **kwargs):
return f(*args, **kwargs) + str(value)
return decorator
return inner_foo
foo = aioflask.patch.patch_decorator_with_args(foo)
app = aioflask.Flask(__name__)
@app.route('/abc/<int:id>')
@foo(456)
async def abc(id):
return str(id)
client = app.test_client()
response = await client.get('/abc/123')
assert response.data == b'123456'
@async_test
async def test_decorator_method(self):
class Foo:
def __init__(self, value):
self.value = value
def deco(self, f):
def decorator(*args, **kwargs):
return f(*args, **kwargs) + str(self.value)
return decorator
Foo.deco = aioflask.patch.patch_decorator_method(Foo, 'deco')
app = aioflask.Flask(__name__)
foo = Foo(456)
@app.route('/abc/<int:id>')
@foo.deco
async def abc(id):
return str(id)
client = app.test_client()
response = await client.get('/abc/123')
assert response.data == b'123456'
@async_test
async def test_decorator_method_with_args(self):
class Foo:
def __init__(self, value):
self.value = value
def deco(self, value2):
def decorator(f):
def inner_decorator(*args, **kwargs):
return f(*args, **kwargs) + str(self.value) + \
str(value2)
return inner_decorator
return decorator
Foo.deco = aioflask.patch.patch_decorator_method_with_args(Foo, 'deco')
app = aioflask.Flask(__name__)
foo = Foo(456)
@app.route('/abc/<int:id>')
@foo.deco(789)
async def abc(id):
return str(id)
client = app.test_client()
response = await client.get('/abc/123')
assert response.data == b'123456789'
================================================
FILE: tests/test_templating.py
================================================
import asyncio
import os
import unittest
from unittest import mock
import aioflask
from .utils import async_test
class TestTemplating(unittest.TestCase):
@async_test
async def test_template_strng(self):
app = aioflask.Flask(__name__)
app.secret_key = 'secret'
@app.before_request
def before_request():
aioflask.g.x = 'foo'
aioflask.session['y'] = 'bar'
@app.route('/')
async def async_route():
return await aioflask.render_template_string(
'{{ g.x }}{{ session.y }}')
client = app.test_client()
response = await client.get('/')
assert response.data == b'foobar'
@async_test
async def test_template(self):
app = aioflask.Flask(__name__)
app.secret_key = 'secret'
@app.before_request
def before_request():
aioflask.g.x = 'foo'
aioflask.session['y'] = 'bar'
@app.route('/')
async def async_route():
return await aioflask.render_template('template.html')
client = app.test_client()
response = await client.get('/')
assert response.data == b'foobar'
================================================
FILE: tests/utils.py
================================================
import asyncio
from greenletio.core import bridge
def async_test(f):
def wrapper(*args, **kwargs):
asyncio.get_event_loop().run_until_complete(f(*args, **kwargs))
return wrapper
================================================
FILE: tox.ini
================================================
[tox]
envlist=flake8,,py37,py38,py39,py310,pypy3,docs
skip_missing_interpreters=True
[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
pypy3: pypy-3
[testenv]
commands=
pip install -e .
pytest -p no:logging --cov=src/aioflask --cov-branch examples/aioflaskr/tests
pytest -p no:logging --cov=src/aioflask --cov-branch --cov-report=term-missing --cov-report=xml --cov-append tests
deps=
aiosqlite
greenletio
alchemical
flask-login
pytest
pytest-asyncio
pytest-cov
[testenv:flake8]
deps=
flake8
commands=
flake8 --ignore=F401,F403 --exclude=".*" src/aioflask tests
[testenv:docs]
changedir=docs
deps=
sphinx
whitelist_externals=
make
commands=
make html
gitextract_sepaq9a1/ ├── .github/ │ └── workflows/ │ └── tests.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGES.md ├── LICENSE ├── README.md ├── examples/ │ ├── AsyncProgressBar/ │ │ ├── README.md │ │ ├── progress_bar.py │ │ └── requirements.txt │ ├── aioflaskr/ │ │ ├── .flaskenv │ │ ├── LICENSE │ │ ├── README.md │ │ ├── flaskr/ │ │ │ ├── __init__.py │ │ │ ├── auth.py │ │ │ ├── blog.py │ │ │ ├── models.py │ │ │ ├── static/ │ │ │ │ └── style.css │ │ │ └── templates/ │ │ │ ├── auth/ │ │ │ │ ├── login.html │ │ │ │ └── register.html │ │ │ ├── base.html │ │ │ └── blog/ │ │ │ ├── create.html │ │ │ ├── index.html │ │ │ └── update.html │ │ ├── requirements.txt │ │ └── tests/ │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_auth.py │ │ ├── test_blog.py │ │ └── test_init.py │ ├── g/ │ │ └── app.py │ ├── hello_world/ │ │ ├── app.py │ │ └── templates/ │ │ └── index.html │ ├── login/ │ │ └── app.py │ ├── quotes-aiohttp/ │ │ ├── README.md │ │ └── quotes.py │ └── quotes-requests/ │ ├── README.md │ ├── quotes.py │ └── quotes_app.py ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src/ │ └── aioflask/ │ ├── __init__.py │ ├── app.py │ ├── asgi.py │ ├── cli.py │ ├── ctx.py │ ├── patch.py │ ├── patched/ │ │ ├── __init__.py │ │ └── flask_login/ │ │ └── __init__.py │ ├── templating.py │ └── testing.py ├── tests/ │ ├── __init__.py │ ├── templates/ │ │ └── template.html │ ├── test_app.py │ ├── test_cli.py │ ├── test_ctx.py │ ├── test_patch.py │ ├── test_templating.py │ └── utils.py └── tox.ini
SYMBOL INDEX (150 symbols across 28 files)
FILE: examples/AsyncProgressBar/progress_bar.py
function some_work (line 13) | async def some_work():
function check_status (line 26) | async def check_status():
function progress (line 55) | async def progress():
function index (line 111) | async def index():
function start_work (line 117) | async def start_work():
FILE: examples/aioflaskr/flaskr/__init__.py
function create_app (line 14) | def create_app(test_config=None):
function init_db (line 59) | async def init_db():
function init_db_command (line 66) | async def init_db_command():
FILE: examples/aioflaskr/flaskr/auth.py
function load_user (line 18) | async def load_user(id):
function register (line 23) | async def register():
function login (line 56) | async def login():
function logout (line 82) | async def logout():
FILE: examples/aioflaskr/flaskr/blog.py
function index (line 18) | async def index():
function get_post (line 24) | async def get_post(id, check_author=True):
function create (line 47) | async def create():
function update (line 69) | async def update(id):
function delete (line 94) | async def delete(id):
FILE: examples/aioflaskr/flaskr/models.py
class User (line 11) | class User(UserMixin, db.Model):
method password (line 17) | def password(self):
method password (line 21) | def password(self, value):
method check_password (line 25) | def check_password(self, value):
class Post (line 29) | class Post(db.Model):
method update_url (line 43) | def update_url(self):
method delete_url (line 47) | def delete_url(self):
FILE: examples/aioflaskr/tests/conftest.py
function app (line 13) | async def app():
function client (line 41) | def client(app):
function runner (line 47) | def runner(app):
class AuthActions (line 52) | class AuthActions:
method __init__ (line 53) | def __init__(self, client):
method login (line 56) | async def login(self, username="test", password="test"):
method logout (line 61) | async def logout(self):
function auth (line 66) | def auth(client):
FILE: examples/aioflaskr/tests/test_auth.py
function test_register (line 8) | async def test_register(client, app):
function test_user_password (line 23) | def test_user_password(app):
function test_register_validate_input (line 38) | async def test_register_validate_input(client, username, password, messa...
function test_login (line 46) | async def test_login(client, auth):
function test_login_validate_input (line 67) | async def test_login_validate_input(auth, username, password, message):
function test_logout (line 73) | async def test_logout(client, auth):
FILE: examples/aioflaskr/tests/test_blog.py
function test_index (line 11) | async def test_index(client, auth):
function test_login_required (line 26) | async def test_login_required(client, path):
function test_author_required (line 32) | async def test_author_required(app, client, auth):
function test_exists_required (line 48) | async def test_exists_required(client, auth, path):
function test_create (line 54) | async def test_create(client, auth, app):
function test_update (line 65) | async def test_update(client, auth, app):
function test_create_update_validate (line 76) | async def test_create_update_validate(client, auth, path):
function test_delete (line 83) | async def test_delete(client, auth, app):
FILE: examples/aioflaskr/tests/test_init.py
function test_config (line 5) | def test_config():
function test_db_url_environ (line 11) | def test_db_url_environ(monkeypatch):
function test_init_db_command (line 19) | async def test_init_db_command(runner, monkeypatch):
FILE: examples/g/app.py
function before_request (line 8) | async def before_request():
function teardown_appcontext (line 13) | async def teardown_appcontext(exc):
function index (line 18) | async def index():
FILE: examples/hello_world/app.py
function index (line 7) | async def index():
function hello (line 12) | async def hello():
FILE: examples/login/app.py
class User (line 11) | class User(UserMixin):
method __init__ (line 12) | def __init__(self, user_id):
function load_user (line 17) | async def load_user(user_id):
function index (line 23) | async def index():
function login (line 36) | def login():
function logout (line 53) | def logout():
FILE: examples/quotes-aiohttp/quotes.py
function get_quote (line 20) | async def get_quote(session):
function index (line 26) | async def index():
FILE: examples/quotes-requests/quotes_app.py
function get_quote (line 26) | def get_quote():
function index (line 32) | async def index():
FILE: src/aioflask/app.py
class Flask (line 18) | class Flask(OriginalFlask):
method __init__ (line 19) | def __init__(self, *args, **kwargs):
method ensure_sync (line 27) | def ensure_sync(self, func):
method app_context (line 51) | def app_context(self):
method request_context (line 54) | def request_context(self, environ):
method _fix_async (line 57) | def _fix_async(self): # pragma: no cover
method asgi_app (line 65) | async def asgi_app(self, scope, receive, send): # pragma: no cover
method __call__ (line 70) | async def __call__(self, scope, receive, send=None): # pragma: no cover
method run (line 77) | def run(self, host=None, port=None, debug=None, load_dotenv=True,
FILE: src/aioflask/asgi.py
class wsgi_to_asgi (line 6) | class wsgi_to_asgi: # pragma: no cover
method __init__ (line 9) | def __init__(self, wsgi_application):
method __call__ (line 12) | async def __call__(self, scope, receive, send):
class WsgiToAsgiInstance (line 21) | class WsgiToAsgiInstance: # pragma: no cover
method __init__ (line 24) | def __init__(self, wsgi_application):
method __call__ (line 28) | async def __call__(self, scope, receive, send):
method build_environ (line 49) | def build_environ(self, scope, body):
method start_response (line 93) | def start_response(self, status, response_headers, exc_info=None):
method run_wsgi_app (line 120) | def run_wsgi_app(self, body):
FILE: src/aioflask/cli.py
function _ensure_sync (line 26) | def _ensure_sync(func, with_appcontext=False):
function with_appcontext (line 48) | def with_appcontext(f):
class AppGroup (line 63) | class AppGroup(OriginalAppGroup):
method command (line 70) | def command(self, *args, **kwargs):
function show_server_banner (line 85) | def show_server_banner(env, debug, app_import_path, eager_loading):
class CertParamType (line 100) | class CertParamType(click.ParamType):
method __init__ (line 108) | def __init__(self):
method convert (line 112) | def convert(self, value, param, ctx):
function run_command (line 186) | def run_command(info, host, port, reload, debugger, eager_loading,
FILE: src/aioflask/ctx.py
class AppContext (line 9) | class AppContext(OriginalAppContext):
method apush (line 10) | async def apush(self):
method apop (line 14) | async def apop(self, exc=_sentinel):
method __aenter__ (line 35) | async def __aenter__(self):
method __aexit__ (line 39) | async def __aexit__(self, exc_type, exc_value, tb):
class RequestContext (line 43) | class RequestContext(OriginalRequestContext):
method apush (line 44) | async def apush(self):
method apop (line 47) | async def apop(self, exc=_sentinel):
method aauto_pop (line 87) | async def aauto_pop(self, exc):
method __aenter__ (line 99) | async def __aenter__(self):
method __aexit__ (line 103) | async def __aexit__(self, exc_type, exc_value, tb):
FILE: src/aioflask/patch.py
function patch_decorator (line 5) | def patch_decorator(decorator):
function patch_decorator_with_args (line 15) | def patch_decorator_with_args(decorator):
function patch_decorator_method (line 27) | def patch_decorator_method(class_, method_name):
function patch_decorator_method_with_args (line 39) | def patch_decorator_method_with_args(class_, method_name):
FILE: src/aioflask/patched/flask_login/__init__.py
function _user_context_processor (line 18) | def _user_context_processor():
function _load_user (line 22) | def _load_user():
function _get_user (line 31) | def _get_user():
class LoginManager (line 43) | class LoginManager(OriginalLoginManager):
method init_app (line 44) | def init_app(self, app, add_context_processor=True):
FILE: src/aioflask/templating.py
function _render (line 6) | async def _render(template, context, app):
function render_template (line 15) | async def render_template(template_name_or_list, **context):
function render_template_string (line 33) | async def render_template_string(source, **context):
FILE: src/aioflask/testing.py
class FlaskClient (line 9) | class FlaskClient(OriginalFlaskClient):
method run_wsgi_app (line 10) | def run_wsgi_app(self, environ, buffered=False):
method get (line 25) | async def get(self, *args, **kwargs):
method post (line 28) | async def post(self, *args, **kwargs):
method put (line 31) | async def put(self, *args, **kwargs):
method patch (line 34) | async def patch(self, *args, **kwargs):
method delete (line 37) | async def delete(self, *args, **kwargs):
method head (line 40) | async def head(self, *args, **kwargs):
method options (line 43) | async def options(self, *args, **kwargs):
method trace (line 46) | async def trace(self, *args, **kwargs):
method __aenter__ (line 49) | async def __aenter__(self):
method __aexit__ (line 55) | async def __aexit__(self, exc_type, exc_value, tb):
class FlaskCliRunner (line 71) | class FlaskCliRunner(OriginalFlaskCliRunner):
method invoke (line 72) | async def invoke(self, *args, **kwargs):
FILE: tests/test_app.py
class TestApp (line 9) | class TestApp(unittest.TestCase):
method test_app (line 11) | async def test_app(self):
method test_g (line 32) | async def test_g(self):
method test_app_run (line 76) | def test_app_run(self, uvicorn):
FILE: tests/test_cli.py
class TestCli (line 11) | class TestCli(unittest.TestCase):
method test_command_with_appcontext (line 13) | async def test_command_with_appcontext(self):
method test_command_without_appcontext (line 25) | async def test_command_without_appcontext(self):
method test_with_appcontext (line 37) | async def test_with_appcontext(self):
method test_aiorun (line 50) | def test_aiorun(self, uvicorn):
method test_aiorun_with_factory (line 94) | def test_aiorun_with_factory(self, uvicorn):
FILE: tests/test_ctx.py
class TestApp (line 7) | class TestApp(unittest.TestCase):
method test_app_context (line 9) | async def test_app_context(self):
method test_req_context (line 36) | async def test_req_context(self):
FILE: tests/test_patch.py
class TestPatch (line 7) | class TestPatch(unittest.TestCase):
method test_decorator (line 9) | async def test_decorator(self):
method test_decorator_with_args (line 30) | async def test_decorator_with_args(self):
method test_decorator_method (line 53) | async def test_decorator_method(self):
method test_decorator_method_with_args (line 79) | async def test_decorator_method_with_args(self):
FILE: tests/test_templating.py
class TestTemplating (line 9) | class TestTemplating(unittest.TestCase):
method test_template_strng (line 11) | async def test_template_strng(self):
method test_template (line 30) | async def test_template(self):
FILE: tests/utils.py
function async_test (line 5) | def async_test(f):
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (99K chars).
[
{
"path": ".github/workflows/tests.yml",
"chars": 1114,
"preview": "name: build\non:\n push:\n branches:\n - main\n pull_request:\n branches:\n - main\njobs:\n lint:\n name: li"
},
{
"path": ".gitignore",
"chars": 1799,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".readthedocs.yaml",
"chars": 198,
"preview": "version: 2\n\nbuild:\n os: ubuntu-22.04\n tools:\n python: \"3.11\"\n\nsphinx:\n configuration: docs/conf.py\n\npython:\n inst"
},
{
"path": "CHANGES.md",
"chars": 3776,
"preview": "# aioflask change log\n\n**Release 0.4.0** - 2021-08-18\n\n- Support for app factory functions with uvicorn ([commit](https:"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2020 Miguel Grinberg\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 2076,
"preview": "# aioflask\n\n [\n"
},
{
"path": "examples/aioflaskr/LICENSE",
"chars": 1540,
"preview": "Copyright 2010 Pallets (original version)\nCopyright 2021 Miguel Grinberg (this version)\n\nRedistribution and use in sourc"
},
{
"path": "examples/aioflaskr/README.md",
"chars": 994,
"preview": "aioflaskr\n=========\n\nThis is the \"Flaskr\" application from the tutorial section of the Flask\ndocumentation, adapted to w"
},
{
"path": "examples/aioflaskr/flaskr/__init__.py",
"chars": 1913,
"preview": "import os\n\nimport click\nfrom aioflask import Flask\nfrom aioflask.cli import with_appcontext\nfrom alchemical.aioflask imp"
},
{
"path": "examples/aioflaskr/flaskr/auth.py",
"chars": 2570,
"preview": "from aioflask import Blueprint\nfrom aioflask import flash\nfrom aioflask import redirect\nfrom aioflask import render_temp"
},
{
"path": "examples/aioflaskr/flaskr/blog.py",
"chars": 2896,
"preview": "from aioflask import Blueprint\nfrom aioflask import flash\nfrom aioflask import redirect\nfrom aioflask import render_temp"
},
{
"path": "examples/aioflaskr/flaskr/models.py",
"chars": 1547,
"preview": "from werkzeug.security import check_password_hash\nfrom werkzeug.security import generate_password_hash\nfrom aioflask imp"
},
{
"path": "examples/aioflaskr/flaskr/static/style.css",
"chars": 1696,
"preview": "html {\n font-family: sans-serif;\n background: #eee;\n padding: 1rem;\n}\n\nbody {\n max-width: 960px;\n margin: 0 auto;\n "
},
{
"path": "examples/aioflaskr/flaskr/templates/auth/login.html",
"chars": 424,
"preview": "{% extends 'base.html' %}\n\n{% block header %}\n <h1>{% block title %}Log In{% endblock %}</h1>\n{% endblock %}\n\n{% block "
},
{
"path": "examples/aioflaskr/flaskr/templates/auth/register.html",
"chars": 428,
"preview": "{% extends 'base.html' %}\n\n{% block header %}\n <h1>{% block title %}Register{% endblock %}</h1>\n{% endblock %}\n\n{% bloc"
},
{
"path": "examples/aioflaskr/flaskr/templates/base.html",
"chars": 778,
"preview": "<!doctype html>\n<title>{% block title %}{% endblock %} - Flaskr</title>\n<link rel=\"stylesheet\" href=\"{{ url_for('static'"
},
{
"path": "examples/aioflaskr/flaskr/templates/blog/create.html",
"chars": 447,
"preview": "{% extends 'base.html' %}\n\n{% block header %}\n <h1>{% block title %}New Post{% endblock %}</h1>\n{% endblock %}\n\n{% bloc"
},
{
"path": "examples/aioflaskr/flaskr/templates/blog/index.html",
"chars": 780,
"preview": "{% extends 'base.html' %}\n\n{% block header %}\n <h1>{% block title %}Posts{% endblock %}</h1>\n {% if current_user.is_au"
},
{
"path": "examples/aioflaskr/flaskr/templates/blog/update.html",
"chars": 668,
"preview": "{% extends 'base.html' %}\n\n{% block header %}\n <h1>{% block title %}Edit \"{{ post['title'] }}\"{% endblock %}</h1>\n{% en"
},
{
"path": "examples/aioflaskr/requirements.txt",
"chars": 328,
"preview": "aioflask\naiosqlite==0.17.0\nalchemical\nasgiref==3.4.1\nclick==8.0.1\nFlask==2.0.1\nFlask-Login==0.5.0\ngreenlet==1.1.1\ngreenl"
},
{
"path": "examples/aioflaskr/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "examples/aioflaskr/tests/conftest.py",
"chars": 1644,
"preview": "from datetime import datetime\n\nimport pytest\n\nfrom flaskr import create_app\nfrom flaskr import db\nfrom flaskr import ini"
},
{
"path": "examples/aioflaskr/tests/test_auth.py",
"chars": 2447,
"preview": "import pytest\n\nfrom flaskr import db\nfrom flaskr.models import User\n\n\n@pytest.mark.asyncio\nasync def test_register(clien"
},
{
"path": "examples/aioflaskr/tests/test_blog.py",
"chars": 2859,
"preview": "import pytest\nfrom sqlalchemy import func\nfrom sqlalchemy import select\n\nfrom flaskr import db\nfrom flaskr.models import"
},
{
"path": "examples/aioflaskr/tests/test_init.py",
"chars": 808,
"preview": "import pytest\nfrom flaskr import create_app\n\n\ndef test_config():\n \"\"\"Test create_app without passing test config.\"\"\"\n"
},
{
"path": "examples/g/app.py",
"chars": 401,
"preview": "from aioflask import Flask, g\nimport aiohttp\n\napp = Flask(__name__)\n\n\n@app.before_request\nasync def before_request():\n "
},
{
"path": "examples/hello_world/app.py",
"chars": 248,
"preview": "from aioflask import Flask, render_template\n\napp = Flask(__name__)\n\n\n@app.route('/')\nasync def index():\n return await"
},
{
"path": "examples/hello_world/templates/index.html",
"chars": 143,
"preview": "<!doctype html>\n<html>\n <head>\n <title>Hello (async) world!</title>\n </head>\n <body>\n <h1>Hello (async) world!<"
},
{
"path": "examples/login/app.py",
"chars": 1191,
"preview": "from aioflask import Flask, request, redirect\nfrom aioflask.patched.flask_login import LoginManager, login_required, Use"
},
{
"path": "examples/quotes-aiohttp/README.md",
"chars": 321,
"preview": "Quotes\n======\n\nReturns 10 famous quotes each time the page is refreshed. Quotes are obtained\nby sending concurrent HTTP "
},
{
"path": "examples/quotes-aiohttp/quotes.py",
"chars": 737,
"preview": "import asyncio\nimport aiohttp\nfrom aioflask import Flask, render_template_string\n\napp = Flask(__name__)\ntemplate = '''<!"
},
{
"path": "examples/quotes-requests/README.md",
"chars": 434,
"preview": "Quotes\n======\n\nReturns 10 famous quotes each time the page is refreshed. Quotes are obtained\nby sending concurrent HTTP "
},
{
"path": "examples/quotes-requests/quotes.py",
"chars": 148,
"preview": "import greenletio\n\n# import the application with blocking functions monkey patched\nwith greenletio.patch_blocking():\n "
},
{
"path": "examples/quotes-requests/quotes_app.py",
"chars": 957,
"preview": "import asyncio\nfrom aioflask import Flask, render_template_string\nfrom greenletio import async_\nimport requests\n\napp = F"
},
{
"path": "pyproject.toml",
"chars": 104,
"preview": "[build-system]\nrequires = [\n \"setuptools>=42\",\n \"wheel\"\n]\nbuild-backend = \"setuptools.build_meta\"\n"
},
{
"path": "setup.cfg",
"chars": 882,
"preview": "[metadata]\nname = aioflask\nversion = 0.4.1.dev0\nauthor = Miguel Grinberg\nauthor_email = miguel.grinberg@gmail.com\ndescri"
},
{
"path": "setup.py",
"chars": 38,
"preview": "import setuptools\n\nsetuptools.setup()\n"
},
{
"path": "src/aioflask/__init__.py",
"chars": 140,
"preview": "from flask import *\nfrom .app import Flask\nfrom .templating import render_template, render_template_string\nfrom .testing"
},
{
"path": "src/aioflask/app.py",
"chars": 4801,
"preview": "import asyncio\nfrom functools import wraps\nfrom inspect import iscoroutinefunction\nimport os\nfrom flask.app import *\nfro"
},
{
"path": "src/aioflask/asgi.py",
"chars": 5444,
"preview": "import sys\nfrom tempfile import SpooledTemporaryFile\nfrom greenletio import async_, await_\n\n\nclass wsgi_to_asgi: # prag"
},
{
"path": "src/aioflask/cli.py",
"chars": 7820,
"preview": "from functools import wraps\nfrom inspect import iscoroutinefunction\nimport os\nimport sys\n\nfrom flask.cli import *\nfrom f"
},
{
"path": "src/aioflask/ctx.py",
"chars": 3431,
"preview": "import sys\nfrom greenletio import async_\nfrom flask.ctx import *\nfrom flask.ctx import AppContext as OriginalAppContext,"
},
{
"path": "src/aioflask/patch.py",
"chars": 1525,
"preview": "from functools import wraps\nfrom aioflask import current_app\n\n\ndef patch_decorator(decorator):\n def patched_decorator"
},
{
"path": "src/aioflask/patched/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/aioflask/patched/flask_login/__init__.py",
"chars": 3308,
"preview": "from functools import wraps\nimport sys\nfrom werkzeug.local import LocalProxy\nfrom aioflask import current_app, g\nfrom fl"
},
{
"path": "src/aioflask/templating.py",
"chars": 1687,
"preview": "from flask.templating import *\nfrom flask.templating import _app_ctx_stack, before_render_template, \\\n template_rende"
},
{
"path": "src/aioflask/testing.py",
"chars": 2471,
"preview": "from flask.testing import *\nfrom flask.testing import FlaskClient as OriginalFlaskClient, \\\n FlaskCliRunner as Origin"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/templates/template.html",
"chars": 25,
"preview": "{{ g.x }}{{ session.y }}\n"
},
{
"path": "tests/test_app.py",
"chars": 4064,
"preview": "import asyncio\nimport os\nimport unittest\nfrom unittest import mock\nimport aioflask\nfrom .utils import async_test\n\n\nclass"
},
{
"path": "tests/test_cli.py",
"chars": 4449,
"preview": "import os\nimport unittest\nfrom unittest import mock\nimport click\nfrom click.testing import CliRunner\nimport aioflask\nimp"
},
{
"path": "tests/test_ctx.py",
"chars": 1881,
"preview": "import unittest\nimport pytest\nimport aioflask\nfrom .utils import async_test\n\n\nclass TestApp(unittest.TestCase):\n @asy"
},
{
"path": "tests/test_patch.py",
"chars": 2844,
"preview": "import unittest\nimport aioflask\nimport aioflask.patch\nfrom .utils import async_test\n\n\nclass TestPatch(unittest.TestCase)"
},
{
"path": "tests/test_templating.py",
"chars": 1202,
"preview": "import asyncio\nimport os\nimport unittest\nfrom unittest import mock\nimport aioflask\nfrom .utils import async_test\n\n\nclass"
},
{
"path": "tests/utils.py",
"chars": 197,
"preview": "import asyncio\nfrom greenletio.core import bridge\n\n\ndef async_test(f):\n def wrapper(*args, **kwargs):\n asyncio"
},
{
"path": "tox.ini",
"chars": 749,
"preview": "[tox]\nenvlist=flake8,,py37,py38,py39,py310,pypy3,docs\nskip_missing_interpreters=True\n\n[gh-actions]\npython =\n 3.7: py3"
}
]
About this extraction
This page contains the full source code of the miguelgrinberg/aioflask GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (89.0 KB), approximately 23.7k tokens, and a symbol index with 150 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.