clear-skies 0.0.3a0__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-0.0.3a0.dist-info/LICENSE +7 -0
- clear_skies-0.0.3a0.dist-info/METADATA +46 -0
- clear_skies-0.0.3a0.dist-info/RECORD +248 -0
- clear_skies-0.0.3a0.dist-info/WHEEL +4 -0
- clearskies/__init__.py +62 -0
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +15 -0
- clearskies/authentication/authentication.py +42 -0
- clearskies/authentication/authorization.py +12 -0
- clearskies/authentication/authorization_pass_through.py +20 -0
- clearskies/authentication/jwks.py +158 -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 +29 -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 +38 -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 +123 -0
- clearskies/backends/cursor_backend.py +335 -0
- clearskies/backends/memory_backend.py +797 -0
- clearskies/backends/secrets_backend.py +107 -0
- clearskies/column.py +1232 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +205 -0
- clearskies/columns/belongs_to_id.py +483 -0
- clearskies/columns/belongs_to_model.py +128 -0
- clearskies/columns/belongs_to_self.py +105 -0
- clearskies/columns/boolean.py +109 -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 +94 -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 +96 -0
- clearskies/columns/created_by_user_agent.py +92 -0
- clearskies/columns/date.py +230 -0
- clearskies/columns/datetime.py +278 -0
- clearskies/columns/email.py +76 -0
- clearskies/columns/float.py +149 -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 +156 -0
- clearskies/columns/json.py +122 -0
- clearskies/columns/many_to_many_ids.py +333 -0
- clearskies/columns/many_to_many_ids_with_data.py +270 -0
- clearskies/columns/many_to_many_models.py +154 -0
- clearskies/columns/many_to_many_pivots.py +133 -0
- clearskies/columns/phone.py +158 -0
- clearskies/columns/select.py +91 -0
- clearskies/columns/string.py +98 -0
- clearskies/columns/timestamp.py +160 -0
- clearskies/columns/updated.py +110 -0
- clearskies/columns/uuid.py +86 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +159 -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 +21 -0
- clearskies/configs/datetime.py +18 -0
- clearskies/configs/datetime_or_callable.py +19 -0
- clearskies/configs/endpoint.py +23 -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 +7 -0
- clearskies/contexts/context.py +84 -0
- clearskies/contexts/wsgi.py +16 -0
- clearskies/contexts/wsgi_ref.py +49 -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 +968 -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 +1309 -0
- clearskies/endpoint_group.py +297 -0
- clearskies/endpoints/__init__.py +23 -0
- clearskies/endpoints/advanced_search.py +526 -0
- clearskies/endpoints/callable.py +387 -0
- clearskies/endpoints/create.py +202 -0
- clearskies/endpoints/delete.py +139 -0
- clearskies/endpoints/get.py +275 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +573 -0
- clearskies/endpoints/restful_api.py +427 -0
- clearskies/endpoints/simple_search.py +286 -0
- clearskies/endpoints/update.py +190 -0
- clearskies/environment.py +104 -0
- clearskies/exceptions/__init__.py +17 -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/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 +170 -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 +662 -0
- clearskies/parameters_to_properties.py +31 -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 +8 -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 +25 -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 +35 -0
- clearskies/validators/timedelta.py +59 -0
- clearskies/validators/unique.py +31 -0
|
@@ -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,84 @@
|
|
|
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
|
+
di: Di = None # type: ignore
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
application: Callable | clearskies.endpoint.Endpoint | clearskies.endpoint_group.EndpointGroup,
|
|
23
|
+
classes: type | list[type] = [],
|
|
24
|
+
modules: ModuleType | list[ModuleType] = [],
|
|
25
|
+
bindings: dict[str, Any] = {},
|
|
26
|
+
additional_configs: AdditionalConfig | list[AdditionalConfig] = [],
|
|
27
|
+
class_overrides: dict[type, Any] = {},
|
|
28
|
+
overrides: dict[str, type] = {},
|
|
29
|
+
now: datetime.datetime | None = None,
|
|
30
|
+
utcnow: datetime.datetime | None = None,
|
|
31
|
+
):
|
|
32
|
+
self.di = Di(
|
|
33
|
+
classes=classes,
|
|
34
|
+
modules=modules,
|
|
35
|
+
bindings=bindings,
|
|
36
|
+
additional_configs=additional_configs,
|
|
37
|
+
class_overrides=class_overrides,
|
|
38
|
+
now=now,
|
|
39
|
+
utcnow=utcnow,
|
|
40
|
+
)
|
|
41
|
+
self.application = application
|
|
42
|
+
|
|
43
|
+
def execute_application(self, input_output: InputOutput):
|
|
44
|
+
if hasattr(self.application, "injectable_properties"):
|
|
45
|
+
self.application.injectable_properties(self.di)
|
|
46
|
+
return self.application(input_output)
|
|
47
|
+
elif callable(self.application):
|
|
48
|
+
try:
|
|
49
|
+
return input_output.respond(
|
|
50
|
+
self.di.call_function(self.application, **input_output.get_context_for_callables())
|
|
51
|
+
)
|
|
52
|
+
except clearskies.exceptions.ClientError as e:
|
|
53
|
+
return input_output.respond(str(e), 400)
|
|
54
|
+
except clearskies.exceptions.Authentication as e:
|
|
55
|
+
return input_output.respond(str(e), 401)
|
|
56
|
+
except clearskies.exceptions.Authorization as e:
|
|
57
|
+
return input_output.respond(str(e), 403)
|
|
58
|
+
except clearskies.exceptions.NotFound as e:
|
|
59
|
+
return input_output.respond(str(e), 404)
|
|
60
|
+
except clearskies.exceptions.MovedPermanently as e:
|
|
61
|
+
return input_output.respond(str(e), 302)
|
|
62
|
+
except clearskies.exceptions.MovedTemporarily as e:
|
|
63
|
+
return input_output.respond(str(e), 307)
|
|
64
|
+
|
|
65
|
+
def __call__(
|
|
66
|
+
self,
|
|
67
|
+
url: str = "",
|
|
68
|
+
request_method: str = "GET",
|
|
69
|
+
body: str | dict[str, Any] | list[Any] = "",
|
|
70
|
+
query_parameters: dict[str, str] = {},
|
|
71
|
+
request_headers: dict[str, str] = {},
|
|
72
|
+
):
|
|
73
|
+
return self.execute_application(
|
|
74
|
+
Programmatic(
|
|
75
|
+
url=url,
|
|
76
|
+
request_method=request_method,
|
|
77
|
+
body=body,
|
|
78
|
+
query_parameters=query_parameters,
|
|
79
|
+
request_headers=request_headers,
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def build(self, thing: Any, cache: bool = False) -> Any:
|
|
84
|
+
return self.di.build(thing, cache=cache)
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
def __call__(self, env, start_response): # type: ignore
|
|
16
|
+
return self.execute_application(WsgiInputOutput(env, start_response))
|
|
@@ -0,0 +1,49 @@
|
|
|
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 WsgiRef(Context):
|
|
15
|
+
port: int = 8080
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
application: Callable | clearskies.endpoint.Endpoint | clearskies.endpoint_group.EndpointGroup,
|
|
20
|
+
port: int = 8080,
|
|
21
|
+
classes: type | list[type] = [],
|
|
22
|
+
modules: ModuleType | list[ModuleType] = [],
|
|
23
|
+
bindings: dict[str, Any] = {},
|
|
24
|
+
additional_configs: AdditionalConfig | list[AdditionalConfig] = [],
|
|
25
|
+
class_overrides: dict[type, type] = {},
|
|
26
|
+
overrides: dict[str, type] = {},
|
|
27
|
+
now: datetime.datetime | None = None,
|
|
28
|
+
utcnow: datetime.datetime | None = None,
|
|
29
|
+
):
|
|
30
|
+
super().__init__(
|
|
31
|
+
application,
|
|
32
|
+
classes=classes,
|
|
33
|
+
modules=modules,
|
|
34
|
+
bindings=bindings,
|
|
35
|
+
additional_configs=additional_configs,
|
|
36
|
+
class_overrides=class_overrides,
|
|
37
|
+
overrides=overrides,
|
|
38
|
+
now=now,
|
|
39
|
+
utcnow=utcnow,
|
|
40
|
+
)
|
|
41
|
+
self.port = port
|
|
42
|
+
|
|
43
|
+
def __call__(self): # type: ignore
|
|
44
|
+
with make_server("", self.port, self.handler) as httpd:
|
|
45
|
+
print(f"Starting WSGI server on port {self.port}. This is NOT intended for production usage.")
|
|
46
|
+
httpd.serve_forever()
|
|
47
|
+
|
|
48
|
+
def handler(self, environment, start_response):
|
|
49
|
+
return self.execute_application(WsgiInputOutput(environment, start_response))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import clearskies.di.inject as inject
|
|
2
|
+
from clearskies.di.additional_config import AdditionalConfig
|
|
3
|
+
from clearskies.di.additional_config_auto_import import AdditionalConfigAutoImport
|
|
4
|
+
from clearskies.di.di import Di
|
|
5
|
+
from clearskies.di.injectable import Injectable
|
|
6
|
+
from clearskies.di.injectable_properties import InjectableProperties
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AdditionalConfig",
|
|
10
|
+
"AdditionalConfigAutoImport",
|
|
11
|
+
"Di",
|
|
12
|
+
"InjectableProperties",
|
|
13
|
+
"injectInjectable",
|
|
14
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AdditionalConfig:
|
|
5
|
+
"""
|
|
6
|
+
This class allows you to add additional names to the Di container.
|
|
7
|
+
|
|
8
|
+
The idea here is that you extend the AdditonalConfig class and attach as many
|
|
9
|
+
`provide_*` methods to the class as you want. This allows the developer to declare a number of
|
|
10
|
+
dependencies and easily attach them to the Di container in one go - helpful for modules that
|
|
11
|
+
come with a variety of things that they want to make available to developers.
|
|
12
|
+
|
|
13
|
+
When an AdditionalConfig instance is attached to the Di container, the container (in essence) finds all the
|
|
14
|
+
`provide_*` methods in the class and registers the corresponding name in the Di container - e.g. if you have
|
|
15
|
+
a method called `provide_widget` then when a class or function requests an argument named `widget`
|
|
16
|
+
from the Di container, the container will call the `provide_widget` method on your instance and pass along
|
|
17
|
+
the return value to the thing that requested a `widget`. The `provide_*` methods can declare their own
|
|
18
|
+
dependencies, including ones declared in the same `AdditionalConfig` class (they just can't be circular, of course).
|
|
19
|
+
|
|
20
|
+
As always, keep in mind the priority of dependency injection names (see the `clearskies.di.Di` class for full
|
|
21
|
+
details). If two `AdditionalConfig` objects declare a `provide_*` method with the same name, then the Di
|
|
22
|
+
system will call the method for the `AdditionalConfig` object that was added last.
|
|
23
|
+
|
|
24
|
+
By default the Di system caches all values. If you have a dependency that shouldn't be cached, you can
|
|
25
|
+
extend the `can_cache` method and have it return True/False depending on the name and context.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
```python
|
|
29
|
+
from clearskies.di import Di, AdditionalConfig
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MyAdditionalConfig(AdditionalConfig):
|
|
33
|
+
def provide_inner_dependency(self):
|
|
34
|
+
return 5
|
|
35
|
+
|
|
36
|
+
def provide_important_thing(self, inner_dependency):
|
|
37
|
+
return inner_dependency * 10
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AnotherAdditionalConfig(AdditionalConfig):
|
|
41
|
+
def provide_inner_dependency(self):
|
|
42
|
+
return 10
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
di = Di(additional_configs=[MyAdditionalConfig(), AnotherAdditionalConfig()])
|
|
46
|
+
# Equivalent:
|
|
47
|
+
# di = Di()
|
|
48
|
+
# di.add_addtional_configs([MyAdditionalConfig(), AnotherAdditionalConfig()])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def my_function(important_thing):
|
|
52
|
+
print(important_thing) # prints 100
|
|
53
|
+
```
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def can_cache(self, name: str, di, context: str) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Cache control.
|
|
59
|
+
|
|
60
|
+
The Di container caches values by default, but this method allows you to override that.
|
|
61
|
+
After fetching an object from the AdditionalConfig class, the Di container will call this method to
|
|
62
|
+
determine whether or not to cache it. `name` will be the name of the Di value that was built, and
|
|
63
|
+
context will be the name of the class that the value was built for. You then return True/False.
|
|
64
|
+
|
|
65
|
+
Note that this controls whether or not to cache the returned value, not whether or not to check the
|
|
66
|
+
cache for a value. The importance is that, once there is a value in the cache, that will be reused
|
|
67
|
+
for all future requests for that name. Example:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from clearskies.di import Di, AdditionalConfig
|
|
71
|
+
import secrets
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class MyAdditionalConfig(AdditionalConfig):
|
|
75
|
+
def provide_random_number_not_cached(self):
|
|
76
|
+
return secrets.randbelow(100)
|
|
77
|
+
|
|
78
|
+
def provide_random_number_cached(self):
|
|
79
|
+
return secrets.randbelow(100)
|
|
80
|
+
|
|
81
|
+
def can_cache(self, name, context=None):
|
|
82
|
+
return name == "random_number_not_cached"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
di = Di(additional_configs=MyAdditionalConfig())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def my_function(random_number_cached, random_number_not_cached):
|
|
89
|
+
print(random_number_cached)
|
|
90
|
+
print(random_number_not_cached)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
di.call_function(my_function)
|
|
94
|
+
di.call_function(my_function)
|
|
95
|
+
di.call_function(my_function)
|
|
96
|
+
di.call_function(my_function)
|
|
97
|
+
|
|
98
|
+
# This prints something like:
|
|
99
|
+
# 58
|
|
100
|
+
# 12
|
|
101
|
+
# 58
|
|
102
|
+
# 14
|
|
103
|
+
# 58
|
|
104
|
+
# 41
|
|
105
|
+
```
|
|
106
|
+
"""
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
def can_build(self, name):
|
|
110
|
+
return hasattr(self, f"provide_{name}")
|
|
111
|
+
|
|
112
|
+
def build(self, name, di, context=None):
|
|
113
|
+
if not hasattr(self, f"provide_{name}"):
|
|
114
|
+
raise KeyError(
|
|
115
|
+
f"AdditionalConfig class '{self.__class__.__name__}' cannot build requested dependency, '{name}'"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return di.call_function(getattr(self, f"provide_{name}"))
|
|
119
|
+
|
|
120
|
+
def can_build_class(self, class_to_check: type) -> bool:
|
|
121
|
+
"""Return True/False to denote if this AdditionalConfig class can provide a given class."""
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
def build_class(self, class_to_provide: type, argument_name: str, di, context: str = "") -> Any:
|
|
125
|
+
"""Return the desired instance of a given class."""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
def can_cache_class(self, class_to_build: type, di, context: str) -> bool:
|
|
129
|
+
"""Control whether or not the Di container caches the instance after building a class."""
|
|
130
|
+
return False
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .additional_config import AdditionalConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AdditionalConfigAutoImport(AdditionalConfig):
|
|
5
|
+
"""
|
|
6
|
+
AdditionalConfig, but now with auto import.
|
|
7
|
+
|
|
8
|
+
This works exactly like the AdditionalConfig class, but will be automatically imported into the Di
|
|
9
|
+
container if found in a module being imported into the Di container. This just provides a way for
|
|
10
|
+
modules to easily inject names into the Di system. In order to be found, an imported module
|
|
11
|
+
just needs to make sure that it imports the class that extends the AdditionalConfigAutoImport class.
|
|
12
|
+
|
|
13
|
+
Note that automatically-imported AdditionalConfig classes automatically have lower priority than
|
|
14
|
+
any AdditionalConfig classes added explicitly to the Di container.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
pass
|