useful-views 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.
Files changed (82) hide show
  1. useful_views-0.1.0/.gitattributes +4 -0
  2. useful_views-0.1.0/.github/workflows/ci.yml +91 -0
  3. useful_views-0.1.0/.github/workflows/docs.yml +110 -0
  4. useful_views-0.1.0/.github/workflows/release.yml +78 -0
  5. useful_views-0.1.0/.gitignore +8 -0
  6. useful_views-0.1.0/CHANGELOG.md +48 -0
  7. useful_views-0.1.0/CITATION.cff +22 -0
  8. useful_views-0.1.0/LICENSE +21 -0
  9. useful_views-0.1.0/PKG-INFO +568 -0
  10. useful_views-0.1.0/README.md +543 -0
  11. useful_views-0.1.0/docs/assets/site.css +22 -0
  12. useful_views-0.1.0/docs/useful-views-icon.svg +65 -0
  13. useful_views-0.1.0/docs/useful-views-logo-dark.svg +66 -0
  14. useful_views-0.1.0/docs/useful-views-logo.svg +66 -0
  15. useful_views-0.1.0/great-docs.yml +103 -0
  16. useful_views-0.1.0/pyproject.toml +52 -0
  17. useful_views-0.1.0/scripts/generate_docs.py +136 -0
  18. useful_views-0.1.0/skills/useful-views/SKILL.md +114 -0
  19. useful_views-0.1.0/src/useful_views/__init__.py +53 -0
  20. useful_views-0.1.0/src/useful_views/__main__.py +5 -0
  21. useful_views-0.1.0/src/useful_views/cli.py +393 -0
  22. useful_views-0.1.0/src/useful_views/cookbooks.py +229 -0
  23. useful_views-0.1.0/src/useful_views/errors.py +151 -0
  24. useful_views-0.1.0/src/useful_views/examples.py +221 -0
  25. useful_views-0.1.0/src/useful_views/lock.py +147 -0
  26. useful_views-0.1.0/src/useful_views/new.py +55 -0
  27. useful_views-0.1.0/src/useful_views/paths.py +216 -0
  28. useful_views-0.1.0/src/useful_views/project.py +216 -0
  29. useful_views-0.1.0/src/useful_views/py.typed +0 -0
  30. useful_views-0.1.0/src/useful_views/recipe.py +396 -0
  31. useful_views-0.1.0/src/useful_views/record.py +427 -0
  32. useful_views-0.1.0/src/useful_views/rulebook.py +94 -0
  33. useful_views-0.1.0/src/useful_views/rules.py +412 -0
  34. useful_views-0.1.0/src/useful_views/runner.py +570 -0
  35. useful_views-0.1.0/src/useful_views/status.py +288 -0
  36. useful_views-0.1.0/src/useful_views/syncing.py +519 -0
  37. useful_views-0.1.0/src/useful_views/templates/01_first_step.py +12 -0
  38. useful_views-0.1.0/src/useful_views/templates/AGENTS.md +21 -0
  39. useful_views-0.1.0/src/useful_views/templates/project_README.md +118 -0
  40. useful_views-0.1.0/src/useful_views/templates/recipe.toml +16 -0
  41. useful_views-0.1.0/tests/conftest.py +229 -0
  42. useful_views-0.1.0/tests/data/examples/pwt/README.md +63 -0
  43. useful_views-0.1.0/tests/data/examples/pwt/cookbook.json +56 -0
  44. useful_views-0.1.0/tests/data/examples/pwt/datapackage.json +482 -0
  45. useful_views-0.1.0/tests/data/examples/pwt/raw/pwt110_bra_deu_esp_usa.csv +169 -0
  46. useful_views-0.1.0/tests/data/examples/pwt/recipes/gdp_per_capita/01_derive_measures.py +27 -0
  47. useful_views-0.1.0/tests/data/examples/pwt/recipes/gdp_per_capita/recipe.toml +3 -0
  48. useful_views-0.1.0/tests/data/examples/pwt/recipes/pwt_panel/01_reshape_long.py +10 -0
  49. useful_views-0.1.0/tests/data/examples/pwt/recipes/pwt_panel/02_keep_core_variables.py +9 -0
  50. useful_views-0.1.0/tests/data/examples/pwt/recipes/pwt_panel/03_pivot_to_panel.py +13 -0
  51. useful_views-0.1.0/tests/data/examples/pwt/recipes/pwt_panel/recipe.toml +3 -0
  52. useful_views-0.1.0/tests/data/examples/pwt/useful-views/README.md +50 -0
  53. useful_views-0.1.0/tests/data/examples/pwt/useful-views/gdp-per-capita.parquet +0 -0
  54. useful_views-0.1.0/tests/data/examples/pwt/useful-views/pwt-panel.parquet +0 -0
  55. useful_views-0.1.0/tests/test_agent_surfaces.py +407 -0
  56. useful_views-0.1.0/tests/test_check_sync.py +895 -0
  57. useful_views-0.1.0/tests/test_cookbook.py +216 -0
  58. useful_views-0.1.0/tests/test_docs.py +36 -0
  59. useful_views-0.1.0/tests/test_edge_cases.py +327 -0
  60. useful_views-0.1.0/tests/test_example.py +208 -0
  61. useful_views-0.1.0/tests/test_formats.py +883 -0
  62. useful_views-0.1.0/tests/test_project.py +815 -0
  63. useful_views-0.1.0/tests/test_quickstart.py +82 -0
  64. useful_views-0.1.0/tests/test_reference.py +78 -0
  65. useful_views-0.1.0/tests/test_refusals.py +885 -0
  66. useful_views-0.1.0/tests/test_regressions.py +325 -0
  67. useful_views-0.1.0/tests/test_robustness.py +484 -0
  68. useful_views-0.1.0/tests/test_run.py +641 -0
  69. useful_views-0.1.0/user_guide/concepts/cookbook.md +180 -0
  70. useful_views-0.1.0/user_guide/concepts/projects.md +98 -0
  71. useful_views-0.1.0/user_guide/concepts/recipes.md +125 -0
  72. useful_views-0.1.0/user_guide/concepts/records.md +82 -0
  73. useful_views-0.1.0/user_guide/concepts/running.md +116 -0
  74. useful_views-0.1.0/user_guide/concepts/steps.md +179 -0
  75. useful_views-0.1.0/user_guide/guides/ai-tools.md +113 -0
  76. useful_views-0.1.0/user_guide/installation.md +120 -0
  77. useful_views-0.1.0/user_guide/quickstart.md +295 -0
  78. useful_views-0.1.0/user_guide/reference/check-findings.md +22 -0
  79. useful_views-0.1.0/user_guide/reference/cli.md +167 -0
  80. useful_views-0.1.0/user_guide/reference/environment.md +60 -0
  81. useful_views-0.1.0/user_guide/reference/rules.md +41 -0
  82. useful_views-0.1.0/uv.lock +2936 -0
@@ -0,0 +1,4 @@
1
+ # The test data is compared byte for byte and pinned by SHA-256, so git must
2
+ # never change its line endings (core.autocrlf on Windows would).
3
+ tests/data/** -text
4
+ *.parquet binary
@@ -0,0 +1,91 @@
1
+ name: CI
2
+
3
+ # Runs on every push to main or dev, on every pull request, and by hand
4
+ # (Actions tab > CI > Run workflow).
5
+ on:
6
+ push:
7
+ branches: [main, dev]
8
+ pull_request:
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: ci-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ test:
20
+ name: test (${{ matrix.os }}, Python ${{ matrix.python }})
21
+ runs-on: ${{ matrix.os }}
22
+ timeout-minutes: 30
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ os: [ubuntu-latest, macos-latest, windows-latest]
27
+ python: ["3.11", "3.12", "3.13", "3.14"]
28
+ steps:
29
+ - uses: actions/checkout@v6
30
+ - uses: astral-sh/setup-uv@v6
31
+ - name: Install
32
+ run: uv sync --locked --python ${{ matrix.python }}
33
+ - name: Test
34
+ run: uv run --locked pytest -n auto
35
+
36
+ lowest-dependencies:
37
+ name: test (oldest supported pyarrow)
38
+ runs-on: ubuntu-latest
39
+ timeout-minutes: 30
40
+ steps:
41
+ - uses: actions/checkout@v6
42
+ - uses: astral-sh/setup-uv@v6
43
+ - name: Install with pyarrow 16
44
+ run: |
45
+ uv venv --python 3.11
46
+ echo "pyarrow==16.0.0" > constraints.txt
47
+ # The dev group's bounds (pyproject.toml), with pyarrow held at the oldest supported.
48
+ uv pip install -c constraints.txt -e . "pytest>=8" "pytest-xdist>=3" "polars>=1.20" "frictionless[pandas]>=5.18,<6"
49
+ - name: Test
50
+ run: .venv/bin/python -m pytest -n auto
51
+
52
+ package:
53
+ name: build and check the package
54
+ runs-on: ubuntu-latest
55
+ timeout-minutes: 15
56
+ steps:
57
+ - uses: actions/checkout@v6
58
+ - uses: astral-sh/setup-uv@v6
59
+ - name: Build
60
+ run: uv build
61
+ - name: Check metadata
62
+ run: uvx twine check --strict dist/*
63
+ - name: Install the wheel and build the real example with it
64
+ # Downloads the pinned example from GitHub, so it also checks the
65
+ # pinned tag and checksums, and that the wheel holds its templates.
66
+ run: |
67
+ uv venv --python 3.11 /tmp/wheel-env
68
+ uv pip install --python /tmp/wheel-env/bin/python dist/*.whl polars
69
+ cd /tmp
70
+ /tmp/wheel-env/bin/useful-views --version
71
+ /tmp/wheel-env/bin/useful-views example pwt smoke
72
+ /tmp/wheel-env/bin/useful-views -C smoke sync
73
+ /tmp/wheel-env/bin/useful-views -C smoke check
74
+ /tmp/wheel-env/bin/useful-views -C smoke cookbook
75
+ /tmp/wheel-env/bin/useful-views -C smoke cookbook --check
76
+ test -f smoke/AGENTS.md
77
+
78
+ # One check that is green only when every job above is, for branch
79
+ # protection to require (instead of naming each job of the matrix).
80
+ all-green:
81
+ name: all checks passed
82
+ if: always()
83
+ needs: [test, lowest-dependencies, package]
84
+ runs-on: ubuntu-latest
85
+ timeout-minutes: 5
86
+ steps:
87
+ - name: Fail unless every job passed
88
+ if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')
89
+ run: exit 1
90
+ - name: Every job passed
91
+ run: echo "All jobs passed."
@@ -0,0 +1,110 @@
1
+ name: CI Docs
2
+
3
+ # Builds on every pull request (as a check, without publishing), and builds
4
+ # and publishes to GitHub Pages on every push to main. Also runs by hand
5
+ # (Actions tab > CI Docs > Run workflow).
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build-docs:
17
+ name: "Build Docs"
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 30
20
+ steps:
21
+ - uses: actions/checkout@v6
22
+ with:
23
+ fetch-depth: 0 # Full history for accurate page timestamps
24
+
25
+ - uses: actions/setup-python@v6
26
+ with:
27
+ python-version: "3.11"
28
+
29
+ - name: Install package and dependencies
30
+ run: |
31
+ python -m pip install uv
32
+ uv sync --locked --group docs
33
+
34
+ - name: Check the generated reference pages are up to date
35
+ run: |
36
+ uv run python scripts/generate_docs.py
37
+ git diff --exit-code user_guide/reference
38
+
39
+ - name: Set up Quarto
40
+ uses: quarto-dev/quarto-actions/setup@v2
41
+
42
+ - name: Build docs
43
+ run: uv run --group docs great-docs build
44
+
45
+ - name: Save docs artifact
46
+ uses: actions/upload-artifact@v7
47
+ with:
48
+ name: docs-html
49
+ path: great-docs/_site
50
+ include-hidden-files: true
51
+
52
+ - name: How to preview this build locally
53
+ if: github.event_name == 'pull_request'
54
+ run: uv run --group docs great-docs ci notice --run ${{ github.run_id }} --pr ${{ github.event.number }}
55
+
56
+ - name: Upload build timings
57
+ uses: actions/upload-artifact@v7
58
+ with:
59
+ name: build-timings
60
+ path: great-docs/_site/build-timings.json
61
+
62
+ publish-docs:
63
+ name: "Publish Docs"
64
+ runs-on: ubuntu-latest
65
+ needs: "build-docs"
66
+ if: github.ref == 'refs/heads/main'
67
+ # One deployment at a time; a newer one waits instead of cancelling.
68
+ concurrency:
69
+ group: pages
70
+ cancel-in-progress: false
71
+ permissions:
72
+ pages: write
73
+ id-token: write
74
+ environment:
75
+ name: github-pages
76
+ url: ${{ steps.deployment.outputs.page_url }}
77
+ steps:
78
+ - uses: actions/download-artifact@v7
79
+ with:
80
+ name: docs-html
81
+ path: great-docs/_site
82
+
83
+ - name: Upload Pages artifact
84
+ uses: actions/upload-pages-artifact@v5
85
+ with:
86
+ path: great-docs/_site
87
+ include-hidden-files: true
88
+
89
+ - name: Deploy to GitHub Pages
90
+ id: deployment
91
+ uses: actions/deploy-pages@v5
92
+
93
+ preview-docs:
94
+ name: "Preview Docs"
95
+ runs-on: ubuntu-latest
96
+ needs: "build-docs"
97
+ if: github.event_name == 'pull_request'
98
+ permissions:
99
+ pull-requests: write
100
+ steps:
101
+ # Post (and keep updated) a sticky PR comment with the local-preview
102
+ # command, pinned to this run so it reproduces the build under review.
103
+ # Runs the released great-docs via pipx (no install step needed); kept as
104
+ # its own minimal job so the docs build never holds a pull-requests:write
105
+ # token. continue-on-error so fork PRs (read-only token) don't fail the run.
106
+ - name: Post preview instructions to the PR
107
+ continue-on-error: true
108
+ run: pipx run great-docs ci pr-comment --run ${{ github.run_id }} --pr ${{ github.event.number }}
109
+ env:
110
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,78 @@
1
+ name: Release
2
+
3
+ # Publishes a release to PyPI when a version tag is pushed:
4
+ #
5
+ # git tag v0.1.0 && git push origin v0.1.0
6
+ #
7
+ # Before the first release, PyPI must trust this workflow: on pypi.org,
8
+ # Your projects > Publishing > Add a new pending publisher, with owner
9
+ # jameshorine, repository useful-views, workflow release.yml and
10
+ # environment pypi. No password or API token is stored anywhere.
11
+ on:
12
+ push:
13
+ tags: ["v*"]
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ build:
20
+ name: Build and check
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 15
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+ - uses: astral-sh/setup-uv@v6
26
+ - name: The tag must match the version in pyproject.toml
27
+ run: |
28
+ version=$(uv run --no-project python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
29
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
30
+ echo "Tag $GITHUB_REF_NAME does not match version $version in pyproject.toml"
31
+ exit 1
32
+ fi
33
+ - name: Build
34
+ run: uv build
35
+ - name: Check metadata
36
+ run: uvx twine check --strict dist/*
37
+ - uses: actions/upload-artifact@v7
38
+ with:
39
+ name: dist
40
+ path: dist/
41
+
42
+ publish:
43
+ name: Publish to PyPI
44
+ needs: build
45
+ runs-on: ubuntu-latest
46
+ timeout-minutes: 15
47
+ environment:
48
+ name: pypi
49
+ url: https://pypi.org/project/useful-views/
50
+ permissions:
51
+ id-token: write # Trusted Publishing: PyPI checks who is publishing
52
+ steps:
53
+ - uses: actions/download-artifact@v7
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+ - uses: pypa/gh-action-pypi-publish@release/v1
58
+
59
+ github-release:
60
+ name: GitHub release
61
+ needs: publish
62
+ runs-on: ubuntu-latest
63
+ timeout-minutes: 10
64
+ permissions:
65
+ contents: write
66
+ steps:
67
+ - uses: actions/checkout@v6
68
+ - uses: actions/download-artifact@v7
69
+ with:
70
+ name: dist
71
+ path: dist/
72
+ - name: Create the release, with this version's CHANGELOG section as its notes
73
+ env:
74
+ GH_TOKEN: ${{ github.token }}
75
+ run: |
76
+ version="${GITHUB_REF_NAME#v}"
77
+ awk -v v="$version" '$0 ~ "^## "v" " {on=1; next} /^## / {on=0} on' CHANGELOG.md > notes.md
78
+ gh release create "$GITHUB_REF_NAME" dist/* --title "useful-views $version" --notes-file notes.md
@@ -0,0 +1,8 @@
1
+ .DS_Store
2
+ .venv/
3
+ __pycache__/
4
+ .pytest_cache/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ great-docs/
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ All notable changes to useful-views are recorded here. Versions follow
4
+ [semantic versioning](https://semver.org/); while the version starts with
5
+ 0, a minor release may change behaviour.
6
+
7
+ ## 0.1.0 (2026-10-15)
8
+
9
+ The first release: the minimum version.
10
+
11
+ - A fixed project structure: `raw/`, `recipes/`, `work/`, `useful-views/`,
12
+ a generated `datapackage.json` and a generated catalog,
13
+ `useful-views/README.md`.
14
+ - `init`, `new_recipe`, `run`, `check` and `sync`, as Python functions and
15
+ as the `useful-views` command, and `root` (Python only). `init` also writes
16
+ an `AGENTS.md` for AI coding assistants, and refuses the home folder or the
17
+ top of a drive without `--force`.
18
+ - `example` downloads a ready-to-run example project from the
19
+ useful-views-data repository, pinned to a tag and checked by SHA-256.
20
+ - `check` reports what `run` and `sync` would refuse (`refused`, with the
21
+ rule number) and recipes changed since they last ran (`out-of-date`).
22
+ `sync` checks every recipe before changing anything and lists every
23
+ problem at once.
24
+ - `useful-views cookbook` writes `cookbook.json`, the ledger of the recipes a
25
+ project uses, in run order (format version 1; file hashes are planned);
26
+ `cookbook --check` verifies it in CI without needing the data, and `check`
27
+ reports `stale-cookbook` (and `stale-catalog` for a hand-edited catalog).
28
+ - For scripts and AI tools: `--json` on every command (with `warnings` and one
29
+ error shape), `-C <path>`, `useful-views rules` (and `useful_views.RULES`),
30
+ exit codes in `--help`, and errors that carry the recipe, rule, file and
31
+ step (`to_dict()`). Progress lines go to standard error.
32
+ - One command at a time: `run`, `sync` and `cookbook` hold a lock on the
33
+ project. On the command line, SIGTERM and SIGHUP stop the running step and
34
+ clean up as Ctrl-C does. `run` warns about recipes downstream of it, and a
35
+ `sync` that stops partway removes the views it left out of date.
36
+ - Recipes are folders with a `recipe.toml` and numbered Python step files;
37
+ every view records the recipe, inputs and steps that made it.
38
+ - `sync` makes the views match the recipe folders, like `uv sync`: it runs
39
+ what is missing or changed, upstream first, and removes what is no longer
40
+ declared.
41
+ - Schemas are read with pyarrow; the generated `datapackage.json` passes
42
+ `frictionless validate`.
43
+ - A documentation site built with great-docs: getting started, concepts
44
+ (projects, steps, recipes, running, records and the cookbook), a guide for
45
+ AI tools, a generated command-line and rules reference, an API reference
46
+ from the docstrings, and `llms.txt` and a hand-written `SKILL.md` for
47
+ coding agents.
48
+ - Tested on Python 3.11 to 3.14.
@@ -0,0 +1,22 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use useful-views in your research, please cite it as below."
3
+ type: software
4
+ title: "useful-views: Documented derived tables for data projects"
5
+ abstract: >-
6
+ A Python package that gives a data project a fixed structure, in
7
+ which every derived table (a "useful view") records the recipe, inputs and
8
+ steps that made it, in a standard Data Package.
9
+ authors:
10
+ - family-names: Horine
11
+ given-names: James
12
+ version: 0.1.0
13
+ date-released: "2026-10-15"
14
+ license: MIT
15
+ repository-code: "https://github.com/jameshorine/useful-views"
16
+ url: "https://jameshorine.github.io/useful-views/"
17
+ keywords:
18
+ - research data
19
+ - reproducibility
20
+ - data provenance
21
+ - data package
22
+ - python
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 James Horine
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.