terp-cli 0.28.0__tar.gz → 0.30.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.
- {terp_cli-0.28.0 → terp_cli-0.30.0}/PKG-INFO +8 -8
- {terp_cli-0.28.0 → terp_cli-0.30.0}/pyproject.toml +8 -8
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/__init__.py +27 -1
- {terp_cli-0.28.0 → terp_cli-0.30.0}/.gitignore +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_appref.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_engine.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_output.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_subjects.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/access.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/apidocs.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/authz_surface.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/capabilities.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/deploy_safety.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/dev.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/docker.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/envfile.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/envschema.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/envseams.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/fmt.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/grants.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/jobs.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/leases.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/module_roles.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/openapi.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/outbox.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/ports.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/profiles.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/py.typed +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/routes.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/scaffold.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/schema.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/seed.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/service_accounts.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/smoke.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/users.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/verify.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/version.py +0 -0
- {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/workbench.py +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: terp-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30.0
|
|
4
4
|
Summary: Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs.
|
|
5
5
|
Project-URL: Repository, https://github.com/AITT-NL/terp-framework
|
|
6
6
|
Project-URL: Changelog, https://github.com/AITT-NL/terp-framework/blob/main/CHANGELOG.md
|
|
7
7
|
License-Expression: Apache-2.0
|
|
8
8
|
Requires-Python: >=3.13
|
|
9
9
|
Requires-Dist: pyyaml>=6.0
|
|
10
|
-
Requires-Dist: terp-arch==0.
|
|
11
|
-
Requires-Dist: terp-core==0.
|
|
12
|
-
Requires-Dist: terp-migrations==0.
|
|
10
|
+
Requires-Dist: terp-arch==0.30.0
|
|
11
|
+
Requires-Dist: terp-core==0.30.0
|
|
12
|
+
Requires-Dist: terp-migrations==0.30.0
|
|
13
13
|
Provides-Extra: jobs
|
|
14
|
-
Requires-Dist: terp-cap-outbox==0.
|
|
15
|
-
Requires-Dist: terp-cap-scheduler-apscheduler==0.
|
|
14
|
+
Requires-Dist: terp-cap-outbox==0.30.0; extra == 'jobs'
|
|
15
|
+
Requires-Dist: terp-cap-scheduler-apscheduler==0.30.0; extra == 'jobs'
|
|
16
16
|
Provides-Extra: scheduler
|
|
17
|
-
Requires-Dist: terp-cap-scheduler-apscheduler==0.
|
|
17
|
+
Requires-Dist: terp-cap-scheduler-apscheduler==0.30.0; extra == 'scheduler'
|
|
18
18
|
Provides-Extra: worker
|
|
19
|
-
Requires-Dist: terp-cap-outbox==0.
|
|
19
|
+
Requires-Dist: terp-cap-outbox==0.30.0; extra == 'worker'
|
|
@@ -4,18 +4,18 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "terp-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.30.0"
|
|
8
8
|
description = "Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs."
|
|
9
9
|
requires-python = ">=3.13"
|
|
10
10
|
license = "Apache-2.0"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"terp-core==0.
|
|
12
|
+
"terp-core==0.30.0",
|
|
13
13
|
# `terp migrate` delegates to terp-migrations (lazily imported), keeping Alembic
|
|
14
14
|
# off the path for `terp inspect` / `terp guide` while making migrations work.
|
|
15
|
-
"terp-migrations==0.
|
|
15
|
+
"terp-migrations==0.30.0",
|
|
16
16
|
# `terp guide rules` projects the live rule registry; terp-arch is imported lazily
|
|
17
17
|
# (only when that topic is rendered), so it stays off the common `terp guide` path.
|
|
18
|
-
"terp-arch==0.
|
|
18
|
+
"terp-arch==0.30.0",
|
|
19
19
|
# `terp verify --only env-seams` and `terp smoke` read the compose profiles as data
|
|
20
20
|
# (anchors and `<<:` merge keys resolved), so both answer without a Docker daemon or
|
|
21
21
|
# even the `docker` binary. Declared explicitly rather than leant on: every app
|
|
@@ -29,11 +29,11 @@ dependencies = [
|
|
|
29
29
|
# or a scheduler engine into apps that only use inspect/check/migrate. Deployments can select
|
|
30
30
|
# one process role, while `jobs` is the convenient complete jobs-process bundle.
|
|
31
31
|
[project.optional-dependencies]
|
|
32
|
-
worker = ["terp-cap-outbox==0.
|
|
33
|
-
scheduler = ["terp-cap-scheduler-apscheduler==0.
|
|
32
|
+
worker = ["terp-cap-outbox==0.30.0"]
|
|
33
|
+
scheduler = ["terp-cap-scheduler-apscheduler==0.30.0"]
|
|
34
34
|
jobs = [
|
|
35
|
-
"terp-cap-outbox==0.
|
|
36
|
-
"terp-cap-scheduler-apscheduler==0.
|
|
35
|
+
"terp-cap-outbox==0.30.0",
|
|
36
|
+
"terp-cap-scheduler-apscheduler==0.30.0",
|
|
37
37
|
]
|
|
38
38
|
|
|
39
39
|
[project.scripts]
|
|
@@ -106,6 +106,10 @@ Add a module (the "10-minute module")
|
|
|
106
106
|
@router.post("/", response_model=InvoiceRead, status_code=201)
|
|
107
107
|
def create_invoice(payload: InvoiceCreate, session: SessionDep) -> InvoiceRead:
|
|
108
108
|
return InvoiceRead.model_validate(_service.create(session, payload))
|
|
109
|
+
Register every route with a route decorator or add_api_route. A plain Starlette route
|
|
110
|
+
(router.add_route), a Starlette WebSocket route or a Mount is served without the
|
|
111
|
+
router's dependencies, so the deny-by-default guard never runs for it, and the boot
|
|
112
|
+
refuses one. A route anyone may call declares route_policy(Policy.public(reason=...)).
|
|
109
113
|
|
|
110
114
|
5) module.py the manifest
|
|
111
115
|
module = ModuleSpec(name="invoices", router=router, policy=Policy.default())
|
|
@@ -434,6 +438,12 @@ Authorization (Policy)
|
|
|
434
438
|
or boot fails closed. Grant via the access capability; the caller must clear the
|
|
435
439
|
min_role floor AND hold the grant.
|
|
436
440
|
- Route-level extra check: dependencies=[Depends(require_permission(APPROVE))].
|
|
441
|
+
- A module that only READS says so, and the boot holds every route to it:
|
|
442
|
+
ModuleSpec(name="reports", router=router, policy=Policy.default(), read_only=True)
|
|
443
|
+
A route that answers POST / PUT / PATCH / DELETE, or a WebSocket, then refuses the
|
|
444
|
+
boot, on an included sub-router too, unless it is declared @read_only (it computes an
|
|
445
|
+
answer and persists nothing). Undeclared, the module is read-only only until someone
|
|
446
|
+
adds a write route. It is not a Policy: route_policy(...) cannot reopen it.
|
|
437
447
|
- Authority is always a typed object (Role / Permission), never a bare string — pass the
|
|
438
448
|
declared constant, not its name. no_adhoc_permission_literals refuses the literal.
|
|
439
449
|
- Choosing between a role and a permission, and the grant lifecycle: `terp guide permissions`.
|
|
@@ -748,6 +758,7 @@ Rows that cannot change, and rows that cannot go (append_only)
|
|
|
748
758
|
- The neighbouring guarantees, so you pick the right one:
|
|
749
759
|
append_only the ROW cannot change after insert
|
|
750
760
|
@read_only the ROUTE writes nothing, though its verb is unsafe
|
|
761
|
+
ModuleSpec(read_only=True) no ROUTE in the module writes (see: policy)
|
|
751
762
|
SoftDeleteMixin the row survives its own delete (see: soft-delete)
|
|
752
763
|
OnDelete.RESTRICT another row cannot be deleted while this one points at it
|
|
753
764
|
BaseUpdateSchema a concurrent writer cannot lose your edit (OCC, 409)
|
|
@@ -1216,6 +1227,13 @@ RESTORES it after. No conftest.py line, no opt-in. Without it a suite goes
|
|
|
1216
1227
|
order-dependent: green together, red alone - the sharpest failure mode there is,
|
|
1217
1228
|
because the green is the wrong answer.
|
|
1218
1229
|
|
|
1230
|
+
SIGNING TOKENS NEEDS NO KEY OF YOUR OWN. The same plugin's `terp_signing_key` fixture
|
|
1231
|
+
(autouse, once per session) replaces the development SECRET_KEY with a random 64-character
|
|
1232
|
+
one while the default is in place, and puts the default back afterwards. So a test that
|
|
1233
|
+
calls create_access_token signs with a strong key and pyjwt does not warn. Never write a
|
|
1234
|
+
key into a test file: it is a credential-shaped literal the secret scan flags. A key the
|
|
1235
|
+
environment sets, or one a test sets with monkeypatch, is left as it is.
|
|
1236
|
+
|
|
1219
1237
|
THE SIX SEAMS. create_app installs six process globals per app; in a test process they
|
|
1220
1238
|
outlive the app that installed them. Restored automatically -- but installed by you:
|
|
1221
1239
|
|
|
@@ -1513,6 +1531,14 @@ Using capabilities
|
|
|
1513
1531
|
BASE PROFILE — auth, identity, users, groups, access, audit — so authentication,
|
|
1514
1532
|
accounts, group membership, permission checks and the audit trail are wired before you
|
|
1515
1533
|
write anything. Install the others you need.
|
|
1534
|
+
- WHO IS CALLING, in words a person reads: take `caller: CallerDep` on the route.
|
|
1535
|
+
from terp.capabilities.identity import CallerDep
|
|
1536
|
+
def pause(name: str, caller: CallerDep, session: SessionDep) -> None:
|
|
1537
|
+
service.pause(session, name, decided_by=caller.name)
|
|
1538
|
+
`caller.name` is a user's email or a service account's name, read from the live row;
|
|
1539
|
+
`caller.id` is the key to store when a record must point at who did it. Never take a
|
|
1540
|
+
"decided by" from the request body (it is whatever the client says), and never build
|
|
1541
|
+
an IdentityService inside a module to look the caller up.
|
|
1516
1542
|
- SEE WHAT EXISTS BEFORE YOU BUILD IT:
|
|
1517
1543
|
terp inspect capabilities
|
|
1518
1544
|
lists every maintained capability, whether this app already has it, the exact
|
|
@@ -1535,7 +1561,7 @@ Using capabilities
|
|
|
1535
1561
|
permission_enforcer=enforce_permission, discover_capabilities=True)
|
|
1536
1562
|
- You can always drop to native FastAPI/SQLModel — the same gate rules still apply.
|
|
1537
1563
|
- Outbound HTTP is a capability concern, never a module concern: importing httpx /
|
|
1538
|
-
requests / urllib.request / urllib3 / aiohttp in a module is refused by the
|
|
1564
|
+
httpx2 / requests / urllib.request / urllib3 / aiohttp in a module is refused by the
|
|
1539
1565
|
no_raw_outbound_http rule — SSRF protection, egress allowlists and timeout policy
|
|
1540
1566
|
belong behind one declared capability, not scattered per call site. That capability
|
|
1541
1567
|
is `terp-cap-egress`, and it is a declaration rather than a client you configure at
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|