Metadata-Version: 2.5
Name: spec-case
Version: 0.2.1
Summary: In-code spec/case/why/ideal/rule/link markers + specgen extractor that compiles them into spec.json (consumed by ccr), plus the canonical Case model (spec_case.model, [model] extra) shared by black-box runners.
Project-URL: Homepage, https://github.com/compforge/spec-case
Project-URL: Repository, https://github.com/compforge/spec-case
Project-URL: Issues, https://github.com/compforge/spec-case/issues
Author: qiankunli
License: Apache-2.0
Keywords: ast,case,code-review,markers,spec,specgen
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Provides-Extra: model
Requires-Dist: pydantic<3.0,>=2.0; extra == 'model'
Requires-Dist: pyyaml<7.0,>=6.0; extra == 'model'
Description-Content-Type: text/markdown

# spec-case (Python)

In-code **spec / case / why / ideal / link / rule** markers + **specgen**, the static extractor
that compiles them into `spec.json` — the artifact [`ccr`](https://github.com/qiankunli/case-code-review)
consumes. Part of [spec-case](https://github.com/compforge/spec-case); see the repo
for concepts, the symbol-id contract, and the Go reference implementation.

## Install

```bash
uv add spec-case        # or: pip install spec-case
```

One dependency covers both ends: the markers are imported by your code at runtime,
and the same package ships the `specgen` console script for CI.

## Use the markers

The markers are **no-op decorators** — at runtime each returns the function
unchanged, so importing and annotating costs nothing and never changes behavior.
They only *mark* functions for specgen's static (`ast`) extraction.

```python
from spec_case import spec, case, why, ideal, link, rule

@spec("(tenant, name) unique; duplicate create -> ConflictError")
@case("happy_minimal", "only Name given should create", expect="201; body.id non-empty")
@case("duplicate_name", "duplicate Name", expect="409", forbid="a second row is written")
@why("database uniqueness is the cross-replica authority")
@ideal("one persistence owner replaces dual writes")
@link("component://docs/tenancy.md")
@rule("hot request path — watch new synchronous DB calls")
def create_notebook(req): ...
```

单个 spec 的 `id` 可省略。`typing.overload` 等同一 symbol 多声明场景可用
`@spec("...", id="string_input")` 区分；多个 spec 必须全部提供唯一 id。`specgen` 始终输出
统一的 `{fqn?, specs[]}` entry。

`spec_case` is zero-dependency (pure stdlib) and tiny, so taking it as a regular
runtime dependency is cheap. Because the decorators apply at **import time**, the
package must be importable anywhere the annotated module is imported (production
included) — it is a runtime dependency, not dev-only.

## Run specgen (CI)

Compile markers into `spec.json`, and gate drift in CI:

```bash
uv run specgen <src-dir> -o spec.json            # extract
uv run specgen <src-dir> -o spec.json --check    # CI gate: exit 1 if spec.json drifted
```

`--check` compares the committed `spec.json` against the current markers and fails
if a symbol was renamed/removed or a marker changed. specgen parses with `ast` and
never imports or runs your code.
