clear-skies 1.19.22__py3-none-any.whl → 2.0.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. clear_skies-2.0.23.dist-info/METADATA +76 -0
  2. clear_skies-2.0.23.dist-info/RECORD +265 -0
  3. {clear_skies-1.19.22.dist-info → clear_skies-2.0.23.dist-info}/WHEEL +1 -1
  4. clearskies/__init__.py +37 -21
  5. clearskies/action.py +7 -0
  6. clearskies/authentication/__init__.py +9 -38
  7. clearskies/authentication/authentication.py +44 -0
  8. clearskies/authentication/authorization.py +14 -8
  9. clearskies/authentication/authorization_pass_through.py +22 -0
  10. clearskies/authentication/jwks.py +135 -58
  11. clearskies/authentication/public.py +3 -26
  12. clearskies/authentication/secret_bearer.py +515 -44
  13. clearskies/autodoc/formats/oai3_json/__init__.py +2 -2
  14. clearskies/autodoc/formats/oai3_json/oai3_json.py +11 -9
  15. clearskies/autodoc/formats/oai3_json/parameter.py +6 -3
  16. clearskies/autodoc/formats/oai3_json/request.py +7 -5
  17. clearskies/autodoc/formats/oai3_json/response.py +7 -4
  18. clearskies/autodoc/formats/oai3_json/schema/object.py +10 -1
  19. clearskies/autodoc/request/__init__.py +2 -0
  20. clearskies/autodoc/request/header.py +4 -6
  21. clearskies/autodoc/request/json_body.py +4 -6
  22. clearskies/autodoc/request/parameter.py +8 -0
  23. clearskies/autodoc/request/request.py +16 -4
  24. clearskies/autodoc/request/url_parameter.py +4 -6
  25. clearskies/autodoc/request/url_path.py +4 -6
  26. clearskies/autodoc/schema/__init__.py +4 -2
  27. clearskies/autodoc/schema/array.py +5 -6
  28. clearskies/autodoc/schema/boolean.py +4 -10
  29. clearskies/autodoc/schema/date.py +0 -3
  30. clearskies/autodoc/schema/datetime.py +1 -4
  31. clearskies/autodoc/schema/double.py +0 -3
  32. clearskies/autodoc/schema/enum.py +4 -2
  33. clearskies/autodoc/schema/integer.py +4 -9
  34. clearskies/autodoc/schema/long.py +0 -3
  35. clearskies/autodoc/schema/number.py +4 -9
  36. clearskies/autodoc/schema/object.py +5 -7
  37. clearskies/autodoc/schema/password.py +0 -3
  38. clearskies/autodoc/schema/schema.py +11 -0
  39. clearskies/autodoc/schema/string.py +4 -10
  40. clearskies/backends/__init__.py +56 -17
  41. clearskies/backends/api_backend.py +1128 -166
  42. clearskies/backends/backend.py +54 -85
  43. clearskies/backends/cursor_backend.py +246 -191
  44. clearskies/backends/memory_backend.py +514 -208
  45. clearskies/backends/secrets_backend.py +68 -31
  46. clearskies/column.py +1221 -0
  47. clearskies/columns/__init__.py +71 -0
  48. clearskies/columns/audit.py +306 -0
  49. clearskies/columns/belongs_to_id.py +478 -0
  50. clearskies/columns/belongs_to_model.py +129 -0
  51. clearskies/columns/belongs_to_self.py +109 -0
  52. clearskies/columns/boolean.py +110 -0
  53. clearskies/columns/category_tree.py +273 -0
  54. clearskies/columns/category_tree_ancestors.py +51 -0
  55. clearskies/columns/category_tree_children.py +126 -0
  56. clearskies/columns/category_tree_descendants.py +48 -0
  57. clearskies/columns/created.py +92 -0
  58. clearskies/columns/created_by_authorization_data.py +114 -0
  59. clearskies/columns/created_by_header.py +103 -0
  60. clearskies/columns/created_by_ip.py +90 -0
  61. clearskies/columns/created_by_routing_data.py +102 -0
  62. clearskies/columns/created_by_user_agent.py +89 -0
  63. clearskies/columns/date.py +232 -0
  64. clearskies/columns/datetime.py +284 -0
  65. clearskies/columns/email.py +78 -0
  66. clearskies/columns/float.py +149 -0
  67. clearskies/columns/has_many.py +529 -0
  68. clearskies/columns/has_many_self.py +62 -0
  69. clearskies/columns/has_one.py +21 -0
  70. clearskies/columns/integer.py +158 -0
  71. clearskies/columns/json.py +126 -0
  72. clearskies/columns/many_to_many_ids.py +335 -0
  73. clearskies/columns/many_to_many_ids_with_data.py +274 -0
  74. clearskies/columns/many_to_many_models.py +156 -0
  75. clearskies/columns/many_to_many_pivots.py +132 -0
  76. clearskies/columns/phone.py +162 -0
  77. clearskies/columns/select.py +95 -0
  78. clearskies/columns/string.py +102 -0
  79. clearskies/columns/timestamp.py +164 -0
  80. clearskies/columns/updated.py +107 -0
  81. clearskies/columns/uuid.py +83 -0
  82. clearskies/configs/README.md +105 -0
  83. clearskies/configs/__init__.py +170 -0
  84. clearskies/configs/actions.py +43 -0
  85. clearskies/configs/any.py +15 -0
  86. clearskies/configs/any_dict.py +24 -0
  87. clearskies/configs/any_dict_or_callable.py +25 -0
  88. clearskies/configs/authentication.py +23 -0
  89. clearskies/configs/authorization.py +23 -0
  90. clearskies/configs/boolean.py +18 -0
  91. clearskies/configs/boolean_or_callable.py +20 -0
  92. clearskies/configs/callable_config.py +20 -0
  93. clearskies/configs/columns.py +34 -0
  94. clearskies/configs/conditions.py +30 -0
  95. clearskies/configs/config.py +26 -0
  96. clearskies/configs/datetime.py +20 -0
  97. clearskies/configs/datetime_or_callable.py +21 -0
  98. clearskies/configs/email.py +10 -0
  99. clearskies/configs/email_list.py +17 -0
  100. clearskies/configs/email_list_or_callable.py +17 -0
  101. clearskies/configs/email_or_email_list_or_callable.py +59 -0
  102. clearskies/configs/endpoint.py +23 -0
  103. clearskies/configs/endpoint_list.py +29 -0
  104. clearskies/configs/float.py +18 -0
  105. clearskies/configs/float_or_callable.py +20 -0
  106. clearskies/configs/headers.py +28 -0
  107. clearskies/configs/integer.py +18 -0
  108. clearskies/configs/integer_or_callable.py +20 -0
  109. clearskies/configs/joins.py +30 -0
  110. clearskies/configs/list_any_dict.py +32 -0
  111. clearskies/configs/list_any_dict_or_callable.py +33 -0
  112. clearskies/configs/model_class.py +35 -0
  113. clearskies/configs/model_column.py +67 -0
  114. clearskies/configs/model_columns.py +58 -0
  115. clearskies/configs/model_destination_name.py +26 -0
  116. clearskies/configs/model_to_id_column.py +45 -0
  117. clearskies/configs/readable_model_column.py +11 -0
  118. clearskies/configs/readable_model_columns.py +11 -0
  119. clearskies/configs/schema.py +23 -0
  120. clearskies/configs/searchable_model_columns.py +11 -0
  121. clearskies/configs/security_headers.py +39 -0
  122. clearskies/configs/select.py +28 -0
  123. clearskies/configs/select_list.py +49 -0
  124. clearskies/configs/string.py +31 -0
  125. clearskies/configs/string_dict.py +34 -0
  126. clearskies/configs/string_list.py +47 -0
  127. clearskies/configs/string_list_or_callable.py +48 -0
  128. clearskies/configs/string_or_callable.py +18 -0
  129. clearskies/configs/timedelta.py +20 -0
  130. clearskies/configs/timezone.py +20 -0
  131. clearskies/configs/url.py +25 -0
  132. clearskies/configs/validators.py +45 -0
  133. clearskies/configs/writeable_model_column.py +11 -0
  134. clearskies/configs/writeable_model_columns.py +11 -0
  135. clearskies/configurable.py +78 -0
  136. clearskies/contexts/__init__.py +8 -8
  137. clearskies/contexts/cli.py +129 -43
  138. clearskies/contexts/context.py +93 -56
  139. clearskies/contexts/wsgi.py +79 -33
  140. clearskies/contexts/wsgi_ref.py +87 -0
  141. clearskies/cursors/__init__.py +7 -0
  142. clearskies/cursors/cursor.py +166 -0
  143. clearskies/cursors/from_environment/__init__.py +5 -0
  144. clearskies/cursors/from_environment/mysql.py +51 -0
  145. clearskies/cursors/from_environment/postgresql.py +49 -0
  146. clearskies/cursors/from_environment/sqlite.py +35 -0
  147. clearskies/cursors/mysql.py +61 -0
  148. clearskies/cursors/postgresql.py +61 -0
  149. clearskies/cursors/sqlite.py +62 -0
  150. clearskies/decorators.py +33 -0
  151. clearskies/decorators.pyi +10 -0
  152. clearskies/di/__init__.py +11 -7
  153. clearskies/di/additional_config.py +117 -3
  154. clearskies/di/additional_config_auto_import.py +12 -0
  155. clearskies/di/di.py +717 -126
  156. clearskies/di/inject/__init__.py +23 -0
  157. clearskies/di/inject/akeyless_sdk.py +16 -0
  158. clearskies/di/inject/by_class.py +24 -0
  159. clearskies/di/inject/by_name.py +22 -0
  160. clearskies/di/inject/di.py +16 -0
  161. clearskies/di/inject/environment.py +15 -0
  162. clearskies/di/inject/input_output.py +19 -0
  163. clearskies/di/inject/now.py +16 -0
  164. clearskies/di/inject/requests.py +16 -0
  165. clearskies/di/inject/secrets.py +15 -0
  166. clearskies/di/inject/utcnow.py +16 -0
  167. clearskies/di/inject/uuid.py +16 -0
  168. clearskies/di/injectable.py +32 -0
  169. clearskies/di/injectable_properties.py +131 -0
  170. clearskies/end.py +219 -0
  171. clearskies/endpoint.py +1303 -0
  172. clearskies/endpoint_group.py +333 -0
  173. clearskies/endpoints/__init__.py +25 -0
  174. clearskies/endpoints/advanced_search.py +519 -0
  175. clearskies/endpoints/callable.py +382 -0
  176. clearskies/endpoints/create.py +201 -0
  177. clearskies/endpoints/delete.py +133 -0
  178. clearskies/endpoints/get.py +267 -0
  179. clearskies/endpoints/health_check.py +181 -0
  180. clearskies/endpoints/list.py +567 -0
  181. clearskies/endpoints/restful_api.py +417 -0
  182. clearskies/endpoints/schema.py +185 -0
  183. clearskies/endpoints/simple_search.py +279 -0
  184. clearskies/endpoints/update.py +188 -0
  185. clearskies/environment.py +7 -3
  186. clearskies/exceptions/__init__.py +19 -0
  187. clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
  188. clearskies/exceptions/missing_dependency.py +2 -0
  189. clearskies/exceptions/moved_permanently.py +3 -0
  190. clearskies/exceptions/moved_temporarily.py +3 -0
  191. clearskies/functional/__init__.py +2 -2
  192. clearskies/functional/json.py +47 -0
  193. clearskies/functional/routing.py +92 -0
  194. clearskies/functional/string.py +19 -11
  195. clearskies/functional/validations.py +61 -9
  196. clearskies/input_outputs/__init__.py +9 -7
  197. clearskies/input_outputs/cli.py +135 -152
  198. clearskies/input_outputs/exceptions/__init__.py +6 -1
  199. clearskies/input_outputs/headers.py +54 -0
  200. clearskies/input_outputs/input_output.py +77 -123
  201. clearskies/input_outputs/programmatic.py +62 -0
  202. clearskies/input_outputs/wsgi.py +36 -48
  203. clearskies/model.py +1894 -199
  204. clearskies/query/__init__.py +12 -0
  205. clearskies/query/condition.py +228 -0
  206. clearskies/query/join.py +136 -0
  207. clearskies/query/query.py +193 -0
  208. clearskies/query/sort.py +27 -0
  209. clearskies/schema.py +82 -0
  210. clearskies/secrets/__init__.py +4 -31
  211. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
  212. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
  213. clearskies/secrets/akeyless.py +421 -155
  214. clearskies/secrets/exceptions/__init__.py +7 -1
  215. clearskies/secrets/exceptions/not_found_error.py +2 -0
  216. clearskies/secrets/exceptions/permissions_error.py +2 -0
  217. clearskies/secrets/secrets.py +12 -11
  218. clearskies/security_header.py +17 -0
  219. clearskies/security_headers/__init__.py +8 -8
  220. clearskies/security_headers/cache_control.py +47 -109
  221. clearskies/security_headers/cors.py +38 -92
  222. clearskies/security_headers/csp.py +76 -150
  223. clearskies/security_headers/hsts.py +14 -15
  224. clearskies/typing.py +11 -0
  225. clearskies/validator.py +36 -0
  226. clearskies/validators/__init__.py +33 -0
  227. clearskies/validators/after_column.py +61 -0
  228. clearskies/validators/before_column.py +15 -0
  229. clearskies/validators/in_the_future.py +29 -0
  230. clearskies/validators/in_the_future_at_least.py +13 -0
  231. clearskies/validators/in_the_future_at_most.py +12 -0
  232. clearskies/validators/in_the_past.py +29 -0
  233. clearskies/validators/in_the_past_at_least.py +12 -0
  234. clearskies/validators/in_the_past_at_most.py +12 -0
  235. clearskies/validators/maximum_length.py +25 -0
  236. clearskies/validators/maximum_value.py +28 -0
  237. clearskies/validators/minimum_length.py +25 -0
  238. clearskies/validators/minimum_value.py +28 -0
  239. clearskies/{input_requirements → validators}/required.py +18 -9
  240. clearskies/validators/timedelta.py +58 -0
  241. clearskies/validators/unique.py +28 -0
  242. clear_skies-1.19.22.dist-info/METADATA +0 -46
  243. clear_skies-1.19.22.dist-info/RECORD +0 -206
  244. clearskies/application.py +0 -29
  245. clearskies/authentication/auth0_jwks.py +0 -118
  246. clearskies/authentication/auth_exception.py +0 -2
  247. clearskies/authentication/jwks_jwcrypto.py +0 -39
  248. clearskies/backends/example_backend.py +0 -43
  249. clearskies/backends/file_backend.py +0 -48
  250. clearskies/backends/json_backend.py +0 -7
  251. clearskies/backends/restful_api_advanced_search_backend.py +0 -138
  252. clearskies/binding_config.py +0 -16
  253. clearskies/column_types/__init__.py +0 -184
  254. clearskies/column_types/audit.py +0 -235
  255. clearskies/column_types/belongs_to.py +0 -250
  256. clearskies/column_types/boolean.py +0 -60
  257. clearskies/column_types/category_tree.py +0 -226
  258. clearskies/column_types/column.py +0 -373
  259. clearskies/column_types/created.py +0 -26
  260. clearskies/column_types/created_by_authorization_data.py +0 -26
  261. clearskies/column_types/created_by_header.py +0 -24
  262. clearskies/column_types/created_by_ip.py +0 -17
  263. clearskies/column_types/created_by_routing_data.py +0 -25
  264. clearskies/column_types/created_by_user_agent.py +0 -17
  265. clearskies/column_types/created_micro.py +0 -26
  266. clearskies/column_types/datetime.py +0 -108
  267. clearskies/column_types/datetime_micro.py +0 -12
  268. clearskies/column_types/email.py +0 -18
  269. clearskies/column_types/float.py +0 -43
  270. clearskies/column_types/has_many.py +0 -139
  271. clearskies/column_types/integer.py +0 -41
  272. clearskies/column_types/json.py +0 -25
  273. clearskies/column_types/many_to_many.py +0 -278
  274. clearskies/column_types/many_to_many_with_data.py +0 -162
  275. clearskies/column_types/select.py +0 -11
  276. clearskies/column_types/string.py +0 -24
  277. clearskies/column_types/updated.py +0 -24
  278. clearskies/column_types/updated_micro.py +0 -24
  279. clearskies/column_types/uuid.py +0 -25
  280. clearskies/columns.py +0 -123
  281. clearskies/condition_parser.py +0 -172
  282. clearskies/contexts/build_context.py +0 -54
  283. clearskies/contexts/convert_to_application.py +0 -190
  284. clearskies/contexts/extract_handler.py +0 -37
  285. clearskies/contexts/test.py +0 -94
  286. clearskies/decorators/__init__.py +0 -39
  287. clearskies/decorators/auth0_jwks.py +0 -22
  288. clearskies/decorators/authorization.py +0 -10
  289. clearskies/decorators/binding_classes.py +0 -9
  290. clearskies/decorators/binding_modules.py +0 -9
  291. clearskies/decorators/bindings.py +0 -9
  292. clearskies/decorators/create.py +0 -10
  293. clearskies/decorators/delete.py +0 -10
  294. clearskies/decorators/docs.py +0 -14
  295. clearskies/decorators/get.py +0 -10
  296. clearskies/decorators/jwks.py +0 -26
  297. clearskies/decorators/merge.py +0 -124
  298. clearskies/decorators/patch.py +0 -10
  299. clearskies/decorators/post.py +0 -10
  300. clearskies/decorators/public.py +0 -11
  301. clearskies/decorators/response_headers.py +0 -10
  302. clearskies/decorators/return_raw_response.py +0 -9
  303. clearskies/decorators/schema.py +0 -10
  304. clearskies/decorators/secret_bearer.py +0 -24
  305. clearskies/decorators/security_headers.py +0 -10
  306. clearskies/di/standard_dependencies.py +0 -140
  307. clearskies/di/test_module/__init__.py +0 -6
  308. clearskies/di/test_module/another_module/__init__.py +0 -2
  309. clearskies/di/test_module/module_class.py +0 -5
  310. clearskies/handlers/__init__.py +0 -41
  311. clearskies/handlers/advanced_search.py +0 -271
  312. clearskies/handlers/base.py +0 -473
  313. clearskies/handlers/callable.py +0 -189
  314. clearskies/handlers/create.py +0 -35
  315. clearskies/handlers/crud_by_method.py +0 -18
  316. clearskies/handlers/database_connector.py +0 -32
  317. clearskies/handlers/delete.py +0 -61
  318. clearskies/handlers/exceptions/__init__.py +0 -5
  319. clearskies/handlers/exceptions/not_found.py +0 -3
  320. clearskies/handlers/get.py +0 -156
  321. clearskies/handlers/health_check.py +0 -59
  322. clearskies/handlers/input_processing.py +0 -79
  323. clearskies/handlers/list.py +0 -530
  324. clearskies/handlers/mygrations.py +0 -82
  325. clearskies/handlers/request_method_routing.py +0 -47
  326. clearskies/handlers/restful_api.py +0 -218
  327. clearskies/handlers/routing.py +0 -62
  328. clearskies/handlers/schema_helper.py +0 -128
  329. clearskies/handlers/simple_routing.py +0 -204
  330. clearskies/handlers/simple_routing_route.py +0 -192
  331. clearskies/handlers/simple_search.py +0 -136
  332. clearskies/handlers/update.py +0 -96
  333. clearskies/handlers/write.py +0 -193
  334. clearskies/input_requirements/__init__.py +0 -68
  335. clearskies/input_requirements/after.py +0 -36
  336. clearskies/input_requirements/before.py +0 -36
  337. clearskies/input_requirements/in_the_future_at_least.py +0 -19
  338. clearskies/input_requirements/in_the_future_at_most.py +0 -19
  339. clearskies/input_requirements/in_the_past_at_least.py +0 -19
  340. clearskies/input_requirements/in_the_past_at_most.py +0 -19
  341. clearskies/input_requirements/maximum_length.py +0 -19
  342. clearskies/input_requirements/minimum_length.py +0 -22
  343. clearskies/input_requirements/requirement.py +0 -25
  344. clearskies/input_requirements/time_delta.py +0 -38
  345. clearskies/input_requirements/unique.py +0 -18
  346. clearskies/mocks/__init__.py +0 -7
  347. clearskies/mocks/input_output.py +0 -124
  348. clearskies/mocks/models.py +0 -142
  349. clearskies/models.py +0 -345
  350. clearskies/security_headers/base.py +0 -12
  351. clearskies/tests/simple_api/models/__init__.py +0 -2
  352. clearskies/tests/simple_api/models/status.py +0 -23
  353. clearskies/tests/simple_api/models/user.py +0 -21
  354. clearskies/tests/simple_api/users_api.py +0 -64
  355. {clear_skies-1.19.22.dist-info → clear_skies-2.0.23.dist-info/licenses}/LICENSE +0 -0
  356. /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
  357. /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
  358. /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
  359. /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
  360. /clearskies/{secrets/exceptions → exceptions}/not_found.py +0 -0
  361. /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
  362. /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
@@ -1,100 +1,64 @@
1
- from abc import ABC, abstractmethod
2
- from collections import OrderedDict
3
- from ..handlers.exceptions import ClientError
1
+ from __future__ import annotations
2
+
4
3
  import json
4
+ from abc import ABC, abstractmethod
5
+ from typing import TYPE_CHECKING, Any
5
6
 
7
+ from clearskies import configs, configurable
6
8
 
7
- class InputOutput(ABC):
8
- _response_headers = None
9
- _body_as_json = None
10
- _body_loaded_as_json = False
11
- _routing_data = None
12
- _authorization_data = None
9
+ from .headers import Headers
13
10
 
14
- @abstractmethod
15
- def respond(self, body, status_code=200):
16
- pass
11
+ if TYPE_CHECKING:
12
+ from clearskies import typing
17
13
 
18
- def error(self, body):
19
- return self.respond(body, 400)
20
14
 
21
- def success(self, body):
22
- return self.respond(body)
15
+ class InputOutput(ABC, configurable.Configurable):
16
+ """Manage the request and response to the client."""
23
17
 
24
- def configure(self):
25
- pass
26
-
27
- def has_header(self, key):
28
- if self._response_headers is None:
29
- return False
30
- return key.upper() in self._response_headers
18
+ response_headers = configs.Headers(default=Headers())
19
+ request_headers = configs.Headers(default=Headers())
20
+ query_parameters = configs.AnyDict(default={})
21
+ routing_data = configs.StringDict(default={})
22
+ authorization_data = configs.AnyDict(default={})
23
+ request_method = configs.Select(["GET", "POST", "PATCH", "OPTIONS", "DELETE", "SEARCH"], default="GET")
24
+ supports_request_method = configs.Boolean(default=True)
25
+ supports_url = configs.Boolean(default=True)
31
26
 
32
- def set_header(self, key, value):
33
- if self._response_headers is None:
34
- self._response_headers = OrderedDict()
35
- self._response_headers[key.upper()] = value
27
+ _body_as_json: dict[str, Any] | list[Any] | None = {}
28
+ _body_loaded_as_json = False
36
29
 
37
- def clear_header(self, key):
38
- if self._response_headers is None:
39
- return
40
- if self.has_header(key):
41
- del self._response_headers[key.upper()]
30
+ def __init__(self):
31
+ self.response_headers = Headers()
32
+ self.finalize_and_validate_configuration()
42
33
 
43
- def clear_headers(self, key):
44
- self._response_headers = None
34
+ @abstractmethod
35
+ def respond(self, body: typing.response, status_code: int = 200) -> Any:
36
+ """
37
+ Pass along a response to the client.
45
38
 
46
- def set_headers(self, headers):
47
- for key, value in headers.items():
48
- self.set_header(key, value)
39
+ Accepts a string, bytes, dictionary, or list. If a content type has not been set then it will automatically
40
+ be set to application/json
41
+ """
42
+ pass
49
43
 
50
44
  @abstractmethod
51
- def get_body(self):
45
+ def get_body(self) -> str:
46
+ """Return the raw body set by the client."""
52
47
  pass
53
48
 
54
49
  @abstractmethod
55
- def has_body(self):
50
+ def has_body(self) -> bool:
51
+ """Whether or not the request included a body."""
56
52
  pass
57
53
 
58
- def routing_data(self):
59
- return self._routing_data if self._routing_data is not None else {}
60
-
61
- def set_routing_data(self, data):
62
- self._routing_data = data
63
-
64
- def add_routing_data(self, key, value=None):
65
- if self._routing_data is None:
66
- self._routing_data = {}
67
- if type(key) == dict:
68
- self._routing_data = {**self._routing_data, **key}
69
- else:
70
- self._routing_data[key] = value
71
-
72
- def request_data(self, required=True):
73
- request_data = self.json_body(False)
74
- if not request_data:
75
- if self.has_body():
76
- raise ClientError("Request body was not valid JSON")
77
- request_data = {}
78
- return request_data
79
-
80
- def json_body(self, required=True):
81
- json = self._get_json_body()
82
- # if we get None then either the body was not JSON or was empty.
83
- # If it is required then we have an exception either way. If it is not required
84
- # then we have an exception if a body was provided but it was not JSON. We can check for this
85
- # if json is None and there is an actual request body. If json is none, the body is empty,
86
- # and it was not required, then we can just return None
87
- if json is None:
88
- if required or self.has_body():
89
- raise ClientError("Request body was not valid JSON")
90
- return json
91
-
92
- def _get_json_body(self):
54
+ @property
55
+ def request_data(self) -> dict[str, Any] | list[Any] | None:
56
+ """Return the data from the request body, assuming it is JSON."""
93
57
  if not self._body_loaded_as_json:
94
- if self.get_body() is None:
58
+ self._body_loaded_as_json = True
59
+ if not self.has_body():
95
60
  self._body_as_json = None
96
61
  else:
97
- self._body_loaded_as_json = True
98
62
  try:
99
63
  self._body_as_json = json.loads(self.get_body())
100
64
  except json.JSONDecodeError:
@@ -102,30 +66,7 @@ class InputOutput(ABC):
102
66
  return self._body_as_json
103
67
 
104
68
  @abstractmethod
105
- def get_request_method(self):
106
- pass
107
-
108
- @abstractmethod
109
- def get_script_name(self):
110
- pass
111
-
112
- @abstractmethod
113
- def get_path_info(self):
114
- pass
115
-
116
- def get_full_path(self):
117
- path_info = self.get_path_info()
118
- script_name = self.get_script_name()
119
- if not path_info or path_info[0] != "/":
120
- path_info = f"/{path_info}"
121
- return f"{path_info}{script_name}".replace("//", "/")
122
-
123
- @abstractmethod
124
- def get_query_string(self):
125
- pass
126
-
127
- @abstractmethod
128
- def get_content_type(self):
69
+ def get_client_ip(self):
129
70
  pass
130
71
 
131
72
  @abstractmethod
@@ -133,30 +74,43 @@ class InputOutput(ABC):
133
74
  pass
134
75
 
135
76
  @abstractmethod
136
- def has_request_header(self, header_name):
137
- pass
138
-
139
- @abstractmethod
140
- def get_request_header(self, header_name, silent=True):
141
- pass
142
-
143
- @abstractmethod
144
- def get_query_parameter(self, key):
77
+ def get_full_path(self) -> str:
145
78
  pass
146
79
 
147
- @abstractmethod
148
- def get_query_parameters(self):
149
- pass
150
-
151
- @abstractmethod
152
- def get_client_ip(self):
153
- pass
154
-
155
- def set_authorization_data(self, data):
156
- self._authorization_data = data
157
-
158
- def get_authorization_data(self):
159
- return self._authorization_data if self._authorization_data else {}
160
-
161
80
  def context_specifics(self):
162
81
  return {}
82
+
83
+ def get_context_for_callables(self) -> dict[str, Any]:
84
+ """
85
+ Return a dictionary with various important parts of the request that are passed along to user-defined functions.
86
+
87
+ It's common to make various aspects of an incoming request available to user-defined functions that are
88
+ attached to clearskies hooks everywhere. This function centralizes the definition of what aspects of
89
+ the reequest shouuld be passed along to callables in this case. When this is in use it typically
90
+ looks like this:
91
+
92
+ di.call_function(some_function, **input_output.get_context_for_callables())
93
+
94
+ And this function returns a dictionary with the following values:
95
+
96
+ | Key | Type | Ref | Value |
97
+ |--------------------|----------------------------------|------------------------------------|---------------------------------------------------------------------------------|
98
+ | routing_data | dict[str, str] | input_output.routing_data | A dictionary of data extracted from URL path parameters. |
99
+ | authorization_data | dict[str, Any] | input_output.authorization_data | A dictionary containing the authorization data set by the authentication method |
100
+ | request_data | dict[str, Any] | None | input_output.request_data | The data sent along with the request (assuming a JSON request body) |
101
+ | query_parameters | dict[str, Any] | input_output.query_parameters | The query parameters |
102
+ | request_headers | clearskies.input_outputs.Headers | input_output.request_headers | The request headers sent by the client |
103
+ | **routing_data | string | **input_output.routing_data | The routing data is unpacked so keys can be fetched directly |
104
+ | **[varies] | varies | **input_output.context_specifics() | Any additional properties added on by the context (see your context docs) |
105
+ """
106
+ return {
107
+ **self.routing_data,
108
+ **self.context_specifics(),
109
+ **{
110
+ "routing_data": self.routing_data,
111
+ "authorization_data": self.authorization_data,
112
+ "request_data": self.request_data,
113
+ "request_headers": self.request_headers,
114
+ "query_parameters": self.query_parameters,
115
+ },
116
+ }
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from clearskies.input_outputs.headers import Headers
6
+ from clearskies.input_outputs.input_output import InputOutput
7
+
8
+
9
+ class Programmatic(InputOutput):
10
+ _body: str | dict[str, Any] | list[Any] = ""
11
+ url: str = ""
12
+ ip_address: str = "127.0.0.1"
13
+ protocol: str = "https"
14
+
15
+ def __init__(
16
+ self,
17
+ url: str = "",
18
+ request_method: str = "GET",
19
+ body: str | dict[str, Any] | list[Any] = "",
20
+ query_parameters: dict[str, Any] = {},
21
+ request_headers: dict[str, str] = {},
22
+ ip_address: str = "127.0.0.1",
23
+ protocol: str = "https",
24
+ ):
25
+ self.url = url
26
+ self.request_headers = Headers(request_headers)
27
+ self.query_parameters = query_parameters
28
+ self.ip_address = ip_address
29
+ self.protocol = protocol
30
+ self._body_loaded_as_json = True
31
+ self._body_as_json = None
32
+ self.request_method = request_method
33
+ if body:
34
+ self._body = body
35
+ if isinstance(body, dict) or isinstance(body, list):
36
+ self._body_as_json = body
37
+
38
+ super().__init__()
39
+
40
+ def respond(self, response, status_code=200):
41
+ return (status_code, response, self.response_headers)
42
+
43
+ def get_full_path(self):
44
+ return self.url
45
+
46
+ def has_body(self):
47
+ return bool(self._body)
48
+
49
+ def get_body(self):
50
+ if not self.has_body():
51
+ return ""
52
+
53
+ return self._body
54
+
55
+ def context_specifics(self):
56
+ return {}
57
+
58
+ def get_client_ip(self):
59
+ return self.ip_address
60
+
61
+ def get_protocol(self):
62
+ return self.protocol
@@ -1,31 +1,42 @@
1
- from .input_output import InputOutput
2
- import urllib, urllib.parse
1
+ from __future__ import annotations
2
+
3
3
  import json
4
+ from typing import Callable
5
+ from urllib.parse import parse_qs
6
+
7
+ from clearskies.input_outputs.headers import Headers
8
+ from clearskies.input_outputs.input_output import InputOutput
4
9
 
5
10
 
6
- class WSGI(InputOutput):
7
- _environment = None
8
- _start_response = None
9
- _request_headers = None
10
- _cached_body = None
11
- _query_parameters = None
11
+ class Wsgi(InputOutput):
12
+ _environment: dict[str, str] = {}
13
+ _start_response: Callable
14
+ _cached_body: str | None = None
12
15
 
13
16
  def __init__(self, environment, start_response):
14
17
  self._environment = environment
15
18
  self._start_response = start_response
16
- self._request_headers = {}
19
+ request_headers = {}
17
20
  for key, value in self._environment.items():
18
21
  if key.upper()[0:5] == "HTTP_":
19
- self._request_headers[key[5:].lower()] = value
22
+ request_headers[key[5:].lower()] = value
23
+
24
+ self.request_headers = Headers(request_headers)
25
+ self.query_parameters = {
26
+ key: val[0] for (key, val) in parse_qs(self._environment.get("QUERY_STRING", "")).items()
27
+ }
28
+ self.request_method = self._environment.get("REQUEST_METHOD").upper()
29
+
30
+ super().__init__()
20
31
 
21
32
  def _from_environment(self, key):
22
33
  return self._environment[key] if key in self._environment else ""
23
34
 
24
35
  def respond(self, body, status_code=200):
25
- if not self.has_header("content-type"):
26
- self.set_header("content-type", "application/json; charset=UTF-8")
36
+ if "content-type" not in self.response_headers:
37
+ self.response_headers.content_type = "application/json; charset=UTF-8"
27
38
 
28
- self._start_response(f"{status_code} Ok", [header for header in self._response_headers.items()])
39
+ self._start_response(f"{status_code} Ok", [header for header in self.response_headers.items()]) # type: ignore
29
40
  if type(body) == bytes:
30
41
  final_body = body
31
42
  elif type(body) == str:
@@ -35,56 +46,33 @@ class WSGI(InputOutput):
35
46
  return [final_body]
36
47
 
37
48
  def has_body(self):
38
- return bool(self.get_body())
49
+ return bool(self._from_environment("CONTENT_LENGTH"))
39
50
 
40
51
  def get_body(self):
41
52
  if self._cached_body is None:
42
- self._cached_body = self._from_environment("wsgi.input").read().decode("utf-8")
53
+ self._cached_body = (
54
+ self._from_environment("wsgi.input").read(int(self._from_environment("CONTENT_LENGTH"))).decode("utf-8")
55
+ if self._from_environment("CONTENT_LENGTH")
56
+ else ""
57
+ )
43
58
  return self._cached_body
44
59
 
45
- def get_request_method(self):
46
- return self._from_environment("REQUEST_METHOD").upper()
47
-
48
60
  def get_script_name(self):
49
61
  return self._from_environment("SCRIPT_NAME")
50
62
 
51
63
  def get_path_info(self):
52
64
  return self._from_environment("PATH_INFO")
53
65
 
54
- def get_query_string(self):
55
- return self._from_environment("QUERY_STRING")
56
-
57
- def get_content_type(self):
58
- return self._from_environment("CONTENT_TYPE")
66
+ def get_full_path(self):
67
+ path_info = self.get_path_info()
68
+ script_name = self.get_script_name()
69
+ if not path_info or path_info[0] != "/":
70
+ path_info = f"/{path_info}"
71
+ return f"{path_info}{script_name}".replace("//", "/")
59
72
 
60
73
  def get_protocol(self):
61
74
  return self._from_environment("wsgi.url_scheme").lower()
62
75
 
63
- def has_request_header(self, header_name):
64
- return header_name.lower() in self._request_headers
65
-
66
- def get_request_header(self, header_name, silent=False):
67
- if not header_name.lower() in self._request_headers:
68
- if not silent:
69
- raise KeyError(f"HTTP header '{header_name}' was not found in request")
70
- return ""
71
- return self._request_headers[header_name.lower()]
72
-
73
- def _parse_query_parameters(self):
74
- if self._query_parameters is None:
75
- self._query_parameters = {
76
- key: val[0] if len(val) == 1 else val
77
- for (key, val) in urllib.parse.parse_qs(self.get_query_string()).items()
78
- }
79
-
80
- def get_query_parameter(self, key):
81
- self._parse_query_parameters()
82
- return self._query_parameters[key] if key in self._query_parameters else []
83
-
84
- def get_query_parameters(self):
85
- self._parse_query_parameters()
86
- return self._query_parameters
87
-
88
76
  def context_specifics(self):
89
77
  return {"wsgi_environment": self._environment}
90
78