Skip to content
This repository was archived by the owner on Aug 25, 2024. It is now read-only.

Commit 36d88ae

Browse files
John Andersenpdxjohnny
authored andcommitted
util: asynchelper: aenter_stack method
Signed-off-by: John Andersen <[email protected]>
1 parent ce03aa7 commit 36d88ae

File tree

9 files changed

+147
-68
lines changed

9 files changed

+147
-68
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- Auto generation of documentation for operation implementations, models, and
1212
sources. Generated docs include information on configuration options and
1313
inputs and outputs for operation implementations.
14+
- Async helpers got an `aenter_stack` method which creates and returns and
15+
`contextlib.AsyncExitStack` after entering all the context's passed to it.
1416
### Changed
1517
- OperationImplementation add_label and add_orig_label methods now use op.name
1618
instead of ENTRY_POINT_ORIG_LABEL and ENTRY_POINT_NAME.
@@ -22,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2224
- MemorySource now decorated with `entry_point`
2325
- MemorySource takes arguments correctly via `config_set` and `config_get`
2426
- skel modules have `long_description_content_type` set to "text/markdown"
27+
- Base Orchestrator `__aenter__` and `__aexit__` methods were moved to the
28+
Memory Orchestrator because they are specific to that config.
2529

2630
## [0.2.0] - 2019-05-23
2731
### Added

dffml/df/base.py

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from ..util.cli.arg import Arg
2828
from ..util.cli.cmd import CMD
29+
from ..util.asynchelper import context_stacker, aenter_stack
2930
from ..util.entrypoint import base_entry_point
3031

3132

@@ -101,7 +102,7 @@ def add_label(cls, *above):
101102
return list(above) + cls.op.name.split("_")
102103

103104

104-
def op(**kwargs):
105+
def op(imp_enter=None, ctx_enter=None, **kwargs):
105106
def wrap(func):
106107
if not "name" in kwargs:
107108
kwargs["name"] = func.__name__
@@ -115,7 +116,9 @@ def wrap(func):
115116
func, OperationImplementationContext
116117
):
117118

118-
class Implementation(OperationImplementation):
119+
class Implementation(
120+
context_stacker(OperationImplementation, imp_enter)
121+
):
119122

120123
op = func.op
121124
CONTEXT = func
@@ -124,14 +127,25 @@ class Implementation(OperationImplementation):
124127
return func
125128
else:
126129

127-
class ImplementationContext(OperationImplementationContext):
130+
class ImplementationContext(
131+
context_stacker(OperationImplementationContext, ctx_enter)
132+
):
128133
async def run(
129134
self, inputs: Dict[str, Any]
130135
) -> Union[bool, Dict[str, Any]]:
131136
# TODO Add auto thread pooling of non-async functions
137+
# If imp_enter or ctx_enter exist then bind the function to
138+
# the ImplementationContext so that it has access to the
139+
# context and it's parent
140+
if imp_enter is not None or ctx_enter is not None:
141+
return await (
142+
func.__get__(self, self.__class__)(**inputs)
143+
)
132144
return await func(**inputs)
133145

134-
class Implementation(OperationImplementation):
146+
class Implementation(
147+
context_stacker(OperationImplementation, imp_enter)
148+
):
135149

136150
op = func.op
137151
CONTEXT = ImplementationContext
@@ -609,37 +623,6 @@ class BaseOrchestratorConfig(BaseConfig, NamedTuple):
609623

610624

611625
class BaseOrchestratorContext(BaseDataFlowObjectContext):
612-
def __init__(self, parent: "BaseOrchestrator") -> None:
613-
super().__init__(parent)
614-
self.__stack = None
615-
616-
async def __aenter__(self) -> "BaseOrchestratorContext":
617-
"""
618-
Ahoy matey, enter if ye dare into the management of the contexts. Eh
619-
well not sure if there's really any context being managed here...
620-
"""
621-
self.__stack = AsyncExitStack()
622-
await self.__stack.__aenter__()
623-
self.rctx = await self.__stack.enter_async_context(
624-
self.parent.rchecker()
625-
)
626-
self.ictx = await self.__stack.enter_async_context(
627-
self.parent.input_network()
628-
)
629-
self.octx = await self.__stack.enter_async_context(
630-
self.parent.operation_network()
631-
)
632-
self.lctx = await self.__stack.enter_async_context(
633-
self.parent.lock_network()
634-
)
635-
self.nctx = await self.__stack.enter_async_context(
636-
self.parent.opimp_network()
637-
)
638-
return self
639-
640-
async def __aexit__(self, exc_type, exc_value, traceback):
641-
await self.__stack.aclose()
642-
643626
@abc.abstractmethod
644627
async def run_operations(
645628
self, strict: bool = False
@@ -651,29 +634,4 @@ async def run_operations(
651634

652635
@base_entry_point("dffml.orchestrator", "dff")
653636
class BaseOrchestrator(BaseDataFlowObject):
654-
def __init__(self, config: "BaseConfig") -> None:
655-
super().__init__(config)
656-
self.__stack = None
657-
658-
async def __aenter__(self) -> "DataFlowFacilitator":
659-
self.__stack = AsyncExitStack()
660-
await self.__stack.__aenter__()
661-
self.rchecker = await self.__stack.enter_async_context(
662-
self.config.rchecker
663-
)
664-
self.input_network = await self.__stack.enter_async_context(
665-
self.config.input_network
666-
)
667-
self.operation_network = await self.__stack.enter_async_context(
668-
self.config.operation_network
669-
)
670-
self.lock_network = await self.__stack.enter_async_context(
671-
self.config.lock_network
672-
)
673-
self.opimp_network = await self.__stack.enter_async_context(
674-
self.config.opimp_network
675-
)
676-
return self
677-
678-
async def __aexit__(self, exc_type, exc_value, traceback):
679-
await self.__stack.aclose()
637+
pass # pragma: no cov

dffml/df/memory.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from ..util.cli.arg import Arg
5050
from ..util.cli.cmd import CMD
5151
from ..util.data import ignore_args
52+
from ..util.asynchelper import context_stacker, aenter_stack
5253

5354
from .log import LOGGER
5455

@@ -804,6 +805,27 @@ class MemoryOrchestratorConfig(BaseOrchestratorConfig):
804805

805806

806807
class MemoryOrchestratorContext(BaseOrchestratorContext):
808+
def __init__(self, parent: "BaseOrchestrator") -> None:
809+
super().__init__(parent)
810+
self._stack = None
811+
812+
async def __aenter__(self) -> "BaseOrchestratorContext":
813+
self._stack = AsyncExitStack()
814+
self._stack = await aenter_stack(
815+
self,
816+
{
817+
"rctx": self.parent.rchecker,
818+
"ictx": self.parent.input_network,
819+
"octx": self.parent.operation_network,
820+
"lctx": self.parent.lock_network,
821+
"nctx": self.parent.opimp_network,
822+
},
823+
)
824+
return self
825+
826+
async def __aexit__(self, exc_type, exc_value, traceback):
827+
await self._stack.aclose()
828+
807829
async def run_operations(
808830
self, strict: bool = False
809831
) -> AsyncIterator[Tuple[BaseContextHandle, Dict[str, Any]]]:
@@ -995,6 +1017,27 @@ class MemoryOrchestrator(BaseOrchestrator):
9951017

9961018
CONTEXT = MemoryOrchestratorContext
9971019

1020+
def __init__(self, config: "BaseConfig") -> None:
1021+
super().__init__(config)
1022+
self._stack = None
1023+
1024+
async def __aenter__(self) -> "DataFlowFacilitator":
1025+
self._stack = await aenter_stack(
1026+
self,
1027+
{
1028+
"rchecker": self.config.rchecker,
1029+
"input_network": self.config.input_network,
1030+
"operation_network": self.config.operation_network,
1031+
"lock_network": self.config.lock_network,
1032+
"opimp_network": self.config.opimp_network,
1033+
},
1034+
call=False,
1035+
)
1036+
return self
1037+
1038+
async def __aexit__(self, exc_type, exc_value, traceback):
1039+
await self._stack.aclose()
1040+
9981041
@classmethod
9991042
def args(cls, args, *above) -> Dict[str, Arg]:
10001043
# Extending above is done right before loading args of subclasses

dffml/util/asynchelper.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import asyncio
99
from collections import UserList
1010
from contextlib import AsyncExitStack
11-
from typing import Dict, Any, AsyncIterator, Tuple
11+
from typing import Dict, Any, AsyncIterator, Tuple, Type, AsyncContextManager
1212

1313
from .log import LOGGER
1414

@@ -106,3 +106,57 @@ async def concurrently(
106106
# For tasks which are done but have expections which we didn't
107107
# raise, collect their execptions
108108
task.exception()
109+
110+
111+
async def aenter_stack(
112+
obj: Any,
113+
context_managers: Dict[str, AsyncContextManager],
114+
call: bool = True,
115+
) -> AsyncExitStack:
116+
"""
117+
Create a :py:class:`contextlib.AsyncExitStack` then go through each key,
118+
value pair in the dict of async context managers. Enter the context of each
119+
async context manager and call setattr on ``obj`` to set the attribute by
120+
the name of ``key`` to the value yielded by the async context manager.
121+
122+
If ``call`` is true then the context entered will be the context returned by
123+
calling each context manager respectively.
124+
"""
125+
stack = AsyncExitStack()
126+
await stack.__aenter__()
127+
if context_managers is not None:
128+
for key, ctxmanager in context_managers.items():
129+
if call:
130+
# Bind if not bound
131+
if hasattr(ctxmanager, "__self__"):
132+
ctxmanager = ctxmanager.__get__(obj, obj.__class__)
133+
setattr(
134+
obj, key, await stack.enter_async_context(ctxmanager())
135+
)
136+
else:
137+
setattr(obj, key, await stack.enter_async_context(ctxmanager))
138+
return stack
139+
140+
141+
def context_stacker(
142+
inherit: Type, context_managers: Dict[str, AsyncContextManager]
143+
) -> Type:
144+
"""
145+
Using :func:`aenter_stack`
146+
"""
147+
148+
class ContextStacker(inherit):
149+
def __init__(self, *args, **kwargs) -> None:
150+
super().__init__(*args, **kwargs)
151+
self._stack = None
152+
153+
async def __aenter__(self):
154+
await super().__aenter__()
155+
self._stack = await aenter_stack(self, context_managers)
156+
return self
157+
158+
async def __aexit__(self, exc_type, exc_value, traceback):
159+
await super().__aexit__(exc_type, exc_value, traceback)
160+
await self._stack.aclose()
161+
162+
return ContextStacker

docs/api.rst renamed to docs/api/index.rst

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ API Reference
66
:maxdepth: 2
77
:caption: Contents:
88

9-
api/df/index
10-
api/feature
11-
api/repo
12-
api/model/index
13-
api/source/index
9+
df/index
10+
feature
11+
repo
12+
model/index
13+
source/index
14+
util/index

docs/api/util/asynchelper.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Asyncio Helpers
2+
===============
3+
4+
.. automodule:: dffml.util.asynchelper
5+
:members:

docs/api/util/index.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
API Helper Utilities Reference
2+
==============================
3+
4+
:py:mod:`asyncio` testing, command line, and configuration helpers live here.
5+
6+
.. toctree::
7+
:glob:
8+
:maxdepth: 2
9+
:caption: Contents:
10+
11+
asynchelper

docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@
3434
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
3535
# ones.
3636
extensions = [
37+
'sphinx.ext.intersphinx',
3738
'sphinx.ext.autodoc',
3839
'sphinx.ext.viewcode',
3940
'sphinxcontrib.asyncio',
4041
]
4142

43+
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
44+
4245
# Add any paths that contain templates here, relative to this directory.
4346
templates_path = ['_templates']
4447

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ set of feature generators which gather data from git repositories.
3232
usage/*
3333
concepts/index
3434
plugins/index
35-
api
35+
api/index
3636

3737

3838
Indices and tables

0 commit comments

Comments
 (0)