edri 2025.11.1rc4__py3-none-any.whl → 2025.12.1__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.
edri/abstract/__init__.py CHANGED
@@ -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.__purpose__ = "request"
7
- return func
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, Generic, TypeVar, Never, get_args, Iterable, get_origin
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 WorkerThread, WorkerProcess
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) and (
35
- purpose := getattr(attr_value, "__purpose__", None)): # Check for the decorator's marker
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
- pass
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
 
edri/api/listener.py CHANGED
@@ -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.debug("Unknown url %s", scope["path"])
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,
edri/config/setting.py CHANGED
@@ -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
- CORS_ORIGINS = getenv("EDRI_CORS_ORIGINS")
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
edri/utility/cache.py ADDED
@@ -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.11.1rc4
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
@@ -1,7 +1,7 @@
1
1
  edri/__init__.py,sha256=bBVs4ynkUzMRudKZyuRScpPmUZTIL1LDfIytZ_L7oKE,6257
2
- edri/abstract/__init__.py,sha256=u51dxLMk2DM1Ywb-DR0NgX918y5yiaSbmhTqP9Fp1TU,325
2
+ edri/abstract/__init__.py,sha256=C6ew041GNVcQlfkE77kHeZ9Ts1rIaWRqqh3bKz7Kb1E,488
3
3
  edri/abstract/manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- edri/abstract/manager/manager_base.py,sha256=aQ9UerJglo94mO6z1Xp0jy-t7KzeMMuFk1iM1ghvhCE,40194
4
+ edri/abstract/manager/manager_base.py,sha256=mguHncpNnx-5RSAPdkzzl74NpOghpl2pI76H0biyZEo,42373
5
5
  edri/abstract/manager/manager_priority_base.py,sha256=1bUGsIr6MUrCNsJsLNF_tYaiFDX5t8vK2794vdZumQo,8219
6
6
  edri/abstract/manager/worker.py,sha256=xMJMDQtcH-j8s37cUoMsF_ZrWS4elbU7vnjdKfAv0gM,403
7
7
  edri/abstract/worker/__init__.py,sha256=qcCF2MnReil9G9ojrr7I3hjMks-1tsap4yrYH5Vr07Q,164
@@ -10,7 +10,7 @@ edri/abstract/worker/worker_process.py,sha256=QiNxOuwkMds0sV2MBLyp7bjrovm5xColC7
10
10
  edri/abstract/worker/worker_thread.py,sha256=xoMPuDn-hAkWk6kFY3Xf8mxOVP__5t7-x7f-b396-8M,2176
11
11
  edri/api/__init__.py,sha256=ZDxCpHKFGajJ1RwDpV7CzxLDUaKpozJRfOCv1OPv5ZY,142
12
12
  edri/api/broker.py,sha256=I3z_bKbcTDnKXk82yteGEQmuxpqHgp5KrhQaJmk3US0,37258
13
- edri/api/listener.py,sha256=B2RgqQmCkcJOYWtTlYkyb2H7xCM233iNfsa5LG4qZZ0,20401
13
+ edri/api/listener.py,sha256=8ffX8ntJ5XbHAqT5hM38teMRsd1cBjcJ7PJAMa4Ez7c,20403
14
14
  edri/api/middleware.py,sha256=6_x55swthVDczT-fu_1ufY1cDsHTZ04jMx6J6xfjbsM,5483
15
15
  edri/api/dataclass/__init__.py,sha256=8Y-zcaJtzMdALnNG7M9jsCaB1qAJKM8Ld3h9MDajYjA,292
16
16
  edri/api/dataclass/api_event.py,sha256=08edshexI9FxdebPIgTQMSQ4fEtGpAa3K_VYCmN1jFs,6587
@@ -22,7 +22,7 @@ edri/api/extensions/url_prefix.py,sha256=kNI6g5ZlW0w-J_IMacYLco1EQvmTtMJyEkN6-SK
22
22
  edri/api/handlers/__init__.py,sha256=MI6OGDf1rM8jf_uCKK_JYeOGMts62CNy10BwwNlG0Tk,200
23
23
  edri/api/handlers/base_handler.py,sha256=aZN95tWX7hkmJ3D401c-JPfF2azjH0t1jJy_zsjPc_4,13113
24
24
  edri/api/handlers/html_handler.py,sha256=OprcTg1IQDI7eBK-_oHqA60P1H30LA9xIQpD7iV-Neg,7464
25
- edri/api/handlers/http_handler.py,sha256=oTyVxbA0xgOLAyt2n4E5dRXrnfdVRpS8-GrXAe5SSeY,36219
25
+ edri/api/handlers/http_handler.py,sha256=AC-8oi4ez5LcNfCd-wrkGSc9fLo2fBNw51yEg8MDiPs,36368
26
26
  edri/api/handlers/rest_handler.py,sha256=GAG5lVTsRMCf9IUmYb_pokxyPcOfbnKZ2p3jxfy_-Dw,3300
27
27
  edri/api/handlers/websocket_handler.py,sha256=Dh2XannDuW0eFj5CEzf3owlGc1VTyQ8ehjpxYRrCYW8,8144
28
28
  edri/api/static_pages/documentation.j2,sha256=Fe7KLsbqp9P-pQYqG2z8rbhhGVDDFf3m6SQ2mc3PFG4,8934
@@ -32,7 +32,7 @@ edri/api/static_pages/status_400.j2,sha256=ArSvsNy9GG-Gbqt6fbRSqETmGV4aTJa3Zgwwt
32
32
  edri/api/static_pages/status_500.j2,sha256=39T6VeU_7m-6-RJyVsS48dD56Hp3ZcOeVC397T8GlsY,1468
33
33
  edri/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  edri/config/constant.py,sha256=5angk0yL3LlMWShzt0D7TflK5d2yLj2HDWoVqCqSLbY,783
35
- edri/config/setting.py,sha256=-6_BDFM-RdxIenYhE1Ykm8uGL73YhYUYbdz5YmI08xo,1833
35
+ edri/config/setting.py,sha256=kzii_wmx4C8-uE_TNfBvRlJMkpJrvVLXvZam-2pm3eE,2065
36
36
  edri/dataclass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  edri/dataclass/event.py,sha256=3XwbS_8Nst0V5D6vQ0FYhrX5rx6KfLGd3-9ba71xUMQ,9866
38
38
  edri/dataclass/health_checker.py,sha256=62H5wGUtOhql3acPwFtMhpGKPUTmFwWQ4hlqIn6tjfo,1784
@@ -41,7 +41,7 @@ edri/dataclass/response.py,sha256=VBMmVdna1IOKC5YGBXor6AayYOoiEYb9xx_RZ3bpKnw,38
41
41
  edri/dataclass/directive/__init__.py,sha256=nfvsh1BmxhACW7Q8gnwy7y3l3_cI1P0k2WP0jV5RJhI,608
42
42
  edri/dataclass/directive/base.py,sha256=2ghQpv1bGcNHYEMA0nyWGumIplXBzj9cPQ34aJ7uVr0,296
43
43
  edri/dataclass/directive/html.py,sha256=UCuwksxt_Q9b1wha1DjEygJWAyq2Hdnir5zG9lGi8as,946
44
- edri/dataclass/directive/http.py,sha256=6Y4LYlERcddg4UXVCFG3ry6PUwMpS1fAGL-WEM_1A3c,2616
44
+ edri/dataclass/directive/http.py,sha256=_kxof6aSZV1IK3awbK2KKG12gWM4d4l_GDzWt_TZTqs,2727
45
45
  edri/events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  edri/events/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  edri/events/api/client/__init__.py,sha256=6q7CJ4eLMAuz_EFIs7us-xDXudy0Z5DIHd0YCVtTeuo,170
@@ -105,6 +105,7 @@ edri/switch/receiver.py,sha256=e2ugVM_DHWspVzHT5HBWajwo0YTexKeisqkXXEYscQ8,4240
105
105
  edri/switch/sender.py,sha256=xNXL6PeP9UMmMFr_Ol-cXJ5aYRVEWzx3GX1OchRbq-k,1173
106
106
  edri/switch/switch.py,sha256=ODxhUU5JQvdQRnw8idZIIW0EJc-DUzfdLdZGY_mwNdI,3388
107
107
  edri/utility/__init__.py,sha256=3ZpwOHlERrIru5wOGNtuhKb3kwvUFv5ALzP2CRyV6lg,136
108
+ edri/utility/cache.py,sha256=fcKa-RDOq3_pMV0HO0s2iV8R2knjiyjNdFeu9N5lOpA,549
108
109
  edri/utility/function.py,sha256=xoW3SUjKbSgDvcArJskHh3wq_17kwMu3X362zbVU15c,6549
109
110
  edri/utility/json_encoder.py,sha256=2VZrVgUKFbkBRCX0V7lZEWiS1DzpFRn5LtJKx36zPaY,3952
110
111
  edri/utility/normalized_default_dict.py,sha256=EgzO-cdpekH3IkldGiD7nwj_2aTYIdGrlXvheEu6uNc,5011
@@ -121,7 +122,7 @@ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
122
  tests/test_edri_init.py,sha256=CME8WwikVgbz3qyjalLauShMcCjERyDBjlFD98xZIgs,4209
122
123
  tests/abstract/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
123
124
  tests/abstract/manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
- tests/abstract/manager/test_manager_base.py,sha256=ogpWnrfJULSbMn-78ti3_Z8s5wKL_Vo5gOJ48t7Q_7U,16445
125
+ tests/abstract/manager/test_manager_base.py,sha256=btKW00EmllyL_LFN7cp4I68vho8wNTEG-sF79ocksnc,16484
125
126
  tests/abstract/manager/test_manager_base_priority.py,sha256=-z4Y1zkOGXNk1ytGla1UeLQutrfXjeWaSNBBsN5gqQk,5869
126
127
  tests/abstract/worker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
128
  tests/abstract/worker/test_worker.py,sha256=4i2MuLSBaQh_wBMK5DEbx-k1F-rs15oRWzvQ_wktN8I,5298
@@ -156,7 +157,7 @@ tests/utility/test_validation.py,sha256=wZcXjLrj3JheVLKnYKkkYfyC8CCpHVAw9Jn_uDnu
156
157
  tests/utility/manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
157
158
  tests/utility/manager/test_scheduler.py,sha256=sROffYvSOaWsYQxQGTy6l9Mn_qeNPRmJoXLVPKU3XNY,9153
158
159
  tests/utility/manager/test_store.py,sha256=xlo1JUsPLIhPJyQn7AXldAgWDo_O8ba2ns25TEaaGdQ,2821
159
- edri-2025.11.1rc4.dist-info/METADATA,sha256=4aYgoa2SpOfeR-2j9x26EOZBQ9Ca62T-E6u1Es76234,8346
160
- edri-2025.11.1rc4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
161
- edri-2025.11.1rc4.dist-info/top_level.txt,sha256=himES6JgPlx4Zt8aDrQEj2fxAd7IDD6MBOsiNZkzKHQ,11
162
- edri-2025.11.1rc4.dist-info/RECORD,,
160
+ edri-2025.12.1.dist-info/METADATA,sha256=4Z-LtT8w_wnSd7I64CpMEAz00c0ya2fgv0kiobSD37w,8371
161
+ edri-2025.12.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
162
+ edri-2025.12.1.dist-info/top_level.txt,sha256=himES6JgPlx4Zt8aDrQEj2fxAd7IDD6MBOsiNZkzKHQ,11
163
+ edri-2025.12.1.dist-info/RECORD,,
@@ -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