Ali RajabNezhad пре 1 година
родитељ
комит
4dfb9e0f9a

+ 36 - 0
frameworks/Python/panther/README.md

@@ -0,0 +1,36 @@
+# Panther Benchmark Test
+
+This is the Panther portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to Panther. 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
+
+[Panther](https://pantherpy.github.io/) Is A Fast & Friendly Web Framework For Building Async APIs With Python 3.10+ 
+<p align="center">
+  <img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo-vertical.png" alt="logo" style="width: 450px">
+</p>
+
+
+## Implementation
+
+All tests are implemented in a single file ([app.py](app.py)).
+
+* [JSON](app.py): "/json"
+* [DB](app.py): "/db"
+* [QUERY](app.py): "/queries?=#"*
+* [FORTUNES](app.py): "/fortunes"
+* [UPDATE](app.py): "/updates?queries=#"*
+* [Plaintext](app.py): "/plaintext"
+
+*Replace # with an actual number.
+
+## Resources
+
+* [GitHub](https://github.com/AliRn76/Panther)
+* [Documentation](https://pantherpy.github.io)
+* [PyPI](https://pypi.org/project/panther)

+ 132 - 0
frameworks/Python/panther/app.py

@@ -0,0 +1,132 @@
+import multiprocessing
+import os
+from random import randint, sample
+
+import asyncpg
+import jinja2
+from panther import Panther
+from panther.app import API
+from panther.request import Request
+from panther.response import Response, PlainTextResponse, HTMLResponse
+
+READ_ROW_SQL = '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.']
+MAX_POOL_SIZE = 1000 // multiprocessing.cpu_count()
+MIN_POOL_SIZE = max(int(MAX_POOL_SIZE / 2), 1)
+
+pool = None
+
+
+async def create_db_pool():
+    global pool
+    pool = await asyncpg.create_pool(
+        user='benchmarkdbuser',
+        password='benchmarkdbpass',
+        database='hello_world',
+        host='tfb-database',
+        port=5432,
+        min_size=MIN_POOL_SIZE,
+        max_size=MAX_POOL_SIZE,
+    )
+
+
+async def clean_db_pool():
+    await pool.close()
+
+
+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)
+
+
+fortune_template = load_fortunes_template()
+
+
+def get_num_queries(request):
+    value = request.query_params.get('queries')
+    if value is None:
+        return 1
+
+    try:
+        query_count = int(value)
+    except ValueError:
+        return 1
+    if query_count < 1:
+        return 1
+    if query_count > 500:
+        return 500
+    return query_count
+
+
+@API()
+async def json_serialization():
+    return Response(data={'message': 'Hello, world!'})
+
+
+@API()
+async def single_database_query():
+    row_id = randint(1, 10000)
+    async with pool.acquire() as connection:
+        number = await connection.fetchval(READ_ROW_SQL, row_id)
+    return Response(data={'id': row_id, 'randomNumber': number})
+
+
+@API()
+async def multiple_database_queries(request: Request):
+    num_queries = get_num_queries(request)
+    row_ids = sample(range(1, 10000), num_queries)
+
+    async with pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        worlds = [{'id': i, 'randomNumber': await statement.fetchval(i)} for i in row_ids]
+
+    return Response(data=worlds)
+
+
+@API()
+async def fortunes():
+    async with pool.acquire() as connection:
+        fortune_records = await connection.fetch('SELECT * FROM Fortune')
+    fortune_records.append(ADDITIONAL_ROW)
+    fortune_records.sort(key=lambda row: row[1])
+    data = fortune_template.render(fortunes=fortune_records)
+    return HTMLResponse(data=data)
+
+
+@API()
+async def database_updates(request: Request):
+    num_queries = get_num_queries(request)
+    ids = sorted(sample(range(1, 10000 + 1), num_queries))
+    numbers = sorted(sample(range(1, 10000), num_queries))
+    updates = list(zip(ids, numbers))
+
+    worlds = [
+        {'id': row_id, 'randomNumber': number} for row_id, number in updates
+    ]
+
+    async with pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id, _ in updates:
+            await statement.fetchval(row_id)
+        await connection.executemany(WRITE_ROW_SQL, updates)
+    return Response(data=worlds)
+
+
+@API()
+async def plaintext():
+    return PlainTextResponse(b'Hello, world!')
+
+
+url_routing = {
+    'json': json_serialization,
+    'db': single_database_query,
+    'queries': multiple_database_queries,
+    'fortunes': fortunes,
+    'updates': database_updates,
+    'plaintext': plaintext,
+}
+
+app = Panther(__name__, configs=__name__, urls=url_routing, startup=create_db_pool, shutdown=clean_db_pool)

+ 28 - 0
frameworks/Python/panther/benchmark_config.json

@@ -0,0 +1,28 @@
+{
+  "framework": "panther",
+  "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",
+      "framework": "panther",
+      "language": "Python",
+      "flavor": "Python3",
+      "platform": "ASGI",
+      "webserver": "Uvicorn",
+      "os": "Linux",
+      "orm": "Raw",
+      "database_os": "Linux",
+      "database": "Postgres",
+      "display_name": "Panther",
+      "versus": "None",
+      "notes": ""
+    }
+  }]
+}

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

@@ -0,0 +1,19 @@
+[framework]
+name = "panther"
+
+[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 = "ASGI"
+webserver = "Uvicorn"
+versus = "None"

+ 12 - 0
frameworks/Python/panther/panther.dockerfile

@@ -0,0 +1,12 @@
+FROM python:3.11-bullseye
+
+WORKDIR /panther
+
+COPY ./ /panther
+
+RUN pip3 install -U pip
+RUN pip3 install -r /panther/requirements.txt
+
+EXPOSE 8080
+
+CMD gunicorn app:app -k uvicorn.workers.UvicornWorker -c panther_conf.py

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

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

@@ -0,0 +1,11 @@
+panther==3.2.1
+
+cython==3.0.6
+jinja2==3.1.2
+
+asyncpg==0.29.0
+
+gunicorn==21.2.0
+uvicorn==0.24.0
+uvloop==0.19.0
+httptools==0.6.1

+ 14 - 0
frameworks/Python/panther/templates/fortune.html

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