edri 2025.11.1rc2__tar.gz → 2025.11.1rc3__tar.gz
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.
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/PKG-INFO +1 -1
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/dataclass/api_event.py +10 -1
- edri-2025.11.1rc3/edri/api/handlers/base_handler.py +328 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/injection.py +6 -2
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/validation.py +42 -1
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri.egg-info/PKG-INFO +1 -1
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/setup.py +1 -1
- edri-2025.11.1rc2/edri/api/handlers/base_handler.py +0 -172
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/README.md +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/manager/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/manager/manager_base.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/manager/manager_priority_base.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/manager/worker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/worker/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/worker/worker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/worker/worker_process.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/abstract/worker/worker_thread.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/broker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/dataclass/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/dataclass/client.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/dataclass/file.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/extensions/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/extensions/url_extension.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/extensions/url_prefix.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/handlers/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/handlers/html_handler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/handlers/http_handler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/handlers/rest_handler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/handlers/websocket_handler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/listener.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/middleware.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/static_pages/documentation.j2 +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/static_pages/health_check_status.j2 +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/static_pages/status_300.j2 +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/static_pages/status_400.j2 +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/api/static_pages/status_500.j2 +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/config/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/config/constant.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/config/setting.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/directive/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/directive/base.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/directive/html.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/directive/http.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/event.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/health_checker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/dataclass/response.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/client/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/client/documentation.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/client/register.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/client/unregister.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/group/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/group/client.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/group/manage.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/manage/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/manage/list_registered.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/manage/register.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/manage/unregister.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/api/manage/unregister_all.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/group/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/group/manager.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/group/router.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/group/scheduler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/group/store.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/group/switch.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/group/test.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/manager/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/manager/restart.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/manager/stream_close.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/manager/stream_create.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/manager/stream_message.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/manager/worker_quit.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/demands.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/health_check.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/last_events.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/send_from.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/subscribe.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/subscribe_connector.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/subscribed_external.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/subscribed_new.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/unsubscribe.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/router/unsubscribe_all.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/scheduler/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/scheduler/cancel.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/scheduler/set.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/scheduler/set_or_update.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/scheduler/update.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/store/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/store/delete.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/store/get.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/store/get_callback.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/events/edri/store/set.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/router/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/router/cache.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/router/connector/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/router/connector/connector.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/router/connector/socket.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/router/health_checker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/router/router.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/switch/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/switch/connection.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/switch/forwarder.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/switch/receiver.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/switch/sender.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/switch/switch.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/function.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/json_encoder.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/manager/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/manager/scheduler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/manager/store.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/normalized_default_dict.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/queue.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/shared_memory_pipe.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/storage.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/transformation.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri/utility/watcher.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri.egg-info/SOURCES.txt +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri.egg-info/dependency_links.txt +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri.egg-info/requires.txt +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/edri.egg-info/top_level.txt +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/setup.cfg +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/abstract/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/abstract/manager/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/abstract/manager/test_manager_base.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/abstract/manager/test_manager_base_priority.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/abstract/worker/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/abstract/worker/test_worker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/api/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/api/handlers/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/api/handlers/test_base_handler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/api/handlers/test_html_handler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/api/handlers/test_http_handler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/api/test_broker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/dataclass/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/dataclass/event/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/dataclass/event/test_event.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/dataclass/event/test_event_init.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/dataclass/event/test_response.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/events/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/events/test/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/events/test/event_request.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/events/test/ping.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/events/test/ping2.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/router/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/router/test_cache.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/router/test_health_checker.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/router/test_router.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/test_edri_init.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/manager/__init__.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/manager/test_scheduler.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/manager/test_store.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/test_function.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/test_json_encoder.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/test_normalized_default_dict.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/test_shared_memory_pipe.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/test_storage.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/test_transformation.py +0 -0
- {edri-2025.11.1rc2 → edri-2025.11.1rc3}/tests/utility/test_validation.py +0 -0
|
@@ -3,7 +3,7 @@ from enum import Enum
|
|
|
3
3
|
from http import HTTPMethod
|
|
4
4
|
from inspect import isclass
|
|
5
5
|
from logging import getLogger
|
|
6
|
-
from types import NoneType, UnionType
|
|
6
|
+
from types import NoneType, UnionType, GenericAlias
|
|
7
7
|
from typing import Type, get_origin, get_args
|
|
8
8
|
from uuid import UUID
|
|
9
9
|
|
|
@@ -14,7 +14,9 @@ from edri.api.dataclass.file import File
|
|
|
14
14
|
from edri.api.extensions.url_prefix import PrefixBase
|
|
15
15
|
from edri.config.constant import ApiType
|
|
16
16
|
from edri.dataclass.event import EventHandlingType, _event, Event
|
|
17
|
+
from edri.dataclass.injection import Injection
|
|
17
18
|
from edri.utility.function import camel2snake
|
|
19
|
+
from edri.utility.validation import ListValidation
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
@dataclass
|
|
@@ -109,6 +111,13 @@ def api(cls=None, /, *, init=True, repr=True, eq=True, order=False,
|
|
|
109
111
|
raise TypeError(f"{item_args[0]} cannot be used as a type for API event")
|
|
110
112
|
elif item_type not in allowed_types and not hasattr(field.type, "fromisoformat"):
|
|
111
113
|
raise TypeError(f"{field.type} cannot be used as a type for API event")
|
|
114
|
+
elif isinstance(field.type, Injection):
|
|
115
|
+
for validator in field.type.classes:
|
|
116
|
+
if validator == ListValidation:
|
|
117
|
+
raise TypeError(
|
|
118
|
+
"ListValidation must be used as ListValidation[T], "
|
|
119
|
+
"e.g. ListValidation[Any] or ListValidation[inject(...)]."
|
|
120
|
+
)
|
|
112
121
|
|
|
113
122
|
http_method = method or dataclass.method
|
|
114
123
|
if http_method is None:
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import fields, MISSING
|
|
3
|
+
from inspect import signature
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from types import UnionType, NoneType, GenericAlias
|
|
6
|
+
from typing import Callable, Type, get_origin, Union, get_args, Any, TypedDict, Literal, TypeAliasType, Iterable
|
|
7
|
+
from urllib.parse import parse_qs, unquote
|
|
8
|
+
|
|
9
|
+
from edri.dataclass.directive import ResponseDirective
|
|
10
|
+
from edri.dataclass.event import Event
|
|
11
|
+
from edri.dataclass.injection import Injection
|
|
12
|
+
from edri.utility.function import camel2snake
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseDirectiveHandlerDict[T](TypedDict):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseHandler[T: ResponseDirective](ABC):
|
|
20
|
+
_directive_handlers: dict[Type[T], BaseDirectiveHandlerDict[T]] = {}
|
|
21
|
+
|
|
22
|
+
def __init__(self,
|
|
23
|
+
scope: dict,
|
|
24
|
+
receive: Callable,
|
|
25
|
+
send: Callable):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.send = send
|
|
28
|
+
self.scope = scope
|
|
29
|
+
self.receive = receive
|
|
30
|
+
self.scope = scope
|
|
31
|
+
self.logger = getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
self.parameters: dict[str, Any] = self.parse_url_parameters()
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def directive_handlers(cls) -> dict[Type[ResponseDirective], BaseDirectiveHandlerDict]:
|
|
37
|
+
handlers = {}
|
|
38
|
+
for class_obj in reversed(cls.mro()):
|
|
39
|
+
if hasattr(class_obj, "_directive_handlers"):
|
|
40
|
+
# noinspection PyProtectedMember
|
|
41
|
+
handlers.update(class_obj._directive_handlers)
|
|
42
|
+
return handlers
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def response(self, status: Any, data: Any, *args, **kwargs) -> None:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
async def response_error(self, status: Any, response: Any, *args, **kwargs) -> None:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
def check_parameters(self, event_constructor: Type[Event]) -> None:
|
|
53
|
+
check_parameters = {}
|
|
54
|
+
for name, annotation in ((f.name, f.type) for f in fields(event_constructor)):
|
|
55
|
+
if name.startswith("_") or name == "method" or name == "response":
|
|
56
|
+
continue
|
|
57
|
+
try:
|
|
58
|
+
value = self.parameters.pop(name)
|
|
59
|
+
except KeyError:
|
|
60
|
+
raise ValueError(f"Missing value for parameter {name}")
|
|
61
|
+
try:
|
|
62
|
+
value = self.convert_type(value, annotation)
|
|
63
|
+
except TypeError:
|
|
64
|
+
raise ValueError(f"Wrong type {type(value)} for {name}:{annotation}")
|
|
65
|
+
except Exception:
|
|
66
|
+
raise ValueError("Unknown error during type checking")
|
|
67
|
+
check_parameters[name] = value
|
|
68
|
+
if self.parameters:
|
|
69
|
+
raise ValueError(f"Unknown parameters: {self.parameters}")
|
|
70
|
+
self.parameters = check_parameters
|
|
71
|
+
|
|
72
|
+
def create_event(self, event_constructor: Type[Event]) -> Event:
|
|
73
|
+
self.insert_default_parameters(event_constructor)
|
|
74
|
+
self.check_parameters(event_constructor)
|
|
75
|
+
# noinspection PyArgumentList
|
|
76
|
+
event = event_constructor(**self.parameters)
|
|
77
|
+
event._timing.stamp(self.__class__.__name__, "Created")
|
|
78
|
+
return event
|
|
79
|
+
|
|
80
|
+
def convert_type(self, value: Any, annotation: type) -> Any:
|
|
81
|
+
"""
|
|
82
|
+
Validates and converts input values to the specified annotation type,
|
|
83
|
+
supporting:
|
|
84
|
+
- basic types
|
|
85
|
+
- Optional / Union / |
|
|
86
|
+
- list / tuple / dict with type args
|
|
87
|
+
- Literal
|
|
88
|
+
- list-like subclasses (e.g. ListValidation[int])
|
|
89
|
+
- Injection of validation classes (e.g. Injection((ListValidation[int],), {...}))
|
|
90
|
+
"""
|
|
91
|
+
annotation = self._normalize_annotation(annotation)
|
|
92
|
+
|
|
93
|
+
# Any
|
|
94
|
+
if annotation is Any:
|
|
95
|
+
return value
|
|
96
|
+
|
|
97
|
+
# Injection (chain of validation classes)
|
|
98
|
+
if isinstance(annotation, Injection):
|
|
99
|
+
return self._convert_injection(value, annotation)
|
|
100
|
+
|
|
101
|
+
# Unions / Optional
|
|
102
|
+
if self._is_union(annotation):
|
|
103
|
+
return self._convert_union(value, annotation)
|
|
104
|
+
|
|
105
|
+
origin = get_origin(annotation)
|
|
106
|
+
|
|
107
|
+
# Generics: list, tuple, dict, Literal, ListValidation[int], ...
|
|
108
|
+
if origin is not None:
|
|
109
|
+
return self._convert_generic(value, annotation, origin)
|
|
110
|
+
|
|
111
|
+
# Non-generic simple types (including bare ListValidation, bool, etc.)
|
|
112
|
+
return self._convert_simple(value, annotation)
|
|
113
|
+
|
|
114
|
+
def _normalize_annotation(self, annotation: type) -> type:
|
|
115
|
+
"""Unwrap TypeAliasType and other simple normalizations."""
|
|
116
|
+
if isinstance(annotation, TypeAliasType):
|
|
117
|
+
return annotation.__value__
|
|
118
|
+
return annotation
|
|
119
|
+
|
|
120
|
+
def _is_union(self, annotation: type) -> bool:
|
|
121
|
+
"""Check if annotation is a Union / Optional / | type."""
|
|
122
|
+
return isinstance(annotation, UnionType) or get_origin(annotation) is Union
|
|
123
|
+
|
|
124
|
+
def _convert_injection(self, value: Any, injection: Injection) -> Any:
|
|
125
|
+
"""
|
|
126
|
+
Run all validation classes in an Injection.
|
|
127
|
+
|
|
128
|
+
Each `cls` in `injection.classes` is:
|
|
129
|
+
- either a plain validation class (e.g. ListValidation),
|
|
130
|
+
- or a GenericAlias like ListValidation[int].
|
|
131
|
+
|
|
132
|
+
For list-like generics (e.g. ListValidation[int]) we:
|
|
133
|
+
- convert each element of `value` using the inner type (int here),
|
|
134
|
+
- then instantiate the validation class with filtered params.
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
for cls in injection.classes:
|
|
138
|
+
# Determine underlying class and optional inner type
|
|
139
|
+
item_type = None
|
|
140
|
+
|
|
141
|
+
if isinstance(cls, GenericAlias):
|
|
142
|
+
origin = get_origin(cls) # e.g. ListValidation
|
|
143
|
+
args = get_args(cls) # e.g. (int,)
|
|
144
|
+
target_cls = origin
|
|
145
|
+
|
|
146
|
+
# list-like validation class: ListValidation[int], MyListValidator[str], ...
|
|
147
|
+
if issubclass(origin, list) and args:
|
|
148
|
+
item_type = args[0]
|
|
149
|
+
else:
|
|
150
|
+
target_cls = cls
|
|
151
|
+
|
|
152
|
+
# Filter parameters to those that target_cls.__init__ actually accepts
|
|
153
|
+
sig = signature(target_cls)
|
|
154
|
+
param_names = [
|
|
155
|
+
p.name for p in sig.parameters.values()
|
|
156
|
+
if p.name != "self"
|
|
157
|
+
]
|
|
158
|
+
filtered_params = {
|
|
159
|
+
k: v for k, v in injection.parameters.items()
|
|
160
|
+
if k in param_names
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# If this is a list-like validator with an inner type -> convert elements first
|
|
164
|
+
if item_type is not None:
|
|
165
|
+
if (not isinstance(value, Iterable)) or isinstance(value, (str, bytes)):
|
|
166
|
+
raise TypeError(
|
|
167
|
+
f"Value '{value}' is not a valid iterable for validator {cls}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
converted_items = [
|
|
171
|
+
self.convert_type(item, item_type) for item in value
|
|
172
|
+
]
|
|
173
|
+
value = target_cls(converted_items, **filtered_params)
|
|
174
|
+
else:
|
|
175
|
+
# Any other validation class: just feed the (possibly already converted) value
|
|
176
|
+
value = target_cls(value, **filtered_params)
|
|
177
|
+
|
|
178
|
+
return value
|
|
179
|
+
|
|
180
|
+
except ValueError:
|
|
181
|
+
# Your original contract: map validator ValueError -> TypeError
|
|
182
|
+
raise TypeError(f"Value '{value}' cannot be converted to type {injection}")
|
|
183
|
+
|
|
184
|
+
def _convert_union(self, value: Any, annotation: type) -> Any:
|
|
185
|
+
"""Handle Union/Optional annotations."""
|
|
186
|
+
annotations = get_args(annotation)
|
|
187
|
+
|
|
188
|
+
# Handle Optional[...] where None is allowed
|
|
189
|
+
if value is None:
|
|
190
|
+
if NoneType in annotations:
|
|
191
|
+
return None
|
|
192
|
+
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
193
|
+
|
|
194
|
+
# Try each type in the Union
|
|
195
|
+
last_error: Exception | None = None
|
|
196
|
+
for ann in annotations:
|
|
197
|
+
try:
|
|
198
|
+
return self.convert_type(value, ann)
|
|
199
|
+
except TypeError as e:
|
|
200
|
+
last_error = e
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}") from last_error
|
|
204
|
+
|
|
205
|
+
def _convert_generic(self, value: Any, annotation: type, origin: type) -> Any:
|
|
206
|
+
"""
|
|
207
|
+
Handle generics like:
|
|
208
|
+
- list[T] and list-like subclasses (ListValidation[T])
|
|
209
|
+
- tuple[X, Y, ...]
|
|
210
|
+
- dict[K, V]
|
|
211
|
+
- Literal[...]
|
|
212
|
+
"""
|
|
213
|
+
args = get_args(annotation)
|
|
214
|
+
|
|
215
|
+
# list[T] and list-like subclasses (e.g. ListValidation[int])
|
|
216
|
+
if isinstance(origin, type) and issubclass(origin, list):
|
|
217
|
+
if not isinstance(value, Iterable) or isinstance(value, (str, bytes)):
|
|
218
|
+
raise TypeError(f"Value '{value}' is not a valid iterable for type {annotation}")
|
|
219
|
+
|
|
220
|
+
item_type = args[0] if args else Any
|
|
221
|
+
converted_items = [self.convert_type(item, item_type) for item in value]
|
|
222
|
+
|
|
223
|
+
if origin is list:
|
|
224
|
+
return converted_items
|
|
225
|
+
|
|
226
|
+
# Subclass of list, e.g. ListValidation[int]
|
|
227
|
+
try:
|
|
228
|
+
return origin(converted_items)
|
|
229
|
+
except Exception as e:
|
|
230
|
+
raise TypeError(
|
|
231
|
+
f"Value '{value}' cannot be converted to list-like type {annotation}"
|
|
232
|
+
) from e
|
|
233
|
+
|
|
234
|
+
# ---- tuple[X, Y, ...] ----
|
|
235
|
+
if origin is tuple:
|
|
236
|
+
if not isinstance(value, tuple):
|
|
237
|
+
raise TypeError(f"Value '{value}' is not a tuple for type {annotation}")
|
|
238
|
+
return tuple(self.convert_type(v, a) for v, a in zip(value, args))
|
|
239
|
+
|
|
240
|
+
# ---- dict[K, V] ----
|
|
241
|
+
if origin is dict:
|
|
242
|
+
if not isinstance(value, dict):
|
|
243
|
+
raise TypeError(f"Value '{value}' is not a dict for type {annotation}")
|
|
244
|
+
if len(args) != 2:
|
|
245
|
+
raise TypeError("Key and value types for dict must be specified")
|
|
246
|
+
key_type, value_type = args
|
|
247
|
+
return {
|
|
248
|
+
self.convert_type(k, key_type): self.convert_type(v, value_type)
|
|
249
|
+
for k, v in value.items()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# ---- Literal["a", "b", ...] ----
|
|
253
|
+
if origin is Literal:
|
|
254
|
+
literal_values = args
|
|
255
|
+
if value in literal_values:
|
|
256
|
+
return value
|
|
257
|
+
raise TypeError(
|
|
258
|
+
f"Value '{value}' is not one of the allowed Literal values {literal_values}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Unknown generic
|
|
262
|
+
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
263
|
+
|
|
264
|
+
def _convert_simple(self, value: Any, annotation: type) -> Any:
|
|
265
|
+
"""
|
|
266
|
+
Handle non-generic types: bool, dataclasses, custom classes,
|
|
267
|
+
and bare list subclasses like ListValidation.
|
|
268
|
+
"""
|
|
269
|
+
# Already correct type
|
|
270
|
+
if isinstance(value, annotation):
|
|
271
|
+
return value
|
|
272
|
+
|
|
273
|
+
# Support bare list-like subclasses (e.g. annotation is ListValidation without [T])
|
|
274
|
+
try:
|
|
275
|
+
is_list_subclass = isinstance(annotation, type) and issubclass(annotation, list)
|
|
276
|
+
except TypeError:
|
|
277
|
+
is_list_subclass = False
|
|
278
|
+
|
|
279
|
+
if is_list_subclass and not isinstance(value, (str, bytes)) and isinstance(value, Iterable):
|
|
280
|
+
try:
|
|
281
|
+
return annotation(value)
|
|
282
|
+
except Exception as e:
|
|
283
|
+
raise TypeError(
|
|
284
|
+
f"Value '{value}' cannot be converted to list-like type {annotation}"
|
|
285
|
+
) from e
|
|
286
|
+
|
|
287
|
+
# Special case: string "false" -> False for bool
|
|
288
|
+
if isinstance(value, str) and value.lower() == "false" and annotation is bool:
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
# Normal constructor-based conversion
|
|
292
|
+
try:
|
|
293
|
+
return annotation(value)
|
|
294
|
+
except Exception:
|
|
295
|
+
# Try fromisoformat if available (e.g., datetime, date)
|
|
296
|
+
if hasattr(annotation, "fromisoformat"):
|
|
297
|
+
try:
|
|
298
|
+
return annotation.fromisoformat(value)
|
|
299
|
+
except Exception:
|
|
300
|
+
raise TypeError(
|
|
301
|
+
"Value '%s' cannot be converted from isoformat to type %s"
|
|
302
|
+
% (value, annotation)
|
|
303
|
+
)
|
|
304
|
+
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
305
|
+
|
|
306
|
+
@abstractmethod
|
|
307
|
+
def handle_directives(self, directives: list[ResponseDirective]) -> ...:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
def insert_default_parameters(self, event_constructor: Type[Event]) -> None:
|
|
311
|
+
for field in fields(event_constructor):
|
|
312
|
+
if field.name.startswith("_") or field.name in ("response", "method"):
|
|
313
|
+
continue
|
|
314
|
+
if field.name in self.parameters:
|
|
315
|
+
continue
|
|
316
|
+
if field.default is not MISSING:
|
|
317
|
+
self.parameters[field.name] = field.default
|
|
318
|
+
continue
|
|
319
|
+
if field.default_factory is not MISSING:
|
|
320
|
+
self.parameters[field.name] = field.default_factory()
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
def parse_url_parameters(self) -> dict[str, Any]:
|
|
324
|
+
url_parameters = parse_qs(unquote(self.scope["query_string"].decode()), keep_blank_values=True)
|
|
325
|
+
return {
|
|
326
|
+
camel2snake(key.strip("[]")): value if key.endswith("[]") else value[-1] for key, value in
|
|
327
|
+
url_parameters.items()
|
|
328
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from inspect import signature
|
|
2
|
-
from
|
|
2
|
+
from types import GenericAlias
|
|
3
|
+
from typing import Any, Type, get_origin
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class Injection:
|
|
@@ -32,7 +33,10 @@ class Injection:
|
|
|
32
33
|
# Create the callable on demand when iterating
|
|
33
34
|
for cls in self.classes:
|
|
34
35
|
# Get the signature of the __init__ method of the class
|
|
35
|
-
|
|
36
|
+
if isinstance(cls, GenericAlias):
|
|
37
|
+
sig = signature(get_origin(cls))
|
|
38
|
+
else:
|
|
39
|
+
sig = signature(cls)
|
|
36
40
|
# Extract parameter names from the signature
|
|
37
41
|
param_names: list[str] = [param.name for param in sig.parameters.values() if param.name != 'self']
|
|
38
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from datetime import date, datetime, time
|
|
2
2
|
from re import Pattern
|
|
3
|
-
from typing import Self
|
|
3
|
+
from typing import Self, Iterable, Any
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class StringValidation(str):
|
|
@@ -222,3 +222,44 @@ class DateTimeValidation(datetime):
|
|
|
222
222
|
raise ValueError(f"Datetime '{instance}' is later than maximum allowed '{maximum_datetime}'")
|
|
223
223
|
|
|
224
224
|
return instance
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ListValidation(list):
|
|
228
|
+
"""
|
|
229
|
+
A list type that performs validation on initialization.
|
|
230
|
+
|
|
231
|
+
This class validates the list against optional constraints:
|
|
232
|
+
- Minimum allowed length.
|
|
233
|
+
- Maximum allowed length.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
iterable (Iterable, optional): Values to initialize the list with.
|
|
237
|
+
minimum_length (int, optional): The smallest allowed list length.
|
|
238
|
+
maximum_length (int, optional): The largest allowed list length.
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
ValueError: If the list length is outside the allowed bounds.
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
>>> ListValidation([1, 2, 3], minimum_length=2)
|
|
245
|
+
[1, 2, 3]
|
|
246
|
+
>>> ListValidation([1, 2, 3], maximum_length=2)
|
|
247
|
+
ValueError: List length '3' is greater than maximum allowed '2'
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def __init__(self, iterable: Iterable[Any] = (), /, *, minimum_length: int | None = None,
|
|
251
|
+
maximum_length: int | None = None):
|
|
252
|
+
|
|
253
|
+
super().__init__(iterable)
|
|
254
|
+
|
|
255
|
+
length = len(self)
|
|
256
|
+
|
|
257
|
+
if minimum_length is not None and length < minimum_length:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
f"List length '{length}' is smaller than minimum allowed '{minimum_length}'"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if maximum_length is not None and length > maximum_length:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"List length '{length}' is greater than maximum allowed '{maximum_length}'"
|
|
265
|
+
)
|
|
@@ -122,7 +122,7 @@ Whether you're building a real-time data processing system, a distributed servic
|
|
|
122
122
|
|
|
123
123
|
setup(
|
|
124
124
|
name='edri',
|
|
125
|
-
version='2025.11.
|
|
125
|
+
version='2025.11.01rc3',
|
|
126
126
|
packages=find_packages(),
|
|
127
127
|
description='Event Driven Routing Infrastructure',
|
|
128
128
|
long_description=long_description,
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from dataclasses import fields, MISSING
|
|
3
|
-
from logging import getLogger
|
|
4
|
-
from types import UnionType, NoneType
|
|
5
|
-
from typing import Callable, Type, get_origin, Union, get_args, Any, TypedDict, Literal, TypeAliasType
|
|
6
|
-
from urllib.parse import parse_qs, unquote
|
|
7
|
-
|
|
8
|
-
from edri.dataclass.directive import ResponseDirective
|
|
9
|
-
from edri.dataclass.event import Event
|
|
10
|
-
from edri.dataclass.injection import Injection
|
|
11
|
-
from edri.utility.function import camel2snake
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class BaseDirectiveHandlerDict[T](TypedDict):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class BaseHandler[T: ResponseDirective](ABC):
|
|
19
|
-
_directive_handlers: dict[Type[T], BaseDirectiveHandlerDict[T]] = {}
|
|
20
|
-
|
|
21
|
-
def __init__(self,
|
|
22
|
-
scope: dict,
|
|
23
|
-
receive: Callable,
|
|
24
|
-
send: Callable):
|
|
25
|
-
super().__init__()
|
|
26
|
-
self.send = send
|
|
27
|
-
self.scope = scope
|
|
28
|
-
self.receive = receive
|
|
29
|
-
self.scope = scope
|
|
30
|
-
self.logger = getLogger(__name__)
|
|
31
|
-
|
|
32
|
-
self.parameters: dict[str, Any] = self.parse_url_parameters()
|
|
33
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def directive_handlers(cls) -> dict[Type[ResponseDirective], BaseDirectiveHandlerDict]:
|
|
36
|
-
handlers = {}
|
|
37
|
-
for class_obj in reversed(cls.mro()):
|
|
38
|
-
if hasattr(class_obj, "_directive_handlers"):
|
|
39
|
-
# noinspection PyProtectedMember
|
|
40
|
-
handlers.update(class_obj._directive_handlers)
|
|
41
|
-
return handlers
|
|
42
|
-
|
|
43
|
-
@abstractmethod
|
|
44
|
-
async def response(self, status: Any, data: Any, *args, **kwargs) -> None:
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
|
-
@abstractmethod
|
|
48
|
-
async def response_error(self, status: Any, response: Any, *args, **kwargs) -> None:
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
|
-
def check_parameters(self, event_constructor: Type[Event]) -> None:
|
|
52
|
-
check_parameters = {}
|
|
53
|
-
for name, annotation in ((f.name, f.type) for f in fields(event_constructor)):
|
|
54
|
-
if name.startswith("_") or name == "method" or name == "response":
|
|
55
|
-
continue
|
|
56
|
-
try:
|
|
57
|
-
value = self.parameters.pop(name)
|
|
58
|
-
except KeyError:
|
|
59
|
-
raise ValueError(f"Missing value for parameter {name}")
|
|
60
|
-
try:
|
|
61
|
-
value = self.convert_type(value, annotation)
|
|
62
|
-
except TypeError:
|
|
63
|
-
raise ValueError(f"Wrong type {type(value)} for {name}:{annotation}")
|
|
64
|
-
except Exception:
|
|
65
|
-
raise ValueError("Unknown error during type checking")
|
|
66
|
-
check_parameters[name] = value
|
|
67
|
-
if self.parameters:
|
|
68
|
-
raise ValueError(f"Unknown parameters: {self.parameters}")
|
|
69
|
-
self.parameters = check_parameters
|
|
70
|
-
|
|
71
|
-
def create_event(self, event_constructor: Type[Event]) -> Event:
|
|
72
|
-
self.insert_default_parameters(event_constructor)
|
|
73
|
-
self.check_parameters(event_constructor)
|
|
74
|
-
# noinspection PyArgumentList
|
|
75
|
-
event = event_constructor(**self.parameters)
|
|
76
|
-
event._timing.stamp(self.__class__.__name__, "Created")
|
|
77
|
-
return event
|
|
78
|
-
|
|
79
|
-
def convert_type(self, value: Any, annotation: type) -> Any:
|
|
80
|
-
"""
|
|
81
|
-
Validates and converts input values to the specified annotation type,
|
|
82
|
-
supporting basic types, Optional, and lists with type annotations.
|
|
83
|
-
|
|
84
|
-
Parameters:
|
|
85
|
-
value: The input value to be validated and converted.
|
|
86
|
-
annotation: The target type annotation for the conversion.
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
The converted value if conversion is successful.
|
|
90
|
-
|
|
91
|
-
Raises:
|
|
92
|
-
TypeError: If the value cannot be converted to the specified type.
|
|
93
|
-
"""
|
|
94
|
-
if isinstance(annotation, TypeAliasType):
|
|
95
|
-
annotation = annotation.__value__
|
|
96
|
-
if get_origin(annotation) or isinstance(annotation, UnionType):
|
|
97
|
-
if isinstance(annotation, UnionType) or get_origin(annotation) == Union:
|
|
98
|
-
annotations = get_args(annotation)
|
|
99
|
-
if value is None:
|
|
100
|
-
if NoneType in annotations:
|
|
101
|
-
return None
|
|
102
|
-
else:
|
|
103
|
-
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
104
|
-
for annotation in annotations:
|
|
105
|
-
try:
|
|
106
|
-
return self.convert_type(value, annotation)
|
|
107
|
-
except TypeError:
|
|
108
|
-
continue
|
|
109
|
-
else:
|
|
110
|
-
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
111
|
-
elif get_origin(annotation) == list and isinstance(value, list):
|
|
112
|
-
if len(get_args(annotation)) != 1:
|
|
113
|
-
raise TypeError("Type of list item must be specified")
|
|
114
|
-
return [self.convert_type(value, get_args(annotation)[0]) for value in value]
|
|
115
|
-
elif get_origin(annotation) == tuple and isinstance(value, tuple):
|
|
116
|
-
return tuple(self.convert_type(v, a) for v, a in zip(value, get_args(annotation)))
|
|
117
|
-
elif get_origin(annotation) == Literal and isinstance(value, str) and value in get_args(annotation):
|
|
118
|
-
return value
|
|
119
|
-
elif get_origin(annotation) == dict and isinstance(value, dict):
|
|
120
|
-
a_args = get_args(annotation)
|
|
121
|
-
return {self.convert_type(k, a_args[0]): self.convert_type(v, a_args[1]) for k, v in value.items()}
|
|
122
|
-
else:
|
|
123
|
-
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
124
|
-
else:
|
|
125
|
-
if annotation is Any:
|
|
126
|
-
return value
|
|
127
|
-
elif isinstance(annotation, Injection):
|
|
128
|
-
try:
|
|
129
|
-
for validator in annotation:
|
|
130
|
-
value = validator(value)
|
|
131
|
-
return value
|
|
132
|
-
except ValueError:
|
|
133
|
-
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
134
|
-
elif isinstance(value, annotation):
|
|
135
|
-
return value
|
|
136
|
-
elif isinstance(value, str) and value.lower() == "false" and annotation == bool:
|
|
137
|
-
return False
|
|
138
|
-
try:
|
|
139
|
-
return annotation(value)
|
|
140
|
-
except Exception:
|
|
141
|
-
if hasattr(annotation, "fromisoformat"):
|
|
142
|
-
try:
|
|
143
|
-
return annotation.fromisoformat(value)
|
|
144
|
-
except Exception:
|
|
145
|
-
raise TypeError(
|
|
146
|
-
"Value '%s' cannot be converted from isoformat to type %s" % (value, annotation))
|
|
147
|
-
else:
|
|
148
|
-
raise TypeError(f"Value '{value}' cannot be converted to type {annotation}")
|
|
149
|
-
|
|
150
|
-
@abstractmethod
|
|
151
|
-
def handle_directives(self, directives: list[ResponseDirective]) -> ...:
|
|
152
|
-
pass
|
|
153
|
-
|
|
154
|
-
def insert_default_parameters(self, event_constructor: Type[Event]) -> None:
|
|
155
|
-
for field in fields(event_constructor):
|
|
156
|
-
if field.name.startswith("_") or field.name in ("response", "method"):
|
|
157
|
-
continue
|
|
158
|
-
if field.name in self.parameters:
|
|
159
|
-
continue
|
|
160
|
-
if field.default is not MISSING:
|
|
161
|
-
self.parameters[field.name] = field.default
|
|
162
|
-
continue
|
|
163
|
-
if field.default_factory is not MISSING:
|
|
164
|
-
self.parameters[field.name] = field.default_factory()
|
|
165
|
-
continue
|
|
166
|
-
|
|
167
|
-
def parse_url_parameters(self) -> dict[str, Any]:
|
|
168
|
-
url_parameters = parse_qs(unquote(self.scope["query_string"].decode()), keep_blank_values=True)
|
|
169
|
-
return {
|
|
170
|
-
camel2snake(key.strip("[]")): value if key.endswith("[]") else value[-1] for key, value in
|
|
171
|
-
url_parameters.items()
|
|
172
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|