edri 2025.11.1rc4__tar.gz → 2025.12.1__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.1rc4 → edri-2025.12.1}/PKG-INFO +2 -1
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/__init__.py +10 -3
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/manager/manager_base.py +42 -6
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/handlers/http_handler.py +4 -1
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/listener.py +1 -1
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/config/setting.py +12 -4
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/directive/http.py +6 -0
- edri-2025.12.1/edri/utility/cache.py +19 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri.egg-info/PKG-INFO +2 -1
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri.egg-info/SOURCES.txt +1 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri.egg-info/requires.txt +1 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/setup.py +3 -2
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/abstract/manager/test_manager_base.py +1 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/README.md +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/manager/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/manager/manager_priority_base.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/manager/worker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/worker/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/worker/worker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/worker/worker_process.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/abstract/worker/worker_thread.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/broker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/dataclass/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/dataclass/api_event.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/dataclass/client.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/dataclass/file.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/extensions/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/extensions/url_extension.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/extensions/url_prefix.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/handlers/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/handlers/base_handler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/handlers/html_handler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/handlers/rest_handler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/handlers/websocket_handler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/middleware.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/static_pages/documentation.j2 +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/static_pages/health_check_status.j2 +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/static_pages/status_300.j2 +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/static_pages/status_400.j2 +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/api/static_pages/status_500.j2 +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/config/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/config/constant.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/directive/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/directive/base.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/directive/html.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/event.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/health_checker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/injection.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/dataclass/response.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/client/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/client/documentation.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/client/register.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/client/unregister.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/group/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/group/client.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/group/manage.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/manage/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/manage/list_registered.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/manage/register.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/manage/unregister.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/api/manage/unregister_all.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/group/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/group/manager.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/group/router.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/group/scheduler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/group/store.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/group/switch.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/group/test.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/manager/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/manager/restart.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/manager/stream_close.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/manager/stream_create.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/manager/stream_message.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/manager/worker_quit.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/demands.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/health_check.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/last_events.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/send_from.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/subscribe.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/subscribe_connector.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/subscribed_external.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/subscribed_new.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/unsubscribe.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/router/unsubscribe_all.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/scheduler/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/scheduler/cancel.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/scheduler/set.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/scheduler/set_or_update.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/scheduler/update.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/store/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/store/delete.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/store/get.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/store/get_callback.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/events/edri/store/set.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/router/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/router/cache.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/router/connector/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/router/connector/connector.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/router/connector/socket.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/router/health_checker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/router/router.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/switch/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/switch/connection.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/switch/forwarder.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/switch/receiver.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/switch/sender.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/switch/switch.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/function.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/json_encoder.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/manager/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/manager/scheduler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/manager/store.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/normalized_default_dict.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/queue.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/shared_memory_pipe.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/storage.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/transformation.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/validation.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri/utility/watcher.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri.egg-info/dependency_links.txt +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/edri.egg-info/top_level.txt +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/setup.cfg +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/abstract/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/abstract/manager/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/abstract/manager/test_manager_base_priority.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/abstract/worker/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/abstract/worker/test_worker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/api/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/api/handlers/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/api/handlers/test_base_handler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/api/handlers/test_html_handler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/api/handlers/test_http_handler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/api/test_broker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/dataclass/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/dataclass/event/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/dataclass/event/test_event.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/dataclass/event/test_event_init.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/dataclass/event/test_response.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/events/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/events/test/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/events/test/event_request.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/events/test/ping.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/events/test/ping2.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/router/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/router/test_cache.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/router/test_health_checker.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/router/test_router.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/test_edri_init.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/manager/__init__.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/manager/test_scheduler.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/manager/test_store.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/test_function.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/test_json_encoder.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/test_normalized_default_dict.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/test_shared_memory_pipe.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/test_storage.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/test_transformation.py +0 -0
- {edri-2025.11.1rc4 → edri-2025.12.1}/tests/utility/test_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: edri
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.12.1
|
|
4
4
|
Summary: Event Driven Routing Infrastructure
|
|
5
5
|
Author: Marek Olšan
|
|
6
6
|
Author-email: marek.olsan@gmail.com
|
|
@@ -26,6 +26,7 @@ Requires-Dist: watchdog>=6
|
|
|
26
26
|
Requires-Dist: websockets>=14
|
|
27
27
|
Requires-Dist: posix-ipc>=1.2.0
|
|
28
28
|
Requires-Dist: markdown>=3.0
|
|
29
|
+
Requires-Dist: pytz>=2024.1
|
|
29
30
|
Provides-Extra: uvicorn
|
|
30
31
|
Requires-Dist: uvicorn[standard]>=0.32.0; extra == "uvicorn"
|
|
31
32
|
Provides-Extra: hypercorn
|
|
@@ -2,9 +2,16 @@ from .manager.manager_base import ManagerBase
|
|
|
2
2
|
from .manager.manager_priority_base import ManagerPriorityBase
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def request(func):
|
|
6
|
-
func
|
|
7
|
-
|
|
5
|
+
def request(func=None, /, *, cache: str = None):
|
|
6
|
+
def wrapper(func):
|
|
7
|
+
func.__purpose__ = "request"
|
|
8
|
+
func.__cache__ = cache
|
|
9
|
+
return func
|
|
10
|
+
|
|
11
|
+
if func is None:
|
|
12
|
+
return wrapper
|
|
13
|
+
|
|
14
|
+
return wrapper(func)
|
|
8
15
|
|
|
9
16
|
|
|
10
17
|
def response(func):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from abc import ABC, ABCMeta
|
|
2
2
|
from copy import deepcopy
|
|
3
|
-
from dataclasses import dataclass
|
|
4
3
|
from datetime import datetime
|
|
4
|
+
from http import HTTPMethod
|
|
5
5
|
from importlib import invalidate_caches, import_module, reload
|
|
6
6
|
from inspect import signature, Signature, ismethod, isfunction
|
|
7
7
|
from logging import getLogger, Logger
|
|
@@ -12,16 +12,21 @@ from random import randint
|
|
|
12
12
|
from time import sleep
|
|
13
13
|
from traceback import format_exc
|
|
14
14
|
from types import UnionType
|
|
15
|
-
from typing import Optional, Type, Tuple, Callable, Union,
|
|
15
|
+
from typing import Optional, Type, Tuple, Callable, Union, TypeVar, Never, get_args, Iterable, get_origin
|
|
16
16
|
|
|
17
17
|
from edri.abstract.manager.worker import Worker
|
|
18
|
-
from edri.abstract.worker import
|
|
18
|
+
from edri.abstract.worker import WorkerProcess
|
|
19
|
+
from edri.api.dataclass.api_event import api_events
|
|
20
|
+
from edri.config.setting import API_CACHE_CONTROL, API_CACHE_HEADERS
|
|
21
|
+
from edri.dataclass.directive.http import NotModifiedResponseDirective, HeaderResponseDirective
|
|
19
22
|
from edri.dataclass.event import Event
|
|
20
23
|
from edri.dataclass.health_checker import Status
|
|
24
|
+
from edri.dataclass.response import ResponseStatus
|
|
21
25
|
from edri.events.edri.group import Manager
|
|
22
26
|
from edri.events.edri.manager import StreamCreate, StreamMessage, StreamClose, WorkerQuit, Restart
|
|
23
27
|
from edri.events.edri.router import Subscribe, HealthCheck, UnsubscribeAll
|
|
24
28
|
from edri.events.edri.store import Get
|
|
29
|
+
from edri.utility.cache import Cache
|
|
25
30
|
from edri.utility.storage import Storage
|
|
26
31
|
|
|
27
32
|
T = TypeVar("T", bound=Event)
|
|
@@ -30,11 +35,23 @@ T = TypeVar("T", bound=Event)
|
|
|
30
35
|
class ManagerBaseMeta(ABCMeta):
|
|
31
36
|
|
|
32
37
|
def __new__(mcls, name, bases, namespace, /, **kwargs):
|
|
38
|
+
namespace["_cache_keys"] = dict()
|
|
39
|
+
namespace["_cache_methods"] = dict()
|
|
33
40
|
for attr_name, attr_value in list(namespace.items()):
|
|
34
|
-
if callable(attr_value)
|
|
35
|
-
|
|
41
|
+
if not callable(attr_value):
|
|
42
|
+
continue
|
|
43
|
+
if purpose := getattr(attr_value, "__purpose__", None): # Check for the decorator's marker
|
|
36
44
|
if purpose == "request":
|
|
37
45
|
new_name = f"solve_req_{attr_name}"
|
|
46
|
+
|
|
47
|
+
if cache := getattr(attr_value, "__cache__", None):
|
|
48
|
+
namespace["_cache_keys"][attr_name] = cache
|
|
49
|
+
event = signature(attr_value).parameters["event"].annotation
|
|
50
|
+
for api_event in api_events:
|
|
51
|
+
if api_event.event == event:
|
|
52
|
+
break
|
|
53
|
+
namespace["_cache_methods"][event] = api_event.method
|
|
54
|
+
|
|
38
55
|
elif purpose == "response":
|
|
39
56
|
new_name = f"solve_res_{attr_name}"
|
|
40
57
|
else:
|
|
@@ -109,6 +126,10 @@ class ManagerBase(ABC, Process, metaclass=ManagerBaseMeta):
|
|
|
109
126
|
self._from_time = from_time
|
|
110
127
|
self._store_get: Optional[Callable] = None
|
|
111
128
|
self._exceptions: list[tuple[str, dict, Exception, str]] = []
|
|
129
|
+
self._cache: Cache
|
|
130
|
+
self._cache_vary = "Accept,Accept-Encoding"
|
|
131
|
+
if API_CACHE_HEADERS:
|
|
132
|
+
self._cache_vary += f",{API_CACHE_HEADERS}"
|
|
112
133
|
|
|
113
134
|
def _subscribe(self) -> None:
|
|
114
135
|
"""
|
|
@@ -609,10 +630,25 @@ class ManagerBase(ABC, Process, metaclass=ManagerBaseMeta):
|
|
|
609
630
|
self.resolve_unknown(event)
|
|
610
631
|
else:
|
|
611
632
|
had_response = event.has_response()
|
|
633
|
+
cache_key = self._cache_keys.get(resolver.__name__, None)
|
|
634
|
+
if cache_key:
|
|
635
|
+
etag = self._cache.tag(cache_key)
|
|
636
|
+
method = self._cache_methods[event.__class__]
|
|
637
|
+
if etag and hasattr(event, "etag") and event.etag and etag in event.etag and method == HTTPMethod.GET:
|
|
638
|
+
event.response.add_directive(NotModifiedResponseDirective())
|
|
639
|
+
self.router_queue.put(event)
|
|
640
|
+
return
|
|
612
641
|
resolver(event)
|
|
613
642
|
if not had_response and event.has_response() and event.response._changed:
|
|
614
643
|
if event._switch:
|
|
615
644
|
event._switch.received = False
|
|
645
|
+
if cache_key and etag:
|
|
646
|
+
if event.response.get_status() == ResponseStatus.OK and method in (HTTPMethod.POST, HTTPMethod.PUT, HTTPMethod.PATCH, HTTPMethod.DELETE):
|
|
647
|
+
self._cache.renew(cache_key)
|
|
648
|
+
else:
|
|
649
|
+
event.response.add_directive(HeaderResponseDirective(name="ETag", value=etag))
|
|
650
|
+
event.response.add_directive(HeaderResponseDirective(name="Cache-Control", value=API_CACHE_CONTROL))
|
|
651
|
+
event.response.add_directive(HeaderResponseDirective(name="Vary", value=self._cache_vary))
|
|
616
652
|
self.router_queue.put(event)
|
|
617
653
|
|
|
618
654
|
def get_pipes(self) -> set[Connection]:
|
|
@@ -828,7 +864,7 @@ class ManagerBase(ABC, Process, metaclass=ManagerBaseMeta):
|
|
|
828
864
|
"""
|
|
829
865
|
Hook method called after the manager process starts. Can be overridden to perform initialization tasks.
|
|
830
866
|
"""
|
|
831
|
-
|
|
867
|
+
self._cache = Cache()
|
|
832
868
|
|
|
833
869
|
def quit(self) -> None:
|
|
834
870
|
self.router_queue.close()
|
|
@@ -32,7 +32,7 @@ from edri.dataclass.directive.base import InternalServerErrorResponseDirective,
|
|
|
32
32
|
from edri.dataclass.directive.http import CookieResponseDirective, AccessDeniedResponseDirective, \
|
|
33
33
|
NotFoundResponseDirective, \
|
|
34
34
|
ConflictResponseDirective, HeaderResponseDirective, UnprocessableContentResponseDirective, \
|
|
35
|
-
BadRequestResponseDirective, NotModifiedResponseDirective
|
|
35
|
+
BadRequestResponseDirective, NotModifiedResponseDirective, ServiceUnavailableResponseDirective
|
|
36
36
|
from edri.dataclass.event import Event
|
|
37
37
|
from edri.dataclass.injection import Injection
|
|
38
38
|
from edri.utility import NormalizedDefaultDict
|
|
@@ -276,6 +276,9 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
|
|
|
276
276
|
},
|
|
277
277
|
NotModifiedResponseDirective: {
|
|
278
278
|
"status": HTTPStatus.NOT_MODIFIED,
|
|
279
|
+
},
|
|
280
|
+
ServiceUnavailableResponseDirective: {
|
|
281
|
+
"status": HTTPStatus.SERVICE_UNAVAILABLE,
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
|
|
@@ -147,7 +147,7 @@ class Listener(Process):
|
|
|
147
147
|
if isinstance(handler, HTMLHandler):
|
|
148
148
|
if await handler.response_assets(scope["path"]):
|
|
149
149
|
return
|
|
150
|
-
self.logger.
|
|
150
|
+
self.logger.warning("Unknown url %s", scope["path"])
|
|
151
151
|
await handler.response_error(HTTPStatus.NOT_FOUND, {
|
|
152
152
|
"reasons": [{
|
|
153
153
|
"status_code": HTTPStatus.NOT_FOUND,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from os import getenv
|
|
2
2
|
from typing import Literal
|
|
3
3
|
|
|
4
|
+
from pytz import timezone
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
def getenv_bool(name: str, default: bool) -> bool:
|
|
6
8
|
"""Read an environment variable as a boolean, fallback to default if unset."""
|
|
@@ -13,9 +15,18 @@ ENVIRONMENT: Literal["development", "production"] = "production" if getenv("ENVI
|
|
|
13
15
|
|
|
14
16
|
HEALTH_CHECK_TIMEOUT = int(getenv("EDRI_HEALTH_CHECK_TIMEOUT", 10))
|
|
15
17
|
HEALTH_CHECK_FAILURE_LIMIT = int(getenv("EDRI_HEALTH_CHECK_FAILURE_LIMIT", 3))
|
|
18
|
+
|
|
19
|
+
CORS_ORIGINS = getenv("EDRI_CORS_ORIGINS")
|
|
20
|
+
CORS_HEADERS = getenv("EDRI_CORS_HEADERS")
|
|
21
|
+
CORS_CREDENTIALS = bool(getenv("EDRI_CORS_CREDENTIALS", False))
|
|
22
|
+
CORS_MAX_AGE = getenv("EDRI_CORS_MAX_AGE", None)
|
|
23
|
+
|
|
24
|
+
TIMEZONE = timezone(getenv("EDRI_TIMEZONE", "UTC"))
|
|
16
25
|
API_RESPONSE_TIMEOUT = int(getenv("EDRI_API_RESPONSE_TIMEOUT", 60))
|
|
17
26
|
API_RESPONSE_WRAPPED = getenv_bool("EDRI_API_RESPONSE_WRAPPED", True)
|
|
18
27
|
API_RESPONSE_TIMING = getenv_bool("EDRI_API_RESPONSE_TIMING", ENVIRONMENT == 'development')
|
|
28
|
+
API_CACHE_CONTROL = getenv("EDRI_API_CACHE_CONTROL", "max-age=0, must-revalidate")
|
|
29
|
+
API_CACHE_HEADERS = getenv("EDRI_API_CACHE_HEADERS", CORS_HEADERS)
|
|
19
30
|
|
|
20
31
|
SWITCH_KEY_LENGTH = int(getenv("EDRI_SWITCH_KEY_LENGTH ", 8))
|
|
21
32
|
SWITCH_HOST = getenv("EDRI_SWITCH_HOST", "localhost")
|
|
@@ -37,7 +48,4 @@ ASSETS_PATH = getenv("EDRI_ASSETS_PATH", "assets")
|
|
|
37
48
|
MAX_BODY_SIZE = int(getenv("EDRI_MAX_BODY_SIZE", 4096 * 1024))
|
|
38
49
|
CHUNK_SIZE = int(getenv("EDRI_CHUNK_SIZE", 256 * 1024))
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
CORS_HEADERS = getenv("EDRI_CORS_HEADERS")
|
|
42
|
-
CORS_CREDENTIALS = bool(getenv("EDRI_CORS_CREDENTIALS", False))
|
|
43
|
-
CORS_MAX_AGE = getenv("EDRI_CORS_MAX_AGE", None)
|
|
51
|
+
|
|
@@ -66,6 +66,12 @@ class UnprocessableContentResponseDirective(HTTPResponseDirective):
|
|
|
66
66
|
class BadRequestResponseDirective(HTTPResponseDirective):
|
|
67
67
|
message: str | None = None
|
|
68
68
|
|
|
69
|
+
|
|
69
70
|
@dataclass
|
|
70
71
|
class NotModifiedResponseDirective(HTTPResponseDirective):
|
|
71
72
|
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class ServiceUnavailableResponseDirective(HTTPResponseDirective):
|
|
77
|
+
message: str | None = None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pytz import timezone
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Cache:
|
|
7
|
+
timezone = timezone("Europe/Prague")
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.last_modified = defaultdict(lambda: datetime.now(tz=self.timezone))
|
|
11
|
+
|
|
12
|
+
def last_change(self, key: str) -> datetime:
|
|
13
|
+
return self.last_modified[key]
|
|
14
|
+
|
|
15
|
+
def tag(self, key: str) -> str:
|
|
16
|
+
return f"{key}-{int(self.last_change(key).timestamp())}"
|
|
17
|
+
|
|
18
|
+
def renew(self, key: str) -> None:
|
|
19
|
+
self.last_modified[key] = datetime.now(tz=self.timezone)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: edri
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.12.1
|
|
4
4
|
Summary: Event Driven Routing Infrastructure
|
|
5
5
|
Author: Marek Olšan
|
|
6
6
|
Author-email: marek.olsan@gmail.com
|
|
@@ -26,6 +26,7 @@ Requires-Dist: watchdog>=6
|
|
|
26
26
|
Requires-Dist: websockets>=14
|
|
27
27
|
Requires-Dist: posix-ipc>=1.2.0
|
|
28
28
|
Requires-Dist: markdown>=3.0
|
|
29
|
+
Requires-Dist: pytz>=2024.1
|
|
29
30
|
Provides-Extra: uvicorn
|
|
30
31
|
Requires-Dist: uvicorn[standard]>=0.32.0; extra == "uvicorn"
|
|
31
32
|
Provides-Extra: hypercorn
|
|
@@ -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.
|
|
125
|
+
version='2025.12.01',
|
|
126
126
|
packages=find_packages(),
|
|
127
127
|
description='Event Driven Routing Infrastructure',
|
|
128
128
|
long_description=long_description,
|
|
@@ -140,7 +140,8 @@ setup(
|
|
|
140
140
|
"watchdog>=6",
|
|
141
141
|
"websockets>=14",
|
|
142
142
|
"posix-ipc>=1.2.0",
|
|
143
|
-
"markdown>=3.0"
|
|
143
|
+
"markdown>=3.0",
|
|
144
|
+
"pytz>=2024.1",
|
|
144
145
|
],
|
|
145
146
|
extras_require={
|
|
146
147
|
"uvicorn": ["uvicorn[standard]>=0.32.0"],
|
|
@@ -229,6 +229,7 @@ class TestManagerBase(unittest.TestCase):
|
|
|
229
229
|
event_response = MagicMock(spec=Event)
|
|
230
230
|
event_response._stream = None
|
|
231
231
|
resolver = MagicMock(side_effect=add_response)
|
|
232
|
+
resolver.__name__ = "resolver"
|
|
232
233
|
|
|
233
234
|
self.manager._requests[event.__class__] = resolver
|
|
234
235
|
event.has_response.return_value = False
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|