Skip to content

Commit 17c1c8d

Browse files
authored
[Python] Add emmett55 (#9331)
1 parent c66c749 commit 17c1c8d

File tree

8 files changed

+255
-0
lines changed

8 files changed

+255
-0
lines changed

frameworks/Python/emmett55/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Emmett55 Benchmark Test
2+
3+
This is the Emmett55 portion of a [benchmarking tests suite](../../) comparing a variety of web development platforms.
4+
5+
The information below is specific to Emmett55. For further guidance, review the [documentation](https://github.com/TechEmpower/FrameworkBenchmarks/wiki).
6+
7+
Also note that there is additional information provided in the [Python README](../).
8+
9+
## Description
10+
11+
[Emmett55](https://github.com/emmett-framework/emmett55) is a Python asyncIO micro web framework.
12+
13+
## Test Paths & Source
14+
15+
* [JSON Serialization](app.py): "/json"
16+
* [Single Database Query](app.py): "/db"
17+
* [Multiple Database Queries](app.py): "queries?queries=#"
18+
* [Fortunes](app.py): "/fortunes"
19+
* [Database Updates](app.py): "updates?queries=#"
20+
* [Plaintext](app.py): "/plaintext"
21+
22+
*Replace # with an actual number.*
23+
24+
### Resources
25+
26+
* [Github repository](https://github.com/emmett-framework/emmett55)

frameworks/Python/emmett55/app.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import os
2+
from operator import itemgetter
3+
from random import randint, sample
4+
5+
import asyncpg
6+
from emmett55 import App, Pipe, current, request, response
7+
from emmett55.extensions import Extension, Signals, listen_signal
8+
from emmett55.tools import service
9+
from renoir import Renoir
10+
11+
12+
class AsyncPG(Extension):
13+
__slots__ = ["pool"]
14+
15+
def on_load(self):
16+
self.pool = None
17+
self.pipe = AsyncPGPipe(self)
18+
19+
async def build_pool(self):
20+
self.pool = await asyncpg.create_pool(
21+
user=os.getenv('PGUSER', 'benchmarkdbuser'),
22+
password=os.getenv('PGPASS', 'benchmarkdbpass'),
23+
database='hello_world',
24+
host='tfb-database',
25+
port=5432,
26+
min_size=16,
27+
max_size=16,
28+
max_queries=64_000_000_000,
29+
max_inactive_connection_lifetime=0
30+
)
31+
32+
@listen_signal(Signals.after_loop)
33+
def _init_pool(self, loop):
34+
loop.run_until_complete(self.build_pool())
35+
36+
37+
class AsyncPGPipe(Pipe):
38+
__slots__ = ["ext"]
39+
40+
def __init__(self, ext):
41+
self.ext = ext
42+
43+
async def open(self):
44+
conn = current._db_conn = self.ext.pool.acquire()
45+
current.db = await conn.__aenter__()
46+
47+
async def close(self):
48+
await current._db_conn.__aexit__()
49+
50+
51+
app = App(__name__)
52+
app.config.handle_static = False
53+
templates = Renoir()
54+
55+
db_ext = app.use_extension(AsyncPG)
56+
57+
SQL_SELECT = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
58+
SQL_UPDATE = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
59+
ROW_ADD = [0, 'Additional fortune added at request time.']
60+
sort_key = itemgetter(1)
61+
62+
63+
@app.route()
64+
@service.json
65+
async def json():
66+
return {'message': 'Hello, World!'}
67+
68+
69+
@app.route("/db", pipeline=[db_ext.pipe])
70+
@service.json
71+
async def get_random_world():
72+
row_id = randint(1, 10000)
73+
number = await current.db.fetchval(SQL_SELECT, row_id)
74+
return {'id': row_id, 'randomNumber': number}
75+
76+
77+
def get_qparam():
78+
try:
79+
rv = int(request.query_params.queries or 1)
80+
except ValueError:
81+
return 1
82+
if rv < 1:
83+
return 1
84+
if rv > 500:
85+
return 500
86+
return rv
87+
88+
89+
@app.route("/queries", pipeline=[db_ext.pipe])
90+
@service.json
91+
async def get_random_worlds():
92+
num_queries = get_qparam()
93+
row_ids = sample(range(1, 10000), num_queries)
94+
worlds = []
95+
statement = await current.db.prepare(SQL_SELECT)
96+
for row_id in row_ids:
97+
number = await statement.fetchval(row_id)
98+
worlds.append({'id': row_id, 'randomNumber': number})
99+
return worlds
100+
101+
102+
@app.route(pipeline=[db_ext.pipe], output='str')
103+
async def fortunes():
104+
response.content_type = "text/html; charset=utf-8"
105+
fortunes = await current.db.fetch('SELECT * FROM Fortune')
106+
fortunes.append(ROW_ADD)
107+
fortunes.sort(key=sort_key)
108+
return templates.render("templates/fortunes.html", {"fortunes": fortunes})
109+
110+
111+
@app.route(pipeline=[db_ext.pipe])
112+
@service.json
113+
async def updates():
114+
num_queries = get_qparam()
115+
updates = list(zip(
116+
sample(range(1, 10000), num_queries),
117+
sorted(sample(range(1, 10000), num_queries))
118+
))
119+
worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
120+
statement = await current.db.prepare(SQL_SELECT)
121+
for row_id, _ in updates:
122+
await statement.fetchval(row_id)
123+
await current.db.executemany(SQL_UPDATE, updates)
124+
return worlds
125+
126+
127+
@app.route(output='bytes')
128+
async def plaintext():
129+
return b'Hello, World!'
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"framework": "emmett55",
3+
"tests": [{
4+
"default": {
5+
"json_url": "/json",
6+
"db_url": "/db",
7+
"query_url": "/queries?queries=",
8+
"fortune_url": "/fortunes",
9+
"update_url": "/updates?queries=",
10+
"plaintext_url": "/plaintext",
11+
"port": 8080,
12+
"approach": "Realistic",
13+
"classification": "Micro",
14+
"database": "Postgres",
15+
"framework": "Emmett55",
16+
"language": "Python",
17+
"orm": "Raw",
18+
"platform": "RSGI",
19+
"webserver": "granian",
20+
"os": "Linux",
21+
"database_os": "Linux",
22+
"display_name": "Emmett55",
23+
"notes": "CPython 3.7",
24+
"versus": "uvicorn"
25+
}
26+
}]
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[framework]
2+
name = "emmett55"
3+
4+
[main]
5+
urls.plaintext = "/plaintext"
6+
urls.json = "/json"
7+
urls.db = "/db"
8+
urls.query = "/queries?queries="
9+
urls.update = "/updates?queries="
10+
urls.fortune = "/fortunes"
11+
approach = "Realistic"
12+
classification = "Micro"
13+
database = "Postgres"
14+
database_os = "Linux"
15+
os = "Linux"
16+
orm = "Raw"
17+
platform = "RSGI"
18+
webserver = "granian"
19+
versus = "uvicorn"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.11-slim
2+
3+
ADD ./ /emmett55
4+
5+
WORKDIR /emmett55
6+
7+
RUN pip install --no-cache-dir -r /emmett55/requirements.txt
8+
9+
EXPOSE 8080
10+
11+
CMD python run.py
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
asyncpg==0.29.0
2+
emmett55[orjson]>=1.0.0,<1.1.0
3+
renoir==1.8.0

frameworks/Python/emmett55/run.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import multiprocessing
2+
3+
from emmett_core.server import run
4+
5+
6+
if __name__ == "__main__":
7+
workers = multiprocessing.cpu_count()
8+
9+
run(
10+
"rsgi",
11+
("app", "app"),
12+
host="0.0.0.0",
13+
port=8080,
14+
workers=workers,
15+
backlog=16384,
16+
threading_mode="runtime",
17+
http="1",
18+
enable_websockets=False,
19+
log_level="warn"
20+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Fortunes</title>
5+
</head>
6+
<body>
7+
<table>
8+
<tr>
9+
<th>id</th>
10+
<th>message</th>
11+
</tr>
12+
{{ for fortune in fortunes: }}
13+
<tr>
14+
<td>{{ =fortune[0] }}</td>
15+
<td>{{ =fortune[1] }}</td>
16+
</tr>
17+
{{ pass }}
18+
</table>
19+
</body>
20+
</html>

0 commit comments

Comments
 (0)