nyxa 0.1.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.
Potentially problematic release.
This version of nyxa might be problematic. Click here for more details.
- nyxa-0.1.0/PKG-INFO +397 -0
- nyxa-0.1.0/README.md +365 -0
- nyxa-0.1.0/pyproject.toml +61 -0
- nyxa-0.1.0/pyproject.toml.orig +58 -0
- nyxa-0.1.0/src/nyxa/__init__.py +100 -0
- nyxa-0.1.0/src/nyxa/abort.py +105 -0
- nyxa-0.1.0/src/nyxa/app.py +85 -0
- nyxa-0.1.0/src/nyxa/auth_stubs.py +34 -0
- nyxa-0.1.0/src/nyxa/config.py +277 -0
- nyxa-0.1.0/src/nyxa/container.py +219 -0
- nyxa-0.1.0/src/nyxa/context.py +105 -0
- nyxa-0.1.0/src/nyxa/cors.py +81 -0
- nyxa-0.1.0/src/nyxa/env.py +97 -0
- nyxa-0.1.0/src/nyxa/errors.py +126 -0
- nyxa-0.1.0/src/nyxa/events.py +41 -0
- nyxa-0.1.0/src/nyxa/generate.py +76 -0
- nyxa-0.1.0/src/nyxa/helpers.py +175 -0
- nyxa-0.1.0/src/nyxa/init_cmd.py +754 -0
- nyxa-0.1.0/src/nyxa/list_cmd.py +78 -0
- nyxa-0.1.0/src/nyxa/logging_config.py +106 -0
- nyxa-0.1.0/src/nyxa/make.py +505 -0
- nyxa-0.1.0/src/nyxa/paths.py +40 -0
- nyxa-0.1.0/src/nyxa/providers.py +129 -0
- nyxa-0.1.0/src/nyxa/registry.py +166 -0
- nyxa-0.1.0/src/nyxa/requests.py +32 -0
- nyxa-0.1.0/src/nyxa/response.py +101 -0
- nyxa-0.1.0/src/nyxa/routing.py +418 -0
- nyxa-0.1.0/src/nyxa/templates.py +1005 -0
nyxa-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: nyxa
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Batteries-included DX for Python API backends on FastAPI — convention-based CLI and runtime bases
|
|
5
|
+
Keywords: fastapi,artisan,cli,scaffold,backend,api,nyxa
|
|
6
|
+
Author: Al-Amin Islam Nerob
|
|
7
|
+
Author-email: Al-Amin Islam Nerob <alamin@aincoder.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Framework :: FastAPI
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Dist: fastapi>=0.115.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
25
|
+
Requires-Dist: typer>=0.12.0
|
|
26
|
+
Requires-Dist: uvicorn>=0.30.0
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Project-URL: Homepage, https://nyxadev.com
|
|
29
|
+
Project-URL: Repository, https://github.com/NyxaDev/nyxa
|
|
30
|
+
Project-URL: Documentation, https://nyxadev.com
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# nyxa
|
|
34
|
+
|
|
35
|
+
Batteries-included DX for **Python API backends** on FastAPI.
|
|
36
|
+
|
|
37
|
+
**Nyxa** is pronounced *Nik-sah* (`/ˈnɪksə/`). Site: [nyxadev.com](https://nyxadev.com) · Repo: [NyxaDev/nyxa](https://github.com/NyxaDev/nyxa).
|
|
38
|
+
|
|
39
|
+
Nyxa gives you a convention-based CLI (`nyxa`, with an `artisan` alias), project layout, central `routes.py` + controllers, and small runtime bases (`AppError`, abort helpers, `register`, events). It is **backend/API only** — no server-side templates, Vite, or frontend stack.
|
|
40
|
+
|
|
41
|
+
> Long-term plans: see the workspace [`roadmap/`](../../roadmap/).
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv add nyxa
|
|
49
|
+
# or
|
|
50
|
+
pip install nyxa
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The import and CLI stay **`nyxa`**:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from nyxa import AppError, Route, register
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uvx --from nyxa nyxa new myapp
|
|
65
|
+
cd myapp
|
|
66
|
+
uv sync
|
|
67
|
+
uv run dev
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Interactive mode (TTY) prompts for database, auth, and optional packs. For CI:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
nyxa new myapp --database sqlite --auth none --no-input
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Cheat sheet: [`docs/cheat-sheet.md`](../../docs/cheat-sheet.md).
|
|
77
|
+
Adapter upgrades: [`docs/upgrading.md`](../../docs/upgrading.md).
|
|
78
|
+
|
|
79
|
+
### CORS and logging
|
|
80
|
+
|
|
81
|
+
Optional; off by default. Configure in `nyxa.toml` or env — applied inside `register()`:
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[cors]
|
|
85
|
+
origins = ["http://localhost:3000"]
|
|
86
|
+
|
|
87
|
+
[logging]
|
|
88
|
+
json = true
|
|
89
|
+
level = "INFO"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
CORS_ORIGINS=http://localhost:3000
|
|
94
|
+
NYXA_LOG_JSON=1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or call `configure_cors(app, origins=[...])` / `configure_logging()` manually.
|
|
98
|
+
|
|
99
|
+
Scaffold into the current directory:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
mkdir myapp && cd myapp
|
|
103
|
+
uvx --from nyxa nyxa init
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Monorepo / unpublished local checkout:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv run nyxa new myapp --nyxa-path /path/to/packages/nyxa
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Routing
|
|
115
|
+
|
|
116
|
+
Define routes in `src/{app}/routes.py`. Nyxa **compiles** them at startup into normal FastAPI endpoints (no Route stack on the request hot path).
|
|
117
|
+
|
|
118
|
+
| What you need | Nyxa |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| Map a path to a controller action | `Route.get("/users", [UserController, "index"])` |
|
|
121
|
+
| Group routes under a prefix | `with Route.prefix("/api"):` |
|
|
122
|
+
| Name a route | `.name("users.index")` |
|
|
123
|
+
| RESTful resource routes | `Route.resource("posts", PostController)` |
|
|
124
|
+
| List compiled routes | `nyxa route:list` |
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# src/myapp/routes.py
|
|
128
|
+
from nyxa.routing import Route
|
|
129
|
+
from myapp.http.controllers.user_controller import UserController
|
|
130
|
+
|
|
131
|
+
with Route.prefix("/api"):
|
|
132
|
+
Route.resource("users", UserController)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Stable CLI (0.1)
|
|
138
|
+
|
|
139
|
+
Entry points: `nyxa`, `artisan` (alias), and `dev`.
|
|
140
|
+
|
|
141
|
+
Generated scaffolds include **Google-style docstrings** (`Args` / `Returns`) on modules, classes, and functions.
|
|
142
|
+
|
|
143
|
+
| Command | Purpose |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `nyxa new` / `nyxa init [name]` | Scaffold a new API app (`--database`, `--auth`, `--no-input`, `--queue`/`--cache`/`--mail`/`--storage`, `--force`, `--nyxa-path`) |
|
|
146
|
+
| `nyxa make:controller` | Controller + auto route; shorts `-a`/`-m`/`-r`/`-R`/`-e`/`-s` |
|
|
147
|
+
| `nyxa make:exc` | Error + handler; nested paths (`user/NotFound`); status heuristic + `--status` |
|
|
148
|
+
| `nyxa make:req` / `make:res` | FormRequest / response models |
|
|
149
|
+
| `nyxa make:policy` | Policy class (`nyxa-auth`) |
|
|
150
|
+
| `nyxa make:middleware` | Middleware class |
|
|
151
|
+
| `nyxa make:provider` | Service provider (`register` / `boot`) |
|
|
152
|
+
| `nyxa make:service` | Service class |
|
|
153
|
+
| `nyxa make:dependency` | FastAPI dependency |
|
|
154
|
+
| `nyxa make:schema` / `make:enum` / `make:config` | Schema, enum, settings |
|
|
155
|
+
| `nyxa make:event` / `make:listener` / `make:job` | Events, listeners (`--event` required), jobs (`Job` class when `nyxa-queue` installed) |
|
|
156
|
+
| `nyxa queue work` | Run a job once in-process (`nyxa-queue`) |
|
|
157
|
+
| `nyxa cache clear` | Flush cache store (`nyxa-cache`) |
|
|
158
|
+
| `nyxa mail test` | Send sample mail via configured mailer (`nyxa-mail`) |
|
|
159
|
+
| `nyxa make:test` | Pytest stub |
|
|
160
|
+
| `nyxa make:resource` | Full CRUD slice + `Route.resource` stub |
|
|
161
|
+
| `nyxa route:list` / `list:routes` | List routes (method, URI, name, action) |
|
|
162
|
+
| `dev` | Run uvicorn using `nyxa.toml` `[dev]` |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Stable public API (0.1)
|
|
167
|
+
|
|
168
|
+
Supported imports for application code:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from nyxa import (
|
|
172
|
+
AppError,
|
|
173
|
+
Controller,
|
|
174
|
+
EventDispatcher,
|
|
175
|
+
ForbiddenError,
|
|
176
|
+
FormRequest,
|
|
177
|
+
Route,
|
|
178
|
+
ServiceProvider,
|
|
179
|
+
abort,
|
|
180
|
+
bind,
|
|
181
|
+
check,
|
|
182
|
+
config,
|
|
183
|
+
context,
|
|
184
|
+
current_user,
|
|
185
|
+
dispatcher,
|
|
186
|
+
env,
|
|
187
|
+
guest,
|
|
188
|
+
no_content,
|
|
189
|
+
not_found,
|
|
190
|
+
register,
|
|
191
|
+
route,
|
|
192
|
+
settings,
|
|
193
|
+
success,
|
|
194
|
+
user,
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
| Symbol | Role |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `AppError` | Base application error (override via `[bases].error`); default JSON handler on `register` |
|
|
201
|
+
| `UnauthorizedError` / `ForbiddenError` / `NotFoundError` / … | Core HTTP-mapped `AppError` subclasses (401–429) |
|
|
202
|
+
| `abort` / `unauthorized` / `forbidden` / `not_found` / … | Raise helpers (`NoReturn`) for common exits |
|
|
203
|
+
| `success` / `created` / `accepted` / `no_content` / `json_response` | JSON envelope / empty-body helpers |
|
|
204
|
+
| `context` / `ContextMiddleware` | Request-scoped key/value bag (auto-wired by `register`) |
|
|
205
|
+
| `user` / `current_user` / `guest` / `check` | Auth stubs reading context (filled by `nyxa-auth`) |
|
|
206
|
+
| `env` / `config` / `settings` | Process env + BaseSettings under `config/` (`model_dump` + typed `settings(stem)`) |
|
|
207
|
+
| `Route` / `Controller` | Routing DSL + controller base (constructor `Depends` OK) |
|
|
208
|
+
| `route(name, **params)` | Reverse URL helper from compiled named routes |
|
|
209
|
+
| `FormRequest` | Pydantic request base (`authorize()` → `ForbiddenError` when falsy) |
|
|
210
|
+
| `ServiceProvider` | App boot hooks (`register` / `boot`) discovered under `providers/` |
|
|
211
|
+
| `bind` / `singleton` / `instance` / `make` / `resolve` / `inject` / `has` | Lean service container (flushed on `register`) |
|
|
212
|
+
| `ForbiddenError` | HTTP 403 `AppError` subclass |
|
|
213
|
+
| `register(app)` | Default AppError handler, custom handlers, middleware, context, container flush, providers, `routes.py`, listeners |
|
|
214
|
+
| `dispatcher` / `EventDispatcher` | In-process events |
|
|
215
|
+
|
|
216
|
+
Everything else under `nyxa.*` (`templates`, `generate`, `make`, `config` internals, …) is **private** and may change without notice.
|
|
217
|
+
|
|
218
|
+
See also [`API.md`](API.md).
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Config (`nyxa.toml`, optional)
|
|
223
|
+
|
|
224
|
+
All keys optional. Defaults:
|
|
225
|
+
|
|
226
|
+
```toml
|
|
227
|
+
app = "myapp" # required in practice — set by `init` / `new`
|
|
228
|
+
src = "src"
|
|
229
|
+
|
|
230
|
+
[dev]
|
|
231
|
+
module = "myapp.main:app" # default: "{app}.main:app"
|
|
232
|
+
host = "0.0.0.0"
|
|
233
|
+
port = 8000
|
|
234
|
+
reload = true
|
|
235
|
+
|
|
236
|
+
[paths]
|
|
237
|
+
errors = "http/exception/errors"
|
|
238
|
+
handlers = "http/exception/handlers"
|
|
239
|
+
requests = "http/requests"
|
|
240
|
+
responses = "http/responses"
|
|
241
|
+
middleware = "http/middleware"
|
|
242
|
+
controllers = "http/controllers"
|
|
243
|
+
dependencies = "http/dependencies"
|
|
244
|
+
services = "services"
|
|
245
|
+
schemas = "schemas"
|
|
246
|
+
enums = "domain/enums"
|
|
247
|
+
config = "config"
|
|
248
|
+
events = "events"
|
|
249
|
+
listeners = "listeners"
|
|
250
|
+
jobs = "jobs"
|
|
251
|
+
providers = "providers"
|
|
252
|
+
tests = "tests" # relative to project root
|
|
253
|
+
|
|
254
|
+
[middleware]
|
|
255
|
+
# global = ["timing"] # optional Kernel-style list (module stems); request-inbound order
|
|
256
|
+
# When unset, all http/middleware/*/Middleware exports are auto-discovered (alphabetical).
|
|
257
|
+
|
|
258
|
+
[providers]
|
|
259
|
+
# boot = ["app_service_provider"] # optional ordered list (module stems under paths.providers)
|
|
260
|
+
# When unset, all providers/*/Provider exports are auto-discovered (alphabetical).
|
|
261
|
+
|
|
262
|
+
[env]
|
|
263
|
+
file = ".env" # primary dotenv under project root
|
|
264
|
+
# environment = "local" # also load .env.{environment}; else NYXA_ENV / APP_ENV
|
|
265
|
+
|
|
266
|
+
[bases]
|
|
267
|
+
error = "nyxa.errors:AppError"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Fallback: `[tool.nyxa] app = "..."` in `pyproject.toml`.
|
|
271
|
+
|
|
272
|
+
### Middleware (two concepts)
|
|
273
|
+
|
|
274
|
+
| Mechanism | Role |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| `Route.middleware(...)` / `Route.group(..., middleware=[...])` | FastAPI **`Depends`** on routes (per-route / group) |
|
|
277
|
+
| Discovered `Middleware` export under `http/middleware/` | Global **Starlette** `BaseHTTPMiddleware` stack via `register` |
|
|
278
|
+
|
|
279
|
+
Use `[middleware] global = ["timing", ...]` for an explicit Kernel-style order (first entry is outermost / sees the request first). Omit `global` to auto-discover all modules alphabetically.
|
|
280
|
+
|
|
281
|
+
### Service providers
|
|
282
|
+
|
|
283
|
+
App boot hooks under `providers/`. Each module exports `Provider` (a `ServiceProvider` subclass) with optional `register` / `boot` methods. `register(app)` calls **all** `register` hooks, then **all** `boot` hooks, before loading `routes.py`.
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
uv run nyxa make:provider AppService
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
```toml
|
|
290
|
+
[providers]
|
|
291
|
+
boot = ["app_service_provider"] # optional; omit for alphabetical discovery
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Service container
|
|
295
|
+
|
|
296
|
+
Lean bind/resolve for app-scoped services. Bind in a provider; inject via FastAPI:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from nyxa import singleton, inject
|
|
300
|
+
|
|
301
|
+
# in ServiceProvider.register:
|
|
302
|
+
singleton(UserService)
|
|
303
|
+
|
|
304
|
+
# in controller __init__:
|
|
305
|
+
service: Annotated[UserService, Depends(inject(UserService))]
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Also: `bind`, `instance`, `make` / `resolve`, `has`. The global `container` is flushed at the start of each `register()`.
|
|
309
|
+
|
|
310
|
+
### Environment (`.env`)
|
|
311
|
+
|
|
312
|
+
`register(app)` loads dotenv files into the process environment:
|
|
313
|
+
|
|
314
|
+
1. Primary file from `[env] file` (default `.env`)
|
|
315
|
+
2. Optional `.env.{environment}` when `[env] environment` is set, else `NYXA_ENV`, else `APP_ENV`
|
|
316
|
+
|
|
317
|
+
Among files, the environment-specific file overrides the primary for shared keys. Existing OS/CI variables are **never** overridden. Missing files are no-ops. `init` / `new` write `.env.example` as a template — copy to `.env` (and optionally `.env.local`, etc.) locally (gitignored).
|
|
318
|
+
|
|
319
|
+
```toml
|
|
320
|
+
[env]
|
|
321
|
+
file = ".env"
|
|
322
|
+
environment = "local" # loads .env then .env.local
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Out of scope (for now)
|
|
328
|
+
|
|
329
|
+
- Database / migrations — **Phase 2** (`nyxa-db`) — Done
|
|
330
|
+
- Auth / policies — **Phase 3** (`nyxa-auth`) — **Done** (JWT, sessions, personal tokens, Google OAuth)
|
|
331
|
+
- Queues, cache, mail, storage — **Phase 4 Done** (`nyxa-queue`, `nyxa-cache`, `nyxa-mail`, `nyxa-storage`)
|
|
332
|
+
- Frontend scaffolding — never (API-only)
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Tests
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# from workspace root
|
|
340
|
+
uv sync --group dev
|
|
341
|
+
uv run pytest packages/nyxa/tests
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Publish (maintainers)
|
|
347
|
+
|
|
348
|
+
### GitHub Release (recommended)
|
|
349
|
+
|
|
350
|
+
[`.github/workflows/publish-nyxa.yml`](../../.github/workflows/publish-nyxa.yml) publishes core `nyxa` and all seven `nyxa-*` packs from one Release tag, using PyPI Trusted Publishing (OIDC, no API token secret).
|
|
351
|
+
|
|
352
|
+
**One-time setup**
|
|
353
|
+
|
|
354
|
+
1. In GitHub → **Settings → Environments**, create one environment per package: `pypi-nyxa`, `pypi-nyxa-db`, `pypi-nyxa-auth`, `pypi-nyxa-queue`, `pypi-nyxa-cache`, `pypi-nyxa-mail`, `pypi-nyxa-storage`, `pypi-nyxa-postgres`.
|
|
355
|
+
2. On PyPI, add a Trusted Publisher for each package (GitHub tab):
|
|
356
|
+
|
|
357
|
+
| PyPI project name | Owner | Repository name | Workflow name | Environment name |
|
|
358
|
+
| --- | --- | --- | --- | --- |
|
|
359
|
+
| `nyxa` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa` |
|
|
360
|
+
| `nyxa-db` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa-db` |
|
|
361
|
+
| `nyxa-auth` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa-auth` |
|
|
362
|
+
| `nyxa-queue` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa-queue` |
|
|
363
|
+
| `nyxa-cache` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa-cache` |
|
|
364
|
+
| `nyxa-mail` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa-mail` |
|
|
365
|
+
| `nyxa-storage` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa-storage` |
|
|
366
|
+
| `nyxa-postgres` | `NyxaDev` | `Nyxa` | `publish-nyxa.yml` | `pypi-nyxa-postgres` |
|
|
367
|
+
|
|
368
|
+
For a project that doesn't exist yet, register it as a *pending* publisher at [pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/). PyPI allows 3 pending publishers per account at a time, and each slot frees up once its project is created. Pending publishers must also be unique on owner/repo/workflow/environment, which is why every package has its own environment.
|
|
369
|
+
|
|
370
|
+
**Each release**
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
gh release create v0.1.1 --target master --title "Nyxa v0.1.1" --notes "…"
|
|
374
|
+
gh run watch
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The tag (leading `v` stripped) becomes the version of every `packages/nyxa*/pyproject.toml`. The workflow builds all eight packages once, then runs one publish job per package in its own environment. A failed job can be re-run with `gh run rerun <run-id> --failed`; files already on PyPI are skipped.
|
|
378
|
+
|
|
379
|
+
### Verify a build locally
|
|
380
|
+
|
|
381
|
+
From the workspace root, build exactly as CI does, then install the wheels into a scratch environment:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
for p in nyxa nyxa-db nyxa-auth nyxa-queue nyxa-cache nyxa-mail nyxa-storage nyxa-postgres; do
|
|
385
|
+
uv build --package "$p" --no-sources --out-dir /tmp/nyxa-dist
|
|
386
|
+
done
|
|
387
|
+
uvx twine check /tmp/nyxa-dist/*
|
|
388
|
+
uvx --from /tmp/nyxa-dist/nyxa-0.1.0-py3-none-any.whl nyxa new /tmp/demoapp --no-input
|
|
389
|
+
# resolve the scaffold against local wheels before they are on PyPI:
|
|
390
|
+
cd /tmp/demoapp && uv sync --find-links /tmp/nyxa-dist
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Roadmap
|
|
396
|
+
|
|
397
|
+
Workspace plans: [`roadmap/README.md`](../../roadmap/README.md).
|