dax-quax 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.
- dax_quax-0.1.0/.github/workflows/ci.yml +46 -0
- dax_quax-0.1.0/.github/workflows/release.yml +49 -0
- dax_quax-0.1.0/.gitignore +53 -0
- dax_quax-0.1.0/LICENSE +21 -0
- dax_quax-0.1.0/PKG-INFO +183 -0
- dax_quax-0.1.0/README.md +146 -0
- dax_quax-0.1.0/pyproject.toml +89 -0
- dax_quax-0.1.0/src/dax_quax/__init__.py +86 -0
- dax_quax-0.1.0/src/dax_quax/analysis/__init__.py +1 -0
- dax_quax-0.1.0/src/dax_quax/analysis/baseline.py +433 -0
- dax_quax-0.1.0/src/dax_quax/analysis/diagram.py +296 -0
- dax_quax-0.1.0/src/dax_quax/analysis/lineage.py +416 -0
- dax_quax-0.1.0/src/dax_quax/analysis/report_findings.py +265 -0
- dax_quax-0.1.0/src/dax_quax/analysis/timings.py +460 -0
- dax_quax-0.1.0/src/dax_quax/analysis/usage.py +486 -0
- dax_quax-0.1.0/src/dax_quax/analysis/vertipaq.py +132 -0
- dax_quax-0.1.0/src/dax_quax/cli/__init__.py +706 -0
- dax_quax-0.1.0/src/dax_quax/cli/__main__.py +10 -0
- dax_quax-0.1.0/src/dax_quax/dax/__init__.py +32 -0
- dax_quax-0.1.0/src/dax_quax/dax/extract.py +142 -0
- dax_quax-0.1.0/src/dax_quax/dax/resolve.py +203 -0
- dax_quax-0.1.0/src/dax_quax/dax/tokenize.py +166 -0
- dax_quax-0.1.0/src/dax_quax/errors.py +45 -0
- dax_quax-0.1.0/src/dax_quax/mcp/__init__.py +12 -0
- dax_quax-0.1.0/src/dax_quax/mcp/server.py +129 -0
- dax_quax-0.1.0/src/dax_quax/mcp/tools.py +564 -0
- dax_quax-0.1.0/src/dax_quax/model/__init__.py +380 -0
- dax_quax-0.1.0/src/dax_quax/render/__init__.py +8 -0
- dax_quax-0.1.0/src/dax_quax/render/_escape.py +56 -0
- dax_quax-0.1.0/src/dax_quax/render/diagram.py +253 -0
- dax_quax-0.1.0/src/dax_quax/render/editor.py +51 -0
- dax_quax-0.1.0/src/dax_quax/render/graph.py +274 -0
- dax_quax-0.1.0/src/dax_quax/render/report.py +313 -0
- dax_quax-0.1.0/src/dax_quax/render/serve.py +407 -0
- dax_quax-0.1.0/src/dax_quax/render/templates/_styles.html.j2 +157 -0
- dax_quax-0.1.0/src/dax_quax/render/templates/report.html.j2 +329 -0
- dax_quax-0.1.0/src/dax_quax/render/templates/workspace.html.j2 +202 -0
- dax_quax-0.1.0/src/dax_quax/render/workspace.py +201 -0
- dax_quax-0.1.0/src/dax_quax/report/__init__.py +56 -0
- dax_quax-0.1.0/src/dax_quax/report/pbir.py +366 -0
- dax_quax-0.1.0/src/dax_quax/runtime.py +131 -0
- dax_quax-0.1.0/src/dax_quax/sources/__init__.py +1 -0
- dax_quax-0.1.0/src/dax_quax/sources/dmv.py +837 -0
- dax_quax-0.1.0/src/dax_quax/sources/file.py +442 -0
- dax_quax-0.1.0/src/dax_quax/sources/live.py +473 -0
- dax_quax-0.1.0/src/dax_quax/sources/pbip.py +673 -0
- dax_quax-0.1.0/src/dax_quax/sources/tmdl.py +305 -0
- dax_quax-0.1.0/src/dax_quax/sources/trace.py +357 -0
- dax_quax-0.1.0/src/dax_quax/sources/workspace.py +379 -0
- dax_quax-0.1.0/tests/conftest.py +87 -0
- dax_quax-0.1.0/tests/fixtures/rich/rowsets.json +560 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.Report/definition/bookmarks/b1.json +19 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.Report/definition/pages/Overview/page.json +13 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.Report/definition/pages/Overview/visuals/v1/visual.json +37 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.Report/definition/pages/Overview/visuals/v2/visual.json +49 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.Report/definition/pages/pages.json +1 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.Report/definition/report.json +11 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.Report/definition.pbir +1 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/model.tmdl +16 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/relationships.tmdl +3 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/roles/Auditor.tmdl +4 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/roles/Sales Rep.tmdl +4 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/tables/Customer.tmdl +29 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/tables/LocalDateTable_8f3c1e2a-0000-4000-9000-1234567890ab.tmdl +10 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/tables/Product.tmdl +57 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/tables/Sales.tmdl +56 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition/tables/Time Intelligence.tmdl +18 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.SemanticModel/definition.pbism +4 -0
- dax_quax-0.1.0/tests/fixtures/rich_pbip/Contoso.pbip +13 -0
- dax_quax-0.1.0/tests/fixtures/synthetic/rowsets.json +330 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.Report/definition/bookmarks/b1.json +19 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.Report/definition/pages/Overview/page.json +13 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.Report/definition/pages/Overview/visuals/v1/visual.json +37 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.Report/definition/pages/Overview/visuals/v2/visual.json +49 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.Report/definition/pages/pages.json +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.Report/definition/report.json +11 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.Report/definition.pbir +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.SemanticModel/definition/model.tmdl +12 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.SemanticModel/definition/relationships.tmdl +3 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.SemanticModel/definition/tables/LocalDateTable_8f3c1e2a-0000-4000-9000-1234567890ab.tmdl +10 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.SemanticModel/definition/tables/Product.tmdl +40 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.SemanticModel/definition/tables/Sales.tmdl +54 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.SemanticModel/definition.pbism +4 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_pbip/Contoso.pbip +13 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.Report/definition/bookmarks/b1.json +19 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.Report/definition/pages/Overview/page.json +13 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.Report/definition/pages/Overview/visuals/v1/visual.json +37 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.Report/definition/pages/Overview/visuals/v2/visual.json +49 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.Report/definition/pages/pages.json +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.Report/definition/report.json +11 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.Report/definition.pbir +8 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.SemanticModel/definition/model.tmdl +12 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.SemanticModel/definition/relationships.tmdl +3 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.SemanticModel/definition/tables/LocalDateTable_8f3c1e2a-0000-4000-9000-1234567890ab.tmdl +10 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.SemanticModel/definition/tables/Product.tmdl +40 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.SemanticModel/definition/tables/Sales.tmdl +54 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.SemanticModel/definition.pbism +4 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Contoso.pbip +3 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Exec.Report/definition/pages/Board/page.json +4 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Exec.Report/definition/pages/Board/visuals/v9/visual.json +38 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Exec.Report/definition/pages/pages.json +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Exec.Report/definition.pbir +8 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Orphan.SemanticModel/definition/model.tmdl +4 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Orphan.SemanticModel/definition/tables/Scratch.tmdl +10 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Orphan.SemanticModel/definition.pbism +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Regional.Report/definition/pages/Map/page.json +4 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Regional.Report/definition/pages/Map/visuals/v1/visual.json +26 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Regional.Report/definition/pages/pages.json +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Regional.Report/definition.pbir +14 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Regional.SemanticModel/definition/model.tmdl +4 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Regional.SemanticModel/definition/tables/Region.tmdl +15 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Regional.SemanticModel/definition.pbism +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Stray.Report/definition/pages/P/page.json +4 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Stray.Report/definition/pages/P/visuals/v1/visual.json +26 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Stray.Report/definition/pages/pages.json +1 -0
- dax_quax-0.1.0/tests/fixtures/synthetic_workspace/Stray.Report/definition.pbir +9 -0
- dax_quax-0.1.0/tests/test_baseline.py +314 -0
- dax_quax-0.1.0/tests/test_byconnection.py +192 -0
- dax_quax-0.1.0/tests/test_check_imports.py +71 -0
- dax_quax-0.1.0/tests/test_dax_extract.py +207 -0
- dax_quax-0.1.0/tests/test_dax_resolve.py +155 -0
- dax_quax-0.1.0/tests/test_demo_corpus.py +204 -0
- dax_quax-0.1.0/tests/test_diagram.py +249 -0
- dax_quax-0.1.0/tests/test_dmv.py +221 -0
- dax_quax-0.1.0/tests/test_file_source.py +371 -0
- dax_quax-0.1.0/tests/test_hardening.py +174 -0
- dax_quax-0.1.0/tests/test_lineage.py +322 -0
- dax_quax-0.1.0/tests/test_live_discovery.py +130 -0
- dax_quax-0.1.0/tests/test_locations.py +245 -0
- dax_quax-0.1.0/tests/test_mcp.py +489 -0
- dax_quax-0.1.0/tests/test_pbip.py +329 -0
- dax_quax-0.1.0/tests/test_r1_gaps.py +156 -0
- dax_quax-0.1.0/tests/test_r2_false_removes.py +300 -0
- dax_quax-0.1.0/tests/test_render.py +291 -0
- dax_quax-0.1.0/tests/test_report_findings.py +247 -0
- dax_quax-0.1.0/tests/test_report_pbir.py +114 -0
- dax_quax-0.1.0/tests/test_runtime_install.py +226 -0
- dax_quax-0.1.0/tests/test_serve.py +258 -0
- dax_quax-0.1.0/tests/test_serve_workspace.py +172 -0
- dax_quax-0.1.0/tests/test_timings.py +377 -0
- dax_quax-0.1.0/tests/test_usage.py +294 -0
- dax_quax-0.1.0/tests/test_vertipaq.py +71 -0
- dax_quax-0.1.0/tests/test_workspace.py +262 -0
- dax_quax-0.1.0/tests/test_workspace_render.py +178 -0
- dax_quax-0.1.0/tools/check_imports.py +146 -0
- dax_quax-0.1.0/tools/fetch_demo.py +55 -0
- dax_quax-0.1.0/tools/fetch_libs.py +76 -0
- dax_quax-0.1.0/tools/fetch_pbix.py +77 -0
- dax_quax-0.1.0/tools/record_fixture.py +93 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
# Ubuntu on purpose. CONVENTIONS §10 promises the suite runs with no Power BI Desktop,
|
|
11
|
+
# no .NET and no network; running it anywhere else would let that promise rot quietly.
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
# Both results, always. With fail-fast one version's failure cancels the other and
|
|
15
|
+
# hides whether the problem is the code or that one interpreter.
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
# Every version pyproject.toml's requires-python claims. ">=3.12" is a promise to
|
|
19
|
+
# anyone installing this, and a promise no job exercises is a guess. Widening
|
|
20
|
+
# requires-python means adding the version here in the same commit.
|
|
21
|
+
python: ["3.12", "3.13", "3.14"]
|
|
22
|
+
name: test (py${{ matrix.python }})
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
- uses: actions/setup-python@v7
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python }}
|
|
28
|
+
- run: pip install -e ".[dev,file,report,serve,mcp]"
|
|
29
|
+
# One version is enough for lint: ruff reads the target-version from pyproject.
|
|
30
|
+
- run: ruff check .
|
|
31
|
+
if: matrix.python == '3.12'
|
|
32
|
+
- run: pytest -q
|
|
33
|
+
|
|
34
|
+
imports-without-extras:
|
|
35
|
+
# Installed bare, so a dependency somebody added to the core requirements without
|
|
36
|
+
# declaring it in pyproject.toml shows up here and nowhere else. The check itself also
|
|
37
|
+
# blocks the known optional packages, which is why it runs as an ordinary test on a
|
|
38
|
+
# developer machine where every extra is present -- see tools/check_imports.py.
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
|
+
- uses: actions/setup-python@v7
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.12"
|
|
45
|
+
- run: pip install -e .
|
|
46
|
+
- run: python tools/check_imports.py
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
# Only the public copy publishes. The private repo carries this file so the two trees
|
|
10
|
+
# stay identical, and a tag pushed there must not race the real release to PyPI.
|
|
11
|
+
if: github.repository == 'S-CurveLabs/dax-quax'
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v7
|
|
15
|
+
- uses: actions/setup-python@v7
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
# A tag that disagrees with pyproject.toml would publish a version nobody asked for,
|
|
19
|
+
# and PyPI never lets a version number be reused.
|
|
20
|
+
- name: tag matches version
|
|
21
|
+
run: |
|
|
22
|
+
version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
23
|
+
test "v$version" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != v$version"; exit 1; }
|
|
24
|
+
- run: pip install -e ".[dev,file,report,serve,mcp]"
|
|
25
|
+
- run: pytest -q
|
|
26
|
+
- run: pip install build twine
|
|
27
|
+
- run: python -m build
|
|
28
|
+
- run: twine check --strict dist/*
|
|
29
|
+
- uses: actions/upload-artifact@v7
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
publish:
|
|
35
|
+
# Trusted publishing: PyPI trusts this workflow and environment by name, so no API
|
|
36
|
+
# token is stored anywhere.
|
|
37
|
+
needs: build
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
environment:
|
|
40
|
+
name: pypi
|
|
41
|
+
url: https://pypi.org/project/dax-quax/
|
|
42
|
+
permissions:
|
|
43
|
+
id-token: write
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/download-artifact@v8
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
|
|
12
|
+
# vendored .NET assemblies — fetched, not committed
|
|
13
|
+
spikes/**/libs/
|
|
14
|
+
src/dax_quax/_libs/
|
|
15
|
+
*.nupkg
|
|
16
|
+
*.runtimeconfig.json
|
|
17
|
+
|
|
18
|
+
# local model artefacts
|
|
19
|
+
*.pbix
|
|
20
|
+
*.pbip
|
|
21
|
+
*.abf
|
|
22
|
+
*.vpax
|
|
23
|
+
cache.abf
|
|
24
|
+
|
|
25
|
+
# ...but fixtures are exempt. The synthetic .pbip project has to be committed, and
|
|
26
|
+
# the patterns above would otherwise swallow it. Last matching rule wins, so this
|
|
27
|
+
# has to sit after them.
|
|
28
|
+
!tests/fixtures/**
|
|
29
|
+
|
|
30
|
+
# local scratch renders
|
|
31
|
+
.scratch/
|
|
32
|
+
|
|
33
|
+
# a real .pbip corpus, borrowed rather than vendored (tools/fetch_demo.py).
|
|
34
|
+
# It carries no licence, and CONVENTIONS §10 keeps CI offline.
|
|
35
|
+
tests/fixtures/demo/
|
|
36
|
+
|
|
37
|
+
# A fixture recorded from the borrowed corpus loaded into Power BI Desktop. Derived from
|
|
38
|
+
# unlicensed content, so it is reproduced rather than committed:
|
|
39
|
+
# python tools/fetch_demo.py && open Report01.pbip && python tools/record_fixture.py demo01
|
|
40
|
+
tests/fixtures/demo01/
|
|
41
|
+
|
|
42
|
+
# Real .pbix samples, borrowed for the pbixray contract (tools/fetch_pbix.py).
|
|
43
|
+
tests/fixtures/pbix/
|
|
44
|
+
|
|
45
|
+
# Generated review / audit reports, from agents or otherwise. Each one is a snapshot of
|
|
46
|
+
# one pass over the tree: it goes stale the moment a finding is fixed, and a stale one
|
|
47
|
+
# gets quoted back as current fact. Keep what survived triage in CONVENTIONS or a test.
|
|
48
|
+
*_report.md
|
|
49
|
+
*_REPORT.md
|
|
50
|
+
*review*.md
|
|
51
|
+
*REVIEW*.md
|
|
52
|
+
*redteam*.md
|
|
53
|
+
*audit*.md
|
dax_quax-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 S-Curve Labs
|
|
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.
|
dax_quax-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dax-quax
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: What does every object in this tabular model cost, what depends on it, and can it be deleted?
|
|
5
|
+
Project-URL: Homepage, https://github.com/S-CurveLabs/dax-quax
|
|
6
|
+
Project-URL: Source, https://github.com/S-CurveLabs/dax-quax
|
|
7
|
+
Project-URL: Issues, https://github.com/S-CurveLabs/dax-quax/issues
|
|
8
|
+
Author: S-Curve Labs
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: dax,mcp,pbip,power-bi,tabular,tmdl,vertipaq
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Database
|
|
18
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: networkx>=3.0
|
|
21
|
+
Requires-Dist: pandas>=2.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
25
|
+
Provides-Extra: file
|
|
26
|
+
Requires-Dist: pbixray>=0.3; extra == 'file'
|
|
27
|
+
Provides-Extra: live
|
|
28
|
+
Requires-Dist: pythonnet>=3.0; extra == 'live'
|
|
29
|
+
Provides-Extra: mcp
|
|
30
|
+
Requires-Dist: mcp>=2.2; extra == 'mcp'
|
|
31
|
+
Provides-Extra: report
|
|
32
|
+
Requires-Dist: jinja2>=3.1; extra == 'report'
|
|
33
|
+
Provides-Extra: serve
|
|
34
|
+
Requires-Dist: fastapi>=0.110; extra == 'serve'
|
|
35
|
+
Requires-Dist: uvicorn>=0.29; extra == 'serve'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# dax-quax
|
|
39
|
+
|
|
40
|
+
**For every object in a Power BI model: what does it cost, what depends on it, and can it be deleted?**
|
|
41
|
+
|
|
42
|
+
Not a DAX Studio clone. It answers one question, and it is careful about the answer, because
|
|
43
|
+
telling you to delete a column that something still uses is the only way a tool like this can
|
|
44
|
+
really hurt you.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Contoso (pbip)
|
|
48
|
+
87 objects, 214.3 MB measured
|
|
49
|
+
REMOVE 6 41.2 MB (19.2% of measured)
|
|
50
|
+
REVIEW 3 8.1 MB
|
|
51
|
+
UNKNOWN 0
|
|
52
|
+
KEEP 78
|
|
53
|
+
|
|
54
|
+
top 3 by size:
|
|
55
|
+
REMOVE 28.4 MB Sales[Order Number]
|
|
56
|
+
Remove it from the Power Query load.
|
|
57
|
+
Contoso.SemanticModel/definition/tables/Sales.tmdl:3
|
|
58
|
+
|
|
59
|
+
scope: 3 reports scanned
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install "dax-quax[file,report]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Reading a `.pbip` project folder or a `.pbix` file needs nothing else — no Power BI Desktop,
|
|
69
|
+
no .NET, and it works on Linux and macOS. To scan a *running* Power BI Desktop you also need
|
|
70
|
+
the Microsoft client assemblies, which cannot ship in the wheel:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install "dax-quax[live]"
|
|
74
|
+
dax-quax install-runtime
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
That fetches ADOMD.NET from nuget.org into a per-user folder. Windows only, because the
|
|
78
|
+
engine it talks to is.
|
|
79
|
+
|
|
80
|
+
If you skip it, the first live connection offers to do it for you when there is a terminal
|
|
81
|
+
to ask at. Set `DAXQUAX_AUTO_INSTALL_RUNTIME=1` to answer yes in advance, or `=0` to be left
|
|
82
|
+
alone — unattended runs are never prompted, because a question nobody can answer is a hang.
|
|
83
|
+
|
|
84
|
+
They are not in the wheel on purpose. The assemblies are Microsoft's: the EULA does have a
|
|
85
|
+
distributable-code clause, but it is scoped to a REDIST list the NuGet package does not
|
|
86
|
+
contain, and one of its conditions is requiring end users to accept terms protecting
|
|
87
|
+
Microsoft, which `pip install` does not do.
|
|
88
|
+
|
|
89
|
+
| Extra | For |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `file` | `.pbix`, `.abf`, PowerPivot `.xlsx` — metadata **and** sizes with nothing installed |
|
|
92
|
+
| `report` | the HTML report |
|
|
93
|
+
| `serve` | `serve`, the browsable version |
|
|
94
|
+
| `live` | a running Power BI Desktop (plus `dax-quax install-runtime`) |
|
|
95
|
+
| `mcp` | the MCP server |
|
|
96
|
+
|
|
97
|
+
## Use it
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
dax-quax scan --pbip path/to/Model.SemanticModel --report-folder path/to/Name.Report
|
|
101
|
+
dax-quax scan --pbix path/to/file.pbix
|
|
102
|
+
dax-quax scan # a running Power BI Desktop
|
|
103
|
+
dax-quax scan --workspace path/to/folder # every model, against every report over it
|
|
104
|
+
|
|
105
|
+
dax-quax report --pbip DIR --out report.html # one self-contained file, no network
|
|
106
|
+
dax-quax serve --pbip DIR # the same, plus lineage and a rescan
|
|
107
|
+
dax-quax bench "EVALUATE ..." --cold # storage-engine vs formula-engine timings
|
|
108
|
+
dax-quax mcp --pbip DIR # answer questions about the model, read-only
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
As a library:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from dax_quax import open_pbip
|
|
115
|
+
|
|
116
|
+
model = open_pbip("path/to/Model.SemanticModel")
|
|
117
|
+
model.findings() # ranked cost x usage
|
|
118
|
+
model.lineage() # what breaks if this goes
|
|
119
|
+
model.where("[Margin]") # the file and line that define it
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## What makes a verdict trustworthy
|
|
123
|
+
|
|
124
|
+
A verdict is only as good as what was scanned, so **the scope travels with it** instead of
|
|
125
|
+
living in a header you might not have read.
|
|
126
|
+
|
|
127
|
+
- `REMOVE` means *nothing in what was scanned referenced it*. It is a question to put to a
|
|
128
|
+
person, not an instruction.
|
|
129
|
+
- **If no report was scanned, nothing is ever `REMOVE`.** Every unreferenced object is
|
|
130
|
+
`UNKNOWN`, because a report you left out is usage that was not counted.
|
|
131
|
+
- In a workspace scan, **one report that cannot be matched to its model degrades every
|
|
132
|
+
verdict in the whole directory.**
|
|
133
|
+
- Anything the scan could not see is listed on the report, next to the verdicts.
|
|
134
|
+
|
|
135
|
+
A false `REMOVE` is the expensive failure. `UNKNOWN` is free. The tool is built to prefer the
|
|
136
|
+
second, and several places say so out loud rather than quietly rounding a gap to zero — a size
|
|
137
|
+
it could not measure stays `None` and never becomes `0 B`.
|
|
138
|
+
|
|
139
|
+
## For agents
|
|
140
|
+
|
|
141
|
+
`dax-quax mcp` exposes the model over MCP, read-only, so you can ask an assistant what is safe
|
|
142
|
+
to delete and why. The surface is fenced three ways: every tool is a function of a snapshot
|
|
143
|
+
that holds no connection, an allowlist controls what is reachable, and each tool is advertised
|
|
144
|
+
with the protocol's own `readOnlyHint`. Every payload carrying a verdict also carries the scope
|
|
145
|
+
caveat — an agent gets one payload and no headline, so the caveat is attached centrally rather
|
|
146
|
+
than left to each tool to remember.
|
|
147
|
+
|
|
148
|
+
```jsonc
|
|
149
|
+
{"dax-quax": {"command": "dax-quax", "args": ["mcp", "--pbip", "C:/path/Model.SemanticModel"]}}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Limits worth knowing before you trust it
|
|
153
|
+
|
|
154
|
+
- Consumers outside the artefacts you scan — Excel, paginated reports, anything on the XMLA
|
|
155
|
+
endpoint — are invisible. A workspace scan narrows this; it cannot close it.
|
|
156
|
+
- Perspectives and translations are not walked. Neither can cause a false `REMOVE`.
|
|
157
|
+
- `.pbix` sizes come from [pbixray](https://github.com/Hugoberry/pbixray), which reports no
|
|
158
|
+
encoding, no row count and no hierarchy size — those stay absent rather than becoming zero.
|
|
159
|
+
- DAX references are *extracted*, not parsed. Dynamic references built with string
|
|
160
|
+
manipulation cannot be seen by anything that does not run the query.
|
|
161
|
+
- Version 0.1. Six of the seven declared interfaces to Microsoft's formats and APIs turned out
|
|
162
|
+
to be wrong the first time they met a real engine; they are fixed and pinned by tests now,
|
|
163
|
+
but that is the nature of the terrain.
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
python -m venv .venv && .venv/Scripts/activate # source .venv/bin/activate elsewhere
|
|
169
|
+
pip install -e ".[dev,file,report,serve,mcp]"
|
|
170
|
+
pytest -q # no Power BI, no .NET, no network
|
|
171
|
+
python tools/check_imports.py # nothing may need an extra to import
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Run `pytest` from inside the environment you installed into. The extras carry the test
|
|
175
|
+
dependencies, so a `pytest` picked up from somewhere else fails during collection on an
|
|
176
|
+
import — `No module named 'networkx'` — rather than on anything you changed.
|
|
177
|
+
|
|
178
|
+
Tests that need something real are deselected by default — `-m live` for a running instance,
|
|
179
|
+
`-m pbix` after `python tools/fetch_pbix.py`, `-m demo` after `python tools/fetch_demo.py`.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT.
|
dax_quax-0.1.0/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# dax-quax
|
|
2
|
+
|
|
3
|
+
**For every object in a Power BI model: what does it cost, what depends on it, and can it be deleted?**
|
|
4
|
+
|
|
5
|
+
Not a DAX Studio clone. It answers one question, and it is careful about the answer, because
|
|
6
|
+
telling you to delete a column that something still uses is the only way a tool like this can
|
|
7
|
+
really hurt you.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Contoso (pbip)
|
|
11
|
+
87 objects, 214.3 MB measured
|
|
12
|
+
REMOVE 6 41.2 MB (19.2% of measured)
|
|
13
|
+
REVIEW 3 8.1 MB
|
|
14
|
+
UNKNOWN 0
|
|
15
|
+
KEEP 78
|
|
16
|
+
|
|
17
|
+
top 3 by size:
|
|
18
|
+
REMOVE 28.4 MB Sales[Order Number]
|
|
19
|
+
Remove it from the Power Query load.
|
|
20
|
+
Contoso.SemanticModel/definition/tables/Sales.tmdl:3
|
|
21
|
+
|
|
22
|
+
scope: 3 reports scanned
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install "dax-quax[file,report]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Reading a `.pbip` project folder or a `.pbix` file needs nothing else — no Power BI Desktop,
|
|
32
|
+
no .NET, and it works on Linux and macOS. To scan a *running* Power BI Desktop you also need
|
|
33
|
+
the Microsoft client assemblies, which cannot ship in the wheel:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install "dax-quax[live]"
|
|
37
|
+
dax-quax install-runtime
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
That fetches ADOMD.NET from nuget.org into a per-user folder. Windows only, because the
|
|
41
|
+
engine it talks to is.
|
|
42
|
+
|
|
43
|
+
If you skip it, the first live connection offers to do it for you when there is a terminal
|
|
44
|
+
to ask at. Set `DAXQUAX_AUTO_INSTALL_RUNTIME=1` to answer yes in advance, or `=0` to be left
|
|
45
|
+
alone — unattended runs are never prompted, because a question nobody can answer is a hang.
|
|
46
|
+
|
|
47
|
+
They are not in the wheel on purpose. The assemblies are Microsoft's: the EULA does have a
|
|
48
|
+
distributable-code clause, but it is scoped to a REDIST list the NuGet package does not
|
|
49
|
+
contain, and one of its conditions is requiring end users to accept terms protecting
|
|
50
|
+
Microsoft, which `pip install` does not do.
|
|
51
|
+
|
|
52
|
+
| Extra | For |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `file` | `.pbix`, `.abf`, PowerPivot `.xlsx` — metadata **and** sizes with nothing installed |
|
|
55
|
+
| `report` | the HTML report |
|
|
56
|
+
| `serve` | `serve`, the browsable version |
|
|
57
|
+
| `live` | a running Power BI Desktop (plus `dax-quax install-runtime`) |
|
|
58
|
+
| `mcp` | the MCP server |
|
|
59
|
+
|
|
60
|
+
## Use it
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
dax-quax scan --pbip path/to/Model.SemanticModel --report-folder path/to/Name.Report
|
|
64
|
+
dax-quax scan --pbix path/to/file.pbix
|
|
65
|
+
dax-quax scan # a running Power BI Desktop
|
|
66
|
+
dax-quax scan --workspace path/to/folder # every model, against every report over it
|
|
67
|
+
|
|
68
|
+
dax-quax report --pbip DIR --out report.html # one self-contained file, no network
|
|
69
|
+
dax-quax serve --pbip DIR # the same, plus lineage and a rescan
|
|
70
|
+
dax-quax bench "EVALUATE ..." --cold # storage-engine vs formula-engine timings
|
|
71
|
+
dax-quax mcp --pbip DIR # answer questions about the model, read-only
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
As a library:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from dax_quax import open_pbip
|
|
78
|
+
|
|
79
|
+
model = open_pbip("path/to/Model.SemanticModel")
|
|
80
|
+
model.findings() # ranked cost x usage
|
|
81
|
+
model.lineage() # what breaks if this goes
|
|
82
|
+
model.where("[Margin]") # the file and line that define it
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## What makes a verdict trustworthy
|
|
86
|
+
|
|
87
|
+
A verdict is only as good as what was scanned, so **the scope travels with it** instead of
|
|
88
|
+
living in a header you might not have read.
|
|
89
|
+
|
|
90
|
+
- `REMOVE` means *nothing in what was scanned referenced it*. It is a question to put to a
|
|
91
|
+
person, not an instruction.
|
|
92
|
+
- **If no report was scanned, nothing is ever `REMOVE`.** Every unreferenced object is
|
|
93
|
+
`UNKNOWN`, because a report you left out is usage that was not counted.
|
|
94
|
+
- In a workspace scan, **one report that cannot be matched to its model degrades every
|
|
95
|
+
verdict in the whole directory.**
|
|
96
|
+
- Anything the scan could not see is listed on the report, next to the verdicts.
|
|
97
|
+
|
|
98
|
+
A false `REMOVE` is the expensive failure. `UNKNOWN` is free. The tool is built to prefer the
|
|
99
|
+
second, and several places say so out loud rather than quietly rounding a gap to zero — a size
|
|
100
|
+
it could not measure stays `None` and never becomes `0 B`.
|
|
101
|
+
|
|
102
|
+
## For agents
|
|
103
|
+
|
|
104
|
+
`dax-quax mcp` exposes the model over MCP, read-only, so you can ask an assistant what is safe
|
|
105
|
+
to delete and why. The surface is fenced three ways: every tool is a function of a snapshot
|
|
106
|
+
that holds no connection, an allowlist controls what is reachable, and each tool is advertised
|
|
107
|
+
with the protocol's own `readOnlyHint`. Every payload carrying a verdict also carries the scope
|
|
108
|
+
caveat — an agent gets one payload and no headline, so the caveat is attached centrally rather
|
|
109
|
+
than left to each tool to remember.
|
|
110
|
+
|
|
111
|
+
```jsonc
|
|
112
|
+
{"dax-quax": {"command": "dax-quax", "args": ["mcp", "--pbip", "C:/path/Model.SemanticModel"]}}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Limits worth knowing before you trust it
|
|
116
|
+
|
|
117
|
+
- Consumers outside the artefacts you scan — Excel, paginated reports, anything on the XMLA
|
|
118
|
+
endpoint — are invisible. A workspace scan narrows this; it cannot close it.
|
|
119
|
+
- Perspectives and translations are not walked. Neither can cause a false `REMOVE`.
|
|
120
|
+
- `.pbix` sizes come from [pbixray](https://github.com/Hugoberry/pbixray), which reports no
|
|
121
|
+
encoding, no row count and no hierarchy size — those stay absent rather than becoming zero.
|
|
122
|
+
- DAX references are *extracted*, not parsed. Dynamic references built with string
|
|
123
|
+
manipulation cannot be seen by anything that does not run the query.
|
|
124
|
+
- Version 0.1. Six of the seven declared interfaces to Microsoft's formats and APIs turned out
|
|
125
|
+
to be wrong the first time they met a real engine; they are fixed and pinned by tests now,
|
|
126
|
+
but that is the nature of the terrain.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python -m venv .venv && .venv/Scripts/activate # source .venv/bin/activate elsewhere
|
|
132
|
+
pip install -e ".[dev,file,report,serve,mcp]"
|
|
133
|
+
pytest -q # no Power BI, no .NET, no network
|
|
134
|
+
python tools/check_imports.py # nothing may need an extra to import
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Run `pytest` from inside the environment you installed into. The extras carry the test
|
|
138
|
+
dependencies, so a `pytest` picked up from somewhere else fails during collection on an
|
|
139
|
+
import — `No module named 'networkx'` — rather than on anything you changed.
|
|
140
|
+
|
|
141
|
+
Tests that need something real are deselected by default — `-m live` for a running instance,
|
|
142
|
+
`-m pbix` after `python tools/fetch_pbix.py`, `-m demo` after `python tools/fetch_demo.py`.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dax-quax"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "What does every object in this tabular model cost, what depends on it, and can it be deleted?"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
authors = [{ name = "S-Curve Labs" }]
|
|
14
|
+
keywords = ["power-bi", "tabular", "vertipaq", "dax", "tmdl", "pbip", "mcp"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Topic :: Database",
|
|
22
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
23
|
+
]
|
|
24
|
+
dependencies = ["pandas>=2.0", "networkx>=3.0"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/S-CurveLabs/dax-quax"
|
|
28
|
+
Source = "https://github.com/S-CurveLabs/dax-quax"
|
|
29
|
+
Issues = "https://github.com/S-CurveLabs/dax-quax/issues"
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
live = ["pythonnet>=3.0"]
|
|
33
|
+
file = ["pbixray>=0.3"]
|
|
34
|
+
report = ["jinja2>=3.1"]
|
|
35
|
+
serve = ["fastapi>=0.110", "uvicorn>=0.29"]
|
|
36
|
+
mcp = ["mcp>=2.2"]
|
|
37
|
+
dev = ["pytest>=8.0", "ruff>=0.6"]
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
dax-quax = "dax_quax.cli:main"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/dax_quax"]
|
|
44
|
+
# Never ship the vendored .NET assemblies. They are Microsoft's, whether we may
|
|
45
|
+
# redistribute them is unsettled, and they are 5 MB that only a live connection uses --
|
|
46
|
+
# `dax-quax install-runtime` fetches them per user instead. Hatchling already honours
|
|
47
|
+
# .gitignore, where _libs/ also sits; this says it outright so the exclusion survives
|
|
48
|
+
# anyone changing how files are selected.
|
|
49
|
+
exclude = ["_libs/**"]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.sdist]
|
|
52
|
+
# The borrowed corpora are fetched per-developer, never shipped. None of them carries a
|
|
53
|
+
# licence: the .pbix samples come from tools/fetch_pbix.py, the demo project from
|
|
54
|
+
# tools/fetch_demo.py, and demo01 is recorded from that project loaded into Desktop.
|
|
55
|
+
# .gitignore keeps them out of the repo, but hatchling's sdist does not honour the
|
|
56
|
+
# re-ignore lines that follow `!tests/fixtures/**`, so it collected them off the local
|
|
57
|
+
# disk instead -- a 28 MB sdist against a 148 KB wheel. Naming them here is what stops an
|
|
58
|
+
# upload from redistributing 26 MB of somebody else's content.
|
|
59
|
+
exclude = [
|
|
60
|
+
"/tests/fixtures/demo/**",
|
|
61
|
+
"/tests/fixtures/demo01/**",
|
|
62
|
+
"/tests/fixtures/pbix/**",
|
|
63
|
+
"/.claude/**",
|
|
64
|
+
# Private-repo only; tools/export_public.py leaves them out of the public tree too.
|
|
65
|
+
"/CONVENTIONS.md",
|
|
66
|
+
"/CODE_REVIEW.md",
|
|
67
|
+
"/spikes/**",
|
|
68
|
+
"/tools/export_public.py",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
line-length = 100
|
|
73
|
+
# spikes/ are frozen reproduction scripts for a finding, not maintained source.
|
|
74
|
+
extend-exclude = ["spikes"]
|
|
75
|
+
target-version = "py312"
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint]
|
|
78
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
testpaths = ["tests"]
|
|
82
|
+
# so `pytest` works without an editable install, which CI should not need
|
|
83
|
+
pythonpath = ["src"]
|
|
84
|
+
markers = [
|
|
85
|
+
"live: requires a running Power BI Desktop instance (deselected by default)",
|
|
86
|
+
"pbix: requires a real .pbix via DAXQUAX_TEST_PBIX (deselected by default)",
|
|
87
|
+
"demo: requires the borrowed .pbip corpus; run tools/fetch_demo.py first",
|
|
88
|
+
]
|
|
89
|
+
addopts = "-m 'not live and not pbix and not demo'"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""dax-quax — what does every object in this tabular model cost, and can it be deleted?
|
|
2
|
+
|
|
3
|
+
Public surface. Everything else is internal and may move.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dax_quax.analysis.usage import (
|
|
9
|
+
Finding,
|
|
10
|
+
ScanScope,
|
|
11
|
+
Thresholds,
|
|
12
|
+
Verdict,
|
|
13
|
+
assess,
|
|
14
|
+
)
|
|
15
|
+
from dax_quax.errors import (
|
|
16
|
+
ConnectionDiscoveryError,
|
|
17
|
+
ContractViolation,
|
|
18
|
+
DaxQuaxError,
|
|
19
|
+
MissingCapability,
|
|
20
|
+
)
|
|
21
|
+
from dax_quax.model import Column, ColumnMetrics, Measure, Model, ObjectRef, Relationship, Table
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def connect(port: int | None = None, database: str | None = None) -> Model:
|
|
27
|
+
"""Load a Model from a running local Power BI Desktop instance.
|
|
28
|
+
|
|
29
|
+
Windows only, and imported lazily so the rest of the package stays cross-platform.
|
|
30
|
+
"""
|
|
31
|
+
from dax_quax.sources.live import connect as _connect
|
|
32
|
+
|
|
33
|
+
return _connect(port=port, database=database)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def open_workspace(path: str):
|
|
37
|
+
"""Scan a directory of .pbip artefacts: every model, against every report over it."""
|
|
38
|
+
from dax_quax.sources.workspace import discover_workspace
|
|
39
|
+
|
|
40
|
+
return discover_workspace(path)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def open_pbip(path: str, *, attach_reports: bool = True) -> Model:
|
|
44
|
+
"""Load a Model from a .pbip project folder, with its sibling reports attached.
|
|
45
|
+
|
|
46
|
+
Cross-platform: no .NET, no Power BI Desktop, nothing but text on disk.
|
|
47
|
+
"""
|
|
48
|
+
from dax_quax.sources.pbip import open_pbip as _open
|
|
49
|
+
|
|
50
|
+
return _open(path, attach_reports=attach_reports)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def open_file(path: str) -> Model:
|
|
54
|
+
"""Load a Model from a .pbix, .abf or PowerPivot .xlsx.
|
|
55
|
+
|
|
56
|
+
The only source that carries metadata **and** storage metrics with no Power BI Desktop
|
|
57
|
+
and no local cache. Needs the ``file`` extra.
|
|
58
|
+
"""
|
|
59
|
+
from dax_quax.sources.file import load_file
|
|
60
|
+
|
|
61
|
+
return load_file(path)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"Column",
|
|
66
|
+
"Finding",
|
|
67
|
+
"ScanScope",
|
|
68
|
+
"Thresholds",
|
|
69
|
+
"Verdict",
|
|
70
|
+
"ColumnMetrics",
|
|
71
|
+
"ConnectionDiscoveryError",
|
|
72
|
+
"ContractViolation",
|
|
73
|
+
"DaxQuaxError",
|
|
74
|
+
"Measure",
|
|
75
|
+
"MissingCapability",
|
|
76
|
+
"Model",
|
|
77
|
+
"ObjectRef",
|
|
78
|
+
"Relationship",
|
|
79
|
+
"Table",
|
|
80
|
+
"__version__",
|
|
81
|
+
"assess",
|
|
82
|
+
"connect",
|
|
83
|
+
"open_file",
|
|
84
|
+
"open_pbip",
|
|
85
|
+
"open_workspace",
|
|
86
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Analyses. Pure functions of a Model."""
|