Metadata-Version: 2.4
Name: pytest-dag
Version: 3.14.15
Summary: A pytest plugin that enforces test execution order via a dependency DAG
Author-email: "SLR Software Solutions Inc." <support@slrsoft.ca>
License-Expression: LicenseRef-Proprietary
Project-URL: Homepage, https://github.com/SLR-Software-Solutions-Inc/pytest-dag
Project-URL: Bug Tracker, https://github.com/SLR-Software-Solutions-Inc/pytest-dag/issues
Keywords: pytest,testing,dag,dependencies,ordering,plugin
Classifier: Framework :: Pytest
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Testing
Classifier: Intended Audience :: Developers
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pytest>=7.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: cryptography>=42.0.0
Dynamic: license-file

# pytest-dag

<p align="center">
  <img src="https://raw.githubusercontent.com/SLR-Software-Solutions-Inc/pytest-dag-documentation/main/docs/_static/branding/pytest-dag-logo-512.png" alt="pytest-dag logo" width="88">
</p>

![pytest-dag banner](https://raw.githubusercontent.com/SLR-Software-Solutions-Inc/pytest-dag-documentation/main/docs/_static/branding/pytest-dag-banner-1280x640.png)

[![Docs status](https://readthedocs.org/projects/pytest-dag/badge/?version=latest)](https://pytest-dag.readthedocs.io/en/latest/)

Documentation: https://pytest-dag.readthedocs.io/en/latest/

`pytest-dag` is a pytest plugin for dependency-aware test execution.

It lets tests declare dependencies, builds a DAG, runs tests in topological
order, and skips downstream tests when required dependencies fail.

## Installation

```bash
pip install pytest-dag
```

## Quick Start

```python
import pytest

def test_a():
    assert True

@pytest.mark.dag(depends=["test_a"])
def test_b():
    assert True

@pytest.mark.dag(depends=["test_b"])
def test_c():
    assert True

@pytest.mark.dag(depends=["test_b"])
def test_d():
    assert True
```

Expected run order:

`test_a -> test_b -> (test_c, test_d)`

If `test_a` fails, dependent tests are skipped with a clear reason:

```text
SKIPPED pytest-dag: blocked by test_file.py::test_a (FAILED)
```

## Marker Syntax

```python
# Single dependency (string)
@pytest.mark.dag(depends="test_login")
def test_profile():
    ...

# Multiple dependencies (list)
@pytest.mark.dag(depends=["test_login", "test_db_connect"])
def test_dashboard():
    ...

# Cross-file dependency (full nodeid)
@pytest.mark.dag(depends=["tests/auth/test_auth.py::test_login"])
def test_profile():
    ...
```

## YAML DAG (for larger test suite, where it really shine!)

For larger suites, you can define dependencies in YAML instead of (or in
addition to) markers.

`pyproject.toml`:

```toml
[tool.pytest.ini_options]
dag_file = "tests/dag.yaml"
```

`tests/dag.yaml`:

```yaml
nodes:
  - id: tests/test_flow.py::test_a
  - id: tests/test_flow.py::test_b
    depends: [tests/test_flow.py::test_a]
  - id: tests/test_flow.py::test_c
    depends: [tests/test_flow.py::test_b]
```

Marker dependencies and YAML dependencies are unioned.

## CLI Options

| Option | Tier | Default | Description |
| ------ | ---- | ------- | ----------- |
| `--dag-block-on-outcomes OUTCOMES` | Free+ | `fail` | Outcomes that block dependents: `fail`, `skip`, `xfail`, `error` |
| `--dag-print-graph` | Free+ | off | Print DAG order and edges after collection |
| `--pytest-dag-license-key` | — | unset | Provide license key directly |
| `--pytest-dag-license-key-file` | — | unset | Read license key from file |
| `--dag-workers N` | **Pro** | 1 | Run DAG-independent tests in parallel |
| `--dag-report-out PATH` | **Pro** | unset | Write interactive HTML DAG report to PATH |

Examples:

```bash
# Cascade skipping (skip dependents when a dependency fails or is skipped)
pytest --dag-block-on-outcomes fail,skip

# Print computed graph for debugging
pytest --dag-print-graph
```

## Migrating from pytest-dependency

If your suite uses `pytest.mark.dependency(...)` markers, the built-in migration
tool scans your tests, emits a `dag.yaml`, and removes the old markers from
source files in one step.

| Option | Default | Description |
| ------ | ------- | ----------- |
| `--migrate-from-pytest-dependency` | off | Run preflight validation, emit `dag.yaml`, remove `pytest.mark.dependency` markers from source, then exit without running tests |
| `--dag-file-out PATH` | `tests/dag.yaml` | Output path for the generated YAML file |
| `--migrate-dry-run` | off | Preview the generated YAML without writing any files |

Example workflow:

```bash
# Step 1: Preview what will be generated (no files written)
pytest --migrate-from-pytest-dependency --migrate-dry-run

# Step 2: Run the full migration (writes dag.yaml, removes old markers)
pytest --migrate-from-pytest-dependency --dag-file-out tests/dag.yaml
```

## Tiers

`pytest-dag` is free to use with no key required. A pro license unlocks additional features.

| Feature | Free | Pro |
| ------- | ---- | --- |
| DAG enforcement, ordering, skip propagation | ✓ | ✓ |
| YAML DAG file | ✓ | ✓ |
| Max DAG nodes | 25 | Unlimited |
| Max DAG depth | 7 | Unlimited |
| Parallel workers (`--dag-workers`) | — | ✓ |
| HTML report (`--dag-report-out`) | — | ✓ |
| Run banner | shown | silent |

### Activating pro

Set the key received from the purchase page via environment variable (recommended for CI):

```bash
export PYTEST_DAG_LICENSE_KEY=<your-license-key>
pytest --dag-report-out report.html --dag-workers 4
```

Or via CLI flag or key file:

```bash
pytest --pytest-dag-license-key <your-license-key>
pytest --pytest-dag-license-key-file /path/to/key.txt
```

License verification is done locally — no network calls are made.

Purchase: `https://slrsoft.ca/app/pytest-dag/purchase`  
Support: `support@slrsoft.ca`

## Viewing Skip Reasons

Use `-v -rs` to see exact skip reasons:

```bash
pytest -v -rs
```

Example summary:

```text
SKIPPED [2] pytest-dag: blocked by test_demo.py::test_login (FAILED)
SKIPPED [1] test_demo.py:104: feature not yet implemented
```

## pytest-xdist Compatibility

On the **free tier**, `pytest-dag` automatically disables `pytest-xdist` (`-n`)
to preserve DAG ordering correctness, and prints a warning.

On the **pro tier**, use `--dag-workers N` for native DAG-aware parallelism.

If xdist is installed but `-n` is not passed, behaviour is unchanged.

## Troubleshooting

### `plugins: ... dag-0.1.0` (not `pytest-dag-0.1.0`)

This is normal. Pytest shortens plugin names in output by dropping the
`pytest-` prefix.

### Plugin not loading in a virtualenv

If `which pytest` points to a global executable after activating a venv, use:

```bash
python -m pytest ...
```

This guarantees the venv interpreter and installed plugin are used.

## License

Proprietary. Copyright (c) 2026 SLR Software Solutions Inc.

See the license terms in the repository `LICENSE` file.

Licensing inquiries: `support@slrsoft.ca`
