edri 2025.12.3__tar.gz → 2025.12.3rc1__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.
Files changed (169) hide show
  1. {edri-2025.12.3 → edri-2025.12.3rc1}/PKG-INFO +1 -1
  2. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/manager/manager_base.py +2 -2
  3. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/dataclass/__init__.py +0 -1
  4. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/dataclass/file.py +0 -1
  5. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/handlers/http_handler.py +16 -194
  6. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/listener.py +1 -1
  7. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/directive/http.py +0 -5
  8. {edri-2025.12.3 → edri-2025.12.3rc1}/edri.egg-info/PKG-INFO +1 -1
  9. {edri-2025.12.3 → edri-2025.12.3rc1}/edri.egg-info/SOURCES.txt +0 -1
  10. {edri-2025.12.3 → edri-2025.12.3rc1}/setup.py +1 -1
  11. edri-2025.12.3/edri/api/dataclass/range.py +0 -71
  12. {edri-2025.12.3 → edri-2025.12.3rc1}/README.md +0 -0
  13. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/__init__.py +0 -0
  14. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/__init__.py +0 -0
  15. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/manager/__init__.py +0 -0
  16. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/manager/manager_priority_base.py +0 -0
  17. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/manager/worker.py +0 -0
  18. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/worker/__init__.py +0 -0
  19. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/worker/worker.py +0 -0
  20. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/worker/worker_process.py +0 -0
  21. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/abstract/worker/worker_thread.py +0 -0
  22. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/__init__.py +0 -0
  23. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/broker.py +0 -0
  24. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/dataclass/api_event.py +0 -0
  25. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/dataclass/client.py +0 -0
  26. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/extensions/__init__.py +0 -0
  27. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/extensions/url_extension.py +0 -0
  28. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/extensions/url_prefix.py +0 -0
  29. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/handlers/__init__.py +0 -0
  30. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/handlers/base_handler.py +0 -0
  31. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/handlers/html_handler.py +0 -0
  32. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/handlers/rest_handler.py +0 -0
  33. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/handlers/websocket_handler.py +0 -0
  34. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/middleware.py +0 -0
  35. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/static_pages/documentation.j2 +0 -0
  36. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/static_pages/health_check_status.j2 +0 -0
  37. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/static_pages/status_300.j2 +0 -0
  38. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/static_pages/status_400.j2 +0 -0
  39. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/api/static_pages/status_500.j2 +0 -0
  40. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/config/__init__.py +0 -0
  41. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/config/constant.py +0 -0
  42. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/config/setting.py +0 -0
  43. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/__init__.py +0 -0
  44. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/directive/__init__.py +0 -0
  45. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/directive/base.py +0 -0
  46. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/directive/html.py +0 -0
  47. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/event.py +0 -0
  48. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/health_checker.py +0 -0
  49. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/injection.py +0 -0
  50. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/dataclass/response.py +0 -0
  51. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/__init__.py +0 -0
  52. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/__init__.py +0 -0
  53. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/client/__init__.py +0 -0
  54. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/client/documentation.py +0 -0
  55. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/client/register.py +0 -0
  56. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/client/unregister.py +0 -0
  57. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/group/__init__.py +0 -0
  58. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/group/client.py +0 -0
  59. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/group/manage.py +0 -0
  60. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/manage/__init__.py +0 -0
  61. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/manage/list_registered.py +0 -0
  62. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/manage/register.py +0 -0
  63. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/manage/unregister.py +0 -0
  64. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/api/manage/unregister_all.py +0 -0
  65. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/__init__.py +0 -0
  66. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/group/__init__.py +0 -0
  67. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/group/manager.py +0 -0
  68. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/group/router.py +0 -0
  69. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/group/scheduler.py +0 -0
  70. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/group/store.py +0 -0
  71. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/group/switch.py +0 -0
  72. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/group/test.py +0 -0
  73. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/manager/__init__.py +0 -0
  74. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/manager/restart.py +0 -0
  75. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/manager/stream_close.py +0 -0
  76. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/manager/stream_create.py +0 -0
  77. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/manager/stream_message.py +0 -0
  78. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/manager/worker_quit.py +0 -0
  79. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/__init__.py +0 -0
  80. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/demands.py +0 -0
  81. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/health_check.py +0 -0
  82. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/last_events.py +0 -0
  83. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/send_from.py +0 -0
  84. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/subscribe.py +0 -0
  85. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/subscribe_connector.py +0 -0
  86. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/subscribed_external.py +0 -0
  87. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/subscribed_new.py +0 -0
  88. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/unsubscribe.py +0 -0
  89. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/router/unsubscribe_all.py +0 -0
  90. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/scheduler/__init__.py +0 -0
  91. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/scheduler/cancel.py +0 -0
  92. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/scheduler/set.py +0 -0
  93. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/scheduler/set_or_update.py +0 -0
  94. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/scheduler/update.py +0 -0
  95. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/store/__init__.py +0 -0
  96. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/store/delete.py +0 -0
  97. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/store/get.py +0 -0
  98. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/store/get_callback.py +0 -0
  99. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/events/edri/store/set.py +0 -0
  100. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/router/__init__.py +0 -0
  101. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/router/cache.py +0 -0
  102. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/router/connector/__init__.py +0 -0
  103. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/router/connector/connector.py +0 -0
  104. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/router/connector/socket.py +0 -0
  105. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/router/health_checker.py +0 -0
  106. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/router/router.py +0 -0
  107. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/switch/__init__.py +0 -0
  108. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/switch/connection.py +0 -0
  109. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/switch/forwarder.py +0 -0
  110. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/switch/receiver.py +0 -0
  111. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/switch/sender.py +0 -0
  112. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/switch/switch.py +0 -0
  113. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/__init__.py +0 -0
  114. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/cache.py +0 -0
  115. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/function.py +0 -0
  116. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/json_encoder.py +0 -0
  117. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/manager/__init__.py +0 -0
  118. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/manager/scheduler.py +0 -0
  119. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/manager/store.py +0 -0
  120. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/normalized_default_dict.py +0 -0
  121. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/queue.py +0 -0
  122. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/shared_memory_pipe.py +0 -0
  123. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/storage.py +0 -0
  124. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/transformation.py +0 -0
  125. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/validation.py +0 -0
  126. {edri-2025.12.3 → edri-2025.12.3rc1}/edri/utility/watcher.py +0 -0
  127. {edri-2025.12.3 → edri-2025.12.3rc1}/edri.egg-info/dependency_links.txt +0 -0
  128. {edri-2025.12.3 → edri-2025.12.3rc1}/edri.egg-info/requires.txt +0 -0
  129. {edri-2025.12.3 → edri-2025.12.3rc1}/edri.egg-info/top_level.txt +0 -0
  130. {edri-2025.12.3 → edri-2025.12.3rc1}/setup.cfg +0 -0
  131. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/__init__.py +0 -0
  132. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/abstract/__init__.py +0 -0
  133. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/abstract/manager/__init__.py +0 -0
  134. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/abstract/manager/test_manager_base.py +0 -0
  135. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/abstract/manager/test_manager_base_priority.py +0 -0
  136. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/abstract/worker/__init__.py +0 -0
  137. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/abstract/worker/test_worker.py +0 -0
  138. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/api/__init__.py +0 -0
  139. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/api/handlers/__init__.py +0 -0
  140. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/api/handlers/test_base_handler.py +0 -0
  141. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/api/handlers/test_html_handler.py +0 -0
  142. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/api/handlers/test_http_handler.py +0 -0
  143. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/api/test_broker.py +0 -0
  144. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/dataclass/__init__.py +0 -0
  145. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/dataclass/event/__init__.py +0 -0
  146. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/dataclass/event/test_event.py +0 -0
  147. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/dataclass/event/test_event_init.py +0 -0
  148. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/dataclass/event/test_response.py +0 -0
  149. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/events/__init__.py +0 -0
  150. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/events/test/__init__.py +0 -0
  151. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/events/test/event_request.py +0 -0
  152. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/events/test/ping.py +0 -0
  153. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/events/test/ping2.py +0 -0
  154. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/router/__init__.py +0 -0
  155. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/router/test_cache.py +0 -0
  156. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/router/test_health_checker.py +0 -0
  157. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/router/test_router.py +0 -0
  158. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/test_edri_init.py +0 -0
  159. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/__init__.py +0 -0
  160. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/manager/__init__.py +0 -0
  161. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/manager/test_scheduler.py +0 -0
  162. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/manager/test_store.py +0 -0
  163. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/test_function.py +0 -0
  164. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/test_json_encoder.py +0 -0
  165. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/test_normalized_default_dict.py +0 -0
  166. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/test_shared_memory_pipe.py +0 -0
  167. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/test_storage.py +0 -0
  168. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/test_transformation.py +0 -0
  169. {edri-2025.12.3 → edri-2025.12.3rc1}/tests/utility/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edri
3
- Version: 2025.12.3
3
+ Version: 2025.12.3rc1
4
4
  Summary: Event Driven Routing Infrastructure
5
5
  Author: Marek Olšan
6
6
  Author-email: marek.olsan@gmail.com
@@ -634,7 +634,7 @@ class ManagerBase(ABC, Process, metaclass=ManagerBaseMeta):
634
634
  if cache_key:
635
635
  etag = self._cache.tag(cache_key)
636
636
  method = self._cache_methods[event.__class__]
637
- if etag and hasattr(event, "etag") and event.etag and f"\"{etag}\"" in event.etag and method == HTTPMethod.GET:
637
+ if etag and hasattr(event, "etag") and event.etag and etag in event.etag and method == HTTPMethod.GET:
638
638
  event.response.add_directive(NotModifiedResponseDirective())
639
639
  self.router_queue.put(event)
640
640
  return
@@ -646,7 +646,7 @@ class ManagerBase(ABC, Process, metaclass=ManagerBaseMeta):
646
646
  if event.response.get_status() == ResponseStatus.OK and method in (HTTPMethod.POST, HTTPMethod.PUT, HTTPMethod.PATCH, HTTPMethod.DELETE):
647
647
  self._cache.renew(cache_key)
648
648
  else:
649
- event.response.add_directive(HeaderResponseDirective(name="ETag", value=f"\"{etag}\""))
649
+ event.response.add_directive(HeaderResponseDirective(name="ETag", value=etag))
650
650
  event.response.add_directive(HeaderResponseDirective(name="Cache-Control", value=API_CACHE_CONTROL))
651
651
  event.response.add_directive(HeaderResponseDirective(name="Vary", value=self._cache_vary))
652
652
  self.router_queue.put(event)
@@ -1,6 +1,5 @@
1
1
  from .file import File
2
2
  from .client import Client
3
- from .range import RangeSpec, RangeValue
4
3
 
5
4
  class Cookie:
6
5
  def __init__(self, name: str):
@@ -9,4 +9,3 @@ class File:
9
9
  file_name: str | None
10
10
  mime_type: str
11
11
  path: Path | SharedMemoryPipe
12
- fingerprint: str | None = None
@@ -9,7 +9,7 @@ from json import loads, JSONDecodeError
9
9
  from logging import warning
10
10
  from mimetypes import guess_type
11
11
  from pathlib import Path
12
- from re import compile, escape, sub, VERBOSE, split
12
+ from re import compile, escape, sub
13
13
  from tempfile import NamedTemporaryFile, TemporaryFile
14
14
  from types import NoneType
15
15
  from typing import Callable, Type, Pattern, Any, Unpack, TypedDict, NotRequired, AnyStr, BinaryIO, Self, Literal, get_args, get_origin, \
@@ -20,7 +20,7 @@ from uuid import UUID
20
20
  from multipart import MultipartParser
21
21
 
22
22
  from edri.api import Headers
23
- from edri.api.dataclass import File, RangeSpec, RangeValue
23
+ from edri.api.dataclass import File
24
24
  from edri.api.dataclass.api_event import api_events
25
25
  from edri.api.handlers import BaseHandler
26
26
  from edri.api.handlers.base_handler import BaseDirectiveHandlerDict
@@ -30,14 +30,15 @@ from edri.config.setting import MAX_BODY_SIZE, ASSETS_PATH, UPLOAD_FILES_PREFIX,
30
30
  from edri.dataclass.directive import HTTPResponseDirective, ResponseDirective
31
31
  from edri.dataclass.directive.base import InternalServerErrorResponseDirective, UnauthorizedResponseDirective
32
32
  from edri.dataclass.directive.http import CookieResponseDirective, AccessDeniedResponseDirective, \
33
- NotFoundResponseDirective, ConflictResponseDirective, HeaderResponseDirective, \
34
- UnprocessableContentResponseDirective, BadRequestResponseDirective, NotModifiedResponseDirective, \
35
- ServiceUnavailableResponseDirective, PartialContentResponseDirective, RangeNotSatisfiableResponseDirective
33
+ NotFoundResponseDirective, \
34
+ ConflictResponseDirective, HeaderResponseDirective, UnprocessableContentResponseDirective, \
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
39
39
  from edri.utility.function import camel2snake
40
40
  from edri.utility.shared_memory_pipe import SharedMemoryPipe
41
+ from edri.utility.validation import StringValidator
41
42
 
42
43
 
43
44
  class EventTypesExtensionsDict(TypedDict):
@@ -235,23 +236,6 @@ class URLNode:
235
236
 
236
237
  return repr_str
237
238
 
238
- _RANGE_VALUE_RE = compile(
239
- r"""
240
- ^\s*
241
- (?P<unit>[A-Za-z][A-Za-z0-9._-]*) # range-unit
242
- \s*=\s*
243
- (?P<specs>.+?) # comma-separated specs (validated below)
244
- \s*$
245
- """,
246
- VERBOSE,
247
- )
248
-
249
- # "bytes" token grammar: 1) first-last / first- OR 2) -suffix
250
- _BYTES_SPEC_RE = compile(
251
- r"^\s*(?:(?P<first>\d+)\s*-\s*(?P<last>\d*)|-\s*(?P<suffix>\d+))\s*$"
252
- )
253
-
254
-
255
239
 
256
240
  class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
257
241
  _directive_handlers: dict[Type[T], HTTPDirectiveHandlerDict[T]] = {
@@ -292,12 +276,6 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
292
276
  },
293
277
  ServiceUnavailableResponseDirective: {
294
278
  "status": HTTPStatus.SERVICE_UNAVAILABLE,
295
- },
296
- PartialContentResponseDirective: {
297
- "status": HTTPStatus.PARTIAL_CONTENT,
298
- },
299
- RangeNotSatisfiableResponseDirective: {
300
- "status": HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
301
279
  }
302
280
  }
303
281
 
@@ -640,39 +618,16 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
640
618
 
641
619
  async def response_file(self, event: Event, *args, **kwargs: Unpack[ResponseKW]):
642
620
  headers = kwargs["headers"]
643
- request_headers = kwargs["request_headers"]
644
621
  file = event.response.file
645
622
 
646
623
  headers["Content-Type"].append(file.mime_type)
647
624
  headers["Content-Disposition"].append(f"attachment;filename*=UTF-8''{quote(file.file_name, encoding='utf-8')}")
648
625
 
649
626
  if isinstance(file.path, Path):
650
- file_size = file.path.stat().st_size
651
- headers["Accept-Ranges"].append("bytes")
652
- if file.fingerprint:
653
- headers["ETag"].append(f"\"{file.fingerprint}\"")
627
+ headers["Content-Length"].append(str(file.path.lstat().st_size))
654
628
  try:
655
629
  with file.path.open("rb") as data:
656
- # Only honor Range if If-Range matches current ETag (and we actually got a Range header).
657
- if request_headers["If-Range"] == headers["ETag"] and "range" in request_headers and len(request_headers["range"]) == 1:
658
- try:
659
- bytes_ranges = await self.parse_range_values(request_headers["Range"][0])
660
-
661
- # Handle only the first range spec (multipart/byteranges is a separate response type).
662
- if bytes_ranges.specs:
663
- spec = bytes_ranges.specs[0]
664
- start, end = self.compute_range(spec, file_size)
665
- data.seek(start)
666
- headers["Content-Length"].append(str(bytes_ranges.content_length(start, end)))
667
- headers["Content-Range"].append(bytes_ranges.content_range(file_size, start, end))
668
- return await self.response_file_path(data, headers, max_bytes=(end - start) + 1, http_status=HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
669
-
670
- except Exception as e:
671
- self.logger.warning("Wrong Range header", exc_info=e)
672
-
673
- headers["Content-Length"].append(str(file_size))
674
- return await self.response_file_path(data, headers)
675
-
630
+ await self.response_file_path(data, headers)
676
631
  except FileNotFoundError as e:
677
632
  await self.response_error(HTTPStatus.INTERNAL_SERVER_ERROR, {
678
633
  "reasons": [{
@@ -725,60 +680,22 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
725
680
  }]
726
681
  })
727
682
 
728
- async def response_file_path(
729
- self,
730
- file: BinaryIO,
731
- headers: "NormalizedDefaultDict[str, Headers]",
732
- *,
733
- max_bytes: int | None = None,
734
- http_status: HTTPStatus = HTTPStatus.OK,
735
- ):
683
+ async def response_file_path(self, file: BinaryIO, headers: NormalizedDefaultDict[str, Headers]):
736
684
  try:
737
685
  await self.send({
738
- "type": "http.response.start",
739
- "status": http_status,
740
- "headers": self.get_headers_binary(headers),
686
+ 'type': 'http.response.start',
687
+ 'status': HTTPStatus.OK,
688
+ 'headers': self.get_headers_binary(headers),
741
689
  })
742
-
743
690
  chunk_size = 1024 * 1024
744
- remaining = max_bytes
745
-
746
- def read_size() -> int:
747
- if remaining is None:
748
- return chunk_size
749
- return min(chunk_size, remaining)
750
-
751
- sent_any = False
752
-
753
- # Lookahead loop so we always send a final more_body=False
754
- if remaining == 0:
755
- await self.send({"type": "http.response.body", "body": b"", "more_body": False})
756
- return
757
-
758
- data = file.read(read_size())
759
- while data:
760
- sent_any = True
761
-
762
- if remaining is not None:
763
- remaining -= len(data)
764
- if remaining <= 0:
765
- await self.send({"type": "http.response.body", "body": data, "more_body": False})
766
- return
767
-
768
- next_data = file.read(read_size())
691
+ while data := file.read(chunk_size):
769
692
  await self.send({
770
- "type": "http.response.body",
771
- "body": data,
772
- "more_body": bool(next_data),
693
+ 'type': 'http.response.body',
694
+ 'body': data,
695
+ 'more_body': len(data) == chunk_size
773
696
  })
774
- data = next_data
775
-
776
- if not sent_any:
777
- await self.send({"type": "http.response.body", "body": b"", "more_body": False})
778
-
779
697
  except Exception as e:
780
698
  self.logger.error(e, exc_info=e)
781
- raise
782
699
 
783
700
  async def response_file_shared_memory_pipe(self, smp: SharedMemoryPipe, headers: NormalizedDefaultDict[str, Headers]):
784
701
  try:
@@ -904,98 +821,3 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
904
821
  return super().convert_type(value.value, annotation)
905
822
 
906
823
  return super().convert_type(value, annotation)
907
-
908
- @staticmethod
909
- async def parse_range_values(value: str, *, max_ranges: int = 100, max_digits: int = 50) -> RangeValue:
910
- """
911
- Parse + validate a Range header *value* (field-value only), e.g.:
912
- "bytes=0-99,200-299"
913
- "bytes=9500-"
914
- "bytes=-500"
915
- "items=1-10" (prepared for other units -> currently throws)
916
-
917
- Returns RangeValue(unit, specs). Raises ValueError on any validation failure.
918
- """
919
- if value is None:
920
- raise ValueError("Range value is None")
921
-
922
- m = _RANGE_VALUE_RE.match(value)
923
- if not m:
924
- raise ValueError("Invalid Range value (expected <unit>=<spec>[,<spec>...])")
925
-
926
- unit = m.group("unit").lower()
927
- specs_blob = m.group("specs").strip()
928
- if not specs_blob:
929
- raise ValueError("Range value missing specs after '='")
930
-
931
- parts = split(r"\s*,\s*", specs_blob)
932
- if any(p == "" for p in parts):
933
- raise ValueError("Invalid Range value (empty range-spec)")
934
- if len(parts) > max_ranges:
935
- raise ValueError(f"Too many ranges: {len(parts)} > {max_ranges}")
936
-
937
- def to_int(s: str) -> int:
938
- s = s.strip()
939
- if not s.isdigit():
940
- raise ValueError(f"Invalid numeric value: {s!r}")
941
- if len(s) > max_digits:
942
- raise ValueError(f"Numeric value too large (>{max_digits} digits)")
943
- return int(s)
944
-
945
- if unit != "bytes":
946
- # Prepared for other units: we parse the unit name, but we don't implement its grammar yet.
947
- raise ValueError(f"Unsupported range unit: {unit!r}")
948
-
949
- parsed: list[RangeSpec] = []
950
- for p in parts:
951
- sm = _BYTES_SPEC_RE.match(p)
952
- if not sm:
953
- raise ValueError(f"Invalid bytes range-spec: {p!r}")
954
-
955
- suffix = sm.group("suffix")
956
- if suffix is not None:
957
- # "-0" is syntactically valid; satisfiable vs unsatisfiable needs representation length.
958
- parsed.append(RangeSpec(suffix_length=to_int(suffix)))
959
- continue
960
-
961
- first = to_int(sm.group("first"))
962
- last_s = (sm.group("last") or "").strip()
963
- if last_s == "":
964
- parsed.append(RangeSpec(first=first, last=None))
965
- else:
966
- last = to_int(last_s)
967
- if last < first:
968
- raise ValueError(f"Invalid bytes range-spec (last < first): {p!r}")
969
- parsed.append(RangeSpec(first=first, last=last))
970
-
971
- return RangeValue(unit=unit, specs=tuple(parsed))
972
-
973
- @staticmethod
974
- def compute_range(spec: RangeSpec, file_size: int) -> tuple[int, int]:
975
- """
976
- Compute (start, end) inclusive for a single byte-range spec.
977
- """
978
-
979
- if spec.suffix_length:
980
- if spec.suffix_length <= 0:
981
- raise ValueError("Invalid suffix length")
982
- start = max(file_size - spec.suffix_length, 0)
983
- end = file_size - 1
984
-
985
- # Open-ended range: bytes=START-
986
- elif spec.first is not None and spec.last is None:
987
- start = spec.first
988
- end = file_size - 1
989
-
990
- # Normal range: bytes=START-END
991
- elif spec.first is not None and spec.last is not None:
992
- start = spec.first
993
- end = min(spec.last, file_size - 1)
994
-
995
- else:
996
- raise ValueError("Invalid range spec")
997
-
998
- if start < 0 or start >= file_size or end < start:
999
- raise ValueError("Range out of bounds")
1000
-
1001
- return start, end
@@ -267,7 +267,7 @@ class Listener(Process):
267
267
  status, headers = handler.handle_directives(event_response.get_response().get_directives())
268
268
  if status.is_success:
269
269
  if event_response.get_response().is_file_only:
270
- await handler.response_file(event_response, headers=headers, request_headers=handler.headers)
270
+ await handler.response_file(event_response, headers=headers)
271
271
  else:
272
272
  await handler.response(status, event_response, headers=headers)
273
273
  elif status.is_redirection:
@@ -80,8 +80,3 @@ class ServiceUnavailableResponseDirective(HTTPResponseDirective):
80
80
  @dataclass
81
81
  class PartialContentResponseDirective(HTTPResponseDirective):
82
82
  pass
83
-
84
-
85
- @dataclass
86
- class RangeNotSatisfiableResponseDirective(HTTPResponseDirective):
87
- pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edri
3
- Version: 2025.12.3
3
+ Version: 2025.12.3rc1
4
4
  Summary: Event Driven Routing Infrastructure
5
5
  Author: Marek Olšan
6
6
  Author-email: marek.olsan@gmail.com
@@ -23,7 +23,6 @@ edri/api/dataclass/__init__.py
23
23
  edri/api/dataclass/api_event.py
24
24
  edri/api/dataclass/client.py
25
25
  edri/api/dataclass/file.py
26
- edri/api/dataclass/range.py
27
26
  edri/api/extensions/__init__.py
28
27
  edri/api/extensions/url_extension.py
29
28
  edri/api/extensions/url_prefix.py
@@ -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.12.03',
125
+ version='2025.12.03rc1',
126
126
  packages=find_packages(),
127
127
  description='Event Driven Routing Infrastructure',
128
128
  long_description=long_description,
@@ -1,71 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
-
4
- @dataclass(slots=True)
5
- class RangeSpec:
6
- first: int | None = None
7
- last: int | None = None
8
- suffix_length: int | None = None
9
-
10
-
11
- @dataclass(slots=True)
12
- class RangeValue:
13
- unit: str
14
- specs: tuple[RangeSpec, ...]
15
-
16
- def content_range(
17
- self,
18
- full_length: int | None,
19
- start: int,
20
- end: int,
21
- ) -> str:
22
- """
23
- Build Content-Range for a *single* range response (206).
24
-
25
- Args:
26
- full_length: total representation length, or None if unknown.
27
- start, end: inclusive byte positions of the payload actually served.
28
- Returns:
29
- e.g. "bytes 0-99/1234" or "bytes 0-99/*"
30
- """
31
- if self.unit != "bytes":
32
- raise ValueError(f"Content-Range generation not implemented for unit={self.unit!r}")
33
-
34
- if start < 0 or end < start:
35
- raise ValueError("Invalid start/end")
36
-
37
- if full_length is not None:
38
- if full_length < 0:
39
- raise ValueError("full_length must be >= 0")
40
- # When length known, served range must be within it (end inclusive)
41
- if full_length == 0:
42
- raise ValueError("Cannot serve a non-empty range for full_length=0")
43
- if end >= full_length:
44
- raise ValueError("end must be < full_length when full_length is known")
45
-
46
- return f"bytes {start}-{end}/{full_length}"
47
-
48
- return f"bytes {start}-{end}/*"
49
-
50
- @staticmethod
51
- def content_range_unsatisfied(*, unit: str = "bytes", full_length: int) -> str:
52
- """
53
- Build Content-Range for 416 Range Not Satisfiable.
54
-
55
- Returns:
56
- e.g. "bytes */1234"
57
- """
58
- if full_length < 0:
59
- raise ValueError("full_length must be >= 0")
60
- return f"{unit} */{full_length}"
61
-
62
-
63
- def content_length(self, start: int, end: int) -> int:
64
- """
65
- Content-Length for a *single* resolved range body (206).
66
- """
67
- if self.unit != "bytes":
68
- raise ValueError(f"Content-Length not implemented for unit={self.unit!r}")
69
- if start < 0 or end < start:
70
- raise ValueError("Invalid start/end")
71
- return (end - start) + 1
File without changes
File without changes
File without changes
File without changes
File without changes