Digitale bierlijst

model.py 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. """
  2. Provides access to the models stored in the database, via the server.
  3. """
  4. import datetime
  5. import logging
  6. from typing import NamedTuple
  7. from urllib.parse import urljoin
  8. import requests
  9. LOG = logging.getLogger(__name__)
  10. SERVER_URL = "http://127.0.0.1:5000"
  11. class ServerStatus:
  12. """ Provides helper classes to check whether the server is up. """
  13. @classmethod
  14. def is_server_running(cls) -> bool:
  15. try:
  16. req = requests.get(urljoin(SERVER_URL, "ping"))
  17. if req.status_code == 200:
  18. return True, req.content
  19. return False, req.content
  20. except requests.ConnectionError as ex:
  21. return False, ex
  22. class Person(NamedTuple):
  23. """ Represents a Person, as retrieved from the database. """
  24. name: str
  25. person_id: int = None
  26. consumptions: dict = {}
  27. def add_consumption(self, type_id: str) -> bool:
  28. """ Register a consumption for this Person. """
  29. req = requests.post(
  30. urljoin(SERVER_URL, f"people/{self.person_id}/add_consumption/{type_id}")
  31. )
  32. try:
  33. data = req.json()
  34. if "error" in data:
  35. LOG.error(
  36. "Could not add consumption for %s (%s): %s",
  37. self.person_id,
  38. req.status_code,
  39. data,
  40. )
  41. return False
  42. self.consumptions.update(data["person"]["consumptions"])
  43. return Consumption.from_dict(data["consumption"])
  44. except ValueError:
  45. LOG.error(
  46. "Did not get JSON on adding Consumption (%s): %s",
  47. req.status_code,
  48. req.content,
  49. )
  50. return False
  51. def create(self) -> "Person":
  52. """ Create a new Person from the current attributes. As tuples are
  53. immutable, a new Person with the correct id is returned. """
  54. req = requests.post(
  55. urljoin(SERVER_URL, "people"), json={"person": {"name": self.name}}
  56. )
  57. try:
  58. data = req.json()
  59. except ValueError:
  60. LOG.error(
  61. "Did not get JSON on adding Person (%s): %s",
  62. req.status_code,
  63. req.content,
  64. )
  65. return None
  66. if "error" in data or req.status_code != 201:
  67. LOG.error("Could not create Person (%s): %s", req.status_code, data)
  68. return None
  69. return Person.from_dict(data["person"])
  70. def set_active(self, new_state=True) -> "Person":
  71. req = requests.patch(
  72. urljoin(SERVER_URL, f"people/{self.person_id}"),
  73. json={"person": {"active": new_state}},
  74. )
  75. try:
  76. data = req.json()
  77. except ValueError:
  78. LOG.error(
  79. "Did not get JSON on updating Person (%s): %s",
  80. req.status_code,
  81. req.content,
  82. )
  83. return None
  84. if "error" in data or req.status_code != 200:
  85. LOG.error("Could not update Person (%s): %s", req.status_code, data)
  86. return None
  87. return Person.from_dict(data["person"])
  88. @classmethod
  89. def get(cls, person_id: int) -> "Person":
  90. """ Retrieve a Person by id. """
  91. req = requests.get(urljoin(SERVER_URL, f"/people/{person_id}"))
  92. try:
  93. data = req.json()
  94. if "error" in data:
  95. LOG.warning(
  96. "Could not get person %s (%s): %s", person_id, req.status_code, data
  97. )
  98. return None
  99. return Person.from_dict(data["person"])
  100. except ValueError:
  101. LOG.error(
  102. "Did not get JSON from server on getting Person (%s): %s",
  103. req.status_code,
  104. req.content,
  105. )
  106. return None
  107. @classmethod
  108. def get_all(cls, active=None) -> ["Person"]:
  109. """ Get all active People. """
  110. active = int(active)
  111. req = requests.get(urljoin(SERVER_URL, "/people"), params={"active": active})
  112. try:
  113. data = req.json()
  114. if "error" in data:
  115. LOG.warning("Could not get people (%s): %s", req.status_code, data)
  116. return [Person.from_dict(item) for item in data["people"]]
  117. except ValueError:
  118. LOG.error(
  119. "Did not get JSON from server on getting People (%s): %s",
  120. req.status_code,
  121. req.content,
  122. )
  123. return None
  124. @classmethod
  125. def from_dict(cls, data: dict) -> "Person":
  126. """ Reconstruct a Person object from a dict. """
  127. return Person(
  128. name=data["name"],
  129. person_id=data["person_id"],
  130. consumptions=data["consumptions"],
  131. )
  132. class ConsumptionType(NamedTuple):
  133. """ Represents a stored ConsumptionType. """
  134. name: str
  135. consumption_type_id: int = None
  136. icon: str = None
  137. def create(self) -> "ConsumptionType":
  138. """ Create a new ConsumptionType from the current attributes. As tuples
  139. are immutable, a new ConsumptionType with the correct id is returned.
  140. """
  141. req = requests.post(
  142. urljoin(SERVER_URL, "consumption_types"),
  143. json={"consumption_type": {"name": self.name, "icon": self.icon}},
  144. )
  145. try:
  146. data = req.json()
  147. except ValueError:
  148. LOG.error(
  149. "Did not get JSON on adding ConsumptionType (%s): %s",
  150. req.status_code,
  151. req.content,
  152. )
  153. return None
  154. if "error" in data or req.status_code != 201:
  155. LOG.error(
  156. "Could not create ConsumptionType (%s): %s", req.status_code, data
  157. )
  158. return None
  159. return ConsumptionType.from_dict(data["consumption_type"])
  160. @classmethod
  161. def get(cls, consumption_type_id: int) -> "ConsumptionType":
  162. """ Retrieve a ConsumptionType by id. """
  163. req = requests.get(
  164. urljoin(SERVER_URL, f"/consumption_types/{consumption_type_id}")
  165. )
  166. try:
  167. data = req.json()
  168. if "error" in data:
  169. LOG.warning(
  170. "Could not get consumption type %s (%s): %s",
  171. consumption_type_id,
  172. req.status_code,
  173. data,
  174. )
  175. return None
  176. return cls.from_dict(data["consumption_type"])
  177. except ValueError:
  178. LOG.error(
  179. "Did not get JSON from server on getting consumption type (%s): %s",
  180. req.status_code,
  181. req.content,
  182. )
  183. return None
  184. @classmethod
  185. def get_all(cls) -> ["ConsumptionType"]:
  186. """ Get all active ConsumptionTypes. """
  187. req = requests.get(urljoin(SERVER_URL, "/consumption_types"))
  188. try:
  189. data = req.json()
  190. if "error" in data:
  191. LOG.warning(
  192. "Could not get consumption types (%s): %s", req.status_code, data
  193. )
  194. return [cls.from_dict(item) for item in data["consumption_types"]]
  195. except ValueError:
  196. LOG.error(
  197. "Did not get JSON from server on getting ConsumptionTypes (%s): %s",
  198. req.status_code,
  199. req.content,
  200. )
  201. return None
  202. @classmethod
  203. def from_dict(cls, data: dict) -> "ConsumptionType":
  204. """ Reconstruct a ConsumptionType from a dict. """
  205. return cls(
  206. name=data["name"],
  207. consumption_type_id=data["consumption_type_id"],
  208. icon=data.get("icon"),
  209. )
  210. class Consumption(NamedTuple):
  211. """ Represents a stored Consumption. """
  212. consumption_id: int
  213. person_id: int
  214. consumption_type_id: int
  215. created_at: datetime.datetime
  216. reversed: bool = False
  217. settlement_id: int = None
  218. @classmethod
  219. def from_dict(cls, data: dict) -> "Consumption":
  220. """ Reconstruct a Consumption from a dict. """
  221. datetime_format = "%Y-%m-%dT%H:%M:%S.%f"
  222. # 2018-08-31T17:30:47.871521
  223. return cls(
  224. consumption_id=data["consumption_id"],
  225. person_id=data["person_id"],
  226. consumption_type_id=data["consumption_type_id"],
  227. settlement_id=data["settlement_id"],
  228. created_at=datetime.datetime.strptime(data["created_at"], datetime_format),
  229. reversed=data["reversed"],
  230. )
  231. def reverse(self) -> "Consumption":
  232. """ Reverse this consumption. """
  233. req = requests.delete(
  234. urljoin(SERVER_URL, f"/consumptions/{self.consumption_id}")
  235. )
  236. try:
  237. data = req.json()
  238. if "error" in data:
  239. LOG.error(
  240. "Could not reverse consumption %s (%s): %s",
  241. self.consumption_id,
  242. req.status_code,
  243. data,
  244. )
  245. return False
  246. return Consumption.from_dict(data["consumption"])
  247. except ValueError:
  248. LOG.error(
  249. "Did not get JSON on reversing Consumption (%s): %s",
  250. req.status_code,
  251. req.content,
  252. )
  253. return False