clear-skies 2.0.5__py3-none-any.whl → 2.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clear-skies might be problematic. Click here for more details.
- {clear_skies-2.0.5.dist-info → clear_skies-2.0.6.dist-info}/METADATA +1 -1
- clear_skies-2.0.6.dist-info/RECORD +251 -0
- clearskies/__init__.py +61 -0
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +15 -0
- clearskies/authentication/authentication.py +46 -0
- clearskies/authentication/authorization.py +16 -0
- clearskies/authentication/authorization_pass_through.py +20 -0
- clearskies/authentication/jwks.py +163 -0
- clearskies/authentication/public.py +5 -0
- clearskies/authentication/secret_bearer.py +553 -0
- clearskies/autodoc/__init__.py +8 -0
- clearskies/autodoc/formats/__init__.py +5 -0
- clearskies/autodoc/formats/oai3_json/__init__.py +7 -0
- clearskies/autodoc/formats/oai3_json/oai3_json.py +87 -0
- clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +15 -0
- clearskies/autodoc/formats/oai3_json/parameter.py +35 -0
- clearskies/autodoc/formats/oai3_json/request.py +68 -0
- clearskies/autodoc/formats/oai3_json/response.py +28 -0
- clearskies/autodoc/formats/oai3_json/schema/__init__.py +11 -0
- clearskies/autodoc/formats/oai3_json/schema/array.py +9 -0
- clearskies/autodoc/formats/oai3_json/schema/default.py +13 -0
- clearskies/autodoc/formats/oai3_json/schema/enum.py +7 -0
- clearskies/autodoc/formats/oai3_json/schema/object.py +35 -0
- clearskies/autodoc/formats/oai3_json/test.json +1985 -0
- clearskies/autodoc/py.typed +0 -0
- clearskies/autodoc/request/__init__.py +15 -0
- clearskies/autodoc/request/header.py +6 -0
- clearskies/autodoc/request/json_body.py +6 -0
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +47 -0
- clearskies/autodoc/request/url_parameter.py +6 -0
- clearskies/autodoc/request/url_path.py +6 -0
- clearskies/autodoc/response/__init__.py +5 -0
- clearskies/autodoc/response/response.py +9 -0
- clearskies/autodoc/schema/__init__.py +31 -0
- clearskies/autodoc/schema/array.py +10 -0
- clearskies/autodoc/schema/base64.py +8 -0
- clearskies/autodoc/schema/boolean.py +5 -0
- clearskies/autodoc/schema/date.py +5 -0
- clearskies/autodoc/schema/datetime.py +5 -0
- clearskies/autodoc/schema/double.py +5 -0
- clearskies/autodoc/schema/enum.py +17 -0
- clearskies/autodoc/schema/integer.py +6 -0
- clearskies/autodoc/schema/long.py +5 -0
- clearskies/autodoc/schema/number.py +6 -0
- clearskies/autodoc/schema/object.py +13 -0
- clearskies/autodoc/schema/password.py +5 -0
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +5 -0
- clearskies/backends/__init__.py +65 -0
- clearskies/backends/api_backend.py +1178 -0
- clearskies/backends/backend.py +136 -0
- clearskies/backends/cursor_backend.py +335 -0
- clearskies/backends/memory_backend.py +797 -0
- clearskies/backends/secrets_backend.py +106 -0
- clearskies/column.py +1233 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +206 -0
- clearskies/columns/belongs_to_id.py +483 -0
- clearskies/columns/belongs_to_model.py +132 -0
- clearskies/columns/belongs_to_self.py +105 -0
- clearskies/columns/boolean.py +113 -0
- clearskies/columns/category_tree.py +275 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +127 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +95 -0
- clearskies/columns/created_by_authorization_data.py +116 -0
- clearskies/columns/created_by_header.py +99 -0
- clearskies/columns/created_by_ip.py +92 -0
- clearskies/columns/created_by_routing_data.py +97 -0
- clearskies/columns/created_by_user_agent.py +92 -0
- clearskies/columns/date.py +234 -0
- clearskies/columns/datetime.py +282 -0
- clearskies/columns/email.py +76 -0
- clearskies/columns/float.py +153 -0
- clearskies/columns/has_many.py +505 -0
- clearskies/columns/has_many_self.py +56 -0
- clearskies/columns/has_one.py +14 -0
- clearskies/columns/integer.py +160 -0
- clearskies/columns/json.py +128 -0
- clearskies/columns/many_to_many_ids.py +337 -0
- clearskies/columns/many_to_many_ids_with_data.py +274 -0
- clearskies/columns/many_to_many_models.py +158 -0
- clearskies/columns/many_to_many_pivots.py +134 -0
- clearskies/columns/phone.py +159 -0
- clearskies/columns/select.py +92 -0
- clearskies/columns/string.py +102 -0
- clearskies/columns/timestamp.py +164 -0
- clearskies/columns/updated.py +110 -0
- clearskies/columns/uuid.py +86 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +162 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +13 -0
- clearskies/configs/any_dict.py +22 -0
- clearskies/configs/any_dict_or_callable.py +23 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +16 -0
- clearskies/configs/boolean_or_callable.py +18 -0
- clearskies/configs/callable_config.py +18 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +24 -0
- clearskies/configs/datetime.py +18 -0
- clearskies/configs/datetime_or_callable.py +19 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +29 -0
- clearskies/configs/float.py +16 -0
- clearskies/configs/float_or_callable.py +18 -0
- clearskies/configs/integer.py +16 -0
- clearskies/configs/integer_or_callable.py +18 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +30 -0
- clearskies/configs/list_any_dict_or_callable.py +31 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +65 -0
- clearskies/configs/model_columns.py +56 -0
- clearskies/configs/model_destination_name.py +25 -0
- clearskies/configs/model_to_id_column.py +43 -0
- clearskies/configs/readable_model_column.py +9 -0
- clearskies/configs/readable_model_columns.py +9 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +9 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +26 -0
- clearskies/configs/select_list.py +47 -0
- clearskies/configs/string.py +29 -0
- clearskies/configs/string_dict.py +32 -0
- clearskies/configs/string_list.py +32 -0
- clearskies/configs/string_list_or_callable.py +35 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +18 -0
- clearskies/configs/timezone.py +18 -0
- clearskies/configs/url.py +23 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +9 -0
- clearskies/configs/writeable_model_columns.py +9 -0
- clearskies/configurable.py +76 -0
- clearskies/contexts/__init__.py +11 -0
- clearskies/contexts/cli.py +117 -0
- clearskies/contexts/context.py +98 -0
- clearskies/contexts/wsgi.py +76 -0
- clearskies/contexts/wsgi_ref.py +82 -0
- clearskies/decorators.py +33 -0
- clearskies/di/__init__.py +14 -0
- clearskies/di/additional_config.py +130 -0
- clearskies/di/additional_config_auto_import.py +17 -0
- clearskies/di/di.py +973 -0
- clearskies/di/inject/__init__.py +23 -0
- clearskies/di/inject/by_class.py +21 -0
- clearskies/di/inject/by_name.py +18 -0
- clearskies/di/inject/di.py +13 -0
- clearskies/di/inject/environment.py +14 -0
- clearskies/di/inject/input_output.py +20 -0
- clearskies/di/inject/now.py +13 -0
- clearskies/di/inject/requests.py +13 -0
- clearskies/di/inject/secrets.py +14 -0
- clearskies/di/inject/utcnow.py +13 -0
- clearskies/di/inject/uuid.py +15 -0
- clearskies/di/injectable.py +29 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/di/test_module/__init__.py +6 -0
- clearskies/di/test_module/another_module/__init__.py +2 -0
- clearskies/di/test_module/module_class.py +5 -0
- clearskies/end.py +183 -0
- clearskies/endpoint.py +1314 -0
- clearskies/endpoint_group.py +336 -0
- clearskies/endpoints/__init__.py +25 -0
- clearskies/endpoints/advanced_search.py +526 -0
- clearskies/endpoints/callable.py +388 -0
- clearskies/endpoints/create.py +205 -0
- clearskies/endpoints/delete.py +139 -0
- clearskies/endpoints/get.py +271 -0
- clearskies/endpoints/health_check.py +183 -0
- clearskies/endpoints/list.py +574 -0
- clearskies/endpoints/restful_api.py +427 -0
- clearskies/endpoints/schema.py +189 -0
- clearskies/endpoints/simple_search.py +286 -0
- clearskies/endpoints/update.py +193 -0
- clearskies/environment.py +104 -0
- clearskies/exceptions/__init__.py +19 -0
- clearskies/exceptions/authentication.py +2 -0
- clearskies/exceptions/authorization.py +2 -0
- clearskies/exceptions/client_error.py +2 -0
- clearskies/exceptions/input_errors.py +4 -0
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/exceptions/not_found.py +2 -0
- clearskies/functional/__init__.py +7 -0
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +112 -0
- clearskies/functional/validations.py +76 -0
- clearskies/input_outputs/__init__.py +13 -0
- clearskies/input_outputs/cli.py +171 -0
- clearskies/input_outputs/exceptions/__init__.py +2 -0
- clearskies/input_outputs/exceptions/cli_input_error.py +2 -0
- clearskies/input_outputs/exceptions/cli_not_found.py +2 -0
- clearskies/input_outputs/headers.py +45 -0
- clearskies/input_outputs/input_output.py +138 -0
- clearskies/input_outputs/programmatic.py +69 -0
- clearskies/input_outputs/py.typed +0 -0
- clearskies/input_outputs/wsgi.py +77 -0
- clearskies/model.py +1922 -0
- clearskies/py.typed +0 -0
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +223 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +196 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +6 -0
- clearskies/secrets/additional_configs/__init__.py +32 -0
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +61 -0
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +160 -0
- clearskies/secrets/akeyless.py +182 -0
- clearskies/secrets/exceptions/__init__.py +1 -0
- clearskies/secrets/exceptions/not_found.py +2 -0
- clearskies/secrets/secrets.py +38 -0
- clearskies/security_header.py +15 -0
- clearskies/security_headers/__init__.py +11 -0
- clearskies/security_headers/cache_control.py +67 -0
- clearskies/security_headers/cors.py +50 -0
- clearskies/security_headers/csp.py +94 -0
- clearskies/security_headers/hsts.py +22 -0
- clearskies/security_headers/x_content_type_options.py +0 -0
- clearskies/security_headers/x_frame_options.py +0 -0
- clearskies/test_base.py +8 -0
- clearskies/typing.py +11 -0
- clearskies/validator.py +37 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +62 -0
- clearskies/validators/before_column.py +13 -0
- clearskies/validators/in_the_future.py +32 -0
- clearskies/validators/in_the_future_at_least.py +11 -0
- clearskies/validators/in_the_future_at_most.py +10 -0
- clearskies/validators/in_the_past.py +32 -0
- clearskies/validators/in_the_past_at_least.py +10 -0
- clearskies/validators/in_the_past_at_most.py +10 -0
- clearskies/validators/maximum_length.py +26 -0
- clearskies/validators/maximum_value.py +29 -0
- clearskies/validators/minimum_length.py +26 -0
- clearskies/validators/minimum_value.py +29 -0
- clearskies/validators/required.py +34 -0
- clearskies/validators/timedelta.py +59 -0
- clearskies/validators/unique.py +30 -0
- clear_skies-2.0.5.dist-info/RECORD +0 -4
- {clear_skies-2.0.5.dist-info → clear_skies-2.0.6.dist-info}/WHEEL +0 -0
- {clear_skies-2.0.5.dist-info → clear_skies-2.0.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from clearskies.configs import config
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class String(config.Config):
|
|
5
|
+
def __init__(self, required=False, default=None, regexp: str = ""):
|
|
6
|
+
self.required = required
|
|
7
|
+
self.default = default
|
|
8
|
+
self.regexp = regexp
|
|
9
|
+
|
|
10
|
+
def __set__(self, instance, value: str):
|
|
11
|
+
if not isinstance(value, str):
|
|
12
|
+
error_prefix = self._error_prefix(instance)
|
|
13
|
+
raise TypeError(
|
|
14
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requires a string."
|
|
15
|
+
)
|
|
16
|
+
if self.regexp:
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
if not re.match(self.regexp, value):
|
|
20
|
+
error_prefix = self._error_prefix(instance)
|
|
21
|
+
raise ValueError(
|
|
22
|
+
f"{error_prefix} attempt to set a value of '{value}' but this does not match the required regexp: '{self.regexp}'."
|
|
23
|
+
)
|
|
24
|
+
instance._set_config(self, value)
|
|
25
|
+
|
|
26
|
+
def __get__(self, instance, parent) -> str:
|
|
27
|
+
if not instance:
|
|
28
|
+
return self # type: ignore
|
|
29
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from clearskies.configs import config
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StringDict(config.Config):
|
|
5
|
+
"""This is for a configuration that should be a dictionary with keys and values that are all strings."""
|
|
6
|
+
|
|
7
|
+
def __set__(self, instance, value: dict[str, str]):
|
|
8
|
+
if value is None:
|
|
9
|
+
return
|
|
10
|
+
|
|
11
|
+
if not isinstance(value, dict):
|
|
12
|
+
error_prefix = self._error_prefix(instance)
|
|
13
|
+
raise TypeError(
|
|
14
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a dict parameter"
|
|
15
|
+
)
|
|
16
|
+
for key, val in value.items():
|
|
17
|
+
if not isinstance(key, str):
|
|
18
|
+
error_prefix = self._error_prefix(instance)
|
|
19
|
+
raise TypeError(
|
|
20
|
+
f"{error_prefix} attempt to set a key of type '{key.__class__.__name__}' when only string keys are allowed."
|
|
21
|
+
)
|
|
22
|
+
if not isinstance(val, str):
|
|
23
|
+
error_prefix = self._error_prefix(instance)
|
|
24
|
+
raise TypeError(
|
|
25
|
+
f"{error_prefix} attempt to set a value of type '{val.__class__.__name__}' for key '{key}'. A string was expected."
|
|
26
|
+
)
|
|
27
|
+
instance._set_config(self, value)
|
|
28
|
+
|
|
29
|
+
def __get__(self, instance, parent) -> dict[str, str]:
|
|
30
|
+
if not instance:
|
|
31
|
+
return self # type: ignore
|
|
32
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from clearskies.configs import config
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StringList(config.Config):
|
|
5
|
+
"""
|
|
6
|
+
This is for a configuration that should be a list of strings.
|
|
7
|
+
|
|
8
|
+
This is different than SelectList, which also accepts a list of strings, but
|
|
9
|
+
valdiates that all of those values match against an allow list.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __set__(self, instance, value: list[str]):
|
|
13
|
+
if value is None:
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
if not isinstance(value, list):
|
|
17
|
+
error_prefix = self._error_prefix(instance)
|
|
18
|
+
raise TypeError(
|
|
19
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a list parameter"
|
|
20
|
+
)
|
|
21
|
+
for index, item in enumerate(value):
|
|
22
|
+
if not isinstance(item, str):
|
|
23
|
+
error_prefix = self._error_prefix(instance)
|
|
24
|
+
raise TypeError(
|
|
25
|
+
f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1}. A string was expected."
|
|
26
|
+
)
|
|
27
|
+
instance._set_config(self, value)
|
|
28
|
+
|
|
29
|
+
def __get__(self, instance, parent) -> list[str]:
|
|
30
|
+
if not instance:
|
|
31
|
+
return self # type: ignore
|
|
32
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from clearskies.configs import config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StringListOrCallable(config.Config):
|
|
7
|
+
"""
|
|
8
|
+
This is for a configuration that should be a list of strings or a callable that returns a list of strings.
|
|
9
|
+
|
|
10
|
+
This is different than SelectList, which also accepts a list of strings, but
|
|
11
|
+
valdiates that all of those values match against an allow list.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __set__(self, instance, value: list[str] | Callable[..., list[str]]):
|
|
15
|
+
if value is None:
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
if not isinstance(value, list) and not callable(value):
|
|
19
|
+
error_prefix = self._error_prefix(instance)
|
|
20
|
+
raise TypeError(
|
|
21
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that should be a list or a callable"
|
|
22
|
+
)
|
|
23
|
+
if isinstance(value, list):
|
|
24
|
+
for index, item in enumerate(value):
|
|
25
|
+
if not isinstance(item, str):
|
|
26
|
+
error_prefix = self._error_prefix(instance)
|
|
27
|
+
raise TypeError(
|
|
28
|
+
f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1}. A string was expected."
|
|
29
|
+
)
|
|
30
|
+
instance._set_config(self, value)
|
|
31
|
+
|
|
32
|
+
def __get__(self, instance, parent) -> list[str] | Callable[..., list[str]]:
|
|
33
|
+
if not instance:
|
|
34
|
+
return self # type: ignore
|
|
35
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from clearskies.configs import config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StringOrCallable(config.Config):
|
|
7
|
+
def __set__(self, instance, value: str | Callable[..., str]):
|
|
8
|
+
if not isinstance(value, str) and not callable(value):
|
|
9
|
+
error_prefix = self._error_prefix(instance)
|
|
10
|
+
raise TypeError(
|
|
11
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requires a string or a callable."
|
|
12
|
+
)
|
|
13
|
+
instance._set_config(self, value)
|
|
14
|
+
|
|
15
|
+
def __get__(self, instance, parent) -> str | Callable[..., str]:
|
|
16
|
+
if not instance:
|
|
17
|
+
return self # type: ignore
|
|
18
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from clearskies.configs import config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Timedelta(config.Config):
|
|
7
|
+
def __set__(self, instance, value: datetime.timedelta):
|
|
8
|
+
if not isinstance(value, datetime.timedelta):
|
|
9
|
+
error_prefix = self._error_prefix(instance)
|
|
10
|
+
raise TypeError(
|
|
11
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a datetime.timedelta object."
|
|
12
|
+
)
|
|
13
|
+
instance._set_config(self, value)
|
|
14
|
+
|
|
15
|
+
def __get__(self, instance, parent) -> datetime.timedelta:
|
|
16
|
+
if not instance:
|
|
17
|
+
return self # type: ignore
|
|
18
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from clearskies.configs import config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Timezone(config.Config):
|
|
7
|
+
def __set__(self, instance, value: datetime.timezone | None):
|
|
8
|
+
if value and not isinstance(value, datetime.timezone):
|
|
9
|
+
error_prefix = self._error_prefix(instance)
|
|
10
|
+
raise TypeError(
|
|
11
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a timezone (datetime.timezone)."
|
|
12
|
+
)
|
|
13
|
+
instance._set_config(self, value)
|
|
14
|
+
|
|
15
|
+
def __get__(self, instance, parent) -> datetime.timezone:
|
|
16
|
+
if not instance:
|
|
17
|
+
return self # type: ignore
|
|
18
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from clearskies.configs import string
|
|
2
|
+
from clearskies.functional import routing
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Url(string.String):
|
|
6
|
+
def __set__(self, instance, value: str):
|
|
7
|
+
if value is None:
|
|
8
|
+
return
|
|
9
|
+
|
|
10
|
+
if not isinstance(value, str):
|
|
11
|
+
error_prefix = self._error_prefix(instance)
|
|
12
|
+
raise TypeError(
|
|
13
|
+
f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a url parameter"
|
|
14
|
+
)
|
|
15
|
+
value = value.strip("/")
|
|
16
|
+
|
|
17
|
+
if value:
|
|
18
|
+
try:
|
|
19
|
+
routing.extract_url_parameter_name_map(value)
|
|
20
|
+
except ValueError as e:
|
|
21
|
+
error_prefix = self._error_prefix(instance)
|
|
22
|
+
raise ValueError(f"{error_prefix} {e}")
|
|
23
|
+
instance._set_config(self, value)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from clearskies.configs import config
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from clearskies import typing
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Validators(config.Config):
|
|
12
|
+
"""
|
|
13
|
+
Validator config.
|
|
14
|
+
|
|
15
|
+
A config that accepts various things that are accepted as validators in model columns:
|
|
16
|
+
|
|
17
|
+
1. An instance of clearskies.validators.Validator
|
|
18
|
+
2. A list of clearskies.validators.Validator
|
|
19
|
+
|
|
20
|
+
Incoming values are normalized to a list so that a list always comes out even if a non-list is provided.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __set__(
|
|
24
|
+
self,
|
|
25
|
+
instance,
|
|
26
|
+
value: typing.validator | list[typing.validator],
|
|
27
|
+
):
|
|
28
|
+
if not isinstance(value, list):
|
|
29
|
+
value = [value]
|
|
30
|
+
|
|
31
|
+
for index, item in enumerate(value):
|
|
32
|
+
if hasattr(item, "additional_write_columns") and hasattr(item, "check"):
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
error_prefix = self._error_prefix(instance)
|
|
36
|
+
raise TypeError(
|
|
37
|
+
f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1} when a Validator is required"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
instance._set_config(self, [*value])
|
|
41
|
+
|
|
42
|
+
def __get__(self, instance, parent) -> list[typing.validator]:
|
|
43
|
+
if not instance:
|
|
44
|
+
return self # type: ignore
|
|
45
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from clearskies.configs import model_column
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WriteableModelColumn(model_column.ModelColumn):
|
|
5
|
+
def get_allowed_columns(self, model_class, column_configs):
|
|
6
|
+
return [name for (name, column) in column_configs.items() if column.is_writeable]
|
|
7
|
+
|
|
8
|
+
def my_description(self):
|
|
9
|
+
return "writeable column"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from clearskies.configs import model_columns
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WriteableModelColumns(model_columns.ModelColumns):
|
|
5
|
+
def get_allowed_columns(self, model_class, column_configs):
|
|
6
|
+
return [name for (name, column) in column_configs.items() if column.is_writeable]
|
|
7
|
+
|
|
8
|
+
def my_description(self):
|
|
9
|
+
return "writeable column"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from clearskies.configs import config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Configurable:
|
|
7
|
+
_config: dict[str, Any] | None = None
|
|
8
|
+
_descriptor_config_map: dict[int, str] | None = None
|
|
9
|
+
|
|
10
|
+
def _set_config(self, descriptor, value):
|
|
11
|
+
if not self._config:
|
|
12
|
+
self._config = {}
|
|
13
|
+
|
|
14
|
+
self._config[self._descriptor_to_name(descriptor)] = value
|
|
15
|
+
|
|
16
|
+
def _get_config(self, descriptor):
|
|
17
|
+
if not self._config:
|
|
18
|
+
self._config = {}
|
|
19
|
+
|
|
20
|
+
name = self._descriptor_to_name(descriptor)
|
|
21
|
+
if name not in self._config:
|
|
22
|
+
raise KeyError(f"Attempt to fetch a config value named '{name}' but no value has been set for this config")
|
|
23
|
+
return self._config[name]
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def _get_config_object(cls, attribute_name):
|
|
27
|
+
return getattr(cls, attribute_name)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_descriptor_config_map(cls):
|
|
31
|
+
if cls._descriptor_config_map:
|
|
32
|
+
return cls._descriptor_config_map
|
|
33
|
+
|
|
34
|
+
descriptor_config_map = {}
|
|
35
|
+
for attribute_name in dir(cls):
|
|
36
|
+
descriptor = getattr(cls, attribute_name)
|
|
37
|
+
if not isinstance(descriptor, config.Config):
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
descriptor_config_map[id(descriptor)] = attribute_name
|
|
41
|
+
|
|
42
|
+
cls._descriptor_config_map = descriptor_config_map
|
|
43
|
+
return cls._descriptor_config_map
|
|
44
|
+
|
|
45
|
+
def _descriptor_to_name(self, descriptor):
|
|
46
|
+
descriptor_config_map = self.get_descriptor_config_map()
|
|
47
|
+
if id(descriptor) not in descriptor_config_map:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"The reason behind this error is kinda long and complicated, but doens't really matter. To make it go away, just add `_descriptor_config_map = None` to the definition of {self.__class__.__name__}"
|
|
50
|
+
)
|
|
51
|
+
return descriptor_config_map[id(descriptor)]
|
|
52
|
+
|
|
53
|
+
def finalize_and_validate_configuration(self):
|
|
54
|
+
my_class = self.__class__
|
|
55
|
+
if not self._config:
|
|
56
|
+
self._config = {}
|
|
57
|
+
|
|
58
|
+
# now it's time to check for required values and provide defaults
|
|
59
|
+
attribute_names = self.get_descriptor_config_map().values()
|
|
60
|
+
for attribute_name in attribute_names:
|
|
61
|
+
config = getattr(my_class, attribute_name)
|
|
62
|
+
if attribute_name not in self._config:
|
|
63
|
+
self._config[attribute_name] = config.default
|
|
64
|
+
|
|
65
|
+
if config.required and self._config.get(attribute_name) is None:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Missing required configuration property '{attribute_name}' for class '{my_class.__name__}'"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# loop through a second time to have the configs check their values
|
|
71
|
+
# we do this as a separate step because we want to make sure required and default
|
|
72
|
+
# values are specified before we have the configs do their validation.
|
|
73
|
+
for attribute_name in attribute_names:
|
|
74
|
+
getattr(my_class, attribute_name).finalize_and_validate_configuration(self)
|
|
75
|
+
if attribute_name not in self._config:
|
|
76
|
+
self._config[attribute_name] = None
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from clearskies.contexts.context import Context
|
|
2
|
+
from clearskies.input_outputs import Cli as CliInputOutput
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Cli(Context):
|
|
6
|
+
"""
|
|
7
|
+
Run an application via a CLI command
|
|
8
|
+
|
|
9
|
+
This context converts a clearskies application into a CLI command. Here's a simple example:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
#!/usr/bin/env python
|
|
13
|
+
import clearskies
|
|
14
|
+
|
|
15
|
+
def my_function():
|
|
16
|
+
return "Hello World!"
|
|
17
|
+
|
|
18
|
+
cli = clearskies.contexts.Cli(my_function)
|
|
19
|
+
cli()
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Which you can then run as expected:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
$ ./example.py
|
|
26
|
+
Hello World!
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Routing is still supported, with routes and route parameters becoming CLI args:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
#!/usr/bin/env python
|
|
33
|
+
import clearskies
|
|
34
|
+
|
|
35
|
+
def my_function(name):
|
|
36
|
+
return f"Hello {name}!"
|
|
37
|
+
|
|
38
|
+
cli = clearskies.contexts.Cli(
|
|
39
|
+
clearskies.endpoints.Callable(
|
|
40
|
+
my_function,
|
|
41
|
+
url="/hello/:name",
|
|
42
|
+
return_standard_response=False,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
cli()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With a url of `/hello/:name` you would invoke like so:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
./example.py hello Bob
|
|
52
|
+
Hello Bob!
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If the endpoint expects a request method you can provide it by setting the `-X` or `--request_method=`
|
|
56
|
+
kwargs. So for tihs example:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
#!/usr/bin/env python
|
|
60
|
+
import clearskies
|
|
61
|
+
|
|
62
|
+
def my_function(name):
|
|
63
|
+
return f"Hello {name}!"
|
|
64
|
+
|
|
65
|
+
cli = clearskies.contexts.Cli(
|
|
66
|
+
clearskies.endpoints.Callable(
|
|
67
|
+
my_function,
|
|
68
|
+
url="/hello/:name",
|
|
69
|
+
request_methods=["POST"],
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
cli()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
And then calling it successfully:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
./example.py hello Bob --request_method=POST
|
|
79
|
+
|
|
80
|
+
./example.py hello Bob -X POST
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
You can pass data as a json string with the -d flag or set individual named arguments. The following
|
|
84
|
+
example just reflects the request data back to the client:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
#!/usr/bin/env python
|
|
88
|
+
import clearskies
|
|
89
|
+
|
|
90
|
+
def my_function(request_data):
|
|
91
|
+
return request_data
|
|
92
|
+
|
|
93
|
+
cli = clearskies.contexts.Cli(
|
|
94
|
+
clearskies.endpoints.Callable(
|
|
95
|
+
my_function,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
cli()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
And these three calls are identical:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
./example.py -d '{"hello": "world"}'
|
|
105
|
+
|
|
106
|
+
echo '{"hello": "world"}' | ./test.py
|
|
107
|
+
|
|
108
|
+
./test.py --hello=world
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Although note that the first two are going to be preferred over the third, simply because with the
|
|
112
|
+
third there's simply no way to specify the type of a variable. As a result, you may run into issues
|
|
113
|
+
with strict type checking on endpoints.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __call__(self): # type: ignore
|
|
117
|
+
return self.execute_application(CliInputOutput())
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
6
|
+
|
|
7
|
+
import clearskies.endpoint
|
|
8
|
+
import clearskies.endpoint_group
|
|
9
|
+
from clearskies.di import Di
|
|
10
|
+
from clearskies.di.additional_config import AdditionalConfig
|
|
11
|
+
from clearskies.input_outputs import Programmatic
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from clearskies.input_outputs import InputOutput
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Context:
|
|
18
|
+
"""
|
|
19
|
+
Context: a flexible way to connect applications to hosting strategies.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
di: Di = None # type: ignore
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
The application to execute.
|
|
26
|
+
|
|
27
|
+
This can be a callable, an endpoint, or an endpoint group. If passed a callable, the callable can request any
|
|
28
|
+
standard or defined dependencies and should return the desired response. It can also raise any exception from
|
|
29
|
+
clearskies.exceptions.
|
|
30
|
+
"""
|
|
31
|
+
application: Callable | clearskies.endpoint.Endpoint | clearskies.endpoint_group.EndpointGroup = None # type: ignore
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
application: Callable | clearskies.endpoint.Endpoint | clearskies.endpoint_group.EndpointGroup,
|
|
36
|
+
classes: type | list[type] = [],
|
|
37
|
+
modules: ModuleType | list[ModuleType] = [],
|
|
38
|
+
bindings: dict[str, Any] = {},
|
|
39
|
+
additional_configs: AdditionalConfig | list[AdditionalConfig] = [],
|
|
40
|
+
class_overrides: dict[type, Any] = {},
|
|
41
|
+
overrides: dict[str, type] = {},
|
|
42
|
+
now: datetime.datetime | None = None,
|
|
43
|
+
utcnow: datetime.datetime | None = None,
|
|
44
|
+
):
|
|
45
|
+
self.di = Di(
|
|
46
|
+
classes=classes,
|
|
47
|
+
modules=modules,
|
|
48
|
+
bindings=bindings,
|
|
49
|
+
additional_configs=additional_configs,
|
|
50
|
+
class_overrides=class_overrides,
|
|
51
|
+
overrides=overrides,
|
|
52
|
+
now=now,
|
|
53
|
+
utcnow=utcnow,
|
|
54
|
+
)
|
|
55
|
+
self.application = application
|
|
56
|
+
|
|
57
|
+
def execute_application(self, input_output: InputOutput):
|
|
58
|
+
if hasattr(self.application, "injectable_properties"):
|
|
59
|
+
self.application.injectable_properties(self.di)
|
|
60
|
+
return self.application(input_output)
|
|
61
|
+
elif callable(self.application):
|
|
62
|
+
try:
|
|
63
|
+
return input_output.respond(
|
|
64
|
+
self.di.call_function(self.application, **input_output.get_context_for_callables())
|
|
65
|
+
)
|
|
66
|
+
except clearskies.exceptions.ClientError as e:
|
|
67
|
+
return input_output.respond(str(e), 400)
|
|
68
|
+
except clearskies.exceptions.Authentication as e:
|
|
69
|
+
return input_output.respond(str(e), 401)
|
|
70
|
+
except clearskies.exceptions.Authorization as e:
|
|
71
|
+
return input_output.respond(str(e), 403)
|
|
72
|
+
except clearskies.exceptions.NotFound as e:
|
|
73
|
+
return input_output.respond(str(e), 404)
|
|
74
|
+
except clearskies.exceptions.MovedPermanently as e:
|
|
75
|
+
return input_output.respond(str(e), 302)
|
|
76
|
+
except clearskies.exceptions.MovedTemporarily as e:
|
|
77
|
+
return input_output.respond(str(e), 307)
|
|
78
|
+
|
|
79
|
+
def __call__(
|
|
80
|
+
self,
|
|
81
|
+
url: str = "",
|
|
82
|
+
request_method: str = "GET",
|
|
83
|
+
body: str | dict[str, Any] | list[Any] = "",
|
|
84
|
+
query_parameters: dict[str, str] = {},
|
|
85
|
+
request_headers: dict[str, str] = {},
|
|
86
|
+
):
|
|
87
|
+
return self.execute_application(
|
|
88
|
+
Programmatic(
|
|
89
|
+
url=url,
|
|
90
|
+
request_method=request_method,
|
|
91
|
+
body=body,
|
|
92
|
+
query_parameters=query_parameters,
|
|
93
|
+
request_headers=request_headers,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def build(self, thing: Any, cache: bool = False) -> Any:
|
|
98
|
+
return self.di.build(thing, cache=cache)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from types import ModuleType
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
from wsgiref.simple_server import make_server
|
|
5
|
+
from wsgiref.util import setup_testing_defaults
|
|
6
|
+
|
|
7
|
+
import clearskies.endpoint
|
|
8
|
+
import clearskies.endpoint_group
|
|
9
|
+
from clearskies.contexts.context import Context
|
|
10
|
+
from clearskies.di import AdditionalConfig
|
|
11
|
+
from clearskies.input_outputs import Wsgi as WsgiInputOutput
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Wsgi(Context):
|
|
15
|
+
"""
|
|
16
|
+
Connect your application to a WSGI server.
|
|
17
|
+
|
|
18
|
+
The Wsgi context is used to connect a clearskies application to a WSGI server of your choice. As with all
|
|
19
|
+
contexts, you first create it and pass in the application (a callable, endpoint, or endpoint group) as well
|
|
20
|
+
as any dependency injection parameters. Then, you call the context from inside of the function invoked by
|
|
21
|
+
your WSGI server, passing along the `environment` and `start_response` variables, and returning the response
|
|
22
|
+
from the context. Here's a simple example:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
import clearskies
|
|
26
|
+
|
|
27
|
+
def hello_world():
|
|
28
|
+
return "Hello World!"
|
|
29
|
+
|
|
30
|
+
wsgi = clearskies.contexts.Wsgi(hello_world)
|
|
31
|
+
|
|
32
|
+
def application(environment, start_response):
|
|
33
|
+
return wsgi(environment, start_response)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You would then launch your WSGI server. For instance, here's how to launch it with uwsgi, which automatically
|
|
37
|
+
looks for a function called `application` and treats that as the WSGI starting point:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
uwsgi --http :9090 --wsgi-file test.py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You could then:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
curl 'http://localhost:9090'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
And see the response from this "hello world" app. Note than in the above example I create the context outside
|
|
50
|
+
of the application function. Of course, you can do the opposite:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
import clearskies
|
|
54
|
+
|
|
55
|
+
def hello_world():
|
|
56
|
+
return "Hello World!"
|
|
57
|
+
|
|
58
|
+
def application(environment, start_response):
|
|
59
|
+
wsgi = clearskies.contexts.Wsgi(hello_world)
|
|
60
|
+
return wsgi(environment, start_response)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The difference is that most wsgi servers will cache any objects created outside of the handler function (e.g. `application`
|
|
64
|
+
in this case). When you first create the context clearskies will configure and validate any endpoints attached.
|
|
65
|
+
Also, it will create an instance of the dependency injection container and cache it. If the context object is created
|
|
66
|
+
outside of the handler, and the server caches objects in this csae, then this validation will only happen once and
|
|
67
|
+
the DI cache will store objects in between HTTP calls. If you create your context inside the handler function, then
|
|
68
|
+
you'll end up with an empty cache everytime and you'll have slower responses because of clearskies checking the
|
|
69
|
+
application configuration everytime. Note that the DI system for clearskies grants you full cache control, so
|
|
70
|
+
by and large it's normal and expected that you'll persist the cache between requests by creating the context outside
|
|
71
|
+
of any handler functions.
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __call__(self, env, start_response): # type: ignore
|
|
76
|
+
return self.execute_application(WsgiInputOutput(env, start_response))
|