engin 0.0.2__tar.gz → 0.0.4__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.0.4/.github/workflows/check.yaml +35 -0
- {engin-0.0.2 → engin-0.0.4}/.github/workflows/publish.yaml +9 -2
- engin-0.0.4/.readthedocs.yaml +25 -0
- engin-0.0.4/CHANGELOG.md +47 -0
- engin-0.0.4/PKG-INFO +61 -0
- engin-0.0.4/README.md +53 -0
- engin-0.0.4/docs/engin.md +1 -0
- engin-0.0.4/docs/index.md +29 -0
- engin-0.0.4/docs/js/readthedocs.js +32 -0
- engin-0.0.4/docs/overrides/main.html +6 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/app.py +2 -2
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/db/adapaters/memory.py +1 -1
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/starlette/endpoint.py +2 -3
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/features/cats/api/get.py +2 -2
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/features/cats/api/post.py +3 -1
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/main.py +1 -1
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/app.py +1 -1
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/main.py +1 -1
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/cats/api.py +3 -3
- {engin-0.0.2 → engin-0.0.4}/examples/simple/main.py +3 -0
- engin-0.0.4/mkdocs.yaml +57 -0
- engin-0.0.4/pyproject.toml +91 -0
- {engin-0.0.2 → engin-0.0.4}/src/engin/_assembler.py +2 -2
- {engin-0.0.2 → engin-0.0.4}/src/engin/_block.py +6 -6
- {engin-0.0.2 → engin-0.0.4}/src/engin/_dependency.py +14 -16
- engin-0.0.4/src/engin/_engin.py +203 -0
- engin-0.0.4/src/engin/_lifecycle.py +38 -0
- {engin-0.0.2 → engin-0.0.4}/src/engin/ext/asgi.py +7 -7
- {engin-0.0.2 → engin-0.0.4}/src/engin/ext/fastapi.py +5 -9
- engin-0.0.4/tests/acceptance/test_error_in_shutdown.py +50 -0
- engin-0.0.4/tests/acceptance/test_error_in_start_up.py +45 -0
- engin-0.0.4/tests/conftest.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/tests/deps.py +2 -2
- {engin-0.0.2 → engin-0.0.4}/tests/test_engin.py +55 -1
- engin-0.0.4/uv.lock +1069 -0
- engin-0.0.2/.github/workflows/check.yaml +0 -30
- engin-0.0.2/CHANGELOG.md +0 -19
- engin-0.0.2/PKG-INFO +0 -56
- engin-0.0.2/README.md +0 -48
- engin-0.0.2/pyproject.toml +0 -39
- engin-0.0.2/src/engin/_engin.py +0 -88
- engin-0.0.2/src/engin/_lifecycle.py +0 -21
- engin-0.0.2/uv.lock +0 -482
- {engin-0.0.2 → engin-0.0.4}/.gitignore +0 -0
- {engin-0.0.2 → engin-0.0.4}/LICENSE +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/db/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/db/adapaters/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/db/block.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/db/ports.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/common/starlette/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/features/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/features/cats/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/features/cats/api/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/features/cats/block.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/asgi/features/cats/domain.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/cats/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/cats/adapters/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/cats/adapters/repository.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/cats/block.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/cats/domain.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/fastapi/routes/cats/ports.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/examples/simple/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/src/engin/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/src/engin/_exceptions.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/src/engin/_type_utils.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/src/engin/ext/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/src/engin/py.typed +0 -0
- {engin-0.0.2 → engin-0.0.4}/tests/__init__.py +0 -0
- /engin-0.0.2/tests/conftest.py → /engin-0.0.4/tests/acceptance/__init__.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/tests/test_assembler.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/tests/test_dependencies.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/tests/test_modules.py +0 -0
- {engin-0.0.2 → engin-0.0.4}/tests/test_utils.py +0 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
name: Check
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
|
6
|
+
jobs:
|
7
|
+
check:
|
8
|
+
name: python
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
|
11
|
+
permissions:
|
12
|
+
id-token: write
|
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 --all-extras --dev
|
30
|
+
|
31
|
+
- name: Quality
|
32
|
+
run: uv run poe check
|
33
|
+
|
34
|
+
- name: Test
|
35
|
+
run: uv run poe test
|
@@ -2,6 +2,8 @@ name: Publish
|
|
2
2
|
|
3
3
|
on:
|
4
4
|
workflow_dispatch:
|
5
|
+
release:
|
6
|
+
types: [created]
|
5
7
|
|
6
8
|
jobs:
|
7
9
|
publish-to-pypi:
|
@@ -15,10 +17,15 @@ jobs:
|
|
15
17
|
- uses: actions/checkout@v4
|
16
18
|
|
17
19
|
- name: Install uv
|
18
|
-
uses: astral-sh/setup-uv@
|
20
|
+
uses: astral-sh/setup-uv@v5
|
21
|
+
with:
|
22
|
+
enable-cache: true
|
23
|
+
cache-dependency-glob: "uv.lock"
|
19
24
|
|
20
25
|
- name: Set up Python
|
21
|
-
|
26
|
+
uses: actions/setup-python@v5
|
27
|
+
with:
|
28
|
+
python-version-file: "pyproject.toml"
|
22
29
|
|
23
30
|
- name: Build the Project
|
24
31
|
run: uv build
|
@@ -0,0 +1,25 @@
|
|
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
|
engin-0.0.4/CHANGELOG.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
|
9
|
+
## [0.0.4] - 2025-01-27
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
|
13
|
+
- Invocations, startups tasks and shutdown tasks are now all run sequentially.
|
14
|
+
- Improved error handling, if an Invocation errors, or a Lifecycle startup tasks errors
|
15
|
+
then the Engin will exit. Whilst errors in shutdown tasks are logged and ignored.
|
16
|
+
- Improved error messaging when Invocations or Lifecycle tasks error.
|
17
|
+
- Removed non-public methods from the Lifecycle class, and renamed `register_context` to
|
18
|
+
`append`.
|
19
|
+
|
20
|
+
|
21
|
+
## [0.0.3] - 2025-01-15
|
22
|
+
|
23
|
+
### Added
|
24
|
+
|
25
|
+
- Blocks can now provide options via the `options` class variable. This allows packaged
|
26
|
+
Blocks to easily expose Providers and Invocations as normal functions whilst allowing
|
27
|
+
them to be part of a Block as well. This makes usage of the Block optional which makes
|
28
|
+
it more flexible for end users.
|
29
|
+
- Added missing type hints and enabled mypy strict mode.
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
|
33
|
+
- Engin now performs Lifecycle shutdown.
|
34
|
+
|
35
|
+
|
36
|
+
## [0.0.2] - 2025-01-10
|
37
|
+
|
38
|
+
### Added
|
39
|
+
|
40
|
+
- The `ext` sub-package is now explicitly exported in the package `__init__.py`
|
41
|
+
|
42
|
+
|
43
|
+
## [0.0.1] - 2024-12-12
|
44
|
+
|
45
|
+
### Added
|
46
|
+
|
47
|
+
- Initial release
|
engin-0.0.4/PKG-INFO
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: engin
|
3
|
+
Version: 0.0.4
|
4
|
+
Summary: An async-first modular application framework
|
5
|
+
License-File: LICENSE
|
6
|
+
Requires-Python: >=3.10
|
7
|
+
Description-Content-Type: text/markdown
|
8
|
+
|
9
|
+
# Engin 🏎️
|
10
|
+
|
11
|
+
Engin is a zero-dependency application framework for modern Python.
|
12
|
+
|
13
|
+
**Documentation**: https://engin.readthedocs.io/
|
14
|
+
|
15
|
+
## Features ✨
|
16
|
+
|
17
|
+
- **Dependency Injection** - Engin includes a fully-featured Dependency Injection system,
|
18
|
+
powered by type hints.
|
19
|
+
- **Lifecycle Management** - Engin provides a simple & portable approach for attaching
|
20
|
+
startup and shutdown tasks to the application's lifecycle.
|
21
|
+
- **Code Reuse** - Engin's modular components, called Blocks, work great as distributed
|
22
|
+
packages allowing zero boiler-plate code reuse across multiple applications. Perfect for
|
23
|
+
maintaining many services across your organisation.
|
24
|
+
- **Ecosystem Compatability** - Engin ships with integrations for popular frameworks that
|
25
|
+
provide their own Dependency Injection, for example FastAPI, allowing you to integrate
|
26
|
+
Engin into existing code bases incrementally.
|
27
|
+
- **Async Native**: Engin is an async framework, meaning first class support for async
|
28
|
+
dependencies. However Engin will happily run synchronous code as well.
|
29
|
+
|
30
|
+
## Installation
|
31
|
+
|
32
|
+
Engin is available on PyPI, install using your favourite dependency manager:
|
33
|
+
|
34
|
+
- **pip**:`pip install engin`
|
35
|
+
- **poetry**: `poetry add engin`
|
36
|
+
- **uv**: `uv add engin`
|
37
|
+
|
38
|
+
## Getting Started
|
39
|
+
|
40
|
+
A minimal example:
|
41
|
+
|
42
|
+
```python
|
43
|
+
import asyncio
|
44
|
+
|
45
|
+
from httpx import AsyncClient
|
46
|
+
|
47
|
+
from engin import Engin, Invoke, Provide
|
48
|
+
|
49
|
+
|
50
|
+
def httpx_client() -> AsyncClient:
|
51
|
+
return AsyncClient()
|
52
|
+
|
53
|
+
|
54
|
+
async def main(http_client: AsyncClient) -> None:
|
55
|
+
print(await http_client.get("https://httpbin.org/get"))
|
56
|
+
|
57
|
+
engin = Engin(Provide(httpx_client), Invoke(main))
|
58
|
+
|
59
|
+
asyncio.run(engin.run())
|
60
|
+
```
|
61
|
+
|
engin-0.0.4/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Engin 🏎️
|
2
|
+
|
3
|
+
Engin is a zero-dependency application framework for modern Python.
|
4
|
+
|
5
|
+
**Documentation**: https://engin.readthedocs.io/
|
6
|
+
|
7
|
+
## Features ✨
|
8
|
+
|
9
|
+
- **Dependency Injection** - Engin includes a fully-featured Dependency Injection system,
|
10
|
+
powered by type hints.
|
11
|
+
- **Lifecycle Management** - Engin provides a simple & portable approach for attaching
|
12
|
+
startup and shutdown tasks to the application's lifecycle.
|
13
|
+
- **Code Reuse** - Engin's modular components, called Blocks, work great as distributed
|
14
|
+
packages allowing zero boiler-plate code reuse across multiple applications. Perfect for
|
15
|
+
maintaining many services across your organisation.
|
16
|
+
- **Ecosystem Compatability** - Engin ships with integrations for popular frameworks that
|
17
|
+
provide their own Dependency Injection, for example FastAPI, allowing you to integrate
|
18
|
+
Engin into existing code bases incrementally.
|
19
|
+
- **Async Native**: Engin is an async framework, meaning first class support for async
|
20
|
+
dependencies. However Engin will happily run synchronous code as well.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Engin is available on PyPI, install using your favourite dependency manager:
|
25
|
+
|
26
|
+
- **pip**:`pip install engin`
|
27
|
+
- **poetry**: `poetry add engin`
|
28
|
+
- **uv**: `uv add engin`
|
29
|
+
|
30
|
+
## Getting Started
|
31
|
+
|
32
|
+
A minimal example:
|
33
|
+
|
34
|
+
```python
|
35
|
+
import asyncio
|
36
|
+
|
37
|
+
from httpx import AsyncClient
|
38
|
+
|
39
|
+
from engin import Engin, Invoke, Provide
|
40
|
+
|
41
|
+
|
42
|
+
def httpx_client() -> AsyncClient:
|
43
|
+
return AsyncClient()
|
44
|
+
|
45
|
+
|
46
|
+
async def main(http_client: AsyncClient) -> None:
|
47
|
+
print(await http_client.get("https://httpbin.org/get"))
|
48
|
+
|
49
|
+
engin = Engin(Provide(httpx_client), Invoke(main))
|
50
|
+
|
51
|
+
asyncio.run(engin.run())
|
52
|
+
```
|
53
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
::: engin.Engin
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Introduction
|
2
|
+
|
3
|
+
Engin is a zero-dependency application framework for modern Python.
|
4
|
+
|
5
|
+
Engin is inspired by [Uber's Fx framework for Go](https://github.com/uber-go/fx) and the
|
6
|
+
[Injector framework for Python](https://github.com/python-injector/injector).
|
7
|
+
|
8
|
+
## Why use Engin?
|
9
|
+
|
10
|
+
- **Dependency Injection** - Engin includes a fully-featured Dependency Injection system,
|
11
|
+
powered by type hints.
|
12
|
+
- **Lifecycle Management** - Engin provides a simple & portable approach for attaching
|
13
|
+
startup and shutdown tasks to the application's lifecycle.
|
14
|
+
- **Code Reuse** - Engin's modular components, called Blocks, work great as distributed
|
15
|
+
packages allowing zero boiler-plate code reuse across multiple applications. Perfect for
|
16
|
+
maintaining many services across your organisation.
|
17
|
+
- **Ecosystem Compatability** - Engin ships with integrations for popular frameworks that
|
18
|
+
provide their own Dependency Injection, for example FastAPI, allowing you to integrate
|
19
|
+
Engin into existing code bases incrementally.
|
20
|
+
- **Async Native**: Engin is an async framework, meaning first class support for async
|
21
|
+
dependencies. However Engin will happily run synchronous code as well.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Engin is available on PyPI, install using your favourite dependency manager:
|
26
|
+
|
27
|
+
- **pip**:`pip install engin`
|
28
|
+
- **poetry**: `poetry add engin`
|
29
|
+
- **uv**: `uv add engin`
|
@@ -0,0 +1,32 @@
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function(event) {
|
2
|
+
// Trigger Read the Docs' search addon instead of Material MkDocs default
|
3
|
+
document.querySelector(".md-search__input").addEventListener("focus", (e) => {
|
4
|
+
const event = new CustomEvent("readthedocs-search-show");
|
5
|
+
document.dispatchEvent(event);
|
6
|
+
});
|
7
|
+
});
|
8
|
+
|
9
|
+
// Use CustomEvent to generate the version selector
|
10
|
+
document.addEventListener(
|
11
|
+
"readthedocs-addons-data-ready",
|
12
|
+
function (event) {
|
13
|
+
const config = event.detail.data();
|
14
|
+
const versioning = `
|
15
|
+
<div class="md-version">
|
16
|
+
<button class="md-version__current" aria-label="Select version">
|
17
|
+
${config.versions.current.slug}
|
18
|
+
</button>
|
19
|
+
|
20
|
+
<ul class="md-version__list">
|
21
|
+
${ config.versions.active.map(
|
22
|
+
(version) => `
|
23
|
+
<li class="md-version__item">
|
24
|
+
<a href="${ version.urls.documentation }" class="md-version__link">
|
25
|
+
${ version.slug }
|
26
|
+
</a>
|
27
|
+
</li>`).join("\n")}
|
28
|
+
</ul>
|
29
|
+
</div>`;
|
30
|
+
|
31
|
+
document.querySelector(".md-header__topic").insertAdjacentHTML("beforeend", versioning);
|
32
|
+
});
|
@@ -5,8 +5,8 @@ from starlette.requests import Request
|
|
5
5
|
from starlette.responses import JSONResponse, Response
|
6
6
|
from starlette.routing import Mount, Route
|
7
7
|
|
8
|
-
from engin import Block,
|
9
|
-
from engin.
|
8
|
+
from engin import Block, provide
|
9
|
+
from engin.ext.asgi import ASGIType
|
10
10
|
|
11
11
|
|
12
12
|
class AppConfig(BaseSettings):
|
@@ -1,5 +1,4 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from collections.abc import Iterable
|
3
2
|
from typing import ClassVar
|
4
3
|
|
5
4
|
from starlette.exceptions import HTTPException
|
@@ -14,7 +13,7 @@ class Endpoint(ABC):
|
|
14
13
|
HTTPEndpoint does not allow you to control class initialisation.
|
15
14
|
"""
|
16
15
|
|
17
|
-
ALLOWED_METHODS: ClassVar[
|
16
|
+
ALLOWED_METHODS: ClassVar[list[str]] = [
|
18
17
|
"GET",
|
19
18
|
"HEAD",
|
20
19
|
"POST",
|
@@ -22,7 +21,7 @@ class Endpoint(ABC):
|
|
22
21
|
"PATCH",
|
23
22
|
"DELETE",
|
24
23
|
"OPTIONS",
|
25
|
-
|
24
|
+
]
|
26
25
|
|
27
26
|
@abstractmethod
|
28
27
|
async def exec(self, request: Request) -> Response: ...
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
from typing import ClassVar
|
2
2
|
|
3
3
|
from starlette.requests import Request
|
4
4
|
from starlette.responses import JSONResponse, Response
|
@@ -9,7 +9,7 @@ from examples.asgi.features.cats.domain import Cat
|
|
9
9
|
|
10
10
|
|
11
11
|
class GetCatEndpoint(Endpoint):
|
12
|
-
ALLOWED_METHODS =
|
12
|
+
ALLOWED_METHODS: ClassVar[list[str]] = ["GET"]
|
13
13
|
|
14
14
|
def __init__(self, db: DatabaseInterface) -> None:
|
15
15
|
self._db = db
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from typing import ClassVar
|
2
|
+
|
1
3
|
from pydantic import ValidationError
|
2
4
|
from starlette.requests import Request
|
3
5
|
from starlette.responses import Response
|
@@ -8,7 +10,7 @@ from examples.asgi.features.cats.domain import Cat
|
|
8
10
|
|
9
11
|
|
10
12
|
class PostCatEndpoint(Endpoint):
|
11
|
-
ALLOWED_METHODS =
|
13
|
+
ALLOWED_METHODS: ClassVar[list[str]] = ["POST"]
|
12
14
|
|
13
15
|
def __init__(self, db: DatabaseInterface) -> None:
|
14
16
|
self._db = db
|
@@ -3,7 +3,7 @@ import logging
|
|
3
3
|
import uvicorn
|
4
4
|
|
5
5
|
from engin import Supply
|
6
|
-
from engin.
|
6
|
+
from engin.ext.asgi import ASGIEngin
|
7
7
|
from examples.asgi.app import AppBlock, AppConfig
|
8
8
|
from examples.asgi.common.db.block import DatabaseBlock
|
9
9
|
from examples.asgi.features.cats.block import CatBlock
|
@@ -3,7 +3,7 @@ import logging
|
|
3
3
|
import uvicorn
|
4
4
|
|
5
5
|
from engin import Supply
|
6
|
-
from engin.
|
6
|
+
from engin.ext.fastapi import FastAPIEngin
|
7
7
|
from examples.fastapi.app import AppBlock, AppConfig
|
8
8
|
from examples.fastapi.routes.cats.block import CatBlock
|
9
9
|
|
@@ -26,17 +26,17 @@ class CatPostModel(BaseModel):
|
|
26
26
|
|
27
27
|
|
28
28
|
@router.post("/")
|
29
|
-
async def
|
29
|
+
async def post_cat(
|
30
30
|
cat: CatPostModel,
|
31
31
|
repository: Annotated[CatRepository, Inject(CatRepository)],
|
32
32
|
) -> int:
|
33
33
|
cat_id = repository.next_id()
|
34
|
-
|
34
|
+
cat_domain = Cat(
|
35
35
|
id=cat_id,
|
36
36
|
name=cat.name,
|
37
37
|
personality=cat.personality,
|
38
38
|
age=cat.age,
|
39
39
|
breed=cat.breed,
|
40
40
|
)
|
41
|
-
repository.set(cat=
|
41
|
+
repository.set(cat=cat_domain)
|
42
42
|
return cat_id
|
engin-0.0.4/mkdocs.yaml
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
site_name: Engin
|
2
|
+
site_description: A lightweight application framework for modern Python.
|
3
|
+
site_url: !ENV READTHEDOCS_CANONICAL_URL
|
4
|
+
|
5
|
+
theme:
|
6
|
+
name: 'material'
|
7
|
+
custom_dir: 'docs/overrides'
|
8
|
+
palette:
|
9
|
+
- scheme: 'default'
|
10
|
+
media: '(prefers-color-scheme: light)'
|
11
|
+
toggle:
|
12
|
+
icon: 'material/lightbulb'
|
13
|
+
name: "Switch to dark mode"
|
14
|
+
- scheme: 'slate'
|
15
|
+
media: '(prefers-color-scheme: dark)'
|
16
|
+
primary: 'blue'
|
17
|
+
toggle:
|
18
|
+
icon: 'material/lightbulb-outline'
|
19
|
+
name: 'Switch to light mode'
|
20
|
+
|
21
|
+
repo_name: invokermain/engin
|
22
|
+
repo_url: https://github.com/invokermain/engin/
|
23
|
+
edit_uri: ""
|
24
|
+
|
25
|
+
extra_javascript:
|
26
|
+
- js/readthedocs.js
|
27
|
+
|
28
|
+
plugins:
|
29
|
+
- mkdocstrings:
|
30
|
+
default_handler: python
|
31
|
+
handlers:
|
32
|
+
python:
|
33
|
+
paths: [.]
|
34
|
+
options:
|
35
|
+
members_order: source
|
36
|
+
separate_signature: true
|
37
|
+
filters: ["!^_"]
|
38
|
+
docstring_options:
|
39
|
+
ignore_init_summary: true
|
40
|
+
merge_init_into_class: true
|
41
|
+
show_signature_annotations: true
|
42
|
+
signature_crossrefs: true
|
43
|
+
import:
|
44
|
+
- url: https://docs.python.org/3/objects.inv
|
45
|
+
domains: [py, std]
|
46
|
+
|
47
|
+
watch:
|
48
|
+
- src
|
49
|
+
|
50
|
+
markdown_extensions:
|
51
|
+
- pymdownx.highlight:
|
52
|
+
anchor_linenums: true
|
53
|
+
line_spans: __span
|
54
|
+
pygments_lang_class: true
|
55
|
+
- pymdownx.inlinehilite
|
56
|
+
- pymdownx.snippets
|
57
|
+
- pymdownx.superfences
|
@@ -0,0 +1,91 @@
|
|
1
|
+
[project]
|
2
|
+
name = "engin"
|
3
|
+
version = "0.0.4"
|
4
|
+
description = "An async-first modular application framework"
|
5
|
+
readme = "README.md"
|
6
|
+
requires-python = ">=3.10"
|
7
|
+
dependencies = []
|
8
|
+
|
9
|
+
[build-system]
|
10
|
+
requires = ["hatchling"]
|
11
|
+
build-backend = "hatchling.build"
|
12
|
+
|
13
|
+
|
14
|
+
[tool.uv]
|
15
|
+
dev-dependencies = [
|
16
|
+
"fastapi>=0.115.6",
|
17
|
+
"httpx>=0.27.2",
|
18
|
+
"mypy>=1",
|
19
|
+
"poethepoet>=0.32.1",
|
20
|
+
"pydantic-settings>=2.6.0",
|
21
|
+
"pydantic>=2.9.2",
|
22
|
+
"pytest-asyncio>=0.24.0",
|
23
|
+
"pytest>=8",
|
24
|
+
"ruff>=0",
|
25
|
+
"starlette>=0.39.2",
|
26
|
+
"uvicorn>=0.31.1",
|
27
|
+
]
|
28
|
+
|
29
|
+
|
30
|
+
[dependency-groups]
|
31
|
+
docs = [
|
32
|
+
"mkdocs-material>=9.5.50",
|
33
|
+
"mkdocstrings[python]>=0.27.0",
|
34
|
+
]
|
35
|
+
|
36
|
+
|
37
|
+
[tool.ruff]
|
38
|
+
line-length = 95
|
39
|
+
target-version = "py310"
|
40
|
+
|
41
|
+
|
42
|
+
[tool.ruff.lint]
|
43
|
+
select = [
|
44
|
+
"ANN", "ASYNC", "B", "C4", "DTZ", "E", "F", "I", "INP", "LOG", "PERF", "PIE", "PT",
|
45
|
+
"PTH", "Q", "UP", "R", "RUF", "S", "SIM", "TCH", "T20", "W"
|
46
|
+
]
|
47
|
+
ignore = [
|
48
|
+
"ANN401",
|
49
|
+
"PERF203", # `try`-`except` within a loop incurs performance overhead
|
50
|
+
"RET505", # Unnecessary `else` after `return` statement
|
51
|
+
"RET506", # Unnecessary `else` after `raise` statement
|
52
|
+
]
|
53
|
+
|
54
|
+
[tool.ruff.lint.per-file-ignores]
|
55
|
+
"**/src/*" = ["PT"]
|
56
|
+
"**/tests/*" = ["S", "ANN"]
|
57
|
+
"**/examples/*" = ["T201"]
|
58
|
+
|
59
|
+
|
60
|
+
[tool.pytest.ini_options]
|
61
|
+
log_cli = true
|
62
|
+
log_cli_level = "DEBUG"
|
63
|
+
asyncio_mode = "auto"
|
64
|
+
asyncio_default_fixture_loop_scope = "session"
|
65
|
+
|
66
|
+
|
67
|
+
[tool.mypy]
|
68
|
+
strict = true
|
69
|
+
disable_error_code = [
|
70
|
+
"type-arg", # allow generic types without type arguments
|
71
|
+
]
|
72
|
+
|
73
|
+
|
74
|
+
[tool.poe.tasks]
|
75
|
+
format.default_item_type = "cmd"
|
76
|
+
format.sequence = [
|
77
|
+
"ruff format src tests examples",
|
78
|
+
"ruff check --select I --fix src tests examples"
|
79
|
+
]
|
80
|
+
|
81
|
+
check.default_item_type = "cmd"
|
82
|
+
check.sequence = [
|
83
|
+
"ruff format --check src tests examples",
|
84
|
+
"ruff check src tests examples",
|
85
|
+
"mypy src examples",
|
86
|
+
]
|
87
|
+
|
88
|
+
fix.default_item_type = "cmd"
|
89
|
+
fix.sequence = ["ruff check src tests --fix"]
|
90
|
+
|
91
|
+
test = "pytest -s tests"
|
@@ -4,7 +4,7 @@ from collections import defaultdict
|
|
4
4
|
from collections.abc import Collection, Iterable
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from inspect import BoundArguments, Signature
|
7
|
-
from typing import Any, Generic, TypeVar
|
7
|
+
from typing import Any, Generic, TypeVar, cast
|
8
8
|
|
9
9
|
from engin._dependency import Dependency, Provide, Supply
|
10
10
|
from engin._exceptions import AssemblyError
|
@@ -113,7 +113,7 @@ class Assembler:
|
|
113
113
|
async def get(self, type_: type[T]) -> T:
|
114
114
|
type_id = type_id_of(type_)
|
115
115
|
if type_id in self._dependencies:
|
116
|
-
return self._dependencies[type_id]
|
116
|
+
return cast(T, self._dependencies[type_id])
|
117
117
|
if type_id.multi:
|
118
118
|
out = []
|
119
119
|
for provider in self._multiproviders[type_id]:
|
@@ -1,29 +1,29 @@
|
|
1
1
|
import inspect
|
2
2
|
from collections.abc import Iterable, Iterator
|
3
|
+
from typing import ClassVar
|
3
4
|
|
4
5
|
from engin._dependency import Func, Invoke, Provide
|
5
6
|
|
6
7
|
|
7
8
|
def provide(func: Func) -> Func:
|
8
|
-
func._opt = Provide(func) # type: ignore[
|
9
|
+
func._opt = Provide(func) # type: ignore[attr-defined]
|
9
10
|
return func
|
10
11
|
|
11
12
|
|
12
13
|
def invoke(func: Func) -> Func:
|
13
|
-
func._opt = Invoke(func) # type: ignore[
|
14
|
+
func._opt = Invoke(func) # type: ignore[attr-defined]
|
14
15
|
return func
|
15
16
|
|
16
17
|
|
17
18
|
class Block(Iterable[Provide | Invoke]):
|
18
|
-
|
19
|
-
_options: list[Provide | Invoke]
|
19
|
+
options: ClassVar[list[Provide | Invoke]] = []
|
20
20
|
|
21
21
|
def __init__(self, /, block_name: str | None = None) -> None:
|
22
|
-
self._options: list[Provide | Invoke] = []
|
22
|
+
self._options: list[Provide | Invoke] = self.options[:]
|
23
23
|
self._name = block_name or f"{type(self).__name__}"
|
24
24
|
for _, method in inspect.getmembers(self):
|
25
25
|
if opt := getattr(method, "_opt", None):
|
26
|
-
if not isinstance(opt,
|
26
|
+
if not isinstance(opt, Provide | Invoke):
|
27
27
|
raise RuntimeError("Block option is not an instance of Provide or Invoke")
|
28
28
|
opt.set_block_name(self._name)
|
29
29
|
self._options.append(opt)
|