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.
@@ -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,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.pyo
4
+ dist/
5
+ *.egg-info/
6
+ .eggs/
7
+ build/
8
+ .venv/
9
+ .env
10
+ .idea/
11
+ .vscode/
12
+ *.iml
13
+ .DS_Store
14
+ Thumbs.db
15
+ .ruff_cache/
16
+ .pytest_cache/
17
+ uv.lock
@@ -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
@@ -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.