clear-skies 2.0.3__py3-none-any.whl → 2.0.5__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.

Potentially problematic release.


This version of clear-skies might be problematic. Click here for more details.

Files changed (251) hide show
  1. clear_skies-2.0.5.dist-info/METADATA +74 -0
  2. clear_skies-2.0.5.dist-info/RECORD +4 -0
  3. {clear_skies-2.0.3.dist-info → clear_skies-2.0.5.dist-info}/WHEEL +1 -1
  4. clear_skies-2.0.3.dist-info/METADATA +0 -46
  5. clear_skies-2.0.3.dist-info/RECORD +0 -249
  6. clearskies/__init__.py +0 -59
  7. clearskies/action.py +0 -7
  8. clearskies/authentication/__init__.py +0 -15
  9. clearskies/authentication/authentication.py +0 -46
  10. clearskies/authentication/authorization.py +0 -16
  11. clearskies/authentication/authorization_pass_through.py +0 -20
  12. clearskies/authentication/jwks.py +0 -163
  13. clearskies/authentication/public.py +0 -5
  14. clearskies/authentication/secret_bearer.py +0 -553
  15. clearskies/autodoc/__init__.py +0 -8
  16. clearskies/autodoc/formats/__init__.py +0 -5
  17. clearskies/autodoc/formats/oai3_json/__init__.py +0 -7
  18. clearskies/autodoc/formats/oai3_json/oai3_json.py +0 -87
  19. clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +0 -15
  20. clearskies/autodoc/formats/oai3_json/parameter.py +0 -35
  21. clearskies/autodoc/formats/oai3_json/request.py +0 -68
  22. clearskies/autodoc/formats/oai3_json/response.py +0 -28
  23. clearskies/autodoc/formats/oai3_json/schema/__init__.py +0 -11
  24. clearskies/autodoc/formats/oai3_json/schema/array.py +0 -9
  25. clearskies/autodoc/formats/oai3_json/schema/default.py +0 -13
  26. clearskies/autodoc/formats/oai3_json/schema/enum.py +0 -7
  27. clearskies/autodoc/formats/oai3_json/schema/object.py +0 -29
  28. clearskies/autodoc/formats/oai3_json/test.json +0 -1985
  29. clearskies/autodoc/py.typed +0 -0
  30. clearskies/autodoc/request/__init__.py +0 -15
  31. clearskies/autodoc/request/header.py +0 -6
  32. clearskies/autodoc/request/json_body.py +0 -6
  33. clearskies/autodoc/request/parameter.py +0 -8
  34. clearskies/autodoc/request/request.py +0 -38
  35. clearskies/autodoc/request/url_parameter.py +0 -6
  36. clearskies/autodoc/request/url_path.py +0 -6
  37. clearskies/autodoc/response/__init__.py +0 -5
  38. clearskies/autodoc/response/response.py +0 -9
  39. clearskies/autodoc/schema/__init__.py +0 -31
  40. clearskies/autodoc/schema/array.py +0 -10
  41. clearskies/autodoc/schema/base64.py +0 -8
  42. clearskies/autodoc/schema/boolean.py +0 -5
  43. clearskies/autodoc/schema/date.py +0 -5
  44. clearskies/autodoc/schema/datetime.py +0 -5
  45. clearskies/autodoc/schema/double.py +0 -5
  46. clearskies/autodoc/schema/enum.py +0 -17
  47. clearskies/autodoc/schema/integer.py +0 -6
  48. clearskies/autodoc/schema/long.py +0 -5
  49. clearskies/autodoc/schema/number.py +0 -6
  50. clearskies/autodoc/schema/object.py +0 -13
  51. clearskies/autodoc/schema/password.py +0 -5
  52. clearskies/autodoc/schema/schema.py +0 -11
  53. clearskies/autodoc/schema/string.py +0 -5
  54. clearskies/backends/__init__.py +0 -65
  55. clearskies/backends/api_backend.py +0 -1178
  56. clearskies/backends/backend.py +0 -136
  57. clearskies/backends/cursor_backend.py +0 -335
  58. clearskies/backends/memory_backend.py +0 -797
  59. clearskies/backends/secrets_backend.py +0 -106
  60. clearskies/column.py +0 -1233
  61. clearskies/columns/__init__.py +0 -71
  62. clearskies/columns/audit.py +0 -206
  63. clearskies/columns/belongs_to_id.py +0 -483
  64. clearskies/columns/belongs_to_model.py +0 -132
  65. clearskies/columns/belongs_to_self.py +0 -105
  66. clearskies/columns/boolean.py +0 -113
  67. clearskies/columns/category_tree.py +0 -275
  68. clearskies/columns/category_tree_ancestors.py +0 -51
  69. clearskies/columns/category_tree_children.py +0 -127
  70. clearskies/columns/category_tree_descendants.py +0 -48
  71. clearskies/columns/created.py +0 -95
  72. clearskies/columns/created_by_authorization_data.py +0 -116
  73. clearskies/columns/created_by_header.py +0 -99
  74. clearskies/columns/created_by_ip.py +0 -92
  75. clearskies/columns/created_by_routing_data.py +0 -97
  76. clearskies/columns/created_by_user_agent.py +0 -92
  77. clearskies/columns/date.py +0 -234
  78. clearskies/columns/datetime.py +0 -282
  79. clearskies/columns/email.py +0 -76
  80. clearskies/columns/float.py +0 -153
  81. clearskies/columns/has_many.py +0 -505
  82. clearskies/columns/has_many_self.py +0 -56
  83. clearskies/columns/has_one.py +0 -14
  84. clearskies/columns/integer.py +0 -160
  85. clearskies/columns/json.py +0 -126
  86. clearskies/columns/many_to_many_ids.py +0 -337
  87. clearskies/columns/many_to_many_ids_with_data.py +0 -274
  88. clearskies/columns/many_to_many_models.py +0 -158
  89. clearskies/columns/many_to_many_pivots.py +0 -134
  90. clearskies/columns/phone.py +0 -159
  91. clearskies/columns/select.py +0 -92
  92. clearskies/columns/string.py +0 -102
  93. clearskies/columns/timestamp.py +0 -164
  94. clearskies/columns/updated.py +0 -110
  95. clearskies/columns/uuid.py +0 -86
  96. clearskies/configs/README.md +0 -105
  97. clearskies/configs/__init__.py +0 -162
  98. clearskies/configs/actions.py +0 -43
  99. clearskies/configs/any.py +0 -13
  100. clearskies/configs/any_dict.py +0 -22
  101. clearskies/configs/any_dict_or_callable.py +0 -23
  102. clearskies/configs/authentication.py +0 -23
  103. clearskies/configs/authorization.py +0 -23
  104. clearskies/configs/boolean.py +0 -16
  105. clearskies/configs/boolean_or_callable.py +0 -18
  106. clearskies/configs/callable_config.py +0 -18
  107. clearskies/configs/columns.py +0 -34
  108. clearskies/configs/conditions.py +0 -30
  109. clearskies/configs/config.py +0 -24
  110. clearskies/configs/datetime.py +0 -18
  111. clearskies/configs/datetime_or_callable.py +0 -19
  112. clearskies/configs/endpoint.py +0 -23
  113. clearskies/configs/endpoint_list.py +0 -28
  114. clearskies/configs/float.py +0 -16
  115. clearskies/configs/float_or_callable.py +0 -18
  116. clearskies/configs/integer.py +0 -16
  117. clearskies/configs/integer_or_callable.py +0 -18
  118. clearskies/configs/joins.py +0 -30
  119. clearskies/configs/list_any_dict.py +0 -30
  120. clearskies/configs/list_any_dict_or_callable.py +0 -31
  121. clearskies/configs/model_class.py +0 -35
  122. clearskies/configs/model_column.py +0 -65
  123. clearskies/configs/model_columns.py +0 -56
  124. clearskies/configs/model_destination_name.py +0 -25
  125. clearskies/configs/model_to_id_column.py +0 -43
  126. clearskies/configs/readable_model_column.py +0 -9
  127. clearskies/configs/readable_model_columns.py +0 -9
  128. clearskies/configs/schema.py +0 -23
  129. clearskies/configs/searchable_model_columns.py +0 -9
  130. clearskies/configs/security_headers.py +0 -39
  131. clearskies/configs/select.py +0 -26
  132. clearskies/configs/select_list.py +0 -47
  133. clearskies/configs/string.py +0 -29
  134. clearskies/configs/string_dict.py +0 -32
  135. clearskies/configs/string_list.py +0 -32
  136. clearskies/configs/string_list_or_callable.py +0 -35
  137. clearskies/configs/string_or_callable.py +0 -18
  138. clearskies/configs/timedelta.py +0 -18
  139. clearskies/configs/timezone.py +0 -18
  140. clearskies/configs/url.py +0 -23
  141. clearskies/configs/validators.py +0 -45
  142. clearskies/configs/writeable_model_column.py +0 -9
  143. clearskies/configs/writeable_model_columns.py +0 -9
  144. clearskies/configurable.py +0 -76
  145. clearskies/contexts/__init__.py +0 -11
  146. clearskies/contexts/cli.py +0 -117
  147. clearskies/contexts/context.py +0 -98
  148. clearskies/contexts/wsgi.py +0 -76
  149. clearskies/contexts/wsgi_ref.py +0 -82
  150. clearskies/decorators.py +0 -33
  151. clearskies/di/__init__.py +0 -14
  152. clearskies/di/additional_config.py +0 -130
  153. clearskies/di/additional_config_auto_import.py +0 -17
  154. clearskies/di/di.py +0 -968
  155. clearskies/di/inject/__init__.py +0 -23
  156. clearskies/di/inject/by_class.py +0 -21
  157. clearskies/di/inject/by_name.py +0 -18
  158. clearskies/di/inject/di.py +0 -13
  159. clearskies/di/inject/environment.py +0 -14
  160. clearskies/di/inject/input_output.py +0 -20
  161. clearskies/di/inject/now.py +0 -13
  162. clearskies/di/inject/requests.py +0 -13
  163. clearskies/di/inject/secrets.py +0 -14
  164. clearskies/di/inject/utcnow.py +0 -13
  165. clearskies/di/inject/uuid.py +0 -15
  166. clearskies/di/injectable.py +0 -29
  167. clearskies/di/injectable_properties.py +0 -131
  168. clearskies/di/test_module/__init__.py +0 -6
  169. clearskies/di/test_module/another_module/__init__.py +0 -2
  170. clearskies/di/test_module/module_class.py +0 -5
  171. clearskies/end.py +0 -183
  172. clearskies/endpoint.py +0 -1310
  173. clearskies/endpoint_group.py +0 -310
  174. clearskies/endpoints/__init__.py +0 -23
  175. clearskies/endpoints/advanced_search.py +0 -526
  176. clearskies/endpoints/callable.py +0 -388
  177. clearskies/endpoints/create.py +0 -202
  178. clearskies/endpoints/delete.py +0 -139
  179. clearskies/endpoints/get.py +0 -275
  180. clearskies/endpoints/health_check.py +0 -181
  181. clearskies/endpoints/list.py +0 -573
  182. clearskies/endpoints/restful_api.py +0 -427
  183. clearskies/endpoints/simple_search.py +0 -286
  184. clearskies/endpoints/update.py +0 -190
  185. clearskies/environment.py +0 -104
  186. clearskies/exceptions/__init__.py +0 -17
  187. clearskies/exceptions/authentication.py +0 -2
  188. clearskies/exceptions/authorization.py +0 -2
  189. clearskies/exceptions/client_error.py +0 -2
  190. clearskies/exceptions/input_errors.py +0 -4
  191. clearskies/exceptions/moved_permanently.py +0 -3
  192. clearskies/exceptions/moved_temporarily.py +0 -3
  193. clearskies/exceptions/not_found.py +0 -2
  194. clearskies/functional/__init__.py +0 -7
  195. clearskies/functional/routing.py +0 -92
  196. clearskies/functional/string.py +0 -112
  197. clearskies/functional/validations.py +0 -76
  198. clearskies/input_outputs/__init__.py +0 -13
  199. clearskies/input_outputs/cli.py +0 -171
  200. clearskies/input_outputs/exceptions/__init__.py +0 -2
  201. clearskies/input_outputs/exceptions/cli_input_error.py +0 -2
  202. clearskies/input_outputs/exceptions/cli_not_found.py +0 -2
  203. clearskies/input_outputs/headers.py +0 -45
  204. clearskies/input_outputs/input_output.py +0 -138
  205. clearskies/input_outputs/programmatic.py +0 -69
  206. clearskies/input_outputs/py.typed +0 -0
  207. clearskies/input_outputs/wsgi.py +0 -77
  208. clearskies/model.py +0 -1922
  209. clearskies/py.typed +0 -0
  210. clearskies/query/__init__.py +0 -12
  211. clearskies/query/condition.py +0 -223
  212. clearskies/query/join.py +0 -136
  213. clearskies/query/query.py +0 -196
  214. clearskies/query/sort.py +0 -27
  215. clearskies/schema.py +0 -82
  216. clearskies/secrets/__init__.py +0 -6
  217. clearskies/secrets/additional_configs/__init__.py +0 -32
  218. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +0 -61
  219. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -160
  220. clearskies/secrets/akeyless.py +0 -182
  221. clearskies/secrets/exceptions/__init__.py +0 -1
  222. clearskies/secrets/exceptions/not_found.py +0 -2
  223. clearskies/secrets/secrets.py +0 -38
  224. clearskies/security_header.py +0 -15
  225. clearskies/security_headers/__init__.py +0 -11
  226. clearskies/security_headers/cache_control.py +0 -67
  227. clearskies/security_headers/cors.py +0 -50
  228. clearskies/security_headers/csp.py +0 -94
  229. clearskies/security_headers/hsts.py +0 -22
  230. clearskies/security_headers/x_content_type_options.py +0 -0
  231. clearskies/security_headers/x_frame_options.py +0 -0
  232. clearskies/test_base.py +0 -8
  233. clearskies/typing.py +0 -11
  234. clearskies/validator.py +0 -37
  235. clearskies/validators/__init__.py +0 -33
  236. clearskies/validators/after_column.py +0 -62
  237. clearskies/validators/before_column.py +0 -13
  238. clearskies/validators/in_the_future.py +0 -32
  239. clearskies/validators/in_the_future_at_least.py +0 -11
  240. clearskies/validators/in_the_future_at_most.py +0 -10
  241. clearskies/validators/in_the_past.py +0 -32
  242. clearskies/validators/in_the_past_at_least.py +0 -10
  243. clearskies/validators/in_the_past_at_most.py +0 -10
  244. clearskies/validators/maximum_length.py +0 -26
  245. clearskies/validators/maximum_value.py +0 -29
  246. clearskies/validators/minimum_length.py +0 -26
  247. clearskies/validators/minimum_value.py +0 -29
  248. clearskies/validators/required.py +0 -34
  249. clearskies/validators/timedelta.py +0 -59
  250. clearskies/validators/unique.py +0 -30
  251. {clear_skies-2.0.3.dist-info → clear_skies-2.0.5.dist-info/licenses}/LICENSE +0 -0
@@ -1,92 +0,0 @@
1
- import re
2
-
3
-
4
- def match_route(expected_route, incoming_route, allow_partial=False) -> tuple[bool, dict[str, str]]:
5
- """
6
- Check if two routes match, and returns the routing data if so.
7
-
8
- A partial match happens when the beginning of the incoming route matches the expected route. It's okay for the
9
- incoming route to be longer because the routing system is hierarchical, so a partial match at the beginning
10
- can work. e.g.:
11
-
12
- Expected route: `/users`
13
- Incoming route: `/users/orders/5`
14
-
15
- But note that it must fully match all route segments, so this is never a match:
16
-
17
- Expected route: `/user`
18
- Incoming route: `/users/orders/5`
19
- """
20
- expected_route = expected_route.strip("/")
21
- incoming_route = incoming_route.strip("/")
22
-
23
- expected_parts = expected_route.split("/")
24
- incoming_parts = incoming_route.split("/")
25
-
26
- # quick check: if there are less parts in the incoming route than the expected route, then we can't possibly match
27
- if len(incoming_parts) < len(expected_parts):
28
- return (False, {})
29
- # ditto the opposite, if we can't do a partial match
30
- if len(expected_parts) < len(incoming_parts) and not allow_partial:
31
- return (False, {})
32
-
33
- # if we got this far then we will do a more complete match, so let's find any routing parameters
34
- routing_data = {}
35
- routing_parameters = extract_url_parameter_name_map(expected_route)
36
- # we want it backwards
37
- routing_parameters_by_index = {value: key for (key, value) in routing_parameters.items()}
38
- for index in range(len(expected_parts)):
39
- if index in routing_parameters_by_index:
40
- if not incoming_parts[index]:
41
- return (False, {})
42
- routing_data[routing_parameters_by_index[index]] = incoming_parts[index]
43
- else:
44
- if expected_parts[index] != incoming_parts[index]:
45
- return (False, {})
46
-
47
- return (True, routing_data)
48
-
49
-
50
- def extract_url_parameter_name_map(url: str) -> dict[str, int]:
51
- """
52
- Create a map to help match URLs with routing parameters.
53
-
54
- Routing parameters are either brace enclosed or start with colons:
55
-
56
- ```python
57
- print(
58
- routing.extract_url_parameter_name_map("my/path/{some_parameter}/:other_parameter/more/paths")
59
- )
60
- # prints {"some_parameter": 2, "other_parameter": 3}
61
- ```
62
-
63
- Note that leading and trailing slashes are stripped, so "/my/path/{id}" and "my/path/{id}" give identical
64
- parameter maps: `{"id": 2}`
65
- """
66
- parameter_name_map = {}
67
- path_parts = url.strip("/").split("/")
68
- for index, part in enumerate(path_parts):
69
- if not part:
70
- continue
71
- if part[0] == ":":
72
- match = re.match("^:(\\w[\\w\\d_]{0,})$", part)
73
- else:
74
- if part[0] != "{":
75
- continue
76
- if part[-1] != "}":
77
- raise ValueError(
78
- f"Invalid route configuration for URL '{url}': section '{part}'"
79
- + " starts with a '{' but does not end with one"
80
- )
81
- match = re.match("^{(\\w[\\w\\d_]{0,})\\}$", part)
82
- if not match:
83
- raise ValueError(
84
- f"Invalid route configuration for URL '{url}', section '{part}': resource identifiers must start with a letter and contain only letters, numbers, and underscores"
85
- )
86
- parameter_name = match.group(1)
87
- if parameter_name in parameter_name_map:
88
- raise ValueError(
89
- f"Invalid route configuration for URL '{url}', a URL path named '{parameter_name}' appeared more than once."
90
- )
91
- parameter_name_map[parameter_name] = index
92
- return parameter_name_map
@@ -1,112 +0,0 @@
1
- import datetime
2
- import re
3
-
4
-
5
- def camel_case_to_snake_case(string: str) -> str:
6
- """Convert a title/camel case string (MyString|myString) to snake case (my_string)."""
7
- return re.sub("([a-z0-9])([A-Z])", r"\1_\2", re.sub("(.)([A-Z][a-z]+)", r"\1_\2", string)).lower()
8
-
9
-
10
- def camel_case_to_title_case(string):
11
- return camel_case_to_words(string).title().replace(" ", "")
12
-
13
-
14
- def camel_case_to_words(string):
15
- string = re.sub("(.)([A-Z][a-z]+)", r"\1 \2", string)
16
- string = re.sub("([a-z0-9])([A-Z])", r"\1 \2", string).lower()
17
- return string
18
-
19
-
20
- def camel_case_to_nice(string):
21
- return camel_case_to_words(string).title()
22
-
23
-
24
- def title_case_to_snake_case(string: str) -> str:
25
- """Convert a title case string (MyString) to snake case (my_string)."""
26
- return camel_case_to_snake_case(string)
27
-
28
-
29
- def title_case_to_camel_case(string: str) -> str:
30
- if len(string) == 0:
31
- return string
32
- if len(string) == 1:
33
- return string.lower()
34
- return string[0].lower() + string[1:]
35
-
36
-
37
- def title_case_to_nice(string: str) -> str:
38
- return camel_case_to_nice(string)
39
-
40
-
41
- def snake_case_to_title_case(string: str) -> str:
42
- """
43
- Convert a snake case string (my_string) to title case (MyString).
44
-
45
- Note this is sometimes ambiguous. Consider:
46
-
47
- TitleCase -> snake_case -> TitleCase
48
- MyDbThing -> my_db_thing -> MyDbThing
49
- MyDBThing -> my_db_thing -> MyDbThing
50
- """
51
- words = string.lower().split("_")
52
- return "".join([x.title() for x in words])
53
-
54
-
55
- def snake_case_to_camel_case(string: str) -> str:
56
- """
57
- Convert a snake case string (my_string) to camel case (myString).
58
-
59
- Note this is sometimes ambiguous. Consider:
60
-
61
- camelCase -> snake_case -> camelCase
62
- myDbThing -> my_db_thing -> myDbThing
63
- myDBThing -> my_db_thing -> myDbThing
64
- """
65
- words = string.lower().split("_")
66
- return words[0] + "".join([x.title() for x in words[1:]])
67
-
68
-
69
- def snake_case_to_nice(string: str) -> str:
70
- return camel_case_to_nice(snake_case_to_camel_case(string))
71
-
72
-
73
- casings = ["camelCase", "snake_case", "TitleCase"]
74
- casing_swap_map = {
75
- "camelCase": {
76
- "camelCase": str,
77
- "snake_case": camel_case_to_snake_case,
78
- "TitleCase": camel_case_to_title_case,
79
- },
80
- "snake_case": {
81
- "camelCase": snake_case_to_camel_case,
82
- "snake_case": str,
83
- "TitleCase": snake_case_to_title_case,
84
- },
85
- "TitleCase": {
86
- "camelCase": title_case_to_camel_case,
87
- "snake_case": title_case_to_snake_case,
88
- "TitleCase": str,
89
- },
90
- }
91
-
92
-
93
- def swap_casing(string: str, from_casing: str, to_casing: str) -> str:
94
- if from_casing not in casings:
95
- raise ValueError(f"Invalid casing '{from_casing}'. Must be one of '" + "', ".join(casings) + "'")
96
- if to_casing not in casings:
97
- raise ValueError(f"Invalid casing '{to_casing}'. Must be one of '" + "', ".join(casings) + "'")
98
- return casing_swap_map[from_casing][to_casing](string) # type: ignore
99
-
100
-
101
- def make_plural(singular: str):
102
- if singular[-1] == "y":
103
- return singular[:-1] + "ies"
104
- if singular[-1] == "s":
105
- return singular + "es"
106
- return f"{singular}s"
107
-
108
-
109
- def datetime_to_iso(value):
110
- if not isinstance(value, datetime.date) and not isinstance(value, datetime.datetime):
111
- return value
112
- return value.isoformat()
@@ -1,76 +0,0 @@
1
- import inspect
2
- from typing import Any
3
-
4
-
5
- def is_model(to_check: Any) -> bool:
6
- """
7
- Return True/False to denote if the given value is a model instance.
8
-
9
- Uses ducktyping rather than checking the type, mostly to minimize the risk of circular imports
10
- """
11
- if not hasattr(to_check, "destination_name"):
12
- return False
13
- return True
14
-
15
-
16
- def is_model_class(to_check: Any) -> bool:
17
- """Return True/False to denote if the given value is a model class."""
18
- return inspect.isclass(to_check) and is_model(to_check)
19
-
20
-
21
- def is_model_class_reference(to_check: Any) -> bool:
22
- """Return True/False to denote if the given value is a reference to a model class."""
23
- if not hasattr(to_check, "get_model_class"):
24
- return False
25
-
26
- if inspect.isclass(to_check):
27
- to_check = to_check()
28
- return is_model_class(to_check.get_model_class())
29
-
30
-
31
- def is_model_class_or_reference(to_check: Any, raise_error_message=False, strict=True) -> bool:
32
- """
33
- Return True/False to denote if the given value is a model class or a model reference.
34
-
35
- If strict is false, then it won't check that the model reference returns a model class. This is often necessary during
36
- validation by configs as, otherwise, it tends to trigger circular dependency errors.
37
- """
38
- if not inspect.isclass(to_check):
39
- # for references we will accept either instances or classes
40
- if hasattr(to_check, "get_model_class"):
41
- return is_model_class(to_check.get_model_class()) if strict else True
42
-
43
- if raise_error_message:
44
- raise TypeError(
45
- f"I expected a model class or reference, but instead I received something of type '{to_check.__class__.__name__}'"
46
- )
47
- return False
48
-
49
- if is_model_class(to_check):
50
- return True
51
-
52
- if hasattr(to_check, "get_model_class"):
53
- if not strict:
54
- return True
55
-
56
- model_class = to_check().get_model_class()
57
- if is_model_class(model_class):
58
- return True
59
- if raise_error_message:
60
- raise TypeError(
61
- f"I expected a model class or reference. I received a model reference of class '{to_check.__name__}', but when I fetched the model out of it, it gave me back something that wasn't a model. Instead, I received something of type '{model_class.__name__}'"
62
- )
63
-
64
- if raise_error_message:
65
- raise TypeError(
66
- "I expected a model class or reference, but instead I received a class that was neither. It had a type of '"
67
- + to_check.__class__.__name__
68
- + "'"
69
- )
70
-
71
- return False
72
-
73
-
74
- def is_model_or_class(to_check: Any) -> bool:
75
- """Return True/False to denote if the given value is a model instance or model class."""
76
- return is_model(to_check) or is_model_class(to_check)
@@ -1,13 +0,0 @@
1
- from clearskies.input_outputs.cli import Cli
2
- from clearskies.input_outputs.headers import Headers
3
- from clearskies.input_outputs.input_output import InputOutput
4
- from clearskies.input_outputs.programmatic import Programmatic
5
- from clearskies.input_outputs.wsgi import Wsgi
6
-
7
- __all__ = [
8
- "Cli",
9
- "Headers",
10
- "InputOutput",
11
- "Programmatic",
12
- "Wsgi",
13
- ]
@@ -1,171 +0,0 @@
1
- import json
2
- import sys
3
- from os import isatty
4
- from sys import stdin
5
-
6
- from clearskies.input_outputs.input_output import InputOutput
7
-
8
-
9
- class Cli(InputOutput):
10
- _args: list[str] = []
11
- _has_body: bool = False
12
- _body: str = ""
13
- _request_method: str = ""
14
- _request_headers: dict[str, str] = {}
15
-
16
- def __init__(self):
17
- self._request_headers = {}
18
- self._args = []
19
- self._parse_args(sys.argv)
20
- super().__init__()
21
-
22
- def respond(self, response, status_code=200):
23
- if type(response) != str:
24
- final = json.dumps(response)
25
- else:
26
- final = response
27
- if status_code != 200:
28
- sys.exit(final)
29
- print(final)
30
-
31
- def get_arguments(self):
32
- return sys.argv
33
-
34
- def _parse_args(self, argv):
35
- tty_data = None
36
- if not isatty(stdin.fileno()):
37
- tty_data = sys.stdin.read().strip()
38
-
39
- request_headers = {}
40
- self._args = []
41
- kwargs = {}
42
- index = 0
43
- # In general we will use positional arguments for routing, and kwargs for request data.
44
- # If things start with a dash then they are assumed to be a kwarg. If not, then a positional argument.
45
- # we don't allow for simple flags: everything is a positional argument or a key/value pair
46
- # For kwargs, we'll allow for using an equal sign or not, e.g.: '--key=value' or '--key value' or '-d thing'.
47
- while index < len(argv) - 1:
48
- index += 1
49
-
50
- # if we don't start with a dash then we are a positional argument
51
- arg = argv[index]
52
- if arg[0] != "-":
53
- self._args.append(arg)
54
- continue
55
-
56
- # otherwise a kwarg
57
- arg = arg.strip("-")
58
-
59
- # if we have an equal sign in our kwarg then it's self-contained
60
- if "=" in arg:
61
- [key, value] = arg.split("=", 1)
62
-
63
- # otherwise we have to grab the next argument to get the value
64
- else:
65
- key = arg
66
- value = argv[index + 1]
67
- if "-" in value:
68
- raise ValueError(
69
- f"Invalid clearskies cli calling sequence: found two key names next to eachother without any values: '-{arg} {value}'"
70
- )
71
- index += 1
72
-
73
- if key.lower() == "h":
74
- parts = value.split(":", 1)
75
- if len(parts) != 2:
76
- raise ValueError(
77
- f"Invalid clearskies cli calling sequence: a parameter named '-H' was found, which is treated as a request header, but it didn't have the proper 'key: value' format."
78
- )
79
- request_headers[parts[0]] = parts[1]
80
- continue
81
-
82
- kwargs[key] = value
83
-
84
- self._request_headers = request_headers
85
- self._request_method = "GET"
86
- request_method_source = ""
87
- for key in ["x", "X", "request_method"]:
88
- if key not in kwargs:
89
- continue
90
-
91
- if request_method_source:
92
- raise ValueError(
93
- f"Invalid clearskies cli calling sequence: the request method was specified via both the -{key} parameter and the -{request_method_source} parameter. To avoid ambiguity, it should only be set once."
94
- )
95
- self._request_method = kwargs[key]
96
- del kwargs[key]
97
- request_method_source = key
98
-
99
- final_data = None
100
- data_source = None
101
- if tty_data:
102
- final_data = tty_data
103
- data_source = "piped input"
104
- if kwargs.get("d"):
105
- if final_data:
106
- raise ValueError(
107
- f"Invalid clearskies cli calling sequence: request data was sent by both the -d parameter and {data_source}. To avoid ambiguity, it should only be sent one way."
108
- )
109
- final_data = kwargs.get("d")
110
- data_source = "the -d parameter"
111
- del kwargs["d"]
112
- if kwargs.get("data"):
113
- if final_data:
114
- raise ValueError(
115
- f"Invalid calling sequence: request data was sent by both the -data parameter and {data_source}. To avoid ambiguity, it should only be sent one way."
116
- )
117
- final_data = kwargs.get("data")
118
- data_source = "the -data parameter"
119
- del kwargs["data"]
120
- if final_data and len(kwargs):
121
- raise ValueError(
122
- f"Invalid calling sequence: extra parameters were specified after sending a body via {data_source}. To avoid ambiguity, send all data via {data_source}."
123
- )
124
- if not final_data and len(kwargs):
125
- final_data = kwargs
126
- data_source = "kwargs"
127
-
128
- # Most of the above inputs result in a string for our final data, in which case we'll leave it as the "raw body"
129
- # so that it can optionally be interpreted as JSON. If we received a bunch of kwargs though, we'll allow those to
130
- # only be "read" as JSON.
131
- if data_source == "kwargs":
132
- self._body_as_json = final_data # type: ignore
133
- self._body_loaded_as_json = True
134
- self._has_body = True
135
- self._body = json.dumps(final_data)
136
- elif final_data:
137
- self._has_body = True
138
- self._body = final_data
139
-
140
- def get_script_name(self):
141
- return sys.argv[0]
142
-
143
- def get_path_info(self):
144
- return "/".join(self._args)
145
-
146
- def get_full_path(self):
147
- return self.get_path_info()
148
-
149
- def get_request_method(self):
150
- return self._request_method
151
-
152
- def has_body(self):
153
- return self._has_body
154
-
155
- def get_body(self):
156
- if not self.has_body():
157
- return ""
158
-
159
- return self._body
160
-
161
- def context_specifics(self):
162
- return {}
163
-
164
- def get_client_ip(self):
165
- return "127.0.0.1"
166
-
167
- def get_query_string(self):
168
- return ""
169
-
170
- def get_request_headers(self):
171
- return self._request_headers
@@ -1,2 +0,0 @@
1
- from .cli_input_error import CLIInputError
2
- from .cli_not_found import CLINotFound
@@ -1,2 +0,0 @@
1
- class CLIInputError(Exception):
2
- pass
@@ -1,2 +0,0 @@
1
- class CLINotFound(Exception):
2
- pass
@@ -1,45 +0,0 @@
1
- import re
2
-
3
-
4
- class Headers:
5
- _headers: dict[str, str] = {}
6
-
7
- def __init__(self, headers: dict[str, str] = {}):
8
- self.__dict__["_headers"] = (
9
- {key.upper().replace("_", "-"): value for (key, value) in headers.items()} if headers else {}
10
- )
11
-
12
- def __contains__(self, key: str):
13
- return key.upper().replace("_", "-") in self._headers
14
-
15
- def __getattr__(self, key: str) -> str | None:
16
- return self._headers.get(key.upper().replace("_", "-"), None)
17
-
18
- def __setattr__(self, key: str, value: str) -> None:
19
- if not isinstance(key, str):
20
- raise TypeError(
21
- f"Header keys must be strings, but an object of type '{value.__class__.__name__}' was provided."
22
- )
23
- if not isinstance(value, str):
24
- raise TypeError(
25
- f"Header values must be strings, but an object of type '{value.__class__.__name__}' was provided."
26
- )
27
- self._headers[re.sub("\\s+", " ", key.upper().replace("_", "-"))] = re.sub("\\s+", " ", value.strip())
28
-
29
- def get(self, key, default=None):
30
- if key not in self:
31
- return default
32
- return self.__getattr__(key)
33
-
34
- def keys(self) -> list[str]:
35
- return list(self._headers.keys())
36
-
37
- def values(self) -> list[str]:
38
- return list(self._headers.keys())
39
-
40
- def items(self) -> list[tuple[str]]:
41
- return list(self._headers.items()) # type: ignore
42
-
43
- def add(self, key: str, value: str) -> None:
44
- """Add a header. This expects a string with a colon separating the key and value."""
45
- setattr(self, key, value)
@@ -1,138 +0,0 @@
1
- import json
2
- from abc import ABC, abstractmethod
3
- from typing import Any
4
- from urllib.parse import parse_qs
5
-
6
- import clearskies.configurable
7
- import clearskies.typing
8
- from clearskies.configs import AnyDict, StringDict
9
- from clearskies.exceptions import ClientError
10
-
11
- from .headers import Headers
12
-
13
-
14
- class InputOutput(ABC, clearskies.configurable.Configurable):
15
- """Manage the request and response to the client."""
16
-
17
- response_headers: Headers = None # type: ignore
18
- request_headers: Headers = None # type: ignore
19
- query_parameters = clearskies.configs.AnyDict(default={})
20
- routing_data = clearskies.configs.StringDict(default={})
21
- authorization_data = clearskies.configs.AnyDict(default={})
22
-
23
- _body_as_json: dict[str, Any] | list[Any] | None = {}
24
- _body_loaded_as_json = False
25
-
26
- def __init__(self):
27
- self.response_headers = Headers()
28
- self.request_headers = Headers(self.get_request_headers())
29
- self.query_parameters = {key: val[0] for (key, val) in parse_qs(self.get_query_string()).items()}
30
- self.authorization_data = {}
31
- self.routing_data = {}
32
- self.finalize_and_validate_configuration()
33
-
34
- @abstractmethod
35
- def respond(self, body: clearskies.typing.response, status_code: int = 200) -> Any:
36
- """
37
- Pass along a response to the client.
38
-
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
43
-
44
- @abstractmethod
45
- def get_body(self) -> str:
46
- """Return the raw body set by the client."""
47
- pass
48
-
49
- @abstractmethod
50
- def has_body(self) -> bool:
51
- """Whether or not the request included a body."""
52
- pass
53
-
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."""
57
- if not self._body_loaded_as_json:
58
- self._body_loaded_as_json = True
59
- if not self.has_body():
60
- self._body_as_json = None
61
- else:
62
- try:
63
- self._body_as_json = json.loads(self.get_body())
64
- except json.JSONDecodeError:
65
- self._body_as_json = None
66
- return self._body_as_json
67
-
68
- @abstractmethod
69
- def get_request_method(self) -> str:
70
- """Return the request method set by the client."""
71
- pass
72
-
73
- @abstractmethod
74
- def get_script_name(self) -> str:
75
- """Return the script name, e.g. the path requested."""
76
- pass
77
-
78
- @abstractmethod
79
- def get_path_info(self) -> str:
80
- """Return the path info for the request."""
81
- pass
82
-
83
- @abstractmethod
84
- def get_query_string(self) -> str:
85
- """Return the full query string for the request (everything after the first question mark in the document URL)."""
86
- pass
87
-
88
- @abstractmethod
89
- def get_client_ip(self):
90
- pass
91
-
92
- @abstractmethod
93
- def get_request_headers(self) -> dict[str, str]:
94
- pass
95
-
96
- def get_full_path(self) -> str:
97
- """Return the full path requested by the client."""
98
- path_info = self.get_path_info()
99
- script_name = self.get_script_name()
100
- if not path_info or path_info[0] != "/":
101
- path_info = f"/{path_info}"
102
- return f"{path_info}{script_name}".replace("//", "/")
103
-
104
- def context_specifics(self):
105
- return {}
106
-
107
- def get_context_for_callables(self) -> dict[str, Any]:
108
- """
109
- Return a dictionary with various important parts of the request that are passed along to user-defined functions.
110
-
111
- It's common to make various aspects of an incoming request available to user-defined functions that are
112
- attached to clearskies hooks everywhere. This function centralizes the definition of what aspects of
113
- the reequest shouuld be passed along to callables in this case. When this is in use it typically
114
- looks like this:
115
-
116
- di.call_function(some_function, **input_output.get_context_for_callables())
117
-
118
- And this function returns a dictionary with the following values:
119
-
120
- | Key | Type | Ref | Value |
121
- |--------------------|----------------------------------|---------------------------------|---------------------------------------------------------------------------------|
122
- | routing_data | dict[str, str] | input_output.routing_data | A dictionary of data extracted from URL path parameters. |
123
- | authorization_data | dict[str, Any] | input_output.authorization_data | A dictionary containing the authorization data set by the authentication method |
124
- | request_data | dict[str, Any] | None | input_output.request_data | The data sent along with the request (assuming a JSON request body) |
125
- | query_parameters | dict[str, Any] | input_output.query_parameters | The query parameters |
126
- | request_headers | clearskies.input_outputs.Headers | input_output.request_headers | The request headers sent by the client |
127
- | **routing_data | string | **input_output.routing_data | The routing data is unpacked so keys can be fetched directly |
128
- """
129
- return {
130
- **self.routing_data,
131
- **{
132
- "routing_data": self.routing_data,
133
- "authorization_data": self.authorization_data,
134
- "request_data": self.request_data,
135
- "request_headers": self.request_headers,
136
- "query_parameters": self.query_parameters,
137
- },
138
- }