slimtest 0.1.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.
- slimtest-0.1.0/.github/CODEOWNERS +9 -0
- slimtest-0.1.0/.github/workflows/ci.yml +45 -0
- slimtest-0.1.0/.github/workflows/release-testpypi.yml +58 -0
- slimtest-0.1.0/.github/workflows/release.yml +73 -0
- slimtest-0.1.0/.gitignore +60 -0
- slimtest-0.1.0/CHANGELOG.md +38 -0
- slimtest-0.1.0/LICENSE +21 -0
- slimtest-0.1.0/PKG-INFO +375 -0
- slimtest-0.1.0/README.md +344 -0
- slimtest-0.1.0/examples/sample_dbt_project/.gitignore +6 -0
- slimtest-0.1.0/examples/sample_dbt_project/README.md +88 -0
- slimtest-0.1.0/examples/sample_dbt_project/dbt_project.yml +26 -0
- slimtest-0.1.0/examples/sample_dbt_project/models/marts/order_statuses.sql +52 -0
- slimtest-0.1.0/examples/sample_dbt_project/models/marts/order_statuses.yml +145 -0
- slimtest-0.1.0/examples/sample_dbt_project/profiles.yml +7 -0
- slimtest-0.1.0/examples/sample_dbt_project/seeds/customers.csv +4 -0
- slimtest-0.1.0/examples/sample_dbt_project/seeds/order_events.csv +13 -0
- slimtest-0.1.0/examples/sample_dbt_project/seeds/orders.csv +4 -0
- slimtest-0.1.0/examples/sample_dbt_project/seeds/products.csv +4 -0
- slimtest-0.1.0/examples/sample_dbt_project/tests/slimtest_factories/customers.yml +14 -0
- slimtest-0.1.0/examples/sample_dbt_project/tests/slimtest_factories/order_events.yml +15 -0
- slimtest-0.1.0/examples/sample_dbt_project/tests/slimtest_factories/orders.yml +17 -0
- slimtest-0.1.0/examples/sample_dbt_project/tests/slimtest_factories/products.yml +10 -0
- slimtest-0.1.0/pyproject.toml +127 -0
- slimtest-0.1.0/src/slimtest/__init__.py +13 -0
- slimtest-0.1.0/src/slimtest/cli.py +178 -0
- slimtest-0.1.0/src/slimtest/compile.py +268 -0
- slimtest-0.1.0/src/slimtest/config.py +57 -0
- slimtest-0.1.0/src/slimtest/dbt_project.py +109 -0
- slimtest-0.1.0/src/slimtest/dbt_runner.py +124 -0
- slimtest-0.1.0/src/slimtest/deep_merge.py +34 -0
- slimtest-0.1.0/src/slimtest/expand.py +141 -0
- slimtest-0.1.0/src/slimtest/factory.py +191 -0
- slimtest-0.1.0/src/slimtest/manifest.py +211 -0
- slimtest-0.1.0/src/slimtest/model_parser.py +197 -0
- slimtest-0.1.0/src/slimtest/parametrize.py +119 -0
- slimtest-0.1.0/src/slimtest/result_parser.py +127 -0
- slimtest-0.1.0/src/slimtest/runner.py +140 -0
- slimtest-0.1.0/src/slimtest/scenario.py +60 -0
- slimtest-0.1.0/src/slimtest/schema.py +182 -0
- slimtest-0.1.0/src/slimtest/selector.py +52 -0
- slimtest-0.1.0/tests/__init__.py +0 -0
- slimtest-0.1.0/tests/conftest.py +41 -0
- slimtest-0.1.0/tests/test_cli.py +212 -0
- slimtest-0.1.0/tests/test_compile.py +671 -0
- slimtest-0.1.0/tests/test_config.py +48 -0
- slimtest-0.1.0/tests/test_dbt_project.py +106 -0
- slimtest-0.1.0/tests/test_dbt_runner.py +156 -0
- slimtest-0.1.0/tests/test_deep_merge.py +99 -0
- slimtest-0.1.0/tests/test_expand.py +332 -0
- slimtest-0.1.0/tests/test_factory.py +186 -0
- slimtest-0.1.0/tests/test_manifest.py +222 -0
- slimtest-0.1.0/tests/test_model_parser.py +217 -0
- slimtest-0.1.0/tests/test_parametrize.py +189 -0
- slimtest-0.1.0/tests/test_result_parser.py +156 -0
- slimtest-0.1.0/tests/test_runner.py +167 -0
- slimtest-0.1.0/tests/test_scenario.py +68 -0
- slimtest-0.1.0/tests/test_schema.py +180 -0
- slimtest-0.1.0/tests/test_selector.py +104 -0
- slimtest-0.1.0/uv.lock +688 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# All files require @Hisaaki-Kato's review by default.
|
|
2
|
+
# CI/release workflows are especially security-sensitive; called out
|
|
3
|
+
# explicitly so the ownership stays intentional even if the catch-all
|
|
4
|
+
# rule is ever relaxed for community contributions.
|
|
5
|
+
* @Hisaaki-Kato
|
|
6
|
+
/.github/ @Hisaaki-Kato
|
|
7
|
+
/.github/workflows/ @Hisaaki-Kato
|
|
8
|
+
/pyproject.toml @Hisaaki-Kato
|
|
9
|
+
/LICENSE @Hisaaki-Kato
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: pytest / ruff / mypy (Python ${{ matrix.python-version }})
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3.2.4
|
|
26
|
+
with:
|
|
27
|
+
enable-cache: true
|
|
28
|
+
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
run: uv python install ${{ matrix.python-version }}
|
|
31
|
+
|
|
32
|
+
- name: Sync dependencies
|
|
33
|
+
run: uv sync --python ${{ matrix.python-version }}
|
|
34
|
+
|
|
35
|
+
- name: Lint (ruff check)
|
|
36
|
+
run: uv run --python ${{ matrix.python-version }} ruff check .
|
|
37
|
+
|
|
38
|
+
- name: Format (ruff format --check)
|
|
39
|
+
run: uv run --python ${{ matrix.python-version }} ruff format --check .
|
|
40
|
+
|
|
41
|
+
- name: Type-check (mypy)
|
|
42
|
+
run: uv run --python ${{ matrix.python-version }} mypy src tests
|
|
43
|
+
|
|
44
|
+
- name: Test (pytest)
|
|
45
|
+
run: uv run --python ${{ matrix.python-version }} pytest -q
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Release (TestPyPI dry run)
|
|
2
|
+
|
|
3
|
+
# Manually-triggered dry run against https://test.pypi.org. Use before
|
|
4
|
+
# a real `v*` tag: exercises the full pipeline (build + quality gate +
|
|
5
|
+
# Trusted Publisher upload) without burning a version number on the
|
|
6
|
+
# real PyPI.
|
|
7
|
+
#
|
|
8
|
+
# Setup on TestPyPI: register a Trusted Publisher at
|
|
9
|
+
# https://test.pypi.org/manage/account/publishing/
|
|
10
|
+
# with
|
|
11
|
+
# Owner: Hisaaki-Kato
|
|
12
|
+
# Repository: slimtest
|
|
13
|
+
# Workflow: release-testpypi.yml
|
|
14
|
+
# Environment: testpypi
|
|
15
|
+
#
|
|
16
|
+
# In GitHub, create the `testpypi` environment mirroring `pypi`
|
|
17
|
+
# (Settings -> Environments) with your user as a required reviewer,
|
|
18
|
+
# so the workflow pauses for a manual approval before uploading.
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
build-and-publish:
|
|
25
|
+
name: Build and publish to TestPyPI
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
environment:
|
|
28
|
+
name: testpypi
|
|
29
|
+
url: https://test.pypi.org/p/slimtest
|
|
30
|
+
permissions:
|
|
31
|
+
# PyPI Trusted Publishers OIDC exchange.
|
|
32
|
+
id-token: write
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
35
|
+
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3.2.4
|
|
38
|
+
|
|
39
|
+
- name: Set up Python
|
|
40
|
+
run: uv python install 3.12
|
|
41
|
+
|
|
42
|
+
- name: Sync dependencies
|
|
43
|
+
run: uv sync
|
|
44
|
+
|
|
45
|
+
- name: Run full quality gate
|
|
46
|
+
run: |
|
|
47
|
+
uv run ruff check .
|
|
48
|
+
uv run ruff format --check .
|
|
49
|
+
uv run mypy src tests
|
|
50
|
+
uv run pytest -q
|
|
51
|
+
|
|
52
|
+
- name: Build sdist and wheel
|
|
53
|
+
run: uv build
|
|
54
|
+
|
|
55
|
+
- name: Publish to TestPyPI
|
|
56
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
57
|
+
with:
|
|
58
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI when a `v*` tag is pushed.
|
|
4
|
+
#
|
|
5
|
+
# Uses PyPI Trusted Publishers (OIDC): no API token required in secrets.
|
|
6
|
+
# Setup: once the workflow file lands on `main`, register the publisher
|
|
7
|
+
# at https://pypi.org/manage/account/publishing/ with
|
|
8
|
+
# repo: Hisaaki-Kato/slimtest
|
|
9
|
+
# workflow: release.yml
|
|
10
|
+
# env: (leave blank)
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
push:
|
|
14
|
+
tags:
|
|
15
|
+
- "v*"
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build-and-publish:
|
|
19
|
+
name: Build and publish to PyPI
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
environment:
|
|
22
|
+
name: pypi
|
|
23
|
+
url: https://pypi.org/p/slimtest
|
|
24
|
+
permissions:
|
|
25
|
+
# Required for PyPI Trusted Publishers OIDC exchange.
|
|
26
|
+
id-token: write
|
|
27
|
+
# Required for softprops/action-gh-release to create the GH Release.
|
|
28
|
+
contents: write
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
31
|
+
|
|
32
|
+
- name: Install uv
|
|
33
|
+
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3.2.4
|
|
34
|
+
|
|
35
|
+
- name: Set up Python
|
|
36
|
+
run: uv python install 3.12
|
|
37
|
+
|
|
38
|
+
- name: Sync dependencies
|
|
39
|
+
run: uv sync
|
|
40
|
+
|
|
41
|
+
- name: Verify tag matches pyproject.toml version
|
|
42
|
+
# Guard against publishing when the pushed `v*` tag disagrees
|
|
43
|
+
# with the version hard-coded in pyproject.toml -- otherwise a
|
|
44
|
+
# `v0.2.0` tag would happily publish `0.1.0` (or vice versa),
|
|
45
|
+
# leaving GitHub tag / Release and PyPI pointing at different
|
|
46
|
+
# artefacts.
|
|
47
|
+
run: |
|
|
48
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
49
|
+
PYPROJECT_VERSION=$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
|
|
50
|
+
if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]; then
|
|
51
|
+
echo "::error::tag '${GITHUB_REF_NAME}' says $TAG_VERSION but pyproject.toml says $PYPROJECT_VERSION"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
echo "✓ tag and pyproject.toml agree on version $PYPROJECT_VERSION"
|
|
55
|
+
|
|
56
|
+
- name: Run full quality gate
|
|
57
|
+
run: |
|
|
58
|
+
uv run ruff check .
|
|
59
|
+
uv run ruff format --check .
|
|
60
|
+
uv run mypy src tests
|
|
61
|
+
uv run pytest -q
|
|
62
|
+
|
|
63
|
+
- name: Build sdist and wheel
|
|
64
|
+
run: uv build
|
|
65
|
+
|
|
66
|
+
- name: Publish to PyPI
|
|
67
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
68
|
+
|
|
69
|
+
- name: Create GitHub Release
|
|
70
|
+
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
|
71
|
+
with:
|
|
72
|
+
generate_release_notes: true
|
|
73
|
+
files: dist/*
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# --- Python ---
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
|
|
8
|
+
# --- Distribution / build ---
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
*.egg
|
|
12
|
+
*.egg-info/
|
|
13
|
+
.eggs/
|
|
14
|
+
wheels/
|
|
15
|
+
share/python-wheels/
|
|
16
|
+
pip-wheel-metadata/
|
|
17
|
+
|
|
18
|
+
# --- Virtual envs ---
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
ENV/
|
|
23
|
+
|
|
24
|
+
# --- uv ---
|
|
25
|
+
.uv/
|
|
26
|
+
|
|
27
|
+
# --- Tool caches ---
|
|
28
|
+
.pytest_cache/
|
|
29
|
+
.mypy_cache/
|
|
30
|
+
.ruff_cache/
|
|
31
|
+
.tox/
|
|
32
|
+
.nox/
|
|
33
|
+
.cache/
|
|
34
|
+
|
|
35
|
+
# --- Coverage ---
|
|
36
|
+
.coverage
|
|
37
|
+
.coverage.*
|
|
38
|
+
htmlcov/
|
|
39
|
+
coverage.xml
|
|
40
|
+
*.cover
|
|
41
|
+
.hypothesis/
|
|
42
|
+
|
|
43
|
+
# --- Type-check / linter side files ---
|
|
44
|
+
.dmypy.json
|
|
45
|
+
dmypy.json
|
|
46
|
+
|
|
47
|
+
# --- IDE / editor ---
|
|
48
|
+
.idea/
|
|
49
|
+
.vscode/
|
|
50
|
+
*.swp
|
|
51
|
+
*.swo
|
|
52
|
+
*~
|
|
53
|
+
|
|
54
|
+
# --- OS ---
|
|
55
|
+
.DS_Store
|
|
56
|
+
Thumbs.db
|
|
57
|
+
|
|
58
|
+
# --- slimtest runtime output (when running against a project at the root) ---
|
|
59
|
+
target/
|
|
60
|
+
logs/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-07-07
|
|
8
|
+
|
|
9
|
+
Initial public release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- CLI: `slimtest compile` and `slimtest unittest` with `--project-dir` and `--select` options.
|
|
14
|
+
- Factory + trait + override DSL under `tests/slimtest_factories/*.yml`, resolved via deep-merge (`base` → `trait` → `override`).
|
|
15
|
+
- Test blocks under `models[*].meta.slimtest.unit_tests` in dbt model YAML.
|
|
16
|
+
- `given:` accepts the more compact `{upstream: [rows]}` dict form (instead of dbt's list-of-pairs).
|
|
17
|
+
- `model:` field inferred from the enclosing `models[*].name`.
|
|
18
|
+
- Automatic `ref()` vs `source()` disambiguation via `target/manifest.json`.
|
|
19
|
+
- Upstream auto-fill: any upstream that a model depends on but the test omits is stitched in from the same-named factory's `base` row (opt-out via `slimtest.yml`).
|
|
20
|
+
- `parametrize:` block to expand one test into N via `$column` substitution, with case-id naming.
|
|
21
|
+
- `scenarios:` block under `meta.slimtest` for shared `given:` bundles referenced by `scenario: <name>`.
|
|
22
|
+
- Partial `expect:` — only list the columns the test asserts about (dbt ignores unmentioned columns).
|
|
23
|
+
- Source map: failures from `dbt test` are mapped back to `<model.yml>:<line>` in the CLI output.
|
|
24
|
+
- Test name prefix `slimtest__<model>__<name>` guarantees no collision with hand-written dbt `unit_tests`.
|
|
25
|
+
- `--select` accepts a model name, the user-written test name, the prefixed test name, `test_type:unit`, or a comma-separated union.
|
|
26
|
+
- `--verbose` / `-v` flag: INFO-level notices (e.g. auto-injected upstream rows) are hidden by default and printed under verbose.
|
|
27
|
+
- `slimtest.yml` project config with `factories_path`, `generated_yml_path`, `auto_fill_upstreams`.
|
|
28
|
+
- Works with any dbt adapter; validated end-to-end against dbt-duckdb.
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
|
|
32
|
+
- Project README with why / install / quick start / concepts / coverage / commands / development.
|
|
33
|
+
- Runnable end-to-end example at `examples/sample_dbt_project/` (DuckDB, 5 test definitions → 8 dbt unit tests).
|
|
34
|
+
- MIT `LICENSE`.
|
|
35
|
+
- Non-affiliation disclaimer and dbt trademark notice in README.
|
|
36
|
+
|
|
37
|
+
[Unreleased]: https://github.com/Hisaaki-Kato/slimtest/compare/v0.1.0...HEAD
|
|
38
|
+
[0.1.0]: https://github.com/Hisaaki-Kato/slimtest/releases/tag/v0.1.0
|
slimtest-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hisaaki-Kato
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
slimtest-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slimtest
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Factory + trait DSL on top of dbt unit tests.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Hisaaki-Kato/slimtest
|
|
6
|
+
Project-URL: Repository, https://github.com/Hisaaki-Kato/slimtest
|
|
7
|
+
Project-URL: Issues, https://github.com/Hisaaki-Kato/slimtest/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Hisaaki-Kato/slimtest/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Hisaaki Kato
|
|
10
|
+
Maintainer: Hisaaki Kato
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: dbt,factory,testing,unit-test,yaml
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Software Development :: Testing :: Unit
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: pydantic<3,>=2.6
|
|
28
|
+
Requires-Dist: ruamel-yaml<1,>=0.18
|
|
29
|
+
Requires-Dist: typer<1,>=0.12
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# slimtest
|
|
33
|
+
|
|
34
|
+
> Factory + trait DSL for dbt™'s `unit_tests`. Removes column-level repetition from your test YAML so the tests stay readable as the upstream schema grows.
|
|
35
|
+
|
|
36
|
+
`slimtest` is **not** a replacement for `dbt test` — it's a thin pre-processor that lets you write `unit_tests` in a far more compact form, then compiles them into the standard YAML dbt already knows how to run.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
your factory + trait YAML
|
|
40
|
+
│
|
|
41
|
+
│ $ slimtest compile
|
|
42
|
+
▼
|
|
43
|
+
target/slimtest/<model>.generated.yml ── standard dbt unit_tests
|
|
44
|
+
│
|
|
45
|
+
│ $ dbt test --select <generated names> (slimtest unittest does this for you)
|
|
46
|
+
▼
|
|
47
|
+
results (with failures mapped back to your source line)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
### Install
|
|
52
|
+
|
|
53
|
+
Recommended — install as a tool managed by `uv`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv tool install slimtest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or as a dev dependency of your dbt project:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv add --dev slimtest
|
|
63
|
+
# or, with pip:
|
|
64
|
+
pip install slimtest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
(Until the package is published, replace `slimtest` with the path to a local clone: `uv tool install /path/to/slimtest`.)
|
|
68
|
+
|
|
69
|
+
### Dependencies
|
|
70
|
+
|
|
71
|
+
| Layer | Required |
|
|
72
|
+
| ------------ | -------------------------------------------------------------- |
|
|
73
|
+
| Python | 3.11+ |
|
|
74
|
+
| Runtime libs | `pydantic >=2.6,<3`, `ruamel.yaml >=0.18,<1`, `typer >=0.12,<1` |
|
|
75
|
+
| dbt | `dbt-core >=1.10` + an adapter — provided by *your* dbt project, **not** bundled with `slimtest` |
|
|
76
|
+
|
|
77
|
+
`slimtest` shells out to whatever `dbt` is on `PATH`. Install the dbt adapter that matches your project (`dbt-duckdb`, `dbt-bigquery`, …).
|
|
78
|
+
|
|
79
|
+
### Run Slimtest
|
|
80
|
+
|
|
81
|
+
Assuming you already have a dbt project:
|
|
82
|
+
|
|
83
|
+
1. Tell dbt to read the generated YAML — add `target/slimtest` to `model-paths` in `dbt_project.yml`:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
model-paths: ["models", "target/slimtest"]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
2. Add a factory at `tests/slimtest_factories/customers.yml`:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
factories:
|
|
93
|
+
customers:
|
|
94
|
+
base:
|
|
95
|
+
customer_id: 1
|
|
96
|
+
country: JP
|
|
97
|
+
tier: standard
|
|
98
|
+
traits:
|
|
99
|
+
premium: {tier: premium}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
3. Write the test under `meta.slimtest` in your `model.yml`:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
models:
|
|
106
|
+
- name: dim_customers
|
|
107
|
+
meta:
|
|
108
|
+
slimtest:
|
|
109
|
+
unit_tests:
|
|
110
|
+
- name: premium_tier_propagates
|
|
111
|
+
given:
|
|
112
|
+
customers:
|
|
113
|
+
- {factory: customers, trait: premium, override: {customer_id: 42}}
|
|
114
|
+
expect:
|
|
115
|
+
- {customer_id: 42, tier: premium}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
4. Run it from your dbt project root:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
dbt seed && dbt run # upstreams must exist before unit tests can introspect their schema
|
|
122
|
+
slimtest unittest
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Failures look like this — note the source location pointing back to the YAML you wrote:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
[slimtest] 0/1 passed; 1 failed, 0 errored, 0 skipped.
|
|
129
|
+
FAIL: premium_tier_propagates (models/dim_customers.yml:5)
|
|
130
|
+
actual differs from expected:
|
|
131
|
+
@@ ,customer_id,tier
|
|
132
|
+
→ ,42 ,standard→premium
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For a more complete example with four upstreams and four scenarios, see [`examples/sample_dbt_project/`](examples/sample_dbt_project/).
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## Why
|
|
139
|
+
|
|
140
|
+
dbt's built-in `unit_tests` (dbt 1.8+) ask you to spell out every upstream row in full. With wide denormalized tables, or models that join several upstreams, the YAML gets verbose fast — the pain is the **column × row** repetition inside each test, not the number of tests.
|
|
141
|
+
|
|
142
|
+
`slimtest` lets you say "this is what a row of `customers` normally looks like; in this test, override the tier", instead of typing the same 15 columns into every row.
|
|
143
|
+
|
|
144
|
+
### Tiny taste
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
# Before: dbt-native unit_tests. One of four upstream blocks shown.
|
|
148
|
+
unit_tests:
|
|
149
|
+
- name: test_full_lifecycle
|
|
150
|
+
model: order_statuses
|
|
151
|
+
given:
|
|
152
|
+
- input: ref('order_events')
|
|
153
|
+
rows:
|
|
154
|
+
- {event_id: 1, order_id: 5001, event_type: placed, occurred_at: '2024-01-01 09:00:00', cancelled_at: null}
|
|
155
|
+
- {event_id: 2, order_id: 5001, event_type: paid, occurred_at: '2024-01-01 09:30:00', cancelled_at: null}
|
|
156
|
+
- {event_id: 3, order_id: 5001, event_type: shipped, occurred_at: '2024-01-02 14:00:00', cancelled_at: null}
|
|
157
|
+
- {event_id: 4, order_id: 5001, event_type: delivered, occurred_at: '2024-01-04 11:00:00', cancelled_at: null}
|
|
158
|
+
- input: ref('orders')
|
|
159
|
+
rows:
|
|
160
|
+
- {order_id: 5001, customer_id: 8001, product_id: 3001, total_amount: 12000, currency: JPY, channel: web, placed_at: '2024-01-01 09:00:00', cancelled_at: null}
|
|
161
|
+
- input: ref('customers')
|
|
162
|
+
rows:
|
|
163
|
+
- {customer_id: 8001, country: JP, tier: standard, signup_source: organic}
|
|
164
|
+
- input: ref('products')
|
|
165
|
+
rows:
|
|
166
|
+
- {product_id: 3001, category: apparel, price: 12000}
|
|
167
|
+
expect:
|
|
168
|
+
rows:
|
|
169
|
+
- {status: pending, valid_from: '2024-01-01 09:00:00', valid_to: '2024-01-01 09:30:00'}
|
|
170
|
+
- {status: processing, valid_from: '2024-01-01 09:30:00', valid_to: '2024-01-02 14:00:00'}
|
|
171
|
+
- {status: shipping, valid_from: '2024-01-02 14:00:00', valid_to: '2024-01-04 11:00:00'}
|
|
172
|
+
- {status: completed, valid_from: '2024-01-04 11:00:00', valid_to: null}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
# After: slimtest. Same assertion, every row carries only what changes.
|
|
177
|
+
models:
|
|
178
|
+
- name: order_statuses
|
|
179
|
+
meta:
|
|
180
|
+
slimtest:
|
|
181
|
+
unit_tests:
|
|
182
|
+
- name: test_full_lifecycle
|
|
183
|
+
given:
|
|
184
|
+
order_events:
|
|
185
|
+
- {factory: order_events, trait: placed, override: {event_id: 1, occurred_at: '2024-01-01 09:00:00'}}
|
|
186
|
+
- {factory: order_events, trait: paid, override: {event_id: 2, occurred_at: '2024-01-01 09:30:00'}}
|
|
187
|
+
- {factory: order_events, trait: shipped, override: {event_id: 3, occurred_at: '2024-01-02 14:00:00'}}
|
|
188
|
+
- {factory: order_events, trait: delivered, override: {event_id: 4, occurred_at: '2024-01-04 11:00:00'}}
|
|
189
|
+
# orders / customers / products are auto-filled from each factory's base row
|
|
190
|
+
expect:
|
|
191
|
+
- {status: pending, valid_from: '2024-01-01 09:00:00', valid_to: '2024-01-01 09:30:00'}
|
|
192
|
+
- {status: processing, valid_from: '2024-01-01 09:30:00', valid_to: '2024-01-02 14:00:00'}
|
|
193
|
+
- {status: shipping, valid_from: '2024-01-02 14:00:00', valid_to: '2024-01-04 11:00:00'}
|
|
194
|
+
- {status: completed, valid_from: '2024-01-04 11:00:00', valid_to: null}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
…plus a one-time `tests/slimtest_factories/order_events.yml`:
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
factories:
|
|
201
|
+
order_events:
|
|
202
|
+
base:
|
|
203
|
+
event_id: 10000
|
|
204
|
+
order_id: 5001
|
|
205
|
+
occurred_at: '2024-01-01 09:00:00'
|
|
206
|
+
cancelled_at: null
|
|
207
|
+
traits:
|
|
208
|
+
placed: {event_type: placed}
|
|
209
|
+
paid: {event_type: paid}
|
|
210
|
+
shipped: {event_type: shipped}
|
|
211
|
+
delivered: {event_type: delivered}
|
|
212
|
+
cancelled: {event_type: cancelled}
|
|
213
|
+
returned: {event_type: returned}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The full runnable version of this lives in [`examples/sample_dbt_project/`](examples/sample_dbt_project/).
|
|
217
|
+
|
|
218
|
+
## Core concepts
|
|
219
|
+
|
|
220
|
+
### Factory
|
|
221
|
+
|
|
222
|
+
One factory represents "one row of upstream model X". By convention the factory **name matches the upstream name** so `slimtest` can auto-fill missing upstreams (see below). Factories live in `tests/slimtest_factories/*.yml`.
|
|
223
|
+
|
|
224
|
+
### `base` and `traits`
|
|
225
|
+
|
|
226
|
+
- **`base`**: the "default row" — common defaults shared by every variant.
|
|
227
|
+
- **`traits`**: named diffs that overlay `base` to represent state / variant (`premium` user, `placed` event, `us` customer, …).
|
|
228
|
+
|
|
229
|
+
In a test, you reference a row as `{factory: <name>, trait: <name>, override: {...}}`. Layers are **deep-merged** in this order: `base → trait → override`.
|
|
230
|
+
|
|
231
|
+
### `override`
|
|
232
|
+
|
|
233
|
+
Per-row, per-test diff applied on top of `base + trait`. This is where test-specific IDs and per-case values go.
|
|
234
|
+
|
|
235
|
+
### Upstream auto-fill
|
|
236
|
+
|
|
237
|
+
If a test's `given:` omits an upstream and a factory with the same name exists, `slimtest` injects one `base` row of that factory automatically. A happy-path test that only cares about events doesn't have to repeat the boilerplate of orders / customers / products / etc.
|
|
238
|
+
|
|
239
|
+
### Scenario
|
|
240
|
+
|
|
241
|
+
A reusable bundle of `given:` rows declared under `meta.slimtest.scenarios.<name>`. Tests reference it with `scenario: <name>` and `slimtest` merges its `given:` into the test's (test wins per upstream key). Used to factor out "all the supporting rows for one order shape" so tests only carry their differing bits.
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
meta:
|
|
245
|
+
slimtest:
|
|
246
|
+
scenarios:
|
|
247
|
+
premium_jp_books:
|
|
248
|
+
given:
|
|
249
|
+
orders: [{factory: orders, override: {order_id: 5002}}]
|
|
250
|
+
customers: [{factory: customers, trait: premium, override: {customer_id: 8002}}]
|
|
251
|
+
products: [{factory: products, trait: books}]
|
|
252
|
+
unit_tests:
|
|
253
|
+
- name: cancellation_after_payment
|
|
254
|
+
scenario: premium_jp_books # orders/customers/products provided by the scenario
|
|
255
|
+
given:
|
|
256
|
+
order_events: [...]
|
|
257
|
+
expect: [...]
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Parametrize
|
|
261
|
+
|
|
262
|
+
A table-driven sugar that expands one test into N by substituting `$<column>` placeholders. Reduces "for each event type, the status maps to X" style tests from N copies to 1 block.
|
|
263
|
+
|
|
264
|
+
```yaml
|
|
265
|
+
unit_tests:
|
|
266
|
+
- name: event_maps_to_status
|
|
267
|
+
parametrize:
|
|
268
|
+
columns: [event_trait, expected_status]
|
|
269
|
+
cases:
|
|
270
|
+
- [placed, pending]
|
|
271
|
+
- [paid, processing]
|
|
272
|
+
- [shipped, shipping]
|
|
273
|
+
- [delivered, completed]
|
|
274
|
+
given:
|
|
275
|
+
order_events:
|
|
276
|
+
- {factory: order_events, trait: $event_trait, override: {event_id: 1}}
|
|
277
|
+
expect:
|
|
278
|
+
- {status: $expected_status}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Each case becomes a separate dbt unit test named `slimtest__<model>__event_maps_to_status__<case_id>`. `case_id` defaults to the first column's value (so the example yields `__placed`, `__paid`, `__shipped`, `__delivered`); set an explicit `id:` column to override.
|
|
282
|
+
|
|
283
|
+
### Source map
|
|
284
|
+
|
|
285
|
+
Failures from `dbt test` are mapped back to the source `model.yml` line where you wrote the test, so you don't have to grep through generated YAML.
|
|
286
|
+
|
|
287
|
+
### Test name prefix
|
|
288
|
+
|
|
289
|
+
Every generated test is named `slimtest__<model>__<original_name>`, guaranteeing no collision with hand-written `unit_tests` in the same project.
|
|
290
|
+
|
|
291
|
+
## Coverage
|
|
292
|
+
|
|
293
|
+
`slimtest` is a wrapper around dbt's **`unit_tests`** feature. It does not replace `dbt test`; it generates the YAML that `dbt test` consumes.
|
|
294
|
+
|
|
295
|
+
| Test kind | Handled by | `slimtest` covers? |
|
|
296
|
+
| ---------------------------------------------- | ------------- | --------------------------------- |
|
|
297
|
+
| Unit tests (dbt 1.8+ `unit_tests:`) | `slimtest` | ✅ |
|
|
298
|
+
| Existing top-level `unit_tests:` (dbt-native) | dbt | passthrough (untouched) |
|
|
299
|
+
| Data tests (`unique`, `not_null`, etc.) | dbt | ❌ — run `dbt test` directly |
|
|
300
|
+
| Singular tests (`tests/*.sql`) | dbt | ❌ — run `dbt test` directly |
|
|
301
|
+
| Snapshot / seed / macro tests | (none) | ❌ — dbt itself does not support these |
|
|
302
|
+
|
|
303
|
+
### Currently supported
|
|
304
|
+
|
|
305
|
+
- factory + trait + override with deep-merge semantics
|
|
306
|
+
- `given:` dict form (`{upstream: [rows]}`) instead of dbt's list-of-pairs form
|
|
307
|
+
- `model:` field inferred from the enclosing `models[*].name`
|
|
308
|
+
- `scenario:` — shared `given:` bundle declared under `meta.slimtest.scenarios`
|
|
309
|
+
- `parametrize:` — `$column` substitution that expands one test into N
|
|
310
|
+
- `ref()` vs `source()` automatic disambiguation via `manifest.json`
|
|
311
|
+
- upstream auto-fill via same-named factories
|
|
312
|
+
- partial `expect:` — only list the columns you care about; dbt ignores unmentioned columns
|
|
313
|
+
- source map: failure → `models/foo.yml:42`
|
|
314
|
+
- `--select` accepts a model name, the user-written test name, the prefixed test name, `test_type:unit`, or a comma-separated union
|
|
315
|
+
- works with any dbt adapter (validated on DuckDB; BigQuery and Postgres are mechanically the same)
|
|
316
|
+
- `slimtest.yml` config file with `factories_path`, `generated_yml_path`, `auto_fill_upstreams`
|
|
317
|
+
|
|
318
|
+
### Not yet supported
|
|
319
|
+
|
|
320
|
+
- `slimtest validate` subcommand (factory / model.yml lint)
|
|
321
|
+
- `slimtest factories list`
|
|
322
|
+
- factory composition (one row built from multiple factories)
|
|
323
|
+
- factory inheritance (one factory extends another)
|
|
324
|
+
- stale generated-yml detection (no source-hash invariant yet)
|
|
325
|
+
- ephemeral upstreams that require `format: sql` fixtures
|
|
326
|
+
- watch mode / IDE integration / LSP completion
|
|
327
|
+
- dbt's `dbtRunner` in-process API (we shell out to `dbt`, so each `unittest` pays the dbt cold-start cost)
|
|
328
|
+
|
|
329
|
+
## Commands
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
slimtest compile [--project-dir DIR] [--select SEL]
|
|
333
|
+
Expand slimtest extension YAML into target/slimtest/*.generated.yml.
|
|
334
|
+
Does NOT invoke dbt. Useful for debugging the expansion.
|
|
335
|
+
|
|
336
|
+
slimtest unittest [--project-dir DIR] [--select SEL]
|
|
337
|
+
1. dbt parse (refresh target/manifest.json)
|
|
338
|
+
2. slimtest compile
|
|
339
|
+
3. dbt test --select <generated test names>
|
|
340
|
+
4. Map failures back to source location.
|
|
341
|
+
|
|
342
|
+
slimtest --version
|
|
343
|
+
slimtest --help
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The `--select` selector accepts:
|
|
347
|
+
|
|
348
|
+
- a model name (`orders`)
|
|
349
|
+
- a user-written test name (`premium_tier_propagates`)
|
|
350
|
+
- a prefixed name (`slimtest__orders__premium_tier_propagates`)
|
|
351
|
+
- `test_type:unit` (no-op; everything `slimtest` emits is a unit test)
|
|
352
|
+
- comma-separated tokens (`orders,customers`)
|
|
353
|
+
|
|
354
|
+
## Development
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
uv sync # install runtime + dev deps into .venv
|
|
358
|
+
uv run pytest # run the test suite
|
|
359
|
+
uv run ruff check . # lint
|
|
360
|
+
uv run ruff format . # format
|
|
361
|
+
uv run mypy src tests # type check
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Conventions:
|
|
365
|
+
|
|
366
|
+
- `ruff` with line-length 88 (E501 is a hard error)
|
|
367
|
+
- `mypy --strict` on `src/`; tests are checked with `disallow_untyped_defs` off
|
|
368
|
+
- `pydantic` v2 models for every user-facing schema; runtime data is plain `dict[str, Any]`
|
|
369
|
+
- dbt is mocked in pytest so the test suite needs no warehouse and no installed adapter
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
374
|
+
|
|
375
|
+
`slimtest` is an independent open-source project and is not affiliated with dbt Labs. `dbt` is a trademark of dbt Labs, LLC.
|