engin 0.2.0a1__tar.gz → 0.2.1__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 (116) hide show
  1. {engin-0.2.0a1 → engin-0.2.1}/PKG-INFO +19 -14
  2. {engin-0.2.0a1 → engin-0.2.1}/README.md +8 -3
  3. {engin-0.2.0a1 → engin-0.2.1}/pyproject.toml +8 -4
  4. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_assembler.py +30 -0
  5. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_block.py +4 -0
  6. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_common.py +1 -1
  7. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_dependency.py +7 -3
  8. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_engin.py +37 -28
  9. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_supervisor.py +2 -1
  10. engin-0.2.0a1/.github/workflows/benchmark.yaml +0 -41
  11. engin-0.2.0a1/.github/workflows/check.yaml +0 -49
  12. engin-0.2.0a1/.github/workflows/publish.yaml +0 -34
  13. engin-0.2.0a1/.gitignore +0 -167
  14. engin-0.2.0a1/.readthedocs.yaml +0 -25
  15. engin-0.2.0a1/CHANGELOG.md +0 -277
  16. engin-0.2.0a1/LICENSE +0 -21
  17. engin-0.2.0a1/docs/cli.md +0 -144
  18. engin-0.2.0a1/docs/concepts/blocks.md +0 -83
  19. engin-0.2.0a1/docs/concepts/engin.md +0 -62
  20. engin-0.2.0a1/docs/concepts/invocations.md +0 -130
  21. engin-0.2.0a1/docs/concepts/lifecycle.md +0 -103
  22. engin-0.2.0a1/docs/concepts/providers.md +0 -273
  23. engin-0.2.0a1/docs/concepts/supervisor.md +0 -76
  24. engin-0.2.0a1/docs/engin-graph-output.png +0 -0
  25. engin-0.2.0a1/docs/index.md +0 -98
  26. engin-0.2.0a1/docs/integrations/fastapi-graph.png +0 -0
  27. engin-0.2.0a1/docs/integrations/fastapi.md +0 -176
  28. engin-0.2.0a1/docs/js/readthedocs.js +0 -7
  29. engin-0.2.0a1/docs/overrides/main.html +0 -6
  30. engin-0.2.0a1/docs/reference.md +0 -12
  31. engin-0.2.0a1/docs/tutorial/1_empty_application.md +0 -37
  32. engin-0.2.0a1/docs/tutorial/2_create_a_publisher.md +0 -101
  33. engin-0.2.0a1/docs/tutorial/3_run_the_application.md +0 -61
  34. engin-0.2.0a1/docs/tutorial/4_refactor_valkey_client.md +0 -100
  35. engin-0.2.0a1/docs/tutorial/index.md +0 -8
  36. engin-0.2.0a1/examples/asgi/__init__.py +0 -0
  37. engin-0.2.0a1/examples/asgi/app.py +0 -34
  38. engin-0.2.0a1/examples/asgi/common/__init__.py +0 -0
  39. engin-0.2.0a1/examples/asgi/common/db/__init__.py +0 -0
  40. engin-0.2.0a1/examples/asgi/common/db/adapaters/__init__.py +0 -0
  41. engin-0.2.0a1/examples/asgi/common/db/adapaters/memory.py +0 -17
  42. engin-0.2.0a1/examples/asgi/common/db/block.py +0 -9
  43. engin-0.2.0a1/examples/asgi/common/db/ports.py +0 -13
  44. engin-0.2.0a1/examples/asgi/common/starlette/__init__.py +0 -0
  45. engin-0.2.0a1/examples/asgi/common/starlette/endpoint.py +0 -46
  46. engin-0.2.0a1/examples/asgi/features/__init__.py +0 -0
  47. engin-0.2.0a1/examples/asgi/features/cats/__init__.py +0 -0
  48. engin-0.2.0a1/examples/asgi/features/cats/api/__init__.py +0 -0
  49. engin-0.2.0a1/examples/asgi/features/cats/api/get.py +0 -32
  50. engin-0.2.0a1/examples/asgi/features/cats/api/post.py +0 -28
  51. engin-0.2.0a1/examples/asgi/features/cats/block.py +0 -45
  52. engin-0.2.0a1/examples/asgi/features/cats/domain.py +0 -17
  53. engin-0.2.0a1/examples/asgi/main.py +0 -19
  54. engin-0.2.0a1/examples/fastapi/__init__.py +0 -0
  55. engin-0.2.0a1/examples/fastapi/app.py +0 -29
  56. engin-0.2.0a1/examples/fastapi/main.py +0 -16
  57. engin-0.2.0a1/examples/fastapi/routes/__init__.py +0 -0
  58. engin-0.2.0a1/examples/fastapi/routes/cats/__init__.py +0 -0
  59. engin-0.2.0a1/examples/fastapi/routes/cats/adapters/__init__.py +0 -0
  60. engin-0.2.0a1/examples/fastapi/routes/cats/adapters/repository.py +0 -18
  61. engin-0.2.0a1/examples/fastapi/routes/cats/api.py +0 -42
  62. engin-0.2.0a1/examples/fastapi/routes/cats/block.py +0 -14
  63. engin-0.2.0a1/examples/fastapi/routes/cats/domain.py +0 -18
  64. engin-0.2.0a1/examples/fastapi/routes/cats/ports.py +0 -14
  65. engin-0.2.0a1/examples/simple/__init__.py +0 -0
  66. engin-0.2.0a1/examples/simple/main.py +0 -23
  67. engin-0.2.0a1/examples/tutorial/__init__.py +0 -0
  68. engin-0.2.0a1/examples/tutorial/app.py +0 -18
  69. engin-0.2.0a1/examples/tutorial/publisher.py +0 -28
  70. engin-0.2.0a1/examples/tutorial/valkey_client.py +0 -23
  71. engin-0.2.0a1/mkdocs.yaml +0 -94
  72. engin-0.2.0a1/src/engin/extensions/__init__.py +0 -0
  73. engin-0.2.0a1/tests/__init__.py +0 -0
  74. engin-0.2.0a1/tests/acceptance/__init__.py +0 -0
  75. engin-0.2.0a1/tests/acceptance/test_engin_signal_handling.py +0 -28
  76. engin-0.2.0a1/tests/acceptance/test_error_in_invocation.py +0 -28
  77. engin-0.2.0a1/tests/acceptance/test_error_in_lifecycle_shutdown.py +0 -60
  78. engin-0.2.0a1/tests/acceptance/test_error_in_lifecycle_startup.py +0 -69
  79. engin-0.2.0a1/tests/acceptance/test_error_in_provider.py +0 -35
  80. engin-0.2.0a1/tests/acceptance/test_error_in_supervisor_task.py +0 -29
  81. engin-0.2.0a1/tests/acceptance/test_fastapi.py +0 -128
  82. engin-0.2.0a1/tests/benchmarks/__init__.py +0 -0
  83. engin-0.2.0a1/tests/benchmarks/conftest.py +0 -21
  84. engin-0.2.0a1/tests/benchmarks/test_bench_assembler.py +0 -114
  85. engin-0.2.0a1/tests/cli/__init__.py +0 -0
  86. engin-0.2.0a1/tests/cli/test_check.py +0 -94
  87. engin-0.2.0a1/tests/cli/test_get_engin_instance.py +0 -111
  88. engin-0.2.0a1/tests/cli/test_graph.py +0 -58
  89. engin-0.2.0a1/tests/cli/test_inspect.py +0 -16
  90. engin-0.2.0a1/tests/conftest.py +0 -0
  91. engin-0.2.0a1/tests/deps.py +0 -44
  92. engin-0.2.0a1/tests/test_assembler.py +0 -220
  93. engin-0.2.0a1/tests/test_block.py +0 -54
  94. engin-0.2.0a1/tests/test_dependencies.py +0 -157
  95. engin-0.2.0a1/tests/test_engin.py +0 -143
  96. engin-0.2.0a1/tests/test_graph.py +0 -33
  97. engin-0.2.0a1/tests/test_lifecycle.py +0 -82
  98. engin-0.2.0a1/tests/test_supervisor.py +0 -131
  99. engin-0.2.0a1/tests/test_type_id.py +0 -81
  100. engin-0.2.0a1/uv.lock +0 -1372
  101. {engin-0.2.0a1 → engin-0.2.1}/src/engin/__init__.py +0 -0
  102. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/__init__.py +0 -0
  103. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_check.py +0 -0
  104. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_graph.html +0 -0
  105. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_graph.py +0 -0
  106. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_inspect.py +0 -0
  107. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_graph.py +0 -0
  108. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_introspect.py +0 -0
  109. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_lifecycle.py +0 -0
  110. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_option.py +0 -0
  111. {engin-0.2.0a1 → engin-0.2.1}/src/engin/_type_utils.py +0 -0
  112. {engin-0.2.0a1 → engin-0.2.1}/src/engin/exceptions.py +0 -0
  113. {engin-0.2.0a1/examples → engin-0.2.1/src/engin/extensions}/__init__.py +0 -0
  114. {engin-0.2.0a1 → engin-0.2.1}/src/engin/extensions/asgi.py +0 -0
  115. {engin-0.2.0a1 → engin-0.2.1}/src/engin/extensions/fastapi.py +0 -0
  116. {engin-0.2.0a1 → engin-0.2.1}/src/engin/py.typed +0 -0
@@ -1,20 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engin
3
- Version: 0.2.0a1
4
- Summary: An async-first modular application framework
5
- Project-URL: Homepage, https://github.com/invokermain/engin
6
- Project-URL: Documentation, https://engin.readthedocs.io/en/latest/
7
- Project-URL: Repository, https://github.com/invokermain/engin.git
8
- Project-URL: Changelog, https://github.com/invokermain/engin/blob/main/CHANGELOG.md
3
+ Version: 0.2.1
4
+ Summary: A dependency-injection powered application framework
5
+ Keywords: Dependency Injection,Application Framework
9
6
  License-Expression: MIT
10
- License-File: LICENSE
11
- Keywords: Application Framework,Dependency Injection
12
- Requires-Python: >=3.10
13
7
  Requires-Dist: anyio>=4
14
8
  Requires-Dist: exceptiongroup>=1
9
+ Requires-Dist: typing-extensions>=4
10
+ Requires-Dist: typer>=0.15 ; extra == 'cli'
11
+ Requires-Dist: tomli>=2.0 ; python_full_version < '3.11' and extra == 'cli'
12
+ Requires-Python: >=3.10
13
+ Project-URL: Changelog, https://github.com/invokermain/engin/blob/main/CHANGELOG.md
14
+ Project-URL: Documentation, https://engin.readthedocs.io/en/latest/
15
+ Project-URL: Homepage, https://github.com/invokermain/engin
16
+ Project-URL: Repository, https://github.com/invokermain/engin.git
15
17
  Provides-Extra: cli
16
- Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'cli'
17
- Requires-Dist: typer>=0.15; extra == 'cli'
18
18
  Description-Content-Type: text/markdown
19
19
 
20
20
  # Engin 🏎️
@@ -29,11 +29,11 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  ---
31
31
 
32
- Engin is a lightweight application framework powered by dependency injection, it helps
33
- you build and maintain large monoliths and many microservices.
32
+ Engin is a lightweight application framework powered by dependency injection. It helps
33
+ you build and maintain everything from large monoliths to hundreds of microservices.
34
34
 
35
35
 
36
- ## Feature
36
+ ## Features
37
37
 
38
38
  The Engin framework gives you:
39
39
 
@@ -120,3 +120,8 @@ Engin is heavily inspired by [Uber's Fx framework for Go](https://github.com/ube
120
120
  and the [Injector framework for Python](https://github.com/python-injector/injector).
121
121
 
122
122
  They are both great projects, go check them out.
123
+
124
+ ## Benchmarks
125
+
126
+ Automated benchmarks for the Engin framework can be viewed
127
+ [here](https://invokermain.github.io/engin/dev/bench/).
@@ -10,11 +10,11 @@
10
10
 
11
11
  ---
12
12
 
13
- Engin is a lightweight application framework powered by dependency injection, it helps
14
- you build and maintain large monoliths and many microservices.
13
+ Engin is a lightweight application framework powered by dependency injection. It helps
14
+ you build and maintain everything from large monoliths to hundreds of microservices.
15
15
 
16
16
 
17
- ## Feature
17
+ ## Features
18
18
 
19
19
  The Engin framework gives you:
20
20
 
@@ -101,3 +101,8 @@ Engin is heavily inspired by [Uber's Fx framework for Go](https://github.com/ube
101
101
  and the [Injector framework for Python](https://github.com/python-injector/injector).
102
102
 
103
103
  They are both great projects, go check them out.
104
+
105
+ ## Benchmarks
106
+
107
+ Automated benchmarks for the Engin framework can be viewed
108
+ [here](https://invokermain.github.io/engin/dev/bench/).
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "engin"
3
- version = "0.2.0a1"
4
- description = "An async-first modular application framework"
3
+ version = "0.2.1"
4
+ description = "A dependency-injection powered application framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  license = "MIT"
@@ -10,6 +10,7 @@ dependencies = [
10
10
  "anyio>=4",
11
11
  # backwards compatability for exception group in 3.10
12
12
  "exceptiongroup>=1",
13
+ "typing-extensions>=4",
13
14
  ]
14
15
 
15
16
  [project.optional-dependencies]
@@ -44,6 +45,7 @@ dev = [
44
45
  "pytest-benchmark>=5.1.0",
45
46
  "websockets>=15.0.1",
46
47
  "valkey>=6.1.0",
48
+ "tomli"
47
49
  ]
48
50
  docs = [
49
51
  "mkdocs-material>=9.5.50",
@@ -54,8 +56,8 @@ docs = [
54
56
  default-groups = ["dev", "docs"]
55
57
 
56
58
  [build-system]
57
- requires = ["hatchling"]
58
- build-backend = "hatchling.build"
59
+ requires = ["uv_build"]
60
+ build-backend = "uv_build"
59
61
 
60
62
 
61
63
  [tool.ruff]
@@ -106,6 +108,7 @@ exclude_lines = [
106
108
  "@overload",
107
109
  'class .*\bProtocol\b.*\):',
108
110
  "raise NotImplementedError",
111
+ "assert_never\\(",
109
112
  ]
110
113
  partial_branches = [
111
114
  "pragma: no branch",
@@ -119,6 +122,7 @@ partial_branches = [
119
122
 
120
123
 
121
124
  [tool.mypy]
125
+ python_version = "3.10"
122
126
  strict = true
123
127
  disable_error_code = [
124
128
  "type-arg", # allow generic types without type arguments
@@ -8,6 +8,8 @@ from inspect import BoundArguments, Signature
8
8
  from types import TracebackType
9
9
  from typing import Any, Generic, TypeVar, cast
10
10
 
11
+ from typing_extensions import Self
12
+
11
13
  from engin._dependency import Dependency, Provide, Supply
12
14
  from engin._type_utils import TypeId
13
15
  from engin.exceptions import NotInScopeError, ProviderError, TypeNotProvidedError
@@ -76,6 +78,34 @@ class Assembler:
76
78
  else:
77
79
  self._multiproviders[type_id].append(provider)
78
80
 
81
+ @classmethod
82
+ def from_mapped_providers(
83
+ cls,
84
+ providers: dict[TypeId, Provide[Any]],
85
+ multiproviders: dict[TypeId, list[Provide[list[Any]]]],
86
+ ) -> Self:
87
+ """
88
+ Create an Assembler from pre-mapped providers.
89
+
90
+ This method is only exposed for performance reasons in the case that Providers
91
+ have already been mapped, it is recommended to use the `__init__` method if this
92
+ is no the case.
93
+
94
+ Args:
95
+ providers: a dictionary of Providers with the Provider's `return_type_id` as
96
+ the key.
97
+ multiproviders: a dictionary of list of Providers with the Provider's
98
+ `return_type_id` as key. All Providers in the given list must be for the
99
+ related `return_type_id`.
100
+
101
+ Returns:
102
+ An Assembler instance.
103
+ """
104
+ assembler = cls(tuple()) # noqa: C408
105
+ assembler._providers = providers
106
+ assembler._multiproviders = multiproviders
107
+ return assembler
108
+
79
109
  @property
80
110
  def providers(self) -> Sequence[Provide[Any]]:
81
111
  multi_providers = [p for multi in self._multiproviders.values() for p in multi]
@@ -10,6 +10,8 @@ from engin.exceptions import InvalidBlockError
10
10
  if TYPE_CHECKING:
11
11
  from engin._engin import Engin
12
12
 
13
+ _BUILTIN_CLASS_FUNCTIONS = ("__annotate_func__",)
14
+
13
15
 
14
16
  def provide(
15
17
  func_: Func | None = None, *, scope: str | None = None, override: bool = False
@@ -84,6 +86,8 @@ class Block:
84
86
  @classmethod
85
87
  def _method_options(cls) -> Iterable[Provide | Invoke]:
86
88
  for name, method in inspect.getmembers(cls, inspect.isfunction):
89
+ if name in _BUILTIN_CLASS_FUNCTIONS:
90
+ continue
87
91
  if option := getattr(method, "_opt", None):
88
92
  if not isinstance(option, Provide | Invoke):
89
93
  raise InvalidBlockError(
@@ -1,11 +1,11 @@
1
1
  import importlib
2
2
  import sys
3
3
  from pathlib import Path
4
- from typing import Never
5
4
 
6
5
  import typer
7
6
  from rich import print
8
7
  from rich.panel import Panel
8
+ from typing_extensions import Never
9
9
 
10
10
  from engin import Engin
11
11
 
@@ -184,7 +184,9 @@ class Provide(Dependency[Any, T]):
184
184
  if self._explicit_type is not None:
185
185
  self._signature = self._signature.replace(return_annotation=self._explicit_type)
186
186
 
187
- self._is_multi = typing.get_origin(self._return_type) is list
187
+ self._is_multi = (
188
+ typing.get_origin(self._return_type) is list or self._return_type is list
189
+ )
188
190
 
189
191
  # Validate that the provider does to depend on its own output value, as this will
190
192
  # cause a recursion error and is undefined behaviour wise.
@@ -198,9 +200,11 @@ class Provide(Dependency[Any, T]):
198
200
  if self._is_multi:
199
201
  args = typing.get_args(self._return_type)
200
202
  if len(args) != 1:
201
- raise ValueError(
202
- f"A multiprovider must be of the form list[X], not '{self._return_type}'"
203
+ msg = (
204
+ "A multiprovider must be of the form list[X], not "
205
+ f"'{self._return_type_id}'"
203
206
  )
207
+ raise ValueError(msg)
204
208
 
205
209
  @property
206
210
  def return_type(self) -> type[T]:
@@ -1,14 +1,14 @@
1
1
  import asyncio
2
2
  import logging
3
- import os
4
3
  import signal
4
+ import sys
5
5
  from asyncio import Event
6
6
  from collections import defaultdict
7
7
  from contextlib import AsyncExitStack
8
8
  from enum import Enum
9
9
  from itertools import chain
10
10
  from types import FrameType
11
- from typing import ClassVar
11
+ from typing import TYPE_CHECKING, ClassVar
12
12
 
13
13
  from anyio import create_task_group, open_signal_receiver
14
14
 
@@ -18,10 +18,12 @@ from engin._graph import DependencyGrapher, Node
18
18
  from engin._lifecycle import Lifecycle
19
19
  from engin._option import Option
20
20
  from engin._supervisor import Supervisor
21
- from engin._type_utils import TypeId
22
21
  from engin.exceptions import EnginError
23
22
 
24
- _OS_IS_WINDOWS = os.name == "nt"
23
+ if TYPE_CHECKING:
24
+ from engin._type_utils import TypeId
25
+
26
+ _OS_IS_WINDOWS = sys.platform == "win32"
25
27
  LOG = logging.getLogger("engin")
26
28
 
27
29
 
@@ -107,12 +109,9 @@ class Engin:
107
109
  self._stop_requested_event = Event()
108
110
  self._stop_complete_event = Event()
109
111
  self._exit_stack = AsyncExitStack()
110
- self._assembler = Assembler([])
111
112
  self._async_context_run_task: asyncio.Task | None = None
112
113
 
113
- self._providers: dict[TypeId, Provide] = {
114
- TypeId.from_type(Assembler): Supply(self._assembler),
115
- }
114
+ self._providers: dict[TypeId, Provide] = {}
116
115
  self._multiproviders: dict[TypeId, list[Provide]] = defaultdict(list)
117
116
  self._invocations: list[Invoke] = []
118
117
 
@@ -120,10 +119,12 @@ class Engin:
120
119
  for option in chain(self._LIB_OPTIONS, options):
121
120
  option.apply(self)
122
121
 
123
- multi_providers = [p for multi in self._multiproviders.values() for p in multi]
124
-
125
- for provider in chain(self._providers.values(), multi_providers):
126
- self._assembler.add(provider)
122
+ # initialise Assembler
123
+ self._assembler = Assembler.from_mapped_providers(
124
+ providers=self._providers,
125
+ multiproviders=self._multiproviders,
126
+ )
127
+ self._assembler.add(Supply(self._assembler))
127
128
 
128
129
  @property
129
130
  def assembler(self) -> Assembler:
@@ -253,29 +254,37 @@ class Engin:
253
254
  async def _stop_engin_on_signal(stop_requested_event: Event) -> None:
254
255
  """
255
256
  A task that waits for a stop signal (SIGINT/SIGTERM) and notifies the given event.
257
+
258
+ On unix-like systems we can use asyncio's `loop.add_signal_handler` method but this
259
+ does not work on Windows as `signal.set_wakeup_fd` is not supported.
260
+
261
+ Therefore on Windows we fallback to using `signal.signal` directly.
256
262
  """
257
263
  if not _OS_IS_WINDOWS:
258
- with open_signal_receiver(signal.SIGINT, signal.SIGTERM) as recieved_signals:
259
- async for signum in recieved_signals:
260
- LOG.debug(f"received {signum.name} signal")
264
+ with open_signal_receiver(signal.SIGINT, signal.SIGTERM) as received_signals:
265
+ async for signum in received_signals:
266
+ LOG.debug(f"received signal: {signum.name}")
261
267
  stop_requested_event.set()
268
+ break
262
269
  else:
263
- should_stop = False
264
270
 
265
- # windows does not support signal_handlers, so this is the workaround
266
271
  def ctrlc_handler(sig: int, frame: FrameType | None) -> None:
267
- LOG.debug(f"received {signal.SIGINT.name} signal")
268
- nonlocal should_stop
269
- if should_stop:
272
+ LOG.debug(f"received signal: {signal.Signals(sig).name}")
273
+ if stop_requested_event.is_set():
270
274
  raise KeyboardInterrupt("Forced keyboard interrupt")
271
- should_stop = True
275
+ else:
276
+ stop_requested_event.set()
272
277
 
273
- signal.signal(signal.SIGINT, ctrlc_handler)
278
+ previous_signal_handler_sigint = signal.signal(signal.SIGINT, ctrlc_handler)
274
279
 
275
- while not should_stop:
276
- # In case engin is stopped via external `stop` call.
277
- if stop_requested_event.is_set():
278
- return
279
- await asyncio.sleep(0.1)
280
+ # technically not needed due to the _OS_IS_WINDOWS checks but keeps mypy happy
281
+ if hasattr(signal, "SIGBREAK"):
282
+ previous_signal_handler_sigbreak = signal.signal(signal.SIGBREAK, ctrlc_handler)
280
283
 
281
- stop_requested_event.set()
284
+ try:
285
+ await stop_requested_event.wait()
286
+ finally:
287
+ # restore orginal signal handlers
288
+ signal.signal(signal.SIGINT, previous_signal_handler_sigint)
289
+ if hasattr(signal, "SIGBREAK"):
290
+ signal.signal(signal.SIGBREAK, previous_signal_handler_sigbreak)
@@ -5,10 +5,11 @@ from collections.abc import Awaitable, Callable
5
5
  from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  from types import TracebackType
8
- from typing import TypeAlias, assert_never
8
+ from typing import TypeAlias
9
9
 
10
10
  import anyio
11
11
  from anyio import get_cancelled_exc_class
12
+ from typing_extensions import assert_never
12
13
 
13
14
  if typing.TYPE_CHECKING:
14
15
  from anyio.abc import TaskGroup
@@ -1,41 +0,0 @@
1
- name: Benchmark Main
2
-
3
- on:
4
- push:
5
- branches: [main]
6
-
7
- env:
8
- UV_FROZEN: "1"
9
-
10
- jobs:
11
- benchmark-main:
12
- runs-on: ubuntu-latest
13
-
14
- steps:
15
- - uses: actions/checkout@v4
16
-
17
- - name: Install uv
18
- uses: astral-sh/setup-uv@v5
19
- with:
20
- enable-cache: true
21
- cache-dependency-glob: "uv.lock"
22
-
23
- - name: Set up Python
24
- uses: actions/setup-python@v5
25
- with:
26
- python-version-file: "pyproject.toml"
27
-
28
- - name: Install the project
29
- run: uv sync --dev
30
-
31
- - name: Run benchmark
32
- run: |
33
- uv run pytest tests --benchmark-only --benchmark-json bench.json
34
-
35
- - name: Store benchmark result
36
- uses: benchmark-action/github-action-benchmark@v1
37
- with:
38
- tool: 'pytest'
39
- output-file-path: bench.json
40
- github-token: ${{ secrets.PAT }}
41
- auto-push: true
@@ -1,49 +0,0 @@
1
- name: Check
2
-
3
- on:
4
- push:
5
-
6
- env:
7
- UV_FROZEN: "1"
8
-
9
- jobs:
10
- check:
11
- name: python
12
-
13
- strategy:
14
- matrix:
15
- os: [ubuntu-latest, windows-latest, macos-latest]
16
-
17
- runs-on: ${{ matrix.os }}
18
-
19
- permissions:
20
- id-token: write
21
-
22
- steps:
23
- - uses: actions/checkout@v4
24
-
25
- - name: Install uv
26
- uses: astral-sh/setup-uv@v5
27
- with:
28
- enable-cache: true
29
- cache-dependency-glob: "uv.lock"
30
-
31
- - name: Set up Python
32
- uses: actions/setup-python@v5
33
- with:
34
- python-version-file: "pyproject.toml"
35
-
36
- - name: Install the project
37
- run: uv sync --all-extras --dev
38
-
39
- - name: Quality
40
- run: uv run poe check
41
-
42
- - name: Test
43
- run: uv run poe ci-test
44
-
45
- - name: Upload coverage reports to Codecov
46
- if: matrix.os == 'ubuntu-latest'
47
- uses: codecov/codecov-action@v5
48
- with:
49
- token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,34 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- workflow_dispatch:
5
- release:
6
- types: [created]
7
-
8
- jobs:
9
- publish-to-pypi:
10
- name: python
11
- runs-on: ubuntu-latest
12
-
13
- permissions:
14
- id-token: write
15
-
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - name: Install uv
20
- uses: astral-sh/setup-uv@v5
21
- with:
22
- enable-cache: true
23
- cache-dependency-glob: "uv.lock"
24
-
25
- - name: Set up Python
26
- uses: actions/setup-python@v5
27
- with:
28
- python-version-file: "pyproject.toml"
29
-
30
- - name: Build the Project
31
- run: uv build
32
-
33
- - name: Publish
34
- run: uv publish
engin-0.2.0a1/.gitignore DELETED
@@ -1,167 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- share/python-wheels/
24
- *.egg-info/
25
- .installed.cfg
26
- *.egg
27
- MANIFEST
28
-
29
- # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
- *.manifest
33
- *.spec
34
-
35
- # Installer logs
36
- pip-log.txt
37
- pip-delete-this-directory.txt
38
-
39
- # Unit test / coverage reports
40
- htmlcov/
41
- .tox/
42
- .nox/
43
- .coverage
44
- .coverage.*
45
- .cache
46
- nosetests.xml
47
- coverage.xml
48
- *.cover
49
- *.py,cover
50
- .hypothesis/
51
- .pytest_cache/
52
- cover/
53
-
54
- # Translations
55
- *.mo
56
- *.pot
57
-
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
- # Flask stuff:
65
- instance/
66
- .webassets-cache
67
-
68
- # Scrapy stuff:
69
- .scrapy
70
-
71
- # Sphinx documentation
72
- docs/_build/
73
-
74
- # PyBuilder
75
- .pybuilder/
76
- target/
77
-
78
- # Jupyter Notebook
79
- .ipynb_checkpoints
80
-
81
- # IPython
82
- profile_default/
83
- ipython_config.py
84
-
85
- # pyenv
86
- # For a library or package, you might want to ignore these files since the code is
87
- # intended to run in multiple environments; otherwise, check them in:
88
- # .python-version
89
-
90
- # pipenv
91
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
- # install all needed dependencies.
95
- #Pipfile.lock
96
-
97
- # poetry
98
- # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
- #poetry.lock
103
-
104
- # pdm
105
- # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
- #pdm.lock
107
- # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
- # in version control.
109
- # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110
- .pdm.toml
111
- .pdm-python
112
- .pdm-build/
113
-
114
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115
- __pypackages__/
116
-
117
- # Celery stuff
118
- celerybeat-schedule
119
- celerybeat.pid
120
-
121
- # SageMath parsed files
122
- *.sage.py
123
-
124
- # Environments
125
- .env
126
- .venv
127
- env/
128
- venv/
129
- ENV/
130
- env.bak/
131
- venv.bak/
132
-
133
- # Spyder project settings
134
- .spyderproject
135
- .spyproject
136
-
137
- # Rope project settings
138
- .ropeproject
139
-
140
- # mkdocs documentation
141
- /site
142
-
143
- # mypy
144
- .mypy_cache/
145
- .dmypy.json
146
- dmypy.json
147
-
148
- # Pyre type checker
149
- .pyre/
150
-
151
- # pytype static type analyzer
152
- .pytype/
153
-
154
- # Cython debug symbols
155
- cython_debug/
156
-
157
- # Python local version specifier
158
- .python-version
159
-
160
- # PyCharm
161
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163
- # and can be added to the global gitignore or merged into this file. For a more nuclear
164
- # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165
- .idea/
166
-
167
- scrap/**
@@ -1,25 +0,0 @@
1
- # Read the Docs configuration file
2
- # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3
-
4
- # Required
5
- version: 2
6
-
7
- # Set the OS, Python version, and other tools you might need
8
- build:
9
- os: ubuntu-24.04
10
- tools:
11
- python: "3.13"
12
- jobs:
13
- create_environment:
14
- - asdf plugin add uv
15
- - asdf install uv latest
16
- - asdf global uv latest
17
- - uv venv
18
- install:
19
- - uv sync --group docs
20
- build:
21
- html:
22
- - NO_COLOR=1 uv run mkdocs build -f mkdocs.yaml --strict --site-dir $READTHEDOCS_OUTPUT/html
23
-
24
- mkdocs:
25
- configuration: mkdocs.yaml