edri 2025.12.3rc1__py3-none-any.whl → 2025.12.3rc2__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/manager/manager_base.py +2 -2
- edri/api/dataclass/__init__.py +1 -0
- edri/api/dataclass/file.py +1 -0
- edri/api/dataclass/range.py +71 -0
- edri/api/handlers/http_handler.py +194 -16
- edri/api/listener.py +1 -1
- edri/dataclass/directive/http.py +5 -0
- {edri-2025.12.3rc1.dist-info → edri-2025.12.3rc2.dist-info}/METADATA +1 -1
- {edri-2025.12.3rc1.dist-info → edri-2025.12.3rc2.dist-info}/RECORD +11 -10
- {edri-2025.12.3rc1.dist-info → edri-2025.12.3rc2.dist-info}/WHEEL +0 -0
- {edri-2025.12.3rc1.dist-info → edri-2025.12.3rc2.dist-info}/top_level.txt +0 -0
|
@@ -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 etag in event.etag and method == HTTPMethod.GET:
|
|
637
|
+
if etag and hasattr(event, "etag") and event.etag and f"\"{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=etag))
|
|
649
|
+
event.response.add_directive(HeaderResponseDirective(name="ETag", value=f"\"{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)
|
edri/api/dataclass/__init__.py
CHANGED
edri/api/dataclass/file.py
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
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
|
|
@@ -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
|
|
12
|
+
from re import compile, escape, sub, VERBOSE, split
|
|
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
|
|
23
|
+
from edri.api.dataclass import File, RangeSpec, RangeValue
|
|
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,15 +30,14 @@ 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, \
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
NotFoundResponseDirective, ConflictResponseDirective, HeaderResponseDirective, \
|
|
34
|
+
UnprocessableContentResponseDirective, BadRequestResponseDirective, NotModifiedResponseDirective, \
|
|
35
|
+
ServiceUnavailableResponseDirective, PartialContentResponseDirective, RangeNotSatisfiableResponseDirective
|
|
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
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
class EventTypesExtensionsDict(TypedDict):
|
|
@@ -236,6 +235,23 @@ class URLNode:
|
|
|
236
235
|
|
|
237
236
|
return repr_str
|
|
238
237
|
|
|
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
|
+
|
|
239
255
|
|
|
240
256
|
class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
|
|
241
257
|
_directive_handlers: dict[Type[T], HTTPDirectiveHandlerDict[T]] = {
|
|
@@ -276,6 +292,12 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
|
|
|
276
292
|
},
|
|
277
293
|
ServiceUnavailableResponseDirective: {
|
|
278
294
|
"status": HTTPStatus.SERVICE_UNAVAILABLE,
|
|
295
|
+
},
|
|
296
|
+
PartialContentResponseDirective: {
|
|
297
|
+
"status": HTTPStatus.PARTIAL_CONTENT,
|
|
298
|
+
},
|
|
299
|
+
RangeNotSatisfiableResponseDirective: {
|
|
300
|
+
"status": HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
|
|
279
301
|
}
|
|
280
302
|
}
|
|
281
303
|
|
|
@@ -618,16 +640,39 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
|
|
|
618
640
|
|
|
619
641
|
async def response_file(self, event: Event, *args, **kwargs: Unpack[ResponseKW]):
|
|
620
642
|
headers = kwargs["headers"]
|
|
643
|
+
request_headers = kwargs["request_headers"]
|
|
621
644
|
file = event.response.file
|
|
622
645
|
|
|
623
646
|
headers["Content-Type"].append(file.mime_type)
|
|
624
647
|
headers["Content-Disposition"].append(f"attachment;filename*=UTF-8''{quote(file.file_name, encoding='utf-8')}")
|
|
625
648
|
|
|
626
649
|
if isinstance(file.path, Path):
|
|
627
|
-
|
|
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}\"")
|
|
628
654
|
try:
|
|
629
655
|
with file.path.open("rb") as data:
|
|
630
|
-
|
|
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
|
+
|
|
631
676
|
except FileNotFoundError as e:
|
|
632
677
|
await self.response_error(HTTPStatus.INTERNAL_SERVER_ERROR, {
|
|
633
678
|
"reasons": [{
|
|
@@ -680,22 +725,60 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
|
|
|
680
725
|
}]
|
|
681
726
|
})
|
|
682
727
|
|
|
683
|
-
async def response_file_path(
|
|
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
|
+
):
|
|
684
736
|
try:
|
|
685
737
|
await self.send({
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
738
|
+
"type": "http.response.start",
|
|
739
|
+
"status": http_status,
|
|
740
|
+
"headers": self.get_headers_binary(headers),
|
|
689
741
|
})
|
|
742
|
+
|
|
690
743
|
chunk_size = 1024 * 1024
|
|
691
|
-
|
|
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())
|
|
692
769
|
await self.send({
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
770
|
+
"type": "http.response.body",
|
|
771
|
+
"body": data,
|
|
772
|
+
"more_body": bool(next_data),
|
|
696
773
|
})
|
|
774
|
+
data = next_data
|
|
775
|
+
|
|
776
|
+
if not sent_any:
|
|
777
|
+
await self.send({"type": "http.response.body", "body": b"", "more_body": False})
|
|
778
|
+
|
|
697
779
|
except Exception as e:
|
|
698
780
|
self.logger.error(e, exc_info=e)
|
|
781
|
+
raise
|
|
699
782
|
|
|
700
783
|
async def response_file_shared_memory_pipe(self, smp: SharedMemoryPipe, headers: NormalizedDefaultDict[str, Headers]):
|
|
701
784
|
try:
|
|
@@ -821,3 +904,98 @@ class HTTPHandler[T: HTTPResponseDirective](BaseHandler, ABC):
|
|
|
821
904
|
return super().convert_type(value.value, annotation)
|
|
822
905
|
|
|
823
906
|
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
|
edri/api/listener.py
CHANGED
|
@@ -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)
|
|
270
|
+
await handler.response_file(event_response, headers=headers, request_headers=handler.headers)
|
|
271
271
|
else:
|
|
272
272
|
await handler.response(status, event_response, headers=headers)
|
|
273
273
|
elif status.is_redirection:
|
edri/dataclass/directive/http.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
edri/__init__.py,sha256=bBVs4ynkUzMRudKZyuRScpPmUZTIL1LDfIytZ_L7oKE,6257
|
|
2
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=
|
|
4
|
+
edri/abstract/manager/manager_base.py,sha256=aEVzFzzl6XfLf3A7I8yAUyBpRbX3d3H6R2b0IRkNQNA,42391
|
|
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,19 +10,20 @@ 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
|
|
13
|
+
edri/api/listener.py,sha256=-29ROnrACEpGPtWuBaikzzxJZxrq2jU_twYiU__GgCA,20442
|
|
14
14
|
edri/api/middleware.py,sha256=6_x55swthVDczT-fu_1ufY1cDsHTZ04jMx6J6xfjbsM,5483
|
|
15
|
-
edri/api/dataclass/__init__.py,sha256=
|
|
15
|
+
edri/api/dataclass/__init__.py,sha256=u3520rEHLLetRI--OhTXwRmhv-ydMhuMhDPDx6_7KG0,333
|
|
16
16
|
edri/api/dataclass/api_event.py,sha256=43pCg4Whm3Y1ANUomu8pXbXIRtWQkrDDqNsr_Qnt_-Y,6629
|
|
17
17
|
edri/api/dataclass/client.py,sha256=ctc2G4mXJR2wUSujANudT3LqxW7qxk_YkpM_TEXD0tM,216
|
|
18
|
-
edri/api/dataclass/file.py,sha256=
|
|
18
|
+
edri/api/dataclass/file.py,sha256=fKQDWBYOSMj0CHXjgkY50wjgvnUlVhaB3rqK9mgp0aE,260
|
|
19
|
+
edri/api/dataclass/range.py,sha256=VaNZTZHtzzKimyg0mdW5auBF7q9ot1JHX5V2h2uw2NE,2222
|
|
19
20
|
edri/api/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
21
|
edri/api/extensions/url_extension.py,sha256=rZKumjR7J6pDTiSLIZf8IzxGgDZP7p2g0Kgs0USug_U,1971
|
|
21
22
|
edri/api/extensions/url_prefix.py,sha256=kNI6g5ZlW0w-J_IMacYLco1EQvmTtMJyEkN6-SK1wC0,491
|
|
22
23
|
edri/api/handlers/__init__.py,sha256=MI6OGDf1rM8jf_uCKK_JYeOGMts62CNy10BwwNlG0Tk,200
|
|
23
24
|
edri/api/handlers/base_handler.py,sha256=Lte9SEprQcIYv1nC4Zj2eMmHcvNcCGJQS4TWaP8XlrI,13156
|
|
24
25
|
edri/api/handlers/html_handler.py,sha256=OprcTg1IQDI7eBK-_oHqA60P1H30LA9xIQpD7iV-Neg,7464
|
|
25
|
-
edri/api/handlers/http_handler.py,sha256=
|
|
26
|
+
edri/api/handlers/http_handler.py,sha256=nN6YVeiDzhOPsb7D0xqcIVR_FVWugOgjouFGZblOhdM,43256
|
|
26
27
|
edri/api/handlers/rest_handler.py,sha256=GAG5lVTsRMCf9IUmYb_pokxyPcOfbnKZ2p3jxfy_-Dw,3300
|
|
27
28
|
edri/api/handlers/websocket_handler.py,sha256=Dh2XannDuW0eFj5CEzf3owlGc1VTyQ8ehjpxYRrCYW8,8144
|
|
28
29
|
edri/api/static_pages/documentation.j2,sha256=Fe7KLsbqp9P-pQYqG2z8rbhhGVDDFf3m6SQ2mc3PFG4,8934
|
|
@@ -41,7 +42,7 @@ edri/dataclass/response.py,sha256=VBMmVdna1IOKC5YGBXor6AayYOoiEYb9xx_RZ3bpKnw,38
|
|
|
41
42
|
edri/dataclass/directive/__init__.py,sha256=nfvsh1BmxhACW7Q8gnwy7y3l3_cI1P0k2WP0jV5RJhI,608
|
|
42
43
|
edri/dataclass/directive/base.py,sha256=2ghQpv1bGcNHYEMA0nyWGumIplXBzj9cPQ34aJ7uVr0,296
|
|
43
44
|
edri/dataclass/directive/html.py,sha256=UCuwksxt_Q9b1wha1DjEygJWAyq2Hdnir5zG9lGi8as,946
|
|
44
|
-
edri/dataclass/directive/http.py,sha256=
|
|
45
|
+
edri/dataclass/directive/http.py,sha256=iah8pVZll6ccf-gJKxwX0sAzzltFZ7WH3gY-yuv_Ex4,2900
|
|
45
46
|
edri/events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
47
|
edri/events/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
48
|
edri/events/api/client/__init__.py,sha256=6q7CJ4eLMAuz_EFIs7us-xDXudy0Z5DIHd0YCVtTeuo,170
|
|
@@ -157,7 +158,7 @@ tests/utility/test_validation.py,sha256=wZcXjLrj3JheVLKnYKkkYfyC8CCpHVAw9Jn_uDnu
|
|
|
157
158
|
tests/utility/manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
158
159
|
tests/utility/manager/test_scheduler.py,sha256=sROffYvSOaWsYQxQGTy6l9Mn_qeNPRmJoXLVPKU3XNY,9153
|
|
159
160
|
tests/utility/manager/test_store.py,sha256=xlo1JUsPLIhPJyQn7AXldAgWDo_O8ba2ns25TEaaGdQ,2821
|
|
160
|
-
edri-2025.12.
|
|
161
|
-
edri-2025.12.
|
|
162
|
-
edri-2025.12.
|
|
163
|
-
edri-2025.12.
|
|
161
|
+
edri-2025.12.3rc2.dist-info/METADATA,sha256=ApA6oZH9FenWEefx2CegCab_RVMUECQx-N9-DxF4gGA,8374
|
|
162
|
+
edri-2025.12.3rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
163
|
+
edri-2025.12.3rc2.dist-info/top_level.txt,sha256=himES6JgPlx4Zt8aDrQEj2fxAd7IDD6MBOsiNZkzKHQ,11
|
|
164
|
+
edri-2025.12.3rc2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|