Skip to content

[aiohttp] Tweaks #9783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1a64195
[aiohttp] Tweaks
Dreamsorcerer Apr 5, 2025
ad113a1
Rename aiohttp.dockerfile to aiohttp-orm.dockerfile
Dreamsorcerer Apr 5, 2025
4f69f8f
Rename aiohttp-pg-raw.dockerfile to aiohttp.dockerfile
Dreamsorcerer Apr 5, 2025
61e69bc
Update benchmark_config.json
Dreamsorcerer Apr 5, 2025
9099ee9
Update config.toml
Dreamsorcerer Apr 5, 2025
edb9962
Update benchmark_config.json
Dreamsorcerer Apr 5, 2025
7523de5
Update requirements.txt
Dreamsorcerer Apr 5, 2025
5c107aa
Update frameworks/Python/aiohttp/requirements.txt
Dreamsorcerer Apr 5, 2025
93c3854
Update config.toml
Dreamsorcerer Apr 5, 2025
5b7c9d3
Update benchmark_config.json
Dreamsorcerer Apr 5, 2025
edbf109
Update views.py
Dreamsorcerer Apr 5, 2025
6ac0228
Update views.py
Dreamsorcerer Apr 5, 2025
2df46f5
Update views.py
Dreamsorcerer Apr 5, 2025
8ca2a89
Update views.py
Dreamsorcerer Apr 5, 2025
5fa8a0f
Update views.py
Dreamsorcerer Apr 5, 2025
cd53d3a
Update main.py
Dreamsorcerer Apr 5, 2025
ce8989d
Update views.py
Dreamsorcerer Apr 5, 2025
dd411b5
Update views.py
Dreamsorcerer Apr 6, 2025
30819c0
Update views.py
Dreamsorcerer Apr 6, 2025
f76026c
Update views.py
Dreamsorcerer Apr 6, 2025
a4d9022
Merge branch 'master' into patch-2
Dreamsorcerer Apr 7, 2025
8dc0a63
Update views.py
Dreamsorcerer Apr 7, 2025
af1b566
Update views.py
Dreamsorcerer Apr 7, 2025
76e1fb0
Update views.py
Dreamsorcerer Apr 7, 2025
3c61b07
aiohtp orm use bindparam
Reskov Apr 8, 2025
49a4567
Merge pull request #1 from Reskov/aiohttp-bindparam
Dreamsorcerer Apr 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ WORKDIR aiohttp
RUN pip3 install cython==3.0.11 && \
pip3 install -r /aiohttp/requirements.txt

ENV CONNECTION=RAW
WORKDIR /aiohttp

EXPOSE 8080

Expand Down
2 changes: 1 addition & 1 deletion frameworks/Python/aiohttp/aiohttp.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ WORKDIR aiohttp
RUN pip3 install cython==3.0.11 && \
pip3 install -r /aiohttp/requirements.txt

WORKDIR /aiohttp
ENV CONNECTION=RAW

EXPOSE 8080

Expand Down
4 changes: 2 additions & 2 deletions frameworks/Python/aiohttp/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ async def db_ctx(app: web.Application):

def setup_routes(app):
if CONNECTION_ORM:
app.router.add_get('/json', json)
app.router.add_get('/db', single_database_query_orm)
app.router.add_get('/queries/{queries:.*}', multiple_database_queries_orm)
app.router.add_get('/fortunes', fortunes)
app.router.add_get('/updates/{queries:.*}', updates)
app.router.add_get('/plaintext', plaintext)
else:
app.router.add_get('/json', json)
app.router.add_get('/plaintext', plaintext)
app.router.add_get('/db', single_database_query_raw)
app.router.add_get('/queries/{queries:.*}', multiple_database_queries_raw)
app.router.add_get('/fortunes', fortunes_raw)
Expand Down
71 changes: 44 additions & 27 deletions frameworks/Python/aiohttp/app/views.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import random
from operator import attrgetter, itemgetter
from pathlib import Path
from random import randint, sample

import aiohttp.web
import jinja2
import sqlalchemy
import sqlalchemy.orm
import orjson
from aiohttp.web import Response
from sqlalchemy import select
from sqlalchemy.orm.attributes import flag_modified

from .models import sa_fortunes, sa_worlds, Fortune, World
from . import models

# In current versions of Python (tested 3.9 and 3.12), from ... import ...
# creates variables that are atleast 4x slower to reference:
# > python3 -m timeit 'from random import sample' 'sample'
# 500000 loops, best of 5: 823 nsec per loop
# > python3 -m timeit 'import random' 'random.sample'
# 2000000 loops, best of 5: 161 nsec per loop
# > python3 -m timeit 'import random; sample = random.sample' 'sample'
# 2000000 loops, best of 5: 161 nsec per loop
dumps = orjson.dumps
randint = random.randint
sample = random.sample
Response = aiohttp.web.Response
select = sqlalchemy.select
flag_modified = sqlalchemy.orm.attributes.flag_modified
Fortune = models.Fortune
World = models.World

ADDITIONAL_FORTUNE_ORM = Fortune(id=0, message='Additional fortune added at request time.')
ADDITIONAL_FORTUNE_ROW = {'id': 0, 'message': 'Additional fortune added at request time.'}
READ_ROW_SQL = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
READ_SELECT_ORM = select(World.randomnumber)
READ_SELECT_ORM = select(World.randomnumber).where(World.id == sqlalchemy.bindparam("id"))
READ_FORTUNES_ORM = select(Fortune.id, Fortune.message)
WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$2 WHERE id=$1'

template_path = Path(__file__).parent / 'templates' / 'fortune.jinja'
Expand All @@ -33,12 +51,14 @@ def get_num_queries(request):
return 500
return num_queries


def json_response(payload):
return Response(
body=orjson.dumps(payload),
body=dumps(payload),
content_type="application/json",
)


async def json(request):
"""
Test 1
Expand All @@ -52,7 +72,7 @@ async def single_database_query_orm(request):
"""
id_ = randint(1, 10000)
async with request.app['db_session']() as sess:
num = await sess.scalar(select(World.randomnumber).filter_by(id=id_))
num = await sess.scalar(READ_SELECT_ORM, {"id": id_})
return json_response({'id': id_, 'randomNumber': num})


Expand All @@ -78,7 +98,7 @@ async def multiple_database_queries_orm(request):
result = []
async with request.app['db_session']() as sess:
for id_ in ids:
num = await sess.scalar(READ_SELECT_ORM.where(World.id == id_))
num = await sess.scalar(READ_SELECT_ORM, {"id": id_})
result.append({'id': id_, 'randomNumber': num})
return json_response(result)

Expand Down Expand Up @@ -107,7 +127,7 @@ async def fortunes(request):
Test 4 ORM
"""
async with request.app['db_session']() as sess:
ret = await sess.execute(select(Fortune.id, Fortune.message))
ret = await sess.execute(READ_FORTUNES_ORM)
fortunes = ret.all()
fortunes.append(ADDITIONAL_FORTUNE_ORM)
fortunes.sort(key=sort_fortunes_orm)
Expand All @@ -132,38 +152,35 @@ async def updates(request):
Test 5 ORM
"""
num_queries = get_num_queries(request)

ids = sample(range(1, 10000 + 1), num_queries)
ids.sort()
worlds = []
update_ids = sample(range(1, 10001), num_queries)
update_ids.sort()
updates = tuple(zip(update_ids, sample(range(1, 10001), num_queries)))
worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]

async with request.app['db_session'].begin() as sess:
for row_id in ids:
random_number = randint(1, 10000)
world = await sess.get(World, row_id, populate_existing=True)
world.randomnumber = random_number
# force sqlalchemy to UPDATE entry even if the value has not changed
# doesn't make sense in a real application, added only for pass `tfb verify`
for id_, number in updates:
world = await sess.get(World, id_, populate_existing=True)
world.randomnumber = number
# Force sqlalchemy to UPDATE entry even if the value has not changed
# doesn't make sense in a real application, added only to pass tests.
flag_modified(world, "randomnumber")
worlds.append({'id': row_id, 'randomNumber': random_number})

return json_response(worlds)

async def updates_raw(request):
"""
Test 5 RAW
"""
num_queries = get_num_queries(request)
ids = sample(range(1, 10000 + 1), num_queries)
ids.sort()
updates = [(row_id, randint(1, 10000)) for row_id in ids]
update_ids = sample(range(1, 10001), num_queries)
update_ids.sort()
updates = tuple(zip(update_ids, sample(range(1, 10001), num_queries)))
worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]

async with request.app['pg'].acquire() as conn:
stmt = await conn.prepare(READ_ROW_SQL)
for row_id in ids:
for id_, _ in updates:
# the result of this is the int previous random number which we don't actually use
await stmt.fetchval(row_id)
await stmt.fetchval(id_)
await conn.executemany(WRITE_ROW_SQL, updates)

return json_response(worlds)
Expand Down
12 changes: 6 additions & 6 deletions frameworks/Python/aiohttp/benchmark_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
"framework": "aiohttp",
"language": "Python",
"flavor": "Python3",
"orm": "Full",
"orm": "Raw",
"platform": "asyncio",
"webserver": "gunicorn",
"os": "Linux",
"database_os": "Linux",
"display_name": "aiohttp",
"notes": "uses aiopg with sqlalchemy for database access"
"notes": "uses asyncpg for database access"
},
"pg-raw": {
"orm": {
"db_url": "/db",
"query_url": "/queries/",
"fortune_url": "/fortunes",
Expand All @@ -35,13 +35,13 @@
"framework": "aiohttp",
"language": "Python",
"flavor": "Python3",
"orm": "Raw",
"orm": "Full",
"platform": "asyncio",
"webserver": "gunicorn",
"os": "Linux",
"database_os": "Linux",
"display_name": "aiohttp-pg-raw",
"notes": "uses asyncpg for database access",
"display_name": "aiohttp-orm",
"notes": "uses sqlalchemy for database access",
"versus": "default"
}
}]
Expand Down
6 changes: 3 additions & 3 deletions frameworks/Python/aiohttp/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ classification = "Micro"
database = "Postgres"
database_os = "Linux"
os = "Linux"
orm = "Full"
orm = "Raw"
platform = "asyncio"
webserver = "gunicorn"
versus = "None"

[pg-raw]
[orm]
urls.db = "/db"
urls.query = "/queries/"
urls.update = "/updates/"
Expand All @@ -28,7 +28,7 @@ classification = "Micro"
database = "Postgres"
database_os = "Linux"
os = "Linux"
orm = "Raw"
orm = "Full"
platform = "asyncio"
webserver = "gunicorn"
versus = "default"
2 changes: 1 addition & 1 deletion frameworks/Python/aiohttp/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ aiohttp==3.11.16
asyncpg==0.30.0
gunicorn==23.0.0
jinja2==3.1.6
SQLAlchemy==2.0.39
SQLAlchemy==2.0.40
orjson==3.10.16
uvloop==0.21.0
Loading