clear-skies 2.0.27__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 (270) hide show
  1. clear_skies-2.0.27.dist-info/METADATA +78 -0
  2. clear_skies-2.0.27.dist-info/RECORD +270 -0
  3. clear_skies-2.0.27.dist-info/WHEEL +4 -0
  4. clear_skies-2.0.27.dist-info/licenses/LICENSE +7 -0
  5. clearskies/__init__.py +69 -0
  6. clearskies/action.py +7 -0
  7. clearskies/authentication/__init__.py +15 -0
  8. clearskies/authentication/authentication.py +44 -0
  9. clearskies/authentication/authorization.py +23 -0
  10. clearskies/authentication/authorization_pass_through.py +22 -0
  11. clearskies/authentication/jwks.py +165 -0
  12. clearskies/authentication/public.py +5 -0
  13. clearskies/authentication/secret_bearer.py +551 -0
  14. clearskies/autodoc/__init__.py +8 -0
  15. clearskies/autodoc/formats/__init__.py +5 -0
  16. clearskies/autodoc/formats/oai3_json/__init__.py +7 -0
  17. clearskies/autodoc/formats/oai3_json/oai3_json.py +87 -0
  18. clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +15 -0
  19. clearskies/autodoc/formats/oai3_json/parameter.py +35 -0
  20. clearskies/autodoc/formats/oai3_json/request.py +68 -0
  21. clearskies/autodoc/formats/oai3_json/response.py +28 -0
  22. clearskies/autodoc/formats/oai3_json/schema/__init__.py +11 -0
  23. clearskies/autodoc/formats/oai3_json/schema/array.py +9 -0
  24. clearskies/autodoc/formats/oai3_json/schema/default.py +13 -0
  25. clearskies/autodoc/formats/oai3_json/schema/enum.py +7 -0
  26. clearskies/autodoc/formats/oai3_json/schema/object.py +35 -0
  27. clearskies/autodoc/formats/oai3_json/test.json +1985 -0
  28. clearskies/autodoc/py.typed +0 -0
  29. clearskies/autodoc/request/__init__.py +15 -0
  30. clearskies/autodoc/request/header.py +6 -0
  31. clearskies/autodoc/request/json_body.py +6 -0
  32. clearskies/autodoc/request/parameter.py +8 -0
  33. clearskies/autodoc/request/request.py +47 -0
  34. clearskies/autodoc/request/url_parameter.py +6 -0
  35. clearskies/autodoc/request/url_path.py +6 -0
  36. clearskies/autodoc/response/__init__.py +5 -0
  37. clearskies/autodoc/response/response.py +9 -0
  38. clearskies/autodoc/schema/__init__.py +31 -0
  39. clearskies/autodoc/schema/array.py +10 -0
  40. clearskies/autodoc/schema/base64.py +8 -0
  41. clearskies/autodoc/schema/boolean.py +5 -0
  42. clearskies/autodoc/schema/date.py +5 -0
  43. clearskies/autodoc/schema/datetime.py +5 -0
  44. clearskies/autodoc/schema/double.py +5 -0
  45. clearskies/autodoc/schema/enum.py +17 -0
  46. clearskies/autodoc/schema/integer.py +6 -0
  47. clearskies/autodoc/schema/long.py +5 -0
  48. clearskies/autodoc/schema/number.py +6 -0
  49. clearskies/autodoc/schema/object.py +13 -0
  50. clearskies/autodoc/schema/password.py +5 -0
  51. clearskies/autodoc/schema/schema.py +11 -0
  52. clearskies/autodoc/schema/string.py +5 -0
  53. clearskies/backends/__init__.py +67 -0
  54. clearskies/backends/api_backend.py +1194 -0
  55. clearskies/backends/backend.py +137 -0
  56. clearskies/backends/cursor_backend.py +339 -0
  57. clearskies/backends/graphql_backend.py +977 -0
  58. clearskies/backends/memory_backend.py +794 -0
  59. clearskies/backends/secrets_backend.py +100 -0
  60. clearskies/clients/__init__.py +5 -0
  61. clearskies/clients/graphql_client.py +182 -0
  62. clearskies/column.py +1221 -0
  63. clearskies/columns/__init__.py +71 -0
  64. clearskies/columns/audit.py +306 -0
  65. clearskies/columns/belongs_to_id.py +478 -0
  66. clearskies/columns/belongs_to_model.py +145 -0
  67. clearskies/columns/belongs_to_self.py +109 -0
  68. clearskies/columns/boolean.py +110 -0
  69. clearskies/columns/category_tree.py +274 -0
  70. clearskies/columns/category_tree_ancestors.py +51 -0
  71. clearskies/columns/category_tree_children.py +125 -0
  72. clearskies/columns/category_tree_descendants.py +48 -0
  73. clearskies/columns/created.py +92 -0
  74. clearskies/columns/created_by_authorization_data.py +114 -0
  75. clearskies/columns/created_by_header.py +103 -0
  76. clearskies/columns/created_by_ip.py +90 -0
  77. clearskies/columns/created_by_routing_data.py +102 -0
  78. clearskies/columns/created_by_user_agent.py +89 -0
  79. clearskies/columns/date.py +232 -0
  80. clearskies/columns/datetime.py +284 -0
  81. clearskies/columns/email.py +78 -0
  82. clearskies/columns/float.py +149 -0
  83. clearskies/columns/has_many.py +552 -0
  84. clearskies/columns/has_many_self.py +62 -0
  85. clearskies/columns/has_one.py +21 -0
  86. clearskies/columns/integer.py +158 -0
  87. clearskies/columns/json.py +126 -0
  88. clearskies/columns/many_to_many_ids.py +335 -0
  89. clearskies/columns/many_to_many_ids_with_data.py +281 -0
  90. clearskies/columns/many_to_many_models.py +163 -0
  91. clearskies/columns/many_to_many_pivots.py +132 -0
  92. clearskies/columns/phone.py +162 -0
  93. clearskies/columns/select.py +95 -0
  94. clearskies/columns/string.py +102 -0
  95. clearskies/columns/timestamp.py +164 -0
  96. clearskies/columns/updated.py +107 -0
  97. clearskies/columns/uuid.py +83 -0
  98. clearskies/configs/README.md +105 -0
  99. clearskies/configs/__init__.py +170 -0
  100. clearskies/configs/actions.py +43 -0
  101. clearskies/configs/any.py +15 -0
  102. clearskies/configs/any_dict.py +24 -0
  103. clearskies/configs/any_dict_or_callable.py +25 -0
  104. clearskies/configs/authentication.py +23 -0
  105. clearskies/configs/authorization.py +23 -0
  106. clearskies/configs/boolean.py +18 -0
  107. clearskies/configs/boolean_or_callable.py +20 -0
  108. clearskies/configs/callable_config.py +20 -0
  109. clearskies/configs/columns.py +34 -0
  110. clearskies/configs/conditions.py +30 -0
  111. clearskies/configs/config.py +26 -0
  112. clearskies/configs/datetime.py +20 -0
  113. clearskies/configs/datetime_or_callable.py +21 -0
  114. clearskies/configs/email.py +10 -0
  115. clearskies/configs/email_list.py +17 -0
  116. clearskies/configs/email_list_or_callable.py +17 -0
  117. clearskies/configs/email_or_email_list_or_callable.py +59 -0
  118. clearskies/configs/endpoint.py +23 -0
  119. clearskies/configs/endpoint_list.py +29 -0
  120. clearskies/configs/float.py +18 -0
  121. clearskies/configs/float_or_callable.py +20 -0
  122. clearskies/configs/headers.py +28 -0
  123. clearskies/configs/integer.py +18 -0
  124. clearskies/configs/integer_or_callable.py +20 -0
  125. clearskies/configs/joins.py +30 -0
  126. clearskies/configs/list_any_dict.py +32 -0
  127. clearskies/configs/list_any_dict_or_callable.py +33 -0
  128. clearskies/configs/model_class.py +35 -0
  129. clearskies/configs/model_column.py +67 -0
  130. clearskies/configs/model_columns.py +58 -0
  131. clearskies/configs/model_destination_name.py +26 -0
  132. clearskies/configs/model_to_id_column.py +45 -0
  133. clearskies/configs/readable_model_column.py +11 -0
  134. clearskies/configs/readable_model_columns.py +11 -0
  135. clearskies/configs/schema.py +23 -0
  136. clearskies/configs/searchable_model_columns.py +11 -0
  137. clearskies/configs/security_headers.py +39 -0
  138. clearskies/configs/select.py +28 -0
  139. clearskies/configs/select_list.py +49 -0
  140. clearskies/configs/string.py +31 -0
  141. clearskies/configs/string_dict.py +34 -0
  142. clearskies/configs/string_list.py +47 -0
  143. clearskies/configs/string_list_or_callable.py +48 -0
  144. clearskies/configs/string_or_callable.py +18 -0
  145. clearskies/configs/timedelta.py +20 -0
  146. clearskies/configs/timezone.py +20 -0
  147. clearskies/configs/url.py +25 -0
  148. clearskies/configs/validators.py +45 -0
  149. clearskies/configs/writeable_model_column.py +11 -0
  150. clearskies/configs/writeable_model_columns.py +11 -0
  151. clearskies/configurable.py +78 -0
  152. clearskies/contexts/__init__.py +11 -0
  153. clearskies/contexts/cli.py +130 -0
  154. clearskies/contexts/context.py +99 -0
  155. clearskies/contexts/wsgi.py +79 -0
  156. clearskies/contexts/wsgi_ref.py +87 -0
  157. clearskies/cursors/__init__.py +10 -0
  158. clearskies/cursors/cursor.py +161 -0
  159. clearskies/cursors/from_environment/__init__.py +5 -0
  160. clearskies/cursors/from_environment/mysql.py +51 -0
  161. clearskies/cursors/from_environment/postgresql.py +49 -0
  162. clearskies/cursors/from_environment/sqlite.py +35 -0
  163. clearskies/cursors/mysql.py +61 -0
  164. clearskies/cursors/postgresql.py +61 -0
  165. clearskies/cursors/sqlite.py +62 -0
  166. clearskies/decorators.py +33 -0
  167. clearskies/decorators.pyi +10 -0
  168. clearskies/di/__init__.py +15 -0
  169. clearskies/di/additional_config.py +130 -0
  170. clearskies/di/additional_config_auto_import.py +17 -0
  171. clearskies/di/di.py +948 -0
  172. clearskies/di/inject/__init__.py +25 -0
  173. clearskies/di/inject/akeyless_sdk.py +16 -0
  174. clearskies/di/inject/by_class.py +24 -0
  175. clearskies/di/inject/by_name.py +22 -0
  176. clearskies/di/inject/di.py +16 -0
  177. clearskies/di/inject/environment.py +15 -0
  178. clearskies/di/inject/input_output.py +19 -0
  179. clearskies/di/inject/logger.py +16 -0
  180. clearskies/di/inject/now.py +16 -0
  181. clearskies/di/inject/requests.py +16 -0
  182. clearskies/di/inject/secrets.py +15 -0
  183. clearskies/di/inject/utcnow.py +16 -0
  184. clearskies/di/inject/uuid.py +16 -0
  185. clearskies/di/injectable.py +32 -0
  186. clearskies/di/injectable_properties.py +131 -0
  187. clearskies/end.py +219 -0
  188. clearskies/endpoint.py +1303 -0
  189. clearskies/endpoint_group.py +333 -0
  190. clearskies/endpoints/__init__.py +25 -0
  191. clearskies/endpoints/advanced_search.py +519 -0
  192. clearskies/endpoints/callable.py +382 -0
  193. clearskies/endpoints/create.py +201 -0
  194. clearskies/endpoints/delete.py +133 -0
  195. clearskies/endpoints/get.py +267 -0
  196. clearskies/endpoints/health_check.py +181 -0
  197. clearskies/endpoints/list.py +567 -0
  198. clearskies/endpoints/restful_api.py +417 -0
  199. clearskies/endpoints/schema.py +185 -0
  200. clearskies/endpoints/simple_search.py +279 -0
  201. clearskies/endpoints/update.py +188 -0
  202. clearskies/environment.py +106 -0
  203. clearskies/exceptions/__init__.py +19 -0
  204. clearskies/exceptions/authentication.py +2 -0
  205. clearskies/exceptions/authorization.py +2 -0
  206. clearskies/exceptions/client_error.py +2 -0
  207. clearskies/exceptions/input_errors.py +4 -0
  208. clearskies/exceptions/missing_dependency.py +2 -0
  209. clearskies/exceptions/moved_permanently.py +3 -0
  210. clearskies/exceptions/moved_temporarily.py +3 -0
  211. clearskies/exceptions/not_found.py +2 -0
  212. clearskies/functional/__init__.py +7 -0
  213. clearskies/functional/json.py +47 -0
  214. clearskies/functional/routing.py +92 -0
  215. clearskies/functional/string.py +112 -0
  216. clearskies/functional/validations.py +76 -0
  217. clearskies/input_outputs/__init__.py +13 -0
  218. clearskies/input_outputs/cli.py +157 -0
  219. clearskies/input_outputs/exceptions/__init__.py +7 -0
  220. clearskies/input_outputs/exceptions/cli_input_error.py +2 -0
  221. clearskies/input_outputs/exceptions/cli_not_found.py +2 -0
  222. clearskies/input_outputs/headers.py +54 -0
  223. clearskies/input_outputs/input_output.py +116 -0
  224. clearskies/input_outputs/programmatic.py +62 -0
  225. clearskies/input_outputs/py.typed +0 -0
  226. clearskies/input_outputs/wsgi.py +80 -0
  227. clearskies/loggable.py +19 -0
  228. clearskies/model.py +2039 -0
  229. clearskies/py.typed +0 -0
  230. clearskies/query/__init__.py +12 -0
  231. clearskies/query/condition.py +228 -0
  232. clearskies/query/join.py +136 -0
  233. clearskies/query/query.py +195 -0
  234. clearskies/query/sort.py +27 -0
  235. clearskies/schema.py +82 -0
  236. clearskies/secrets/__init__.py +7 -0
  237. clearskies/secrets/additional_configs/__init__.py +32 -0
  238. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +61 -0
  239. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +160 -0
  240. clearskies/secrets/akeyless.py +507 -0
  241. clearskies/secrets/exceptions/__init__.py +7 -0
  242. clearskies/secrets/exceptions/not_found_error.py +2 -0
  243. clearskies/secrets/exceptions/permissions_error.py +2 -0
  244. clearskies/secrets/secrets.py +39 -0
  245. clearskies/security_header.py +17 -0
  246. clearskies/security_headers/__init__.py +11 -0
  247. clearskies/security_headers/cache_control.py +68 -0
  248. clearskies/security_headers/cors.py +51 -0
  249. clearskies/security_headers/csp.py +95 -0
  250. clearskies/security_headers/hsts.py +23 -0
  251. clearskies/security_headers/x_content_type_options.py +0 -0
  252. clearskies/security_headers/x_frame_options.py +0 -0
  253. clearskies/typing.py +11 -0
  254. clearskies/validator.py +36 -0
  255. clearskies/validators/__init__.py +33 -0
  256. clearskies/validators/after_column.py +61 -0
  257. clearskies/validators/before_column.py +15 -0
  258. clearskies/validators/in_the_future.py +29 -0
  259. clearskies/validators/in_the_future_at_least.py +13 -0
  260. clearskies/validators/in_the_future_at_most.py +12 -0
  261. clearskies/validators/in_the_past.py +29 -0
  262. clearskies/validators/in_the_past_at_least.py +12 -0
  263. clearskies/validators/in_the_past_at_most.py +12 -0
  264. clearskies/validators/maximum_length.py +25 -0
  265. clearskies/validators/maximum_value.py +28 -0
  266. clearskies/validators/minimum_length.py +25 -0
  267. clearskies/validators/minimum_value.py +28 -0
  268. clearskies/validators/required.py +32 -0
  269. clearskies/validators/timedelta.py +58 -0
  270. clearskies/validators/unique.py +28 -0
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Self, overload
4
+
5
+ from clearskies.columns import CategoryTreeChildren
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies import Model
9
+
10
+
11
+ class CategoryTreeDescendants(CategoryTreeChildren):
12
+ """
13
+ Return all descendants from a category tree column.
14
+
15
+ See the CategoryTree column for usage examples.
16
+
17
+ The descendants are the recursive children of a given category. So, given the following tree:
18
+
19
+ ```
20
+ Root/
21
+ ├─ Sub/
22
+ │ ├─ Sub Sub/
23
+ │ │ ├─ Sub Sub Sub/
24
+ ├─ Another Child/
25
+
26
+ The descendants of `Root` are `["Sub", "Sub Sub", "Sub Sub Sub", "Another Child"]`.
27
+ """
28
+
29
+ _descriptor_config_map = None
30
+
31
+ @overload
32
+ def __get__(self, instance: None, cls: type[Model]) -> Self:
33
+ pass
34
+
35
+ @overload
36
+ def __get__(self, instance: Model, cls: type[Model]) -> Model:
37
+ pass
38
+
39
+ def __get__(self, model, cls):
40
+ if model is None:
41
+ self.model_class = cls
42
+ return self # type: ignore
43
+
44
+ # this makes sure we're initialized
45
+ if "name" not in self._config: # type: ignore
46
+ model.get_columns()
47
+
48
+ return self.relatives(model, include_all=True)
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs, decorators
6
+ from clearskies.columns.datetime import Datetime
7
+ from clearskies.di import inject
8
+
9
+ if TYPE_CHECKING:
10
+ from clearskies import Model, typing
11
+
12
+
13
+ class Created(Datetime):
14
+ """
15
+ The created column records the time that a record is created.
16
+
17
+ This will always populate the column when the model is first created. If you attempt to set a value
18
+ to this column on create then it will be overwritten.
19
+
20
+ ```python
21
+ import clearskies
22
+
23
+
24
+ class MyModel(clearskies.Model):
25
+ backend = clearskies.backends.MemoryBackend()
26
+ id_column_name = "id"
27
+ id = clearskies.columns.Uuid()
28
+ name = clearskies.columns.String()
29
+ created = clearskies.columns.Created()
30
+
31
+
32
+ cli = clearskies.contexts.Cli(
33
+ clearskies.endpoints.Callable(
34
+ lambda my_models: my_models.create({"name": "An Example"}),
35
+ model_class=MyModel,
36
+ readable_column_names=["id", "name", "created"],
37
+ ),
38
+ classes=[MyModel],
39
+ )
40
+ cli()
41
+ ```
42
+
43
+ And if you execute this you'll see that the `created` column was automatically populated:
44
+
45
+ ```json
46
+ {
47
+ "status": "success",
48
+ "error": "",
49
+ "data": {
50
+ "id": "c54d74ac-5282-439e-af4f-23efb9ba96d4",
51
+ "name": "An Example",
52
+ "created": "2025-05-09T19:58:43+00:00",
53
+ },
54
+ "pagination": {},
55
+ "input_errors": {},
56
+ }
57
+ ```
58
+ """
59
+
60
+ """
61
+ Created fields are never writeable because they always set the created time automatically.
62
+ """
63
+ is_writeable = configs.Boolean(default=False)
64
+ _descriptor_config_map = None
65
+
66
+ now = inject.Now()
67
+
68
+ @decorators.parameters_to_properties
69
+ def __init__(
70
+ self,
71
+ date_format: str = "%Y-%m-%d %H:%M:%S",
72
+ in_utc: bool = True,
73
+ backend_default: str = "0000-00-00 00:00:00",
74
+ is_readable: bool = True,
75
+ is_searchable: bool = True,
76
+ is_temporary: bool = False,
77
+ on_change_pre_save: typing.action | list[typing.action] = [],
78
+ on_change_post_save: typing.action | list[typing.action] = [],
79
+ on_change_save_finished: typing.action | list[typing.action] = [],
80
+ ):
81
+ pass
82
+
83
+ def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
84
+ if model:
85
+ return data
86
+ now = self.now
87
+ if self.timezone_aware:
88
+ now = now.astimezone(self.timezone)
89
+ data = {**data, self.name: now}
90
+ if self.on_change_pre_save:
91
+ data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
92
+ return data
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies import configs, decorators
6
+ from clearskies.columns.string import String
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import typing
10
+
11
+
12
+ class CreatedByAuthorizationData(String):
13
+ """
14
+ This column will automatically take data from the authorization data attached to a request and store it in the model upon creation.
15
+
16
+ If authorization data isn't available from the context being executed, then you may end up with an error
17
+ (depending on the context). This is a good thing if you are trying to consistely provide audit information,
18
+ but may be a problem if your model creation needs to happen more flexibly. Obviously, a pre-requisite for using this
19
+ class is to have an authentication class attached to your endpoint which populates the authorization data.
20
+
21
+ NOTE: columns generally also have the `created_by_source_type` and `created_by_source_key` properties that perform
22
+ this exact same function. Why do we have those properties and this column? This column works well if we have
23
+ some simple string values that we want to always pull from the authorization data (email, for instance). The
24
+ properties work better if you need to pull authorization data but it's not just a string type. An example might
25
+ be if you wanted to pull the user id out of the authorziation data to populate a `BelongsToId` column. You wouldn't
26
+ use this column because it can't provide all the functionality related to `BelongsToId`, so instead you would
27
+ use the `BelongsToId` column and set `created_by_source_type` to `authorization_data` and `created_by_source_key` to `user_id`.
28
+ Example usage:
29
+
30
+ ```python
31
+ class MyModel(clearskies.Model):
32
+ backend = clearskies.backends.MemoryBackend()
33
+ id_column_name = "id"
34
+
35
+ id = clearskies.columns.Uuid()
36
+ name = clearskies.columns.String()
37
+ organization_id = clearskies.columns.CreatedByAuthorizationData("organization_id")
38
+
39
+
40
+ class MyAuthentication(clearskies.authentication.Authentication):
41
+ def authenticate(self, input_output) -> bool:
42
+ # Authenticate the user!
43
+ #
44
+ # This is where you would normally authenticate the user and provide any data about them to the
45
+ # authorization system. This might mean validating a JWT and then providing the claims as authorization data,
46
+ # or looking up a session id and providing the session data to the authorization system. In this case,
47
+ # we're just going to return a fixed id for our organization.
48
+ input_output.authorization_data = {
49
+ "organization_id": "my-super-awesome-organization",
50
+ }
51
+ return True
52
+
53
+
54
+ cli = clearskies.contexts.Cli(
55
+ clearskies.endpoints.Create(
56
+ MyModel,
57
+ writeable_column_names=["name"],
58
+ readable_column_names=["id", "name", "organization_id"],
59
+ authentication=MyAuthentication(),
60
+ ),
61
+ classes=[MyModel],
62
+ )
63
+ cli()
64
+ ```
65
+
66
+ And running this will give you something like:
67
+
68
+ ```json
69
+ {
70
+ "status": "success",
71
+ "error": "",
72
+ "data": {
73
+ "id": "49816ea4-0956-461e-abd4-03dbde845ba9",
74
+ "name": "Bob",
75
+ "organization_id": "my-super-awesome-organization",
76
+ },
77
+ "pagination": {},
78
+ "input_errors": {},
79
+ }
80
+ ```
81
+ """
82
+
83
+ """
84
+ The key inside the authorization data that should be pulled into the column value when the record is created
85
+ """
86
+ authorization_data_key_name = configs.String(required=True)
87
+
88
+ """
89
+ Whether or not to throw an error if the key is not present in the authorization data.
90
+ """
91
+ strict = configs.Boolean(default=True)
92
+
93
+ """
94
+ Since this column is always populated automatically, it is never directly writeable.
95
+ """
96
+ is_writeable = configs.Boolean(default=False)
97
+ _descriptor_config_map = None
98
+
99
+ _allowed_search_operators = ["=", "in", "is not null", "is null", "like"]
100
+
101
+ @decorators.parameters_to_properties
102
+ def __init__(
103
+ self,
104
+ authorization_data_key_name: str,
105
+ strict: bool = True,
106
+ is_readable: bool = True,
107
+ is_searchable: bool = True,
108
+ is_temporary: bool = False,
109
+ on_change_pre_save: typing.action | list[typing.action] = [],
110
+ on_change_post_save: typing.action | list[typing.action] = [],
111
+ on_change_save_finished: typing.action | list[typing.action] = [],
112
+ ):
113
+ self.created_by_source_key = authorization_data_key_name
114
+ self.created_by_source_type = "authorization_data"
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies import configs, decorators
6
+ from clearskies.columns.string import String
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import typing
10
+
11
+
12
+ class CreatedByHeader(String):
13
+ """
14
+ This column will automatically take data from the header attached to a request and store it in the model upon creation.
15
+
16
+ If header data isn't available from the context being executed, then you may end up with an error
17
+ (depending on the context). This is a good thing if you are trying to consistely provide audit information,
18
+ but may be a problem if your model creation needs to happen more flexibly.
19
+
20
+ NOTE: columns generally also have the `created_by_source_type` and `created_by_source_key` properties that perform
21
+ this exact same function. Why do we have those properties and this column? This column works well if we have
22
+ some simple string values that we want to always pull from the header data. The properties work better if you need to
23
+ pull header data but it's not just a string type. An example might be if you wanted to pull the user id out of the
24
+ header data to populate a `BelongsToId` column. You wouldn't use this column because it can't provide all the functionality
25
+ related to `BelongsToId`, so instead you would use the `BelongsToId` column and set `created_by_source_type` to `http_header` and
26
+ `created_by_source_key` to `user_id`. Example usage:
27
+
28
+ ```python
29
+ import clearskies
30
+
31
+
32
+ class MyModel(clearskies.Model):
33
+ backend = clearskies.backends.MemoryBackend()
34
+ id_column_name = "id"
35
+
36
+ id = clearskies.columns.Uuid()
37
+ name = clearskies.columns.String()
38
+ custom_header = clearskies.columns.CreatedByHeader("my_custom_header")
39
+
40
+
41
+ wsgi = clearskies.contexts.WsgiRef(
42
+ clearskies.endpoints.Create(
43
+ MyModel,
44
+ writeable_column_names=["name"],
45
+ readable_column_names=["id", "name", "custom_header"],
46
+ ),
47
+ classes=[MyModel],
48
+ )
49
+ wsgi()
50
+ ```
51
+
52
+ If you invoked this:
53
+
54
+ ```bash
55
+ $ curl 'http://localhost:8080' -d '{"name":"Bob"}' -H 'my_custom_header: some header value' | jq
56
+
57
+ {
58
+ "status": "success",
59
+ "error": "",
60
+ "data": {
61
+ "id": "10459ee4-a75e-4fd1-9993-2feeea629144",
62
+ "name": "Bob",
63
+ "custom_header": "some header value"
64
+ },
65
+ "pagination": {},
66
+ "input_errors": {}
67
+ }
68
+
69
+ ```
70
+ """
71
+
72
+ """
73
+ The name of the header that this column should be populated from.
74
+ """
75
+ header_name = configs.String(required=True)
76
+
77
+ """
78
+ Whether or not to throw an error if the key is not present in the header data.
79
+ """
80
+ strict = configs.Boolean(default=True)
81
+
82
+ """
83
+ Since this column is always populated automatically, it is never directly writeable.
84
+ """
85
+ is_writeable = configs.Boolean(default=False)
86
+ _descriptor_config_map = None
87
+
88
+ _allowed_search_operators = ["=", "in", "is not null", "is null", "like"]
89
+
90
+ @decorators.parameters_to_properties
91
+ def __init__(
92
+ self,
93
+ header_name: str,
94
+ strict: bool = True,
95
+ is_readable: bool = True,
96
+ is_searchable: bool = True,
97
+ is_temporary: bool = False,
98
+ on_change_pre_save: typing.action | list[typing.action] = [],
99
+ on_change_post_save: typing.action | list[typing.action] = [],
100
+ on_change_save_finished: typing.action | list[typing.action] = [],
101
+ ):
102
+ self.created_by_source_key = header_name
103
+ self.created_by_source_type = "http_header"
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs, decorators
6
+ from clearskies.columns.string import String
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import Model, typing
10
+
11
+
12
+ class CreatedByIp(String):
13
+ """
14
+ Return the ip address of the client when the record is created.
15
+
16
+ If the ip address isn't available from the context being executed, then you may end up with an error
17
+ (depending on the context). This is a good thing if you are trying to consistely provide audit information,
18
+ but may be a problem if your model creation needs to happen more flexibly. Example:
19
+
20
+ ```python
21
+ import clearskies
22
+
23
+
24
+ class MyModel(clearskies.Model):
25
+ backend = clearskies.backends.MemoryBackend()
26
+ id_column_name = "id"
27
+
28
+ id = clearskies.columns.Uuid()
29
+ name = clearskies.columns.String()
30
+ ip_address = clearskies.columns.CreatedByIp()
31
+
32
+
33
+ wsgi = clearskies.contexts.WsgiRef(
34
+ clearskies.endpoints.Create(
35
+ MyModel,
36
+ writeable_column_names=["name"],
37
+ readable_column_names=["id", "name", "ip_address"],
38
+ ),
39
+ classes=[MyModel],
40
+ )
41
+ wsgi()
42
+ ```
43
+
44
+ And if you invoked this:
45
+
46
+ ```bash
47
+ $ curl 'http://localhost:8080' -d '{"name":"Bob"}' | jq
48
+ {
49
+ "status": "success",
50
+ "error": "",
51
+ "data": {
52
+ "id": "62e457fe-1680-4fa1-b4e3-0329f304fedb",
53
+ "name": "Bob",
54
+ "ip_address": "127.0.0.1"
55
+ },
56
+ "pagination": {},
57
+ "input_errors": {}
58
+ }
59
+
60
+ ```
61
+ """
62
+
63
+ """
64
+ Since this column is always populated automatically, it is never directly writeable.
65
+ """
66
+ is_writeable = configs.Boolean(default=False)
67
+ _descriptor_config_map = None
68
+
69
+ _allowed_search_operators = ["=", "in", "is not null", "is null", "like"]
70
+
71
+ @decorators.parameters_to_properties
72
+ def __init__(
73
+ self,
74
+ is_readable: bool = True,
75
+ is_searchable: bool = True,
76
+ is_temporary: bool = False,
77
+ on_change_pre_save: typing.action | list[typing.action] = [],
78
+ on_change_post_save: typing.action | list[typing.action] = [],
79
+ on_change_save_finished: typing.action | list[typing.action] = [],
80
+ ):
81
+ pass
82
+
83
+ def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
84
+ if model:
85
+ return data
86
+ input_output = self.di.build("input_output", cache=True)
87
+ data = {**data, self.name: input_output.get_client_ip()}
88
+ if self.on_change_pre_save:
89
+ data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
90
+ return data
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies import configs, decorators
6
+ from clearskies.columns.string import String
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import typing
10
+
11
+
12
+ class CreatedByRoutingData(String):
13
+ """
14
+ This column will automatically take data from the route path in the request and store it in the model upon creation.
15
+
16
+ If routing data isn't available from the context being executed, then you may end up with an error
17
+ (depending on the context). This is a good thing if you are trying to consistely provide audit information,
18
+ but may be a problem if your model creation needs to happen more flexibly.
19
+
20
+ NOTE: columns generally also have the `created_by_source_type` and `created_by_source_key` properties that perform
21
+ this exact same function. Why do we have those properties and this column? This column works well if we have
22
+ some simple string values that we want to always pull from the route path. The properties work better if you need to
23
+ pull route data but it's not just a string type. An example might be if you wanted to pull the user id out of the
24
+ route data to populate a `BelongsToId` column. You wouldn't use this column because it can't provide all the functionality
25
+ related to `BelongsToId`, so instead you would use the `BelongsToId` column and set `created_by_source_type` to `routing_data` and
26
+ `created_by_source_key` to `user_id`. Example usage:
27
+
28
+ ```python
29
+ import clearskies
30
+
31
+
32
+ class MyModel(clearskies.Model):
33
+ backend = clearskies.backends.MemoryBackend()
34
+ id_column_name = "id"
35
+
36
+ id = clearskies.columns.Uuid()
37
+ name = clearskies.columns.String()
38
+ organization_id = clearskies.columns.CreatedByRoutingData("organization_id")
39
+
40
+
41
+ wsgi = clearskies.contexts.WsgiRef(
42
+ clearskies.endpoints.Create(
43
+ MyModel,
44
+ url="/{organization_id}",
45
+ writeable_column_names=["name"],
46
+ readable_column_names=["id", "name", "organization_id"],
47
+ ),
48
+ classes=[MyModel],
49
+ )
50
+ wsgi()
51
+ ```
52
+
53
+ And if you invoked this:
54
+
55
+ ```bash
56
+ $ curl 'http://localhost:8080/my-org-id' -d '{"name":"Bob"}' | jq
57
+ {
58
+ "status": "success",
59
+ "error": "",
60
+ "data": {
61
+ "id": "3643db8c-c9d4-47ee-a747-8922c59d9e7e",
62
+ "name": "Bob",
63
+ "organization_id": "my-org-id"
64
+ },
65
+ "pagination": {},
66
+ "input_errors": {}
67
+ }
68
+ ```
69
+ """
70
+
71
+ """
72
+ The name of the route path to populate the column with.
73
+ """
74
+ routing_path_name = configs.String(required=True)
75
+
76
+ """
77
+ Whether or not to throw an error if the key is not present in the header data.
78
+ """
79
+ strict = configs.Boolean(default=True)
80
+
81
+ """
82
+ Since this column is always populated automatically, it is never directly writeable.
83
+ """
84
+ is_writeable = configs.Boolean(default=False)
85
+ _descriptor_config_map = None
86
+
87
+ _allowed_search_operators = ["=", "in", "is not null", "is null", "like"]
88
+
89
+ @decorators.parameters_to_properties
90
+ def __init__(
91
+ self,
92
+ routing_path_name: str,
93
+ strict: bool = True,
94
+ is_readable: bool = True,
95
+ is_searchable: bool = True,
96
+ is_temporary: bool = False,
97
+ on_change_pre_save: typing.action | list[typing.action] = [],
98
+ on_change_post_save: typing.action | list[typing.action] = [],
99
+ on_change_save_finished: typing.action | list[typing.action] = [],
100
+ ):
101
+ self.created_by_source_key = routing_path_name
102
+ self.created_by_source_type = "routing_data"
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs, decorators
6
+ from clearskies.columns.string import String
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import Model, typing
10
+
11
+
12
+ class CreatedByUserAgent(String):
13
+ """
14
+ This column will automatically take the user agent from the client and store it in the model upon creation.
15
+
16
+ If the user agent isn't available from the context being executed, then you may end up with an error
17
+ (depending on the context). This is a good thing if you are trying to consistely provide audit information,
18
+ but may be a problem if your model creation needs to happen more flexibly. Example:
19
+
20
+ ```python
21
+ import clearskies
22
+
23
+
24
+ class MyModel(clearskies.Model):
25
+ backend = clearskies.backends.MemoryBackend()
26
+ id_column_name = "id"
27
+
28
+ id = clearskies.columns.Uuid()
29
+ name = clearskies.columns.String()
30
+ user_agent = clearskies.columns.CreatedByUserAgent()
31
+
32
+
33
+ wsgi = clearskies.contexts.WsgiRef(
34
+ clearskies.endpoints.Create(
35
+ MyModel,
36
+ writeable_column_names=["name"],
37
+ readable_column_names=["id", "name", "user_agent"],
38
+ ),
39
+ classes=[MyModel],
40
+ )
41
+ wsgi()
42
+ ```
43
+
44
+ And if you invoked this:
45
+
46
+ ```bash
47
+ $ curl 'http://localhost:8080' -d '{"name":"Bob"}' | jq
48
+ {
49
+ "status": "success",
50
+ "error": "",
51
+ "data": {
52
+ "id": "a66e5fa9-6377-4d3b-9d50-fc7feaed6d1a",
53
+ "name": "Bob",
54
+ "user_agent": "curl/8.5.0"
55
+ },
56
+ "pagination": {},
57
+ "input_errors": {}
58
+ }
59
+ ```
60
+ """
61
+
62
+ """
63
+ Since this column is always populated automatically, it is never directly writeable.
64
+ """
65
+ is_writeable = configs.Boolean(default=False)
66
+ _descriptor_config_map = None
67
+
68
+ _allowed_search_operators = ["=", "in", "is not null", "is null", "like"]
69
+
70
+ @decorators.parameters_to_properties
71
+ def __init__(
72
+ self,
73
+ is_readable: bool = True,
74
+ is_searchable: bool = True,
75
+ is_temporary: bool = False,
76
+ on_change_pre_save: typing.action | list[typing.action] = [],
77
+ on_change_post_save: typing.action | list[typing.action] = [],
78
+ on_change_save_finished: typing.action | list[typing.action] = [],
79
+ ):
80
+ pass
81
+
82
+ def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
83
+ if model:
84
+ return data
85
+ input_output = self.di.build("input_output", cache=True)
86
+ data = {**data, self.name: input_output.request_headers.user_agent}
87
+ if self.on_change_pre_save:
88
+ data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
89
+ return data