소스 검색

Updates & fixes to Python frameworks (#7311)

* Python-Quart: Minor updates to the Quart app

This updates to the more recent API styles used for Quart. It is not
expected to impact the performance or validity.

* Python: Fix asyncpg PreparedStatement usage

asyncpg's PreparedStatement
(https://magicstack.github.io/asyncpg/current/api/index.html#prepared-statements)
will cache results in a local LRU cache which is not desired as per
https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview#general-test-requirements
this results in some test failures whereby not enough queries are
completed.

The fix is to sample the range and hence get the `queries` number of
unique IDs rather than potentially get overlapping IDs.

I've done this for the frameworks I am familiar enough with to change
with confidence. It may apply to the other frameworks using asyncpg.

* Partially revert c18853d56f3dad20d3a72ea57cda375d26e8319a

There is a separate setup for OrJSON responses see app-orjson.py.

* Python-Starlette: Remove UJSONResponse usage

It was removed from Starlette in 0.14.1. Replaced with JSONResponse as
that is the Starlette default and hence a more realistic
representation of usage.

* Python-Sanic: Fix and upgrade sanic

* Python Starlette: Update dependencies

* Python: Utilise startup events to create DB connection (Starlette, FastAPI)

This is safer as it ensures the conneciton is created in the correct
asyncio event loop.
Phil Jones 3 년 전
부모
커밋
ed4ef81158

+ 19 - 36
frameworks/Python/fastapi/app.py

@@ -4,7 +4,7 @@ import os
 import jinja2
 import jinja2
 from fastapi import FastAPI
 from fastapi import FastAPI
 from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse
 from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse
-from random import randint
+from random import randint, sample
 from operator import itemgetter
 from operator import itemgetter
 from urllib.parse import parse_qs
 from urllib.parse import parse_qs
 
 
@@ -14,31 +14,6 @@ WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
 ADDITIONAL_ROW = [0, 'Additional fortune added at request time.']
 ADDITIONAL_ROW = [0, 'Additional fortune added at request time.']
 
 
 
 
-# https://www.starlette.io/responses/#custom-json-serialization
-try:
-    import orjson
-
-    class CustomJSONResponse(JSONResponse):
-        def render(self, content):
-            return orjson.dumps(content)
-
-except ImportError:
-
-    class CustomJSONResponse(JSONResponse):
-        pass
-
-
-async def setup_database():
-    global connection_pool
-    connection_pool = await asyncpg.create_pool(
-        user=os.getenv('PGUSER', 'benchmarkdbuser'),
-        password=os.getenv('PGPASS', 'benchmarkdbpass'),
-        database='hello_world',
-        host='tfb-database',
-        port=5432
-    )
-
-
 def load_fortunes_template():
 def load_fortunes_template():
     path = os.path.join('templates', 'fortune.html')
     path = os.path.join('templates', 'fortune.html')
     with open(path, 'r') as template_file:
     with open(path, 'r') as template_file:
@@ -62,16 +37,25 @@ def get_num_queries(queries):
 connection_pool = None
 connection_pool = None
 sort_fortunes_key = itemgetter(1)
 sort_fortunes_key = itemgetter(1)
 template = load_fortunes_template()
 template = load_fortunes_template()
-loop = asyncio.get_event_loop()
-loop.run_until_complete(setup_database())
-
 
 
 app = FastAPI()
 app = FastAPI()
 
 
 
 
[email protected]_event("startup")
+async def setup_database():
+    global connection_pool
+    connection_pool = await asyncpg.create_pool(
+        user=os.getenv('PGUSER', 'benchmarkdbuser'),
+        password=os.getenv('PGPASS', 'benchmarkdbpass'),
+        database='hello_world',
+        host='tfb-database',
+        port=5432
+    )
+
+
 @app.get('/json')
 @app.get('/json')
 async def json_serialization():
 async def json_serialization():
-    return CustomJSONResponse({'message': 'Hello, world!'})
+    return JSONResponse({'message': 'Hello, world!'})
 
 
 
 
 @app.get('/db')
 @app.get('/db')
@@ -81,14 +65,13 @@ async def single_database_query():
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
         number = await connection.fetchval(READ_ROW_SQL, row_id)
         number = await connection.fetchval(READ_ROW_SQL, row_id)
 
 
-    return CustomJSONResponse({'id': row_id, 'randomNumber': number})
+    return JSONResponse({'id': row_id, 'randomNumber': number})
 
 
 
 
 @app.get('/queries')
 @app.get('/queries')
 async def multiple_database_queries(queries = None):
 async def multiple_database_queries(queries = None):
-
     num_queries = get_num_queries(queries)
     num_queries = get_num_queries(queries)
-    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    row_ids = sample(range(1, 10000), num_queries)
     worlds = []
     worlds = []
 
 
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
@@ -97,7 +80,7 @@ async def multiple_database_queries(queries = None):
             number = await statement.fetchval(row_id)
             number = await statement.fetchval(row_id)
             worlds.append({'id': row_id, 'randomNumber': number})
             worlds.append({'id': row_id, 'randomNumber': number})
 
 
-    return CustomJSONResponse(worlds)
+    return JSONResponse(worlds)
 
 
 
 
 @app.get('/fortunes')
 @app.get('/fortunes')
@@ -114,7 +97,7 @@ async def fortunes():
 @app.get('/updates')
 @app.get('/updates')
 async def database_updates(queries = None):
 async def database_updates(queries = None):
     num_queries = get_num_queries(queries)
     num_queries = get_num_queries(queries)
-    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    updates = [(row_id, randint(1, 10000)) for row_id in sample(range(1, 10000), num_queries)]
     worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
     worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
 
 
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
@@ -123,7 +106,7 @@ async def database_updates(queries = None):
             await statement.fetchval(row_id)
             await statement.fetchval(row_id)
         await connection.executemany(WRITE_ROW_SQL, updates)
         await connection.executemany(WRITE_ROW_SQL, updates)
 
 
-    return CustomJSONResponse(worlds)
+    return JSONResponse(worlds)
 
 
 
 
 @app.get('/plaintext')
 @app.get('/plaintext')

+ 8 - 8
frameworks/Python/fastapi/app_orjson.py

@@ -4,12 +4,10 @@ import os
 import jinja2
 import jinja2
 from fastapi import FastAPI
 from fastapi import FastAPI
 from fastapi.responses import HTMLResponse, ORJSONResponse, PlainTextResponse
 from fastapi.responses import HTMLResponse, ORJSONResponse, PlainTextResponse
-from random import randint
+from random import randint, sample
 from operator import itemgetter
 from operator import itemgetter
 from functools import partial
 from functools import partial
 
 
-_randint = partial(randint, 1, 10000)
-
 READ_ROW_SQL = 'SELECT "id", "randomnumber" FROM "world" WHERE id = $1'
 READ_ROW_SQL = 'SELECT "id", "randomnumber" FROM "world" WHERE id = $1'
 WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
 WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
 ADDITIONAL_ROW = [0, "Additional fortune added at request time."]
 ADDITIONAL_ROW = [0, "Additional fortune added at request time."]
@@ -64,7 +62,7 @@ async def json_serialization():
 @app.get("/db")
 @app.get("/db")
 async def single_database_query():
 async def single_database_query():
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
-        record = await connection.fetchrow(READ_ROW_SQL, _randint())
+        record = await connection.fetchrow(READ_ROW_SQL, randint(1, 10000))
 
 
     return ORJSONResponse({"id": record['id'], "randomNumber": record['randomnumber']})
     return ORJSONResponse({"id": record['id'], "randomNumber": record['randomnumber']})
 
 
@@ -72,12 +70,14 @@ async def single_database_query():
 @app.get("/queries")
 @app.get("/queries")
 async def multiple_database_queries(queries=None):
 async def multiple_database_queries(queries=None):
     num_queries = get_num_queries(queries)
     num_queries = get_num_queries(queries)
-    worlds = tuple(map(lambda _: {"id": _randint(), "randomNumber": None}, range(num_queries)))
+    row_ids = sample(range(1, 10000), num_queries)
+    worlds = []
 
 
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
         statement = await connection.prepare(READ_ROW_SQL)
         statement = await connection.prepare(READ_ROW_SQL)
-        for world in worlds:
-            world["randomNumber"] = await statement.fetchval(world["id"])
+        for row_id in row_ids:
+            number = await statement.fetchval(row_id)
+            worlds.append({'id': row_id, 'randomNumber': number})
 
 
     return ORJSONResponse(worlds)
     return ORJSONResponse(worlds)
 
 
@@ -96,7 +96,7 @@ async def fortunes():
 @app.get("/updates")
 @app.get("/updates")
 async def database_updates(queries=None):
 async def database_updates(queries=None):
     num_queries = get_num_queries(queries)
     num_queries = get_num_queries(queries)
-    updates = [(_randint(), _randint()) for _ in range(num_queries)]
+    updates = [(row_id, randint(1, 10000)) for row_id in sample(range(1, 10000), num_queries)]
     worlds = [{"id": row_id, "randomNumber": number} for row_id, number in updates]
     worlds = [{"id": row_id, "randomNumber": number} for row_id, number in updates]
 
 
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:

+ 21 - 30
frameworks/Python/quart/app.py

@@ -3,11 +3,11 @@ import random
 import os
 import os
 
 
 import asyncpg
 import asyncpg
-from quart import Quart, jsonify, make_response, request, render_template
+from quart import jsonify, Quart, request, render_template
 
 
 app = Quart(__name__)
 app = Quart(__name__)
 
 
-GET_WORLD = "select id,randomnumber from world where id = $1"
+GET_WORLD = "select id, randomnumber from world where id = $1"
 UPDATE_WORLD = "update world set randomNumber = $2 where id = $1"
 UPDATE_WORLD = "update world set randomNumber = $2 where id = $1"
 
 
 
 
@@ -27,20 +27,17 @@ async def disconnect_from_db():
     await app.db.close()
     await app.db.close()
 
 
 
 
-@app.route("/json")
+@app.get("/json")
 async def json():
 async def json():
     return {"message": "Hello, World!"}
     return {"message": "Hello, World!"}
 
 
 
 
-@app.route("/plaintext")
+@app.get("/plaintext")
 async def plaintext():
 async def plaintext():
-    response = await make_response(b"Hello, World!")
-    # Quart assumes string responses are 'text/html', so make a custom one
-    response.mimetype = "text/plain"
-    return response
+    return "Hello, World!", {"Content-Type": "text/plain"}
 
 
 
 
-@app.route("/db")
[email protected]("/db")
 async def db():
 async def db():
     async with app.db.acquire() as conn:
     async with app.db.acquire() as conn:
         key = random.randint(1, 10000)
         key = random.randint(1, 10000)
@@ -48,48 +45,42 @@ async def db():
         return jsonify({"id": key, "randomNumber": number})
         return jsonify({"id": key, "randomNumber": number})
 
 
 
 
-def get_query_count(args):
-    qc = args.get("queries")
-
-    if qc is None:
-        return 1
-
+def get_query_count():
     try:
     try:
-        qc = int(qc)
+        num_queries = request.args.get("queries", 1, type=int)
     except ValueError:
     except ValueError:
+        num_queries = 1
+    if num_queries < 1:
         return 1
         return 1
-
-    qc = max(qc, 1)
-    qc = min(qc, 500)
-    return qc
+    if num_queries > 500:
+        return 500
+    return num_queries
 
 
 
 
-@app.route("/queries")
+@app.get("/queries")
 async def queries():
 async def queries():
-    queries = get_query_count(request.args)
+    queries = get_query_count()
 
 
     worlds = []
     worlds = []
     async with app.db.acquire() as conn:
     async with app.db.acquire() as conn:
         pst = await conn.prepare(GET_WORLD)
         pst = await conn.prepare(GET_WORLD)
-        for _ in range(queries):
-            key = random.randint(1, 10000)
+        for key in random.sample(range(1, 10000), queries):
             number = await pst.fetchval(key)
             number = await pst.fetchval(key)
             worlds.append({"id": key, "randomNumber": number})
             worlds.append({"id": key, "randomNumber": number})
 
 
     return jsonify(worlds)
     return jsonify(worlds)
 
 
 
 
-@app.route("/updates")
+@app.get("/updates")
 async def updates():
 async def updates():
-    queries = get_query_count(request.args)
+    queries = get_query_count()
 
 
     new_worlds = []
     new_worlds = []
     async with app.db.acquire() as conn, conn.transaction():
     async with app.db.acquire() as conn, conn.transaction():
         pst = await conn.prepare(GET_WORLD)
         pst = await conn.prepare(GET_WORLD)
 
 
-        for _ in range(queries):
-            key = random.randint(1, 10000)
-            old_number = await pst.fetchval(key)
+        for key in random.sample(range(1, 10000), queries):
+            await pst.fetchval(key)
             new_number = random.randint(1, 10000)
             new_number = random.randint(1, 10000)
             new_worlds.append((key, new_number))
             new_worlds.append((key, new_number))
 
 
@@ -100,7 +91,7 @@ async def updates():
     )
     )
 
 
 
 
-@app.route("/fortunes")
+@app.get("/fortunes")
 async def fortunes():
 async def fortunes():
     async with app.db.acquire() as conn:
     async with app.db.acquire() as conn:
         rows = await conn.fetch("select * from fortune")
         rows = await conn.fetch("select * from fortune")

+ 1 - 1
frameworks/Python/quart/quart.dockerfile

@@ -8,4 +8,4 @@ RUN pip3 install -r /quart/requirements.txt
 
 
 EXPOSE 8080
 EXPOSE 8080
 
 
-CMD hypercorn app:app --config=python:hypercorn_conf.py
+CMD hypercorn app:app --config=file:hypercorn_conf.py

+ 4 - 4
frameworks/Python/quart/requirements-uvicorn.txt

@@ -1,4 +1,4 @@
-gunicorn==20.0.4
-httptools==0.1.1
-uvicorn==0.11.7
-websockets==9.1
+gunicorn==20.1.0
+httptools==0.4.0
+uvicorn==0.17.6
+websockets==10.3

+ 18 - 18
frameworks/Python/quart/requirements.txt

@@ -1,19 +1,19 @@
-aiofiles==0.4.0
-asyncpg==0.21.0
+aiofiles==0.8.0
+asyncpg==0.25.0
 blinker==1.4
 blinker==1.4
-Click==7.0
-h11==0.9.0
-h2==3.2.0
-hpack==3.0.0
-Hypercorn==0.9.2
-hyperframe==5.2.0
-itsdangerous==1.1.0
-Jinja2==2.11.3
-MarkupSafe==1.1.1
-priority==1.3.0
-Quart==0.11.3
-toml==0.10.0
-typing-extensions==3.7.4.1
-uvloop==0.14.0
-Werkzeug==1.0.0
-wsproto==0.15.0
+click==8.1.3
+h11==0.13.0
+h2==4.1.0
+hpack==4.0.0
+hypercorn==0.13.2
+hyperframe==6.0.1
+itsdangerous==2.1.2
+Jinja2==3.1.2
+MarkupSafe==2.1.1
+priority==2.0.0
+Quart==0.17.0
+toml==0.10.2
+typing_extensions==4.2.0
+uvloop==0.16.0
+Werkzeug==2.1.2
+wsproto==1.1.0

+ 5 - 5
frameworks/Python/sanic/app.py

@@ -2,7 +2,7 @@ import asyncpg
 import os
 import os
 import jinja2
 import jinja2
 from logging import getLogger
 from logging import getLogger
-from random import randint
+from random import randint, sample
 from operator import itemgetter
 from operator import itemgetter
 
 
 import multiprocessing
 import multiprocessing
@@ -45,7 +45,7 @@ connection_pool = None
 sort_fortunes_key = itemgetter(1)
 sort_fortunes_key = itemgetter(1)
 template = load_fortunes_template()
 template = load_fortunes_template()
 
 
-app = sanic.Sanic()
+app = sanic.Sanic(name=__name__)
 
 
 
 
 @app.listener('before_server_start')
 @app.listener('before_server_start')
@@ -81,7 +81,7 @@ async def single_database_query_view(request):
 @app.get('/queries')
 @app.get('/queries')
 async def multiple_database_queries_view(request):
 async def multiple_database_queries_view(request):
     num_queries = get_num_queries(request.args.get('queries', 1))
     num_queries = get_num_queries(request.args.get('queries', 1))
-    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    row_ids = sample(range(1, 10000), num_queries)
     worlds = []
     worlds = []
 
 
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
@@ -119,8 +119,8 @@ async def database_updates_view(request):
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
         statement = await connection.prepare(READ_ROW_SQL_TO_UPDATE)
         statement = await connection.prepare(READ_ROW_SQL_TO_UPDATE)
 
 
-        for _ in range(get_num_queries(queries)):
-            record = await statement.fetchrow(randint(1, 10000))
+        for row_id in sample(range(1, 10000), get_num_queries(queries)):
+            record = await statement.fetchrow(row_id)
             world = dict(
             world = dict(
                 id=record['id'], randomNumber=record['randomnumber']
                 id=record['id'], randomNumber=record['randomnumber']
             )
             )

+ 4 - 4
frameworks/Python/sanic/requirements.txt

@@ -1,4 +1,4 @@
-asyncpg==0.20.1
-Jinja2==2.11.3
-uvloop==0.14.0
-sanic==20.12.6
+asyncpg==0.25.0
+Jinja2==3.1.2
+sanic==22.3.1
+uvloop==0.16.0

+ 9 - 12
frameworks/Python/starlette/app.py

@@ -1,11 +1,10 @@
-import asyncio
 import asyncpg
 import asyncpg
 import os
 import os
 import jinja2
 import jinja2
 from starlette.applications import Starlette
 from starlette.applications import Starlette
-from starlette.responses import HTMLResponse, UJSONResponse, PlainTextResponse
+from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse
 from starlette.routing import Route
 from starlette.routing import Route
-from random import randint
+from random import randint, sample
 from operator import itemgetter
 from operator import itemgetter
 from urllib.parse import parse_qs
 from urllib.parse import parse_qs
 
 
@@ -51,8 +50,6 @@ def get_num_queries(request):
 connection_pool = None
 connection_pool = None
 sort_fortunes_key = itemgetter(1)
 sort_fortunes_key = itemgetter(1)
 template = load_fortunes_template()
 template = load_fortunes_template()
-loop = asyncio.get_event_loop()
-loop.run_until_complete(setup_database())
 
 
 
 
 async def single_database_query(request):
 async def single_database_query(request):
@@ -61,12 +58,12 @@ async def single_database_query(request):
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
         number = await connection.fetchval(READ_ROW_SQL, row_id)
         number = await connection.fetchval(READ_ROW_SQL, row_id)
 
 
-    return UJSONResponse({'id': row_id, 'randomNumber': number})
+    return JSONResponse({'id': row_id, 'randomNumber': number})
 
 
 
 
 async def multiple_database_queries(request):
 async def multiple_database_queries(request):
     num_queries = get_num_queries(request)
     num_queries = get_num_queries(request)
-    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    row_ids = sample(range(1, 10000), num_queries)
     worlds = []
     worlds = []
 
 
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
@@ -75,7 +72,7 @@ async def multiple_database_queries(request):
             number = await statement.fetchval(row_id)
             number = await statement.fetchval(row_id)
             worlds.append({'id': row_id, 'randomNumber': number})
             worlds.append({'id': row_id, 'randomNumber': number})
 
 
-    return UJSONResponse(worlds)
+    return JSONResponse(worlds)
 
 
 
 
 async def fortunes(request):
 async def fortunes(request):
@@ -90,7 +87,7 @@ async def fortunes(request):
 
 
 async def database_updates(request):
 async def database_updates(request):
     num_queries = get_num_queries(request)
     num_queries = get_num_queries(request)
-    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    updates = [(row_id, randint(1, 10000)) for row_id in sample(range(1, 10000), num_queries)]
     worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
     worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
 
 
     async with connection_pool.acquire() as connection:
     async with connection_pool.acquire() as connection:
@@ -99,11 +96,11 @@ async def database_updates(request):
             await statement.fetchval(row_id)
             await statement.fetchval(row_id)
         await connection.executemany(WRITE_ROW_SQL, updates)
         await connection.executemany(WRITE_ROW_SQL, updates)
 
 
-    return UJSONResponse(worlds)
+    return JSONResponse(worlds)
 
 
 
 
 routes = [
 routes = [
-    Route('/json', UJSONResponse({'message': 'Hello, world!'})),
+    Route('/json', JSONResponse({'message': 'Hello, world!'})),
     Route('/db', single_database_query),
     Route('/db', single_database_query),
     Route('/queries', multiple_database_queries),
     Route('/queries', multiple_database_queries),
     Route('/fortunes', fortunes),
     Route('/fortunes', fortunes),
@@ -111,4 +108,4 @@ routes = [
     Route('/plaintext', PlainTextResponse(b'Hello, world!')),
     Route('/plaintext', PlainTextResponse(b'Hello, world!')),
 ]
 ]
 
 
-app = Starlette(routes=routes)
+app = Starlette(routes=routes, on_startup=[setup_database])

+ 7 - 7
frameworks/Python/starlette/requirements.txt

@@ -1,7 +1,7 @@
-asyncpg==0.21.0
-gunicorn==20.0.4
-Jinja2==2.11.3
-ujson==2.0.3
-uvloop==0.14.0
-uvicorn==0.11.7
-starlette==0.13.2
+asyncpg==0.25.0
+gunicorn==20.1.0
+Jinja2==3.1.2
+starlette==0.19.1
+ujson==5.2.0
+uvicorn==0.17.6
+uvloop==0.16.0

+ 3 - 3
frameworks/Python/uvicorn/app.py

@@ -3,7 +3,7 @@ import asyncpg
 import jinja2
 import jinja2
 import os
 import os
 import ujson
 import ujson
-from random import randint
+from random import randint, sample
 from operator import itemgetter
 from operator import itemgetter
 from urllib.parse import parse_qs
 from urllib.parse import parse_qs
 
 
@@ -114,7 +114,7 @@ async def multiple_database_queries(scope, receive, send):
     Test type 3: Multiple database queries
     Test type 3: Multiple database queries
     """
     """
     num_queries = get_num_queries(scope)
     num_queries = get_num_queries(scope)
-    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    row_ids = sample(range(1, 10000), num_queries)
     worlds = []
     worlds = []
 
 
     connection = await pool.acquire()
     connection = await pool.acquire()
@@ -161,7 +161,7 @@ async def database_updates(scope, receive, send):
     Test type 5: Database updates
     Test type 5: Database updates
     """
     """
     num_queries = get_num_queries(scope)
     num_queries = get_num_queries(scope)
-    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    updates = [(row_id, randint(1, 10000)) for row_id in sample(range(1, 10000), num_queries)]
     worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
     worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
 
 
     connection = await pool.acquire()
     connection = await pool.acquire()