Browse Source

[Python] Add emmett55 (#9331)

Giovanni Barillari 9 months ago
parent
commit
17c1c8deb4

+ 26 - 0
frameworks/Python/emmett55/README.md

@@ -0,0 +1,26 @@
+# Emmett55 Benchmark Test
+
+This is the Emmett55 portion of a [benchmarking tests suite](../../) comparing a variety of web development platforms.
+
+The information below is specific to Emmett55. 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
+
+[Emmett55](https://github.com/emmett-framework/emmett55) is a Python asyncIO micro web framework.
+
+## Test Paths & Source
+
+* [JSON Serialization](app.py): "/json"
+* [Single Database Query](app.py): "/db"
+* [Multiple Database Queries](app.py): "queries?queries=#"
+* [Fortunes](app.py): "/fortunes"
+* [Database Updates](app.py): "updates?queries=#"
+* [Plaintext](app.py): "/plaintext"
+
+*Replace # with an actual number.*
+
+### Resources
+
+* [Github repository](https://github.com/emmett-framework/emmett55)

+ 129 - 0
frameworks/Python/emmett55/app.py

@@ -0,0 +1,129 @@
+import os
+from operator import itemgetter
+from random import randint, sample
+
+import asyncpg
+from emmett55 import App, Pipe, current, request, response
+from emmett55.extensions import Extension, Signals, listen_signal
+from emmett55.tools import service
+from renoir import Renoir
+
+
+class AsyncPG(Extension):
+    __slots__ = ["pool"]
+
+    def on_load(self):
+        self.pool = None
+        self.pipe = AsyncPGPipe(self)
+
+    async def build_pool(self):
+        self.pool = await asyncpg.create_pool(
+            user=os.getenv('PGUSER', 'benchmarkdbuser'),
+            password=os.getenv('PGPASS', 'benchmarkdbpass'),
+            database='hello_world',
+            host='tfb-database',
+            port=5432,
+            min_size=16,
+            max_size=16,
+            max_queries=64_000_000_000,
+            max_inactive_connection_lifetime=0
+        )
+
+    @listen_signal(Signals.after_loop)
+    def _init_pool(self, loop):
+        loop.run_until_complete(self.build_pool())
+
+
+class AsyncPGPipe(Pipe):
+    __slots__ = ["ext"]
+
+    def __init__(self, ext):
+        self.ext = ext
+
+    async def open(self):
+        conn = current._db_conn = self.ext.pool.acquire()
+        current.db = await conn.__aenter__()
+
+    async def close(self):
+        await current._db_conn.__aexit__()
+
+
+app = App(__name__)
+app.config.handle_static = False
+templates = Renoir()
+
+db_ext = app.use_extension(AsyncPG)
+
+SQL_SELECT = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
+SQL_UPDATE = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
+ROW_ADD = [0, 'Additional fortune added at request time.']
+sort_key = itemgetter(1)
+
+
[email protected]()
[email protected]
+async def json():
+    return {'message': 'Hello, World!'}
+
+
[email protected]("/db", pipeline=[db_ext.pipe])
[email protected]
+async def get_random_world():
+    row_id = randint(1, 10000)
+    number = await current.db.fetchval(SQL_SELECT, row_id)
+    return {'id': row_id, 'randomNumber': number}
+
+
+def get_qparam():
+    try:
+        rv = int(request.query_params.queries or 1)
+    except ValueError:
+        return 1
+    if rv < 1:
+        return 1
+    if rv > 500:
+        return 500
+    return rv
+
+
[email protected]("/queries", pipeline=[db_ext.pipe])
[email protected]
+async def get_random_worlds():
+    num_queries = get_qparam()
+    row_ids = sample(range(1, 10000), num_queries)
+    worlds = []
+    statement = await current.db.prepare(SQL_SELECT)
+    for row_id in row_ids:
+        number = await statement.fetchval(row_id)
+        worlds.append({'id': row_id, 'randomNumber': number})
+    return worlds
+
+
[email protected](pipeline=[db_ext.pipe], output='str')
+async def fortunes():
+    response.content_type = "text/html; charset=utf-8"
+    fortunes = await current.db.fetch('SELECT * FROM Fortune')
+    fortunes.append(ROW_ADD)
+    fortunes.sort(key=sort_key)
+    return templates.render("templates/fortunes.html", {"fortunes": fortunes})
+
+
[email protected](pipeline=[db_ext.pipe])
[email protected]
+async def updates():
+    num_queries = get_qparam()
+    updates = list(zip(
+        sample(range(1, 10000), num_queries),
+        sorted(sample(range(1, 10000), num_queries))
+    ))
+    worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
+    statement = await current.db.prepare(SQL_SELECT)
+    for row_id, _ in updates:
+        await statement.fetchval(row_id)
+    await current.db.executemany(SQL_UPDATE, updates)
+    return worlds
+
+
[email protected](output='bytes')
+async def plaintext():
+    return b'Hello, World!'

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

@@ -0,0 +1,27 @@
+{
+  "framework": "emmett55",
+  "tests": [{
+    "default": {
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Emmett55",
+      "language": "Python",
+      "orm": "Raw",
+      "platform": "RSGI",
+      "webserver": "granian",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Emmett55",
+      "notes": "CPython 3.7",
+      "versus": "uvicorn"
+    }
+  }]
+}

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

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

+ 11 - 0
frameworks/Python/emmett55/emmett55.dockerfile

@@ -0,0 +1,11 @@
+FROM python:3.11-slim
+
+ADD ./ /emmett55
+
+WORKDIR /emmett55
+
+RUN pip install --no-cache-dir -r /emmett55/requirements.txt
+
+EXPOSE 8080
+
+CMD python run.py

+ 3 - 0
frameworks/Python/emmett55/requirements.txt

@@ -0,0 +1,3 @@
+asyncpg==0.29.0
+emmett55[orjson]>=1.0.0,<1.1.0
+renoir==1.8.0

+ 20 - 0
frameworks/Python/emmett55/run.py

@@ -0,0 +1,20 @@
+import multiprocessing
+
+from emmett_core.server import run
+
+
+if __name__ == "__main__":
+    workers = multiprocessing.cpu_count()
+
+    run(
+        "rsgi",
+        ("app", "app"),
+        host="0.0.0.0",
+        port=8080,
+        workers=workers,
+        backlog=16384,
+        threading_mode="runtime",
+        http="1",
+        enable_websockets=False,
+        log_level="warn"
+    )

+ 20 - 0
frameworks/Python/emmett55/templates/fortunes.html

@@ -0,0 +1,20 @@
+<!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] }}</td>
+            </tr>
+            {{ pass }}
+        </table>
+    </body>
+</html>