clear-skies 2.0.5__py3-none-any.whl → 2.0.7__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 (252) hide show
  1. {clear_skies-2.0.5.dist-info → clear_skies-2.0.7.dist-info}/METADATA +1 -1
  2. clear_skies-2.0.7.dist-info/RECORD +251 -0
  3. clearskies/__init__.py +61 -0
  4. clearskies/action.py +7 -0
  5. clearskies/authentication/__init__.py +15 -0
  6. clearskies/authentication/authentication.py +46 -0
  7. clearskies/authentication/authorization.py +16 -0
  8. clearskies/authentication/authorization_pass_through.py +20 -0
  9. clearskies/authentication/jwks.py +163 -0
  10. clearskies/authentication/public.py +5 -0
  11. clearskies/authentication/secret_bearer.py +553 -0
  12. clearskies/autodoc/__init__.py +8 -0
  13. clearskies/autodoc/formats/__init__.py +5 -0
  14. clearskies/autodoc/formats/oai3_json/__init__.py +7 -0
  15. clearskies/autodoc/formats/oai3_json/oai3_json.py +87 -0
  16. clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +15 -0
  17. clearskies/autodoc/formats/oai3_json/parameter.py +35 -0
  18. clearskies/autodoc/formats/oai3_json/request.py +68 -0
  19. clearskies/autodoc/formats/oai3_json/response.py +28 -0
  20. clearskies/autodoc/formats/oai3_json/schema/__init__.py +11 -0
  21. clearskies/autodoc/formats/oai3_json/schema/array.py +9 -0
  22. clearskies/autodoc/formats/oai3_json/schema/default.py +13 -0
  23. clearskies/autodoc/formats/oai3_json/schema/enum.py +7 -0
  24. clearskies/autodoc/formats/oai3_json/schema/object.py +35 -0
  25. clearskies/autodoc/formats/oai3_json/test.json +1985 -0
  26. clearskies/autodoc/py.typed +0 -0
  27. clearskies/autodoc/request/__init__.py +15 -0
  28. clearskies/autodoc/request/header.py +6 -0
  29. clearskies/autodoc/request/json_body.py +6 -0
  30. clearskies/autodoc/request/parameter.py +8 -0
  31. clearskies/autodoc/request/request.py +47 -0
  32. clearskies/autodoc/request/url_parameter.py +6 -0
  33. clearskies/autodoc/request/url_path.py +6 -0
  34. clearskies/autodoc/response/__init__.py +5 -0
  35. clearskies/autodoc/response/response.py +9 -0
  36. clearskies/autodoc/schema/__init__.py +31 -0
  37. clearskies/autodoc/schema/array.py +10 -0
  38. clearskies/autodoc/schema/base64.py +8 -0
  39. clearskies/autodoc/schema/boolean.py +5 -0
  40. clearskies/autodoc/schema/date.py +5 -0
  41. clearskies/autodoc/schema/datetime.py +5 -0
  42. clearskies/autodoc/schema/double.py +5 -0
  43. clearskies/autodoc/schema/enum.py +17 -0
  44. clearskies/autodoc/schema/integer.py +6 -0
  45. clearskies/autodoc/schema/long.py +5 -0
  46. clearskies/autodoc/schema/number.py +6 -0
  47. clearskies/autodoc/schema/object.py +13 -0
  48. clearskies/autodoc/schema/password.py +5 -0
  49. clearskies/autodoc/schema/schema.py +11 -0
  50. clearskies/autodoc/schema/string.py +5 -0
  51. clearskies/backends/__init__.py +65 -0
  52. clearskies/backends/api_backend.py +1178 -0
  53. clearskies/backends/backend.py +136 -0
  54. clearskies/backends/cursor_backend.py +335 -0
  55. clearskies/backends/memory_backend.py +797 -0
  56. clearskies/backends/secrets_backend.py +106 -0
  57. clearskies/column.py +1233 -0
  58. clearskies/columns/__init__.py +71 -0
  59. clearskies/columns/audit.py +206 -0
  60. clearskies/columns/belongs_to_id.py +483 -0
  61. clearskies/columns/belongs_to_model.py +132 -0
  62. clearskies/columns/belongs_to_self.py +105 -0
  63. clearskies/columns/boolean.py +113 -0
  64. clearskies/columns/category_tree.py +275 -0
  65. clearskies/columns/category_tree_ancestors.py +51 -0
  66. clearskies/columns/category_tree_children.py +127 -0
  67. clearskies/columns/category_tree_descendants.py +48 -0
  68. clearskies/columns/created.py +95 -0
  69. clearskies/columns/created_by_authorization_data.py +116 -0
  70. clearskies/columns/created_by_header.py +99 -0
  71. clearskies/columns/created_by_ip.py +92 -0
  72. clearskies/columns/created_by_routing_data.py +97 -0
  73. clearskies/columns/created_by_user_agent.py +92 -0
  74. clearskies/columns/date.py +234 -0
  75. clearskies/columns/datetime.py +282 -0
  76. clearskies/columns/email.py +76 -0
  77. clearskies/columns/float.py +153 -0
  78. clearskies/columns/has_many.py +505 -0
  79. clearskies/columns/has_many_self.py +56 -0
  80. clearskies/columns/has_one.py +14 -0
  81. clearskies/columns/integer.py +160 -0
  82. clearskies/columns/json.py +128 -0
  83. clearskies/columns/many_to_many_ids.py +337 -0
  84. clearskies/columns/many_to_many_ids_with_data.py +274 -0
  85. clearskies/columns/many_to_many_models.py +158 -0
  86. clearskies/columns/many_to_many_pivots.py +134 -0
  87. clearskies/columns/phone.py +159 -0
  88. clearskies/columns/select.py +92 -0
  89. clearskies/columns/string.py +102 -0
  90. clearskies/columns/timestamp.py +164 -0
  91. clearskies/columns/updated.py +110 -0
  92. clearskies/columns/uuid.py +86 -0
  93. clearskies/configs/README.md +105 -0
  94. clearskies/configs/__init__.py +162 -0
  95. clearskies/configs/actions.py +43 -0
  96. clearskies/configs/any.py +13 -0
  97. clearskies/configs/any_dict.py +22 -0
  98. clearskies/configs/any_dict_or_callable.py +23 -0
  99. clearskies/configs/authentication.py +23 -0
  100. clearskies/configs/authorization.py +23 -0
  101. clearskies/configs/boolean.py +16 -0
  102. clearskies/configs/boolean_or_callable.py +18 -0
  103. clearskies/configs/callable_config.py +18 -0
  104. clearskies/configs/columns.py +34 -0
  105. clearskies/configs/conditions.py +30 -0
  106. clearskies/configs/config.py +24 -0
  107. clearskies/configs/datetime.py +18 -0
  108. clearskies/configs/datetime_or_callable.py +19 -0
  109. clearskies/configs/endpoint.py +23 -0
  110. clearskies/configs/endpoint_list.py +29 -0
  111. clearskies/configs/float.py +16 -0
  112. clearskies/configs/float_or_callable.py +18 -0
  113. clearskies/configs/integer.py +16 -0
  114. clearskies/configs/integer_or_callable.py +18 -0
  115. clearskies/configs/joins.py +30 -0
  116. clearskies/configs/list_any_dict.py +30 -0
  117. clearskies/configs/list_any_dict_or_callable.py +31 -0
  118. clearskies/configs/model_class.py +35 -0
  119. clearskies/configs/model_column.py +65 -0
  120. clearskies/configs/model_columns.py +56 -0
  121. clearskies/configs/model_destination_name.py +25 -0
  122. clearskies/configs/model_to_id_column.py +43 -0
  123. clearskies/configs/readable_model_column.py +9 -0
  124. clearskies/configs/readable_model_columns.py +9 -0
  125. clearskies/configs/schema.py +23 -0
  126. clearskies/configs/searchable_model_columns.py +9 -0
  127. clearskies/configs/security_headers.py +39 -0
  128. clearskies/configs/select.py +26 -0
  129. clearskies/configs/select_list.py +47 -0
  130. clearskies/configs/string.py +29 -0
  131. clearskies/configs/string_dict.py +32 -0
  132. clearskies/configs/string_list.py +32 -0
  133. clearskies/configs/string_list_or_callable.py +35 -0
  134. clearskies/configs/string_or_callable.py +18 -0
  135. clearskies/configs/timedelta.py +18 -0
  136. clearskies/configs/timezone.py +18 -0
  137. clearskies/configs/url.py +23 -0
  138. clearskies/configs/validators.py +45 -0
  139. clearskies/configs/writeable_model_column.py +9 -0
  140. clearskies/configs/writeable_model_columns.py +9 -0
  141. clearskies/configurable.py +76 -0
  142. clearskies/contexts/__init__.py +11 -0
  143. clearskies/contexts/cli.py +117 -0
  144. clearskies/contexts/context.py +98 -0
  145. clearskies/contexts/wsgi.py +76 -0
  146. clearskies/contexts/wsgi_ref.py +82 -0
  147. clearskies/decorators.py +33 -0
  148. clearskies/di/__init__.py +15 -0
  149. clearskies/di/additional_config.py +130 -0
  150. clearskies/di/additional_config_auto_import.py +17 -0
  151. clearskies/di/di.py +973 -0
  152. clearskies/di/inject/__init__.py +23 -0
  153. clearskies/di/inject/by_class.py +21 -0
  154. clearskies/di/inject/by_name.py +18 -0
  155. clearskies/di/inject/di.py +13 -0
  156. clearskies/di/inject/environment.py +14 -0
  157. clearskies/di/inject/input_output.py +20 -0
  158. clearskies/di/inject/now.py +13 -0
  159. clearskies/di/inject/requests.py +13 -0
  160. clearskies/di/inject/secrets.py +14 -0
  161. clearskies/di/inject/utcnow.py +13 -0
  162. clearskies/di/inject/uuid.py +15 -0
  163. clearskies/di/injectable.py +29 -0
  164. clearskies/di/injectable_properties.py +131 -0
  165. clearskies/di/test_module/__init__.py +6 -0
  166. clearskies/di/test_module/another_module/__init__.py +2 -0
  167. clearskies/di/test_module/module_class.py +5 -0
  168. clearskies/end.py +183 -0
  169. clearskies/endpoint.py +1314 -0
  170. clearskies/endpoint_group.py +336 -0
  171. clearskies/endpoints/__init__.py +25 -0
  172. clearskies/endpoints/advanced_search.py +526 -0
  173. clearskies/endpoints/callable.py +388 -0
  174. clearskies/endpoints/create.py +205 -0
  175. clearskies/endpoints/delete.py +139 -0
  176. clearskies/endpoints/get.py +271 -0
  177. clearskies/endpoints/health_check.py +183 -0
  178. clearskies/endpoints/list.py +574 -0
  179. clearskies/endpoints/restful_api.py +427 -0
  180. clearskies/endpoints/schema.py +189 -0
  181. clearskies/endpoints/simple_search.py +286 -0
  182. clearskies/endpoints/update.py +193 -0
  183. clearskies/environment.py +104 -0
  184. clearskies/exceptions/__init__.py +19 -0
  185. clearskies/exceptions/authentication.py +2 -0
  186. clearskies/exceptions/authorization.py +2 -0
  187. clearskies/exceptions/client_error.py +2 -0
  188. clearskies/exceptions/input_errors.py +4 -0
  189. clearskies/exceptions/missing_dependency.py +2 -0
  190. clearskies/exceptions/moved_permanently.py +3 -0
  191. clearskies/exceptions/moved_temporarily.py +3 -0
  192. clearskies/exceptions/not_found.py +2 -0
  193. clearskies/functional/__init__.py +7 -0
  194. clearskies/functional/routing.py +92 -0
  195. clearskies/functional/string.py +112 -0
  196. clearskies/functional/validations.py +76 -0
  197. clearskies/input_outputs/__init__.py +13 -0
  198. clearskies/input_outputs/cli.py +171 -0
  199. clearskies/input_outputs/exceptions/__init__.py +2 -0
  200. clearskies/input_outputs/exceptions/cli_input_error.py +2 -0
  201. clearskies/input_outputs/exceptions/cli_not_found.py +2 -0
  202. clearskies/input_outputs/headers.py +45 -0
  203. clearskies/input_outputs/input_output.py +138 -0
  204. clearskies/input_outputs/programmatic.py +69 -0
  205. clearskies/input_outputs/py.typed +0 -0
  206. clearskies/input_outputs/wsgi.py +77 -0
  207. clearskies/model.py +1922 -0
  208. clearskies/py.typed +0 -0
  209. clearskies/query/__init__.py +12 -0
  210. clearskies/query/condition.py +223 -0
  211. clearskies/query/join.py +136 -0
  212. clearskies/query/query.py +196 -0
  213. clearskies/query/sort.py +27 -0
  214. clearskies/schema.py +82 -0
  215. clearskies/secrets/__init__.py +6 -0
  216. clearskies/secrets/additional_configs/__init__.py +32 -0
  217. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +61 -0
  218. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +160 -0
  219. clearskies/secrets/akeyless.py +182 -0
  220. clearskies/secrets/exceptions/__init__.py +1 -0
  221. clearskies/secrets/exceptions/not_found.py +2 -0
  222. clearskies/secrets/secrets.py +38 -0
  223. clearskies/security_header.py +15 -0
  224. clearskies/security_headers/__init__.py +11 -0
  225. clearskies/security_headers/cache_control.py +67 -0
  226. clearskies/security_headers/cors.py +50 -0
  227. clearskies/security_headers/csp.py +94 -0
  228. clearskies/security_headers/hsts.py +22 -0
  229. clearskies/security_headers/x_content_type_options.py +0 -0
  230. clearskies/security_headers/x_frame_options.py +0 -0
  231. clearskies/test_base.py +8 -0
  232. clearskies/typing.py +11 -0
  233. clearskies/validator.py +37 -0
  234. clearskies/validators/__init__.py +33 -0
  235. clearskies/validators/after_column.py +62 -0
  236. clearskies/validators/before_column.py +13 -0
  237. clearskies/validators/in_the_future.py +32 -0
  238. clearskies/validators/in_the_future_at_least.py +11 -0
  239. clearskies/validators/in_the_future_at_most.py +10 -0
  240. clearskies/validators/in_the_past.py +32 -0
  241. clearskies/validators/in_the_past_at_least.py +10 -0
  242. clearskies/validators/in_the_past_at_most.py +10 -0
  243. clearskies/validators/maximum_length.py +26 -0
  244. clearskies/validators/maximum_value.py +29 -0
  245. clearskies/validators/minimum_length.py +26 -0
  246. clearskies/validators/minimum_value.py +29 -0
  247. clearskies/validators/required.py +34 -0
  248. clearskies/validators/timedelta.py +59 -0
  249. clearskies/validators/unique.py +30 -0
  250. clear_skies-2.0.5.dist-info/RECORD +0 -4
  251. {clear_skies-2.0.5.dist-info → clear_skies-2.0.7.dist-info}/WHEEL +0 -0
  252. {clear_skies-2.0.5.dist-info → clear_skies-2.0.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ import clearskies.decorators
6
+ import clearskies.di
7
+ import clearskies.typing
8
+ from clearskies import configs
9
+ from clearskies.columns.string import String
10
+
11
+ if TYPE_CHECKING:
12
+ from clearskies import Model
13
+
14
+
15
+ class Uuid(String):
16
+ """
17
+ Populates the column with a UUID upon record creation.
18
+
19
+ This column really just has a very specific purpose: ids!
20
+
21
+ When used, it will automatically populate the column with a random UUID upon record creation.
22
+ It is not a writeable column, which means that you cannot expose it for write operations via an endpoint.
23
+
24
+ ```python
25
+ import clearskies
26
+
27
+
28
+ class MyModel(clearskies.Model):
29
+ backend = clearskies.backends.MemoryBackend()
30
+ id_column_name = "id"
31
+
32
+ id = clearskies.columns.Uuid()
33
+ name = clearskies.columns.String()
34
+
35
+
36
+ wsgi = clearskies.contexts.WsgiRef(
37
+ clearskies.endpoints.Create(
38
+ MyModel,
39
+ writeable_column_names=["name"],
40
+ readable_column_names=["id", "name"],
41
+ ),
42
+ )
43
+ wsgi()
44
+ ```
45
+
46
+ and when invoked:
47
+
48
+ ```bash
49
+ $ curl http://localhost:8080 -d '{"name": "John Doe"}' | jq
50
+ {
51
+ "status": "success",
52
+ "error": "",
53
+ "data": {
54
+ "id": "d4f23106-b48a-4dc5-9bf6-df61f6ca54f7",
55
+ "name": "John Doe"
56
+ },
57
+ "pagination": {},
58
+ "input_errors": {}
59
+ }
60
+ ```
61
+ """
62
+
63
+ is_writeable = configs.Boolean(default=False)
64
+ _descriptor_config_map = None
65
+
66
+ uuid = clearskies.di.inject.Uuid()
67
+
68
+ @clearskies.decorators.parameters_to_properties
69
+ def __init__(
70
+ self,
71
+ is_readable: bool = True,
72
+ is_searchable: bool = True,
73
+ is_temporary: bool = False,
74
+ on_change_pre_save: clearskies.typing.action | list[clearskies.typing.action] = [],
75
+ on_change_post_save: clearskies.typing.action | list[clearskies.typing.action] = [],
76
+ on_change_save_finished: clearskies.typing.action | list[clearskies.typing.action] = [],
77
+ ):
78
+ pass
79
+
80
+ def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
81
+ if model:
82
+ return data
83
+ data = {**data, self.name: str(self.uuid.uuid4())}
84
+ if self.on_change_pre_save:
85
+ data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
86
+ return data
@@ -0,0 +1,105 @@
1
+ # About
2
+
3
+ There are all sorts of things in clearskies that need to be configured - handlers, columns, models, etc... `configurable.Configurable` works together with the config classes to make this happen. The idea is that something that needs to be configured extends `Configurable` and then declares configs as properties. A simple example:
4
+
5
+ ```python
6
+ class ConfigurableThing(configurable.Configurable):
7
+ my_name = config.String(required=True)
8
+ is_required = config.Boolean(default=False)
9
+ some_option = config.Select(allowed_values=["option 1", "option 2", "option 3"])
10
+ ```
11
+
12
+ We've declared three configuration options for our `ConfigurableThing` class:
13
+
14
+ 1. `my_name` which is a string and must be set
15
+ 2. `is_required` which is a boolean and defaults to `False`
16
+ 3. `some_option` which is a string and must be one of `[None, "option 1", "option 2", "option 3"]`
17
+
18
+ However, our example above is missing one important thing: actually setting these values. They act like standard descriptors, so with just the above code you could:
19
+
20
+ ```python
21
+ configruable_thing = ConfigurableThing()
22
+ configruable_thing.my_name = "Jane Doe"
23
+ configruable_thing.is_required = True
24
+ configruable_thing.some_option = "option 2"
25
+ ```
26
+
27
+ Typically though you need a well defined way to set these values **AND** the class must call `super().finalize_and_validate_configuration()` once the configuration is set. This is because many of the validations are only possible after all the configs are set, so the configurable class treats the process of setting the configuration as a one-time, monolithic process: you set the configs, validate everything, and then use the config. It's *NOT* the goal to continually change the configuration for an object after creation. The simplest way to do this would be in the constructor:
28
+
29
+ ```python
30
+ class ConfigurableThing(configurable.Configurable):
31
+ my_name = config.String(required=True)
32
+ is_required = config.Boolean(default=False)
33
+ some_option = config.Select(allowed_values=["option 1", "option 2", "option 3"])
34
+
35
+ def __init__(self,
36
+ my_name: str,
37
+ is_required: bool=False,
38
+ some_option: str=None,
39
+ ):
40
+ self.my_name = my_name
41
+ self.is_required = is_required
42
+ self.some_option = some_option
43
+
44
+ super().finalize_and_validate_configuration()
45
+ ```
46
+
47
+ However, this doesn't always work because your class may be constructed via the dependency injection system. In this case, the constructor must be reserved for injecting the necessary dependencies. In addition, the object won't be constructed directly via code, so it's not possible to specify the configuration options there. In this case, config values can be shifted to the `configure` method and you can use a binding config:
48
+
49
+ ```python
50
+ class ConfigurableThing(configurable.Configurable):
51
+ my_name = config.String(required=True)
52
+ is_required = config.Boolean(default=False)
53
+ some_option = config.Select(allowed_values=["option 1", "option 2", "option 3"])
54
+
55
+ def __init__(self, some_dependency, other_dependency):
56
+ self.some_dependency = some_dependency
57
+ self.other_dependency = other_dependency
58
+
59
+ def configure(self,
60
+ my_name: str,
61
+ is_required: bool=False,
62
+ some_option: str=None,
63
+ ):
64
+ self.my_name = my_name
65
+ self.is_required = is_required
66
+ self.some_option = some_option
67
+
68
+ super().finalize_and_validate_configuration()
69
+
70
+ context = clearskies.contexts.cli(
71
+ SomeApplication,
72
+ bindings={
73
+ "configurable_thing": clearskies.bindings.Binding(ConfigurableThing, my_name="hey", is_required=Flase, some_option="option 2"),
74
+ }
75
+ )
76
+ ```
77
+
78
+ Note that we've lost our strong typing when creating the binding, but that can be fixed by extending the binding config:
79
+
80
+ ```python
81
+ class ConfigurableThing:
82
+ """ See above """
83
+
84
+ class ConfigurableThingBinding(clearskies.bindings.Binding):
85
+ def __init__(
86
+ self,
87
+ my_name: str,
88
+ is_required: bool=False,
89
+ some_option: str=None,
90
+ ):
91
+ self.object_class = ConfigurableThing
92
+ self.args = [my_name]
93
+ self.kwargs = {"is_required": is_required, "some_option": some_option}
94
+
95
+ context = clearskies.contexts.cli(
96
+ SomeApplication,
97
+ bindings={
98
+ "configurable_thing": ConfigurableThingBinding("hey", some_option="option 3")
99
+ }
100
+ )
101
+ ```
102
+
103
+ The primary example of classes that implement this pattern are the column config classes (`clearskies.columns.*`, but excluding `clearskies.columns.implementors`). In this case the config and implementation are completely separated, so the configuration is set in the constructor instead of a separate `configure` method.
104
+
105
+ Validators, actions, and handlers also use the above config pattern, but those use the `Binding` pattern and so make use of a `configure` method.
@@ -0,0 +1,162 @@
1
+ """
2
+ This module helps classes declare their configuration parameters via properties.
3
+
4
+ To use it, the class needs to include the clearskies.configs.Confirgurable in its parent chain and
5
+ then create properties as needed using the various classes in the clearskies.configs module. Each
6
+ class represents a specific "kind" of configuration, has typing declared to help while writing code,
7
+ and runtime checks to verify configs while the application is (preferably) booting.
8
+
9
+ Each config accepts a `required` and `default` kwarg to assist with validation/construction of
10
+ the configuration
11
+
12
+ Data is stored in the `_config` property on the instance.
13
+
14
+ Usage:
15
+
16
+ ```python
17
+ from clearskies import configs
18
+
19
+
20
+ class MyConfigurableClass(configs.Configurable):
21
+ name = configs.String()
22
+ age = configs.Integer(required=True)
23
+ property_with_default = configs.String(default="some value")
24
+
25
+ def __init__(self, name, age, optional=None):
26
+ self.name = name
27
+ self.age = age
28
+
29
+ # always call this after saving the confiuration values to the properties.
30
+ # It will fill in default values for any properties that have a default and
31
+ # are none, and it will raise a ValueError if there is a required property
32
+ # that does not have a value.
33
+ self.finalize_and_validate_configuration()
34
+
35
+
36
+ configured_thingie = MyConfigurableClass("Bob", 18)
37
+ print(configured_thingie.age) # prints: 18
38
+ print(configured_thingie.property_with_default) # prints: some value
39
+
40
+ invalid_thingie = MyConfigurableClass(18, 20) # raises a TypeError
41
+
42
+ also_invalid = MyConfigurableClass("", 18) # raises a ValueError
43
+ ```
44
+
45
+ Finally, parameters_to_properties is a decorator that will take any parameters passed into the
46
+ decorated function and assign them as instance properties. You can use this to skip some code,
47
+ especially if you have a lot of configuration parameters. In the above example, you could simplify
48
+ it as:
49
+
50
+ ```python
51
+ from clearskies import configs
52
+
53
+
54
+ class MyConfigurableClass(configs.Configurable):
55
+ name = configs.String()
56
+ age = configs.Integer(required=True)
57
+ property_with_default = configs.String(default="some value")
58
+
59
+ @clearskies.decorators()
60
+ def __init__(self, name: str, age: int, optional: string = None):
61
+ self.finalize_and_validate_configuration()
62
+ ```
63
+
64
+ """
65
+
66
+ import inspect
67
+
68
+ from .actions import Actions
69
+ from .any import Any
70
+ from .any_dict import AnyDict
71
+ from .any_dict_or_callable import AnyDictOrCallable
72
+ from .authentication import Authentication
73
+ from .authorization import Authorization
74
+ from .boolean import Boolean
75
+ from .boolean_or_callable import BooleanOrCallable
76
+ from .callable_config import Callable
77
+ from .columns import Columns
78
+ from .conditions import Conditions
79
+ from .config import Config
80
+ from .datetime import Datetime
81
+ from .datetime_or_callable import DatetimeOrCallable
82
+ from .endpoint import Endpoint
83
+ from .endpoint_list import EndpointList
84
+ from .float import Float
85
+ from .float_or_callable import FloatOrCallable
86
+ from .integer import Integer
87
+ from .integer_or_callable import IntegerOrCallable
88
+ from .joins import Joins
89
+ from .list_any_dict import ListAnyDict
90
+ from .list_any_dict_or_callable import ListAnyDictOrCallable
91
+ from .model_class import ModelClass
92
+ from .model_column import ModelColumn
93
+ from .model_columns import ModelColumns
94
+ from .model_destination_name import ModelDestinationName
95
+ from .model_to_id_column import ModelToIdColumn
96
+ from .readable_model_column import ReadableModelColumn
97
+ from .readable_model_columns import ReadableModelColumns
98
+ from .schema import Schema
99
+ from .searchable_model_columns import SearchableModelColumns
100
+ from .security_headers import SecurityHeaders
101
+ from .select import Select
102
+ from .select_list import SelectList
103
+ from .string import String
104
+ from .string_dict import StringDict
105
+ from .string_list import StringList
106
+ from .string_list_or_callable import StringListOrCallable
107
+ from .string_or_callable import StringOrCallable
108
+ from .timedelta import Timedelta
109
+ from .timezone import Timezone
110
+ from .url import Url
111
+ from .validators import Validators
112
+ from .writeable_model_column import WriteableModelColumn
113
+ from .writeable_model_columns import WriteableModelColumns
114
+
115
+ __all__ = [
116
+ "Actions",
117
+ "Any",
118
+ "AnyDict",
119
+ "AnyDictOrCallable",
120
+ "Authentication",
121
+ "Authorization",
122
+ "Boolean",
123
+ "BooleanOrCallable",
124
+ "Callable",
125
+ "Columns",
126
+ "Conditions",
127
+ "Config",
128
+ "Datetime",
129
+ "DatetimeOrCallable",
130
+ "Endpoint",
131
+ "EndpointList",
132
+ "Float",
133
+ "FloatOrCallable",
134
+ "Joins",
135
+ "Integer",
136
+ "IntegerOrCallable",
137
+ "ListAnyDict",
138
+ "ListAnyDictOrCallable",
139
+ "ModelClass",
140
+ "ModelColumn",
141
+ "ModelColumns",
142
+ "ModelToIdColumn",
143
+ "ModelDestinationName",
144
+ "ReadableModelColumn",
145
+ "ReadableModelColumns",
146
+ "Schema",
147
+ "SearchableModelColumns",
148
+ "SecurityHeaders",
149
+ "Select",
150
+ "SelectList",
151
+ "String",
152
+ "StringDict",
153
+ "StringList",
154
+ "StringListOrCallable",
155
+ "StringOrCallable",
156
+ "Timedelta",
157
+ "Timezone",
158
+ "Url",
159
+ "Validators",
160
+ "WriteableModelColumn",
161
+ "WriteableModelColumns",
162
+ ]
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies import action
6
+ from clearskies.configs import config
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import typing
10
+
11
+
12
+ class Actions(config.Config):
13
+ """
14
+ Action config.
15
+
16
+ A config that accepts various things that are accepted as actions in model lifecycle hooks:
17
+
18
+ 1. A callable (which should accept `model` as a parameter)
19
+ 2. An instance of clearskies.actions.Action
20
+ 3. A list containing any combination of the above
21
+
22
+ Incoming values are normalized to a list so that a list always comes out even if a non-list is provided.
23
+ """
24
+
25
+ def __set__(self, instance, value: typing.action | list[typing.action]):
26
+ if not isinstance(value, list):
27
+ value = [value]
28
+
29
+ for index, item in enumerate(value):
30
+ if callable(item) or isinstance(item, action.Action):
31
+ continue
32
+
33
+ error_prefix = self._error_prefix(instance)
34
+ raise TypeError(
35
+ f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1} when a callable or Action is required"
36
+ )
37
+
38
+ instance._set_config(self, [*value])
39
+
40
+ def __get__(self, instance, parent) -> list[typing.action]:
41
+ if not instance:
42
+ return self # type: ignore
43
+ return instance._get_config(self)
@@ -0,0 +1,13 @@
1
+ from typing import Any as AnyType
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class Any(config.Config):
7
+ def __set__(self, instance, value: AnyType):
8
+ instance._set_config(self, value)
9
+
10
+ def __get__(self, instance, parent) -> AnyType:
11
+ if not instance:
12
+ return self # type: ignore
13
+ return instance._get_config(self)
@@ -0,0 +1,22 @@
1
+ from typing import Any
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class AnyDict(config.Config):
7
+ def __set__(self, instance, value: dict[str, Any]):
8
+ if not isinstance(value, dict):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a dictionary."
12
+ )
13
+ for key in value.keys():
14
+ if not isinstance(key, str):
15
+ error_prefix = self._error_prefix(instance)
16
+ raise TypeError(f"{error_prefix} attempt to set a dictionary with a non-string key.")
17
+ instance._set_config(self, value)
18
+
19
+ def __get__(self, instance, parent) -> dict[str, Any]:
20
+ if not instance:
21
+ return self # type: ignore
22
+ return instance._get_config(self)
@@ -0,0 +1,23 @@
1
+ from typing import Any, Callable
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class AnyDictOrCallable(config.Config):
7
+ def __set__(self, instance, value: dict[str, Any] | Callable[..., dict[str, Any]]):
8
+ if not isinstance(value, dict) and not callable(value):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a dictionary or a callable."
12
+ )
13
+ if isinstance(value, dict):
14
+ for key, val in value.items():
15
+ if not isinstance(key, str):
16
+ error_prefix = self._error_prefix(instance)
17
+ raise TypeError(f"{error_prefix} attempt to set a dictionary with a non-string key.")
18
+ instance._set_config(self, value)
19
+
20
+ def __get__(self, instance, parent) -> dict[str, Any] | Callable[..., dict[str, Any]]:
21
+ if not instance:
22
+ return self # type: ignore
23
+ return instance._get_config(self)
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies.configs import config
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies.authentication import Authentication as AuthenticationType
9
+
10
+
11
+ class Authentication(config.Config):
12
+ def __set__(self, instance, value: AuthenticationType):
13
+ if not hasattr(value, "authenticate"):
14
+ error_prefix = self._error_prefix(instance)
15
+ raise TypeError(
16
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to parameter that requires an instance of clearskies.authentication.Authentication."
17
+ )
18
+ instance._set_config(self, value)
19
+
20
+ def __get__(self, instance, parent) -> AuthenticationType:
21
+ if not instance:
22
+ return self # type: ignore
23
+ return instance._get_config(self)
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies.configs import config
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies.authentication import Authorization as AuthorizationType
9
+
10
+
11
+ class Authorization(config.Config):
12
+ def __set__(self, instance, value: AuthorizationType):
13
+ if not hasattr(value, "gate"):
14
+ error_prefix = self._error_prefix(instance)
15
+ raise TypeError(
16
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to parameter that requires an instance of clearskies.authentication.Authorization."
17
+ )
18
+ instance._set_config(self, value)
19
+
20
+ def __get__(self, instance, parent) -> AuthorizationType:
21
+ if not instance:
22
+ return self # type: ignore
23
+ return instance._get_config(self)
@@ -0,0 +1,16 @@
1
+ from clearskies.configs import config
2
+
3
+
4
+ class Boolean(config.Config):
5
+ def __set__(self, instance, value: bool):
6
+ if not isinstance(value, bool):
7
+ error_prefix = self._error_prefix(instance)
8
+ raise TypeError(
9
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a boolean."
10
+ )
11
+ instance._set_config(self, value)
12
+
13
+ def __get__(self, instance, parent) -> bool:
14
+ if not instance:
15
+ return self # type: ignore
16
+ return instance._get_config(self)
@@ -0,0 +1,18 @@
1
+ from typing import Callable
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class BooleanOrCallable(config.Config):
7
+ def __set__(self, instance, value: bool | Callable[..., bool]):
8
+ if not isinstance(value, bool) and not callable(value):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a boolean or a callable."
12
+ )
13
+ instance._set_config(self, value)
14
+
15
+ def __get__(self, instance, parent) -> bool | Callable[..., bool]:
16
+ if not instance:
17
+ return self # type: ignore
18
+ return instance._get_config(self)
@@ -0,0 +1,18 @@
1
+ from typing import Callable as CallableType
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class Callable(config.Config):
7
+ def __set__(self, instance, value: CallableType):
8
+ if not callable(value):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a Callable."
12
+ )
13
+ instance._set_config(self, value)
14
+
15
+ def __get__(self, instance, parent) -> CallableType | None:
16
+ if not instance:
17
+ return self # type: ignore
18
+ return instance._get_config(self)
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies.configs import config
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies.column import Column
9
+
10
+
11
+ class Columns(config.Config):
12
+ """This is for a configuration that should be a dictionary of columns with the key being the column name."""
13
+
14
+ def __set__(self, instance, value: dict[str, Column]):
15
+ if value is None:
16
+ return
17
+
18
+ if not isinstance(value, dict):
19
+ error_prefix = self._error_prefix(instance)
20
+ raise TypeError(
21
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a dictionary with columns"
22
+ )
23
+ for index, item in enumerate(value.values()):
24
+ if not hasattr(item, "on_change_pre_save"):
25
+ error_prefix = self._error_prefix(instance)
26
+ raise TypeError(
27
+ f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1}. A column was expected."
28
+ )
29
+ instance._set_config(self, value)
30
+
31
+ def __get__(self, instance, parent) -> dict[str, Column]:
32
+ if not instance:
33
+ return self # type: ignore
34
+ return instance._get_config(self)
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies.configs import config
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies import typing
9
+
10
+
11
+ class Conditions(config.Config):
12
+ def __set__(self, instance, value: typing.condition | list[typing.condition]):
13
+ if not isinstance(value, list):
14
+ value = [value]
15
+
16
+ for index, item in enumerate(value):
17
+ if callable(item) or isinstance(item, str) or hasattr(item, "_raw_condition"):
18
+ continue
19
+
20
+ error_prefix = self._error_prefix(instance)
21
+ raise TypeError(
22
+ f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1} when a string, condition, callable is required" # type: ignore
23
+ )
24
+
25
+ instance._set_config(self, [*value])
26
+
27
+ def __get__(self, instance, parent) -> list[typing.condition]:
28
+ if not instance:
29
+ return self # type: ignore
30
+ return instance._get_config(self)
@@ -0,0 +1,24 @@
1
+ from typing import Any
2
+
3
+
4
+ class Config:
5
+ def __init__(self, required: bool = False, default: Any = None):
6
+ self.required = required
7
+ self.default = default
8
+
9
+ def _error_prefix(self, instance) -> str:
10
+ name = instance._descriptor_to_name(self)
11
+ class_name = instance.__class__.__name__
12
+ return f"Error with '{class_name}.{name}':"
13
+
14
+ def finalize_and_validate_configuration(self, instance):
15
+ if self.default:
16
+ try:
17
+ instance._get_config(self)
18
+ except KeyError:
19
+ instance._set_config(self.default)
20
+
21
+ if self.required and instance._get_config(self) is None:
22
+ name = instance._descriptor_to_name(self)
23
+ prefix = self._error_prefix(instance)
24
+ raise ValueError("{prefix} {name} is a required configuration setting, but no value was set")
@@ -0,0 +1,18 @@
1
+ import datetime
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class Datetime(config.Config):
7
+ def __set__(self, instance, value: datetime.datetime):
8
+ if not isinstance(value, datetime.datetime):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a datetime object."
12
+ )
13
+ instance._set_config(self, value)
14
+
15
+ def __get__(self, instance, parent) -> datetime.datetime:
16
+ if not instance:
17
+ return self # type: ignore
18
+ return instance._get_config(self)