-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TopicAdapter inventory that convert a topic into an inventory
- Loading branch information
Showing
6 changed files
with
150 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import typing as t | ||
|
||
import dataclasses | ||
from contextlib import AsyncExitStack | ||
|
||
from saturn_engine.core.api import ComponentDefinition | ||
from saturn_engine.core.types import Cursor | ||
from saturn_engine.worker.inventory import Item | ||
from saturn_engine.worker.inventory import IteratorInventory | ||
from saturn_engine.worker.services import Services | ||
|
||
|
||
@dataclasses.dataclass | ||
class ItemAdapter(Item): | ||
_context: AsyncExitStack = None # type: ignore[assignment] | ||
|
||
async def __aexit__(self, *exc: t.Any) -> t.Optional[bool]: | ||
return await self._context.__aexit__(*exc) | ||
|
||
|
||
class TopicAdapter(IteratorInventory): | ||
@dataclasses.dataclass | ||
class Options: | ||
topic: ComponentDefinition | ||
|
||
def __init__(self, options: Options, services: Services, **kwargs: object) -> None: | ||
# This import must be done late since work_factory depends on this module. | ||
from saturn_engine.worker.work_factory import build_topic | ||
|
||
self.topic = build_topic(options.topic, services=services) | ||
|
||
async def iterate(self, after: t.Optional[Cursor] = None) -> t.AsyncIterator[Item]: | ||
async for message_ctx in self.topic.run(): | ||
try: | ||
async with AsyncExitStack() as stack: | ||
message = await stack.enter_async_context(message_ctx) | ||
yield ItemAdapter( | ||
id=message.id, | ||
cursor=None, | ||
args=message.args, | ||
tags=message.tags, | ||
metadata=message.metadata, | ||
_context=stack.pop_all(), | ||
) | ||
except Exception: | ||
self.logger.exception("Failed to convert message") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import typing as t | ||
|
||
from contextlib import asynccontextmanager | ||
|
||
import asyncstdlib as alib | ||
import pytest | ||
|
||
from saturn_engine.core.topic import TopicMessage | ||
from saturn_engine.utils.inspect import get_import_name | ||
from saturn_engine.worker.inventories.topic import TopicAdapter | ||
from saturn_engine.worker.topic import Topic | ||
from saturn_engine.worker.topic import TopicOutput | ||
|
||
|
||
class FakeTopic(Topic): | ||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: | ||
self.processing = 0 | ||
self.processed = 0 | ||
|
||
async def run(self) -> t.AsyncGenerator[TopicOutput, None]: | ||
for x in range(10): | ||
yield self.context(TopicMessage(args={"x": x})) | ||
|
||
@asynccontextmanager | ||
async def context(self, message: TopicMessage) -> t.AsyncIterator[TopicMessage]: | ||
self.processing += 1 | ||
yield message | ||
self.processed += 1 | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_static_inventory() -> None: | ||
inventory = TopicAdapter.from_options( | ||
{ | ||
"topic": { | ||
"name": "topic", | ||
"type": get_import_name(FakeTopic), | ||
}, | ||
}, | ||
services=None, | ||
) | ||
topic: FakeTopic = t.cast(FakeTopic, inventory.topic) | ||
|
||
messages = await alib.list(inventory.iterate()) | ||
|
||
assert topic.processing == 10 | ||
for i, ctx in enumerate(messages): | ||
async with ctx as message: | ||
assert message.args["x"] == i | ||
assert message.cursor is None | ||
assert topic.processed == i |