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
clearskies/functional/string.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import re
|
|
2
1
|
import datetime
|
|
2
|
+
import re
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def camel_case_to_snake_case(string: str) -> str:
|
|
6
|
-
"""
|
|
7
|
-
Converts a title/camel case string (MyString|myString) to snake case (my_string)
|
|
8
|
-
"""
|
|
6
|
+
"""Convert a title/camel case string (MyString|myString) to snake case (my_string)."""
|
|
9
7
|
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", re.sub("(.)([A-Z][a-z]+)", r"\1_\2", string)).lower()
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
def camel_case_to_title_case(string):
|
|
13
|
-
return camel_case_to_words.title()
|
|
11
|
+
return camel_case_to_words(string).title().replace(" ", "")
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
def camel_case_to_words(string):
|
|
@@ -19,10 +17,12 @@ def camel_case_to_words(string):
|
|
|
19
17
|
return string
|
|
20
18
|
|
|
21
19
|
|
|
20
|
+
def camel_case_to_nice(string):
|
|
21
|
+
return camel_case_to_words(string).title()
|
|
22
|
+
|
|
23
|
+
|
|
22
24
|
def title_case_to_snake_case(string: str) -> str:
|
|
23
|
-
"""
|
|
24
|
-
Converts a title case string (MyString) to snake case (my_string)
|
|
25
|
-
"""
|
|
25
|
+
"""Convert a title case string (MyString) to snake case (my_string)."""
|
|
26
26
|
return camel_case_to_snake_case(string)
|
|
27
27
|
|
|
28
28
|
|
|
@@ -34,9 +34,13 @@ def title_case_to_camel_case(string: str) -> str:
|
|
|
34
34
|
return string[0].lower() + string[1:]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def title_case_to_nice(string: str) -> str:
|
|
38
|
+
return camel_case_to_nice(string)
|
|
39
|
+
|
|
40
|
+
|
|
37
41
|
def snake_case_to_title_case(string: str) -> str:
|
|
38
42
|
"""
|
|
39
|
-
|
|
43
|
+
Convert a snake case string (my_string) to title case (MyString).
|
|
40
44
|
|
|
41
45
|
Note this is sometimes ambiguous. Consider:
|
|
42
46
|
|
|
@@ -50,7 +54,7 @@ def snake_case_to_title_case(string: str) -> str:
|
|
|
50
54
|
|
|
51
55
|
def snake_case_to_camel_case(string: str) -> str:
|
|
52
56
|
"""
|
|
53
|
-
|
|
57
|
+
Convert a snake case string (my_string) to camel case (myString).
|
|
54
58
|
|
|
55
59
|
Note this is sometimes ambiguous. Consider:
|
|
56
60
|
|
|
@@ -62,6 +66,10 @@ def snake_case_to_camel_case(string: str) -> str:
|
|
|
62
66
|
return words[0] + "".join([x.title() for x in words[1:]])
|
|
63
67
|
|
|
64
68
|
|
|
69
|
+
def snake_case_to_nice(string: str) -> str:
|
|
70
|
+
return camel_case_to_nice(snake_case_to_camel_case(string))
|
|
71
|
+
|
|
72
|
+
|
|
65
73
|
casings = ["camelCase", "snake_case", "TitleCase"]
|
|
66
74
|
casing_swap_map = {
|
|
67
75
|
"camelCase": {
|
|
@@ -87,7 +95,7 @@ def swap_casing(string: str, from_casing: str, to_casing: str) -> str:
|
|
|
87
95
|
raise ValueError(f"Invalid casing '{from_casing}'. Must be one of '" + "', ".join(casings) + "'")
|
|
88
96
|
if to_casing not in casings:
|
|
89
97
|
raise ValueError(f"Invalid casing '{to_casing}'. Must be one of '" + "', ".join(casings) + "'")
|
|
90
|
-
return casing_swap_map[from_casing][to_casing](string)
|
|
98
|
+
return casing_swap_map[from_casing][to_casing](string) # type: ignore
|
|
91
99
|
|
|
92
100
|
|
|
93
101
|
def make_plural(singular: str):
|
|
@@ -1,24 +1,76 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
from .. import model
|
|
3
1
|
import inspect
|
|
2
|
+
from typing import Any
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
def is_model(to_check: Any) -> bool:
|
|
7
6
|
"""
|
|
8
|
-
|
|
7
|
+
Return True/False to denote if the given value is a model instance.
|
|
8
|
+
|
|
9
|
+
Uses ducktyping rather than checking the type, mostly to minimize the risk of circular imports
|
|
9
10
|
"""
|
|
10
|
-
|
|
11
|
+
if not hasattr(to_check, "destination_name"):
|
|
12
|
+
return False
|
|
13
|
+
return True
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
def is_model_class(to_check: Any) -> bool:
|
|
17
|
+
"""Return True/False to denote if the given value is a model class."""
|
|
18
|
+
return inspect.isclass(to_check) and is_model(to_check)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_model_class_reference(to_check: Any) -> bool:
|
|
22
|
+
"""Return True/False to denote if the given value is a reference to a model class."""
|
|
23
|
+
if not hasattr(to_check, "get_model_class"):
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
if inspect.isclass(to_check):
|
|
27
|
+
to_check = to_check()
|
|
28
|
+
return is_model_class(to_check.get_model_class())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_model_class_or_reference(to_check: Any, raise_error_message=False, strict=True) -> bool:
|
|
14
32
|
"""
|
|
15
|
-
|
|
33
|
+
Return True/False to denote if the given value is a model class or a model reference.
|
|
34
|
+
|
|
35
|
+
If strict is false, then it won't check that the model reference returns a model class. This is often necessary during
|
|
36
|
+
validation by configs as, otherwise, it tends to trigger circular dependency errors.
|
|
16
37
|
"""
|
|
17
|
-
|
|
38
|
+
if not inspect.isclass(to_check):
|
|
39
|
+
# for references we will accept either instances or classes
|
|
40
|
+
if hasattr(to_check, "get_model_class"):
|
|
41
|
+
return is_model_class(to_check.get_model_class()) if strict else True
|
|
42
|
+
|
|
43
|
+
if raise_error_message:
|
|
44
|
+
raise TypeError(
|
|
45
|
+
f"I expected a model class or reference, but instead I received something of type '{to_check.__class__.__name__}'"
|
|
46
|
+
)
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
if is_model_class(to_check):
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
if hasattr(to_check, "get_model_class"):
|
|
53
|
+
if not strict:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
model_class = to_check().get_model_class()
|
|
57
|
+
if is_model_class(model_class):
|
|
58
|
+
return True
|
|
59
|
+
if raise_error_message:
|
|
60
|
+
raise TypeError(
|
|
61
|
+
f"I expected a model class or reference. I received a model reference of class '{to_check.__name__}', but when I fetched the model out of it, it gave me back something that wasn't a model. Instead, I received something of type '{model_class.__name__}'"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if raise_error_message:
|
|
65
|
+
raise TypeError(
|
|
66
|
+
"I expected a model class or reference, but instead I received a class that was neither. It had a type of '"
|
|
67
|
+
+ to_check.__class__.__name__
|
|
68
|
+
+ "'"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return False
|
|
18
72
|
|
|
19
73
|
|
|
20
74
|
def is_model_or_class(to_check: Any) -> bool:
|
|
21
|
-
"""
|
|
22
|
-
Returns True/False to denote if the given value is a model instance or model class
|
|
23
|
-
"""
|
|
75
|
+
"""Return True/False to denote if the given value is a model instance or model class."""
|
|
24
76
|
return is_model(to_check) or is_model_class(to_check)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
from .cli import
|
|
2
|
-
from .
|
|
3
|
-
from .
|
|
4
|
-
from . import
|
|
1
|
+
from clearskies.input_outputs.cli import Cli
|
|
2
|
+
from clearskies.input_outputs.headers import Headers
|
|
3
|
+
from clearskies.input_outputs.input_output import InputOutput
|
|
4
|
+
from clearskies.input_outputs.programmatic import Programmatic
|
|
5
|
+
from clearskies.input_outputs.wsgi import Wsgi
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
|
-
"
|
|
8
|
+
"Cli",
|
|
9
|
+
"Headers",
|
|
8
10
|
"InputOutput",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
+
"Programmatic",
|
|
12
|
+
"Wsgi",
|
|
11
13
|
]
|
clearskies/input_outputs/cli.py
CHANGED
|
@@ -1,182 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class CLI:
|
|
7
|
-
_sys = None
|
|
8
|
-
_args = None
|
|
9
|
-
_flags = None
|
|
10
|
-
_cached_body = None
|
|
11
|
-
_has_body = None
|
|
12
|
-
_input_type = None
|
|
13
|
-
_body_loaded_as_json = None
|
|
14
|
-
_body_as_json = None
|
|
15
|
-
_routing_data = None
|
|
16
|
-
_authorization_data = None
|
|
17
|
-
|
|
18
|
-
def __init__(self, sys):
|
|
19
|
-
self._sys = sys
|
|
20
|
-
self._args = []
|
|
21
|
-
self._kwargs = {}
|
|
22
|
-
self._request_method = None
|
|
23
|
-
self._parse_args(self._sys.argv)
|
|
24
|
-
self._authorization_data = None
|
|
4
|
+
import sys
|
|
5
|
+
from os import isatty
|
|
6
|
+
from sys import stdin
|
|
25
7
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
raise exceptions.CLINotFound()
|
|
29
|
-
if status_code != 200:
|
|
30
|
-
self._sys.exit(response)
|
|
31
|
-
if type(response) != str:
|
|
32
|
-
print(json.dumps(response))
|
|
33
|
-
else:
|
|
34
|
-
print(response)
|
|
8
|
+
from clearskies.input_outputs.headers import Headers
|
|
9
|
+
from clearskies.input_outputs.input_output import InputOutput
|
|
35
10
|
|
|
36
|
-
def error(self, body):
|
|
37
|
-
return self.respond(body, 400)
|
|
38
11
|
|
|
39
|
-
|
|
40
|
-
|
|
12
|
+
class Cli(InputOutput):
|
|
13
|
+
path: str
|
|
14
|
+
_has_body: bool = False
|
|
15
|
+
_body: str = ""
|
|
41
16
|
|
|
42
|
-
def
|
|
43
|
-
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._parse_args(sys.argv)
|
|
19
|
+
super().__init__()
|
|
20
|
+
|
|
21
|
+
def respond(self, response, status_code=200):
|
|
22
|
+
if type(response) != str:
|
|
23
|
+
final = json.dumps(response)
|
|
24
|
+
else:
|
|
25
|
+
final = response
|
|
26
|
+
if status_code != 200:
|
|
27
|
+
sys.exit(final)
|
|
28
|
+
print(final)
|
|
44
29
|
|
|
45
30
|
def _parse_args(self, argv):
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
31
|
+
tty_data = None
|
|
32
|
+
if not isatty(stdin.fileno()):
|
|
33
|
+
tty_data = sys.stdin.read().strip()
|
|
34
|
+
|
|
35
|
+
request_headers = {}
|
|
36
|
+
args = []
|
|
37
|
+
kwargs = {}
|
|
38
|
+
index = 0
|
|
39
|
+
# In general we will use positional arguments for routing, and kwargs for request data.
|
|
40
|
+
# If things start with a dash then they are assumed to be a kwarg. If not, then a positional argument.
|
|
41
|
+
# we don't allow for simple flags: everything is a positional argument or a key/value pair
|
|
42
|
+
# For kwargs, we'll allow for using an equal sign or not, e.g.: '--key=value' or '--key value' or '-d thing'.
|
|
43
|
+
while index < len(argv) - 1:
|
|
44
|
+
index += 1
|
|
45
|
+
|
|
46
|
+
# if we don't start with a dash then we are a positional argument which are used for building the URL-equivalent
|
|
47
|
+
arg = argv[index]
|
|
48
|
+
if arg[0] != "-":
|
|
49
|
+
args.append(arg)
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
# otherwise a kwarg
|
|
53
|
+
arg = arg.strip("-")
|
|
54
|
+
|
|
55
|
+
# if we have an equal sign in our kwarg then it's self-contained
|
|
56
|
+
if "=" in arg:
|
|
57
|
+
[key, value] = arg.split("=", 1)
|
|
58
|
+
|
|
59
|
+
# otherwise we have to grab the next argument to get the value
|
|
67
60
|
else:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
key = arg
|
|
62
|
+
value = argv[index + 1]
|
|
63
|
+
if "-" in value:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Invalid clearskies cli calling sequence: found two key names next to eachother without any values: '-{arg} {value}'"
|
|
66
|
+
)
|
|
67
|
+
index += 1
|
|
68
|
+
|
|
69
|
+
if key.lower() == "h":
|
|
70
|
+
parts = value.split(":", 1)
|
|
71
|
+
if len(parts) != 2:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Invalid clearskies cli calling sequence: a parameter named '-H' was found, which is treated as a request header, but it didn't have the proper 'key: value' format."
|
|
74
|
+
)
|
|
75
|
+
request_headers[parts[0]] = parts[1]
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
kwargs[key] = value
|
|
79
|
+
|
|
80
|
+
self.request_headers = Headers(request_headers)
|
|
81
|
+
self.request_method = "GET"
|
|
82
|
+
request_method_source = ""
|
|
83
|
+
for key in ["x", "X", "request_method"]:
|
|
84
|
+
if key not in kwargs:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
if request_method_source:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"Invalid clearskies cli calling sequence: the request method was specified via both the -{key} parameter and the -{request_method_source} parameter. To avoid ambiguity, it should only be set once."
|
|
90
|
+
)
|
|
91
|
+
self.request_method = kwargs[key].upper()
|
|
92
|
+
del kwargs[key]
|
|
93
|
+
request_method_source = key
|
|
94
|
+
|
|
95
|
+
final_data = None
|
|
96
|
+
data_source = None
|
|
97
|
+
if tty_data:
|
|
98
|
+
final_data = tty_data
|
|
99
|
+
data_source = "piped input"
|
|
100
|
+
if kwargs.get("d"):
|
|
101
|
+
if final_data:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"Invalid clearskies cli calling sequence: request data was sent by both the -d parameter and {data_source}. To avoid ambiguity, it should only be sent one way."
|
|
104
|
+
)
|
|
105
|
+
final_data = kwargs.get("d")
|
|
106
|
+
data_source = "the -d parameter"
|
|
107
|
+
del kwargs["d"]
|
|
108
|
+
if kwargs.get("data"):
|
|
109
|
+
if final_data:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Invalid calling sequence: request data was sent by both the -data parameter and {data_source}. To avoid ambiguity, it should only be sent one way."
|
|
112
|
+
)
|
|
113
|
+
final_data = kwargs.get("data")
|
|
114
|
+
data_source = "the -data parameter"
|
|
115
|
+
del kwargs["data"]
|
|
116
|
+
if final_data and len(kwargs):
|
|
117
|
+
raise ValueError(
|
|
118
|
+
f"Invalid calling sequence: extra parameters were specified after sending a body via {data_source}. To avoid ambiguity, send all data via {data_source}."
|
|
119
|
+
)
|
|
120
|
+
if not final_data and len(kwargs):
|
|
121
|
+
final_data = kwargs
|
|
122
|
+
data_source = "kwargs"
|
|
123
|
+
|
|
124
|
+
self.path = "/".join(args)
|
|
125
|
+
|
|
126
|
+
# Most of the above inputs result in a string for our final data, in which case we'll leave it as the "raw body"
|
|
127
|
+
# so that it can optionally be interpreted as JSON. If we received a bunch of kwargs though, we'll allow those to
|
|
128
|
+
# only be "read" as JSON.
|
|
129
|
+
if data_source == "kwargs":
|
|
130
|
+
self._body_as_json = final_data # type: ignore
|
|
131
|
+
self._body_loaded_as_json = True
|
|
132
|
+
self._has_body = True
|
|
133
|
+
self._body = json.dumps(final_data)
|
|
134
|
+
elif final_data:
|
|
135
|
+
self._has_body = True
|
|
136
|
+
self._body = final_data
|
|
75
137
|
|
|
76
138
|
def get_full_path(self):
|
|
77
|
-
return self.
|
|
78
|
-
|
|
79
|
-
def get_request_method(self):
|
|
80
|
-
return self._request_method if self._request_method else "GET"
|
|
81
|
-
|
|
82
|
-
def request_data(self, required=True):
|
|
83
|
-
request_data = self.json_body(False)
|
|
84
|
-
if not request_data:
|
|
85
|
-
if self.has_body():
|
|
86
|
-
raise ClientError("Request body was not valid JSON")
|
|
87
|
-
request_data = {}
|
|
88
|
-
return request_data
|
|
139
|
+
return self.path
|
|
89
140
|
|
|
90
141
|
def has_body(self):
|
|
91
|
-
if self._has_body is None:
|
|
92
|
-
self._has_body = False
|
|
93
|
-
# we have a number of different input modes that we will treat as data input,
|
|
94
|
-
# all of which the callable handler will use as structured input when trying to
|
|
95
|
-
# compare data against a schema:
|
|
96
|
-
|
|
97
|
-
# isatty() means that someone is piping input into the program
|
|
98
|
-
# however, it behaves unreliably in "alternate" environments in later versions
|
|
99
|
-
# of python, so I'm removing it until I can find a better solution
|
|
100
|
-
# if not self._sys.stdin.isatty():
|
|
101
|
-
# self._has_body = True
|
|
102
|
-
# self._input_type = 'atty'
|
|
103
|
-
# or if the user set 'data' or 'd' keys
|
|
104
|
-
if "data" in self._kwargs or "d" in self._kwargs:
|
|
105
|
-
self._has_body = True
|
|
106
|
-
self._input_type = "data" if "data" in self._kwargs else "d"
|
|
107
|
-
# or finally if we have kwargs in general
|
|
108
|
-
elif len(self._kwargs):
|
|
109
|
-
self._has_body = True
|
|
110
|
-
self._input_type = "kwargs"
|
|
111
142
|
return self._has_body
|
|
112
143
|
|
|
113
144
|
def get_body(self):
|
|
114
145
|
if not self.has_body():
|
|
115
146
|
return ""
|
|
116
147
|
|
|
117
|
-
|
|
118
|
-
if self._input_type == "atty":
|
|
119
|
-
self._cached_body = "\n".join([line.strip() for line in self._sys.stdin])
|
|
120
|
-
elif self._input_type == "data":
|
|
121
|
-
self._cached_body = self._kwargs["data"]
|
|
122
|
-
elif self._input_type == "data":
|
|
123
|
-
self._cached_body = self._kwargs["d"]
|
|
124
|
-
# we don't do anything about self._input_type == 'kwargs' because that only
|
|
125
|
-
# makes sense when trying to interpret the body as JSON, so we cover it
|
|
126
|
-
# in the _get_json_body method
|
|
127
|
-
return self._cached_body
|
|
128
|
-
|
|
129
|
-
def json_body(self, required=True):
|
|
130
|
-
json = self._get_json_body()
|
|
131
|
-
# if we get None then either the body was not JSON or was empty.
|
|
132
|
-
# If it is required then we have an exception either way. If it is not required
|
|
133
|
-
# then we have an exception if a body was provided but it was not JSON. We can check for this
|
|
134
|
-
# if json is None and there is an actual request body. If json is none, the body is empty,
|
|
135
|
-
# and it was not required, then we can just return None
|
|
136
|
-
if json is None:
|
|
137
|
-
if required or self.has_body():
|
|
138
|
-
raise ClientError("Request body was not valid JSON")
|
|
139
|
-
return json
|
|
140
|
-
|
|
141
|
-
def _get_json_body(self):
|
|
142
|
-
if not self.has_body():
|
|
143
|
-
return None
|
|
144
|
-
if not self._body_loaded_as_json:
|
|
145
|
-
if self._input_type == "kwargs":
|
|
146
|
-
self._body_loaded_as_json = True
|
|
147
|
-
self._body_as_json = self._kwargs
|
|
148
|
-
elif self.get_body() is None:
|
|
149
|
-
self._body_as_json = None
|
|
150
|
-
else:
|
|
151
|
-
self._body_loaded_as_json = True
|
|
152
|
-
try:
|
|
153
|
-
self._body_as_json = json.loads(self.get_body())
|
|
154
|
-
except json.JSONDecodeError:
|
|
155
|
-
self._body_as_json = None
|
|
156
|
-
return self._body_as_json
|
|
157
|
-
|
|
158
|
-
def routing_data(self):
|
|
159
|
-
return self._routing_data if self._routing_data is not None else {}
|
|
160
|
-
|
|
161
|
-
def set_routing_data(self, data):
|
|
162
|
-
self._routing_data = data
|
|
163
|
-
|
|
164
|
-
def add_routing_data(self, key, value=None):
|
|
165
|
-
if self._routing_data is None:
|
|
166
|
-
self._routing_data = {}
|
|
167
|
-
if type(key) == dict:
|
|
168
|
-
self._routing_data = {**self._routing_data, **key}
|
|
169
|
-
else:
|
|
170
|
-
self._routing_data[key] = value
|
|
148
|
+
return self._body
|
|
171
149
|
|
|
172
|
-
def
|
|
173
|
-
return {}
|
|
174
|
-
|
|
175
|
-
def get_client_ip(self):
|
|
150
|
+
def get_protocol(self):
|
|
176
151
|
return "cli"
|
|
177
152
|
|
|
178
|
-
def
|
|
179
|
-
|
|
153
|
+
def context_specifics(self):
|
|
154
|
+
return {"sys_argv": sys.argv}
|
|
180
155
|
|
|
181
|
-
def
|
|
182
|
-
return
|
|
156
|
+
def get_client_ip(self):
|
|
157
|
+
return "127.0.0.1"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
_T = TypeVar("_T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Headers(dict[str, str]):
|
|
10
|
+
_duck_cheat = "headers"
|
|
11
|
+
|
|
12
|
+
def __init__(self, headers: dict[str, str] = {}) -> None:
|
|
13
|
+
normalized_headers = (
|
|
14
|
+
{key.upper().replace("_", "-"): value for (key, value) in headers.items()} if headers else {}
|
|
15
|
+
)
|
|
16
|
+
super().__init__(normalized_headers)
|
|
17
|
+
|
|
18
|
+
def __contains__(self, key: object) -> bool:
|
|
19
|
+
if not isinstance(key, str):
|
|
20
|
+
return False
|
|
21
|
+
return super().__contains__(key.upper().replace("_", "-"))
|
|
22
|
+
|
|
23
|
+
def __getitem__(self, key: str) -> str:
|
|
24
|
+
return super().__getitem__(key.upper().replace("_", "-"))
|
|
25
|
+
|
|
26
|
+
def __setitem__(self, key: str, value: str) -> None:
|
|
27
|
+
if not isinstance(key, str):
|
|
28
|
+
raise TypeError(
|
|
29
|
+
f"Header keys must be strings, but an object of type '{key.__class__.__name__}' was provided."
|
|
30
|
+
)
|
|
31
|
+
if not isinstance(value, str):
|
|
32
|
+
raise TypeError(
|
|
33
|
+
f"Header values must be strings, but an object of type '{value.__class__.__name__}' was provided."
|
|
34
|
+
)
|
|
35
|
+
normalized_key = re.sub("\\s+", " ", key.upper().replace("_", "-"))
|
|
36
|
+
normalized_value = re.sub("\\s+", " ", value.strip())
|
|
37
|
+
super().__setitem__(normalized_key, normalized_value)
|
|
38
|
+
|
|
39
|
+
def __getattr__(self, key: str) -> str | None:
|
|
40
|
+
return self.get(key.upper().replace("_", "-"), None)
|
|
41
|
+
|
|
42
|
+
def __setattr__(self, key: str, value: str) -> None:
|
|
43
|
+
if key.startswith("_") or key == "_duck_cheat":
|
|
44
|
+
# Allow setting private attributes and special attributes normally
|
|
45
|
+
super().__setattr__(key, value)
|
|
46
|
+
else:
|
|
47
|
+
self.__setitem__(key, value)
|
|
48
|
+
|
|
49
|
+
def get(self, key: str, default: _T = None) -> str | _T: # type: ignore[assignment]
|
|
50
|
+
return super().get(key.upper().replace("_", "-"), default)
|
|
51
|
+
|
|
52
|
+
def add(self, key: str, value: str) -> None:
|
|
53
|
+
"""Add a header. This expects a string with a colon separating the key and value."""
|
|
54
|
+
setattr(self, key, value)
|