app.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import os
  2. from operator import itemgetter
  3. from random import randint, sample
  4. from urllib.parse import parse_qs
  5. import asyncpg
  6. import jinja2
  7. import ujson
  8. async def setup():
  9. global pool
  10. pool = await asyncpg.create_pool(
  11. user=os.getenv('PGUSER', 'benchmarkdbuser'),
  12. password=os.getenv('PGPASS', 'benchmarkdbpass'),
  13. database='hello_world',
  14. host='tfb-database',
  15. port=5432
  16. )
  17. READ_ROW_SQL = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
  18. WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
  19. ADDITIONAL_ROW = [0, 'Additional fortune added at request time.']
  20. JSON_RESPONSE = {
  21. 'type': 'http.response.start',
  22. 'status': 200,
  23. 'headers': [
  24. [b'content-type', b'application/json'],
  25. ]
  26. }
  27. HTML_RESPONSE = {
  28. 'type': 'http.response.start',
  29. 'status': 200,
  30. 'headers': [
  31. [b'content-type', b'text/html; charset=utf-8'],
  32. ]
  33. }
  34. PLAINTEXT_RESPONSE = {
  35. 'type': 'http.response.start',
  36. 'status': 200,
  37. 'headers': [
  38. [b'content-type', b'text/plain; charset=utf-8'],
  39. ]
  40. }
  41. pool = None
  42. key = itemgetter(1)
  43. json_dumps = ujson.dumps
  44. template = None
  45. path = os.path.join('templates', 'fortune.html')
  46. with open(path, 'r') as template_file:
  47. template_text = template_file.read()
  48. template = jinja2.Template(template_text)
  49. def get_num_queries(scope):
  50. try:
  51. query_string = scope['query_string']
  52. query_count = int(parse_qs(query_string)[b'queries'][0])
  53. except (KeyError, IndexError, ValueError):
  54. return 1
  55. if query_count < 1:
  56. return 1
  57. if query_count > 500:
  58. return 500
  59. return query_count
  60. async def json_serialization(scope, receive, send):
  61. """
  62. Test type 1: JSON Serialization
  63. """
  64. content = json_dumps({'message': 'Hello, world!'}).encode('utf-8')
  65. await send(JSON_RESPONSE)
  66. await send({
  67. 'type': 'http.response.body',
  68. 'body': content,
  69. 'more_body': False
  70. })
  71. async def single_database_query(scope, receive, send):
  72. """
  73. Test type 2: Single database object
  74. """
  75. row_id = randint(1, 10000)
  76. connection = await pool.acquire()
  77. try:
  78. number = await connection.fetchval(READ_ROW_SQL, row_id)
  79. world = {'id': row_id, 'randomNumber': number}
  80. finally:
  81. await pool.release(connection)
  82. content = json_dumps(world).encode('utf-8')
  83. await send(JSON_RESPONSE)
  84. await send({
  85. 'type': 'http.response.body',
  86. 'body': content,
  87. 'more_body': False
  88. })
  89. async def multiple_database_queries(scope, receive, send):
  90. """
  91. Test type 3: Multiple database queries
  92. """
  93. num_queries = get_num_queries(scope)
  94. row_ids = sample(range(1, 10000), num_queries)
  95. worlds = []
  96. connection = await pool.acquire()
  97. try:
  98. statement = await connection.prepare(READ_ROW_SQL)
  99. for row_id in row_ids:
  100. number = await statement.fetchval(row_id)
  101. worlds.append({'id': row_id, 'randomNumber': number})
  102. finally:
  103. await pool.release(connection)
  104. content = json_dumps(worlds).encode('utf-8')
  105. await send(JSON_RESPONSE)
  106. await send({
  107. 'type': 'http.response.body',
  108. 'body': content,
  109. 'more_body': False
  110. })
  111. async def fortunes(scope, receive, send):
  112. """
  113. Test type 4: Fortunes
  114. """
  115. connection = await pool.acquire()
  116. try:
  117. fortunes = await connection.fetch('SELECT * FROM Fortune')
  118. finally:
  119. await pool.release(connection)
  120. fortunes.append(ADDITIONAL_ROW)
  121. fortunes.sort(key=key)
  122. content = template.render(fortunes=fortunes).encode('utf-8')
  123. await send(HTML_RESPONSE)
  124. await send({
  125. 'type': 'http.response.body',
  126. 'body': content,
  127. 'more_body': False
  128. })
  129. async def database_updates(scope, receive, send):
  130. """
  131. Test type 5: Database updates
  132. """
  133. num_queries = get_num_queries(scope)
  134. updates = [(row_id, randint(1, 10000)) for row_id in sample(range(1, 10000), num_queries)]
  135. worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
  136. connection = await pool.acquire()
  137. try:
  138. statement = await connection.prepare(READ_ROW_SQL)
  139. for row_id, _ in updates:
  140. await statement.fetchval(row_id)
  141. await connection.executemany(WRITE_ROW_SQL, updates)
  142. finally:
  143. await pool.release(connection)
  144. content = json_dumps(worlds).encode('utf-8')
  145. await send(JSON_RESPONSE)
  146. await send({
  147. 'type': 'http.response.body',
  148. 'body': content,
  149. 'more_body': False
  150. })
  151. async def plaintext(scope, receive, send):
  152. """
  153. Test type 6: Plaintext
  154. """
  155. content = b'Hello, world!'
  156. await send(PLAINTEXT_RESPONSE)
  157. await send({
  158. 'type': 'http.response.body',
  159. 'body': content,
  160. 'more_body': False
  161. })
  162. async def handle_404(scope, receive, send):
  163. content = b'Not found'
  164. await send(PLAINTEXT_RESPONSE)
  165. await send({
  166. 'type': 'http.response.body',
  167. 'body': content,
  168. 'more_body': False
  169. })
  170. routes = {
  171. '/json': json_serialization,
  172. '/db': single_database_query,
  173. '/queries': multiple_database_queries,
  174. '/fortunes': fortunes,
  175. '/updates': database_updates,
  176. '/plaintext': plaintext,
  177. }
  178. async def main(scope, receive, send):
  179. if scope['type'] == 'lifespan':
  180. message = await receive()
  181. if message['type'] == 'lifespan.startup':
  182. await setup()
  183. await send({'type': 'lifespan.startup.complete'})
  184. path = scope['path']
  185. handler = routes.get(path, handle_404)
  186. await handler(scope, receive, send)