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