|
@@ -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'
|
|
@@ -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
|
|
@@ -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})
|
|
|
|
|
|
|
|
@@ -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)
|
|
|
|
|
@@ -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)
|
|
@@ -132,21 +152,18 @@ 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):
|
|
@@ -154,16 +171,16 @@ 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)
|