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,483 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections import OrderedDict
4
- from typing import TYPE_CHECKING, Any, Callable
5
-
6
- import clearskies.decorators
7
- import clearskies.typing
8
- from clearskies import configs
9
- from clearskies.autodoc.schema import Object as AutoDocObject
10
- from clearskies.autodoc.schema import Schema as AutoDocSchema
11
- from clearskies.autodoc.schema import String as AutoDocString
12
- from clearskies.columns.string import String
13
- from clearskies.di.inject import InputOutput
14
- from clearskies.functional import validations
15
-
16
- if TYPE_CHECKING:
17
- from clearskies import Column, Model
18
-
19
-
20
- class BelongsToId(String):
21
- """
22
- Declares that this model belongs to another - that it has a parent.
23
-
24
- ## Usage
25
-
26
- The way that a belongs to relationship works is that the child model (e.g. the one with
27
- the BelongsToId column) needs to have a column that stores the id of the parent it is related
28
- to. Then you can attach the BelongsToModel class and point it to the column containing the
29
- id. If you allow the end-user to set the parent id in a save action, the belongs to column
30
- will automatically verify that the given id corresponds to an actual record. Here's a simple
31
- usage example:
32
-
33
- ```python
34
- import clearskies
35
-
36
-
37
- class Category(clearskies.Model):
38
- id_column_name = "id"
39
- backend = clearskies.backends.MemoryBackend()
40
-
41
- id = clearskies.columns.Uuid()
42
- name = clearskies.columns.String()
43
-
44
-
45
- class Product(clearskies.Model):
46
- id_column_name = "id"
47
- backend = clearskies.backends.MemoryBackend()
48
-
49
- id = clearskies.columns.Uuid()
50
- name = clearskies.columns.String()
51
- category_id = clearskies.columns.BelongsToId(Category)
52
- category = clearskies.columns.BelongsToModel("category_id")
53
-
54
-
55
- def test_belongs_to(products: Product, categories: Category):
56
- toys = categories.create({"name": "Toys"})
57
- auto = categories.create({"name": "Auto"})
58
-
59
- # Note: we set the cateogry by setting "category_id"
60
- ball = products.create({"name": "ball", "category_id": toys.id})
61
-
62
- # note: we set the category by saving a category model to "category"
63
- fidget_spinner = products.create({"name": "Fidget Spinner", "category": toys})
64
-
65
- return {
66
- "ball_category": ball.category.name,
67
- "fidget_spinner_category": fidget_spinner.category.name,
68
- "ball_id_check": ball.category_id == ball.category.id,
69
- "ball_fidget_id_check": fidget_spinner.category_id == ball.category.id,
70
- }
71
-
72
-
73
- cli = clearskies.contexts.Cli(
74
- clearskies.endpoints.Callable(test_belongs_to),
75
- classes=[Category, Product],
76
- )
77
-
78
- if __name__ == "__main__":
79
- cli()
80
- ```
81
-
82
- ## Circular Dependency Trees
83
-
84
- The opposite of a BelongsToId relationship is a HasMany relationship. It's common
85
- for the child model to contain a BelonsToId column to point to the parent, and then
86
- have the parent contain a HasMany column to point to the child. This creates circular
87
- depenency errors in python. To work around this, clearskies requires the addition of
88
- a "model reference" class that looks like this:
89
-
90
- ```python
91
- import some_model
92
-
93
-
94
- class SomeModelReference:
95
- def get_model_class(self):
96
- return some_model.SomeModel
97
- ```
98
-
99
- These have to live in their own file, should use relative imports to import the file containing
100
- the model, and should not be imported into the module they live in. So, sticking with the example
101
- of categories and products, you would have the following directory structure:
102
-
103
- ```
104
- ├── models
105
- │ ├── category.py
106
- │ ├── category_reference.py
107
- │ ├── product.py
108
- │ └── product_reference.py
109
-
110
- └── app.py
111
- ```
112
-
113
- The files would then contain:
114
-
115
- category.py
116
- ```python
117
- import clearskies
118
- import models.product_reference
119
-
120
-
121
- class Category(clearskies.Model):
122
- id_column_name = "id"
123
- backend = clearskies.backends.MemoryBackend()
124
-
125
- id = clearskies.columns.Uuid()
126
- name = clearskies.columns.String()
127
- products = clearskies.columns.HasMany(product_reference.ProductReference)
128
- ```
129
-
130
- category_reference.py
131
- ```python
132
- from clearskies.model import ModelClassReference
133
- from . import cateogry
134
-
135
-
136
- class CategoryReference(ModelClassReference):
137
- def get_model_class(self):
138
- return category.Category
139
- ```
140
-
141
- product.py
142
- ```python
143
- import clearskies
144
- import models.category_reference
145
-
146
-
147
- class Product(clearskies.model.Model):
148
- id_column_name = "id"
149
- backend = clearskies.backends.MemoryBackend()
150
-
151
- id = clearskies.columns.Uuid()
152
- name = clearskies.columns.String()
153
- category_id = clearskies.columns.BelongsToId(CategoryReference)
154
- category = clearskies.columns.BelongsToModel("category_id")
155
- ```
156
-
157
- product_reference.py
158
- ```python
159
- from clearskies.model import ModelClassReference
160
- from . import product
161
-
162
-
163
- class ProductReference(ModelClassReference):
164
- def get_model_class(self):
165
- return product.Product
166
- ```
167
- """
168
-
169
- """ The model class we belong to. """
170
- parent_model_class = configs.ModelClass(required=True)
171
-
172
- """
173
- The name of the property used to fetch the parent model itself.
174
-
175
- Note that this isn't set explicitly, but by adding a BelongsToModel column to the model.
176
- """
177
- model_column_name = configs.String()
178
-
179
- """
180
- The list of columns from the parent that should be included when converting this column to JSON.
181
-
182
- When configuring readable columns for an endpoint, you can specify the BelongsToModel column.
183
- If you do this, you must set readable_parent_columns on the BelongsToId column to specify which
184
- columns from the parent model should be returned in the response. See this example:
185
-
186
- ```python
187
- import clearskies
188
-
189
- class Owner(clearskies.Model):
190
- id_column_name = "id"
191
- backend = clearskies.backends.MemoryBackend()
192
-
193
- id = clearskies.columns.Uuid()
194
- name = clearskies.columns.String()
195
-
196
- class Pet(clearskies.Model):
197
- id_column_name = "id"
198
- backend = clearskies.backends.MemoryBackend()
199
-
200
- id = clearskies.columns.Uuid()
201
- name = clearskies.columns.String()
202
- owner_id = clearskies.columns.BelongsToId(
203
- Owner,
204
- readable_parent_columns=["id", "name"],
205
- )
206
- owner = clearskies.columns.BelongsToModel("owner_id")
207
-
208
- cli = clearskies.contexts.Cli(
209
- clearskies.endpoints.List(
210
- Pet,
211
- sortable_column_names=["id", "name"],
212
- readable_column_names=["id", "name", "owner"],
213
- default_sort_column_name="name",
214
- ),
215
- classes=[Owner, Pet],
216
- bindings={
217
- "memory_backend_default_data": [
218
- {
219
- "model_class": Owner,
220
- "records": [
221
- {"id": "1-2-3-4", "name": "John Doe"},
222
- {"id": "5-6-7-8", "name": "Jane Doe"},
223
- ],
224
- },
225
- {
226
- "model_class": Pet,
227
- "records": [
228
- {"id": "a-b-c-d", "name": "Fido", "owner_id": "1-2-3-4"},
229
- {"id": "e-f-g-h", "name": "Spot", "owner_id": "1-2-3-4"},
230
- {"id": "i-j-k-l", "name": "Puss in Boots", "owner_id": "5-6-7-8"},
231
- ],
232
- },
233
- ],
234
- }
235
- )
236
-
237
- if __name__ == "__main__":
238
- cli()
239
- ```
240
-
241
- With readable_parent_columns set in the Pet.owner_id column, and owner set in the list configuration,
242
- The owner id and name are included in the `owner` key of the returned Pet dictionary:
243
-
244
- ```bash
245
- $ ./test.py | jq
246
- {
247
- "status": "success",
248
- "error": "",
249
- "data": [
250
- {
251
- "id": "a-b-c-d",
252
- "name": "Fido",
253
- "owner": {
254
- "id": "1-2-3-4",
255
- "name": "John Doe"
256
- }
257
- },
258
- {
259
- "id": "i-j-k-l",
260
- "name": "Puss in Boots",
261
- "owner": {
262
- "id": "5-6-7-8",
263
- "name": "Jane Doe"
264
- }
265
- },
266
- {
267
- "id": "e-f-g-h",
268
- "name": "Spot",
269
- "owner": {
270
- "id": "1-2-3-4",
271
- "name": "John Doe"
272
- }
273
- }
274
- ],
275
- "pagination": {},
276
- "input_errors": {}
277
- }
278
- ```
279
-
280
- """
281
- readable_parent_columns = configs.ReadableModelColumns("parent_model_class")
282
-
283
- """
284
- The type of join to use when searching on the parent.
285
- """
286
- join_type = configs.Select(["LEFT", "INNER", "RIGHT"], default="LEFT")
287
-
288
- """
289
- Any additional conditions to place on the parent table when finding related records.
290
-
291
- where should be a list containing a combination of conditions-as-strings, queries built from the columns
292
- themselves, or callable functions which accept the model and apply filters. This is primarily used in
293
- input validation to exclude values as allowed parents.
294
- """
295
- where = configs.Conditions()
296
-
297
- input_output = InputOutput()
298
- wants_n_plus_one = True
299
- _allowed_search_operators = ["="]
300
- _descriptor_config_map = None
301
-
302
- @clearskies.decorators.parameters_to_properties
303
- def __init__(
304
- self,
305
- parent_model_class,
306
- readable_parent_columns: list[str] = [],
307
- join_type: str | None = None,
308
- where: clearskies.typing.condition | list[clearskies.typing.condition] = [],
309
- default: str | None = None,
310
- setable: str | Callable | None = None,
311
- is_readable: bool = True,
312
- is_writeable: bool = True,
313
- is_searchable: bool = True,
314
- is_temporary: bool = False,
315
- validators: clearskies.typing.validator | list[clearskies.typing.validator] = [],
316
- on_change_pre_save: clearskies.typing.action | list[clearskies.typing.action] = [],
317
- on_change_post_save: clearskies.typing.action | list[clearskies.typing.action] = [],
318
- on_change_save_finished: clearskies.typing.action | list[clearskies.typing.action] = [],
319
- created_by_source_type: str = "",
320
- created_by_source_key: str = "",
321
- created_by_source_strict: bool = True,
322
- ):
323
- pass
324
-
325
- @property
326
- def parent_model(self) -> Model:
327
- parents = self.di.build(self.parent_model_class, cache=True)
328
- if not self.where:
329
- return parents
330
-
331
- return self.apply_wheres(parents)
332
-
333
- def apply_wheres(self, parents: Model) -> Model:
334
- if not self.where:
335
- return parents
336
-
337
- for index, where in enumerate(self.where):
338
- if callable(where):
339
- parents = self.di.call_function(where, model=parents, **self.input_output.get_context_for_callables())
340
- if not validations.is_model(parents):
341
- raise ValueError(
342
- f"Configuration error for {self.model_class.__name__}.{self.name}: when 'where' is a callable, it must return a model class, but when the callable in where entry #{index + 1} was called, it returned something else."
343
- )
344
- else:
345
- parents = parents.where(where)
346
- return parents
347
-
348
- @property
349
- def parent_columns(self) -> dict[str, Any]:
350
- return self.parent_model_class.get_columns()
351
-
352
- def input_error_for_value(self, value: str, operator: str | None = None) -> str:
353
- parent_check = super().input_error_for_value(value)
354
- if parent_check:
355
- return parent_check
356
- parent_model = self.parent_model
357
- matching_parents = parent_model.where(f"{parent_model.id_column_name}={value}")
358
- matching_parents = self.apply_wheres(matching_parents)
359
- matching_parents = matching_parents.where_for_request_all(
360
- matching_parents,
361
- self.input_output,
362
- routing_data=self.input_output.routing_data,
363
- authorization_data=self.input_output.authorization_data,
364
- )
365
- if not len(matching_parents):
366
- return f"Invalid selection for {self.name}: record does not exist"
367
- return ""
368
-
369
- def n_plus_one_add_joins(self, model: Model, column_names: list[str] = []) -> Model:
370
- """Add any additional joins to solve the N+1 problem."""
371
- if not column_names:
372
- column_names = self.readable_parent_columns
373
- if not column_names:
374
- return model
375
-
376
- model = self.add_join(model)
377
- alias = self.join_table_alias()
378
- parent_id_column_name = self.parent_model.id_column_name
379
- select_parts = [f"{alias}.{column_name} AS {alias}_{column_name}" for column_name in column_names]
380
- if parent_id_column_name not in column_names:
381
- select_parts.append(f"{alias}.{parent_id_column_name} AS {alias}_{parent_id_column_name}")
382
- return model.select(", ".join(select_parts))
383
-
384
- def add_join(self, model: Model) -> Model:
385
- parent_table = self.parent_model.destination_name()
386
- alias = self.join_table_alias()
387
-
388
- if model.is_joined(parent_table, alias=alias):
389
- return model
390
-
391
- join_type = "LEFT " if self.join_type == "LEFT" else ""
392
- own_table_name = model.destination_name()
393
- parent_id_column_name = self.parent_model.id_column_name
394
- return model.join(
395
- f"{join_type}JOIN {parent_table} as {alias} on {alias}.{parent_id_column_name}={own_table_name}.{self.name}"
396
- )
397
-
398
- def join_table_alias(self) -> str:
399
- return self.parent_model.destination_name() + "_" + self.name
400
-
401
- def is_allowed_operator(self, operator, relationship_reference=None):
402
- """Proces user data to decide if the end-user is specifying an allowed operator."""
403
- if not relationship_reference:
404
- return "="
405
- parent_columns = self.parent_columns
406
- if relationship_reference not in self.parent_columns:
407
- raise ValueError(
408
- "I was asked to search on a related column that doens't exist. This shouldn't have happened :("
409
- )
410
- return self.parent_columns[relationship_reference].is_allowed_operator(operator)
411
-
412
- def check_search_value(self, value, operator=None, relationship_reference=None):
413
- if not relationship_reference:
414
- return self.input_error_for_value(value, operator=operator)
415
- parent_columns = self.parent_columns
416
- if relationship_reference not in self.parent_columns:
417
- raise ValueError(
418
- "I was asked to search on a related column that doens't exist. This shouldn't have happened :("
419
- )
420
- return self.parent_columns[relationship_reference].check_search_value(value, operator=operator)
421
-
422
- def is_allowed_search_operator(self, operator: str, relationship_reference: str = "") -> bool:
423
- if not relationship_reference:
424
- return operator in self._allowed_search_operators
425
- parent_columns = self.parent_columns
426
- if relationship_reference not in self.parent_columns:
427
- raise ValueError(
428
- "I was asked to check search operators on a related column that doens't exist. This shouldn't have happened :("
429
- )
430
- return self.parent_columns[relationship_reference].is_allowed_search_operator(
431
- operator, relationship_reference=relationship_reference
432
- )
433
-
434
- def allowed_search_operators(self, relationship_reference: str = ""):
435
- if not relationship_reference:
436
- return self._allowed_search_operators
437
- parent_columns = self.parent_columns
438
- if relationship_reference not in self.parent_columns:
439
- raise ValueError(
440
- "I was asked for allowed search operators on a related column that doens't exist. This shouldn't have happened :("
441
- )
442
- return self.parent_columns[relationship_reference].allowed_search_operators()
443
-
444
- def add_search(
445
- self, model: clearskies.model.Model, value: str, operator: str = "", relationship_reference: str = ""
446
- ) -> clearskies.model.Model:
447
- if not relationship_reference:
448
- return super().add_search(model, value, operator=operator)
449
-
450
- if relationship_reference not in self.parent_columns:
451
- raise ValueError(
452
- "I was asked to search on a related column that doens't exist. This shouldn't have happened :("
453
- )
454
-
455
- model = self.add_join(model)
456
- related_column = self.parent_columns[relationship_reference]
457
- alias = self.join_table_alias()
458
- return model.where(related_column.build_condition(value, operator=operator, column_prefix=f"{alias}."))
459
-
460
- def documentation(
461
- self, name: str | None = None, example: str | None = None, value: str | None = None
462
- ) -> list[AutoDocSchema]:
463
- columns = self.parent_columns
464
- parent_id_column_name = self.parent_model.id_column_name
465
- parent_properties = [columns[parent_id_column_name].documentation()]
466
- parent_id_doc = AutoDocString(name if name is not None else self.name)
467
-
468
- readable_parent_columns = self.readable_parent_columns
469
- if not readable_parent_columns:
470
- return [parent_id_doc]
471
-
472
- for column_name in readable_parent_columns:
473
- if column_name == parent_id_column_name:
474
- continue
475
- parent_properties.append(columns[column_name].documentation())
476
-
477
- return [
478
- parent_id_doc,
479
- AutoDocObject(
480
- self.model_column_name,
481
- parent_properties,
482
- ),
483
- ]
@@ -1,132 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections import OrderedDict
4
- from typing import TYPE_CHECKING, Any, Self, overload
5
-
6
- import clearskies.decorators
7
- from clearskies import configs
8
- from clearskies.column import Column
9
- from clearskies.columns.belongs_to_id import BelongsToId
10
- from clearskies.functional import validations
11
-
12
- if TYPE_CHECKING:
13
- from clearskies import Model
14
-
15
-
16
- class BelongsToModel(Column):
17
- """Return the model object for a belongs to relationship."""
18
-
19
- """ The name of the belongs to column we are connected to. """
20
- belongs_to_column_name = configs.ModelColumn(required=True)
21
-
22
- is_temporary = clearskies.configs.boolean.Boolean(default=True)
23
- _descriptor_config_map = None
24
-
25
- @clearskies.decorators.parameters_to_properties
26
- def __init__(
27
- self,
28
- belongs_to_column_name: str,
29
- ):
30
- pass
31
-
32
- def finalize_configuration(self, model_class: type, name: str) -> None:
33
- """Finalize and check the configuration."""
34
- getattr(self.__class__, "belongs_to_column_name").set_model_class(model_class)
35
- self.model_class = model_class
36
- self.name = name
37
- self.finalize_and_validate_configuration()
38
-
39
- # finally, let the belongs to column know about us and make sure it's the right thing.
40
- belongs_to_column = getattr(model_class, self.belongs_to_column_name)
41
- if not isinstance(belongs_to_column, BelongsToId):
42
- raise ValueError(
43
- f"Error with configuration for {model_class.__name__}.{name}, which is a BelongsToModel. It needs to point to a belongs to column, and it was told to use {model_class.__name__}.{self.belongs_to_column_name}, but this is not a BelongsToId column."
44
- )
45
- belongs_to_column.model_column_name = name
46
-
47
- @overload
48
- def __get__(self, instance: None, cls: type[Model]) -> Self:
49
- pass
50
-
51
- @overload
52
- def __get__(self, instance: Model, cls: type[Model]) -> Model:
53
- pass
54
-
55
- def __get__(self, model, cls):
56
- if model is None:
57
- self.model_class = cls
58
- return self # type: ignore
59
-
60
- # this makes sure we're initialized
61
- if "name" not in self._config: # type: ignore
62
- model.get_columns()
63
-
64
- belongs_to_column = getattr(model.__class__, self.belongs_to_column_name)
65
- parent_id = getattr(model, self.belongs_to_column_name)
66
- parent_class = belongs_to_column.parent_model_class
67
- parent_model = self.di.build(parent_class, cache=False)
68
- if not parent_id:
69
- return parent_model.empty_model()
70
-
71
- parent_id_column_name = parent_model.id_column_name
72
- join_alias = belongs_to_column.join_table_alias()
73
- raw_data = model.get_raw_data()
74
-
75
- # sometimes the model is loaded via the N+1 functionality, in which case the data will already exist
76
- # in model.data but hiding under a different name.
77
- if raw_data.get(f"{join_alias}.{parent_id_column_name}"):
78
- parent_data = {parent_id_column_name: raw_data[f"{join_alias}_{parent_id_column_name}"]}
79
- for column_name in belongs_to_column.readable_parent_columns:
80
- select_alias = f"{join_alias}_{column_name}"
81
- parent_data[column_name] = raw_data[select_alias] if select_alias in raw_data else None
82
- return parent_model.model(parent_data)
83
-
84
- return parent_model.find(f"{parent_id_column_name}={parent_id}")
85
-
86
- def __set__(self, model: Model, value: Model) -> None:
87
- # this makes sure we're initialized
88
- if "name" not in self._config: # type: ignore
89
- model.get_columns()
90
-
91
- setattr(model, self.belongs_to_column_name, getattr(value, value.id_column_name))
92
-
93
- def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
94
- # if we have a model coming in then we want to extract the id. Either way, the model id needs to go to the
95
- # belongs_to_id column, which is the only one that is actually saved.
96
- if self.name in data:
97
- value = data[self.name]
98
- data[self.belongs_to_column_name] = (
99
- getattr(value, value.id_column_name) if validations.is_model(value) else value
100
- )
101
- return super().pre_save(data, model)
102
-
103
- def add_join(self, model: Model) -> Model:
104
- return getattr(model.__class__, self.belongs_to_column_name).add_join(model)
105
-
106
- def join_table_alias(self) -> str:
107
- return getattr(self.model_class, self.belongs_to_column_name).join_table_alias()
108
-
109
- def add_search(
110
- self, model: clearskies.model.Model, value: str, operator: str = "", relationship_reference: str = ""
111
- ) -> clearskies.model.Model:
112
- return getattr(self.model_class, self.belongs_to_column_name).add_search(
113
- model, value, operator, relationship_reference=relationship_reference
114
- )
115
-
116
- def to_json(self, model: Model) -> dict[str, Any]:
117
- """Convert the column into a json-friendly representation."""
118
- belongs_to_column = getattr(model.__class__, self.belongs_to_column_name)
119
- if not belongs_to_column.readable_parent_columns:
120
- raise ValueError(
121
- f"Configuration error for {model.__class__.__name__}: I can't convert to JSON unless you set readable_parent_columns on my parent attribute, {model.__class__.__name__}.{self.belongs_to_column_name}."
122
- )
123
-
124
- # otherwise return an object with the readable parent columns
125
- columns = belongs_to_column.parent_columns
126
- parent = getattr(model, self.name)
127
- json: dict[str, Any] = OrderedDict()
128
- for column_name in belongs_to_column.readable_parent_columns:
129
- json = {**json, **columns[column_name].to_json(parent)} # type: ignore
130
- return {
131
- self.name: json,
132
- }