Sfoglia il codice sorgente

Update APIDaora Python Framework tests (#5128)

* Add apidaora-core python tests

* Update apidaora python tests

* Fix apidaora python framework
Diogo Dutra 5 anni fa
parent
commit
3934542bd2

+ 2 - 1
frameworks/Python/apidaora/README.md

@@ -14,7 +14,8 @@ the [Python README](../).
 
 ## Test Paths & Sources
 
-All of the test implementations are located within a single file ([app.py](app.py)).
+The default test implementations are located within the file ([app.py](app.py)).
+The core module test implementations are located within the file ([coreapp.py](coreapp.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.
 

+ 20 - 0
frameworks/Python/apidaora/apidaora-core.dockerfile

@@ -0,0 +1,20 @@
+FROM python:3.8-rc-buster
+
+ADD templates/fortune.html /apidaora/templates/
+
+ADD Cython-3.0a0-py3.8-linux-x86_64.egg \
+    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
+
+ADD requirements.txt /apidaora/
+
+RUN pip3 install -r /apidaora/requirements.txt
+
+ADD apidaora_core_conf.py coreapp.py /apidaora/
+
+CMD gunicorn coreapp:app -k uvicorn.workers.UvicornWorker -c apidaora_core_conf.py

+ 1 - 3
frameworks/Python/apidaora/apidaora.dockerfile

@@ -3,15 +3,13 @@ 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
+    uvloop-0.14.0.dev0-py3.8-linux-x86_64.egg
 
 ADD requirements.txt /apidaora/
 

+ 1 - 1
frameworks/Python/apidaora/apidaora_conf.py

@@ -10,5 +10,5 @@ if _is_travis:
 bind = "0.0.0.0:8080"
 keepalive = 120
 errorlog = '-'
-pidfile = '/tmp/fastapi.pid'
+pidfile = '/tmp/apidaora.pid'
 loglevel = 'error'

+ 14 - 0
frameworks/Python/apidaora/apidaora_core_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/apidaora-core.pid'
+loglevel = 'error'

+ 27 - 84
frameworks/Python/apidaora/app.py

@@ -3,21 +3,10 @@ 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 apidaora import appdaora, html, route, text
 from random import randint
 from operator import itemgetter
-from http import HTTPStatus
-from typing import List, TypedDict, Optional
+from typing import TypedDict, Optional
 
 
 logger = getLogger(__name__)
@@ -29,7 +18,6 @@ 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(
@@ -68,55 +56,28 @@ loop = asyncio.get_event_loop()
 loop.run_until_complete(setup_database())
 
 
-@jsondaora
-class JsonResponse(JSONResponse):
-    class Body(TypedDict):
-        message: str
-    body: Body
-
[email protected]('/json')
+async def json_serialization():
+    return {'message': 'Hello, world!'}
 
-@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
+class DatabaseObject(TypedDict):
+    id: int
+    randomNumber: float
 
 
-@path('/db', MethodType.GET)
-async def single_database_query() -> SingleDatabaseResponse:
[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 SingleDatabaseResponse(
-        body=SingleDatabaseResponse.Body(id=row_id, randomNumber=number)
-    )
-
-
-@jsondaora
-class MultipleDatabaseObject(TypedDict):
-    id: int
-    randomNumber: float
-
-
-@jsondaora
-class MultipleDatabaseResponse(JSONResponse):
-    body: List[MultipleDatabaseObject]
+    return DatabaseObject(id=row_id, randomNumber=number)
 
 
-@path('/queries', MethodType.GET)
-async def multiple_database_queries(
-    queries: Optional[str] = None
-) -> MultipleDatabaseResponse:
[email protected]('/queries')
+async def multiple_database_queries(queries: Optional[str] = None):
     num_queries = get_num_queries(queries)
     row_ids = [randint(1, 10000) for _ in range(num_queries)]
     worlds = []
@@ -126,37 +87,28 @@ async def multiple_database_queries(
         for row_id in row_ids:
             number = await statement.fetchval(row_id)
             worlds.append(
-                MultipleDatabaseObject(
+                DatabaseObject(
                     id=row_id,
                     randomNumber=number
                 )
             )
 
-    return MultipleDatabaseResponse(body=worlds)
-
-
-@jsondaora
-class FortunesResponse(HTMLResponse):
-    body: str
+    return worlds
 
 
-@path('/fortunes', MethodType.GET)
-async def fortunes() -> FortunesResponse:
[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 FortunesResponse(
-        body=content
-    )
+    return html(content)
 
 
-@path('/updates', MethodType.GET)
-async def database_updates(
-    queries: Optional[str] = None
-) -> MultipleDatabaseResponse:
[email protected]('/updates')
+async def database_updates(queries: Optional[str] = None):
     worlds = []
     updates = set()
 
@@ -165,7 +117,7 @@ async def database_updates(
 
         for _ in range(get_num_queries(queries)):
             record = await statement.fetchrow(randint(1, 10000))
-            world = MultipleDatabaseObject(
+            world = DatabaseObject(
                 id=record['id'], randomNumber=record['randomnumber']
             )
             world['randomNumber'] = randint(1, 10000)
@@ -174,28 +126,19 @@ async def database_updates(
 
         await connection.executemany(WRITE_ROW_SQL, updates)
 
-    return MultipleDatabaseResponse(
-        body=worlds
-    )
-
+    return worlds
 
-@jsondaora
-class TextResponse(PlainResponse):
-    body: str
 
-
-@path('/plaintext', MethodType.GET)
-async def plaintext() -> TextResponse:
-    return TextResponse(
-        body='Hello, world!'
-    )
[email protected]('/plaintext')
+async def plaintext():
+    return text('Hello, world!')
 
 
-app = appdaora(operations=[
+app = appdaora([
     json_serialization,
     single_database_query,
     multiple_database_queries,
     fortunes,
     database_updates,
     plaintext
-])
+])

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

@@ -24,6 +24,29 @@
         "display_name": "APIDaora",
         "notes": "",
         "versus": "None"
+      },
+      "core": {
+        "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 Core",
+        "notes": "",
+        "versus": "None"
       }
     }
   ]

+ 143 - 0
frameworks/Python/apidaora/coreapp.py

@@ -0,0 +1,143 @@
+import asyncio
+import asyncpg
+import os
+import jinja2
+import orjson
+from logging import getLogger
+from random import randint
+from operator import itemgetter
+from apidaora.asgi.app import asgi_app
+from apidaora.asgi.responses import (
+    JSON_RESPONSE, HTML_RESPONSE, PLAINTEXT_RESPONSE
+)
+from apidaora.asgi.router import Route, make_router
+from apidaora.method import MethodType
+
+
+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[0])
+    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())
+
+
+def json_serialization(path_args, query_dict, headers, body):
+    return JSON_RESPONSE, orjson.dumps({'message': 'Hello, world!'})
+
+
+async def single_database_query(path_args, query_dict, headers, body):
+    row_id = randint(1, 10000)
+
+    async with connection_pool.acquire() as connection:
+        number = await connection.fetchval(READ_ROW_SQL, row_id)
+
+    return JSON_RESPONSE, orjson.dumps(
+        {'id': row_id, 'randomNumber': number}
+    )
+
+
+async def multiple_database_queries(path_args, query_dict, headers, body):
+    num_queries = get_num_queries(query_dict.get('queries', 1))
+    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(
+                dict(
+                    id=row_id,
+                    randomNumber=number
+                )
+            )
+
+    return JSON_RESPONSE, orjson.dumps(worlds)
+
+
+async def fortunes(path_args, query_dict, headers, body):
+    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).encode('utf-8')
+    return HTML_RESPONSE, content
+
+
+async def database_updates(path_args, query_dict, headers, body):
+    worlds = []
+    updates = set()
+    queries = query_dict.get('queries', 1)
+
+    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 = dict(
+                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 JSON_RESPONSE, orjson.dumps(worlds)
+
+
+def plaintext(path_args, query_dict, headers, body):
+    return PLAINTEXT_RESPONSE, b'Hello, world!'
+
+
+routes = (
+    Route('/json', MethodType.GET, json_serialization),
+    Route('/db', MethodType.GET, single_database_query),
+    Route('/queries', MethodType.GET, multiple_database_queries, has_query=True),
+    Route('/fortunes', MethodType.GET, fortunes),
+    Route('/updates', MethodType.GET, database_updates, has_query=True),
+    Route('/plaintext', MethodType.GET, plaintext),
+)
+router = make_router(routes)
+app = asgi_app(router)

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


+ 1 - 2
frameworks/Python/apidaora/requirements.txt

@@ -2,5 +2,4 @@ asyncpg==0.18.3
 gunicorn==19.9.0
 jinja2==2.10.1
 uvicorn==0.9.0
-apidaora==0.6.0a3
-jsondaora==0.6.0
+apidaora==0.9.0