clear-skies 2.0.4__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.
- clear_skies-2.0.5.dist-info/METADATA +74 -0
- clear_skies-2.0.5.dist-info/RECORD +4 -0
- {clear_skies-2.0.4.dist-info → clear_skies-2.0.5.dist-info}/WHEEL +1 -1
- clear_skies-2.0.4.dist-info/METADATA +0 -36
- clear_skies-2.0.4.dist-info/RECORD +0 -251
- clearskies/__init__.py +0 -61
- clearskies/action.py +0 -7
- clearskies/authentication/__init__.py +0 -15
- clearskies/authentication/authentication.py +0 -46
- clearskies/authentication/authorization.py +0 -16
- clearskies/authentication/authorization_pass_through.py +0 -20
- clearskies/authentication/jwks.py +0 -163
- clearskies/authentication/public.py +0 -5
- clearskies/authentication/secret_bearer.py +0 -553
- clearskies/autodoc/__init__.py +0 -8
- clearskies/autodoc/formats/__init__.py +0 -5
- clearskies/autodoc/formats/oai3_json/__init__.py +0 -7
- clearskies/autodoc/formats/oai3_json/oai3_json.py +0 -87
- clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +0 -15
- clearskies/autodoc/formats/oai3_json/parameter.py +0 -35
- clearskies/autodoc/formats/oai3_json/request.py +0 -68
- clearskies/autodoc/formats/oai3_json/response.py +0 -28
- clearskies/autodoc/formats/oai3_json/schema/__init__.py +0 -11
- clearskies/autodoc/formats/oai3_json/schema/array.py +0 -9
- clearskies/autodoc/formats/oai3_json/schema/default.py +0 -13
- clearskies/autodoc/formats/oai3_json/schema/enum.py +0 -7
- clearskies/autodoc/formats/oai3_json/schema/object.py +0 -35
- clearskies/autodoc/formats/oai3_json/test.json +0 -1985
- clearskies/autodoc/py.typed +0 -0
- clearskies/autodoc/request/__init__.py +0 -15
- clearskies/autodoc/request/header.py +0 -6
- clearskies/autodoc/request/json_body.py +0 -6
- clearskies/autodoc/request/parameter.py +0 -8
- clearskies/autodoc/request/request.py +0 -47
- clearskies/autodoc/request/url_parameter.py +0 -6
- clearskies/autodoc/request/url_path.py +0 -6
- clearskies/autodoc/response/__init__.py +0 -5
- clearskies/autodoc/response/response.py +0 -9
- clearskies/autodoc/schema/__init__.py +0 -31
- clearskies/autodoc/schema/array.py +0 -10
- clearskies/autodoc/schema/base64.py +0 -8
- clearskies/autodoc/schema/boolean.py +0 -5
- clearskies/autodoc/schema/date.py +0 -5
- clearskies/autodoc/schema/datetime.py +0 -5
- clearskies/autodoc/schema/double.py +0 -5
- clearskies/autodoc/schema/enum.py +0 -17
- clearskies/autodoc/schema/integer.py +0 -6
- clearskies/autodoc/schema/long.py +0 -5
- clearskies/autodoc/schema/number.py +0 -6
- clearskies/autodoc/schema/object.py +0 -13
- clearskies/autodoc/schema/password.py +0 -5
- clearskies/autodoc/schema/schema.py +0 -11
- clearskies/autodoc/schema/string.py +0 -5
- clearskies/backends/__init__.py +0 -65
- clearskies/backends/api_backend.py +0 -1178
- clearskies/backends/backend.py +0 -136
- clearskies/backends/cursor_backend.py +0 -335
- clearskies/backends/memory_backend.py +0 -797
- clearskies/backends/secrets_backend.py +0 -106
- clearskies/column.py +0 -1233
- clearskies/columns/__init__.py +0 -71
- clearskies/columns/audit.py +0 -206
- clearskies/columns/belongs_to_id.py +0 -483
- clearskies/columns/belongs_to_model.py +0 -132
- clearskies/columns/belongs_to_self.py +0 -105
- clearskies/columns/boolean.py +0 -113
- clearskies/columns/category_tree.py +0 -275
- clearskies/columns/category_tree_ancestors.py +0 -51
- clearskies/columns/category_tree_children.py +0 -127
- clearskies/columns/category_tree_descendants.py +0 -48
- clearskies/columns/created.py +0 -95
- clearskies/columns/created_by_authorization_data.py +0 -116
- clearskies/columns/created_by_header.py +0 -99
- clearskies/columns/created_by_ip.py +0 -92
- clearskies/columns/created_by_routing_data.py +0 -97
- clearskies/columns/created_by_user_agent.py +0 -92
- clearskies/columns/date.py +0 -234
- clearskies/columns/datetime.py +0 -282
- clearskies/columns/email.py +0 -76
- clearskies/columns/float.py +0 -153
- clearskies/columns/has_many.py +0 -505
- clearskies/columns/has_many_self.py +0 -56
- clearskies/columns/has_one.py +0 -14
- clearskies/columns/integer.py +0 -160
- clearskies/columns/json.py +0 -126
- clearskies/columns/many_to_many_ids.py +0 -337
- clearskies/columns/many_to_many_ids_with_data.py +0 -274
- clearskies/columns/many_to_many_models.py +0 -158
- clearskies/columns/many_to_many_pivots.py +0 -134
- clearskies/columns/phone.py +0 -159
- clearskies/columns/select.py +0 -92
- clearskies/columns/string.py +0 -102
- clearskies/columns/timestamp.py +0 -164
- clearskies/columns/updated.py +0 -110
- clearskies/columns/uuid.py +0 -86
- clearskies/configs/README.md +0 -105
- clearskies/configs/__init__.py +0 -162
- clearskies/configs/actions.py +0 -43
- clearskies/configs/any.py +0 -13
- clearskies/configs/any_dict.py +0 -22
- clearskies/configs/any_dict_or_callable.py +0 -23
- clearskies/configs/authentication.py +0 -23
- clearskies/configs/authorization.py +0 -23
- clearskies/configs/boolean.py +0 -16
- clearskies/configs/boolean_or_callable.py +0 -18
- clearskies/configs/callable_config.py +0 -18
- clearskies/configs/columns.py +0 -34
- clearskies/configs/conditions.py +0 -30
- clearskies/configs/config.py +0 -24
- clearskies/configs/datetime.py +0 -18
- clearskies/configs/datetime_or_callable.py +0 -19
- clearskies/configs/endpoint.py +0 -23
- clearskies/configs/endpoint_list.py +0 -28
- clearskies/configs/float.py +0 -16
- clearskies/configs/float_or_callable.py +0 -18
- clearskies/configs/integer.py +0 -16
- clearskies/configs/integer_or_callable.py +0 -18
- clearskies/configs/joins.py +0 -30
- clearskies/configs/list_any_dict.py +0 -30
- clearskies/configs/list_any_dict_or_callable.py +0 -31
- clearskies/configs/model_class.py +0 -35
- clearskies/configs/model_column.py +0 -65
- clearskies/configs/model_columns.py +0 -56
- clearskies/configs/model_destination_name.py +0 -25
- clearskies/configs/model_to_id_column.py +0 -43
- clearskies/configs/readable_model_column.py +0 -9
- clearskies/configs/readable_model_columns.py +0 -9
- clearskies/configs/schema.py +0 -23
- clearskies/configs/searchable_model_columns.py +0 -9
- clearskies/configs/security_headers.py +0 -39
- clearskies/configs/select.py +0 -26
- clearskies/configs/select_list.py +0 -47
- clearskies/configs/string.py +0 -29
- clearskies/configs/string_dict.py +0 -32
- clearskies/configs/string_list.py +0 -32
- clearskies/configs/string_list_or_callable.py +0 -35
- clearskies/configs/string_or_callable.py +0 -18
- clearskies/configs/timedelta.py +0 -18
- clearskies/configs/timezone.py +0 -18
- clearskies/configs/url.py +0 -23
- clearskies/configs/validators.py +0 -45
- clearskies/configs/writeable_model_column.py +0 -9
- clearskies/configs/writeable_model_columns.py +0 -9
- clearskies/configurable.py +0 -76
- clearskies/contexts/__init__.py +0 -11
- clearskies/contexts/cli.py +0 -117
- clearskies/contexts/context.py +0 -98
- clearskies/contexts/wsgi.py +0 -76
- clearskies/contexts/wsgi_ref.py +0 -82
- clearskies/decorators.py +0 -33
- clearskies/di/__init__.py +0 -14
- clearskies/di/additional_config.py +0 -130
- clearskies/di/additional_config_auto_import.py +0 -17
- clearskies/di/di.py +0 -973
- clearskies/di/inject/__init__.py +0 -23
- clearskies/di/inject/by_class.py +0 -21
- clearskies/di/inject/by_name.py +0 -18
- clearskies/di/inject/di.py +0 -13
- clearskies/di/inject/environment.py +0 -14
- clearskies/di/inject/input_output.py +0 -20
- clearskies/di/inject/now.py +0 -13
- clearskies/di/inject/requests.py +0 -13
- clearskies/di/inject/secrets.py +0 -14
- clearskies/di/inject/utcnow.py +0 -13
- clearskies/di/inject/uuid.py +0 -15
- clearskies/di/injectable.py +0 -29
- clearskies/di/injectable_properties.py +0 -131
- clearskies/di/test_module/__init__.py +0 -6
- clearskies/di/test_module/another_module/__init__.py +0 -2
- clearskies/di/test_module/module_class.py +0 -5
- clearskies/end.py +0 -183
- clearskies/endpoint.py +0 -1314
- clearskies/endpoint_group.py +0 -338
- clearskies/endpoints/__init__.py +0 -25
- clearskies/endpoints/advanced_search.py +0 -526
- clearskies/endpoints/callable.py +0 -388
- clearskies/endpoints/create.py +0 -205
- clearskies/endpoints/delete.py +0 -139
- clearskies/endpoints/get.py +0 -271
- clearskies/endpoints/health_check.py +0 -183
- clearskies/endpoints/list.py +0 -574
- clearskies/endpoints/restful_api.py +0 -427
- clearskies/endpoints/schema.py +0 -189
- clearskies/endpoints/simple_search.py +0 -286
- clearskies/endpoints/update.py +0 -193
- clearskies/environment.py +0 -104
- clearskies/exceptions/__init__.py +0 -19
- clearskies/exceptions/authentication.py +0 -2
- clearskies/exceptions/authorization.py +0 -2
- clearskies/exceptions/client_error.py +0 -2
- clearskies/exceptions/input_errors.py +0 -4
- clearskies/exceptions/missing_dependency.py +0 -2
- clearskies/exceptions/moved_permanently.py +0 -3
- clearskies/exceptions/moved_temporarily.py +0 -3
- clearskies/exceptions/not_found.py +0 -2
- clearskies/functional/__init__.py +0 -7
- clearskies/functional/routing.py +0 -92
- clearskies/functional/string.py +0 -112
- clearskies/functional/validations.py +0 -76
- clearskies/input_outputs/__init__.py +0 -13
- clearskies/input_outputs/cli.py +0 -171
- clearskies/input_outputs/exceptions/__init__.py +0 -2
- clearskies/input_outputs/exceptions/cli_input_error.py +0 -2
- clearskies/input_outputs/exceptions/cli_not_found.py +0 -2
- clearskies/input_outputs/headers.py +0 -45
- clearskies/input_outputs/input_output.py +0 -138
- clearskies/input_outputs/programmatic.py +0 -69
- clearskies/input_outputs/py.typed +0 -0
- clearskies/input_outputs/wsgi.py +0 -77
- clearskies/model.py +0 -1922
- clearskies/py.typed +0 -0
- clearskies/query/__init__.py +0 -12
- clearskies/query/condition.py +0 -223
- clearskies/query/join.py +0 -136
- clearskies/query/query.py +0 -196
- clearskies/query/sort.py +0 -27
- clearskies/schema.py +0 -82
- clearskies/secrets/__init__.py +0 -6
- clearskies/secrets/additional_configs/__init__.py +0 -32
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +0 -61
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -160
- clearskies/secrets/akeyless.py +0 -182
- clearskies/secrets/exceptions/__init__.py +0 -1
- clearskies/secrets/exceptions/not_found.py +0 -2
- clearskies/secrets/secrets.py +0 -38
- clearskies/security_header.py +0 -15
- clearskies/security_headers/__init__.py +0 -11
- clearskies/security_headers/cache_control.py +0 -67
- clearskies/security_headers/cors.py +0 -50
- clearskies/security_headers/csp.py +0 -94
- clearskies/security_headers/hsts.py +0 -22
- clearskies/security_headers/x_content_type_options.py +0 -0
- clearskies/security_headers/x_frame_options.py +0 -0
- clearskies/test_base.py +0 -8
- clearskies/typing.py +0 -11
- clearskies/validator.py +0 -37
- clearskies/validators/__init__.py +0 -33
- clearskies/validators/after_column.py +0 -62
- clearskies/validators/before_column.py +0 -13
- clearskies/validators/in_the_future.py +0 -32
- clearskies/validators/in_the_future_at_least.py +0 -11
- clearskies/validators/in_the_future_at_most.py +0 -10
- clearskies/validators/in_the_past.py +0 -32
- clearskies/validators/in_the_past_at_least.py +0 -10
- clearskies/validators/in_the_past_at_most.py +0 -10
- clearskies/validators/maximum_length.py +0 -26
- clearskies/validators/maximum_value.py +0 -29
- clearskies/validators/minimum_length.py +0 -26
- clearskies/validators/minimum_value.py +0 -29
- clearskies/validators/required.py +0 -34
- clearskies/validators/timedelta.py +0 -59
- clearskies/validators/unique.py +0 -30
- {clear_skies-2.0.4.dist-info → clear_skies-2.0.5.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import inspect
|
|
4
|
-
from collections import OrderedDict
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Type
|
|
6
|
-
|
|
7
|
-
import clearskies.configs
|
|
8
|
-
import clearskies.exceptions
|
|
9
|
-
from clearskies import authentication, autodoc, typing
|
|
10
|
-
from clearskies.endpoints.simple_search import SimpleSearch
|
|
11
|
-
from clearskies.functional import string
|
|
12
|
-
from clearskies.input_outputs import InputOutput
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from clearskies import SecurityHeader
|
|
16
|
-
from clearskies.model import Model
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class AdvancedSearch(SimpleSearch):
|
|
20
|
-
"""
|
|
21
|
-
An endpoint that grants the client extensive control over searching and filtering.
|
|
22
|
-
|
|
23
|
-
Rather than accepting URL parameters (like the SimpleSearch endpoint), this endpoint accepts a JSON POST
|
|
24
|
-
body. Search conditions are specified as a list of dictionaries containing `column`, `operator`, and
|
|
25
|
-
`value`. It also accepts up to two sort directives. Of course, while this endpoint supports arbitrary
|
|
26
|
-
searching, it won't work if the backend itself doesn't support it. The following is the list of allowed
|
|
27
|
-
keys in the JSON body:
|
|
28
|
-
|
|
29
|
-
| Name | Type | Description | Example |
|
|
30
|
-
|-------|----------------------|----------------------------------------------------------------------------|---------|
|
|
31
|
-
| sort | list[dict[str, str]] | A list of sort directives containing `column` and `direction` | `{"sort": [ {"column": "age", "direction": "desc} ] }` |
|
|
32
|
-
| limit | int | The number of records to return | `{"limit": `100`}` |
|
|
33
|
-
| where | list[dict[str, Any]] | A list of conditions containing `column`, `operator`, and `value` | `{"where": [ {"column": "age", "operator": ">", "value": 10} ] }` |
|
|
34
|
-
| * | str, int | Pagination information. The key name and value type depend on the backend | `{"start": 100}` |
|
|
35
|
-
|
|
36
|
-
Here's an example making use of the AdvancedSearch endpoint:
|
|
37
|
-
|
|
38
|
-
```python
|
|
39
|
-
import clearskies
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class Company(clearskies.Model):
|
|
43
|
-
id_column_name = "id"
|
|
44
|
-
backend = clearskies.backends.MemoryBackend()
|
|
45
|
-
|
|
46
|
-
id = clearskies.columns.Uuid()
|
|
47
|
-
name = clearskies.columns.String()
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class User(clearskies.Model):
|
|
51
|
-
id_column_name = "id"
|
|
52
|
-
backend = clearskies.backends.MemoryBackend()
|
|
53
|
-
|
|
54
|
-
id = clearskies.columns.Uuid()
|
|
55
|
-
name = clearskies.columns.String()
|
|
56
|
-
username = clearskies.columns.String()
|
|
57
|
-
age = clearskies.columns.Integer()
|
|
58
|
-
company_id = clearskies.columns.BelongsToId(Company, readable_parent_columns=["id", "name"])
|
|
59
|
-
company = clearskies.columns.BelongsToModel("company_id")
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
wsgi = clearskies.contexts.WsgiRef(
|
|
63
|
-
clearskies.endpoints.AdvancedSearch(
|
|
64
|
-
model_class=User,
|
|
65
|
-
readable_column_names=["id", "name", "username", "age", "company"],
|
|
66
|
-
sortable_column_names=["name", "username", "age", "company.name"],
|
|
67
|
-
searchable_column_names=["id", "name", "username", "age", "company_id", "company.name"],
|
|
68
|
-
default_sort_column_name="name",
|
|
69
|
-
),
|
|
70
|
-
bindings={
|
|
71
|
-
"memory_backend_default_data": [
|
|
72
|
-
{
|
|
73
|
-
"model_class": Company,
|
|
74
|
-
"records": [
|
|
75
|
-
{"id": "5-5-5-5", "name": "Bob's Widgets"},
|
|
76
|
-
{"id": "3-3-3-3", "name": "New Venture"},
|
|
77
|
-
{"id": "7-7-7-7", "name": "Jane's Cool Stuff"},
|
|
78
|
-
],
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"model_class": User,
|
|
82
|
-
"records": [
|
|
83
|
-
{
|
|
84
|
-
"id": "1-2-3-4",
|
|
85
|
-
"name": "Bob Brown",
|
|
86
|
-
"username": "bobbrown",
|
|
87
|
-
"age": 18,
|
|
88
|
-
"company_id": "5-5-5-5",
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"id": "1-2-3-5",
|
|
92
|
-
"name": "Jane Doe",
|
|
93
|
-
"username": "janedoe",
|
|
94
|
-
"age": 52,
|
|
95
|
-
"company_id": "7-7-7-7",
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"id": "1-2-3-6",
|
|
99
|
-
"name": "Greg",
|
|
100
|
-
"username": "greg",
|
|
101
|
-
"age": 37,
|
|
102
|
-
"company_id": "7-7-7-7",
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
"id": "1-2-3-7",
|
|
106
|
-
"name": "Curious George",
|
|
107
|
-
"username": "curious",
|
|
108
|
-
"age": 7,
|
|
109
|
-
"company_id": "3-3-3-3",
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
},
|
|
115
|
-
)
|
|
116
|
-
wsgi()
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
If you invoke the endpoint without any additional data, it will simply list all records:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
$ curl 'http://localhost:8080/' | jq
|
|
123
|
-
{
|
|
124
|
-
"status": "success",
|
|
125
|
-
"error": "",
|
|
126
|
-
"data": [
|
|
127
|
-
{
|
|
128
|
-
"id": "1-2-3-4",
|
|
129
|
-
"name": "Bob Brown",
|
|
130
|
-
"username": "bobbrown",
|
|
131
|
-
"age": 18,
|
|
132
|
-
"company": {
|
|
133
|
-
"id": "5-5-5-5",
|
|
134
|
-
"name": "Bob's Widgets"
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
"id": "1-2-3-7",
|
|
139
|
-
"name": "Curious George",
|
|
140
|
-
"username": "curious",
|
|
141
|
-
"age": 7,
|
|
142
|
-
"company": {
|
|
143
|
-
"id": "3-3-3-3",
|
|
144
|
-
"name": "New Venture"
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
"id": "1-2-3-6",
|
|
149
|
-
"name": "Greg",
|
|
150
|
-
"username": "greg",
|
|
151
|
-
"age": 37,
|
|
152
|
-
"company": {
|
|
153
|
-
"id": "7-7-7-7",
|
|
154
|
-
"name": "Jane's Cool Stuff"
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"id": "1-2-3-5",
|
|
159
|
-
"name": "Jane Doe",
|
|
160
|
-
"username": "janedoe",
|
|
161
|
-
"age": 52,
|
|
162
|
-
"company": {
|
|
163
|
-
"id": "7-7-7-7",
|
|
164
|
-
"name": "Jane's Cool Stuff"
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
],
|
|
168
|
-
"pagination": {
|
|
169
|
-
"number_results": 4,
|
|
170
|
-
"limit": 50,
|
|
171
|
-
"next_page": {}
|
|
172
|
-
},
|
|
173
|
-
"input_errors": {}
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Of course you can also sort and paginate. Keep in mind that pagination is backend-dependent:
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
$ curl 'http://localhost:8080/' -d '{"sort":[ {"column": "name", "direction": "desc"} ], "limit": 2, "start": 1}' | jq
|
|
181
|
-
{
|
|
182
|
-
"status": "success",
|
|
183
|
-
"error": "",
|
|
184
|
-
"data": [
|
|
185
|
-
{
|
|
186
|
-
"id": "1-2-3-6",
|
|
187
|
-
"name": "Greg",
|
|
188
|
-
"username": "greg",
|
|
189
|
-
"age": 37,
|
|
190
|
-
"company": {
|
|
191
|
-
"id": "7-7-7-7",
|
|
192
|
-
"name": "Jane's Cool Stuff"
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
"id": "1-2-3-7",
|
|
197
|
-
"name": "Curious George",
|
|
198
|
-
"username": "curious",
|
|
199
|
-
"age": 7,
|
|
200
|
-
"company": {
|
|
201
|
-
"id": "3-3-3-3",
|
|
202
|
-
"name": "New Venture"
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
],
|
|
206
|
-
"pagination": {
|
|
207
|
-
"number_results": 4,
|
|
208
|
-
"limit": 2,
|
|
209
|
-
"next_page": {
|
|
210
|
-
"start": 3
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
"input_errors": {}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Note that sorting on columns in related models is done via the syntax `relationship_column.column_name`. These
|
|
219
|
-
must be listed as such in the list of sortable/searchable columns, and then you use the same name to sort/search
|
|
220
|
-
by them:
|
|
221
|
-
|
|
222
|
-
```bash
|
|
223
|
-
$ curl 'http://localhost:8080/' -d '{"sort":[ {"column": "company.name", "direction": "desc"}, {"column": "age", "direction": "asc"} ]}' | jq
|
|
224
|
-
{
|
|
225
|
-
"status": "success",
|
|
226
|
-
"error": "",
|
|
227
|
-
"data": [
|
|
228
|
-
{
|
|
229
|
-
"id": "1-2-3-7",
|
|
230
|
-
"name": "Curious George",
|
|
231
|
-
"username": "curious",
|
|
232
|
-
"age": 7,
|
|
233
|
-
"company": {
|
|
234
|
-
"id": "3-3-3-3",
|
|
235
|
-
"name": "New Venture"
|
|
236
|
-
}
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
"id": "1-2-3-6",
|
|
240
|
-
"name": "Greg",
|
|
241
|
-
"username": "greg",
|
|
242
|
-
"age": 37,
|
|
243
|
-
"company": {
|
|
244
|
-
"id": "7-7-7-7",
|
|
245
|
-
"name": "Jane's Cool Stuff"
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
"id": "1-2-3-5",
|
|
250
|
-
"name": "Jane Doe",
|
|
251
|
-
"username": "janedoe",
|
|
252
|
-
"age": 52,
|
|
253
|
-
"company": {
|
|
254
|
-
"id": "7-7-7-7",
|
|
255
|
-
"name": "Jane's Cool Stuff"
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
"id": "1-2-3-4",
|
|
260
|
-
"name": "Bob Brown",
|
|
261
|
-
"username": "bobbrown",
|
|
262
|
-
"age": 18,
|
|
263
|
-
"company": {
|
|
264
|
-
"id": "5-5-5-5",
|
|
265
|
-
"name": "Bob's Widgets"
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
],
|
|
269
|
-
"pagination": {
|
|
270
|
-
"number_results": 4,
|
|
271
|
-
"limit": 50,
|
|
272
|
-
"next_page": {}
|
|
273
|
-
},
|
|
274
|
-
"input_errors": {}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
And finally searching:
|
|
280
|
-
|
|
281
|
-
```bash
|
|
282
|
-
$ curl 'http://localhost:8080/' -d '{"where":[ {"column": "age", "operator": "<=", "value": 37}, {"column": "username", "operator": "in", "value": ["curious", "greg"]} ]}' | jq
|
|
283
|
-
{
|
|
284
|
-
"status": "success",
|
|
285
|
-
"error": "",
|
|
286
|
-
"data": [
|
|
287
|
-
{
|
|
288
|
-
"id": "1-2-3-7",
|
|
289
|
-
"name": "Curious George",
|
|
290
|
-
"username": "curious",
|
|
291
|
-
"age": 7,
|
|
292
|
-
"company": {
|
|
293
|
-
"id": "3-3-3-3",
|
|
294
|
-
"name": "New Venture"
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
"id": "1-2-3-6",
|
|
299
|
-
"name": "Greg",
|
|
300
|
-
"username": "greg",
|
|
301
|
-
"age": 37,
|
|
302
|
-
"company": {
|
|
303
|
-
"id": "7-7-7-7",
|
|
304
|
-
"name": "Jane's Cool Stuff"
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
],
|
|
308
|
-
"pagination": {
|
|
309
|
-
"number_results": 2,
|
|
310
|
-
"limit": 50,
|
|
311
|
-
"next_page": {}
|
|
312
|
-
},
|
|
313
|
-
"input_errors": {}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
In terms of the allowed search operators, the standard list of operators is:
|
|
319
|
-
|
|
320
|
-
* `<=>`
|
|
321
|
-
* `!=`
|
|
322
|
-
* `<=`
|
|
323
|
-
* `>=`
|
|
324
|
-
* `>`
|
|
325
|
-
* `<`
|
|
326
|
-
* `=`
|
|
327
|
-
* `in`
|
|
328
|
-
* `is not null`
|
|
329
|
-
* `is null`
|
|
330
|
-
* `is not`
|
|
331
|
-
* `is`
|
|
332
|
-
* `like`
|
|
333
|
-
|
|
334
|
-
Although not all operators are supported by all columns. You can use `%` with the `LIKE` operator
|
|
335
|
-
to perform a wildcard search.
|
|
336
|
-
|
|
337
|
-
"""
|
|
338
|
-
|
|
339
|
-
@property
|
|
340
|
-
def allowed_request_keys(self) -> list[str]:
|
|
341
|
-
return self.internal_request_keys
|
|
342
|
-
|
|
343
|
-
@property
|
|
344
|
-
def internal_request_keys(self) -> list[str]:
|
|
345
|
-
return ["sort", "limit", "where"]
|
|
346
|
-
|
|
347
|
-
def check_request_data(
|
|
348
|
-
self, request_data: dict[str, Any], query_parameters: dict[str, Any], pagination_data: dict[str, Any]
|
|
349
|
-
) -> None:
|
|
350
|
-
if pagination_data:
|
|
351
|
-
error = self.model.validate_pagination_data(pagination_data, self.auto_case_internal_column_name)
|
|
352
|
-
if error:
|
|
353
|
-
raise clearskies.exceptions.ClientError(error)
|
|
354
|
-
if query_parameters:
|
|
355
|
-
raise clearskies.exceptions.ClientError("Query parameters were found but are not supported.")
|
|
356
|
-
for key in request_data.keys():
|
|
357
|
-
if key not in self.allowed_request_keys:
|
|
358
|
-
raise clearskies.exceptions.ClientError(
|
|
359
|
-
f"Invalid request parameter found in request body: '{key}'. Expected parameters: "
|
|
360
|
-
+ ", ".join([self.auto_case_internal_column_name(key) for key in self.allowed_request_keys])
|
|
361
|
-
)
|
|
362
|
-
self.validate_limit(request_data, {})
|
|
363
|
-
sort_key_name = self.auto_case_internal_column_name("sort")
|
|
364
|
-
sort = request_data.get(sort_key_name, [])
|
|
365
|
-
if not isinstance(sort, list):
|
|
366
|
-
raise clearskies.exceptions.ClientError(
|
|
367
|
-
f"'{sort_key_name}' property in request body should be a list, but I found a value of type "
|
|
368
|
-
+ sort.__class__.__name
|
|
369
|
-
)
|
|
370
|
-
if sort:
|
|
371
|
-
column_key_name = self.auto_case_internal_column_name("column")
|
|
372
|
-
direction_key_name = self.auto_case_internal_column_name("direction")
|
|
373
|
-
for index, sort_entry in enumerate(sort):
|
|
374
|
-
if not isinstance(sort_entry, dict):
|
|
375
|
-
raise clearskies.exceptions.ClientError(
|
|
376
|
-
f"'{sort_key_name}' should be a list of dictionaries, but entry #{index + 1} is a value of type '{sort_entry.__class__.__name}', not a dict"
|
|
377
|
-
)
|
|
378
|
-
for key_name in [column_key_name, direction_key_name]:
|
|
379
|
-
if not sort_entry.get(key_name):
|
|
380
|
-
raise clearskies.exceptions.ClientError(
|
|
381
|
-
f"Each entry in the sort list should contain both '{column_key_name}' and '{direction_key_name}' but entry #{index + 1} is missing '{key_name}'"
|
|
382
|
-
)
|
|
383
|
-
if not isinstance(sort_entry[key_name], str):
|
|
384
|
-
raise clearskies.exceptions.ClientError(
|
|
385
|
-
f"{key_name}' must be a string, but for entry #{index + 1} it is a value of type "
|
|
386
|
-
+ sort_entry[key_name].__class__.__name__
|
|
387
|
-
)
|
|
388
|
-
if sort_entry[direction_key_name].lower() not in ["asc", "desc"]:
|
|
389
|
-
raise clearskies.exceptions.ClientError(
|
|
390
|
-
f"{direction_key_name}' must be either 'ASC' or 'DESC', but a different value was found for entry #{index + 1}"
|
|
391
|
-
)
|
|
392
|
-
if self.auto_case_column_name(sort_entry[column_key_name], False) not in self.sortable_column_names:
|
|
393
|
-
raise clearskies.exceptions.ClientError(
|
|
394
|
-
f"Invalid sort column for entry #{index + 1}. Allowed values are: "
|
|
395
|
-
+ ", ".join(
|
|
396
|
-
[
|
|
397
|
-
self.auto_case_column_name(column_name, False)
|
|
398
|
-
for column_name in self.sortable_column_names
|
|
399
|
-
]
|
|
400
|
-
)
|
|
401
|
-
)
|
|
402
|
-
where_key_name = self.auto_case_internal_column_name("where")
|
|
403
|
-
where = request_data.get(where_key_name, [])
|
|
404
|
-
if not isinstance(where, list):
|
|
405
|
-
raise clearskies.exceptions.ClientError(
|
|
406
|
-
f"'{where_key_name}' property in request body should be a list, but I found a value of type "
|
|
407
|
-
+ where.__class__.__name
|
|
408
|
-
)
|
|
409
|
-
if where:
|
|
410
|
-
column_key_name = self.auto_case_internal_column_name("column")
|
|
411
|
-
operator_key_name = self.auto_case_internal_column_name("operator")
|
|
412
|
-
value_key_name = self.auto_case_internal_column_name("value")
|
|
413
|
-
for index, where_entry in enumerate(where):
|
|
414
|
-
if not isinstance(where_entry, dict):
|
|
415
|
-
raise clearskies.exceptions.ClientError(
|
|
416
|
-
f"'{where_key_name}' should be a list of dictionaries, but entry #{index + 1} is a value of type '{where_entry.__class__.__name}', not a dict"
|
|
417
|
-
)
|
|
418
|
-
for key_name in [column_key_name, operator_key_name, value_key_name]:
|
|
419
|
-
if key_name not in where_entry:
|
|
420
|
-
raise clearskies.exceptions.ClientError(
|
|
421
|
-
f"Each entry in the where list should contain '{column_key_name}', '{operator_key_name}', and '{value_key_name}', but entry #{index + 1} is missing '{key_name}'"
|
|
422
|
-
)
|
|
423
|
-
if key_name != value_key_name and not isinstance(where_entry[key_name], str):
|
|
424
|
-
raise clearskies.exceptions.ClientError(
|
|
425
|
-
f"{key_name}' must be a string, but for entry #{index + 1} it is a value of type "
|
|
426
|
-
+ sort_entry[key_name].__class__.__name__
|
|
427
|
-
)
|
|
428
|
-
if where_entry[column_key_name] not in self.searchable_column_names:
|
|
429
|
-
raise clearskies.exceptions.ClientError(
|
|
430
|
-
f"Invalid where column for entry #{index + 1}. Allowed values are: "
|
|
431
|
-
+ ", ".join(
|
|
432
|
-
[
|
|
433
|
-
self.auto_case_column_name(column_name, True)
|
|
434
|
-
for column_name in self.searchable_column_names
|
|
435
|
-
]
|
|
436
|
-
)
|
|
437
|
-
)
|
|
438
|
-
[relationship_column_name, column_name] = self.unpack_column_name_with_relationship(
|
|
439
|
-
self.auto_case_column_name(where_entry[column_key_name], False),
|
|
440
|
-
)
|
|
441
|
-
operator = where_entry[operator_key_name].lower()
|
|
442
|
-
value = where_entry[value_key_name]
|
|
443
|
-
error_allowed_operators = None
|
|
444
|
-
if relationship_column_name:
|
|
445
|
-
column = self.columns[relationship_column_name]
|
|
446
|
-
if not column.is_allowed_search_operator(operator, relationship_reference=column_name):
|
|
447
|
-
error_allowed_operators = column.allowed_search_operators(
|
|
448
|
-
relationship_reference=column_name
|
|
449
|
-
)
|
|
450
|
-
else:
|
|
451
|
-
error = column.check_search_value(
|
|
452
|
-
value if operator != "in" else value[0],
|
|
453
|
-
where_entry[operator_key_name],
|
|
454
|
-
relationship_reference=column_name,
|
|
455
|
-
)
|
|
456
|
-
else:
|
|
457
|
-
column = self.columns[column_name]
|
|
458
|
-
if not column.is_allowed_search_operator(operator):
|
|
459
|
-
error_allowed_operators = column.allowed_search_operators()
|
|
460
|
-
else:
|
|
461
|
-
error = column.check_search_value(
|
|
462
|
-
value if operator != "in" else value[0], where_entry[operator_key_name]
|
|
463
|
-
)
|
|
464
|
-
if error_allowed_operators:
|
|
465
|
-
raise clearskies.exceptions.ClientError(
|
|
466
|
-
f"Invalid operator for entry #{index + 1}. Allowed operators are: "
|
|
467
|
-
+ ", ".join(column.allowed_search_operators(relationship_reference=column_name))
|
|
468
|
-
)
|
|
469
|
-
if error:
|
|
470
|
-
raise clearskies.exceptions.ClientError(f"Invalid search value for entry #{index + 1}: {error}")
|
|
471
|
-
|
|
472
|
-
def configure_model_from_request_data(
|
|
473
|
-
self,
|
|
474
|
-
model: Model,
|
|
475
|
-
request_data: dict[str, Any],
|
|
476
|
-
query_parameters: dict[str, Any],
|
|
477
|
-
pagination_data: dict[str, Any],
|
|
478
|
-
) -> Model:
|
|
479
|
-
if pagination_data:
|
|
480
|
-
model = model.pagination(**pagination_data)
|
|
481
|
-
sort = request_data.get(self.auto_case_internal_column_name("sort"), [])
|
|
482
|
-
if sort:
|
|
483
|
-
column_key_name = self.auto_case_internal_column_name("column")
|
|
484
|
-
direction_key_name = self.auto_case_internal_column_name("direction")
|
|
485
|
-
model = self.add_join(sort[0][column_key_name], model)
|
|
486
|
-
[primary_table_name, primary_column_name] = self.resolve_references_for_query(sort[0][column_key_name])
|
|
487
|
-
primary_direction = sort[0][direction_key_name]
|
|
488
|
-
|
|
489
|
-
if len(sort) > 1:
|
|
490
|
-
[secondary_table_name, secondary_column_name] = self.resolve_references_for_query(
|
|
491
|
-
sort[1][column_key_name]
|
|
492
|
-
)
|
|
493
|
-
secondary_direction = sort[1][direction_key_name]
|
|
494
|
-
else:
|
|
495
|
-
secondary_column_name = ""
|
|
496
|
-
secondary_direction = ""
|
|
497
|
-
secondary_table_name = ""
|
|
498
|
-
model = model.sort_by(
|
|
499
|
-
primary_column_name if primary_column_name else "",
|
|
500
|
-
primary_direction if primary_direction else "",
|
|
501
|
-
primary_table_name=primary_table_name if primary_table_name else "",
|
|
502
|
-
secondary_column_name=secondary_column_name if secondary_column_name else "",
|
|
503
|
-
secondary_direction=secondary_direction if secondary_direction else "",
|
|
504
|
-
secondary_table_name=secondary_table_name if secondary_table_name else "",
|
|
505
|
-
)
|
|
506
|
-
if request_data.get("limit"):
|
|
507
|
-
model = model.limit(request_data["limit"])
|
|
508
|
-
|
|
509
|
-
for where in request_data.get(self.auto_case_internal_column_name("where"), []):
|
|
510
|
-
raw_column_name = self.auto_case_column_name(where[self.auto_case_internal_column_name("column")], False)
|
|
511
|
-
[relationship_column_name, column_name] = self.unpack_column_name_with_relationship(raw_column_name)
|
|
512
|
-
operator = where[self.auto_case_internal_column_name("operator")].lower()
|
|
513
|
-
value = where[self.auto_case_internal_column_name("value")]
|
|
514
|
-
|
|
515
|
-
model = self.add_join(raw_column_name, model)
|
|
516
|
-
if relationship_column_name:
|
|
517
|
-
model = self.columns[relationship_column_name].add_search(
|
|
518
|
-
model, value, operator=operator, relationship_reference=column_name
|
|
519
|
-
)
|
|
520
|
-
else:
|
|
521
|
-
model = self.columns[column_name].add_search(model, value, operator=operator)
|
|
522
|
-
|
|
523
|
-
return model
|
|
524
|
-
|
|
525
|
-
def documentation_url_search_parameters(self) -> list[autodoc.request.Parameter]:
|
|
526
|
-
return []
|