xtr-cli 5.1.0__tar.gz → 5.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.
Files changed (75) hide show
  1. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/PKG-INFO +29 -101
  2. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/README.md +28 -100
  3. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/pyproject.toml +1 -1
  4. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/pyproject.toml.orig +1 -1
  5. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/__init__.py +6 -15
  6. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/command/init_command.py +11 -19
  7. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/command/new_command.py +12 -21
  8. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/command_support.py +12 -41
  9. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/__init__.py +0 -2
  10. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/unknown_template_error.py +5 -5
  11. xtr_cli-5.2.1/src/xtr_cli/project_settings.py +52 -0
  12. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/scaffolder.py +1 -3
  13. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/template_catalog.py +8 -13
  14. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/template.toml +1 -2
  15. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/README.md.tmpl +7 -3
  16. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/kernel.py.tmpl +3 -0
  17. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/template.toml +11 -2
  18. xtr_cli-5.1.0/src/xtr_cli/add_on.py +0 -53
  19. xtr_cli-5.1.0/src/xtr_cli/add_on_catalog.py +0 -179
  20. xtr_cli-5.1.0/src/xtr_cli/add_on_choice.py +0 -40
  21. xtr_cli-5.1.0/src/xtr_cli/exception/unknown_add_on_error.py +0 -28
  22. xtr_cli-5.1.0/src/xtr_cli/project_questions.py +0 -108
  23. xtr_cli-5.1.0/src/xtr_cli/project_settings.py +0 -89
  24. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/LICENSE +0 -0
  25. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/__main__.py +0 -0
  26. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/command/__init__.py +0 -0
  27. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/command/serve_command.py +0 -0
  28. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/ambiguous_web_entry_point_error.py +0 -0
  29. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/cli_error.py +0 -0
  30. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/directory_not_empty_error.py +0 -0
  31. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/invalid_project_name_error.py +0 -0
  32. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/invalid_template_error.py +0 -0
  33. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/project_not_found_error.py +0 -0
  34. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/step_failed_error.py +0 -0
  35. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/unsupported_python_error.py +0 -0
  36. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/exception/web_entry_point_not_found_error.py +0 -0
  37. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/process_runner_interface.py +0 -0
  38. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/project_name.py +0 -0
  39. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/py.typed +0 -0
  40. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/scaffold_plan.py +0 -0
  41. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/subprocess_runner.py +0 -0
  42. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/template.py +0 -0
  43. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/__init__.py +0 -0
  44. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/README.md.tmpl +0 -0
  45. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/app/__init__.py.tmpl +0 -0
  46. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/app/__main__.py.tmpl +0 -0
  47. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/app/command/__init__.py.tmpl +0 -0
  48. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/app/command/hello_command.py.tmpl +0 -0
  49. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/app/kernel.py.tmpl +0 -0
  50. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/app/py.typed.tmpl +0 -0
  51. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/gitignore.tmpl +0 -0
  52. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/pyproject.toml.tmpl +0 -0
  53. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/python-version.tmpl +0 -0
  54. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/tests/__init__.py.tmpl +0 -0
  55. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/cli/files/tests/test_kernel.py.tmpl +0 -0
  56. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/__init__.py.tmpl +0 -0
  57. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/__main__.py.tmpl +0 -0
  58. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/command/__init__.py.tmpl +0 -0
  59. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/command/hello_command.py.tmpl +0 -0
  60. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/config/__init__.py.tmpl +0 -0
  61. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/config/packages/__init__.py.tmpl +0 -0
  62. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/config/packages/http_kernel.py.tmpl +0 -0
  63. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/py.typed.tmpl +0 -0
  64. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/web/__init__.py.tmpl +0 -0
  65. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/web/__main__.py.tmpl +0 -0
  66. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/web/app.py.tmpl +0 -0
  67. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/app/web/routes.py.tmpl +0 -0
  68. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/gitignore.tmpl +0 -0
  69. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/pyproject.toml.tmpl +0 -0
  70. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/python-version.tmpl +0 -0
  71. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/tests/__init__.py.tmpl +0 -0
  72. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/tests/test_kernel.py.tmpl +0 -0
  73. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/templates/webapp/files/tests/test_web.py.tmpl +0 -0
  74. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/web_entry_point.py +0 -0
  75. {xtr_cli-5.1.0 → xtr_cli-5.2.1}/src/xtr_cli/web_entry_point_finder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xtr-cli
3
- Version: 5.1.0
3
+ Version: 5.2.1
4
4
  Summary: Scaffolds a new xtr application — a console application or a web application — in one command, and serves it.
5
5
  Keywords: scaffold,new project,template,console,web,xtr-dependency-injection
6
6
  Author: Xterr
@@ -67,27 +67,14 @@ Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/) on the `PATH`, and `g
67
67
  ## Quick start
68
68
 
69
69
  ```console
70
- $ uvx xtr-cli new shop
71
- • cli — A console application: a kernel, its commands, the layered .env files and logging.
72
- • webapp — A web application: FastAPI served from the kernel, with the console beside it.
73
- Template [cli/webapp] (cli): webapp
74
- • orm — Database engines, sessions per unit of work and versioned migrations.
75
- • messenger — A message bus: handlers, middleware, transports and consumers.
76
- …
77
- Add-ons, comma-separated (empty for none): orm, messenger
78
- Database drivers for orm — sqlite, postgres, mysql, joined with + (sqlite): sqlite+postgres
79
- Transports and serializers for messenger — amqp, pydantic, joined with +:
70
+ $ uvx xtr-cli new shop --webapp
80
71
 
81
72
  shop — the webapp template
82
73
  --------------------------
83
74
 
84
75
  • directory: /home/ada/shop
85
- • add-ons: orm (sqlite, postgres), messenger
86
76
  • python: 3.13
87
- The same, with no question asked:
88
- uvx xtr-cli new shop --template=webapp --with=orm:sqlite+postgres,messenger --python=3.13 -n
89
77
 
90
- Create it? [y/n] (y):
91
78
  write pyproject.toml
92
79
  …
93
80
  run uv add -- 'xtr-dependency-injection>=5.0,<6' …
@@ -108,26 +95,23 @@ uv run pytest
108
95
  ## Commands
109
96
 
110
97
  ```sh
111
- xtr project:new [<directory>] [options] # alias: new — into a new or empty directory
112
- xtr project:init [options] # alias: init — into the current directory
113
- xtr server:start [options] # alias: serve — run the project's web application
98
+ xtr project:new <directory> [options] # alias: new — into a new or empty directory
99
+ xtr project:init [options] # alias: init — into the current directory
100
+ xtr server:start [options] # alias: serve — run the project's web application
114
101
  ```
115
102
 
116
103
  | Option | Default | |
117
104
  |---|---|---|
118
- | `--template` | asked; `cli` with `-n` | `cli`, `webapp`, or a directory holding a `template.toml` |
105
+ | `--webapp` | | a web application rather than a console application — see [Templates](#templates) |
119
106
  | `--name` | the directory's name | the distribution and console script; dashes become underscores for the import package |
120
- | `--with` | asked; none with `-n` | add-ons, comma-separated: `orm:sqlite+postgres,messenger` |
121
107
  | `--python` | the Python running this tool | the `3.<minor>` the project is pinned to; 3.11 or later |
122
108
  | `--no-git` | | do not run `git init`; `init` never re-initialises an existing repository |
123
109
  | `--dry-run` | | print every file and command, and write nothing |
124
110
  | `--local-packages` | | install the xtr packages, editable, from a directory of their sources |
125
- | `-n` | | ask nothing: every question takes its default |
126
111
 
127
- **Questions fill in only what the command line left out**, and an option given is never asked.
128
- With `-n` — or wherever nothing can answer, such as CI — nothing is asked at all. Before anything
129
- is written the run prints the command that makes the same project with no question asked, so an
130
- answered run can be repeated in a script.
112
+ **Nothing is asked.** Every option has a default, so the same command line makes the same project
113
+ at a terminal, in a script or in CI. More packages are added afterwards, from inside the project,
114
+ with `uv run xtr-recipes recipes:add <package>`.
131
115
 
132
116
  The directory must be missing, or hold nothing but `.git` and editor metadata: nothing is ever
133
117
  written over. A name that would not import — a keyword, a module of the standard library — is
@@ -136,8 +120,8 @@ refused before anything is written.
136
120
  ### What it runs
137
121
 
138
122
  1. Writes the template's files.
139
- 2. `uv add` the template's packages and the add-ons; every unversioned xtr requirement is pinned
140
- to this tool's major version, so the files and the packages are from the same release.
123
+ 2. `uv add` the template's packages; every unversioned xtr requirement is pinned to this tool's
124
+ major version, so the files and the packages are from the same release.
141
125
  3. `uv add --dev` the tools: `xtr-recipes`, `pytest`, `ruff`, `basedpyright`, `ty`.
142
126
  4. `uv run xtr-recipes recipes:sync` — each package's recipe writes `<app>/config/bundles.py`, its
143
127
  configuration files, its `.env` and `.gitignore` blocks, and `xtr.lock`; the steps it cannot
@@ -186,76 +170,24 @@ application's shutdown hooks run.
186
170
 
187
171
  ## Templates
188
172
 
189
- | Template | Packages | Scripts |
190
- |---|---|---|
191
- | `cli` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv`, `xtr-logging` | `<name>` |
192
- | `webapp` | the same, `xtr-http-kernel`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
173
+ | Template | Made by | Packages | Scripts |
174
+ |---|---|---|---|
175
+ | `cli` | `xtr new <directory>` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv` | `<name>` |
176
+ | `webapp` | `xtr new <directory> --webapp` | the same, and `xtr-logging`, `xtr-http-kernel`, `xtr-orm[postgres]`, `xtr-messenger[amqp]`, `xtr-http-client`, `xtr-notifier`, `xtr-security`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
193
177
 
194
178
  Both write `pyproject.toml` (with ruff, basedpyright, ty and pytest configured), `.python-version`,
195
179
  `.gitignore`, `README.md`, `src/<app>/kernel.py`, `src/<app>/__main__.py`, an example
196
180
  `app:hello` command and a test that builds the kernel. `webapp` adds `src/<app>/web/` — the
197
- FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point — and
198
- a test that requests the home page.
199
-
200
- ### A template of your own
201
-
202
- `--template=<directory>` takes any directory holding a `template.toml`:
203
-
204
- ```toml
205
- summary = "What it makes, in one line."
206
-
207
- [dependencies]
208
- packages = ["xtr-console"] # uv add
209
- dev = ["xtr-recipes", "pytest"] # uv add --dev; with xtr-recipes, the recipes are applied
210
-
211
- [files]
212
- # destination in the project = file under the template directory
213
- "pyproject.toml" = "files/pyproject.toml.tmpl"
214
- "src/${app}/__main__.py" = "files/main.py.tmpl"
181
+ FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point — a
182
+ kernel built with `concurrent_scoped_access=True` for the security bundle, and a test that
183
+ requests the home page.
215
184
 
216
- [notes]
217
- next = ["uv run ${name} list"] # printed once the project is made
218
- ```
219
-
220
- Destinations, files and notes substitute `${name}` (the distribution), `${app}` (the import
221
- name), `${python}` (`3.13`) and `${minor}` (`13`); a literal `$` is written `$$`. An unknown
222
- placeholder, table or key is an `InvalidTemplateError` before anything is written. Write only
223
- what no recipe writes: the bundle list, `.env` and the ignore lines are the recipes'.
224
-
225
- ## Add-ons
226
-
227
- Choosing an add-on only adds its requirement; the recipe the package ships configures it, the
228
- way it would for a package added later with `xtr-recipes recipes:add`.
229
-
230
- | Add-on | Installs | Variants |
231
- |---|---|---|
232
- | `orm` | `xtr-orm` | `postgres` (default), `sqlite`, `mysql` |
233
- | `messenger` | `xtr-messenger` | `amqp` (default), `pydantic` |
234
- | `scheduler` | `xtr-scheduler[cron]` | |
235
- | `event-dispatcher` | `xtr-event-dispatcher` | |
236
- | `cache` | `xtr-cache` | `redis` |
237
- | `lock` | `xtr-lock` | `redis` |
238
- | `rate-limiter` | `xtr-rate-limiter` | |
239
- | `storage` | `xtr-storage` | `s3`, `gcs` |
240
- | `http-client` | `xtr-http-client` | `http2` |
241
- | `security` | `xtr-security` — `webapp` only | |
242
- | `jwt` | `xtr-security-jwt` — `webapp` only; brings `security` | |
243
- | `oauth2` | `xtr-security-oauth2-server` — `webapp` only; brings `security` and `jwt` | |
244
- | `webhook` | `xtr-webhook` — `webapp` only; brings `messenger` | |
245
-
246
- Variants only pick third-party backends. Integrations between xtr packages turn on when both are
247
- installed: choose `orm` beside `oauth2` to keep its clients in a database, `http-client` beside
248
- `webhook` to send webhooks, `rate-limiter` beside `http-client` to pace requests.
249
-
250
- `jwt` and `oauth2` each have their recipe mint a development signing key under `secrets/`,
251
- encrypted with a pass phrase it generates in `.env` — and, for `oauth2`, the encryption key too.
252
- The keys are ignored: another checkout runs `uv run xtr-recipes recipes:install <package>` to
253
- mint its own, and production mounts the deployment's own key and sets its own secrets. `oauth2`
254
- still issues nothing until its issuer is configured.
255
-
256
- Variants follow the name after `:`, joined with `+`: `orm:sqlite+postgres`. A name alone takes
257
- the defaults; `messenger:` picks none. Asked interactively, an add-on with variants is followed by
258
- a question for them.
185
+ Each package's recipe configures it: the `webapp` project starts with a `compose.yaml` running
186
+ PostgreSQL — published on `5432` by `compose.override.yaml` — that `DATABASE_URL` points at,
187
+ `MESSENGER_DSN` pointing at a RabbitMQ on this machine, and both notifier DSNs at `null://null`.
188
+ Nothing connects until it is used, so the project boots and passes its tests with neither
189
+ running. Every other package — `xtr-cache`, `xtr-scheduler`, `xtr-security-jwt` — is a
190
+ `recipes:add` away.
259
191
 
260
192
  ## Errors
261
193
 
@@ -267,11 +199,10 @@ attributes. A command reports it and exits `1`.
267
199
  | `DirectoryNotEmptyError` | the directory is a file, or holds anything but version-control and editor metadata |
268
200
  | `AmbiguousWebEntryPointError` | `serve` found several web entry points, and no question may be asked |
269
201
  | `InvalidProjectNameError` | the name normalises to nothing, or to a keyword, a non-identifier or a standard-library module |
270
- | `InvalidTemplateError` | a `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
202
+ | `InvalidTemplateError` | a shipped `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
271
203
  | `ProjectNotFoundError` | `serve` was run outside a project |
272
204
  | `StepFailedError` | `uv` or `git` exited with a failure; the later steps were not run |
273
- | `UnknownAddOnError` | `--with` names an unknown add-on, one the template cannot take, one twice, or an unknown variant |
274
- | `UnknownTemplateError` | `--template` is neither a shipped template nor a directory holding one |
205
+ | `UnknownTemplateError` | `TemplateCatalog.load` is given a name no template ships under |
275
206
  | `UnsupportedPythonError` | `--python` is not `3.<minor>` with a minor of 11 or more |
276
207
  | `WebEntryPointNotFoundError` | the project has nothing to serve, or `--entrypoint` names nothing it has |
277
208
 
@@ -280,14 +211,11 @@ attributes. A command reports it and exits `1`.
280
211
  ```
281
212
  xtr_cli/
282
213
  ├── command/ project:new, project:init and server:start
283
- ├── command_support.py what both commands share: questions, plan, apply, report
284
- ├── project_questions.py what the command line left out, asked
214
+ ├── command_support.py what both commands share: settings, plan, apply, report
285
215
  ├── project_name.py the distribution and import names
286
- ├── project_settings.py everything decided, and the command line repeating it
216
+ ├── project_settings.py everything decided about the project
287
217
  ├── template.py a template.toml and its files
288
- ├── template_catalog.py the shipped templates, or a directory
289
- ├── add_on.py one add-on: a name standing for a requirement
290
- ├── add_on_catalog.py the add-ons, and how --with names them
218
+ ├── template_catalog.py the shipped templates
291
219
  ├── scaffolder.py settings into a plan, a plan into a project
292
220
  ├── scaffold_plan.py the files and commands, before anything is made
293
221
  ├── web_entry_point.py what serves an application, and the command starting it
@@ -46,27 +46,14 @@ Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/) on the `PATH`, and `g
46
46
  ## Quick start
47
47
 
48
48
  ```console
49
- $ uvx xtr-cli new shop
50
- • cli — A console application: a kernel, its commands, the layered .env files and logging.
51
- • webapp — A web application: FastAPI served from the kernel, with the console beside it.
52
- Template [cli/webapp] (cli): webapp
53
- • orm — Database engines, sessions per unit of work and versioned migrations.
54
- • messenger — A message bus: handlers, middleware, transports and consumers.
55
- …
56
- Add-ons, comma-separated (empty for none): orm, messenger
57
- Database drivers for orm — sqlite, postgres, mysql, joined with + (sqlite): sqlite+postgres
58
- Transports and serializers for messenger — amqp, pydantic, joined with +:
49
+ $ uvx xtr-cli new shop --webapp
59
50
 
60
51
  shop — the webapp template
61
52
  --------------------------
62
53
 
63
54
  • directory: /home/ada/shop
64
- • add-ons: orm (sqlite, postgres), messenger
65
55
  • python: 3.13
66
- The same, with no question asked:
67
- uvx xtr-cli new shop --template=webapp --with=orm:sqlite+postgres,messenger --python=3.13 -n
68
56
 
69
- Create it? [y/n] (y):
70
57
  write pyproject.toml
71
58
  …
72
59
  run uv add -- 'xtr-dependency-injection>=5.0,<6' …
@@ -87,26 +74,23 @@ uv run pytest
87
74
  ## Commands
88
75
 
89
76
  ```sh
90
- xtr project:new [<directory>] [options] # alias: new — into a new or empty directory
91
- xtr project:init [options] # alias: init — into the current directory
92
- xtr server:start [options] # alias: serve — run the project's web application
77
+ xtr project:new <directory> [options] # alias: new — into a new or empty directory
78
+ xtr project:init [options] # alias: init — into the current directory
79
+ xtr server:start [options] # alias: serve — run the project's web application
93
80
  ```
94
81
 
95
82
  | Option | Default | |
96
83
  |---|---|---|
97
- | `--template` | asked; `cli` with `-n` | `cli`, `webapp`, or a directory holding a `template.toml` |
84
+ | `--webapp` | | a web application rather than a console application — see [Templates](#templates) |
98
85
  | `--name` | the directory's name | the distribution and console script; dashes become underscores for the import package |
99
- | `--with` | asked; none with `-n` | add-ons, comma-separated: `orm:sqlite+postgres,messenger` |
100
86
  | `--python` | the Python running this tool | the `3.<minor>` the project is pinned to; 3.11 or later |
101
87
  | `--no-git` | | do not run `git init`; `init` never re-initialises an existing repository |
102
88
  | `--dry-run` | | print every file and command, and write nothing |
103
89
  | `--local-packages` | | install the xtr packages, editable, from a directory of their sources |
104
- | `-n` | | ask nothing: every question takes its default |
105
90
 
106
- **Questions fill in only what the command line left out**, and an option given is never asked.
107
- With `-n` — or wherever nothing can answer, such as CI — nothing is asked at all. Before anything
108
- is written the run prints the command that makes the same project with no question asked, so an
109
- answered run can be repeated in a script.
91
+ **Nothing is asked.** Every option has a default, so the same command line makes the same project
92
+ at a terminal, in a script or in CI. More packages are added afterwards, from inside the project,
93
+ with `uv run xtr-recipes recipes:add <package>`.
110
94
 
111
95
  The directory must be missing, or hold nothing but `.git` and editor metadata: nothing is ever
112
96
  written over. A name that would not import — a keyword, a module of the standard library — is
@@ -115,8 +99,8 @@ refused before anything is written.
115
99
  ### What it runs
116
100
 
117
101
  1. Writes the template's files.
118
- 2. `uv add` the template's packages and the add-ons; every unversioned xtr requirement is pinned
119
- to this tool's major version, so the files and the packages are from the same release.
102
+ 2. `uv add` the template's packages; every unversioned xtr requirement is pinned to this tool's
103
+ major version, so the files and the packages are from the same release.
120
104
  3. `uv add --dev` the tools: `xtr-recipes`, `pytest`, `ruff`, `basedpyright`, `ty`.
121
105
  4. `uv run xtr-recipes recipes:sync` — each package's recipe writes `<app>/config/bundles.py`, its
122
106
  configuration files, its `.env` and `.gitignore` blocks, and `xtr.lock`; the steps it cannot
@@ -165,76 +149,24 @@ application's shutdown hooks run.
165
149
 
166
150
  ## Templates
167
151
 
168
- | Template | Packages | Scripts |
169
- |---|---|---|
170
- | `cli` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv`, `xtr-logging` | `<name>` |
171
- | `webapp` | the same, `xtr-http-kernel`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
152
+ | Template | Made by | Packages | Scripts |
153
+ |---|---|---|---|
154
+ | `cli` | `xtr new <directory>` | `xtr-dependency-injection`, `xtr-console`, `xtr-dotenv` | `<name>` |
155
+ | `webapp` | `xtr new <directory> --webapp` | the same, and `xtr-logging`, `xtr-http-kernel`, `xtr-orm[postgres]`, `xtr-messenger[amqp]`, `xtr-http-client`, `xtr-notifier`, `xtr-security`, `fastapi`, `uvicorn` | `<name>`, `<name>-web` |
172
156
 
173
157
  Both write `pyproject.toml` (with ruff, basedpyright, ty and pytest configured), `.python-version`,
174
158
  `.gitignore`, `README.md`, `src/<app>/kernel.py`, `src/<app>/__main__.py`, an example
175
159
  `app:hello` command and a test that builds the kernel. `webapp` adds `src/<app>/web/` — the
176
- FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point — and
177
- a test that requests the home page.
178
-
179
- ### A template of your own
180
-
181
- `--template=<directory>` takes any directory holding a `template.toml`:
182
-
183
- ```toml
184
- summary = "What it makes, in one line."
185
-
186
- [dependencies]
187
- packages = ["xtr-console"] # uv add
188
- dev = ["xtr-recipes", "pytest"] # uv add --dev; with xtr-recipes, the recipes are applied
189
-
190
- [files]
191
- # destination in the project = file under the template directory
192
- "pyproject.toml" = "files/pyproject.toml.tmpl"
193
- "src/${app}/__main__.py" = "files/main.py.tmpl"
160
+ FastAPI application served with `setup(app, kernel)`, its routes and a uvicorn entry point — a
161
+ kernel built with `concurrent_scoped_access=True` for the security bundle, and a test that
162
+ requests the home page.
194
163
 
195
- [notes]
196
- next = ["uv run ${name} list"] # printed once the project is made
197
- ```
198
-
199
- Destinations, files and notes substitute `${name}` (the distribution), `${app}` (the import
200
- name), `${python}` (`3.13`) and `${minor}` (`13`); a literal `$` is written `$$`. An unknown
201
- placeholder, table or key is an `InvalidTemplateError` before anything is written. Write only
202
- what no recipe writes: the bundle list, `.env` and the ignore lines are the recipes'.
203
-
204
- ## Add-ons
205
-
206
- Choosing an add-on only adds its requirement; the recipe the package ships configures it, the
207
- way it would for a package added later with `xtr-recipes recipes:add`.
208
-
209
- | Add-on | Installs | Variants |
210
- |---|---|---|
211
- | `orm` | `xtr-orm` | `postgres` (default), `sqlite`, `mysql` |
212
- | `messenger` | `xtr-messenger` | `amqp` (default), `pydantic` |
213
- | `scheduler` | `xtr-scheduler[cron]` | |
214
- | `event-dispatcher` | `xtr-event-dispatcher` | |
215
- | `cache` | `xtr-cache` | `redis` |
216
- | `lock` | `xtr-lock` | `redis` |
217
- | `rate-limiter` | `xtr-rate-limiter` | |
218
- | `storage` | `xtr-storage` | `s3`, `gcs` |
219
- | `http-client` | `xtr-http-client` | `http2` |
220
- | `security` | `xtr-security` — `webapp` only | |
221
- | `jwt` | `xtr-security-jwt` — `webapp` only; brings `security` | |
222
- | `oauth2` | `xtr-security-oauth2-server` — `webapp` only; brings `security` and `jwt` | |
223
- | `webhook` | `xtr-webhook` — `webapp` only; brings `messenger` | |
224
-
225
- Variants only pick third-party backends. Integrations between xtr packages turn on when both are
226
- installed: choose `orm` beside `oauth2` to keep its clients in a database, `http-client` beside
227
- `webhook` to send webhooks, `rate-limiter` beside `http-client` to pace requests.
228
-
229
- `jwt` and `oauth2` each have their recipe mint a development signing key under `secrets/`,
230
- encrypted with a pass phrase it generates in `.env` — and, for `oauth2`, the encryption key too.
231
- The keys are ignored: another checkout runs `uv run xtr-recipes recipes:install <package>` to
232
- mint its own, and production mounts the deployment's own key and sets its own secrets. `oauth2`
233
- still issues nothing until its issuer is configured.
234
-
235
- Variants follow the name after `:`, joined with `+`: `orm:sqlite+postgres`. A name alone takes
236
- the defaults; `messenger:` picks none. Asked interactively, an add-on with variants is followed by
237
- a question for them.
164
+ Each package's recipe configures it: the `webapp` project starts with a `compose.yaml` running
165
+ PostgreSQL — published on `5432` by `compose.override.yaml` — that `DATABASE_URL` points at,
166
+ `MESSENGER_DSN` pointing at a RabbitMQ on this machine, and both notifier DSNs at `null://null`.
167
+ Nothing connects until it is used, so the project boots and passes its tests with neither
168
+ running. Every other package — `xtr-cache`, `xtr-scheduler`, `xtr-security-jwt` — is a
169
+ `recipes:add` away.
238
170
 
239
171
  ## Errors
240
172
 
@@ -246,11 +178,10 @@ attributes. A command reports it and exits `1`.
246
178
  | `DirectoryNotEmptyError` | the directory is a file, or holds anything but version-control and editor metadata |
247
179
  | `AmbiguousWebEntryPointError` | `serve` found several web entry points, and no question may be asked |
248
180
  | `InvalidProjectNameError` | the name normalises to nothing, or to a keyword, a non-identifier or a standard-library module |
249
- | `InvalidTemplateError` | a `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
181
+ | `InvalidTemplateError` | a shipped `template.toml` does not parse, holds an unknown key, names a missing file, or a placeholder is unknown |
250
182
  | `ProjectNotFoundError` | `serve` was run outside a project |
251
183
  | `StepFailedError` | `uv` or `git` exited with a failure; the later steps were not run |
252
- | `UnknownAddOnError` | `--with` names an unknown add-on, one the template cannot take, one twice, or an unknown variant |
253
- | `UnknownTemplateError` | `--template` is neither a shipped template nor a directory holding one |
184
+ | `UnknownTemplateError` | `TemplateCatalog.load` is given a name no template ships under |
254
185
  | `UnsupportedPythonError` | `--python` is not `3.<minor>` with a minor of 11 or more |
255
186
  | `WebEntryPointNotFoundError` | the project has nothing to serve, or `--entrypoint` names nothing it has |
256
187
 
@@ -259,14 +190,11 @@ attributes. A command reports it and exits `1`.
259
190
  ```
260
191
  xtr_cli/
261
192
  ├── command/ project:new, project:init and server:start
262
- ├── command_support.py what both commands share: questions, plan, apply, report
263
- ├── project_questions.py what the command line left out, asked
193
+ ├── command_support.py what both commands share: settings, plan, apply, report
264
194
  ├── project_name.py the distribution and import names
265
- ├── project_settings.py everything decided, and the command line repeating it
195
+ ├── project_settings.py everything decided about the project
266
196
  ├── template.py a template.toml and its files
267
- ├── template_catalog.py the shipped templates, or a directory
268
- ├── add_on.py one add-on: a name standing for a requirement
269
- ├── add_on_catalog.py the add-ons, and how --with names them
197
+ ├── template_catalog.py the shipped templates
270
198
  ├── scaffolder.py settings into a plan, a plan into a project
271
199
  ├── scaffold_plan.py the files and commands, before anything is made
272
200
  ├── web_entry_point.py what serves an application, and the command starting it
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "xtr-cli"
3
- version = "5.1.0"
3
+ version = "5.2.1"
4
4
  description = "Scaffolds a new xtr application — a console application or a web application — in one command, and serves it."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "xtr-cli"
3
- version = "5.1.0"
3
+ version = "5.2.1"
4
4
  description = "Scaffolds a new xtr application — a console application or a web application — in one command, and serves it."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,10 +1,11 @@
1
1
  """Creates a new application on the xtr packages, ready to run, in one command.
2
2
 
3
- ``xtr new <directory>`` asks for what it was not told — the template, the
4
- add-ons — writes the few files no package's recipe can (the project manifest,
5
- the kernel and the entry points), installs the packages with ``uv``, and lets
6
- each package's recipe configure itself with ``xtr-recipes recipes:sync``. The
7
- project it leaves boots, and is already in the state a recipe check expects.
3
+ ``xtr new <directory>`` asks nothing: it makes a console application, or a web
4
+ application with ``--webapp``. It writes the few files no package's recipe can
5
+ (the project manifest, the kernel and the entry points), installs the packages
6
+ with ``uv``, and lets each package's recipe configure itself with
7
+ ``xtr-recipes recipes:sync``. The project it leaves boots, and is already in
8
+ the state a recipe check expects.
8
9
 
9
10
  This module exposes the pieces that plan and make a project; the commands that
10
11
  drive them live in :mod:`xtr_cli.command`, behind the ``xtr`` console script.
@@ -14,9 +15,6 @@ from __future__ import annotations
14
15
 
15
16
  from importlib.metadata import PackageNotFoundError, version
16
17
 
17
- from .add_on import AddOn
18
- from .add_on_catalog import AddOnCatalog
19
- from .add_on_choice import AddOnChoice
20
18
  from .exception import (
21
19
  AmbiguousWebEntryPointError,
22
20
  CliError,
@@ -25,14 +23,12 @@ from .exception import (
25
23
  InvalidTemplateError,
26
24
  ProjectNotFoundError,
27
25
  StepFailedError,
28
- UnknownAddOnError,
29
26
  UnknownTemplateError,
30
27
  UnsupportedPythonError,
31
28
  WebEntryPointNotFoundError,
32
29
  )
33
30
  from .process_runner_interface import ProcessRunnerInterface
34
31
  from .project_name import ProjectName
35
- from .project_questions import ProjectQuestions
36
32
  from .project_settings import ProjectSettings
37
33
  from .scaffold_plan import ScaffoldPlan
38
34
  from .scaffolder import Scaffolder
@@ -50,9 +46,6 @@ except PackageNotFoundError: # pragma: no cover
50
46
  __version__ = "0+unknown"
51
47
 
52
48
  __all__ = [
53
- "AddOn",
54
- "AddOnCatalog",
55
- "AddOnChoice",
56
49
  "AmbiguousWebEntryPointError",
57
50
  "CliError",
58
51
  "DirectoryNotEmptyError",
@@ -61,7 +54,6 @@ __all__ = [
61
54
  "ProcessRunnerInterface",
62
55
  "ProjectName",
63
56
  "ProjectNotFoundError",
64
- "ProjectQuestions",
65
57
  "ProjectSettings",
66
58
  "ScaffoldPlan",
67
59
  "Scaffolder",
@@ -69,7 +61,6 @@ __all__ = [
69
61
  "SubprocessRunner",
70
62
  "Template",
71
63
  "TemplateCatalog",
72
- "UnknownAddOnError",
73
64
  "UnknownTemplateError",
74
65
  "UnsupportedPythonError",
75
66
  "WebEntryPoint",
@@ -3,9 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
- from typing import Annotated, final
6
+ from typing import final
7
7
 
8
- from xtr_console import ConsoleStyle, Option, as_command
8
+ from xtr_console import ConsoleStyle, as_command
9
9
 
10
10
  from xtr_cli import command_support
11
11
 
@@ -15,37 +15,30 @@ __all__ = ["InitCommand"]
15
15
  @as_command("project:init", aliases="init")
16
16
  @final
17
17
  class InitCommand:
18
- """Create a new application, from a template, in the current directory.
18
+ """Create a new application in the current directory.
19
19
 
20
- The directory must hold nothing but version-control or editor metadata;
21
- an existing git repository is kept rather than initialised again.
20
+ Nothing is asked: a console application by default, a web application with
21
+ ``--webapp``. The directory must hold nothing but version-control or editor
22
+ metadata; an existing git repository is kept rather than initialised again.
22
23
  """
23
24
 
24
25
  async def __call__( # noqa: PLR0913 — one parameter per option.
25
26
  self,
26
27
  io: ConsoleStyle,
27
28
  *,
28
- template: str | None = None,
29
+ webapp: bool = False,
29
30
  name: str | None = None,
30
- add_ons: Annotated[
31
- str | None,
32
- Option(
33
- name="--with",
34
- help="More packages to start with, comma-separated: orm:sqlite+postgres,messenger.",
35
- ),
36
- ] = None,
37
31
  python: str | None = None,
38
32
  no_git: bool = False,
39
33
  dry_run: bool = False,
40
34
  local_packages: Path | None = None,
41
35
  ) -> int:
42
- """Create a new application, from a template, in the current directory.
36
+ """Create a new application in the current directory.
43
37
 
44
38
  Args:
45
- io: Where the questions are asked and the steps reported.
46
- template: cli or webapp, or a directory holding a template.toml.
39
+ io: Where the steps are reported.
40
+ webapp: Make a web application, with a database, a message bus and security.
47
41
  name: The project's name; the directory's name by default.
48
- add_ons: The ``--with`` add-ons.
49
42
  python: The 3.<minor> version to pin; the one running this tool by default.
50
43
  no_git: Do not initialise a git repository.
51
44
  dry_run: Print the files and commands, and write nothing.
@@ -59,9 +52,8 @@ class InitCommand:
59
52
  io,
60
53
  command="init",
61
54
  directory=Path.cwd(),
62
- template=template,
55
+ webapp=webapp,
63
56
  name=name,
64
- add_ons=add_ons,
65
57
  python=python,
66
58
  git=not no_git,
67
59
  dry_run=dry_run,
@@ -3,9 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path # noqa: TC003 — the console reads annotations at runtime.
6
- from typing import Annotated, final
6
+ from typing import final
7
7
 
8
- from xtr_console import ConsoleStyle, Option, as_command
8
+ from xtr_console import ConsoleStyle, as_command
9
9
 
10
10
  from xtr_cli import command_support
11
11
 
@@ -15,40 +15,32 @@ __all__ = ["NewCommand"]
15
15
  @as_command("project:new", aliases="new")
16
16
  @final
17
17
  class NewCommand:
18
- """Create a new application, from a template, in a new directory.
18
+ """Create a new application in a new directory.
19
19
 
20
- What is not given is asked for; with ``-n`` nothing is asked and the
21
- defaults are taken. The packages are installed with ``uv``, and each one's
22
- recipe is applied, before the command ends.
20
+ Nothing is asked: a console application by default, a web application with
21
+ ``--webapp``. The packages are installed with ``uv``, and each one's recipe
22
+ is applied, before the command ends.
23
23
  """
24
24
 
25
25
  async def __call__( # noqa: PLR0913 — one parameter per option.
26
26
  self,
27
27
  io: ConsoleStyle,
28
- directory: Path | None = None,
28
+ directory: Path,
29
29
  *,
30
- template: str | None = None,
30
+ webapp: bool = False,
31
31
  name: str | None = None,
32
- add_ons: Annotated[
33
- str | None,
34
- Option(
35
- name="--with",
36
- help="More packages to start with, comma-separated: orm:sqlite+postgres,messenger.",
37
- ),
38
- ] = None,
39
32
  python: str | None = None,
40
33
  no_git: bool = False,
41
34
  dry_run: bool = False,
42
35
  local_packages: Path | None = None,
43
36
  ) -> int:
44
- """Create a new application, from a template, in a new directory.
37
+ """Create a new application in a new directory.
45
38
 
46
39
  Args:
47
- io: Where the questions are asked and the steps reported.
40
+ io: Where the steps are reported.
48
41
  directory: Where to create it; it must be missing or empty.
49
- template: cli or webapp, or a directory holding a template.toml.
42
+ webapp: Make a web application, with a database, a message bus and security.
50
43
  name: The project's name; the directory's name by default.
51
- add_ons: The ``--with`` add-ons.
52
44
  python: The 3.<minor> version to pin; the one running this tool by default.
53
45
  no_git: Do not initialise a git repository.
54
46
  dry_run: Print the files and commands, and write nothing.
@@ -62,9 +54,8 @@ class NewCommand:
62
54
  io,
63
55
  command="new",
64
56
  directory=directory,
65
- template=template,
57
+ webapp=webapp,
66
58
  name=name,
67
- add_ons=add_ons,
68
59
  python=python,
69
60
  git=not no_git,
70
61
  dry_run=dry_run,