moat-lib-micro 0.3.0__tar.gz → 0.3.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moat-lib-micro
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Compatibility layer for CPython/anyio and MicroPython/uasyncio
5
5
  Maintainer-email: Matthias Urlichs <matthias@urlichs.de>
6
6
  Project-URL: homepage, https://m-o-a-t.org
@@ -16,7 +16,7 @@ Requires-Python: >=3.8
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE.txt
18
18
  Requires-Dist: anyio~=4.0
19
- Requires-Dist: moat-util~=0.62.3
19
+ Requires-Dist: moat-util~=0.63.0
20
20
  Dynamic: license-file
21
21
 
22
22
  # moat-lib-micro
@@ -1,3 +1,27 @@
1
+ moat-lib-micro (0.3.2-1) unstable; urgency=medium
2
+
3
+ * New release for 26.1.10
4
+
5
+ -- Matthias Urlichs <matthias@urlichs.de> Thu, 23 Apr 2026 21:16:05 +0200
6
+
7
+ moat-lib-micro (0.3.1-2) unstable; urgency=medium
8
+
9
+ * New release for 26.0.12
10
+
11
+ -- Matthias Urlichs <matthias@urlichs.de> Wed, 04 Mar 2026 11:00:47 +0100
12
+
13
+ moat-lib-micro (0.3.1-1) unstable; urgency=medium
14
+
15
+ * New release for 26.0.11
16
+
17
+ -- Matthias Urlichs <matthias@urlichs.de> Mon, 02 Mar 2026 11:21:43 +0100
18
+
19
+ moat-lib-micro (0.3.0-5) unstable; urgency=medium
20
+
21
+ * New release for 26.0.1
22
+
23
+ -- Matthias Urlichs <matthias@urlichs.de> Wed, 18 Feb 2026 18:08:18 +0100
24
+
1
25
  moat-lib-micro (0.3.0-3) unstable; urgency=medium
2
26
 
3
27
  * New release for 26.0.0
@@ -13,7 +13,7 @@ classifiers = [
13
13
  ]
14
14
  dependencies = [
15
15
  "anyio ~= 4.0",
16
- "moat-util ~= 0.62.3",
16
+ "moat-util ~= 0.63.0",
17
17
  ]
18
18
  keywords = ["MoaT"]
19
19
  requires-python = ">=3.8"
@@ -21,7 +21,7 @@ name = "moat-lib-micro"
21
21
  maintainers = [{email = "matthias@urlichs.de",name = "Matthias Urlichs"}]
22
22
  description='Compatibility layer for CPython/anyio and MicroPython/uasyncio'
23
23
  readme = "README.md"
24
- version = "0.3.0"
24
+ version = "0.3.2"
25
25
 
26
26
  [project.urls]
27
27
  homepage = "https://m-o-a-t.org"
@@ -39,12 +39,101 @@ R = TypeVar("R")
39
39
 
40
40
  if TYPE_CHECKING:
41
41
  from contextlib import AbstractAsyncContextManager, AbstractContextManager
42
+ from types import TracebackType
42
43
 
43
44
  from collections.abc import Awaitable, Callable
44
- from typing import NoReturn, Self
45
+ from typing import NoReturn, ParamSpec, Protocol, Self
46
+
47
+ P = ParamSpec("P")
48
+
49
+ class _EveryProto(Protocol):
50
+ @overload
51
+ def __call__(
52
+ self, t: float, p: None = None, /, *a: Any, **k: Any
53
+ ) -> AsyncIterator[None]: ...
54
+
55
+ @overload
56
+ def __call__(
57
+ self,
58
+ t: float,
59
+ p: Callable[P, Awaitable[R]],
60
+ /,
61
+ *a: P.args,
62
+ **k: P.kwargs,
63
+ ) -> AsyncIterator[R]: ...
64
+
65
+ class _RunProto(Protocol):
66
+ def __call__(
67
+ self,
68
+ p: Callable[P, Awaitable[R]],
69
+ *a: P.args,
70
+ **k: P.kwargs,
71
+ ) -> R | None: ...
72
+
73
+ class _WaitForProto(Protocol):
74
+ def __call__(
75
+ self,
76
+ timeout: float,
77
+ p: Callable[P, Awaitable[R]],
78
+ *a: P.args,
79
+ **k: P.kwargs,
80
+ ) -> Awaitable[R]: ...
81
+
82
+ class _TaskGroupProto(AbstractAsyncContextManager[Any], Protocol):
83
+ async def __aenter__(self) -> Self: ...
84
+
85
+ async def __aexit__(
86
+ self,
87
+ exc_type: type[BaseException] | None,
88
+ exc_value: BaseException | None,
89
+ traceback: TracebackType | None,
90
+ /,
91
+ ) -> bool | None: ...
92
+
93
+ async def spawn(
94
+ self,
95
+ p: Callable[P, Awaitable[Any]],
96
+ *a: P.args,
97
+ _name: str | None = None, # ty:ignore[invalid-paramspec]
98
+ **k: P.kwargs,
99
+ ) -> _anyio.CancelScope: ...
100
+
101
+ def start_soon(
102
+ self,
103
+ p: Callable[P, Awaitable[Any]],
104
+ *a: P.args,
105
+ _name: str | None = None, # ty:ignore[invalid-paramspec]
106
+ **k: P.kwargs,
107
+ ) -> None: ...
108
+
109
+ def cancel(self) -> None: ...
110
+
111
+ class _TaskGroupFactoryProto(Protocol):
112
+ def __call__(self) -> _TaskGroupProto: ...
113
+
45
114
 
46
115
  logger = logging.getLogger(__name__)
47
116
 
117
+
118
+ def _ac_task_id() -> int | None:
119
+ """Return the current task ID if the backend exposes one."""
120
+ try:
121
+ return _anyio.get_current_task().id
122
+ except Exception:
123
+ return None
124
+
125
+
126
+ def _ac_check_owner(obj: Any, acm: AsyncExitStack, op: str) -> None:
127
+ """Reject cross-task use of an object's attached AC stack."""
128
+ owner = getattr(acm, "_moat_ac_task", None)
129
+ if owner is None:
130
+ return
131
+ cur = _ac_task_id()
132
+ if cur is None or cur == owner:
133
+ return
134
+ raise RuntimeError(f"{op}: cross-task AC use on {obj!r} (owner={owner}, current={cur})")
135
+
136
+
48
137
  # Monkeypatch aclosing's docstring to be Sphinx-compatible
49
138
  aclosing.__doc__ = """
50
139
  Async context manager for safely finalizing an asynchronously cleaned-up
@@ -74,6 +163,7 @@ __all__ = [
74
163
  "Event",
75
164
  "L",
76
165
  "Lock",
166
+ "ModuleNotFoundError",
77
167
  "ObjSequence",
78
168
  "Queue",
79
169
  "QueueEmpty",
@@ -92,6 +182,8 @@ __all__ = [
92
182
  "log",
93
183
  "log_exc",
94
184
  "print_exc",
185
+ "retry",
186
+ "retry_ms",
95
187
  "run",
96
188
  "run_server",
97
189
  "shield",
@@ -116,6 +208,7 @@ EndOfStream = _anyio.EndOfStream
116
208
  BrokenResourceError = _anyio.BrokenResourceError
117
209
  ClosedResourceError = _anyio.ClosedResourceError
118
210
  TimeoutError = TimeoutError # noqa:PLW0127,A001
211
+ ModuleNotFoundError = ModuleNotFoundError # noqa:PLW0127,A001
119
212
  ExceptionGroup = ExceptionGroup # noqa: A001, PLW0127
120
213
  BaseExceptionGroup = BaseExceptionGroup # noqa: A001, PLW0127
121
214
 
@@ -233,7 +326,7 @@ async def every_ms(
233
326
  tt = ticks_add(ticks_ms(), int(t))
234
327
  while True:
235
328
  try:
236
- yield None if p is None else await p(*a, **k)
329
+ yield (None if p is None else await p(*a, **k))
237
330
  except StopAsyncIteration:
238
331
  return
239
332
  tn = ticks_ms()
@@ -251,6 +344,24 @@ def every(t: float, *a, **k) -> AsyncIterator[Any]:
251
344
  return every_ms(t * 1000, *a, **k)
252
345
 
253
346
 
347
+ async def retry_ms(n: int, t: float, p: Callable[..., Awaitable[R]], *a, _exc=Exception, **k) -> R:
348
+ "every t milliseconds, call ``p(*a,**k)``"
349
+ while True:
350
+ try:
351
+ return await p(*a, **k)
352
+ except _exc:
353
+ if n >= 0:
354
+ n -= 1
355
+ if n == 0:
356
+ raise
357
+ await sleep_ms(t)
358
+
359
+
360
+ def retry(n: int, t: float, p: Callable[..., Awaitable[R]], *a, **k) -> Awaitable[R]:
361
+ "every t seconds, call ``p(*a,**k)``"
362
+ return retry_ms(n, t * 1000, p, *a, **k)
363
+
364
+
254
365
  async def idle() -> None:
255
366
  "sleep forever"
256
367
  await _anyio.sleep_forever()
@@ -275,7 +386,7 @@ _tg = None
275
386
  _tgt = None
276
387
 
277
388
 
278
- def TaskGroup() -> Any: # Returns augmented TaskGroup instance
389
+ def TaskGroup() -> _TaskGroupProto: # Returns augmented TaskGroup instance
279
390
  "A TaskGroup subclass that supports ``spawn`` and ``cancel``"
280
391
 
281
392
  global _tg, _tgt
@@ -286,7 +397,7 @@ def TaskGroup() -> Any: # Returns augmented TaskGroup instance
286
397
  if tgt is not _tgt:
287
398
  _tgt = tgt
288
399
 
289
- class TaskGroup_(_tgt): # type: ignore[misc]
400
+ class TaskGroup_(_tgt): # ty:ignore[unsupported-base]
290
401
  """An augmented taskgroup"""
291
402
 
292
403
  async def spawn(
@@ -314,6 +425,15 @@ def TaskGroup() -> Any: # Returns augmented TaskGroup instance
314
425
  return _tg()
315
426
 
316
427
 
428
+ if TYPE_CHECKING:
429
+ every: _EveryProto = every # noqa: PLW0127
430
+ every_ms: _EveryProto = every_ms # noqa: PLW0127
431
+ run: _RunProto = run # noqa: PLW0127
432
+ wait_for: _WaitForProto = wait_for # noqa: PLW0127
433
+ wait_for_ms: _WaitForProto = wait_for_ms # noqa: PLW0127
434
+ TaskGroup: _TaskGroupFactoryProto = TaskGroup # noqa: PLW0127
435
+
436
+
317
437
  async def run_server(
318
438
  cb: Callable,
319
439
  host: str,
@@ -406,10 +526,11 @@ def ACM(obj: Any) -> Callable[[Any], Awaitable[Any]]:
406
526
  obj._AC_ = []
407
527
 
408
528
  cm = AsyncExitStack()
529
+ cm._moat_ac_task = _ac_task_id() # noqa:SLF001 # ty:ignore[unresolved-attribute]
409
530
  obj._AC_.append(cm)
410
531
 
411
- # AsyncExitStack.__aenter__ is a no-op. We don't depend on that but at
412
- # least it shouldn't yield
532
+ # AsyncExitStack.__aenter__ is a no-op. We don't depend on that, but at
533
+ # least it shouldn't yield.
413
534
  # log("AC_Enter",nback=2)
414
535
  try:
415
536
  # pylint:disable=no-member,unnecessary-dunder-call
@@ -452,6 +573,7 @@ async def AC_use(obj: Any, ctx: Any) -> Any:
452
573
  Otherwise it's a callable and will run on exit.
453
574
  """
454
575
  acm: AsyncExitStack = obj._AC_[-1]
576
+ _ac_check_owner(obj, acm, "AC_use")
455
577
  if hasattr(ctx, "__aenter__"):
456
578
  return await acm.enter_async_context(ctx)
457
579
  elif hasattr(ctx, "__enter__"):
@@ -469,7 +591,9 @@ async def AC_exit(obj: Any, *exc) -> bool | None:
469
591
  """End the latest AsyncExitStack opened by `ACM`."""
470
592
  if not exc:
471
593
  exc = (None, None, None)
472
- return await obj._AC_.pop().__aexit__(*exc)
594
+ acm = obj._AC_.pop()
595
+ _ac_check_owner(obj, acm, "AC_exit")
596
+ return await acm.__aexit__(*exc)
473
597
 
474
598
 
475
599
  def is_async(obj: Any) -> bool:
@@ -482,5 +606,5 @@ def is_async(obj: Any) -> bool:
482
606
  async def to_thread(p: Callable[..., R], *a, **k) -> R:
483
607
  """run this function in a thread"""
484
608
  if k:
485
- return await _anyio.to_thread.run_sync(partial(p, *a, **k), abandon_on_cancel=True) # type: ignore[attr-defined]
486
- return await _anyio.to_thread.run_sync(p, *a, abandon_on_cancel=True) # type: ignore[attr-defined]
609
+ return await _anyio.to_thread.run_sync(partial(p, *a, **k), abandon_on_cancel=True) # ty:ignore[unresolved-attribute]
610
+ return await _anyio.to_thread.run_sync(p, *a, abandon_on_cancel=True) # ty:ignore[unresolved-attribute]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moat-lib-micro
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Compatibility layer for CPython/anyio and MicroPython/uasyncio
5
5
  Maintainer-email: Matthias Urlichs <matthias@urlichs.de>
6
6
  Project-URL: homepage, https://m-o-a-t.org
@@ -16,7 +16,7 @@ Requires-Python: >=3.8
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE.txt
18
18
  Requires-Dist: anyio~=4.0
19
- Requires-Dist: moat-util~=0.62.3
19
+ Requires-Dist: moat-util~=0.63.0
20
20
  Dynamic: license-file
21
21
 
22
22
  # moat-lib-micro
@@ -0,0 +1,2 @@
1
+ anyio~=4.0
2
+ moat-util~=0.63.0
@@ -1,2 +0,0 @@
1
- anyio~=4.0
2
- moat-util~=0.62.3
File without changes
File without changes
File without changes