瀏覽代碼

Add Starlette (#3919)

* Add Starlette

* Point dockerfile at application properly
Tom Christie 7 年之前
父節點
當前提交
bbe14b201a

+ 22 - 0
frameworks/Python/starlette/README.md

@@ -0,0 +1,22 @@
+# Starlette Benchmark Test
+
+This is the Starlette portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to Starlette. For further guidance,
+review the [documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/).
+Also note that there is additional information provided in
+the [Python README](../).
+
+## Description
+
+[Starlette](https://github.com/encode/starlette) is a lightweight ASGI framework.
+
+## Test Paths & Sources
+
+All of the test implementations are located within a single file ([app.py](app.py)).
+
+## Resources
+
+* [Starlette on GitHub](https://github.com/encode/starlette)
+* [ASGI specification](https://asgi.readthedocs.io/en/latest/)

+ 131 - 0
frameworks/Python/starlette/app.py

@@ -0,0 +1,131 @@
+import asyncio
+import asyncpg
+import os
+import jinja2
+from starlette import (
+    asgi_application, HTMLResponse, JSONResponse, Path, PlainTextResponse, Router
+)
+from random import randint
+from operator import itemgetter
+from urllib.parse import parse_qs
+from ujson import dumps as ujson_dumps
+
+
+READ_ROW_SQL = 'SELECT "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.']
+
+
+class UJSONResponse(JSONResponse):
+    def render(self, content):
+        return ujson_dumps(content).encode('utf-8')
+
+
+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():
+    path = os.path.join('templates', 'fortune.html')
+    with open(path, 'r') as template_file:
+        template_text = template_file.read()
+        return jinja2.Template(template_text)
+
+
+def get_num_queries(request):
+    try:
+        query_string = request['query_string']
+        query_count = int(parse_qs(query_string)[b'queries'][0])
+    except (KeyError, IndexError, ValueError):
+        return 1
+
+    if query_count < 1:
+        return 1
+    if query_count > 500:
+        return 500
+    return query_count
+
+
+connection_pool = None
+sort_fortunes_key = itemgetter(1)
+template = load_fortunes_template()
+loop = asyncio.get_event_loop()
+loop.run_until_complete(setup_database())
+
+
+@asgi_application
+def json_serialization(request):
+    return UJSONResponse({'message': 'Hello, world!'})
+
+
+@asgi_application
+async def single_database_query(request):
+    row_id = randint(1, 10000)
+
+    async with connection_pool.acquire() as connection:
+        number = await connection.fetchval(READ_ROW_SQL, row_id)
+
+    return UJSONResponse({'id': row_id, 'randomNumber': number})
+
+
+@asgi_application
+async def multiple_database_queries(request):
+    num_queries = get_num_queries(request)
+    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    worlds = []
+
+    async with connection_pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id in row_ids:
+            number = await statement.fetchval(row_id)
+            worlds.append({'id': row_id, 'randomNumber': number})
+
+    return UJSONResponse(worlds)
+
+
+@asgi_application
+async def fortunes(request):
+    async with connection_pool.acquire() as connection:
+        fortunes = await connection.fetch('SELECT * FROM Fortune')
+
+    fortunes.append(ADDITIONAL_ROW)
+    fortunes.sort(key=sort_fortunes_key)
+    content = template.render(fortunes=fortunes)
+    return HTMLResponse(content)
+
+
+@asgi_application
+async def database_updates(request):
+    num_queries = get_num_queries(request)
+    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
+
+    async with connection_pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id, number in updates:
+            await statement.fetchval(row_id)
+        await connection.executemany(WRITE_ROW_SQL, updates)
+
+    return UJSONResponse(worlds)
+
+
+@asgi_application
+def plaintext(request):
+    return PlainTextResponse(b'Hello, world!')
+
+
+app = Router(routes=[
+    Path('/json', json_serialization),
+    Path('/db', single_database_query),
+    Path('/queries', multiple_database_queries),
+    Path('/fortunes', fortunes),
+    Path('/updates', database_updates),
+    Path('/plaintext', plaintext),
+])

+ 27 - 0
frameworks/Python/starlette/benchmark_config.json

@@ -0,0 +1,27 @@
+{
+  "framework": "starlette",
+  "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": "Platform",
+      "framework": "starlette",
+      "language": "Python",
+      "flavor": "Python3",
+      "platform": "None",
+      "webserver": "None",
+      "os": "Linux",
+      "orm": "Raw",
+      "database_os": "Linux",
+      "database": "Postgres",
+      "display_name": "Starlette",
+      "notes": ""
+    }
+  }]
+}

+ 6 - 0
frameworks/Python/starlette/requirements.txt

@@ -0,0 +1,6 @@
+asyncpg==0.16.0
+gunicorn==19.9.0
+starlette==0.1.6
+jinja2==2.10
+ujson==1.35
+uvicorn==0.2.9

+ 9 - 0
frameworks/Python/starlette/starlette.dockerfile

@@ -0,0 +1,9 @@
+FROM python:3.6.5
+
+ADD ./ /starlette
+
+WORKDIR /starlette
+
+RUN pip3 install -r /starlette/requirements.txt
+
+CMD gunicorn app:app -k uvicorn.workers.UvicornWorker -c starlette_conf.py

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

+ 10 - 0
frameworks/Python/starlette/templates/fortune.html

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