views.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import random
  2. from operator import attrgetter, itemgetter
  3. from pathlib import Path
  4. import aiohttp.web
  5. import jinja2
  6. import sqlalchemy
  7. import sqlalchemy.orm
  8. import orjson
  9. from . import models
  10. # In current versions of Python (tested 3.9 and 3.12), from ... import ...
  11. # creates variables that are atleast 4x slower to reference:
  12. # > python3 -m timeit 'from random import sample' 'sample'
  13. # 500000 loops, best of 5: 823 nsec per loop
  14. # > python3 -m timeit 'import random' 'random.sample'
  15. # 2000000 loops, best of 5: 161 nsec per loop
  16. # > python3 -m timeit 'import random; sample = random.sample' 'sample'
  17. # 2000000 loops, best of 5: 161 nsec per loop
  18. dumps = orjson.dumps
  19. randint = random.randint
  20. sample = random.sample
  21. Response = aiohttp.web.Response
  22. select = sqlalchemy.select
  23. flag_modified = sqlalchemy.orm.attributes.flag_modified
  24. Fortune = models.Fortune
  25. World = models.World
  26. ADDITIONAL_FORTUNE_ORM = Fortune(id=0, message='Additional fortune added at request time.')
  27. ADDITIONAL_FORTUNE_ROW = {'id': 0, 'message': 'Additional fortune added at request time.'}
  28. READ_ROW_SQL = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
  29. READ_SELECT_ORM = select(World.randomnumber).where(World.id == sqlalchemy.bindparam("id"))
  30. READ_FORTUNES_ORM = select(Fortune.id, Fortune.message)
  31. WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$2 WHERE id=$1'
  32. template_path = Path(__file__).parent / 'templates' / 'fortune.jinja'
  33. template = jinja2.Template(template_path.read_text())
  34. sort_fortunes_orm = attrgetter('message')
  35. sort_fortunes_raw = itemgetter('message')
  36. def get_num_queries(request):
  37. try:
  38. num_queries = int(request.match_info.get('queries', 1))
  39. except ValueError:
  40. return 1
  41. if num_queries < 1:
  42. return 1
  43. if num_queries > 500:
  44. return 500
  45. return num_queries
  46. def json_response(payload):
  47. return Response(
  48. body=dumps(payload),
  49. content_type="application/json",
  50. )
  51. async def json(request):
  52. """
  53. Test 1
  54. """
  55. return json_response({'message': 'Hello, World!'})
  56. async def single_database_query_orm(request):
  57. """
  58. Test 2 ORM
  59. """
  60. id_ = randint(1, 10000)
  61. async with request.app['db_session']() as sess:
  62. num = await sess.scalar(READ_SELECT_ORM, {"id": id_})
  63. return json_response({'id': id_, 'randomNumber': num})
  64. async def single_database_query_raw(request):
  65. """
  66. Test 2 RAW
  67. """
  68. id_ = randint(1, 10000)
  69. async with request.app['pg'].acquire() as conn:
  70. r = await conn.fetchval('SELECT id,randomnumber FROM world WHERE id = $1', id_)
  71. return json_response({'id': id_, 'randomNumber': r})
  72. async def multiple_database_queries_orm(request):
  73. """
  74. Test 3 ORM
  75. """
  76. num_queries = get_num_queries(request)
  77. ids = [randint(1, 10000) for _ in range(num_queries)]
  78. result = []
  79. async with request.app['db_session']() as sess:
  80. for id_ in ids:
  81. num = await sess.scalar(READ_SELECT_ORM, {"id": id_})
  82. result.append({'id': id_, 'randomNumber': num})
  83. return json_response(result)
  84. async def multiple_database_queries_raw(request):
  85. """
  86. Test 3 RAW
  87. """
  88. num_queries = get_num_queries(request)
  89. ids = [randint(1, 10000) for _ in range(num_queries)]
  90. result = []
  91. async with request.app['pg'].acquire() as conn:
  92. stmt = await conn.prepare(READ_ROW_SQL)
  93. for id_ in ids:
  94. result.append({
  95. 'id': id_,
  96. 'randomNumber': await stmt.fetchval(id_),
  97. })
  98. return json_response(result)
  99. async def fortunes(request):
  100. """
  101. Test 4 ORM
  102. """
  103. async with request.app['db_session']() as sess:
  104. ret = await sess.execute(READ_FORTUNES_ORM)
  105. fortunes = ret.all()
  106. fortunes.append(ADDITIONAL_FORTUNE_ORM)
  107. fortunes.sort(key=sort_fortunes_orm)
  108. content = template.render(fortunes=fortunes)
  109. return Response(text=content, content_type='text/html')
  110. async def fortunes_raw(request):
  111. """
  112. Test 4 RAW
  113. """
  114. async with request.app['pg'].acquire() as conn:
  115. fortunes = await conn.fetch('SELECT * FROM Fortune')
  116. fortunes.append(ADDITIONAL_FORTUNE_ROW)
  117. fortunes.sort(key=sort_fortunes_raw)
  118. content = template.render(fortunes=fortunes)
  119. return Response(text=content, content_type='text/html')
  120. async def updates(request):
  121. """
  122. Test 5 ORM
  123. """
  124. num_queries = get_num_queries(request)
  125. update_ids = sample(range(1, 10001), num_queries)
  126. update_ids.sort()
  127. updates = tuple(zip(update_ids, sample(range(1, 10001), num_queries)))
  128. worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
  129. async with request.app['db_session'].begin() as sess:
  130. for id_, number in updates:
  131. world = await sess.get(World, id_, populate_existing=True)
  132. world.randomnumber = number
  133. # Force sqlalchemy to UPDATE entry even if the value has not changed
  134. # doesn't make sense in a real application, added only to pass tests.
  135. flag_modified(world, "randomnumber")
  136. return json_response(worlds)
  137. async def updates_raw(request):
  138. """
  139. Test 5 RAW
  140. """
  141. num_queries = get_num_queries(request)
  142. update_ids = sample(range(1, 10001), num_queries)
  143. update_ids.sort()
  144. updates = tuple(zip(update_ids, sample(range(1, 10001), num_queries)))
  145. worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
  146. async with request.app['pg'].acquire() as conn:
  147. stmt = await conn.prepare(READ_ROW_SQL)
  148. for id_, _ in updates:
  149. # the result of this is the int previous random number which we don't actually use
  150. await stmt.fetchval(id_)
  151. await conn.executemany(WRITE_ROW_SQL, updates)
  152. return json_response(worlds)
  153. async def plaintext(request):
  154. """
  155. Test 6
  156. """
  157. return Response(body=b'Hello, World!', content_type='text/plain')