chartbook 0.2.0__tar.gz → 0.3.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.
- {chartbook-0.2.0 → chartbook-0.3.0}/PKG-INFO +20 -5
- {chartbook-0.2.0 → chartbook-0.3.0}/README.md +15 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/pyproject.toml +11 -8
- chartbook-0.3.0/src/chartbook/__about__.py +1 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/__init__.py +6 -1
- chartbook-0.3.0/src/chartbook/assets.py +344 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/backends/__init__.py +34 -36
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/build_docs.py +1 -1
- chartbook-0.3.0/src/chartbook/built_from.txt +1 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/check.py +262 -6
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/cli.py +242 -14
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/conf_validation.py +38 -10
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/create_data_glimpses.py +41 -20
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/data.py +111 -69
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/diagnostics.py +29 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_static/custom.css +6 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/dataframe_manifest.md +1 -1
- chartbook-0.3.0/src/chartbook/docs_src_catalog/_templates/file_manifest.md +14 -0
- chartbook-0.3.0/src/chartbook/docs_src_catalog/files.md +8 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/index.md +9 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_static/custom.css +6 -0
- chartbook-0.3.0/src/chartbook/docs_src_mkdocs/catalog/files.md +4 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/index.md +1 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_static/custom.css +6 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/index.md +6 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_static/custom.css +6 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/dataframe_manifest.md +1 -1
- chartbook-0.3.0/src/chartbook/docs_src_pipeline/_templates/file_manifest.md +14 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/index.md +8 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/errors.py +19 -0
- chartbook-0.3.0/src/chartbook/files.py +320 -0
- chartbook-0.3.0/src/chartbook/formats.py +1316 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/hashing.py +115 -13
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/incremental.py +7 -2
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/index.py +125 -24
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/indexdiff.py +1 -1
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/lineage.py +1 -1
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/lock.py +305 -23
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/manifest.py +52 -3
- chartbook-0.3.0/src/chartbook/migrate.py +662 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/provenance.py +4 -2
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/refs.py +5 -4
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/resolve.py +62 -17
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/lock/v1.json +1 -1
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/snapshot/v1.json +17 -0
- chartbook-0.3.0/src/chartbook/schemas/chartbook/snapshot/v2.json +359 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Dataframe.json +57 -0
- chartbook-0.3.0/src/chartbook/schemas/chartbook/v1/File.json +195 -0
- chartbook-0.3.0/src/chartbook/sharing.py +250 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/sitefiles.py +48 -7
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/skills/SKILL.md +23 -7
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/skills/catalog_system.md +6 -4
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/skills/manifest_files.md +60 -3
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/snapshot.py +212 -57
- chartbook-0.3.0/src/chartbook/store.py +3316 -0
- chartbook-0.3.0/src/chartbook/stores/s3.py +2282 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/utils.py +30 -23
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/writers/mkdocs.py +12 -2
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/writers/sphinx.py +298 -23
- {chartbook-0.2.0 → chartbook-0.3.0}/template/cookiecutter.json +2 -2
- {chartbook-0.2.0 → chartbook-0.3.0}/template/hooks/post_gen_project.py +5 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/chartbook/SKILL.md +23 -7
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/chartbook/catalog_system.md +6 -4
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/chartbook/manifest_files.md +60 -3
- chartbook-0.3.0/template/{{cookiecutter.project_slug}}/.github/workflows/python_tests.yml +51 -0
- chartbook-0.3.0/template/{{cookiecutter.project_slug}}/.python-version +1 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/environment.yml +2 -2
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/pixi.toml +2 -2
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/pyproject.toml +2 -2
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/requirements.txt +2 -2
- chartbook-0.2.0/src/chartbook/__about__.py +0 -1
- chartbook-0.2.0/src/chartbook/assets.py +0 -150
- chartbook-0.2.0/src/chartbook/built_from.txt +0 -1
- chartbook-0.2.0/src/chartbook/store.py +0 -1659
- chartbook-0.2.0/src/chartbook/stores/s3.py +0 -914
- chartbook-0.2.0/template/{{cookiecutter.project_slug}}/.github/workflows/python_tests.yml +0 -32
- {chartbook-0.2.0 → chartbook-0.3.0}/.gitignore +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/LICENSE.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/hatch_build.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/backends/databricks.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/android-chrome-192x192.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/android-chrome-512x512.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/apple-touch-icon.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/favicon-16x16.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/favicon-32x32.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/favicon.ico +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/logo.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/logo_only.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/site.webmanifest +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/wordmark_and_logo.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/claude_skill.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/config.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_static/chartbook-custom.js +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_static/chartbook-freshness.js +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/blog_top_nav.html +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/chart_entry_bottom.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/chart_entry_top.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/dataframe_entry_top.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/freshness.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/layout.html +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/model_manifest.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/pipeline_dependencies.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/pipeline_manifest.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/provenance.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/charts.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/conf.py.j2 +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/contributing.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/dataframes.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/diagnostics.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/models.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/pipelines.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/versions.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_static/chartbook-freshness.js +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_templates/chart_entry_top.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_templates/freshness.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/charts.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/contributing.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/dataframes.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/diagnostics.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/models.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/pipelines.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/versions.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_static/chartbook-freshness.js +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_templates/chart_entry_top.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_templates/freshness.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/charts.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_static/chartbook-freshness.js +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/chart_entry_bottom.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/chart_entry_top.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/dataframe_entry_top.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/freshness.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/model_manifest.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/pipeline_dependencies.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/pipeline_manifest.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/provenance.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/charts.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/conf.py.j2 +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/env.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/gitcache.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/identity.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/models.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/path_validation.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/__init__.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_api.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_config.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_dual.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_exceptions.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_output.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_overlays.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_types.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_validation.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/__init__.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/_base.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/_matplotlib.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/_plotly.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/styles/chartbook.mplstyle +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/publish.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/run.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/scaffold.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/index/v1.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Catalog.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Chart.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Model.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Note.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Notebook.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Pipeline.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Series.json +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/stores/__init__.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/versions.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/writers/__init__.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/README.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/hooks/pre_gen_project.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/batch-commit/SKILL.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/jupytext-notebook/SKILL.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.env.example +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.gitignore +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.latexmkrc +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/README.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/assets/logo.png +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/assets/star_network_n_nodes.ipe +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/assets/star_network_n_nodes.pdf +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/chartbook.toml +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/data_manual/data_README.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/charts/repo_rates.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/charts/repo_rates_normalized.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/charts/repo_rates_normalized_w_balance_sheet.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/dataframes/repo_public.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/dataframes/repo_public_relative_fed.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/index_toc.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview/data_sources.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview/goals.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview/methodology.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview.md +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/dodo.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/r_requirements.txt +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/bibliography.bib +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/jpe.bst +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/my_article_header.sty +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/my_beamer_header.sty +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/my_common_header.sty +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/report_example.tex +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/report_simple_example.tex +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/slides_example.tex +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/slides_simple_example.tex +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/01_example_notebook_interactive.ipynb.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/02_example_with_dependencies.ipynb.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/03_public_repo_summary_charts.ipynb.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/04_example_regressions.Rmd +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/chart_relative_repo_rates.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_plot.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_r_plot.r +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_stata_plot.do +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_table.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/install_packages.r +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/load_fed_yield_curve.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/misc_tools.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pandas_to_latex_demo.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_CRSP_Compustat.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_CRSP_stock.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_bloomberg.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_fred.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_ofr_api_data.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_public_repo_data.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/settings.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/test_misc_tools.py +0 -0
- {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/test_pull_fred.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chartbook
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A tool for generating chart documentation websites
|
|
5
5
|
Project-URL: Documentation, https://backofficedev.github.io/chartbook
|
|
6
6
|
Project-URL: Issues, https://github.com/backofficedev/chartbook/issues
|
|
@@ -22,7 +22,7 @@ Requires-Dist: ablog>=0.11.11
|
|
|
22
22
|
Requires-Dist: click>=8.1.7
|
|
23
23
|
Requires-Dist: cookiecutter>=2.2
|
|
24
24
|
Requires-Dist: cruft
|
|
25
|
-
Requires-Dist: duckdb>=1.
|
|
25
|
+
Requires-Dist: duckdb>=1.5
|
|
26
26
|
Requires-Dist: fredapi>=0.5.0
|
|
27
27
|
Requires-Dist: jinja2>=3.1.4
|
|
28
28
|
Requires-Dist: kaleido>=0.2.1
|
|
@@ -36,8 +36,8 @@ Requires-Dist: pandas-datareader>=0.10.0
|
|
|
36
36
|
Requires-Dist: pandas>=2.1.4
|
|
37
37
|
Requires-Dist: plotly>=5.24.0
|
|
38
38
|
Requires-Dist: pluggy>=1.3.0
|
|
39
|
-
Requires-Dist: polars>=1.
|
|
40
|
-
Requires-Dist: pyarrow
|
|
39
|
+
Requires-Dist: polars>=1.33
|
|
40
|
+
Requires-Dist: pyarrow>=16
|
|
41
41
|
Requires-Dist: pydata-sphinx-theme>=0.15.4
|
|
42
42
|
Requires-Dist: python-decouple>=3.8
|
|
43
43
|
Requires-Dist: sphinx-autodoc2>=0.5.0
|
|
@@ -55,7 +55,7 @@ Requires-Dist: mkdocs-jupyter>=0.25; extra == 'mkdocs'
|
|
|
55
55
|
Requires-Dist: mkdocs-material>=9.5; extra == 'mkdocs'
|
|
56
56
|
Requires-Dist: mkdocs>=1.6; extra == 'mkdocs'
|
|
57
57
|
Provides-Extra: s3
|
|
58
|
-
Requires-Dist: boto3>=1.
|
|
58
|
+
Requires-Dist: boto3>=1.35; extra == 's3'
|
|
59
59
|
Description-Content-Type: text/markdown
|
|
60
60
|
|
|
61
61
|
# ChartBook
|
|
@@ -129,6 +129,21 @@ from chartbook import models
|
|
|
129
129
|
joblib.dump(model, models.path("rate_forecaster")) # the model gets a page: scores, lineage
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
### Keep files that are not tables
|
|
133
|
+
|
|
134
|
+
```toml
|
|
135
|
+
[files.filings]
|
|
136
|
+
path = "./_data/filings/**/*.htm" # a file, a folder, or a glob: text, PDFs, images, archives
|
|
137
|
+
license = "SEC public data"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from chartbook import assets
|
|
142
|
+
|
|
143
|
+
for path in assets.files("file/filings"): # the files, sorted; another pipeline's as pinned
|
|
144
|
+
...
|
|
145
|
+
```
|
|
146
|
+
|
|
132
147
|
### Browse your catalog
|
|
133
148
|
|
|
134
149
|
```bash
|
|
@@ -69,6 +69,21 @@ from chartbook import models
|
|
|
69
69
|
joblib.dump(model, models.path("rate_forecaster")) # the model gets a page: scores, lineage
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### Keep files that are not tables
|
|
73
|
+
|
|
74
|
+
```toml
|
|
75
|
+
[files.filings]
|
|
76
|
+
path = "./_data/filings/**/*.htm" # a file, a folder, or a glob: text, PDFs, images, archives
|
|
77
|
+
license = "SEC public data"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from chartbook import assets
|
|
82
|
+
|
|
83
|
+
for path in assets.files("file/filings"): # the files, sorted; another pipeline's as pinned
|
|
84
|
+
...
|
|
85
|
+
```
|
|
86
|
+
|
|
72
87
|
### Browse your catalog
|
|
73
88
|
|
|
74
89
|
```bash
|
|
@@ -31,11 +31,14 @@ dependencies = [
|
|
|
31
31
|
"tomli-w>=1.1.0",
|
|
32
32
|
# Data loading
|
|
33
33
|
"packaging",
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
#
|
|
38
|
-
"
|
|
34
|
+
# DuckDB's lazy frames are built on polars' IO-plugin seam: DuckDB 1.5's
|
|
35
|
+
# ask it for is_pure, which polars has from 1.33
|
|
36
|
+
"polars>=1.33",
|
|
37
|
+
# polars writes text in an Arrow IPC file as string_view, which pyarrow reads from 16
|
|
38
|
+
"pyarrow>=16",
|
|
39
|
+
# The local SQL engine: [sources] with backend = "duckdb", and data.sql().
|
|
40
|
+
# 1.4's lazy frames (.pl(lazy=True)) close their connection under polars 1.33 and later
|
|
41
|
+
"duckdb>=1.5",
|
|
39
42
|
"python-decouple>=3.8",
|
|
40
43
|
# Plotting
|
|
41
44
|
"matplotlib>=3.7.0",
|
|
@@ -79,7 +82,7 @@ dependencies = [
|
|
|
79
82
|
#
|
|
80
83
|
# The store in an S3 bucket: [store] url = "s3://bucket/prefix"
|
|
81
84
|
s3 = [
|
|
82
|
-
"boto3>=1.
|
|
85
|
+
"boto3>=1.35",
|
|
83
86
|
]
|
|
84
87
|
# A source in a Databricks SQL warehouse: [sources.x] backend = "databricks"
|
|
85
88
|
databricks = [
|
|
@@ -113,7 +116,7 @@ dev = [
|
|
|
113
116
|
"openpyxl",
|
|
114
117
|
"requests",
|
|
115
118
|
"seaborn",
|
|
116
|
-
"boto3>=1.
|
|
119
|
+
"boto3>=1.35",
|
|
117
120
|
"moto[s3]>=5",
|
|
118
121
|
"mkdocs>=1.6",
|
|
119
122
|
"mkdocs-material>=9.5",
|
|
@@ -195,7 +198,7 @@ extra-dependencies = [
|
|
|
195
198
|
"requests",
|
|
196
199
|
"seaborn",
|
|
197
200
|
# The S3 store, against a mocked bucket
|
|
198
|
-
"boto3>=1.
|
|
201
|
+
"boto3>=1.35",
|
|
199
202
|
"moto[s3]>=5",
|
|
200
203
|
# The MkDocs writer's build test
|
|
201
204
|
"mkdocs>=1.6",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -10,6 +10,10 @@ For the path of anything a pipeline declares:
|
|
|
10
10
|
assets.path("chart/yield_curve")
|
|
11
11
|
models.path("rate_forecaster")
|
|
12
12
|
|
|
13
|
+
For the files of an asset, and a table of them:
|
|
14
|
+
assets.files("file/filings")
|
|
15
|
+
assets.index("file/filings")
|
|
16
|
+
|
|
13
17
|
For plotting:
|
|
14
18
|
from chartbook import plotting
|
|
15
19
|
plotting.line(df, chart_id="my_chart", x="date", y="value")
|
|
@@ -21,7 +25,7 @@ For CLI (install via pipx):
|
|
|
21
25
|
|
|
22
26
|
import importlib
|
|
23
27
|
|
|
24
|
-
from . import assets, data, env, models
|
|
28
|
+
from . import assets, data, env, files, models
|
|
25
29
|
from .__about__ import __version__
|
|
26
30
|
|
|
27
31
|
# Lazy import for plotting to avoid requiring plotting dependencies
|
|
@@ -43,6 +47,7 @@ __all__ = [
|
|
|
43
47
|
"assets",
|
|
44
48
|
"data",
|
|
45
49
|
"models",
|
|
50
|
+
"files",
|
|
46
51
|
"env",
|
|
47
52
|
"plotting",
|
|
48
53
|
]
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Assets: everything a pipeline declares, addressed one way.
|
|
2
|
+
|
|
3
|
+
An asset is something ``chartbook.toml`` declares: a dataframe, a chart, a
|
|
4
|
+
notebook, a note, a model or a files asset. Each is addressed by a
|
|
5
|
+
*reference*. Within the pipeline you are in, that is the asset's name, with
|
|
6
|
+
its kind in front unless it is a dataframe::
|
|
7
|
+
|
|
8
|
+
from chartbook import assets
|
|
9
|
+
|
|
10
|
+
assets.path("treasury_rates") # [dataframes.treasury_rates]
|
|
11
|
+
assets.path("chart/yield_curve") # [charts.yield_curve]
|
|
12
|
+
assets.path("notebook/analysis") # [notebooks.analysis]
|
|
13
|
+
assets.path("note/methodology") # [notes.methodology]
|
|
14
|
+
assets.path("model/rate_forecaster") # [models.rate_forecaster]
|
|
15
|
+
assets.path("file/filings") # [files.filings]
|
|
16
|
+
|
|
17
|
+
An asset's path may be a folder or a glob. :func:`files` gives the files
|
|
18
|
+
themselves, sorted, and :func:`index` a table of them, with their sizes and
|
|
19
|
+
hashes, to choose from before opening any::
|
|
20
|
+
|
|
21
|
+
assets.files("file/filings") # [Path(".../2024/10-K.htm"), ...]
|
|
22
|
+
assets.index("file/filings") # a LazyFrame: path, size_bytes, sha256
|
|
23
|
+
|
|
24
|
+
The kind may be given as an argument instead, which reads better when the
|
|
25
|
+
name is in a variable. It says what you expect: a reference that names no
|
|
26
|
+
kind takes it, and one that names a different kind is an error::
|
|
27
|
+
|
|
28
|
+
assets.path("yield_curve", kind="chart") # the same chart
|
|
29
|
+
assets.path("chart/yield_curve", kind="chart") # the same chart
|
|
30
|
+
assets.path("yield_curve") # a *dataframe* called yield_curve
|
|
31
|
+
|
|
32
|
+
Data is the common case, so it gets the short spelling here and its own
|
|
33
|
+
front door: :func:`chartbook.data.path` is this function for dataframes
|
|
34
|
+
only, and :func:`chartbook.models.path` is this function for models only.
|
|
35
|
+
|
|
36
|
+
A reference can also be built and passed as an object, :class:`AssetRef`,
|
|
37
|
+
which is what :func:`parse` returns and what ChartBook carries internally::
|
|
38
|
+
|
|
39
|
+
ref = assets.parse("chart/yield_curve")
|
|
40
|
+
ref.kind, ref.name # ("chart", "yield_curve")
|
|
41
|
+
assets.path(ref)
|
|
42
|
+
|
|
43
|
+
An asset of another pipeline has that pipeline's name in front::
|
|
44
|
+
|
|
45
|
+
assets.path("fed_yield_curve:treasury_rates")
|
|
46
|
+
assets.path("fed_yield_curve:chart/curve_today")
|
|
47
|
+
|
|
48
|
+
The pipeline must be one that ``[dependencies]`` declares, and the path is
|
|
49
|
+
that of the copy ``chartbook.lock`` pins, in the store. It is not the other
|
|
50
|
+
pipeline's working directory, so what it holds does not change until you
|
|
51
|
+
run ``chartbook update``. Read it; do not write to it.
|
|
52
|
+
|
|
53
|
+
The full grammar, ``[registry/][scope/]pipeline:[kind/]name[#part][@rev]``,
|
|
54
|
+
is specified in ``docs_src/design/references.md``. A part, a revision and a
|
|
55
|
+
registry parse, and are refused as not yet supported.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import hashlib
|
|
59
|
+
import os
|
|
60
|
+
import sys
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
from typing import Optional, Union
|
|
63
|
+
|
|
64
|
+
from chartbook.refs import (
|
|
65
|
+
DEFAULT_KIND,
|
|
66
|
+
KINDS,
|
|
67
|
+
AssetRef,
|
|
68
|
+
KindMismatchError,
|
|
69
|
+
)
|
|
70
|
+
from chartbook.refs import parse_asset_ref as parse
|
|
71
|
+
from chartbook.resolve import FileLocation, caller_start_dirs, require_file, resolve
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"DEFAULT_KIND",
|
|
75
|
+
"KINDS",
|
|
76
|
+
"AssetRef",
|
|
77
|
+
"KindMismatchError",
|
|
78
|
+
"files",
|
|
79
|
+
"index",
|
|
80
|
+
"parse",
|
|
81
|
+
"path",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
#: The columns of :func:`index` that every row has; a ``key=value`` folder's key with one of these names is left out.
|
|
85
|
+
INDEX_COLUMNS = ("path", "size_bytes", "sha256")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _path(ref: Union[str, AssetRef], kind: Optional[str], start_dirs: list) -> Path:
|
|
89
|
+
"""Resolve a reference to a file path, from given starting points.
|
|
90
|
+
|
|
91
|
+
The public functions are thin shells over this one, because each must
|
|
92
|
+
read *its own* caller's stack frame to find the calling file.
|
|
93
|
+
"""
|
|
94
|
+
asset = parse(ref, kind=kind)
|
|
95
|
+
location = resolve(asset, start_dirs=start_dirs)
|
|
96
|
+
return require_file(location, f"The {asset.kind} {asset.name!r}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def path(
|
|
100
|
+
ref: Union[str, AssetRef],
|
|
101
|
+
kind: Optional[str] = None,
|
|
102
|
+
project_dir: Optional[Union[str, Path]] = None,
|
|
103
|
+
) -> Path:
|
|
104
|
+
"""Get the path of an asset: this pipeline's own, or a pinned copy of another's.
|
|
105
|
+
|
|
106
|
+
The enclosing ``chartbook.toml`` is the single source of truth for where
|
|
107
|
+
an asset lives, so code can ask for the path rather than repeat it:
|
|
108
|
+
|
|
109
|
+
>>> from chartbook import assets
|
|
110
|
+
>>> fig.write_html(assets.path("chart/yield_curve")) # doctest: +SKIP
|
|
111
|
+
|
|
112
|
+
and a build definition can use the same call for what it reads and what
|
|
113
|
+
it writes. The path is whatever ``path`` the entry declares, made
|
|
114
|
+
absolute; nothing checks that the file exists, so a target can be named
|
|
115
|
+
before it is built.
|
|
116
|
+
|
|
117
|
+
Each kind of asset has its own names: ``[dataframes.gdp]`` and
|
|
118
|
+
``[charts.gdp]`` are two assets, ``gdp`` and ``chart/gdp``. Asking for a
|
|
119
|
+
name its table does not declare raises immediately, naming the table to
|
|
120
|
+
add, or saying what kind of asset the name is declared as.
|
|
121
|
+
|
|
122
|
+
This is a convenience, never a requirement. A script that hardcodes the
|
|
123
|
+
same path, in any language, works just as well; ``chartbook path`` on the
|
|
124
|
+
command line gives the same answer to a shell, Make, R or Stata.
|
|
125
|
+
|
|
126
|
+
**Another pipeline's asset** is named ``"pipeline:name"``, and resolves
|
|
127
|
+
differently on purpose. The pipeline must be declared in
|
|
128
|
+
``[dependencies]``, and the path returned is in the store: the copy that
|
|
129
|
+
``chartbook.lock`` pins, not the other pipeline's working directory.
|
|
130
|
+
|
|
131
|
+
>>> assets.path("fed_yield_curve:treasury_rates") # doctest: +SKIP
|
|
132
|
+
|
|
133
|
+
So it does not change while the other pipeline is being worked on, and
|
|
134
|
+
moves only when you run ``chartbook update``. It must have been pinned
|
|
135
|
+
(``chartbook pin``), and is for reading: never write to it.
|
|
136
|
+
|
|
137
|
+
:param ref: A reference to the asset: ``"name"`` for a dataframe,
|
|
138
|
+
``"kind/name"`` for anything else, ``"pipeline:name"`` for another
|
|
139
|
+
pipeline's, or an :class:`AssetRef`.
|
|
140
|
+
:type ref: Union[str, AssetRef]
|
|
141
|
+
:param kind: The kind of asset expected: ``"dataframe"``, ``"chart"``,
|
|
142
|
+
``"notebook"``, ``"note"``, ``"model"`` or ``"file"``. A reference that names no kind takes
|
|
143
|
+
this one. By default, a reference that names no kind is a dataframe.
|
|
144
|
+
:type kind: Optional[str]
|
|
145
|
+
:param project_dir: Where to look for the pipeline. The enclosing
|
|
146
|
+
``chartbook.toml`` is found by walking up from here. By default the
|
|
147
|
+
search starts at the calling file's directory, then falls back to the
|
|
148
|
+
current working directory.
|
|
149
|
+
:type project_dir: Optional[Union[str, Path]]
|
|
150
|
+
:returns: The absolute path of the asset (it may contain glob
|
|
151
|
+
characters for partitioned data; :func:`files` gives the files).
|
|
152
|
+
:rtype: Path
|
|
153
|
+
:raises AssetNotDeclaredError: If the manifest declares no such asset.
|
|
154
|
+
:raises DependencyNotDeclaredError: If the reference names a pipeline
|
|
155
|
+
that ``[dependencies]`` does not declare.
|
|
156
|
+
:raises AssetNotPinnedError: If another pipeline's asset has no pinned
|
|
157
|
+
copy in the store.
|
|
158
|
+
:raises ManifestNotFoundError: If no ``chartbook.toml`` encloses the
|
|
159
|
+
starting directory.
|
|
160
|
+
:raises KindMismatchError: If the reference names a kind other than *kind*.
|
|
161
|
+
:raises ValueError: If the reference does not follow the grammar, uses
|
|
162
|
+
syntax that is reserved and not yet supported, or the entry has no
|
|
163
|
+
``path``.
|
|
164
|
+
"""
|
|
165
|
+
return _path(ref, kind, caller_start_dirs(project_dir, sys._getframe(1)))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _located(ref: Union[str, AssetRef], kind: Optional[str], start_dirs: list) -> tuple[AssetRef, FileLocation]:
|
|
169
|
+
"""A reference and where its files are; a table or a query has none."""
|
|
170
|
+
asset = parse(ref, kind=kind)
|
|
171
|
+
location = resolve(asset, start_dirs=start_dirs)
|
|
172
|
+
if not isinstance(location, FileLocation):
|
|
173
|
+
raise TypeError(
|
|
174
|
+
f"The {asset.kind} {asset.name!r} is {location.kind}-backed, so it has no files. "
|
|
175
|
+
f"Use data.load() to read it, data.sql() to query it, or data.ref() for its name in SQL."
|
|
176
|
+
)
|
|
177
|
+
return asset, location
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _not_there(asset: AssetRef, location: FileLocation) -> FileNotFoundError:
|
|
181
|
+
return FileNotFoundError(
|
|
182
|
+
f"The {asset.kind} {str(asset)!r} has no files: no file is at {location.path}. "
|
|
183
|
+
f"If it is this pipeline's own, its build makes them."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _found(asset: AssetRef, location: FileLocation) -> list[tuple[str, Path]]:
|
|
188
|
+
"""Each file of an asset, by its path in the asset, sorted as a hash lists them."""
|
|
189
|
+
from chartbook import hashing
|
|
190
|
+
from chartbook.store import on_disk, plain_parts
|
|
191
|
+
|
|
192
|
+
if location.listed:
|
|
193
|
+
# A folder from the store: the files its record lists, and nothing else the folder may hold
|
|
194
|
+
listed = [(relative, plain_parts(relative)) for relative in location.listed]
|
|
195
|
+
return sorted((relative, on_disk(Path(location.path), parts)) for relative, parts in listed if parts)
|
|
196
|
+
found = hashing.find_files(location.path)
|
|
197
|
+
if found is None or not found.files:
|
|
198
|
+
raise _not_there(asset, location)
|
|
199
|
+
if found.single:
|
|
200
|
+
return [(found.files[0].name, Path(found.files[0]))]
|
|
201
|
+
return sorted((Path(os.path.relpath(file, found.root)).as_posix(), Path(file)) for file in found.files)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def files(
|
|
205
|
+
ref: Union[str, AssetRef],
|
|
206
|
+
kind: Optional[str] = None,
|
|
207
|
+
project_dir: Optional[Union[str, Path]] = None,
|
|
208
|
+
) -> list[Path]:
|
|
209
|
+
"""Get the files of an asset: the file, a folder's files, or a glob's matches, sorted.
|
|
210
|
+
|
|
211
|
+
>>> from chartbook import assets
|
|
212
|
+
>>> for path in assets.files("filings:file/earnings_releases"): # doctest: +SKIP
|
|
213
|
+
... records = read_one(path)
|
|
214
|
+
|
|
215
|
+
The asset is found as :func:`path` finds it, and its files are the
|
|
216
|
+
ones its content hash is made of (:mod:`chartbook.hashing`): every file
|
|
217
|
+
under a folder or matched by a glob, but ``.DS_Store`` and the few
|
|
218
|
+
others operating systems write by themselves. Of another pipeline's
|
|
219
|
+
asset they are the files of the pinned copy, as its record lists them.
|
|
220
|
+
They are sorted by their path inside the asset, so a dataset of a file
|
|
221
|
+
a year is read in order of the years.
|
|
222
|
+
|
|
223
|
+
It works for every asset with files: a files asset, a dataframe of
|
|
224
|
+
many parts (to read one at a time), a model that is a folder.
|
|
225
|
+
|
|
226
|
+
:param ref: A reference to the asset, as for :func:`path`.
|
|
227
|
+
:param kind: The kind of asset expected, as for :func:`path`.
|
|
228
|
+
:param project_dir: Where to look for the pipeline, as for :func:`path`.
|
|
229
|
+
:returns: The absolute path of each file.
|
|
230
|
+
:raises FileNotFoundError: If nothing is there: the asset is not built.
|
|
231
|
+
:raises TypeError: If the asset is a table or a query in a source.
|
|
232
|
+
:raises AssetNotDeclaredError: As for :func:`path`, and the rest of its errors.
|
|
233
|
+
"""
|
|
234
|
+
asset, location = _located(ref, kind, caller_start_dirs(project_dir, sys._getframe(1)))
|
|
235
|
+
return [file for _, file in _found(asset, location)]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _hash_cache(declared: Path):
|
|
239
|
+
"""The hash cache for one asset's files, kept in this machine's cache: an asset hashed again reads only what changed."""
|
|
240
|
+
from chartbook import hashing
|
|
241
|
+
from chartbook.config import get_cache_path
|
|
242
|
+
|
|
243
|
+
name = hashlib.sha256(str(declared).encode("utf-8", errors="surrogateescape")).hexdigest()[:32]
|
|
244
|
+
return hashing.HashCache(get_cache_path() / "hashes" / f"{name}.json")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _rows(asset: AssetRef, location: FileLocation) -> list[tuple[Path, str, int, str]]:
|
|
248
|
+
"""Each file of an asset with its path in the asset, its size and its hash.
|
|
249
|
+
|
|
250
|
+
From the store's record when the store holds what is read: nothing is
|
|
251
|
+
read but the record. Otherwise the files are hashed, each read only if
|
|
252
|
+
it has changed since it was last hashed.
|
|
253
|
+
"""
|
|
254
|
+
from chartbook import hashing
|
|
255
|
+
from chartbook.files import listing_root
|
|
256
|
+
from chartbook.store import default_store, on_disk, plain_parts
|
|
257
|
+
from chartbook.utils import is_glob_pattern
|
|
258
|
+
|
|
259
|
+
declared = Path(location.path)
|
|
260
|
+
single = not is_glob_pattern(str(declared)) and declared.is_file()
|
|
261
|
+
listed = default_store().listing(location.content_hash, single) if location.content_hash else None
|
|
262
|
+
if listed is not None:
|
|
263
|
+
if single:
|
|
264
|
+
# One file, read by the name its asset gives it: the same bytes may be two assets'
|
|
265
|
+
return [(declared, declared.name, listed[0].size_bytes, listed[0].content_hash)]
|
|
266
|
+
root = listing_root(declared)
|
|
267
|
+
return [
|
|
268
|
+
(on_disk(root, plain_parts(entry.path)), entry.path, entry.size_bytes, entry.content_hash)
|
|
269
|
+
for entry in listed
|
|
270
|
+
if plain_parts(entry.path)
|
|
271
|
+
]
|
|
272
|
+
cache = _hash_cache(declared)
|
|
273
|
+
try:
|
|
274
|
+
content = hashing.hash_path(declared, cache)
|
|
275
|
+
except OSError as error:
|
|
276
|
+
raise FileNotFoundError(f"The {asset.kind} {str(asset)!r} could not be read: {error}") from None
|
|
277
|
+
if content is None:
|
|
278
|
+
raise _not_there(asset, location)
|
|
279
|
+
try:
|
|
280
|
+
cache.save()
|
|
281
|
+
except OSError:
|
|
282
|
+
pass # a cache that cannot be written is only slower
|
|
283
|
+
if not content.files and not single:
|
|
284
|
+
raise _not_there(asset, location) # a folder with no file in it
|
|
285
|
+
if not content.files:
|
|
286
|
+
return [(declared, declared.name, content.size_bytes, content.content_hash)]
|
|
287
|
+
root = listing_root(declared)
|
|
288
|
+
return [(root.joinpath(*entry.path.split("/")), entry.path, entry.size_bytes, entry.content_hash) for entry in content.files]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def index(
|
|
292
|
+
ref: Union[str, AssetRef],
|
|
293
|
+
kind: Optional[str] = None,
|
|
294
|
+
project_dir: Optional[Union[str, Path]] = None,
|
|
295
|
+
):
|
|
296
|
+
"""Get a table of an asset's files, one row per file, to choose from before opening any.
|
|
297
|
+
|
|
298
|
+
>>> import polars as pl
|
|
299
|
+
>>> from chartbook import assets
|
|
300
|
+
>>> chosen = (
|
|
301
|
+
... assets.index("filings:file/earnings_releases")
|
|
302
|
+
... .filter(pl.col("year") >= "2020")
|
|
303
|
+
... .collect()
|
|
304
|
+
... ) # doctest: +SKIP
|
|
305
|
+
|
|
306
|
+
The columns are ``path`` (the file, as :func:`files` gives it),
|
|
307
|
+
``size_bytes``, ``sha256`` (the file's hash, in hex), and then the key
|
|
308
|
+
of each ``key=value`` folder the files are in, as text:
|
|
309
|
+
``filings/year=2024/q=1/x.htm`` has ``year`` ``"2024"`` and ``q``
|
|
310
|
+
``"1"``. The rows are in the order :func:`files` gives.
|
|
311
|
+
|
|
312
|
+
Of another pipeline's asset, every file's size and hash come from the
|
|
313
|
+
record the store keeps of it, and nothing is read. Of this pipeline's,
|
|
314
|
+
the files are hashed, and a file is read again only when it has changed
|
|
315
|
+
(a hash cache in this machine's cache, ``~/.chartbook/cache/hashes``).
|
|
316
|
+
|
|
317
|
+
:param ref: A reference to the asset, as for :func:`path`.
|
|
318
|
+
:param kind: The kind of asset expected, as for :func:`path`.
|
|
319
|
+
:param project_dir: Where to look for the pipeline, as for :func:`path`.
|
|
320
|
+
:returns: A ``polars.LazyFrame``.
|
|
321
|
+
:raises FileNotFoundError: If nothing is there: the asset is not built.
|
|
322
|
+
:raises TypeError: If the asset is a table or a query in a source.
|
|
323
|
+
:raises AssetNotDeclaredError: As for :func:`path`, and the rest of its errors.
|
|
324
|
+
"""
|
|
325
|
+
import polars as pl
|
|
326
|
+
|
|
327
|
+
from chartbook.files import partitions
|
|
328
|
+
|
|
329
|
+
asset, location = _located(ref, kind, caller_start_dirs(project_dir, sys._getframe(1)))
|
|
330
|
+
rows = sorted(_rows(asset, location), key=lambda row: row[1])
|
|
331
|
+
keys: dict = {}
|
|
332
|
+
for _, relative, _, _ in rows:
|
|
333
|
+
for key in partitions(relative):
|
|
334
|
+
if key not in INDEX_COLUMNS:
|
|
335
|
+
keys.setdefault(key, None)
|
|
336
|
+
columns: dict = {
|
|
337
|
+
"path": [str(file) for file, _, _, _ in rows],
|
|
338
|
+
"size_bytes": [size for _, _, size, _ in rows],
|
|
339
|
+
"sha256": [content_hash.split(":", 1)[-1] for _, _, _, content_hash in rows],
|
|
340
|
+
}
|
|
341
|
+
for key in keys:
|
|
342
|
+
columns[key] = [partitions(relative).get(key) for _, relative, _, _ in rows]
|
|
343
|
+
schema = {"path": pl.String, "size_bytes": pl.Int64, "sha256": pl.String, **{key: pl.String for key in keys}}
|
|
344
|
+
return pl.DataFrame(columns, schema=schema).lazy()
|
|
@@ -18,9 +18,11 @@ and which dialect lives there, and it substitutes identifiers. The SQL is
|
|
|
18
18
|
the user's (:func:`chartbook.data.sql`).
|
|
19
19
|
|
|
20
20
|
Three backends are built. ``parquet`` is what a file-backed dataframe has
|
|
21
|
-
always been: it reads the file,
|
|
21
|
+
always been: it reads the file, parquet, CSV, JSON lines or Arrow IPC
|
|
22
|
+
(:mod:`chartbook.formats`), and in SQL it is DuckDB's reader of its format,
|
|
23
|
+
``read_parquet('…')``, ``read_csv('…', …)`` or ``read_json('…', …)``.
|
|
22
24
|
``duckdb`` is the local engine: a file, or ``:memory:``, that holds tables
|
|
23
|
-
and runs any SQL that mentions them, and reads
|
|
25
|
+
and runs any SQL that mentions them, and reads those files too, so a
|
|
24
26
|
query can join a table of one source with a file of this pipeline.
|
|
25
27
|
``databricks`` is a SQL warehouse (:mod:`chartbook.backends.databricks`),
|
|
26
28
|
whose driver ``pip install chartbook[databricks]`` installs. A source's
|
|
@@ -391,7 +393,7 @@ class Backend:
|
|
|
391
393
|
:class:`BackendError` itself, by Polars version.
|
|
392
394
|
|
|
393
395
|
``reads_files`` says whether SQL run in it may name a file of this
|
|
394
|
-
machine as :class:`ParquetBackend` writes one, ``read_parquet('…')
|
|
396
|
+
machine as :class:`ParquetBackend` writes one, ``read_parquet('…')`` say:
|
|
395
397
|
so whether :func:`chartbook.data.sql` may join this engine's tables
|
|
396
398
|
with a pipeline's files in one query.
|
|
397
399
|
"""
|
|
@@ -428,11 +430,16 @@ class Backend:
|
|
|
428
430
|
"""A table as a quoted identifier; a query as a sub-select: what stands for it in SQL here."""
|
|
429
431
|
raise NotImplementedError
|
|
430
432
|
|
|
431
|
-
def sql(self, query: str):
|
|
433
|
+
def sql(self, query: str, arrow: Optional[dict] = None):
|
|
432
434
|
"""Run SQL in this engine and return a Polars LazyFrame.
|
|
433
435
|
|
|
434
436
|
The engine binds the query here, so a name it does not know is
|
|
435
|
-
refused at once; collecting the frame runs it.
|
|
437
|
+
refused at once; collecting the frame runs it. *arrow* is Arrow
|
|
438
|
+
data the query names, by the name it goes by, for an engine that
|
|
439
|
+
reads files (``reads_files``) and can scan Arrow data: an Arrow IPC
|
|
440
|
+
file, which DuckDB has no reader of its own for. A backend whose
|
|
441
|
+
``sql()`` takes no such argument is handed none, and a query naming
|
|
442
|
+
such a file is refused before it reaches it.
|
|
436
443
|
"""
|
|
437
444
|
raise NotImplementedError
|
|
438
445
|
|
|
@@ -450,10 +457,12 @@ class Backend:
|
|
|
450
457
|
|
|
451
458
|
|
|
452
459
|
class ParquetBackend:
|
|
453
|
-
"""What a file-backed dataframe has always been.
|
|
460
|
+
"""What a file-backed dataframe has always been, in any format it is stored in.
|
|
454
461
|
|
|
455
|
-
|
|
456
|
-
|
|
462
|
+
It reads through :mod:`chartbook.formats`, as everything that reads a
|
|
463
|
+
file does. In SQL a file is DuckDB's reader of its format:
|
|
464
|
+
``read_parquet('…')``, which DuckDB reads as a table, partitions and
|
|
465
|
+
all; ``read_csv('…', …)``; or ``read_json('…', …)``.
|
|
457
466
|
"""
|
|
458
467
|
|
|
459
468
|
name = "parquet"
|
|
@@ -461,34 +470,15 @@ class ParquetBackend:
|
|
|
461
470
|
@staticmethod
|
|
462
471
|
def ref(location: "FileLocation") -> str:
|
|
463
472
|
"""The file as a DuckDB table expression."""
|
|
464
|
-
from chartbook
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
said = nothing_to_read(location)
|
|
469
|
-
if said:
|
|
470
|
-
raise ValueError(said)
|
|
471
|
-
# With / on every system: DuckDB reads it so on Windows too, and SQL
|
|
472
|
-
# with a path in it reads the same wherever it is pasted
|
|
473
|
-
text = Path(location.path).as_posix()
|
|
474
|
-
if location.files:
|
|
475
|
-
# A directory pinned from another pipeline: the files its record lists
|
|
476
|
-
listed = ", ".join(quote_literal(Path(file).as_posix()) for file in location.files)
|
|
477
|
-
return f"read_parquet([{listed}], hive_partitioning = true)"
|
|
478
|
-
if is_glob_pattern(text):
|
|
479
|
-
return f"read_parquet({quote_literal(text)}, hive_partitioning = true)"
|
|
480
|
-
return f"read_parquet({quote_literal(text)})"
|
|
473
|
+
from chartbook import formats
|
|
474
|
+
|
|
475
|
+
return formats.sql(location)
|
|
481
476
|
|
|
482
477
|
@staticmethod
|
|
483
478
|
def scan(location: "FileLocation"):
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
from chartbook.resolve import nothing_to_read
|
|
479
|
+
from chartbook import formats
|
|
487
480
|
|
|
488
|
-
|
|
489
|
-
if said:
|
|
490
|
-
raise ValueError(said)
|
|
491
|
-
return pl.scan_parquet([str(file) for file in location.files] or location.path, hive_partitioning=True)
|
|
481
|
+
return formats.scan_location(location)
|
|
492
482
|
|
|
493
483
|
|
|
494
484
|
class DuckDBBackend(Backend):
|
|
@@ -564,7 +554,7 @@ class DuckDBBackend(Backend):
|
|
|
564
554
|
return subselect(location.sql)
|
|
565
555
|
return ParquetBackend.ref(location)
|
|
566
556
|
|
|
567
|
-
def sql(self, query: str):
|
|
557
|
+
def sql(self, query: str, arrow: Optional[dict] = None):
|
|
568
558
|
"""Run SQL in this engine and return a Polars LazyFrame.
|
|
569
559
|
|
|
570
560
|
The engine binds the query here, so a name it does not know or SQL
|
|
@@ -573,13 +563,20 @@ class DuckDBBackend(Backend):
|
|
|
573
563
|
a value, is raised then with DuckDB's message: as Polars'
|
|
574
564
|
``ComputeError`` or as DuckDB's own exception, by Polars version.
|
|
575
565
|
|
|
566
|
+
:param arrow: Arrow data the query names, by the name it goes by:
|
|
567
|
+
an Arrow IPC file, which DuckDB has no reader of its own for
|
|
568
|
+
(:func:`chartbook.formats.arrow_dataset`). It is registered with
|
|
569
|
+
the connection the query runs in, for as long as that lives.
|
|
576
570
|
:raises chartbook.backends.BackendError: If DuckDB refuses the query. The message is
|
|
577
571
|
DuckDB's own; the SQL is the user's.
|
|
578
572
|
"""
|
|
579
573
|
import duckdb
|
|
580
574
|
|
|
581
575
|
try:
|
|
582
|
-
|
|
576
|
+
connection = self.connect()
|
|
577
|
+
for name, table in (arrow or {}).items():
|
|
578
|
+
connection.register(name, table)
|
|
579
|
+
return connection.sql(query).pl(lazy=True)
|
|
583
580
|
except duckdb.Error as error:
|
|
584
581
|
raise BackendError(str(error)) from None
|
|
585
582
|
|
|
@@ -589,8 +586,9 @@ class DuckDBBackend(Backend):
|
|
|
589
586
|
|
|
590
587
|
try:
|
|
591
588
|
connection = self.connect()
|
|
589
|
+
# Fetched whole: a relation has no fetchone from DuckDB 2.0, and a result read only in part keeps the connection busy
|
|
592
590
|
described = connection.sql(f"describe select * from {self.ref(location)}").fetchall()
|
|
593
|
-
(rows,) = connection.sql(f"select count(*) from {self.ref(location)}").
|
|
591
|
+
((rows,),) = connection.sql(f"select count(*) from {self.ref(location)}").fetchall()
|
|
594
592
|
except duckdb.Error as error:
|
|
595
593
|
raise BackendError(str(error)) from None
|
|
596
594
|
return [{"name": row[0], "dtype": row[1]} for row in described], int(rows)
|
|
@@ -608,7 +606,7 @@ class DuckDBBackend(Backend):
|
|
|
608
606
|
return self.connect().sql(
|
|
609
607
|
f"select min(try_cast({name} as timestamp)), max(try_cast({name} as timestamp)) "
|
|
610
608
|
f"from {self.ref(location)}"
|
|
611
|
-
).
|
|
609
|
+
).fetchall()[0]
|
|
612
610
|
except duckdb.Error as error:
|
|
613
611
|
raise BackendError(str(error)) from None
|
|
614
612
|
|
|
@@ -362,7 +362,7 @@ def _refuse_unreadable(built: dict) -> None:
|
|
|
362
362
|
unreadable = [
|
|
363
363
|
record
|
|
364
364
|
for record in built.get("records", [])
|
|
365
|
-
if record.get("kind") in ("Dataframe", "Chart", "Notebook", "Model")
|
|
365
|
+
if record.get("kind") in ("Dataframe", "Chart", "Notebook", "Model", "File")
|
|
366
366
|
and (record.get("location") or {}).get("kind") == "file"
|
|
367
367
|
and (record.get("location") or {}).get("resolved_path")
|
|
368
368
|
and "content_hash" not in record
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
8fc26a3d96b1b6f62651edd08ac1d0f88b3330e2
|