aiomisc 17.5.12__tar.gz → 17.5.17__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.
Files changed (63) hide show
  1. {aiomisc-17.5.12 → aiomisc-17.5.17}/PKG-INFO +1 -1
  2. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/aggregate.py +111 -54
  3. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/uvicorn.py +8 -4
  4. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/version.py +2 -2
  5. {aiomisc-17.5.12 → aiomisc-17.5.17}/pyproject.toml +1 -1
  6. {aiomisc-17.5.12 → aiomisc-17.5.17}/COPYING +0 -0
  7. {aiomisc-17.5.12 → aiomisc-17.5.17}/README.rst +0 -0
  8. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/__init__.py +0 -0
  9. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/_context_vars.py +0 -0
  10. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/backoff.py +0 -0
  11. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/circuit_breaker.py +0 -0
  12. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/compat.py +0 -0
  13. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/context.py +0 -0
  14. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/counters.py +0 -0
  15. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/cron.py +0 -0
  16. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/entrypoint.py +0 -0
  17. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/io.py +0 -0
  18. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/iterator_wrapper.py +0 -0
  19. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/log.py +0 -0
  20. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/periodic.py +0 -0
  21. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/plugins/__init__.py +0 -0
  22. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/plugins/__main__.py +0 -0
  23. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/pool.py +0 -0
  24. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/process_pool.py +0 -0
  25. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/py.typed +0 -0
  26. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/recurring.py +0 -0
  27. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/__init__.py +0 -0
  28. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/aiohttp.py +0 -0
  29. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/asgi.py +0 -0
  30. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/base.py +0 -0
  31. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/carbon.py +0 -0
  32. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/cron.py +0 -0
  33. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/grpc_server.py +0 -0
  34. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/periodic.py +0 -0
  35. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/process.py +0 -0
  36. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/profiler.py +0 -0
  37. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/raven.py +0 -0
  38. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/sdwatchdog.py +0 -0
  39. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/tcp.py +0 -0
  40. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/tls.py +0 -0
  41. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/tracer.py +0 -0
  42. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/service/udp.py +0 -0
  43. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/signal.py +0 -0
  44. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/thread_pool.py +0 -0
  45. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/timeout.py +0 -0
  46. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/utils.py +0 -0
  47. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc/worker_pool.py +0 -0
  48. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/__init__.py +0 -0
  49. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/enum.py +0 -0
  50. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/formatter/__init__.py +0 -0
  51. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/formatter/color.py +0 -0
  52. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/formatter/journald.py +0 -0
  53. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/formatter/json.py +0 -0
  54. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/formatter/rich.py +0 -0
  55. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_log/py.typed +0 -0
  56. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/__init__.py +0 -0
  57. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/__main__.py +0 -0
  58. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/forking.py +0 -0
  59. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/process.py +0 -0
  60. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/process_inner.py +0 -0
  61. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/protocol.py +0 -0
  62. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/py.typed +0 -0
  63. {aiomisc-17.5.12 → aiomisc-17.5.17}/aiomisc_worker/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aiomisc
3
- Version: 17.5.12
3
+ Version: 17.5.17
4
4
  Summary: aiomisc - miscellaneous utils for asyncio
5
5
  Home-page: https://github.com/aiokitchen/aiomisc
6
6
  License: MIT
@@ -1,10 +1,21 @@
1
1
  import asyncio
2
+ import functools
2
3
  import inspect
3
4
  import logging
4
5
  from asyncio import CancelledError, Event, Future, Lock, wait_for
5
6
  from dataclasses import dataclass
6
7
  from inspect import Parameter
7
- from typing import Any, Awaitable, Callable, Iterable, List, Optional, Union
8
+ from typing import (
9
+ Any,
10
+ Awaitable,
11
+ Callable,
12
+ Generic,
13
+ Iterable,
14
+ List,
15
+ Optional,
16
+ Protocol,
17
+ TypeVar,
18
+ )
8
19
 
9
20
  from .compat import EventLoopMixin
10
21
  from .counters import Statistic
@@ -13,19 +24,25 @@ from .counters import Statistic
13
24
  log = logging.getLogger(__name__)
14
25
 
15
26
 
27
+ V = TypeVar("V")
28
+ R = TypeVar("R")
29
+
30
+
16
31
  @dataclass(frozen=True)
17
- class Arg:
18
- value: Any
19
- future: Future
32
+ class Arg(Generic[V, R]):
33
+ value: V
34
+ future: "Future[R]"
20
35
 
21
36
 
22
37
  class ResultNotSetError(Exception):
23
38
  pass
24
39
 
25
40
 
26
- AggFuncHighLevel = Callable[[Any], Awaitable[Iterable]]
27
- AggFuncAsync = Callable[[Arg], Awaitable]
28
- AggFunc = Union[AggFuncHighLevel, AggFuncAsync]
41
+ class AggregateAsyncFunc(Protocol, Generic[V, R]):
42
+ __name__: str
43
+
44
+ async def __call__(self, *args: Arg[V, R]) -> None:
45
+ ...
29
46
 
30
47
 
31
48
  class AggregateStatistic(Statistic):
@@ -36,27 +53,30 @@ class AggregateStatistic(Statistic):
36
53
  done: int
37
54
 
38
55
 
39
- class Aggregator(EventLoopMixin):
56
+ def _has_variadic_positional(func: Callable[..., Any]) -> bool:
57
+ return any(
58
+ parameter.kind == Parameter.VAR_POSITIONAL
59
+ for parameter in inspect.signature(func).parameters.values()
60
+ )
40
61
 
41
- _func: AggFunc
62
+
63
+ class AggregatorAsync(EventLoopMixin, Generic[V, R]):
64
+
65
+ _func: AggregateAsyncFunc[V, R]
42
66
  _max_count: Optional[int]
43
67
  _leeway: float
44
68
  _first_call_at: Optional[float]
45
69
  _args: list
46
- _futures: List[Future]
70
+ _futures: "List[Future[R]]"
47
71
  _event: Event
48
72
  _lock: Lock
49
73
 
50
74
  def __init__(
51
- self, func: AggFunc, *, leeway_ms: float,
75
+ self, func: AggregateAsyncFunc[V, R], *, leeway_ms: float,
52
76
  max_count: Optional[int] = None,
53
77
  statistic_name: Optional[str] = None,
54
78
  ):
55
- has_variadic_positional = any(
56
- parameter.kind == Parameter.VAR_POSITIONAL
57
- for parameter in inspect.signature(func).parameters.values()
58
- )
59
- if not has_variadic_positional:
79
+ if not _has_variadic_positional(func):
60
80
  raise ValueError(
61
81
  "Function must accept variadic positional arguments",
62
82
  )
@@ -94,9 +114,18 @@ class Aggregator(EventLoopMixin):
94
114
  def count(self) -> int:
95
115
  return len(self._args)
96
116
 
97
- async def _execute(self, *, args: list, futures: List[Future]) -> None:
117
+ async def _execute(
118
+ self,
119
+ *,
120
+ args: List[V],
121
+ futures: "List[Future[R]]",
122
+ ) -> None:
123
+ args_ = [
124
+ Arg(value=arg, future=future)
125
+ for arg, future in zip(args, futures)
126
+ ]
98
127
  try:
99
- results = await self._func(*args)
128
+ await self._func(*args_)
100
129
  self._statistic.success += 1
101
130
  except CancelledError:
102
131
  # Other waiting tasks can try to finish the job instead.
@@ -108,31 +137,29 @@ class Aggregator(EventLoopMixin):
108
137
  finally:
109
138
  self._statistic.done += 1
110
139
 
111
- self._set_results(results, futures)
112
-
113
- def _set_results(self, results: Iterable, futures: List[Future]) -> None:
114
- for future, result in zip(futures, results):
140
+ # Validate that all results/exceptions are set by the func
141
+ for future in futures:
115
142
  if not future.done():
116
- future.set_result(result)
143
+ future.set_exception(ResultNotSetError)
117
144
 
118
145
  def _set_exception(
119
- self, exc: Exception, futures: List[Future],
146
+ self, exc: Exception, futures: List["Future[R]"],
120
147
  ) -> None:
121
148
  for future in futures:
122
149
  if not future.done():
123
150
  future.set_exception(exc)
124
151
 
125
- async def aggregate(self, arg: Any) -> Any:
152
+ async def aggregate(self, arg: V) -> R:
126
153
  if self._first_call_at is None:
127
154
  self._first_call_at = self.loop.time()
128
155
  first_call_at = self._first_call_at
129
156
 
130
157
  args: list = self._args
131
- futures: List[Future] = self._futures
158
+ futures: "List[Future[R]]" = self._futures
132
159
  event: Event = self._event
133
160
  lock: Lock = self._lock
134
161
  args.append(arg)
135
- future: Future = Future()
162
+ future: "Future[R]" = Future()
136
163
  futures.append(future)
137
164
 
138
165
  if self.count == self.max_count:
@@ -165,33 +192,61 @@ class Aggregator(EventLoopMixin):
165
192
  return future.result()
166
193
 
167
194
 
168
- class AggregatorAsync(Aggregator):
195
+ S = TypeVar("S", contravariant=True)
196
+ T = TypeVar("T", covariant=True)
169
197
 
170
- async def _execute(self, *, args: list, futures: List[Future]) -> None:
171
- args = [
172
- Arg(value=arg, future=future)
173
- for arg, future in zip(args, futures)
174
- ]
175
- try:
176
- await self._func(*args)
177
- self._statistic.success += 1
178
- except CancelledError:
179
- # Other waiting tasks can try to finish the job instead.
180
- raise
181
- except Exception as e:
182
- self._set_exception(e, futures)
183
- self._statistic.error += 1
184
- return
185
- finally:
186
- self._statistic.done += 1
187
198
 
188
- # Validate that all results/exceptions are set by the func
189
- for future in futures:
190
- if not future.done():
191
- future.set_exception(ResultNotSetError)
199
+ class AggregateFunc(Protocol, Generic[S, T]):
200
+ __name__: str
201
+
202
+ async def __call__(self, *args: S) -> Iterable[T]:
203
+ ...
204
+
205
+
206
+ def _to_async_aggregate(func: AggregateFunc[V, R]) -> AggregateAsyncFunc[V, R]:
207
+ @functools.wraps(
208
+ func,
209
+ assigned=tuple(
210
+ item
211
+ for item in functools.WRAPPER_ASSIGNMENTS
212
+ if item != "__annotations__"
213
+ ),
214
+ )
215
+ async def wrapper(*args: Arg[V, R]) -> None:
216
+ args_ = [item.value for item in args]
217
+ results = await func(*args_)
218
+ for res, arg in zip(results, args):
219
+ if not arg.future.done():
220
+ arg.future.set_result(res)
221
+
222
+ return wrapper
223
+
224
+
225
+ class Aggregator(AggregatorAsync[V, R], Generic[V, R]):
226
+ def __init__(
227
+ self,
228
+ func: AggregateFunc[V, R],
229
+ *,
230
+ leeway_ms: float,
231
+ max_count: Optional[int] = None,
232
+ statistic_name: Optional[str] = None,
233
+ ) -> None:
234
+ if not _has_variadic_positional(func):
235
+ raise ValueError(
236
+ "Function must accept variadic positional arguments",
237
+ )
238
+
239
+ super().__init__(
240
+ _to_async_aggregate(func),
241
+ leeway_ms=leeway_ms,
242
+ max_count=max_count,
243
+ statistic_name=statistic_name,
244
+ )
192
245
 
193
246
 
194
- def aggregate(leeway_ms: float, max_count: Optional[int] = None) -> Callable:
247
+ def aggregate(
248
+ leeway_ms: float, max_count: Optional[int] = None
249
+ ) -> Callable[[AggregateFunc[V, R]], Callable[[V], Awaitable[R]]]:
195
250
  """
196
251
  Parametric decorator that aggregates multiple
197
252
  (but no more than ``max_count`` defaulting to ``None``) single-argument
@@ -220,7 +275,7 @@ def aggregate(leeway_ms: float, max_count: Optional[int] = None) -> Callable:
220
275
 
221
276
  :return:
222
277
  """
223
- def decorator(func: AggFuncHighLevel) -> Callable[[Any], Awaitable]:
278
+ def decorator(func: AggregateFunc[V, R]) -> Callable[[V], Awaitable[R]]:
224
279
  aggregator = Aggregator(
225
280
  func, max_count=max_count, leeway_ms=leeway_ms,
226
281
  )
@@ -229,8 +284,8 @@ def aggregate(leeway_ms: float, max_count: Optional[int] = None) -> Callable:
229
284
 
230
285
 
231
286
  def aggregate_async(
232
- leeway_ms: float, max_count: Optional[int] = None,
233
- ) -> Callable:
287
+ leeway_ms: float, max_count: Optional[int] = None,
288
+ ) -> Callable[[AggregateAsyncFunc[V, R]], Callable[[V], Awaitable[R]]]:
234
289
  """
235
290
  Same as ``aggregate``, but with ``func`` arguments of type ``Arg``
236
291
  containing ``value`` and ``future`` attributes instead. In this setting
@@ -241,7 +296,9 @@ def aggregate_async(
241
296
 
242
297
  :return:
243
298
  """
244
- def decorator(func: AggFuncAsync) -> Callable[[Any], Awaitable]:
299
+ def decorator(
300
+ func: AggregateAsyncFunc[V, R]
301
+ ) -> Callable[[V], Awaitable[R]]:
245
302
  aggregator = AggregatorAsync(
246
303
  func, max_count=max_count, leeway_ms=leeway_ms,
247
304
  )
@@ -24,6 +24,7 @@ UvicornApplication = Union[ASGIApplication, Callable]
24
24
  class UvicornService(Service, abc.ABC):
25
25
  __async_required__: Tuple[str, ...] = (
26
26
  "start",
27
+ "stop",
27
28
  "create_application",
28
29
  )
29
30
 
@@ -114,8 +115,11 @@ class UvicornService(Service, abc.ABC):
114
115
  )
115
116
  if not self.sock:
116
117
  self.sock = config.bind_socket()
117
- server = Server(config)
118
-
119
- self.start_event.set()
118
+ self.server = Server(config)
119
+ self.serve_task = asyncio.create_task(
120
+ self.server.serve(sockets=[self.sock])
121
+ )
120
122
 
121
- await server.serve(sockets=[self.sock])
123
+ async def stop(self, exception: Optional[Exception] = None) -> None:
124
+ self.server.should_exit = True
125
+ await self.serve_task
@@ -2,5 +2,5 @@
2
2
  # BY: poem-plugins "git" plugin
3
3
  # NEVER EDIT THIS FILE MANUALLY
4
4
 
5
- version_info = (17, 5, 12)
6
- __version__ = "17.5.12"
5
+ version_info = (17, 5, 17)
6
+ __version__ = "17.5.17"
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "aiomisc"
3
3
  # This is a dummy version which will be rewritten with poem-plugins
4
- version = "17.5.12"
4
+ version = "17.5.17"
5
5
  description = "aiomisc - miscellaneous utils for asyncio"
6
6
  authors = ["Dmitry Orlov <me@mosquito.su>"]
7
7
  readme = "README.rst"
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