pyapi.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. #
  2. # Copyright (c) Daniel Sheffield 2023
  3. # All rights reserved
  4. #
  5. # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
  6. import os
  7. from threading import Thread
  8. from typing import Union
  9. from bottle import (
  10. route, request, response,
  11. static_file, redirect,
  12. FormsDict, HTTPResponse,
  13. )
  14. from psycopg import Cursor, connect
  15. from psycopg.rows import TupleRow
  16. from .route_decorators import normalize, poison, cursor
  17. from .query_to_xml import get_categories, get_groups, get_products, get_tags
  18. from .CachedLoadingPage import CachedLoadingPage
  19. from .Cache import Cache
  20. from . import trend as worker
  21. from time import sleep
  22. host = f"host={os.getenv('HOST')}"
  23. db = f"dbname={os.getenv('DB', 'grocery')}"
  24. user = f"user={os.getenv('USER', 'das')}"
  25. password = f"password={os.getenv('PASSWORD','')}"
  26. if not password.split('=',1)[1]:
  27. password = ''
  28. conn = connect(f"{host} {db} {user} {password}")
  29. @route('/grocery/static/<filename:path>')
  30. def send_static(filename):
  31. return static_file(filename, root='app/rest/static')
  32. def trend_thread(conn, path, forms):
  33. def cb(queue):
  34. return Thread(target=worker.trend, args=(
  35. queue, conn, path, forms
  36. )).start()
  37. return cb
  38. @route('/grocery/trend', method=['GET', 'POST'])
  39. @poison(cache=Cache(10))
  40. @normalize
  41. def trend(key: str, forms: FormsDict, cache: Cache):
  42. _, _, path, *_ = request.urlparts
  43. page = cache[key]
  44. if page is None:
  45. page = cache.add(key, CachedLoadingPage([], trend_thread(conn, path, forms)))
  46. for i in iter_page(page):
  47. yield i
  48. sleep(0.5)
  49. def iter_page(page):
  50. # copy first to avoid races
  51. resp = list(page.value)
  52. pos = len(resp)
  53. yield ''.join(resp)
  54. while not page.loaded:
  55. page.update()
  56. # all changes since last yield
  57. resp = list(page.value[pos:])
  58. pos = pos + len(resp)
  59. yield ''.join(resp)
  60. # possibly have not yielded the entire page
  61. if pos < len(page.value):
  62. yield ''.join(page.value[pos:])
  63. @route('/grocery/groups', method=['GET', 'POST'])
  64. @poison(cache=Cache(10))
  65. @normalize
  66. @cursor(connection=conn)
  67. def groups(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache):
  68. response.content_type = 'application/xhtml+xml; charset=utf-8'
  69. return get_groups(cur, forms)
  70. @route('/grocery/categories', method=['GET', 'POST'])
  71. @poison(cache=Cache(10))
  72. @normalize
  73. @cursor(connection=conn)
  74. def categories(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache):
  75. response.content_type = 'application/xhtml+xml; charset=utf-8'
  76. return get_categories(cur, forms)
  77. @route('/grocery/products', method=['GET', 'POST'])
  78. @poison(cache=Cache(10))
  79. @normalize
  80. @cursor(connection=conn)
  81. def products(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache):
  82. response.content_type = 'application/xhtml+xml; charset=utf-8'
  83. return get_products(cur, forms)
  84. @route('/grocery/tags', method=['GET', 'POST'])
  85. @poison(cache=Cache(10))
  86. @normalize
  87. @cursor(connection=conn)
  88. def tags(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache):
  89. response.content_type = 'application/xhtml+xml; charset=utf-8'
  90. return get_tags(cur, forms)