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
|
@@ -1,59 +1,530 @@
|
|
|
1
|
-
from
|
|
1
|
+
from clearskies import configs, decorators, di
|
|
2
2
|
|
|
3
|
+
from .authentication import Authentication
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SecretBearer(Authentication, di.InjectableProperties):
|
|
7
|
+
"""
|
|
8
|
+
Secret Bearer performs authentication by checking against a static API key stored in either environment variables or a secret manager.
|
|
9
|
+
|
|
10
|
+
This can be used in two different ways:
|
|
11
|
+
|
|
12
|
+
1. Attached to an endpoint to enforce authentication
|
|
13
|
+
2. Attached to an API backend to specify how to authenticate to the API endpoint.
|
|
14
|
+
|
|
15
|
+
### Authenticating Endpoints.
|
|
16
|
+
|
|
17
|
+
When attached to an endpoint this will enforce authentication. Clients authenticate themselves by providing the secret value
|
|
18
|
+
via the `authorization` header. In the following example we configure the secret bearer class to get the secretfrom an
|
|
19
|
+
environment variable which is set in the code itself. Normally you wouldn't set environment variables like this,
|
|
20
|
+
but it's done here to create a self-contained example that is easy to run:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import os
|
|
24
|
+
import clearskies
|
|
25
|
+
|
|
26
|
+
os.environ["MY_AUTH_SECRET"] = "SUPERSECRET"
|
|
27
|
+
|
|
28
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
29
|
+
clearskies.endpoints.Callable(
|
|
30
|
+
lambda: {"hello": "world"},
|
|
31
|
+
authentication=clearskies.authentication.SecretBearer(environment_key="MY_AUTH_SECRET"),
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
wsgi()
|
|
35
|
+
```
|
|
36
|
+
We can then call it with and without the authentication header:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
$ curl 'http://localhost:8080' -H 'Authorization: SUPERSECRET' | jq
|
|
40
|
+
{
|
|
41
|
+
"status": "success",
|
|
42
|
+
"error": "",
|
|
43
|
+
"data": {
|
|
44
|
+
"hello": "world"
|
|
45
|
+
},
|
|
46
|
+
"pagination": {},
|
|
47
|
+
"input_errors": {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$ curl 'http://localhost:8080' | jq
|
|
51
|
+
{
|
|
52
|
+
"status": "client_error",
|
|
53
|
+
"error": "Not Authenticated",
|
|
54
|
+
"data": [],
|
|
55
|
+
"pagination": {},
|
|
56
|
+
"input_errors": {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$ curl 'http://localhost:8080' -H 'Authorization: NOTTHESECRET' | jq
|
|
60
|
+
{
|
|
61
|
+
"status": "client_error",
|
|
62
|
+
"error": "Not Authenticated",
|
|
63
|
+
"data": [],
|
|
64
|
+
"pagination": {},
|
|
65
|
+
"input_errors": {}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Authenticating to APIs
|
|
70
|
+
|
|
71
|
+
The secret bearer class can also be attached to an API Backend to provide authentication to remote APIs. To
|
|
72
|
+
demonstrate, here is an example server that expects a secret token in the authorization header:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import os
|
|
76
|
+
import clearskies
|
|
77
|
+
from clearskies import columns
|
|
78
|
+
|
|
79
|
+
os.environ["MY_SECRET"] = "SUPERSECRET"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Widget(clearskies.Model):
|
|
83
|
+
id_column_name = "id"
|
|
84
|
+
backend = clearskies.backends.MemoryBackend()
|
|
85
|
+
|
|
86
|
+
id = columns.Uuid()
|
|
87
|
+
name = columns.String()
|
|
88
|
+
category = columns.String()
|
|
89
|
+
cost = columns.Float()
|
|
90
|
+
created_at = columns.Created()
|
|
91
|
+
updated_at = columns.Updated()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
95
|
+
clearskies.endpoints.RestfulApi(
|
|
96
|
+
url="widgets",
|
|
97
|
+
model_class=Widget,
|
|
98
|
+
authentication=clearskies.authentication.SecretBearer(environment_key="MY_SECRET"),
|
|
99
|
+
readable_column_names=["id", "name", "category", "cost", "created_at", "updated_at"],
|
|
100
|
+
writeable_column_names=["name", "category", "cost"],
|
|
101
|
+
sortable_column_names=["name", "category", "cost"],
|
|
102
|
+
searchable_column_names=["id", "name", "category", "cost"],
|
|
103
|
+
default_sort_column_name="name",
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
wsgi()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Then here is a client app (you can launch the above server and then run this in a new terminal) that
|
|
110
|
+
similarly uses the secret bearer class to authenticate to the server:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import os
|
|
114
|
+
import clearskies
|
|
115
|
+
from clearskies import columns
|
|
116
|
+
|
|
117
|
+
os.environ["MY_SECRET"] = "SUPERSECRET"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class Widget(clearskies.Model):
|
|
121
|
+
id_column_name = "id"
|
|
122
|
+
backend = clearskies.backends.ApiBackend(
|
|
123
|
+
base_url="http://localhost:8080",
|
|
124
|
+
authentication=clearskies.authentication.SecretBearer(environment_key="MY_SECRET"),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
id = columns.String()
|
|
128
|
+
name = columns.String()
|
|
129
|
+
category = columns.String()
|
|
130
|
+
cost = columns.Float()
|
|
131
|
+
created_at = columns.Datetime()
|
|
132
|
+
updated_at = columns.Datetime()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def api_demo(widgets: Widget) -> Widget:
|
|
136
|
+
thinga = widgets.create({"name": "Thinga", "category": "Doohickey", "cost": 125})
|
|
137
|
+
mabob = widgets.create({"name": "Mabob", "category": "Doohicky", "cost": 150})
|
|
138
|
+
return widgets
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
cli = clearskies.contexts.Cli(
|
|
142
|
+
clearskies.endpoints.Callable(
|
|
143
|
+
api_demo,
|
|
144
|
+
model_class=Widget,
|
|
145
|
+
return_records=True,
|
|
146
|
+
readable_column_names=["id", "name", "category", "cost", "created_at", "updated_at"],
|
|
147
|
+
),
|
|
148
|
+
classes=[Widget],
|
|
149
|
+
)
|
|
150
|
+
cli()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The above app declares a model class that matches the output from our server/api. Note that the id,
|
|
154
|
+
created_at, and updated_at columns all changed types to their "plain" types. This is very normal. The API
|
|
155
|
+
is the one that is responsible for assigning ids and setting created/updated timestamps, so from the
|
|
156
|
+
perspective of our client, these are plain string/datetime fields. If we used the UUID or created/updated
|
|
157
|
+
columns, then when the client called the API it would try to set all of these columns. Since they are not
|
|
158
|
+
writeable columns, the API would return an input error. If you launch the above server/API and then run
|
|
159
|
+
the given client script, you'll see output like this:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"status": "success",
|
|
164
|
+
"error": "",
|
|
165
|
+
"data": [
|
|
166
|
+
{
|
|
167
|
+
"id": "54eef01d-7c87-4959-b525-dcb9047d9692",
|
|
168
|
+
"name": "Mabob",
|
|
169
|
+
"category": "Doohicky",
|
|
170
|
+
"cost": 150.0,
|
|
171
|
+
"created_at": "2025-06-13T15:19:27+00:00",
|
|
172
|
+
"updated_at": "2025-06-13T15:19:27+00:00",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "ed1421b8-88ad-49d2-a130-c34b4ac4dfcf",
|
|
176
|
+
"name": "Thinga",
|
|
177
|
+
"category": "Doohickey",
|
|
178
|
+
"cost": 125.0,
|
|
179
|
+
"created_at": "2025-06-13T15:19:27+00:00",
|
|
180
|
+
"updated_at": "2025-06-13T15:19:27+00:00",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
"pagination": {},
|
|
184
|
+
"input_errors": {},
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
"""
|
|
3
188
|
|
|
4
|
-
class SecretBearer:
|
|
5
189
|
is_public = False
|
|
6
190
|
can_authorize = False
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def
|
|
22
|
-
|
|
191
|
+
|
|
192
|
+
environment = di.inject.Environment()
|
|
193
|
+
secrets = di.inject.Secrets()
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
The path in our secret manager from which the secret should be fetched.
|
|
197
|
+
|
|
198
|
+
Of course, to use `secret_key`, you must also provide a secret manager. The below example uses the dependency
|
|
199
|
+
injection system to create a faux secret manager to demonstrate how it works in general:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from types import SimpleNamespace
|
|
203
|
+
import clearskies
|
|
204
|
+
|
|
205
|
+
def fetch_secret(path):
|
|
206
|
+
if path == "/path/to/my/secret":
|
|
207
|
+
return "SUPERSECRET"
|
|
208
|
+
raise KeyError(f"Attempt to fetch non-existent secret: {path}")
|
|
209
|
+
|
|
210
|
+
fake_secret_manager = SimpleNamespace(get=fetch_secret)
|
|
211
|
+
|
|
212
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
213
|
+
clearskies.endpoints.Callable(
|
|
214
|
+
lambda: {"hello": "world"},
|
|
215
|
+
authentication=clearskies.authentication.SecretBearer(secret_key="/path/to/my/secret"),
|
|
216
|
+
),
|
|
217
|
+
bindings={
|
|
218
|
+
"secrets": fake_secret_manager,
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
wsgi()
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
And when invoked:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
$ curl 'http://localhost:8080/' -H "Authorization: SUPERSECRET" | jq
|
|
228
|
+
{
|
|
229
|
+
"status": "success",
|
|
230
|
+
"error": "",
|
|
231
|
+
"data": {
|
|
232
|
+
"hello": "world"
|
|
233
|
+
},
|
|
234
|
+
"pagination": {},
|
|
235
|
+
"input_errors": {}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
$ curl 'http://localhost:8080/' -H "Authorization: definitely-not-the-api-key" | jq
|
|
239
|
+
{
|
|
240
|
+
"status": "client_error",
|
|
241
|
+
"error": "Not Authenticated",
|
|
242
|
+
"data": [],
|
|
243
|
+
"pagination": {},
|
|
244
|
+
"input_errors": {}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
secret_key = configs.String(default="")
|
|
250
|
+
|
|
251
|
+
"""
|
|
252
|
+
The path in our secret manager where an alternate secret can also be fetched
|
|
253
|
+
|
|
254
|
+
The alternate secret is exclusively used to authenticate incoming requests. This allows for secret
|
|
255
|
+
rotation - Point secret_key to a new secret and alternate_secret_key to the old secret. Both will then
|
|
256
|
+
be accepted and you can migrate your applications to only send the new secret. Once they are all updated,
|
|
257
|
+
remove the alternate_secret_key:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from types import SimpleNamespace
|
|
261
|
+
import clearskies
|
|
262
|
+
|
|
263
|
+
def fetch_secret(path):
|
|
264
|
+
if path == "/path/to/my/secret":
|
|
265
|
+
return "SUPERSECRET"
|
|
266
|
+
if path == "/path/to/alternate/secret":
|
|
267
|
+
return "ALSOOKAY"
|
|
268
|
+
raise KeyError(f"Attempt to fetch non-existent secret: {path}")
|
|
269
|
+
|
|
270
|
+
fake_secret_manager = SimpleNamespace(get=fetch_secret)
|
|
271
|
+
|
|
272
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
273
|
+
clearskies.endpoints.Callable(
|
|
274
|
+
lambda: {"hello": "world"},
|
|
275
|
+
authentication=clearskies.authentication.SecretBearer(
|
|
276
|
+
secret_key="/path/to/my/secret",
|
|
277
|
+
alternate_secret_key="/path/to/alternate/secret",
|
|
278
|
+
),
|
|
279
|
+
),
|
|
280
|
+
bindings={
|
|
281
|
+
"secrets": fake_secret_manager,
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
wsgi()
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
And when invoked:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
$ curl 'http://localhost:8080/' -H "Authorization: SUPERSECRET" | jq
|
|
291
|
+
{
|
|
292
|
+
"status": "success",
|
|
293
|
+
"error": "",
|
|
294
|
+
"data": {
|
|
295
|
+
"hello": "world"
|
|
296
|
+
},
|
|
297
|
+
"pagination": {},
|
|
298
|
+
"input_errors": {}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
$ curl 'http://localhost:8080/' -H "Authorization: ALSOOKAY" | jq
|
|
302
|
+
{
|
|
303
|
+
"status": "success",
|
|
304
|
+
"error": "",
|
|
305
|
+
"data": {
|
|
306
|
+
"hello": "world"
|
|
307
|
+
},
|
|
308
|
+
"pagination": {},
|
|
309
|
+
"input_errors": {}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
$ curl 'http://localhost:8080/' -H "Authorization: NOTTHESECRET" | jq
|
|
313
|
+
{
|
|
314
|
+
"status": "client_error",
|
|
315
|
+
"error": "Not Authenticated",
|
|
316
|
+
"data": [],
|
|
317
|
+
"pagination": {},
|
|
318
|
+
"input_errors": {}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
alternate_secret_key = configs.String(default="")
|
|
324
|
+
|
|
325
|
+
"""
|
|
326
|
+
The name of the environment variable from which we should fetch our key.
|
|
327
|
+
"""
|
|
328
|
+
environment_key = configs.String(default="")
|
|
329
|
+
|
|
330
|
+
"""
|
|
331
|
+
The name of an alternate environment variable from which we should fetch our key.
|
|
332
|
+
|
|
333
|
+
This allows for secret rotation by allowing the API to accept a secret from two different
|
|
334
|
+
environment variables: an old value and a new value. You can then migrate your client applications
|
|
335
|
+
to use the new key and, once they are all migrated, remove the old key from the application
|
|
336
|
+
configuration. Here's an example:
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
import os
|
|
340
|
+
import clearskies
|
|
341
|
+
|
|
342
|
+
os.environ["MY_AUTH_SECRET"] = "SUPERSECRET"
|
|
343
|
+
os.environ["MY_ALT_SECRET"] = "ALSOOKAY"
|
|
344
|
+
|
|
345
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
346
|
+
clearskies.endpoints.Callable(
|
|
347
|
+
lambda: {"hello": "world"},
|
|
348
|
+
authentication=clearskies.authentication.SecretBearer(
|
|
349
|
+
environment_key="MY_AUTH_SECRET",
|
|
350
|
+
alternate_environment_key="MY_ALT_SECRET",
|
|
351
|
+
),
|
|
352
|
+
),
|
|
353
|
+
)
|
|
354
|
+
wsgi()
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
And when invoked:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
$ curl 'http://localhost:8080/' -H "Authorization: SUPERSECRET" | jq
|
|
361
|
+
{
|
|
362
|
+
"status": "success",
|
|
363
|
+
"error": "",
|
|
364
|
+
"data": {
|
|
365
|
+
"hello": "world"
|
|
366
|
+
},
|
|
367
|
+
"pagination": {},
|
|
368
|
+
"input_errors": {}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
$ curl 'http://localhost:8080/' -H "Authorization: ALSOOKAY" | jq
|
|
372
|
+
{
|
|
373
|
+
"status": "success",
|
|
374
|
+
"error": "",
|
|
375
|
+
"data": {
|
|
376
|
+
"hello": "world"
|
|
377
|
+
},
|
|
378
|
+
"pagination": {},
|
|
379
|
+
"input_errors": {}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
$ curl 'http://localhost:8080/' -H "Authorization: NOTTHESECRET" | jq
|
|
383
|
+
{
|
|
384
|
+
"status": "client_error",
|
|
385
|
+
"error": "Not Authenticated",
|
|
386
|
+
"data": [],
|
|
387
|
+
"pagination": {},
|
|
388
|
+
"input_errors": {}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
"""
|
|
393
|
+
alternate_environment_key = configs.String(default="")
|
|
394
|
+
|
|
395
|
+
"""
|
|
396
|
+
The expected prefix (if any) that should come before the secret key in the authorization header.
|
|
397
|
+
|
|
398
|
+
This applies to both the incoming authentication process and outgoing authentication headers. Some systems
|
|
399
|
+
require a prefix before the auth token in the HTTP header (e.g. `Authorization: TOKEN [auth key here]`).
|
|
400
|
+
You can provide that prefix to `header_prefix` in order for the endpoint to require a prefix or the api backend
|
|
401
|
+
to provide such a prefix. Note that the prefix is case-insensitive and it does not assume a space between the
|
|
402
|
+
prefix and the token (so, if you want a space, you must explicitly put it in the prefix). Here's an example:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
import os
|
|
406
|
+
import clearskies
|
|
407
|
+
|
|
408
|
+
os.environ["MY_AUTH_SECRET"] = "SUPERSECRET"
|
|
409
|
+
|
|
410
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
411
|
+
clearskies.endpoints.Callable(
|
|
412
|
+
lambda: {"hello": "world"},
|
|
413
|
+
authentication=clearskies.authentication.SecretBearer(
|
|
414
|
+
environment_key="MY_AUTH_SECRET",
|
|
415
|
+
header_prefix="secret-token ",
|
|
416
|
+
),
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
wsgi()
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
And then usage:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
$ curl 'http://localhost:8080/' -H "Authorization: SECRET-TOKEN SUPERSECRET" | jq
|
|
426
|
+
{
|
|
427
|
+
"status": "success",
|
|
428
|
+
"error": "",
|
|
429
|
+
"data": {
|
|
430
|
+
"hello": "world"
|
|
431
|
+
},
|
|
432
|
+
"pagination": {},
|
|
433
|
+
"input_errors": {}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
$ curl 'http://localhost:8080/' -H "Authorization: SUPERSECRET" | jq
|
|
437
|
+
{
|
|
438
|
+
"status": "client_error",
|
|
439
|
+
"error": "Not Authenticated",
|
|
440
|
+
"data": [],
|
|
441
|
+
"pagination": {},
|
|
442
|
+
"input_errors": {}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
"""
|
|
446
|
+
header_prefix = configs.String(default="")
|
|
447
|
+
|
|
448
|
+
"""
|
|
449
|
+
The length of our header prefix
|
|
450
|
+
"""
|
|
451
|
+
header_prefix_length = None
|
|
452
|
+
|
|
453
|
+
"""
|
|
454
|
+
The name of our security scheme in the auto-generated documentation
|
|
455
|
+
"""
|
|
456
|
+
documentation_security_name = configs.String(default="ApiKey")
|
|
457
|
+
|
|
458
|
+
_secret: str | None = None
|
|
459
|
+
_alternate_secret: str | None = None
|
|
460
|
+
|
|
461
|
+
@decorators.parameters_to_properties
|
|
462
|
+
def __init__(
|
|
463
|
+
self,
|
|
464
|
+
secret_key: str = "",
|
|
465
|
+
alternate_secret_key: str = "",
|
|
466
|
+
environment_key: str = "",
|
|
467
|
+
alternate_environment_key: str = "",
|
|
468
|
+
header_prefix: str = "",
|
|
469
|
+
documentation_security_name: str = "",
|
|
23
470
|
):
|
|
24
|
-
if secret_key:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
471
|
+
if not secret_key and not environment_key:
|
|
472
|
+
raise ValueError("Must set either 'secret_key' or 'environment_key' when configuring the SecretBearer")
|
|
473
|
+
self.header_prefix_length = len(header_prefix)
|
|
474
|
+
self.finalize_and_validate_configuration()
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def secret(self):
|
|
478
|
+
if not self._secret:
|
|
479
|
+
self._secret = (
|
|
480
|
+
self.secrets.get(self.secret_key) if self.secret_key else self.environment.get(self.environment_key)
|
|
33
481
|
)
|
|
34
|
-
self.
|
|
35
|
-
|
|
36
|
-
|
|
482
|
+
return self._secret
|
|
483
|
+
|
|
484
|
+
def clear_credential_cache(self):
|
|
485
|
+
if self.secret_key:
|
|
486
|
+
self._secret = None # type: ignore
|
|
487
|
+
|
|
488
|
+
@property
|
|
489
|
+
def alternate_secret(self):
|
|
490
|
+
if not self.alternate_secret_key and not self.alternate_environment_key:
|
|
491
|
+
return ""
|
|
492
|
+
|
|
493
|
+
if not self._alternate_secret:
|
|
494
|
+
self._alternate_secret = (
|
|
495
|
+
self.secrets.get(self.alternate_secret_key)
|
|
496
|
+
if self.alternate_secret_key
|
|
497
|
+
else self.environment.get(self.alternate_environment_key)
|
|
498
|
+
)
|
|
499
|
+
return self._alternate_secret
|
|
37
500
|
|
|
38
501
|
def headers(self, retry_auth=False):
|
|
39
502
|
self._configured_guard()
|
|
40
|
-
|
|
503
|
+
if retry_auth:
|
|
504
|
+
self.clear_credential_cache()
|
|
505
|
+
return {"Authorization": f"{self.header_prefix}{self.secret}"}
|
|
41
506
|
|
|
42
507
|
def authenticate(self, input_output):
|
|
43
508
|
self._configured_guard()
|
|
44
|
-
auth_header = input_output.
|
|
45
|
-
if auth_header
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
509
|
+
auth_header = input_output.request_headers.authorization
|
|
510
|
+
if not auth_header:
|
|
511
|
+
return False
|
|
512
|
+
if auth_header[: self.header_prefix_length].lower() != self.header_prefix.lower():
|
|
513
|
+
# self._logging.debug(
|
|
514
|
+
# "Authentication failure due to prefix mismatch. Configured prefix: "
|
|
515
|
+
# + self._header_prefix.lower()
|
|
516
|
+
# + ". Found prefix: "
|
|
517
|
+
# + auth_header[: self._header_prefix_length].lower()
|
|
518
|
+
# )
|
|
52
519
|
return False
|
|
53
|
-
|
|
54
|
-
|
|
520
|
+
provided_secret = auth_header[self.header_prefix_length :]
|
|
521
|
+
if self.secret == provided_secret:
|
|
522
|
+
# self._logging.debug("Authentication success")
|
|
55
523
|
return True
|
|
56
|
-
self.
|
|
524
|
+
if self.alternate_secret and provided_secret == self._alternate_secret:
|
|
525
|
+
# self._logging.debug("Authentication success with alternate secret")
|
|
526
|
+
return True
|
|
527
|
+
# self._logging.debug("Authentication failure due to secret mismatch")
|
|
57
528
|
return False
|
|
58
529
|
|
|
59
530
|
def authorize(self, authorization):
|
|
@@ -63,7 +534,7 @@ class SecretBearer:
|
|
|
63
534
|
cors.add_header("Authorization")
|
|
64
535
|
|
|
65
536
|
def _configured_guard(self):
|
|
66
|
-
if not self.
|
|
537
|
+
if not self.secret:
|
|
67
538
|
raise ValueError("Attempted to use SecretBearer authentication class without providing the configuration")
|
|
68
539
|
|
|
69
540
|
def documentation_request_parameters(self):
|
|
@@ -77,4 +548,4 @@ class SecretBearer:
|
|
|
77
548
|
}
|
|
78
549
|
|
|
79
550
|
def documentation_security_scheme_name(self):
|
|
80
|
-
return self.
|
|
551
|
+
return self.documentation_security_name
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
from .request import Request
|
|
2
1
|
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .request import Request
|
|
3
5
|
|
|
4
6
|
|
|
5
|
-
class
|
|
6
|
-
requests = None
|
|
7
|
-
formatted = None
|
|
8
|
-
models = None
|
|
9
|
-
security_schemes = None
|
|
7
|
+
class Oai3Json:
|
|
8
|
+
requests: Any = None
|
|
9
|
+
formatted: Any = None
|
|
10
|
+
models: Any = None
|
|
11
|
+
security_schemes: Any = None
|
|
10
12
|
|
|
11
13
|
def __init__(self, oai3_schema_resolver):
|
|
12
14
|
self.oai3_schema_resolver = oai3_schema_resolver
|
|
@@ -56,7 +58,7 @@ class OAI3JSON:
|
|
|
56
58
|
return json.dumps(data)
|
|
57
59
|
|
|
58
60
|
def convert(self):
|
|
59
|
-
paths = {}
|
|
61
|
+
paths: dict[str, Any] = {}
|
|
60
62
|
for request in self.formatted:
|
|
61
63
|
absolute_path = "/" + request.relative_path.lstrip("/")
|
|
62
64
|
if absolute_path not in paths:
|
|
@@ -65,10 +67,10 @@ class OAI3JSON:
|
|
|
65
67
|
path_data = request.convert()
|
|
66
68
|
for request_method, path_doc in path_data.items():
|
|
67
69
|
if request_method in paths[absolute_path]:
|
|
68
|
-
|
|
70
|
+
continue
|
|
69
71
|
paths[absolute_path][request_method] = path_doc
|
|
70
72
|
|
|
71
|
-
data = {
|
|
73
|
+
data: dict[str, Any] = {
|
|
72
74
|
"openapi": "3.0.0",
|
|
73
75
|
"paths": paths,
|
|
74
76
|
"components": {},
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from ...schema import Object
|
|
2
4
|
from .parameter import Parameter
|
|
3
|
-
from
|
|
5
|
+
from .response import Response
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class Request:
|
|
7
|
-
formatted_responses = None
|
|
8
|
-
request = None
|
|
9
|
-
relative_path =
|
|
9
|
+
formatted_responses: Any = None
|
|
10
|
+
request: Any = None
|
|
11
|
+
relative_path: str = ""
|
|
10
12
|
|
|
11
13
|
def __init__(self, oai3_schema_resolver):
|
|
12
14
|
self.oai3_schema_resolver = oai3_schema_resolver
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class Response:
|
|
2
|
-
response = None
|
|
3
|
-
formatted_schema = None
|
|
4
|
-
oai3_schema_resolver = None
|
|
5
|
-
status_code = None
|
|
5
|
+
response: Any = None
|
|
6
|
+
formatted_schema: Any = None
|
|
7
|
+
oai3_schema_resolver: Any = None
|
|
8
|
+
status_code: Any = None
|
|
6
9
|
|
|
7
10
|
def __init__(self, oai3_schema_resolver):
|
|
8
11
|
self.oai3_schema_resolver = oai3_schema_resolver
|