clear-skies 1.22.10__py3-none-any.whl → 2.0.23__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.
- clear_skies-2.0.23.dist-info/METADATA +76 -0
- clear_skies-2.0.23.dist-info/RECORD +265 -0
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info}/WHEEL +1 -1
- clearskies/__init__.py +37 -21
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +8 -39
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +14 -8
- clearskies/authentication/authorization_pass_through.py +14 -10
- clearskies/authentication/jwks.py +135 -58
- clearskies/authentication/public.py +3 -26
- clearskies/authentication/secret_bearer.py +515 -44
- clearskies/autodoc/formats/oai3_json/__init__.py +2 -2
- clearskies/autodoc/formats/oai3_json/oai3_json.py +11 -9
- clearskies/autodoc/formats/oai3_json/parameter.py +6 -3
- clearskies/autodoc/formats/oai3_json/request.py +7 -5
- clearskies/autodoc/formats/oai3_json/response.py +7 -4
- clearskies/autodoc/formats/oai3_json/schema/object.py +10 -1
- clearskies/autodoc/request/__init__.py +2 -0
- clearskies/autodoc/request/header.py +4 -6
- clearskies/autodoc/request/json_body.py +4 -6
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +16 -4
- clearskies/autodoc/request/url_parameter.py +4 -6
- clearskies/autodoc/request/url_path.py +4 -6
- clearskies/autodoc/schema/__init__.py +4 -2
- clearskies/autodoc/schema/array.py +5 -6
- clearskies/autodoc/schema/boolean.py +4 -10
- clearskies/autodoc/schema/date.py +0 -3
- clearskies/autodoc/schema/datetime.py +1 -4
- clearskies/autodoc/schema/double.py +0 -3
- clearskies/autodoc/schema/enum.py +4 -2
- clearskies/autodoc/schema/integer.py +4 -9
- clearskies/autodoc/schema/long.py +0 -3
- clearskies/autodoc/schema/number.py +4 -9
- clearskies/autodoc/schema/object.py +5 -7
- clearskies/autodoc/schema/password.py +0 -3
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +4 -10
- clearskies/backends/__init__.py +55 -20
- clearskies/backends/api_backend.py +1118 -280
- clearskies/backends/backend.py +54 -85
- clearskies/backends/cursor_backend.py +246 -191
- clearskies/backends/memory_backend.py +514 -208
- clearskies/backends/secrets_backend.py +68 -31
- clearskies/column.py +1221 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +306 -0
- clearskies/columns/belongs_to_id.py +478 -0
- clearskies/columns/belongs_to_model.py +129 -0
- clearskies/columns/belongs_to_self.py +109 -0
- clearskies/columns/boolean.py +110 -0
- clearskies/columns/category_tree.py +273 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +126 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +92 -0
- clearskies/columns/created_by_authorization_data.py +114 -0
- clearskies/columns/created_by_header.py +103 -0
- clearskies/columns/created_by_ip.py +90 -0
- clearskies/columns/created_by_routing_data.py +102 -0
- clearskies/columns/created_by_user_agent.py +89 -0
- clearskies/columns/date.py +232 -0
- clearskies/columns/datetime.py +284 -0
- clearskies/columns/email.py +78 -0
- clearskies/columns/float.py +149 -0
- clearskies/columns/has_many.py +529 -0
- clearskies/columns/has_many_self.py +62 -0
- clearskies/columns/has_one.py +21 -0
- clearskies/columns/integer.py +158 -0
- clearskies/columns/json.py +126 -0
- clearskies/columns/many_to_many_ids.py +335 -0
- clearskies/columns/many_to_many_ids_with_data.py +274 -0
- clearskies/columns/many_to_many_models.py +156 -0
- clearskies/columns/many_to_many_pivots.py +132 -0
- clearskies/columns/phone.py +162 -0
- clearskies/columns/select.py +95 -0
- clearskies/columns/string.py +102 -0
- clearskies/columns/timestamp.py +164 -0
- clearskies/columns/updated.py +107 -0
- clearskies/columns/uuid.py +83 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +170 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +15 -0
- clearskies/configs/any_dict.py +24 -0
- clearskies/configs/any_dict_or_callable.py +25 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +18 -0
- clearskies/configs/boolean_or_callable.py +20 -0
- clearskies/configs/callable_config.py +20 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +26 -0
- clearskies/configs/datetime.py +20 -0
- clearskies/configs/datetime_or_callable.py +21 -0
- clearskies/configs/email.py +10 -0
- clearskies/configs/email_list.py +17 -0
- clearskies/configs/email_list_or_callable.py +17 -0
- clearskies/configs/email_or_email_list_or_callable.py +59 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +29 -0
- clearskies/configs/float.py +18 -0
- clearskies/configs/float_or_callable.py +20 -0
- clearskies/configs/headers.py +28 -0
- clearskies/configs/integer.py +18 -0
- clearskies/configs/integer_or_callable.py +20 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +32 -0
- clearskies/configs/list_any_dict_or_callable.py +33 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +67 -0
- clearskies/configs/model_columns.py +58 -0
- clearskies/configs/model_destination_name.py +26 -0
- clearskies/configs/model_to_id_column.py +45 -0
- clearskies/configs/readable_model_column.py +11 -0
- clearskies/configs/readable_model_columns.py +11 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +11 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +28 -0
- clearskies/configs/select_list.py +49 -0
- clearskies/configs/string.py +31 -0
- clearskies/configs/string_dict.py +34 -0
- clearskies/configs/string_list.py +47 -0
- clearskies/configs/string_list_or_callable.py +48 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +20 -0
- clearskies/configs/timezone.py +20 -0
- clearskies/configs/url.py +25 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +11 -0
- clearskies/configs/writeable_model_columns.py +11 -0
- clearskies/configurable.py +78 -0
- clearskies/contexts/__init__.py +8 -8
- clearskies/contexts/cli.py +129 -43
- clearskies/contexts/context.py +93 -56
- clearskies/contexts/wsgi.py +79 -33
- clearskies/contexts/wsgi_ref.py +87 -0
- clearskies/cursors/__init__.py +7 -0
- clearskies/cursors/cursor.py +166 -0
- clearskies/cursors/from_environment/__init__.py +5 -0
- clearskies/cursors/from_environment/mysql.py +51 -0
- clearskies/cursors/from_environment/postgresql.py +49 -0
- clearskies/cursors/from_environment/sqlite.py +35 -0
- clearskies/cursors/mysql.py +61 -0
- clearskies/cursors/postgresql.py +61 -0
- clearskies/cursors/sqlite.py +62 -0
- clearskies/decorators.py +33 -0
- clearskies/decorators.pyi +10 -0
- clearskies/di/__init__.py +11 -7
- clearskies/di/additional_config.py +115 -4
- clearskies/di/additional_config_auto_import.py +12 -0
- clearskies/di/di.py +714 -125
- clearskies/di/inject/__init__.py +23 -0
- clearskies/di/inject/akeyless_sdk.py +16 -0
- clearskies/di/inject/by_class.py +24 -0
- clearskies/di/inject/by_name.py +22 -0
- clearskies/di/inject/di.py +16 -0
- clearskies/di/inject/environment.py +15 -0
- clearskies/di/inject/input_output.py +19 -0
- clearskies/di/inject/now.py +16 -0
- clearskies/di/inject/requests.py +16 -0
- clearskies/di/inject/secrets.py +15 -0
- clearskies/di/inject/utcnow.py +16 -0
- clearskies/di/inject/uuid.py +16 -0
- clearskies/di/injectable.py +32 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/end.py +219 -0
- clearskies/endpoint.py +1303 -0
- clearskies/endpoint_group.py +333 -0
- clearskies/endpoints/__init__.py +25 -0
- clearskies/endpoints/advanced_search.py +519 -0
- clearskies/endpoints/callable.py +382 -0
- clearskies/endpoints/create.py +201 -0
- clearskies/endpoints/delete.py +133 -0
- clearskies/endpoints/get.py +267 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +567 -0
- clearskies/endpoints/restful_api.py +417 -0
- clearskies/endpoints/schema.py +185 -0
- clearskies/endpoints/simple_search.py +279 -0
- clearskies/endpoints/update.py +188 -0
- clearskies/environment.py +7 -3
- clearskies/exceptions/__init__.py +19 -0
- clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/functional/__init__.py +2 -2
- clearskies/functional/json.py +47 -0
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +19 -11
- clearskies/functional/validations.py +61 -9
- clearskies/input_outputs/__init__.py +9 -7
- clearskies/input_outputs/cli.py +135 -160
- clearskies/input_outputs/exceptions/__init__.py +6 -1
- clearskies/input_outputs/headers.py +54 -0
- clearskies/input_outputs/input_output.py +77 -123
- clearskies/input_outputs/programmatic.py +62 -0
- clearskies/input_outputs/wsgi.py +36 -48
- clearskies/model.py +1874 -193
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +228 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +193 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +4 -31
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
- clearskies/secrets/akeyless.py +421 -155
- clearskies/secrets/exceptions/__init__.py +7 -1
- clearskies/secrets/exceptions/not_found_error.py +2 -0
- clearskies/secrets/exceptions/permissions_error.py +2 -0
- clearskies/secrets/secrets.py +12 -11
- clearskies/security_header.py +17 -0
- clearskies/security_headers/__init__.py +8 -8
- clearskies/security_headers/cache_control.py +47 -109
- clearskies/security_headers/cors.py +38 -92
- clearskies/security_headers/csp.py +76 -150
- clearskies/security_headers/hsts.py +14 -15
- clearskies/typing.py +11 -0
- clearskies/validator.py +36 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +61 -0
- clearskies/validators/before_column.py +15 -0
- clearskies/validators/in_the_future.py +29 -0
- clearskies/validators/in_the_future_at_least.py +13 -0
- clearskies/validators/in_the_future_at_most.py +12 -0
- clearskies/validators/in_the_past.py +29 -0
- clearskies/validators/in_the_past_at_least.py +12 -0
- clearskies/validators/in_the_past_at_most.py +12 -0
- clearskies/validators/maximum_length.py +25 -0
- clearskies/validators/maximum_value.py +28 -0
- clearskies/validators/minimum_length.py +25 -0
- clearskies/validators/minimum_value.py +28 -0
- clearskies/{input_requirements → validators}/required.py +18 -9
- clearskies/validators/timedelta.py +58 -0
- clearskies/validators/unique.py +28 -0
- clear_skies-1.22.10.dist-info/METADATA +0 -47
- clear_skies-1.22.10.dist-info/RECORD +0 -213
- clearskies/application.py +0 -29
- clearskies/authentication/auth0_jwks.py +0 -118
- clearskies/authentication/auth_exception.py +0 -2
- clearskies/authentication/jwks_jwcrypto.py +0 -51
- clearskies/backends/api_get_only_backend.py +0 -48
- clearskies/backends/example_backend.py +0 -43
- clearskies/backends/file_backend.py +0 -48
- clearskies/backends/json_backend.py +0 -7
- clearskies/backends/restful_api_advanced_search_backend.py +0 -103
- clearskies/binding_config.py +0 -16
- clearskies/column_types/__init__.py +0 -203
- clearskies/column_types/audit.py +0 -249
- clearskies/column_types/belongs_to.py +0 -271
- clearskies/column_types/boolean.py +0 -60
- clearskies/column_types/category_tree.py +0 -304
- clearskies/column_types/column.py +0 -373
- clearskies/column_types/created.py +0 -26
- clearskies/column_types/created_by_authorization_data.py +0 -26
- clearskies/column_types/created_by_header.py +0 -24
- clearskies/column_types/created_by_ip.py +0 -17
- clearskies/column_types/created_by_routing_data.py +0 -25
- clearskies/column_types/created_by_user_agent.py +0 -17
- clearskies/column_types/created_micro.py +0 -26
- clearskies/column_types/datetime.py +0 -109
- clearskies/column_types/datetime_micro.py +0 -13
- clearskies/column_types/email.py +0 -18
- clearskies/column_types/float.py +0 -43
- clearskies/column_types/has_many.py +0 -179
- clearskies/column_types/has_one.py +0 -58
- clearskies/column_types/integer.py +0 -41
- clearskies/column_types/json.py +0 -25
- clearskies/column_types/many_to_many.py +0 -278
- clearskies/column_types/many_to_many_with_data.py +0 -162
- clearskies/column_types/phone.py +0 -48
- clearskies/column_types/select.py +0 -11
- clearskies/column_types/string.py +0 -24
- clearskies/column_types/timestamp.py +0 -73
- clearskies/column_types/updated.py +0 -26
- clearskies/column_types/updated_micro.py +0 -26
- clearskies/column_types/uuid.py +0 -25
- clearskies/columns.py +0 -123
- clearskies/condition_parser.py +0 -172
- clearskies/contexts/build_context.py +0 -54
- clearskies/contexts/convert_to_application.py +0 -190
- clearskies/contexts/extract_handler.py +0 -37
- clearskies/contexts/test.py +0 -94
- clearskies/decorators/__init__.py +0 -39
- clearskies/decorators/auth0_jwks.py +0 -22
- clearskies/decorators/authorization.py +0 -10
- clearskies/decorators/binding_classes.py +0 -9
- clearskies/decorators/binding_modules.py +0 -9
- clearskies/decorators/bindings.py +0 -9
- clearskies/decorators/create.py +0 -10
- clearskies/decorators/delete.py +0 -10
- clearskies/decorators/docs.py +0 -14
- clearskies/decorators/get.py +0 -10
- clearskies/decorators/jwks.py +0 -26
- clearskies/decorators/merge.py +0 -124
- clearskies/decorators/patch.py +0 -10
- clearskies/decorators/post.py +0 -10
- clearskies/decorators/public.py +0 -11
- clearskies/decorators/response_headers.py +0 -10
- clearskies/decorators/return_raw_response.py +0 -9
- clearskies/decorators/schema.py +0 -10
- clearskies/decorators/secret_bearer.py +0 -24
- clearskies/decorators/security_headers.py +0 -10
- clearskies/di/standard_dependencies.py +0 -151
- 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/handlers/__init__.py +0 -41
- clearskies/handlers/advanced_search.py +0 -271
- clearskies/handlers/base.py +0 -479
- clearskies/handlers/callable.py +0 -191
- clearskies/handlers/create.py +0 -35
- clearskies/handlers/crud_by_method.py +0 -18
- clearskies/handlers/database_connector.py +0 -32
- clearskies/handlers/delete.py +0 -61
- clearskies/handlers/exceptions/__init__.py +0 -5
- clearskies/handlers/exceptions/not_found.py +0 -3
- clearskies/handlers/get.py +0 -156
- clearskies/handlers/health_check.py +0 -59
- clearskies/handlers/input_processing.py +0 -79
- clearskies/handlers/list.py +0 -530
- clearskies/handlers/mygrations.py +0 -82
- clearskies/handlers/request_method_routing.py +0 -47
- clearskies/handlers/restful_api.py +0 -218
- clearskies/handlers/routing.py +0 -62
- clearskies/handlers/schema_helper.py +0 -128
- clearskies/handlers/simple_routing.py +0 -206
- clearskies/handlers/simple_routing_route.py +0 -192
- clearskies/handlers/simple_search.py +0 -136
- clearskies/handlers/update.py +0 -96
- clearskies/handlers/write.py +0 -193
- clearskies/input_requirements/__init__.py +0 -78
- clearskies/input_requirements/after.py +0 -36
- clearskies/input_requirements/before.py +0 -36
- clearskies/input_requirements/in_the_future_at_least.py +0 -19
- clearskies/input_requirements/in_the_future_at_most.py +0 -19
- clearskies/input_requirements/in_the_past_at_least.py +0 -19
- clearskies/input_requirements/in_the_past_at_most.py +0 -19
- clearskies/input_requirements/maximum_length.py +0 -19
- clearskies/input_requirements/maximum_value.py +0 -19
- clearskies/input_requirements/minimum_length.py +0 -22
- clearskies/input_requirements/minimum_value.py +0 -19
- clearskies/input_requirements/requirement.py +0 -25
- clearskies/input_requirements/time_delta.py +0 -38
- clearskies/input_requirements/unique.py +0 -18
- clearskies/mocks/__init__.py +0 -7
- clearskies/mocks/input_output.py +0 -124
- clearskies/mocks/models.py +0 -142
- clearskies/models.py +0 -350
- clearskies/security_headers/base.py +0 -12
- clearskies/tests/simple_api/models/__init__.py +0 -2
- clearskies/tests/simple_api/models/status.py +0 -23
- clearskies/tests/simple_api/models/user.py +0 -21
- clearskies/tests/simple_api/users_api.py +0 -64
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info/licenses}/LICENSE +0 -0
- /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
- /clearskies/{secrets/exceptions → exceptions}/not_found.py +0 -0
- /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
- /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Self
|
|
4
|
+
|
|
5
|
+
from clearskies import configs, configurable, decorators, di, end
|
|
6
|
+
from clearskies.authentication import Authentication, Authorization, Public
|
|
7
|
+
from clearskies.endpoint import Endpoint
|
|
8
|
+
from clearskies.functional import routing
|
|
9
|
+
from clearskies.input_outputs import InputOutput
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from clearskies import SecurityHeader
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EndpointGroup(
|
|
16
|
+
end.End, # type: ignore
|
|
17
|
+
configurable.Configurable,
|
|
18
|
+
di.InjectableProperties,
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
An endpoint group brings endpoints together: it basically handles routing.
|
|
22
|
+
|
|
23
|
+
The endpoint group accepts a list of endpoints/endpoint groups and routes requests to them. You can set a URL for
|
|
24
|
+
the endpoint group, and this becomes a URL prefix for all of the endpoints under it. Note that all routing is
|
|
25
|
+
greedy, which means you want to put endpoints with more specific URLs first. Here's an example of how
|
|
26
|
+
you can use them to build a fully functional API that manages both users and companies. Each individual
|
|
27
|
+
endpoint is defined for the purpose of the example, but note that in practice you could accomplish this same
|
|
28
|
+
thing with much less code by using the RestfulApi endpoint:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import clearskies
|
|
32
|
+
from clearskies.validators import Required, Unique
|
|
33
|
+
from clearskies import columns
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Company(clearskies.Model):
|
|
37
|
+
id_column_name = "id"
|
|
38
|
+
backend = clearskies.backends.MemoryBackend()
|
|
39
|
+
|
|
40
|
+
id = columns.Uuid()
|
|
41
|
+
name = columns.String(
|
|
42
|
+
validators=[
|
|
43
|
+
Required(),
|
|
44
|
+
Unique(),
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class User(clearskies.Model):
|
|
50
|
+
id_column_name = "id"
|
|
51
|
+
backend = clearskies.backends.MemoryBackend()
|
|
52
|
+
|
|
53
|
+
id = columns.Uuid()
|
|
54
|
+
name = columns.String(validators=[Required()])
|
|
55
|
+
username = columns.String(
|
|
56
|
+
validators=[
|
|
57
|
+
Required(),
|
|
58
|
+
Unique(),
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
age = columns.Integer(validators=[Required()])
|
|
62
|
+
created_at = columns.Created()
|
|
63
|
+
updated_at = columns.Updated()
|
|
64
|
+
company_id = columns.BelongsToId(
|
|
65
|
+
Company,
|
|
66
|
+
readable_parent_columns=["id", "name"],
|
|
67
|
+
validators=[Required()],
|
|
68
|
+
)
|
|
69
|
+
company = columns.BelongsToModel("company_id")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
readable_user_column_names = [
|
|
73
|
+
"id",
|
|
74
|
+
"name",
|
|
75
|
+
"username",
|
|
76
|
+
"age",
|
|
77
|
+
"created_at",
|
|
78
|
+
"updated_at",
|
|
79
|
+
"company",
|
|
80
|
+
]
|
|
81
|
+
writeable_user_column_names = ["name", "username", "age", "company_id"]
|
|
82
|
+
users_api = clearskies.EndpointGroup(
|
|
83
|
+
[
|
|
84
|
+
clearskies.endpoints.Update(
|
|
85
|
+
model_class=User,
|
|
86
|
+
url="/:id",
|
|
87
|
+
readable_column_names=readable_user_column_names,
|
|
88
|
+
writeable_column_names=writeable_user_column_names,
|
|
89
|
+
),
|
|
90
|
+
clearskies.endpoints.Delete(
|
|
91
|
+
model_class=User,
|
|
92
|
+
url="/:id",
|
|
93
|
+
),
|
|
94
|
+
clearskies.endpoints.Get(
|
|
95
|
+
model_class=User,
|
|
96
|
+
url="/:id",
|
|
97
|
+
readable_column_names=readable_user_column_names,
|
|
98
|
+
),
|
|
99
|
+
clearskies.endpoints.Create(
|
|
100
|
+
model_class=User,
|
|
101
|
+
readable_column_names=readable_user_column_names,
|
|
102
|
+
writeable_column_names=writeable_user_column_names,
|
|
103
|
+
),
|
|
104
|
+
clearskies.endpoints.SimpleSearch(
|
|
105
|
+
model_class=User,
|
|
106
|
+
readable_column_names=readable_user_column_names,
|
|
107
|
+
sortable_column_names=readable_user_column_names,
|
|
108
|
+
searchable_column_names=readable_user_column_names,
|
|
109
|
+
default_sort_column_name="name",
|
|
110
|
+
),
|
|
111
|
+
],
|
|
112
|
+
url="users",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
readable_company_column_names = ["id", "name"]
|
|
116
|
+
writeable_company_column_names = ["name"]
|
|
117
|
+
companies_api = clearskies.EndpointGroup(
|
|
118
|
+
[
|
|
119
|
+
clearskies.endpoints.Update(
|
|
120
|
+
model_class=Company,
|
|
121
|
+
url="/:id",
|
|
122
|
+
readable_column_names=readable_company_column_names,
|
|
123
|
+
writeable_column_names=writeable_company_column_names,
|
|
124
|
+
),
|
|
125
|
+
clearskies.endpoints.Delete(
|
|
126
|
+
model_class=Company,
|
|
127
|
+
url="/:id",
|
|
128
|
+
),
|
|
129
|
+
clearskies.endpoints.Get(
|
|
130
|
+
model_class=Company,
|
|
131
|
+
url="/:id",
|
|
132
|
+
readable_column_names=readable_company_column_names,
|
|
133
|
+
),
|
|
134
|
+
clearskies.endpoints.Create(
|
|
135
|
+
model_class=Company,
|
|
136
|
+
readable_column_names=readable_company_column_names,
|
|
137
|
+
writeable_column_names=writeable_company_column_names,
|
|
138
|
+
),
|
|
139
|
+
clearskies.endpoints.SimpleSearch(
|
|
140
|
+
model_class=Company,
|
|
141
|
+
readable_column_names=readable_company_column_names,
|
|
142
|
+
sortable_column_names=readable_company_column_names,
|
|
143
|
+
searchable_column_names=readable_company_column_names,
|
|
144
|
+
default_sort_column_name="name",
|
|
145
|
+
),
|
|
146
|
+
],
|
|
147
|
+
url="companies",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
wsgi = clearskies.contexts.WsgiRef(clearskies.EndpointGroup([users_api, companies_api]))
|
|
151
|
+
wsgi()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Usage then works exactly as expected:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
$ curl 'http://localhost:8080/companies' -d '{"name": "Box Store"}' | jq
|
|
158
|
+
{
|
|
159
|
+
"status": "success",
|
|
160
|
+
"error": "",
|
|
161
|
+
"data": {
|
|
162
|
+
"id": "f073ee4d-318d-4e0b-a796-f450c40aa771",
|
|
163
|
+
"name": "Box Store"
|
|
164
|
+
},
|
|
165
|
+
"pagination": {},
|
|
166
|
+
"input_errors": {}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
curl 'http://localhost:8080/users' -d '{"name": "Bob Brown", "username": "bobbrown", "age": 25, "company_id": "f073ee4d-318d-4e0b-a796-f450c40aa771"}'
|
|
170
|
+
curl 'http://localhost:8080/users' -d '{"name": "Jane Doe", "username": "janedoe", "age": 32, "company_id": "f073ee4d-318d-4e0b-a796-f450c40aa771"}'
|
|
171
|
+
|
|
172
|
+
$ curl 'http://localhost:8080/users' | jq
|
|
173
|
+
{
|
|
174
|
+
"status": "success",
|
|
175
|
+
"error": "",
|
|
176
|
+
"data": [
|
|
177
|
+
{
|
|
178
|
+
"id": "68cbb9e9-689a-4ae0-af77-d60e4cb344f1",
|
|
179
|
+
"name": "Bob Brown",
|
|
180
|
+
"username": "bobbrown",
|
|
181
|
+
"age": 25,
|
|
182
|
+
"created_at": "2025-06-08T10:40:37+00:00",
|
|
183
|
+
"updated_at": "2025-06-08T10:40:37+00:00",
|
|
184
|
+
"company": {
|
|
185
|
+
"id": "f073ee4d-318d-4e0b-a796-f450c40aa771",
|
|
186
|
+
"name": "Box Store"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "e69c4ebf-38b1-40d2-b523-5d58f5befc7b",
|
|
191
|
+
"name": "Jane Doe",
|
|
192
|
+
"username": "janedoe",
|
|
193
|
+
"age": 32,
|
|
194
|
+
"created_at": "2025-06-08T10:41:04+00:00",
|
|
195
|
+
"updated_at": "2025-06-08T10:41:04+00:00",
|
|
196
|
+
"company": {
|
|
197
|
+
"id": "f073ee4d-318d-4e0b-a796-f450c40aa771",
|
|
198
|
+
"name": "Box Store"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
"pagination": {
|
|
203
|
+
"number_results": 2,
|
|
204
|
+
"limit": 50,
|
|
205
|
+
"next_page": {}
|
|
206
|
+
},
|
|
207
|
+
"input_errors": {}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
The dependency injection container
|
|
215
|
+
"""
|
|
216
|
+
di = di.inject.Di()
|
|
217
|
+
|
|
218
|
+
"""
|
|
219
|
+
The base URL for the endpoint group.
|
|
220
|
+
|
|
221
|
+
This URL is added as a prefix to all endpoints attached to the group. This includes any named URL parameters:
|
|
222
|
+
"""
|
|
223
|
+
url = configs.String(default="")
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
The list of endpoints connected to this endpoint group
|
|
227
|
+
"""
|
|
228
|
+
endpoints = configs.EndpointList()
|
|
229
|
+
|
|
230
|
+
internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
231
|
+
external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
232
|
+
response_headers = configs.StringListOrCallable(default=[])
|
|
233
|
+
authentication = configs.Authentication(default=Public())
|
|
234
|
+
authorization = configs.Authorization(default=Authorization())
|
|
235
|
+
security_headers = configs.SecurityHeaders(default=[])
|
|
236
|
+
|
|
237
|
+
cors_header: SecurityHeader = None # type: ignore
|
|
238
|
+
has_cors: bool = False
|
|
239
|
+
endpoints_initialized = False
|
|
240
|
+
|
|
241
|
+
@decorators.parameters_to_properties
|
|
242
|
+
def __init__(
|
|
243
|
+
self,
|
|
244
|
+
endpoints: list[Endpoint | Self],
|
|
245
|
+
url: str = "",
|
|
246
|
+
response_headers: list[str | Callable[..., list[str]]] = [],
|
|
247
|
+
security_headers: list[SecurityHeader] = [],
|
|
248
|
+
internal_casing: str = "snake_case",
|
|
249
|
+
external_casing: str = "snake_case",
|
|
250
|
+
authentication: Authentication = Public(),
|
|
251
|
+
authorization: Authorization = Authorization(),
|
|
252
|
+
):
|
|
253
|
+
self.finalize_and_validate_configuration()
|
|
254
|
+
for security_header in self.security_headers:
|
|
255
|
+
if not security_header.is_cors:
|
|
256
|
+
continue
|
|
257
|
+
self.cors_header = security_header
|
|
258
|
+
self.has_cors = True
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
if not endpoints:
|
|
262
|
+
raise ValueError(
|
|
263
|
+
"An endpoint group must receive a list of endpoints/endpoint groups, but my list of endpoints is empty."
|
|
264
|
+
)
|
|
265
|
+
if not isinstance(endpoints, list):
|
|
266
|
+
raise ValueError(
|
|
267
|
+
f"An endpoint group must receive a list of endpoints/endpoint groups, but instead of a list I found an object of type '{endpoints.__class__.__name__}'"
|
|
268
|
+
)
|
|
269
|
+
for index, endpoint in enumerate(endpoints):
|
|
270
|
+
if not isinstance(endpoint, Endpoint) and not isinstance(endpoint, self.__class__):
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"An endpoint group must receive a list of endpoints/endpoint groups, but item #{index + 1} was neither an endpoint nor an endpoint group, but an object of type '{endpoints.__class__.__name__}'"
|
|
273
|
+
)
|
|
274
|
+
if self.url.strip("/"):
|
|
275
|
+
endpoint.add_url_prefix(self.url)
|
|
276
|
+
|
|
277
|
+
def add_url_prefix(self, prefix: str) -> None:
|
|
278
|
+
self.url = (prefix.rstrip("/") + "/" + self.url.lstrip("/")).lstrip("/")
|
|
279
|
+
for endpoint in self.endpoints:
|
|
280
|
+
endpoint.add_url_prefix(self.url)
|
|
281
|
+
|
|
282
|
+
def matches_request(self, input_output: InputOutput, allow_partial=True) -> bool:
|
|
283
|
+
"""Whether or not we can handle an incoming request based on URL and request method."""
|
|
284
|
+
expected_url = self.url.strip("/")
|
|
285
|
+
incoming_url = input_output.get_full_path().strip("/")
|
|
286
|
+
if not expected_url and not incoming_url:
|
|
287
|
+
return True
|
|
288
|
+
(matches, routing_data) = routing.match_route(expected_url, incoming_url, allow_partial=allow_partial)
|
|
289
|
+
return matches
|
|
290
|
+
|
|
291
|
+
def populate_routing_data(self, input_output: InputOutput) -> Any:
|
|
292
|
+
# only endpoints (not the endpoint group) can handle this because the endpoint group doesn't have the full url
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
def handle(self, input_output):
|
|
296
|
+
if not self.endpoints_initialized:
|
|
297
|
+
self.endpoints_initialized = True
|
|
298
|
+
for endpoint in self.endpoints:
|
|
299
|
+
endpoint.injectable_properties(self.di)
|
|
300
|
+
|
|
301
|
+
has_match = False
|
|
302
|
+
for endpoint in self.endpoints:
|
|
303
|
+
if not endpoint.matches_request(input_output):
|
|
304
|
+
continue
|
|
305
|
+
has_match = True
|
|
306
|
+
break
|
|
307
|
+
|
|
308
|
+
if not has_match:
|
|
309
|
+
return self.error(input_output, "Not Found", 404)
|
|
310
|
+
|
|
311
|
+
self.add_response_headers(input_output)
|
|
312
|
+
|
|
313
|
+
# "register" ourself with the DI system
|
|
314
|
+
current_endpoint_groups = self.di.build_from_name("endpoint_groups", cache=True)
|
|
315
|
+
current_endpoint_groups.append(self)
|
|
316
|
+
self.di.add_binding("endpoint_groups", current_endpoint_groups)
|
|
317
|
+
|
|
318
|
+
return endpoint(input_output)
|
|
319
|
+
|
|
320
|
+
def error(self, input_output: InputOutput, message: str, status_code: int) -> Any:
|
|
321
|
+
"""Return a client-side error (e.g. 400)."""
|
|
322
|
+
return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
|
|
323
|
+
|
|
324
|
+
def all_endpoints(self) -> list[Endpoint]:
|
|
325
|
+
"""Return the full (recursive) list of all endpoints associated with this endpoint group."""
|
|
326
|
+
all_endpoints: list[Endpoint] = []
|
|
327
|
+
for endpoint in self.endpoints:
|
|
328
|
+
if hasattr(endpoint, "all_endpoints"):
|
|
329
|
+
all_endpoints = [*all_endpoints, *endpoint.all_endpoints()]
|
|
330
|
+
else:
|
|
331
|
+
all_endpoints.append(endpoint)
|
|
332
|
+
|
|
333
|
+
return all_endpoints
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from clearskies.endpoints.advanced_search import AdvancedSearch
|
|
2
|
+
from clearskies.endpoints.callable import Callable
|
|
3
|
+
from clearskies.endpoints.create import Create
|
|
4
|
+
from clearskies.endpoints.delete import Delete
|
|
5
|
+
from clearskies.endpoints.get import Get
|
|
6
|
+
from clearskies.endpoints.health_check import HealthCheck
|
|
7
|
+
from clearskies.endpoints.list import List
|
|
8
|
+
from clearskies.endpoints.restful_api import RestfulApi
|
|
9
|
+
from clearskies.endpoints.schema import Schema
|
|
10
|
+
from clearskies.endpoints.simple_search import SimpleSearch
|
|
11
|
+
from clearskies.endpoints.update import Update
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AdvancedSearch",
|
|
15
|
+
"Callable",
|
|
16
|
+
"Create",
|
|
17
|
+
"Delete",
|
|
18
|
+
"Get",
|
|
19
|
+
"HealthCheck",
|
|
20
|
+
"List",
|
|
21
|
+
"RestfulApi",
|
|
22
|
+
"Schema",
|
|
23
|
+
"SimpleSearch",
|
|
24
|
+
"Update",
|
|
25
|
+
]
|