Просмотр исходного кода

Add apidaora python framework (#5079)

* Add apidaora python framework

* Update apidaora interface

* :ok_hand: Fix apidora framework tests
Diogo Dutra 5 лет назад
Родитель
Сommit
abe5b4a5fd

+ 1 - 1
.travis.yml

@@ -103,7 +103,7 @@ env:
     - 'TESTDIR="PHP/php"'
     - 'TESTDIR="PHP/cakephp PHP/codeigniter PHP/fat-free PHP/fuel PHP/kumbiaphp PHP/phpixie PHP/slim PHP/symfony PHP/yii2 PHP/zend PHP/spiral"'
     - 'TESTDIR="PHP/amp PHP/hamlet PHP/laravel PHP/lumen PHP/hhvm PHP/peachpie PHP/swoole PHP/workerman PHP/phalcon PHP/ubiquity PHP/hyperf PHP/sw-fw-less"'
-    - 'TESTDIR="Python/aiohttp Python/api_hour Python/blacksheep Python/bottle Python/cherrypy Python/django Python/eve Python/falcon Python/fastapi Python/flask"'
+    - 'TESTDIR="Python/aiohttp Python/api_hour Python/apidaora Python/blacksheep Python/bottle Python/cherrypy Python/django Python/eve Python/falcon Python/fastapi Python/flask"'
     - 'TESTDIR="Python/hug Python/japronto Python/klein Python/morepath Python/pyramid Python/quart Python/responder Python/sanic Python/spyne Python/starlette"'
     - 'TESTDIR="Python/tornado Python/turbogears Python/uvicorn Python/uwsgi Python/vibora Python/web2py Python/webware Python/weppy Python/wsgi"'
     - 'TESTDIR="Ruby/agoo Ruby/grape Ruby/h2o_mruby Ruby/hanami Ruby/padrino Ruby/rack Ruby/rack-sequel"'

BIN
frameworks/Python/apidaora/Cython-3.0a0-py3.8-linux-x86_64.egg


+ 24 - 0
frameworks/Python/apidaora/README.md

@@ -0,0 +1,24 @@
+# APIDaora Benchmarking Test
+
+This is the APIDaora portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to APIDaora. For further guidance,
+review the [documentation](https://github.com/TechEmpower/FrameworkBenchmarks/wiki).
+Also note that there is additional information provided in
+the [Python README](../).
+
+## Description
+
+[**APIDaora**](https://github.com/dutradda/apidaora) is a HTTP/REST API using <b>dataclasses</b> and <b>TypedDict</b> annotation for python3.8+.
+
+## 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 FastAPI, as APIDaora is an asgi application and have the same principles of using typing annotations for validation/serialization of data.
+
+## Resources
+
+* [APIDaora source code on GitHub](https://github.com/dutradda/apidaora)
+* [APIDaora website - documentation](https://dutradda.github.io/apidaora/)

+ 22 - 0
frameworks/Python/apidaora/apidaora.dockerfile

@@ -0,0 +1,22 @@
+FROM python:3.8-rc-buster
+
+ADD templates/fortune.html /apidaora/templates/
+
+ADD Cython-3.0a0-py3.8-linux-x86_64.egg \
+    orjson-2.0.7-cp38-cp38-manylinux1_x86_64.whl \
+    uvloop-0.14.0.dev0-py3.8-linux-x86_64.egg \
+    /apidaora/
+
+WORKDIR /apidaora
+
+RUN easy_install Cython-3.0a0-py3.8-linux-x86_64.egg \
+    uvloop-0.14.0.dev0-py3.8-linux-x86_64.egg && \
+    pip3 install orjson-2.0.7-cp38-cp38-manylinux1_x86_64.whl
+
+ADD requirements.txt /apidaora/
+
+RUN pip3 install -r /apidaora/requirements.txt
+
+ADD apidaora_conf.py app.py /apidaora/
+
+CMD gunicorn app:app -k uvicorn.workers.UvicornWorker -c apidaora_conf.py

+ 14 - 0
frameworks/Python/apidaora/apidaora_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'

+ 201 - 0
frameworks/Python/apidaora/app.py

@@ -0,0 +1,201 @@
+import asyncio
+import asyncpg
+import os
+import jinja2
+from logging import getLogger
+from apidaora import (
+    HTMLResponse,
+    JSONRequestBody,
+    JSONResponse,
+    MethodType,
+    PlainResponse,
+    appdaora,
+    header_param,
+    path,
+)
+from jsondaora import jsondaora
+from random import randint
+from operator import itemgetter
+from http import HTTPStatus
+from typing import List, TypedDict, Optional
+
+
+logger = getLogger(__name__)
+
+
+READ_ROW_SQL = 'SELECT "randomnumber" FROM "world" WHERE id = $1'
+READ_ROW_SQL_TO_UPDATE = 'SELECT "id", "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())
+
+
+@jsondaora
+class JsonResponse(JSONResponse):
+    class Body(TypedDict):
+        message: str
+    body: Body
+
+
+@path('/json', MethodType.GET)
+async def json_serialization() -> JsonResponse:
+    return JsonResponse(
+        body=JsonResponse.Body(message='Hello, world!')
+    )
+
+
+@jsondaora
+class SingleDatabaseResponse(JSONResponse):
+    class Body(TypedDict):
+        id: int
+        randomNumber: float
+    body: Body
+
+
+@path('/db', MethodType.GET)
+async def single_database_query() -> SingleDatabaseResponse:
+    row_id = randint(1, 10000)
+
+    async with connection_pool.acquire() as connection:
+        number = await connection.fetchval(READ_ROW_SQL, row_id)
+
+    return SingleDatabaseResponse(
+        body=SingleDatabaseResponse.Body(id=row_id, randomNumber=number)
+    )
+
+
+@jsondaora
+class MultipleDatabaseObject(TypedDict):
+    id: int
+    randomNumber: float
+
+
+@jsondaora
+class MultipleDatabaseResponse(JSONResponse):
+    body: List[MultipleDatabaseObject]
+
+
+@path('/queries', MethodType.GET)
+async def multiple_database_queries(
+    queries: Optional[str] = None
+) -> MultipleDatabaseResponse:
+    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(
+                MultipleDatabaseObject(
+                    id=row_id,
+                    randomNumber=number
+                )
+            )
+
+    return MultipleDatabaseResponse(body=worlds)
+
+
+@jsondaora
+class FortunesResponse(HTMLResponse):
+    body: str
+
+
+@path('/fortunes', MethodType.GET)
+async def fortunes() -> FortunesResponse:
+    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 FortunesResponse(
+        body=content
+    )
+
+
+@path('/updates', MethodType.GET)
+async def database_updates(
+    queries: Optional[str] = None
+) -> MultipleDatabaseResponse:
+    worlds = []
+    updates = set()
+
+    async with connection_pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL_TO_UPDATE)
+
+        for _ in range(get_num_queries(queries)):
+            record = await statement.fetchrow(randint(1, 10000))
+            world = MultipleDatabaseObject(
+                id=record['id'], randomNumber=record['randomnumber']
+            )
+            world['randomNumber'] = randint(1, 10000)
+            worlds.append(world)
+            updates.add((world['id'], world['randomNumber']))
+
+        await connection.executemany(WRITE_ROW_SQL, updates)
+
+    return MultipleDatabaseResponse(
+        body=worlds
+    )
+
+
+@jsondaora
+class TextResponse(PlainResponse):
+    body: str
+
+
+@path('/plaintext', MethodType.GET)
+async def plaintext() -> TextResponse:
+    return TextResponse(
+        body='Hello, world!'
+    )
+
+
+app = appdaora(operations=[
+    json_serialization,
+    single_database_query,
+    multiple_database_queries,
+    fortunes,
+    database_updates,
+    plaintext
+])

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

@@ -0,0 +1,30 @@
+{
+  "framework": "apidaora",
+  "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": "APIDaora",
+        "language": "Python",
+        "flavor": "Python3.8",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "APIDaora",
+        "notes": "",
+        "versus": "None"
+      }
+    }
+  ]
+}

BIN
frameworks/Python/apidaora/orjson-2.0.7-cp38-cp38-manylinux1_x86_64.whl


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

@@ -0,0 +1,6 @@
+asyncpg==0.18.3
+gunicorn==19.9.0
+jinja2==2.10.1
+uvicorn==0.9.0
+apidaora==0.6.0a3
+jsondaora==0.6.0

+ 10 - 0
frameworks/Python/apidaora/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>

BIN
frameworks/Python/apidaora/uvloop-0.14.0.dev0-py3.8-linux-x86_64.egg