Skip to content
This repository has been archived by the owner on Jan 28, 2022. It is now read-only.

Commit

Permalink
Merge pull request #403 from Clariteia/0.0.18
Browse files Browse the repository at this point in the history
0.0.18
  • Loading branch information
Sergio García Prado authored Oct 4, 2021
2 parents 0adf307 + f5fa585 commit 642a769
Show file tree
Hide file tree
Showing 31 changed files with 851 additions and 118 deletions.
6 changes: 6 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,9 @@ History

* Add support for multiple handling functions for events.
* Fix troubles related with dependency injections.

0.0.18 (2021-10-04)
------------------

* Add `PeriodicTask`, `PeriodicTaskScheduler` and `PeriodicTaskSchedulerService`.
* Add `@enroute.periodic.event` decorator
12 changes: 11 additions & 1 deletion minos/networks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.0.17"
__version__ = "0.0.18"

from .brokers import (
Broker,
Expand All @@ -18,6 +18,8 @@
EnrouteBuilder,
EnrouteDecorator,
EnrouteDecoratorKind,
PeriodicEnrouteDecorator,
PeriodicEventEnrouteDecorator,
RestCommandEnrouteDecorator,
RestEnrouteDecorator,
RestQueryEnrouteDecorator,
Expand Down Expand Up @@ -70,6 +72,14 @@
RestResponseException,
RestService,
)
from .scheduling import (
PeriodicTask,
PeriodicTaskScheduler,
PeriodicTaskSchedulerService,
ScheduledRequest,
ScheduledRequestContent,
ScheduledResponseException,
)
from .snapshots import (
SnapshotService,
)
Expand Down
2 changes: 2 additions & 0 deletions minos/networks/decorators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
BrokerQueryEnrouteDecorator,
EnrouteDecorator,
EnrouteDecoratorKind,
PeriodicEnrouteDecorator,
PeriodicEventEnrouteDecorator,
RestCommandEnrouteDecorator,
RestEnrouteDecorator,
RestQueryEnrouteDecorator,
Expand Down
12 changes: 11 additions & 1 deletion minos/networks/decorators/analyzers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
BrokerEventEnrouteDecorator,
BrokerQueryEnrouteDecorator,
EnrouteDecorator,
PeriodicEventEnrouteDecorator,
RestCommandEnrouteDecorator,
RestEnrouteDecorator,
RestQueryEnrouteDecorator,
Expand All @@ -30,7 +31,8 @@
class EnrouteAnalyzer:
"""Search decorators in specified class"""

def __init__(self, decorated: Union[str, Type], config: Optional[MinosConfig] = None):
# noinspection PyUnusedLocal
def __init__(self, decorated: Union[str, Type], config: Optional[MinosConfig] = None, **kwargs):
if isinstance(decorated, str):
decorated = import_module(decorated)

Expand Down Expand Up @@ -71,6 +73,14 @@ def get_broker_event(self) -> dict[str, set[BrokerEnrouteDecorator]]:
# noinspection PyTypeChecker
return self._get_items({BrokerEventEnrouteDecorator})

def get_periodic_event(self) -> dict[str, set[PeriodicEventEnrouteDecorator]]:
"""Returns periodic event values.
:return: A mapping with functions as keys and a sets of decorators as values.
"""
# noinspection PyTypeChecker
return self._get_items({PeriodicEventEnrouteDecorator})

def _get_items(self, expected_types: set[Type[EnrouteDecorator]]) -> dict[str, set[EnrouteDecorator]]:
items = dict()
for fn, decorators in self.get_all().items():
Expand Down
12 changes: 12 additions & 0 deletions minos/networks/decorators/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from __future__ import (
annotations,
)

from .definitions import (
BrokerCommandEnrouteDecorator,
BrokerEventEnrouteDecorator,
BrokerQueryEnrouteDecorator,
PeriodicEventEnrouteDecorator,
RestCommandEnrouteDecorator,
RestQueryEnrouteDecorator,
)
Expand All @@ -22,11 +27,18 @@ class RestEnroute:
query = RestQueryEnrouteDecorator


class PeriodicEnroute:
"""Periodic Enroute class."""

event = PeriodicEventEnrouteDecorator


class Enroute:
"""Enroute decorator main class"""

broker = BrokerEnroute
rest = RestEnroute
periodic = PeriodicEnroute


enroute = Enroute
84 changes: 60 additions & 24 deletions minos/networks/decorators/builders.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from asyncio import (
gather,
)
from collections import (
defaultdict,
)
from inspect import (
iscoroutinefunction,
)
Expand Down Expand Up @@ -26,61 +32,91 @@
from .definitions import (
BrokerEnrouteDecorator,
EnrouteDecorator,
EnrouteDecoratorKind,
PeriodicEnrouteDecorator,
RestEnrouteDecorator,
)

Handler = Callable[[Request], Awaitable[Optional[Response]]]


class EnrouteBuilder:
"""Enroute builder class."""

def __init__(self, decorated: Union[str, Type], *args, **kwargs):
if isinstance(decorated, str):
decorated = import_module(decorated)
def __init__(self, *classes: Union[str, Type]):
classes = tuple((class_ if not isinstance(class_, str) else import_module(class_)) for class_ in classes)

self.decorated = decorated
self.analyzer = EnrouteAnalyzer(decorated, *args, **kwargs)
self.classes = classes

def get_rest_command_query(self) -> dict[RestEnrouteDecorator, Callable[[Request], Awaitable[Response]]]:
def get_rest_command_query(self, **kwargs) -> dict[RestEnrouteDecorator, Handler]:
"""Get the rest handlers for commands and queries.
:return: A dictionary with decorator classes as keys and callable handlers as values.
"""
mapping = self.analyzer.get_rest_command_query()
# noinspection PyTypeChecker
return self._build(mapping)
return self._build("get_rest_command_query", **kwargs)

def get_broker_command_query(self) -> dict[BrokerEnrouteDecorator, Callable[[Request], Awaitable[Response]]]:
def get_broker_command_query(self, **kwargs) -> dict[BrokerEnrouteDecorator, Handler]:
"""Get the broker handlers for commands and queries.
:return: A dictionary with decorator classes as keys and callable handlers as values.
"""
mapping = self.analyzer.get_broker_command_query()
# noinspection PyTypeChecker
return self._build(mapping)
return self._build("get_broker_command_query", **kwargs)

def get_broker_event(self) -> dict[BrokerEnrouteDecorator, Callable[[Request], Awaitable[Response]]]:
def get_broker_event(self, **kwargs) -> dict[BrokerEnrouteDecorator, Handler]:
"""Get the broker handlers for events.
:return: A dictionary with decorator classes as keys and callable handlers as values.
"""
mapping = self.analyzer.get_broker_event()
# noinspection PyTypeChecker
return self._build(mapping)
return self._build("get_broker_event", **kwargs)

def get_periodic_event(self, **kwargs) -> dict[PeriodicEnrouteDecorator, Handler]:
"""Get the periodic handlers for events.
:return: A dictionary with decorator classes as keys and callable handlers as values.
"""
# noinspection PyTypeChecker
return self._build("get_periodic_event", **kwargs)

def _build(self, method_name: str, **kwargs) -> dict[EnrouteDecorator, Handler]:
def _flatten(decorator: EnrouteDecorator, fns: set[Handler]) -> Handler:
if len(fns) == 1:
return next(iter(fns))

if decorator.KIND != EnrouteDecoratorKind.Event:
raise MinosRedefinedEnrouteDecoratorException(f"{decorator!r} can be used only once.")

async def _fn(*ag, **kw):
return await gather(*(fn(*ag, **kw) for fn in fns))

return _fn

return {
decorator: _flatten(decorator, fns)
for decorator, fns in self._build_all_classes(method_name, **kwargs).items()
}

def _build_all_classes(self, method_name: str, **kwargs) -> dict[EnrouteDecorator, set[Handler]]:
decomposed_handlers = defaultdict(set)
for class_ in self.classes:
self._build_one_class(class_, method_name, decomposed_handlers, **kwargs)
return decomposed_handlers

def _build(
self, mapping: dict[str, set[EnrouteDecorator]]
) -> dict[EnrouteDecorator, Callable[[Request], Awaitable[Response]]]:
def _build_one_class(
self, class_: type, method_name: str, ans: dict[EnrouteDecorator, set[Handler]], **kwargs
) -> None:
analyzer = EnrouteAnalyzer(class_, **kwargs)
mapping = getattr(analyzer, method_name)()

ans = dict()
for name, decorators in mapping.items():
for decorator in decorators:
if decorator in ans:
raise MinosRedefinedEnrouteDecoratorException(f"{decorator!r} can be used only once.")
ans[decorator] = self._build_one(name, decorator.pre_fn_name)
return ans
ans[decorator].add(self._build_one_method(class_, name, decorator.pre_fn_name))

def _build_one(self, name: str, pref_fn_name: str) -> Callable:
instance = self.decorated()
@staticmethod
def _build_one_method(class_: type, name: str, pref_fn_name: str, **kwargs) -> Handler:
instance = class_(**kwargs)
fn = getattr(instance, name)
pre_fn = getattr(instance, pref_fn_name, None)

Expand Down
4 changes: 4 additions & 0 deletions minos/networks/decorators/definitions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
from .kinds import (
EnrouteDecoratorKind,
)
from .periodic import (
PeriodicEnrouteDecorator,
PeriodicEventEnrouteDecorator,
)
from .rest import (
RestCommandEnrouteDecorator,
RestEnrouteDecorator,
Expand Down
40 changes: 40 additions & 0 deletions minos/networks/decorators/definitions/periodic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from abc import (
ABC,
)
from typing import (
Final,
Iterable,
Union,
)

from crontab import (
CronTab,
)

from .abc import (
EnrouteDecorator,
)
from .kinds import (
EnrouteDecoratorKind,
)


class PeriodicEnrouteDecorator(EnrouteDecorator, ABC):
"""Periodic Enroute class"""

def __init__(self, crontab: Union[str, CronTab]):
if isinstance(crontab, str):
crontab = CronTab(crontab)
self.crontab = crontab

def __iter__(self) -> Iterable:
yield from (self.crontab,)

def __hash__(self):
return hash(tuple((s if not isinstance(s, CronTab) else s.matchers) for s in self))


class PeriodicEventEnrouteDecorator(PeriodicEnrouteDecorator):
"""Periodic Command Enroute class"""

KIND: Final[EnrouteDecoratorKind] = EnrouteDecoratorKind.Event
26 changes: 10 additions & 16 deletions minos/networks/handlers/commands/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
from inspect import (
isawaitable,
)
from itertools import (
chain,
)
from typing import (
Any,
Awaitable,
Expand All @@ -28,7 +25,6 @@
CommandStatus,
MinosBroker,
MinosConfig,
MinosException,
)

from ...decorators import (
Expand Down Expand Up @@ -63,16 +59,17 @@ def __init__(self, broker: MinosBroker = Provide["command_reply_broker"], **kwar

@classmethod
def _from_config(cls, *args, config: MinosConfig, **kwargs) -> CommandHandler:
command_decorators = EnrouteBuilder(config.commands.service, config).get_broker_command_query()
query_decorators = EnrouteBuilder(config.queries.service, config).get_broker_command_query()

handlers = {
decorator.topic: fn for decorator, fn in chain(command_decorators.items(), query_decorators.items())
}

handlers = cls._handlers_from_config(config, **kwargs)
# noinspection PyProtectedMember
return cls(handlers=handlers, **config.broker.queue._asdict(), **kwargs)

@staticmethod
def _handlers_from_config(config: MinosConfig, **kwargs) -> dict[str, Callable[[HandlerRequest], Awaitable]]:
builder = EnrouteBuilder(config.commands.service, config.queries.service)
decorators = builder.get_broker_command_query(config=config, **kwargs)
handlers = {decorator.topic: fn for decorator, fn in decorators.items()}
return handlers

async def dispatch_one(self, entry: HandlerEntry[Command]) -> None:
"""Dispatch one row.
Expand Down Expand Up @@ -107,13 +104,10 @@ async def _fn(command: Command) -> Tuple[Any, CommandStatus]:
response = await response.content()
return response, CommandStatus.SUCCESS
except ResponseException as exc:
logger.info(f"Raised a user exception: {exc!s}")
logger.warning(f"Raised an application exception: {exc!s}")
return repr(exc), CommandStatus.ERROR
except MinosException as exc:
logger.warning(f"Raised a 'minos' exception: {exc!r}")
return repr(exc), CommandStatus.SYSTEM_ERROR
except Exception as exc:
logger.exception(f"Raised an exception: {exc!r}.")
logger.exception(f"Raised a system exception: {exc!r}")
return repr(exc), CommandStatus.SYSTEM_ERROR

return _fn
Loading

0 comments on commit 642a769

Please sign in to comment.