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.
- {engin-0.2.0a1 → engin-0.2.1}/PKG-INFO +19 -14
- {engin-0.2.0a1 → engin-0.2.1}/README.md +8 -3
- {engin-0.2.0a1 → engin-0.2.1}/pyproject.toml +8 -4
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_assembler.py +30 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_block.py +4 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_common.py +1 -1
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_dependency.py +7 -3
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_engin.py +37 -28
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_supervisor.py +2 -1
- engin-0.2.0a1/.github/workflows/benchmark.yaml +0 -41
- engin-0.2.0a1/.github/workflows/check.yaml +0 -49
- engin-0.2.0a1/.github/workflows/publish.yaml +0 -34
- engin-0.2.0a1/.gitignore +0 -167
- engin-0.2.0a1/.readthedocs.yaml +0 -25
- engin-0.2.0a1/CHANGELOG.md +0 -277
- engin-0.2.0a1/LICENSE +0 -21
- engin-0.2.0a1/docs/cli.md +0 -144
- engin-0.2.0a1/docs/concepts/blocks.md +0 -83
- engin-0.2.0a1/docs/concepts/engin.md +0 -62
- engin-0.2.0a1/docs/concepts/invocations.md +0 -130
- engin-0.2.0a1/docs/concepts/lifecycle.md +0 -103
- engin-0.2.0a1/docs/concepts/providers.md +0 -273
- engin-0.2.0a1/docs/concepts/supervisor.md +0 -76
- engin-0.2.0a1/docs/engin-graph-output.png +0 -0
- engin-0.2.0a1/docs/index.md +0 -98
- engin-0.2.0a1/docs/integrations/fastapi-graph.png +0 -0
- engin-0.2.0a1/docs/integrations/fastapi.md +0 -176
- engin-0.2.0a1/docs/js/readthedocs.js +0 -7
- engin-0.2.0a1/docs/overrides/main.html +0 -6
- engin-0.2.0a1/docs/reference.md +0 -12
- engin-0.2.0a1/docs/tutorial/1_empty_application.md +0 -37
- engin-0.2.0a1/docs/tutorial/2_create_a_publisher.md +0 -101
- engin-0.2.0a1/docs/tutorial/3_run_the_application.md +0 -61
- engin-0.2.0a1/docs/tutorial/4_refactor_valkey_client.md +0 -100
- engin-0.2.0a1/docs/tutorial/index.md +0 -8
- engin-0.2.0a1/examples/asgi/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/app.py +0 -34
- engin-0.2.0a1/examples/asgi/common/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/common/db/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/common/db/adapaters/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/common/db/adapaters/memory.py +0 -17
- engin-0.2.0a1/examples/asgi/common/db/block.py +0 -9
- engin-0.2.0a1/examples/asgi/common/db/ports.py +0 -13
- engin-0.2.0a1/examples/asgi/common/starlette/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/common/starlette/endpoint.py +0 -46
- engin-0.2.0a1/examples/asgi/features/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/features/cats/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/features/cats/api/__init__.py +0 -0
- engin-0.2.0a1/examples/asgi/features/cats/api/get.py +0 -32
- engin-0.2.0a1/examples/asgi/features/cats/api/post.py +0 -28
- engin-0.2.0a1/examples/asgi/features/cats/block.py +0 -45
- engin-0.2.0a1/examples/asgi/features/cats/domain.py +0 -17
- engin-0.2.0a1/examples/asgi/main.py +0 -19
- engin-0.2.0a1/examples/fastapi/__init__.py +0 -0
- engin-0.2.0a1/examples/fastapi/app.py +0 -29
- engin-0.2.0a1/examples/fastapi/main.py +0 -16
- engin-0.2.0a1/examples/fastapi/routes/__init__.py +0 -0
- engin-0.2.0a1/examples/fastapi/routes/cats/__init__.py +0 -0
- engin-0.2.0a1/examples/fastapi/routes/cats/adapters/__init__.py +0 -0
- engin-0.2.0a1/examples/fastapi/routes/cats/adapters/repository.py +0 -18
- engin-0.2.0a1/examples/fastapi/routes/cats/api.py +0 -42
- engin-0.2.0a1/examples/fastapi/routes/cats/block.py +0 -14
- engin-0.2.0a1/examples/fastapi/routes/cats/domain.py +0 -18
- engin-0.2.0a1/examples/fastapi/routes/cats/ports.py +0 -14
- engin-0.2.0a1/examples/simple/__init__.py +0 -0
- engin-0.2.0a1/examples/simple/main.py +0 -23
- engin-0.2.0a1/examples/tutorial/__init__.py +0 -0
- engin-0.2.0a1/examples/tutorial/app.py +0 -18
- engin-0.2.0a1/examples/tutorial/publisher.py +0 -28
- engin-0.2.0a1/examples/tutorial/valkey_client.py +0 -23
- engin-0.2.0a1/mkdocs.yaml +0 -94
- engin-0.2.0a1/src/engin/extensions/__init__.py +0 -0
- engin-0.2.0a1/tests/__init__.py +0 -0
- engin-0.2.0a1/tests/acceptance/__init__.py +0 -0
- engin-0.2.0a1/tests/acceptance/test_engin_signal_handling.py +0 -28
- engin-0.2.0a1/tests/acceptance/test_error_in_invocation.py +0 -28
- engin-0.2.0a1/tests/acceptance/test_error_in_lifecycle_shutdown.py +0 -60
- engin-0.2.0a1/tests/acceptance/test_error_in_lifecycle_startup.py +0 -69
- engin-0.2.0a1/tests/acceptance/test_error_in_provider.py +0 -35
- engin-0.2.0a1/tests/acceptance/test_error_in_supervisor_task.py +0 -29
- engin-0.2.0a1/tests/acceptance/test_fastapi.py +0 -128
- engin-0.2.0a1/tests/benchmarks/__init__.py +0 -0
- engin-0.2.0a1/tests/benchmarks/conftest.py +0 -21
- engin-0.2.0a1/tests/benchmarks/test_bench_assembler.py +0 -114
- engin-0.2.0a1/tests/cli/__init__.py +0 -0
- engin-0.2.0a1/tests/cli/test_check.py +0 -94
- engin-0.2.0a1/tests/cli/test_get_engin_instance.py +0 -111
- engin-0.2.0a1/tests/cli/test_graph.py +0 -58
- engin-0.2.0a1/tests/cli/test_inspect.py +0 -16
- engin-0.2.0a1/tests/conftest.py +0 -0
- engin-0.2.0a1/tests/deps.py +0 -44
- engin-0.2.0a1/tests/test_assembler.py +0 -220
- engin-0.2.0a1/tests/test_block.py +0 -54
- engin-0.2.0a1/tests/test_dependencies.py +0 -157
- engin-0.2.0a1/tests/test_engin.py +0 -143
- engin-0.2.0a1/tests/test_graph.py +0 -33
- engin-0.2.0a1/tests/test_lifecycle.py +0 -82
- engin-0.2.0a1/tests/test_supervisor.py +0 -131
- engin-0.2.0a1/tests/test_type_id.py +0 -81
- engin-0.2.0a1/uv.lock +0 -1372
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/__init__.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/__init__.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_check.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_graph.html +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_graph.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_cli/_inspect.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_graph.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_introspect.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_lifecycle.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_option.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/_type_utils.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/exceptions.py +0 -0
- {engin-0.2.0a1/examples → engin-0.2.1/src/engin/extensions}/__init__.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/extensions/asgi.py +0 -0
- {engin-0.2.0a1 → engin-0.2.1}/src/engin/extensions/fastapi.py +0 -0
- {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.
|
4
|
-
Summary:
|
5
|
-
|
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
|
33
|
-
you build and maintain large monoliths
|
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
|
-
##
|
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
|
14
|
-
you build and maintain large monoliths
|
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
|
-
##
|
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.
|
4
|
-
description = "
|
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 = ["
|
58
|
-
build-backend = "
|
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(
|
@@ -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 =
|
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
|
-
|
202
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
self.
|
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
|
259
|
-
async for signum in
|
260
|
-
LOG.debug(f"received {signum.name}
|
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.
|
268
|
-
|
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
|
-
|
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
|
-
|
276
|
-
|
277
|
-
|
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
|
-
|
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
|
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/**
|
engin-0.2.0a1/.readthedocs.yaml
DELETED
@@ -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
|