"""
# speclike

`speclike` is a **pytest helper library** designed to define tests in a more structured and expressive way.  
It provides a declarative approach for building tests from two complementary perspectives:

- **Individual test bodies**, written as ordinary methods.
- **Externally defined dispatchers and actors**, representing scenario-driven or behavior-based tests.

The framework automatically generates executable pytest test functions (`test_...`) from decorated functions and classes.

---

## 🧩 Core Concepts

### 1. `Spec` and `ExSpec` Classes
- **`Spec`** — the main base class for declarative test specifications.  
  It manages auto-generated tests and delegates execution through `dispatch()` or `dispatch_async()`.
- **`ExSpec`** — groups externally defined dispatchers (functions that control test flow outside of the class).

Both are implemented using metaclasses (`_SpecMeta`, `_ExSpecMeta`) that synthesize pytest-compatible test functions during class creation.

---

### 2. `Case` and `Ex` Decorators
- **`Case`** — marks individual test bodies or actor functions (`def _(...):`) within a `Spec` class.  
  It can attach pytest marks, parametrize data, or skip tests dynamically.
- **`Ex`** — marks dispatcher functions used in `ExSpec` or top-level definitions.  
  Dispatchers define parameter structure using `PRM`, and connect to actors via `@case.ex(dispatcher)`.

---

### 3. `PRM` (Parameter Prefix Rules)
Defines how test parameters behave and interact between dispatcher and actor.

| Prefix | Kind | Behavior |
|---------|------|-----------|
| `_` | AO (Actor-Only) | Created by dispatcher, passed to actor (not parametrized) |
| *(none)* | AP (Actor-Parametrized) | Parametrized and passed to actor |
| `__` | PO (Param-Only) | Parametrized but **not** passed to actor (used for assertions) |

Parameter ordering must follow `AO → AP → PO`.

`PRM` validates actor signatures, generates pytest parametrization, and bridges runtime values through `_ParamsBridge`.

---

### 4. `Dispatcher` and `Actor`
- **Dispatcher**: a function decorated with `@ex` that defines test input combinations using `PRM`.
- **Actor**: a function named `_` decorated with `@case.ex(dispatcher)` that performs the actual behavior under test.
- The library automatically links each actor to its dispatcher and generates a `test_<dispatcher>` method that executes the pair.

---

### 5. Test Generation Workflow
1. The metaclass scans for decorated functions (`TargetKind`).
2. Each test body or dispatcher/actor pair is converted into a pytest-visible `test_...` function.
3. Signatures, parametrization, and pytest marks are copied to preserve readability and IDE support.
4. For external specs (`ExSpec`), tests are created dynamically based on defined dispatchers.

---

### 6. Highlights
- Strong signature validation for actors against their `PRM` definitions.
- Automatic propagation of `pytest.mark.parametrize` and other pytest marks.
- Source location (`co_firstlineno`) is preserved for accurate traceback references.
- Supports both sync and async test execution paths.

---

### Example

```python
from speclike import Spec, PRM

# Example domain object
class Context:
    def compute(self, x: int) -> int:
        return x * 10

# Get decorators
case, ex = Spec.get_decorators()

# Dispatcher (external). Parameters are NOT taken as direct function args.
# Access AP/PO values via the bridge `p`, and call the actor via `p.act(...)`.
@ex.follows(
    [(1, 10), (2, 20), (3, 30)],  # (value, __expected)
    ids=["x1", "x2", "x3"]
)
def check(p = PRM(_ctx=Context, value=int, __expected=int)):
    ctx = Context()                             # AO: create here
    result = p.act(_ctx=ctx, value=p.value)     # call actor with AO/AP
    assert result == p.__expected               # PO: only used in dispatcher

# Spec class with actor method named "_"
class TestCompute(Spec):
    @case.ex(check)
    def _(self, _ctx: Context, value: int) -> int:
        # Actor receives AO/AP only, in the declared order.
        return _ctx.compute(value)
````

At runtime, this generates:

* `test_check` — a parametrized pytest function executing the dispatcher.
* `act_for_check` — an internal bound actor function used by the dispatcher.

---

## Labeling Decorator System (`Case` / `Ex`)

`speclike` provides a **hierarchical labeling system** for organizing and classifying tests in a flexible, domain-neutral way.

Each decorator chain expresses **up to three levels of labels** (tiers), and the structure is enforced but not restricted to predefined words.

```python
@case.api.input.default
@case.tmp
@case.network.timeout
```

Each decorator call selects one label per level, forming a hierarchical path such as:

```
Primary → Secondary → Tertiary
```

You can specify between **0 and 3 levels**.
All identifiers are **user-defined arbitrary strings**, validated dynamically by a user-provided validator.

---

### `Tier` Enumeration

Internally, the classification levels are represented by the `Tier` enum:

```python
class Tier(Enum):
    PRIMARY = 0
    SECONDARY = auto()
    TERTIARY = auto()
```

Each label belongs to one of these tiers, depending on its position in the decorator chain.

---

### `Label Validator`

A **label validator** function can be provided to control or restrict allowed labels:

```python
def _custom_validator(tier: Tier, name: str):
    if tier is Tier.PRIMARY and name not in {"api", "feature", "resource"}:
        raise ValueError(f"Invalid primary label '{name}'")
```

Validators receive both the `Tier` and the `name` string.
By default, `_ALL_ACCEPTS` is used, which allows all labels.

Custom validators can be injected through the factory method:

```python
case, ex = Spec.get_decorators(case_label_validator=_custom_validator)
```

---

### Decorator Behavior

When you write:

```python
@case.feature.io.default
def check_something(): ...
```

the decorator:

1. Collects the labels `["feature", "io", "default"]`
2. Normalizes the target’s `pytestmark` list
3. Appends a structured mark:

   ```python
   pytest.mark.speclike("feature", "io", "default")
   ```
4. Ensures this mark coexists cleanly with other pytest marks (`skip`, `parametrize`, etc.)

---

### Key Properties

* Labels are **free-form identifiers**; any valid Python attribute name is accepted.
* Only the **depth (up to 3 levels)** is enforced.
* Each decorated function carries one consolidated mark (`pytest.mark.speclike(...)`).
* Validators can enforce naming rules, prevent duplicates, or introduce domain semantics.

---

### Example Usage

```python
@case.api.input.default
def test_api_input_default(): ...

@case.tmp
def test_tmp_behavior(): ...

@ex.performance.load.stress
def dispatcher(...): ...
```

Generates pytest marks such as:

```python
pytest.mark.speclike("api", "input", "default")
pytest.mark.speclike("tmp")
pytest.mark.speclike("performance", "load", "stress")
```

---

### Current Status of the `speclike` Marker

A pytest marker named **`speclike`** is automatically attached to each decorated function.
Currently:

* pytest recognizes and lists the mark
* filtering such as `-m "speclike"` is available
* argument-based filtering (`-m "speclike('api')"` or `--speclike`) is **not yet implemented**

The next step will be a dedicated pytest plugin that interprets these structured labels for filtering, grouping, or reporting.

---

### Why This Classification Matters

Even before plugin support, this classification brings major benefits:

* Consistent metadata describing **feature area**, **scenario**, or **intent**
* Easier navigation of large test suites
* Predictable naming and grouping for reports
* A foundation for future tooling—custom filters, dashboards, or hierarchical reports

Once the plugin is introduced, the `speclike` mark will enable **rich test selection and reporting**
while keeping the decorator syntax simple and expressive.

---
"""

from speclike.impl import Spec, ExSpec, PRM, Tier

__all__ = [
    "Spec", "ExSpec", "PRM", "Tier"
]

