Quellcode durchsuchen

feat: add routerling python framework (#6994)

Co-authored-by: Tersoo <[email protected]>
Tersoo vor 3 Jahren
Ursprung
Commit
1002b5e019

+ 22 - 0
frameworks/Python/routerling/README.md

@@ -0,0 +1,22 @@
+# Routerling Benchmarking Test
+
+This is the Routerling portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to Routerling. 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
+
+[Routerling](https://github.com/rayattack/pyrouterling) is a lightweight ASGI framework.
+
+## Test Paths & Sources
+
+All of the test implementations are located within a single file ([app.py](app.py)).
+
+## Resources
+
+* [Routerling on GitHub](https://github.com/rayattack/pyrouterling)
+* [ASGI specification](https://asgi.readthedocs.io/en/latest/)

+ 170 - 0
frameworks/Python/routerling/app.py

@@ -0,0 +1,170 @@
+import asyncio
+import asyncpg
+import jinja2
+import os
+import ujson
+from random import randint
+from operator import itemgetter
+from urllib.parse import parse_qs
+
+from routerling import Router
+
+
+router = Router()
+
+
+async def setup():
+    global pool
+    pool = await asyncpg.create_pool(
+        user=os.getenv('PGUSER', 'benchmarkdbuser'),
+        password=os.getenv('PGPASS', 'benchmarkdbpass'),
+        database='hello_world',
+        host='tfb-database',
+        port=5432
+    )
+
+
+CONTENT_TYPE = 'Content-Type'
+JSON = 'application/json'
+
+READ_ROW_SQL = 'SELECT "randomnumber", "id" 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.']
+
+
+pool = None
+key = itemgetter(1)
+json_dumps = ujson.dumps
+template = None
+path = os.path.join('templates', 'fortune.html')
+with open(path, 'r') as template_file:
+    template_text = template_file.read()
+    template = jinja2.Template(template_text)
+
+loop = asyncio.get_event_loop()
+loop.run_until_complete(setup())
+
+
+def get_num_queries(scope):
+    try:
+        query_string = scope['query_string']
+        query_count = int(parse_qs(query_string)[b'queries'][0])
+    except (KeyError, IndexError, ValueError):
+        return 1
+
+    if query_count < 1:
+        return 1
+    if query_count > 500:
+        return 500
+    return query_count
+
+
+async def json_serialization(r, w, c):
+    """
+    Test type 1: JSON Serialization
+    """
+    content = json_dumps({'message': 'Hello, world!'})
+    w.headers = CONTENT_TYPE, JSON
+    w.body = content
+
+
+async def single_database_query(r, w, c):
+    """
+    Test type 2: Single database object
+    """
+    row_id = randint(1, 10000)
+    connection = await pool.acquire()
+    try:
+        number = await connection.fetchval(READ_ROW_SQL, row_id)
+        world = {'id': row_id, 'randomNumber': number}
+    finally:
+        await pool.release(connection)
+
+    content = json_dumps(world).encode('utf-8')
+    w.headers = CONTENT_TYPE, JSON
+    w.body = content
+
+
+async def multiple_database_queries(r, w, c):
+    """
+    Test type 3: Multiple database queries
+    """
+    num_queries = get_num_queries(r._scope)
+    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    worlds = []
+
+    connection = await pool.acquire()
+    try:
+        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})
+    finally:
+        await pool.release(connection)
+
+    content = json_dumps(worlds).encode('utf-8')
+    w.headers = CONTENT_TYPE, JSON
+    w.body = content
+
+
+async def fortunes(r, w, c):
+    """
+    Test type 4: Fortunes
+    """
+    connection = await pool.acquire()
+    try:
+        fortunes = await connection.fetch('SELECT * FROM Fortune')
+    finally:
+        await pool.release(connection)
+
+    fortunes.append(ADDITIONAL_ROW)
+    fortunes.sort(key=key)
+    content = template.render(fortunes=fortunes).encode('utf-8')
+    w.headers = CONTENT_TYPE, 'text/html; charset=utf-8'
+    w.body = content
+
+
+async def database_updates(r, w, c):
+    """
+    Test type 5: Database updates
+    """
+    num_queries = get_num_queries(r._scope)
+    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
+
+    connection = await pool.acquire()
+    try:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id, _ in updates:
+            await statement.fetchval(row_id)
+        await connection.executemany(WRITE_ROW_SQL, updates)
+    finally:
+        await pool.release(connection)
+
+    content = json_dumps(worlds).encode('utf-8')
+    w.headers = CONTENT_TYPE, JSON
+    w.body = content
+
+
+async def plaintext(r, w, c):
+    """
+    Test type 6: Plaintext
+    """
+    content = 'Hello, world!'
+    w.headers = CONTENT_TYPE, 'text/plain; charset=utf-8'
+    w.body = content
+
+
+async def handle_404(r, w, c):
+    content = b'Not found'
+    w.headers = CONTENT_TYPE, b'text/plain; charset=utf-8'
+    w.body = content
+
+
+
+router.HTTP('/json', json_serialization)
+router.HTTP('/db', single_database_query)
+router.HTTP('/queries', multiple_database_queries)
+router.HTTP('/fortunes', fortunes)
+router.HTTP('/updates', database_updates)
+router.HTTP('/plaintext', plaintext)

+ 27 - 0
frameworks/Python/routerling/benchmark_config.json

@@ -0,0 +1,27 @@
+{
+  "framework": "routerling",
+  "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": "Platform",
+      "framework": "uvicorn",
+      "language": "Python",
+      "flavor": "Python3",
+      "platform": "None",
+      "webserver": "None",
+      "os": "Linux",
+      "orm": "Raw",
+      "database_os": "Linux",
+      "database": "Postgres",
+      "display_name": "routerling",
+      "notes": ""
+    }
+  }]
+}

+ 19 - 0
frameworks/Python/routerling/config.toml

@@ -0,0 +1,19 @@
+[framework]
+name = "routerling"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Platform"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "None"
+versus = "None"

+ 11 - 0
frameworks/Python/routerling/requirements.txt

@@ -0,0 +1,11 @@
+asgiref==3.4.1
+asyncpg==0.24.0
+click==8.0.1
+gunicorn==20.1.0
+h11==0.12.0
+Jinja2==3.0.1
+MarkupSafe==2.0.1
+routerling==0.3.1
+ujson==4.1.0
+uvloop==0.16.0
+uvicorn==0.14.0

+ 17 - 0
frameworks/Python/routerling/routerling.dockerfile

@@ -0,0 +1,17 @@
+FROM python:3.8
+
+WORKDIR /routerling
+
+RUN pip3 install cython==0.29.13
+
+ADD requirements.txt /routerling/
+
+RUN pip3 install -r /routerling/requirements.txt
+
+ADD templates/fortune.html /routerling/templates/
+
+ADD settings.py app.py /routerling/
+
+EXPOSE 8080
+
+CMD gunicorn app:router -k uvicorn.workers.UvicornWorker -c settings.py

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

+ 18 - 0
frameworks/Python/routerling/templates/fortune.html

@@ -0,0 +1,18 @@
+<!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>