nexus-kit 0.4.0__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.
- nexus_kit-0.4.0/.ai/guide.md +194 -0
- nexus_kit-0.4.0/.github/workflows/ci.yml +20 -0
- nexus_kit-0.4.0/.github/workflows/publish.yml +18 -0
- nexus_kit-0.4.0/.gitignore +17 -0
- nexus_kit-0.4.0/CHANGELOG.md +25 -0
- nexus_kit-0.4.0/LICENSE +21 -0
- nexus_kit-0.4.0/PKG-INFO +425 -0
- nexus_kit-0.4.0/README.md +403 -0
- nexus_kit-0.4.0/pyproject.toml +41 -0
- nexus_kit-0.4.0/src/nexus_kit/__init__.py +11 -0
- nexus_kit-0.4.0/src/nexus_kit/cli.py +295 -0
- nexus_kit-0.4.0/src/nexus_kit/impl/__init__.py +4 -0
- nexus_kit-0.4.0/src/nexus_kit/impl/container_injector.py +25 -0
- nexus_kit-0.4.0/src/nexus_kit/impl/service_runner.py +121 -0
- nexus_kit-0.4.0/src/nexus_kit/interfaces/__init__.py +11 -0
- nexus_kit-0.4.0/src/nexus_kit/interfaces/application.py +16 -0
- nexus_kit-0.4.0/src/nexus_kit/interfaces/container.py +13 -0
- nexus_kit-0.4.0/src/nexus_kit/interfaces/environment.py +10 -0
- nexus_kit-0.4.0/src/nexus_kit/interfaces/service.py +19 -0
- nexus_kit-0.4.0/src/nexus_kit/logging/__init__.py +5 -0
- nexus_kit-0.4.0/src/nexus_kit/logging/log_formatter.py +24 -0
- nexus_kit-0.4.0/src/nexus_kit/logging/named_logger.py +47 -0
- nexus_kit-0.4.0/src/nexus_kit/logging/stdout_handler.py +17 -0
- nexus_kit-0.4.0/src/nexus_kit/root.py +31 -0
- nexus_kit-0.4.0/tests/test_cli.py +57 -0
- nexus_kit-0.4.0/tests/test_environment.py +33 -0
- nexus_kit-0.4.0/tests/test_root.py +32 -0
- nexus_kit-0.4.0/tests/test_service_runner.py +187 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Nexus — AI Agent Guide
|
|
2
|
+
|
|
3
|
+
Context for AI assistants working in projects built with nexus.
|
|
4
|
+
|
|
5
|
+
## What nexus is
|
|
6
|
+
|
|
7
|
+
Nexus is a minimal Python application framework. It provides:
|
|
8
|
+
|
|
9
|
+
- Interfaces (abstract contracts) for bootstrapping an application
|
|
10
|
+
- `ContainerInjector` — a concrete DI container implementation (thin wrapper over the `injector` library)
|
|
11
|
+
- `ServiceInterface` / `ServiceRunner` — lifecycle: ordered start, guaranteed reverse-order stop of long-lived services (sync and async)
|
|
12
|
+
- `Root` — a path utility that works in dev and PyInstaller-bundled environments
|
|
13
|
+
- A logging base (`NamedLogger` / `StdoutHandler` / `LogFormatter`), DI-injectable
|
|
14
|
+
- A scaffolding CLI: `nexus-kit new <app-name>`
|
|
15
|
+
|
|
16
|
+
Nexus does NOT contain domain logic, UI code, or data access. It is infrastructure only.
|
|
17
|
+
|
|
18
|
+
**Gotcha:** `@singleton`, `@inject` and `Injector` come from the third-party `injector`
|
|
19
|
+
package, **not** from Nexus — import them `from injector import inject, singleton`.
|
|
20
|
+
Nexus never re-exports them.
|
|
21
|
+
|
|
22
|
+
**Dependencies:** `injector` and `pydantic-settings` are core dependencies of nexus —
|
|
23
|
+
no extras, everything works out of the box.
|
|
24
|
+
|
|
25
|
+
## Package layout
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
nexus_kit/
|
|
29
|
+
├── interfaces/ # abstract contracts — always import from here first
|
|
30
|
+
│ ├── application.py # ApplicationInterface
|
|
31
|
+
│ ├── container.py # ContainerInterface
|
|
32
|
+
│ └── environment.py # EnvironmentInterface
|
|
33
|
+
├── impl/ # concrete implementations — import explicitly
|
|
34
|
+
│ └── container_injector.py
|
|
35
|
+
├── logging/ # DI-injectable logging base
|
|
36
|
+
│ ├── named_logger.py # NamedLogger
|
|
37
|
+
│ ├── stdout_handler.py# StdoutHandler
|
|
38
|
+
│ └── log_formatter.py # LogFormatter
|
|
39
|
+
├── cli.py # `nexus-kit new` scaffolder
|
|
40
|
+
└── root.py # Root utility
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Import conventions:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from nexus_kit.interfaces import ApplicationInterface, ContainerInterface, EnvironmentInterface
|
|
47
|
+
from nexus_kit.impl import ContainerInjector # explicit — this is a concrete choice
|
|
48
|
+
from nexus_kit import Root
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Bootstrap pattern
|
|
52
|
+
|
|
53
|
+
Every nexus-based app follows this sequence in `main.py`:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
env = Environment(Root.external(".env")) # 1. load config
|
|
57
|
+
container = ContainerInjector(DI_CONFIG) # 2. wire dependencies
|
|
58
|
+
Application(env, container).run() # 3. start app
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`Environment` extends `EnvironmentInterface`, `Application` extends `ApplicationInterface`.
|
|
62
|
+
`DI_CONFIG` lives in `app/config/di.py` — it is the composition root.
|
|
63
|
+
|
|
64
|
+
## How to extend ApplicationInterface
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from nexus_kit.interfaces import ApplicationInterface, ContainerInterface, EnvironmentInterface
|
|
68
|
+
|
|
69
|
+
class Application(ApplicationInterface):
|
|
70
|
+
def __init__(self, environment: EnvironmentInterface, container: ContainerInterface) -> None:
|
|
71
|
+
self._env = environment
|
|
72
|
+
self._container = container
|
|
73
|
+
# resolve top-level services here:
|
|
74
|
+
# self._service = container.get(SomeServiceInterface)
|
|
75
|
+
|
|
76
|
+
def run(self) -> None:
|
|
77
|
+
... # start event loop, server, CLI, etc.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## How to extend EnvironmentInterface
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from pathlib import Path
|
|
84
|
+
from injector import singleton
|
|
85
|
+
from nexus_kit.interfaces import EnvironmentInterface
|
|
86
|
+
|
|
87
|
+
@singleton
|
|
88
|
+
class Environment(EnvironmentInterface):
|
|
89
|
+
DATABASE_URL: str
|
|
90
|
+
DEBUG: bool = False
|
|
91
|
+
MAX_WORKERS: int = 4
|
|
92
|
+
|
|
93
|
+
def __init__(self, env_path: Path) -> None:
|
|
94
|
+
super().__init__(_env_file=env_path)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Pydantic BaseSettings rules apply: values come from environment variables and the `.env` file.
|
|
98
|
+
Pass the `.env` path via `Root.external(".env")`.
|
|
99
|
+
|
|
100
|
+
## How to register services in DI
|
|
101
|
+
|
|
102
|
+
Composition root lives in `app/config/di.py`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from app.services.greeter import Greeter
|
|
106
|
+
from app.services.greeter_interface import GreeterInterface
|
|
107
|
+
|
|
108
|
+
DI_CONFIG = {
|
|
109
|
+
GreeterInterface: Greeter,
|
|
110
|
+
# Interface: ConcreteImplementation
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Mark long-lived services with `@singleton`, use `@inject` for constructor injection:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from injector import inject, singleton
|
|
118
|
+
from app.services.dep_interface import DepInterface
|
|
119
|
+
|
|
120
|
+
@singleton
|
|
121
|
+
class Greeter(GreeterInterface):
|
|
122
|
+
@inject
|
|
123
|
+
def __init__(self, dep: DepInterface) -> None:
|
|
124
|
+
self._dep = dep
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Logging
|
|
128
|
+
|
|
129
|
+
Named logger channels are DI-injectable types, not `getLogger(str)` lookups. Subclass
|
|
130
|
+
`NamedLogger`, set a class-level `name`; inject the subclass by type. Change the line
|
|
131
|
+
format by rebinding `LogFormatter` in `DI_CONFIG` (independent of the destination handler).
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from injector import singleton
|
|
135
|
+
from nexus_kit.logging import NamedLogger, LogFormatter
|
|
136
|
+
|
|
137
|
+
@singleton
|
|
138
|
+
class MainLogger(NamedLogger):
|
|
139
|
+
name = "app.main"
|
|
140
|
+
|
|
141
|
+
class JsonFormatter(LogFormatter):
|
|
142
|
+
def format(self, record): ... # rebind in DI_CONFIG: {LogFormatter: JsonFormatter}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Lifecycle
|
|
146
|
+
|
|
147
|
+
Long-lived services implement `ServiceInterface` (`start()`/`stop()`, sync or async,
|
|
148
|
+
`stop()` idempotent). `Application` lists them in startup order and wraps the app body
|
|
149
|
+
in a `ServiceRunner` context:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from nexus_kit.impl import ServiceRunner
|
|
153
|
+
|
|
154
|
+
SERVICES = [Database, WebhookDispatcher, HttpApiService] # startup order
|
|
155
|
+
|
|
156
|
+
# async app:
|
|
157
|
+
async with ServiceRunner(self._container, SERVICES):
|
|
158
|
+
await self._container.get(HttpApiService).wait()
|
|
159
|
+
# sync app (pygame, Qt):
|
|
160
|
+
with ServiceRunner(self._container, SERVICES):
|
|
161
|
+
self._main_loop()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Guarantees: reverse-order stop on any exit; crash-safe startup (a failed `start()`
|
|
165
|
+
rolls back the already-started services); a failing `stop()` is logged and teardown
|
|
166
|
+
continues; async stops are bounded by `stop_grace` (10s default). The runner installs
|
|
167
|
+
no signal handlers — exit is triggered by uvicorn, Qt `aboutToQuit`, or your own code.
|
|
168
|
+
|
|
169
|
+
## What nexus does NOT provide (you hand-roll these)
|
|
170
|
+
|
|
171
|
+
- No signal handling — `ServiceRunner` never grabs SIGINT/SIGTERM; wire the exit
|
|
172
|
+
trigger yourself (uvicorn's handlers, Qt `aboutToQuit`, own handler).
|
|
173
|
+
- No background-service / worker base class, no scheduling.
|
|
174
|
+
- No repository / persistence / DB layer.
|
|
175
|
+
- No testing helpers or fixtures.
|
|
176
|
+
- No HTTP/server/routing, retries, or middleware.
|
|
177
|
+
- Config is stock `pydantic-settings`; logging is a stdout stream handler only.
|
|
178
|
+
|
|
179
|
+
## Key conventions
|
|
180
|
+
|
|
181
|
+
- Abstract contracts have the `Interface` suffix: `GreeterInterface`, `UserRepositoryInterface`
|
|
182
|
+
- Implementations have no special suffix: `Greeter`, `UserRepository`
|
|
183
|
+
- One class per file; file named after the class in snake_case
|
|
184
|
+
- `@singleton` for long-lived managers and services
|
|
185
|
+
- Dynamic objects (created at runtime) use Factories: `WidgetFactoryInterface` → `WidgetFactory`
|
|
186
|
+
- Do not import concrete implementations outside of `di.py` (exceptions: tests, and
|
|
187
|
+
the `SERVICES` list in `Application` — both are part of the composition root)
|
|
188
|
+
|
|
189
|
+
## What NOT to do
|
|
190
|
+
|
|
191
|
+
- Do not put business logic in `Application.__init__` or `main.py`
|
|
192
|
+
- Do not make `ContainerInterface` globally accessible — pass it only to `Application`
|
|
193
|
+
- Do not register primitive types (str, int, bool) in DI unless they carry policy semantics
|
|
194
|
+
- Do not add `@inject` to `Application.__init__` — it is constructed manually in `main.py`
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest]
|
|
14
|
+
python: ["3.12", "3.13", "3.14"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
- run: uv sync --python ${{ matrix.python }}
|
|
20
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write # PyPI trusted publishing (OIDC), no tokens needed
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
- run: uv sync
|
|
16
|
+
- run: uv run pytest -q
|
|
17
|
+
- run: uv build
|
|
18
|
+
- run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.1]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `nexus.logging` — DI-injectable logging base (`NamedLogger`, `StdoutHandler`, `LogFormatter`). Present since the initial release but previously undocumented in this changelog.
|
|
8
|
+
- `nexus new <app-name>` now scaffolds an AI-ready project: a `CLAUDE.md` pointing to `.ai/`, and `.ai/nexus.md` (a compact, self-contained Nexus API reference) so any AI assistant picks up how to build on nexus without reading the framework source.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Refreshed `.ai/guide.md` (AI Agent Guide): package layout now lists `logging/` and `cli.py`; documents the `injector`-not-nexus import gotcha, the `nexus[pydantic]` extra requirement, and what nexus deliberately does NOT provide.
|
|
13
|
+
|
|
14
|
+
## [0.1.0] — 2025-07-02
|
|
15
|
+
|
|
16
|
+
Initial release.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `ApplicationInterface` — abstract contract for application bootstrap
|
|
21
|
+
- `ContainerInterface` — abstract contract for dependency injection container
|
|
22
|
+
- `EnvironmentInterface` — abstract base for typed configuration (Pydantic BaseSettings + singleton)
|
|
23
|
+
- `Root` — path utility for development and PyInstaller-bundled environments
|
|
24
|
+
- `ContainerInjector` — `ContainerInterface` implementation using the `injector` library
|
|
25
|
+
- `nexus new <app-name>` CLI command to scaffold a minimal working application
|
nexus_kit-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Astislav Bozhevolnov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|