Pārlūkot izejas kodu

Add benchmark tests for FastAPI (#4282)

* Add benchmark tests for FastAPI

* Udate FastAPI testing code and versions
Sebastián Ramírez 6 gadi atpakaļ
vecāks
revīzija
2b68a9c332

+ 38 - 0
frameworks/Python/fastapi/README.md

@@ -0,0 +1,38 @@
+# FastAPI Benchmarking Test
+
+This is the FastAPI portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to FastAPI. For further guidance,
+review the [documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/).
+Also note that there is additional information provided in
+the [Python README](../).
+
+## Description
+
+[**FastAPI**](https://github.com/tiangolo/fastapi) is a modern, fast (high-performance), web framework for building APIs with Python 3.6+.
+
+The key features are:
+
+* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic).
+
+* **Fast to code**: Increase the speed to develop features by about 200% to 300% *.
+* **Less bugs**: Reduce about 40% of human (developer) induced errors. *
+* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
+* **Easy**: Designed to be easy to use and learn. Less time reading docs.
+* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Less bugs.
+* **Robust**: Get production-ready code. With automatic interactive documentation.
+* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
+
+<small>* estimation based on tests on an internal development team, building production applications.</small>
+
+## Test Paths & Sources
+
+All of the test implementations are located within a single file ([app.py](app.py)).
+
+All the tests are based on the ones for Starlette, as FastAPI is basically Starlette on steroids plus Pydantic, with many features specifically desgined for API development. All this while still supporting all the other features provided by Starlette.
+
+## Resources
+
+* [FastAPI source code on GitHub](https://github.com/tiangolo/fastapi)
+* [FastAPI website - documentation](https://fastapi.tiangolo.com)

+ 118 - 0
frameworks/Python/fastapi/app.py

@@ -0,0 +1,118 @@
+import asyncio
+import asyncpg
+import os
+import jinja2
+from fastapi import FastAPI
+from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse
+from random import randint
+from operator import itemgetter
+from urllib.parse import parse_qs
+
+
+READ_ROW_SQL = 'SELECT "randomnumber" FROM "world" WHERE id = $1'
+WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
+ADDITIONAL_ROW = [0, 'Additional fortune added at request time.']
+
+
+
+async def setup_database():
+    global connection_pool
+    connection_pool = await asyncpg.create_pool(
+        user=os.getenv('PGUSER', 'benchmarkdbuser'),
+        password=os.getenv('PGPASS', 'benchmarkdbpass'),
+        database='hello_world',
+        host='tfb-database',
+        port=5432
+    )
+
+
+def load_fortunes_template():
+    path = os.path.join('templates', 'fortune.html')
+    with open(path, 'r') as template_file:
+        template_text = template_file.read()
+        return jinja2.Template(template_text)
+
+
+def get_num_queries(queries):
+    try:
+        query_count = int(queries)
+    except (ValueError, TypeError):
+        return 1
+
+    if query_count < 1:
+        return 1
+    if query_count > 500:
+        return 500
+    return query_count
+
+
+connection_pool = None
+sort_fortunes_key = itemgetter(1)
+template = load_fortunes_template()
+loop = asyncio.get_event_loop()
+loop.run_until_complete(setup_database())
+
+
+app = FastAPI()
+
+
[email protected]('/json')
+async def json_serialization():
+    return {'message': 'Hello, world!'}
+
+
[email protected]('/db')
+async def single_database_query():
+    row_id = randint(1, 10000)
+
+    async with connection_pool.acquire() as connection:
+        number = await connection.fetchval(READ_ROW_SQL, row_id)
+
+    return {'id': row_id, 'randomNumber': number}
+
+
[email protected]('/queries')
+async def multiple_database_queries(queries = None):
+
+    num_queries = get_num_queries(queries)
+    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    worlds = []
+
+    async with connection_pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id in row_ids:
+            number = await statement.fetchval(row_id)
+            worlds.append({'id': row_id, 'randomNumber': number})
+
+    return worlds
+
+
[email protected]('/fortunes')
+async def fortunes():
+    async with connection_pool.acquire() as connection:
+        fortunes = await connection.fetch('SELECT * FROM Fortune')
+
+    fortunes.append(ADDITIONAL_ROW)
+    fortunes.sort(key=sort_fortunes_key)
+    content = template.render(fortunes=fortunes)
+    return HTMLResponse(content)
+
+
[email protected]('/updates')
+async def database_updates(queries = None):
+    num_queries = get_num_queries(queries)
+    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
+
+    async with connection_pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id, number in updates:
+            await statement.fetchval(row_id)
+        await connection.executemany(WRITE_ROW_SQL, updates)
+
+    return worlds
+
+
[email protected]('/plaintext')
+async def plaintext():
+    return PlainTextResponse(b'Hello, world!')

+ 30 - 0
frameworks/Python/fastapi/benchmark_config.json

@@ -0,0 +1,30 @@
+{
+  "framework": "fastapi",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "FastAPI",
+        "language": "Python",
+        "flavor": "Python3",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "FastAPI",
+        "notes": "",
+        "versus": "None"
+      }
+    }
+  ]
+}

+ 9 - 0
frameworks/Python/fastapi/fastapi.dockerfile

@@ -0,0 +1,9 @@
+FROM python:3.6.6-stretch
+
+ADD ./ /fastapi
+
+WORKDIR /fastapi
+
+RUN pip3 install -r /fastapi/requirements.txt
+
+CMD gunicorn app:app -k uvicorn.workers.UvicornWorker -c fastapi_conf.py

+ 14 - 0
frameworks/Python/fastapi/fastapi_conf.py

@@ -0,0 +1,14 @@
+import multiprocessing
+import os
+
+_is_travis = os.environ.get('TRAVIS') == 'true'
+
+workers = multiprocessing.cpu_count()
+if _is_travis:
+    workers = 2
+
+bind = "0.0.0.0:8080"
+keepalive = 120
+errorlog = '-'
+pidfile = '/tmp/fastapi.pid'
+loglevel = 'error'

+ 6 - 0
frameworks/Python/fastapi/requirements.txt

@@ -0,0 +1,6 @@
+asyncpg==0.18.2
+gunicorn==19.9.0
+fastapi==0.1.12
+jinja2==2.10
+ujson==1.35
+uvicorn==0.3.23

+ 10 - 0
frameworks/Python/fastapi/templates/fortune.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+{% for fortune in fortunes %}<tr><td>{{ fortune[0] }}</td><td>{{ fortune[1]|e }}</td></tr>
+{% endfor %}</table>
+</body>
+</html>