terp-cli 0.5.1__tar.gz → 0.5.2__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,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: terp-cli
3
+ Version: 0.5.2
4
+ Summary: Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs.
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: terp-arch==0.5.2
8
+ Requires-Dist: terp-core==0.5.2
9
+ Requires-Dist: terp-migrations==0.5.2
10
+ Provides-Extra: jobs
11
+ Requires-Dist: terp-cap-outbox==0.5.2; extra == 'jobs'
12
+ Requires-Dist: terp-cap-scheduler-apscheduler==0.5.2; extra == 'jobs'
13
+ Provides-Extra: scheduler
14
+ Requires-Dist: terp-cap-scheduler-apscheduler==0.5.2; extra == 'scheduler'
15
+ Provides-Extra: worker
16
+ Requires-Dist: terp-cap-outbox==0.5.2; extra == 'worker'
@@ -4,29 +4,29 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "terp-cli"
7
- version = "0.5.1"
7
+ version = "0.5.2"
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.5.1",
12
+ "terp-core==0.5.2",
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.5.1",
15
+ "terp-migrations==0.5.2",
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.5.1",
18
+ "terp-arch==0.5.2",
19
19
  ]
20
20
 
21
21
  # Job-process adapters stay optional: the base CLI must not install the table-owning outbox
22
22
  # or a scheduler engine into apps that only use inspect/check/migrate. Deployments can select
23
23
  # one process role, while `jobs` is the convenient complete jobs-process bundle.
24
24
  [project.optional-dependencies]
25
- worker = ["terp-cap-outbox==0.5.1"]
26
- scheduler = ["terp-cap-scheduler-apscheduler==0.5.1"]
25
+ worker = ["terp-cap-outbox==0.5.2"]
26
+ scheduler = ["terp-cap-scheduler-apscheduler==0.5.2"]
27
27
  jobs = [
28
- "terp-cap-outbox==0.5.1",
29
- "terp-cap-scheduler-apscheduler==0.5.1",
28
+ "terp-cap-outbox==0.5.2",
29
+ "terp-cap-scheduler-apscheduler==0.5.2",
30
30
  ]
31
31
 
32
32
  [project.scripts]
@@ -337,15 +337,60 @@ Password strength (PasswordPolicy, Tier-B)
337
337
  "events": """\
338
338
  Domain events (eventbus capability)
339
339
 
340
+ - The capability is a separate distribution — nothing below exists until you add it:
341
+ uv add terp-cap-eventbus
342
+ Then import from terp.capabilities.eventbus (EventEmittingService, LifecycleEventMap,
343
+ subscribe, dispatch_in_process).
340
344
  - Declare typed events in your control plane (never bare strings):
341
345
  NOTE_CREATED = EventDefinition("note.created", payload_schema=NoteCreatedPayload)
342
346
  event_catalog = EventCatalog([NOTE_CREATED])
347
+ - Declare what the module produces in its manifest. The emits list is the module's
348
+ published contract, so the gate refuses an event you emit without naming it here:
349
+ module = ModuleSpec(name="notes", emits=[NOTE_CREATED, NOTE_ARCHIVED])
343
350
  - Emit declaratively from a service (atomic with the write):
344
351
  class NoteService(EventEmittingService[Note, NoteCreate, NoteUpdate]):
345
352
  model = Note
346
353
  event_map = LifecycleEventMap(created=NOTE_CREATED)
354
+ - The map covers "every write of this shape emits this event". When the decision is
355
+ CONDITIONAL — a state transition, one event for some updates and none for others —
356
+ extend the same hook rather than emitting from a router or a task:
357
+ class NoteService(EventEmittingService[Note, NoteCreate, NoteUpdate]):
358
+ model = Note
359
+ event_map = LifecycleEventMap(created=NOTE_CREATED)
360
+
361
+ def _after_write(self, session, entity, action) -> None:
362
+ super()._after_write(session, entity, action)
363
+ if action is AuditAction.UPDATED and entity.status == "archived":
364
+ emit(session, event=NOTE_ARCHIVED, payload=NoteArchivedPayload(id=entity.id))
365
+ _after_write runs inside the write's transaction, so a conditional emit is still
366
+ atomic with the row it describes — which is the whole reason the map lives there.
347
367
  - Subscribe with @subscribe(NOTE_CREATED). Reference catalog constants only (the gate
348
368
  enforces no-drift). Wire create_app(..., event_dispatcher=dispatch_in_process).
369
+ - Switching the bus on in a service-level test: the `terp_events` fixture, never a
370
+ direct configure_events (see `terp guide testing`).
371
+ """,
372
+ "testing": """\
373
+ Testing a Terp app (process-global runtime isolation)
374
+
375
+ - create_app installs SIX process globals per app: the audit policy+sink, the event
376
+ catalog+dispatcher, the job catalog+queue, the schedule catalog, the password policy,
377
+ and the decrypt call site. In a test process they outlive the app that installed them.
378
+ - You get isolation for free. terp-core ships a pytest plugin (terp.core.testing,
379
+ registered under pytest11) whose autouse fixture SNAPSHOTS every runtime before a
380
+ test and RESTORES it after. No conftest.py line, no opt-in. Without it a suite goes
381
+ order-dependent: green together, red alone - the sharpest failure mode there is,
382
+ because the green is the wrong answer.
383
+ - Isolation does not INSTALL a runtime your test needs - that is your decision:
384
+ * whole runtime -> compose the app in a fixture (see apps/example/tests/conftest.py,
385
+ where db_engine + app_db are function-scoped and build() runs per test)
386
+ * only the event bus -> the `terp_events` fixture, which installs a catalog
387
+ (and dispatcher) for the duration of one test:
388
+ terp_events(event_catalog, dispatcher=dispatch_in_process)
389
+ * asserting on what was written to a FAKE/bare session -> `terp_default_runtime`,
390
+ which states the baseline instead of assuming it. Without it a composed app's
391
+ durable audit sink quietly adds audit rows to the session under assertion.
392
+ - If a test passes in the suite and fails alone (or vice versa), it is reading a
393
+ runtime it never installed. Install it explicitly; never re-order the suite.
349
394
  """,
350
395
  "jobs": """\
351
396
  Background jobs (terp.core.enqueue + JobCatalog)
terp_cli-0.5.1/PKG-INFO DELETED
@@ -1,16 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: terp-cli
3
- Version: 0.5.1
4
- Summary: Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs.
5
- License-Expression: Apache-2.0
6
- Requires-Python: >=3.13
7
- Requires-Dist: terp-arch==0.5.1
8
- Requires-Dist: terp-core==0.5.1
9
- Requires-Dist: terp-migrations==0.5.1
10
- Provides-Extra: jobs
11
- Requires-Dist: terp-cap-outbox==0.5.1; extra == 'jobs'
12
- Requires-Dist: terp-cap-scheduler-apscheduler==0.5.1; extra == 'jobs'
13
- Provides-Extra: scheduler
14
- Requires-Dist: terp-cap-scheduler-apscheduler==0.5.1; extra == 'scheduler'
15
- Provides-Extra: worker
16
- Requires-Dist: terp-cap-outbox==0.5.1; extra == 'worker'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes