hexkit 8.3.0__tar.gz → 8.4.0__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.
- {hexkit-8.3.0/src/hexkit.egg-info → hexkit-8.4.0}/PKG-INFO +1 -1
- {hexkit-8.3.0 → hexkit-8.4.0}/pyproject.toml +1 -1
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/dao.py +8 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongokafka/provider/daopub.py +14 -2
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/testing/dao.py +24 -4
- {hexkit-8.3.0 → hexkit-8.4.0/src/hexkit.egg-info}/PKG-INFO +1 -1
- {hexkit-8.3.0 → hexkit-8.4.0}/LICENSE +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/MANIFEST.in +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/README.md +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/setup.cfg +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/__main__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/base.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/config.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/correlation.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/custom_types.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/log.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/opentelemetry.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/daopub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/daosub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/eventpub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/eventsub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/kvstore.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/protocols/objstorage.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/config.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/provider/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/provider/daosub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/provider/eventpub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/provider/eventsub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/provider/utils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/testcontainer.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/akafka/testutils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/config.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/migrations/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/migrations/_manager.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/migrations/_utils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/migrations/helpers.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/provider/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/provider/client.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/provider/dao.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/provider/kvstore.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/provider/utils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongodb/testutils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongokafka/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongokafka/config.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongokafka/provider/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongokafka/provider/persistent_pub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/mongokafka/testutils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/redis/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/redis/config.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/redis/provider/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/redis/provider/client.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/redis/provider/kvstore.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/redis/testutils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/config.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/provider/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/provider/kvstore.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/provider/objstorage.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/test_files/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/test_files/test_file1.yaml +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/test_files/test_file2.yaml +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/test_files/test_file3.yaml +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/test_files/test_file4.yaml +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/testutils/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/testutils/_fixtures.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/testutils/_typical_workflow.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/s3/testutils/_utils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/testing/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/testing/eventpub.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/testing/kvstore.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/testing/objstorage.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/vault/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/vault/config.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/vault/provider/__init__.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/vault/provider/client.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/vault/provider/kvstore.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/providers/vault/testutils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/py.typed +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit/utils.py +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit.egg-info/SOURCES.txt +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit.egg-info/dependency_links.txt +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit.egg-info/requires.txt +0 -0
- {hexkit-8.3.0 → hexkit-8.4.0}/src/hexkit.egg-info/top_level.txt +0 -0
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Intended Audience :: Developers",
|
|
24
24
|
]
|
|
25
25
|
name = "hexkit"
|
|
26
|
-
version = "8.
|
|
26
|
+
version = "8.4.0"
|
|
27
27
|
description = "A Toolkit for Building Microservices using the Hexagonal Architecture"
|
|
28
28
|
dependencies = [
|
|
29
29
|
"opentelemetry-api >=1.39, <2",
|
|
@@ -170,6 +170,14 @@ class FindResult(AsyncIterator[Dto]):
|
|
|
170
170
|
self._cached_total = await self._get_total_count()
|
|
171
171
|
return self._cached_total
|
|
172
172
|
|
|
173
|
+
async def to_list(self) -> list[Dto]:
|
|
174
|
+
"""Collect all results into a list.
|
|
175
|
+
|
|
176
|
+
Calling this function will consume the iterator. Subsequent cals will return
|
|
177
|
+
an empty list.
|
|
178
|
+
"""
|
|
179
|
+
return [item async for item in self]
|
|
180
|
+
|
|
173
181
|
|
|
174
182
|
class Dao(typing.Protocol[Dto]):
|
|
175
183
|
"""A duck type with methods common to all DAOs."""
|
|
@@ -420,8 +420,20 @@ class MongoKafkaDaoPublisher(Generic[Dto]):
|
|
|
420
420
|
validate_find_mapping(mapping, dto_model=self._dto_model)
|
|
421
421
|
mapping = replace_id_field_in_find_mapping(mapping, self._id_field)
|
|
422
422
|
|
|
423
|
-
# Ensure we don't retrieve deleted docs
|
|
424
|
-
|
|
423
|
+
# Ensure we don't retrieve deleted docs. Documents lacking outbox metadata are
|
|
424
|
+
# treated as valid (matching update/delete), and the $and wrapper avoids
|
|
425
|
+
# clobbering any caller-supplied $or in the mapping.
|
|
426
|
+
mapping_without_deleted = {
|
|
427
|
+
"$and": [
|
|
428
|
+
dict(mapping),
|
|
429
|
+
{
|
|
430
|
+
"$or": [
|
|
431
|
+
{"__metadata__": {"$exists": False}},
|
|
432
|
+
{"__metadata__.deleted": False},
|
|
433
|
+
]
|
|
434
|
+
},
|
|
435
|
+
]
|
|
436
|
+
}
|
|
425
437
|
|
|
426
438
|
# Convert generic sort spec to MongoDB-specific sort spec
|
|
427
439
|
mongodb_sort: list[tuple[str, int]] = []
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
"""A mock (in-memory) DAO"""
|
|
17
17
|
|
|
18
18
|
from collections.abc import AsyncIterator, Callable, Mapping
|
|
19
|
-
from contextlib import suppress
|
|
19
|
+
from contextlib import AbstractAsyncContextManager, suppress
|
|
20
20
|
from copy import deepcopy
|
|
21
|
+
from functools import partial
|
|
21
22
|
from typing import Any, Generic, Protocol, TypeVar
|
|
22
|
-
from unittest.mock import AsyncMock
|
|
23
|
+
from unittest.mock import AsyncMock
|
|
23
24
|
|
|
24
25
|
from pydantic import BaseModel
|
|
25
26
|
|
|
@@ -87,6 +88,18 @@ def _get_nested_value(resource: Document, field_path: str) -> Any:
|
|
|
87
88
|
return value
|
|
88
89
|
|
|
89
90
|
|
|
91
|
+
def _sort_key(resource: Document, *, field_path: str) -> tuple[bool, Any]:
|
|
92
|
+
"""Build a sort key for a (possibly nested) field path of a resource.
|
|
93
|
+
|
|
94
|
+
Missing values (None) are grouped together and sorted ahead of present values,
|
|
95
|
+
mirroring MongoDB's handling of missing/null fields. The leading boolean keeps
|
|
96
|
+
None values from being compared against present values (or one another), which
|
|
97
|
+
would otherwise raise a TypeError.
|
|
98
|
+
"""
|
|
99
|
+
value = _get_nested_value(resource, field_path)
|
|
100
|
+
return (value is not None, value if value is not None else 0)
|
|
101
|
+
|
|
102
|
+
|
|
90
103
|
class Predicate(Protocol):
|
|
91
104
|
def evaluate(self, resource: dict[str, Any]) -> bool:
|
|
92
105
|
"""Determine if the supplied dict value satisfies the predicate."""
|
|
@@ -346,7 +359,10 @@ class BaseInMemDao(Generic[DTO]):
|
|
|
346
359
|
_deserialize: Callable
|
|
347
360
|
publish_pending = AsyncMock()
|
|
348
361
|
republish = AsyncMock()
|
|
349
|
-
|
|
362
|
+
|
|
363
|
+
@classmethod
|
|
364
|
+
def with_transaction(cls) -> AbstractAsyncContextManager["BaseInMemDao[DTO]"]:
|
|
365
|
+
raise NotImplementedError()
|
|
350
366
|
|
|
351
367
|
def __init__(self) -> None:
|
|
352
368
|
self.resources: dict[ID, Document] = {}
|
|
@@ -437,8 +453,12 @@ class BaseInMemDao(Generic[DTO]):
|
|
|
437
453
|
for spec in reversed(sort or []):
|
|
438
454
|
desc = spec.startswith("-")
|
|
439
455
|
field = spec.removeprefix("-")
|
|
456
|
+
# Only the exact ID field is translated to the internal "_id" field, matching
|
|
457
|
+
# the MongoDB provider. Other fields (including nested sub-fields of the ID,
|
|
458
|
+
# which MongoDB cannot sort by) are looked up as-is and resolve to None when
|
|
459
|
+
# absent, sorting consistently rather than raising - again like MongoDB.
|
|
440
460
|
doc_field = "_id" if field == self._id_field else field
|
|
441
|
-
matching.sort(key=
|
|
461
|
+
matching.sort(key=partial(_sort_key, field_path=doc_field), reverse=desc)
|
|
442
462
|
|
|
443
463
|
total = len(matching)
|
|
444
464
|
|
|
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
|