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.
Files changed (226) hide show
  1. {chartbook-0.2.0 → chartbook-0.3.0}/PKG-INFO +20 -5
  2. {chartbook-0.2.0 → chartbook-0.3.0}/README.md +15 -0
  3. {chartbook-0.2.0 → chartbook-0.3.0}/pyproject.toml +11 -8
  4. chartbook-0.3.0/src/chartbook/__about__.py +1 -0
  5. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/__init__.py +6 -1
  6. chartbook-0.3.0/src/chartbook/assets.py +344 -0
  7. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/backends/__init__.py +34 -36
  8. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/build_docs.py +1 -1
  9. chartbook-0.3.0/src/chartbook/built_from.txt +1 -0
  10. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/check.py +262 -6
  11. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/cli.py +242 -14
  12. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/conf_validation.py +38 -10
  13. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/create_data_glimpses.py +41 -20
  14. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/data.py +111 -69
  15. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/diagnostics.py +29 -0
  16. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_static/custom.css +6 -0
  17. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/dataframe_manifest.md +1 -1
  18. chartbook-0.3.0/src/chartbook/docs_src_catalog/_templates/file_manifest.md +14 -0
  19. chartbook-0.3.0/src/chartbook/docs_src_catalog/files.md +8 -0
  20. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/index.md +9 -0
  21. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_static/custom.css +6 -0
  22. chartbook-0.3.0/src/chartbook/docs_src_mkdocs/catalog/files.md +4 -0
  23. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/index.md +1 -0
  24. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_static/custom.css +6 -0
  25. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/index.md +6 -0
  26. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_static/custom.css +6 -0
  27. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/dataframe_manifest.md +1 -1
  28. chartbook-0.3.0/src/chartbook/docs_src_pipeline/_templates/file_manifest.md +14 -0
  29. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/index.md +8 -0
  30. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/errors.py +19 -0
  31. chartbook-0.3.0/src/chartbook/files.py +320 -0
  32. chartbook-0.3.0/src/chartbook/formats.py +1316 -0
  33. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/hashing.py +115 -13
  34. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/incremental.py +7 -2
  35. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/index.py +125 -24
  36. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/indexdiff.py +1 -1
  37. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/lineage.py +1 -1
  38. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/lock.py +305 -23
  39. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/manifest.py +52 -3
  40. chartbook-0.3.0/src/chartbook/migrate.py +662 -0
  41. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/provenance.py +4 -2
  42. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/refs.py +5 -4
  43. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/resolve.py +62 -17
  44. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/lock/v1.json +1 -1
  45. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/snapshot/v1.json +17 -0
  46. chartbook-0.3.0/src/chartbook/schemas/chartbook/snapshot/v2.json +359 -0
  47. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Dataframe.json +57 -0
  48. chartbook-0.3.0/src/chartbook/schemas/chartbook/v1/File.json +195 -0
  49. chartbook-0.3.0/src/chartbook/sharing.py +250 -0
  50. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/sitefiles.py +48 -7
  51. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/skills/SKILL.md +23 -7
  52. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/skills/catalog_system.md +6 -4
  53. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/skills/manifest_files.md +60 -3
  54. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/snapshot.py +212 -57
  55. chartbook-0.3.0/src/chartbook/store.py +3316 -0
  56. chartbook-0.3.0/src/chartbook/stores/s3.py +2282 -0
  57. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/utils.py +30 -23
  58. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/writers/mkdocs.py +12 -2
  59. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/writers/sphinx.py +298 -23
  60. {chartbook-0.2.0 → chartbook-0.3.0}/template/cookiecutter.json +2 -2
  61. {chartbook-0.2.0 → chartbook-0.3.0}/template/hooks/post_gen_project.py +5 -0
  62. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/chartbook/SKILL.md +23 -7
  63. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/chartbook/catalog_system.md +6 -4
  64. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/chartbook/manifest_files.md +60 -3
  65. chartbook-0.3.0/template/{{cookiecutter.project_slug}}/.github/workflows/python_tests.yml +51 -0
  66. chartbook-0.3.0/template/{{cookiecutter.project_slug}}/.python-version +1 -0
  67. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/environment.yml +2 -2
  68. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/pixi.toml +2 -2
  69. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/pyproject.toml +2 -2
  70. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/requirements.txt +2 -2
  71. chartbook-0.2.0/src/chartbook/__about__.py +0 -1
  72. chartbook-0.2.0/src/chartbook/assets.py +0 -150
  73. chartbook-0.2.0/src/chartbook/built_from.txt +0 -1
  74. chartbook-0.2.0/src/chartbook/store.py +0 -1659
  75. chartbook-0.2.0/src/chartbook/stores/s3.py +0 -914
  76. chartbook-0.2.0/template/{{cookiecutter.project_slug}}/.github/workflows/python_tests.yml +0 -32
  77. {chartbook-0.2.0 → chartbook-0.3.0}/.gitignore +0 -0
  78. {chartbook-0.2.0 → chartbook-0.3.0}/LICENSE.md +0 -0
  79. {chartbook-0.2.0 → chartbook-0.3.0}/hatch_build.py +0 -0
  80. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/backends/databricks.py +0 -0
  81. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/android-chrome-192x192.png +0 -0
  82. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/android-chrome-512x512.png +0 -0
  83. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/apple-touch-icon.png +0 -0
  84. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/favicon-16x16.png +0 -0
  85. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/favicon-32x32.png +0 -0
  86. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/favicon.ico +0 -0
  87. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/logo.png +0 -0
  88. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/logo_only.png +0 -0
  89. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/site.webmanifest +0 -0
  90. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/branding/wordmark_and_logo.png +0 -0
  91. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/claude_skill.py +0 -0
  92. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/config.py +0 -0
  93. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_static/chartbook-custom.js +0 -0
  94. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_static/chartbook-freshness.js +0 -0
  95. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/blog_top_nav.html +0 -0
  96. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/chart_entry_bottom.md +0 -0
  97. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/chart_entry_top.md +0 -0
  98. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/dataframe_entry_top.md +0 -0
  99. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/freshness.md +0 -0
  100. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/layout.html +0 -0
  101. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/model_manifest.md +0 -0
  102. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/pipeline_dependencies.md +0 -0
  103. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/pipeline_manifest.md +0 -0
  104. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/_templates/provenance.md +0 -0
  105. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/charts.md +0 -0
  106. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/conf.py.j2 +0 -0
  107. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/contributing.md +0 -0
  108. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/dataframes.md +0 -0
  109. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/diagnostics.md +0 -0
  110. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/models.md +0 -0
  111. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/pipelines.md +0 -0
  112. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_catalog/versions.md +0 -0
  113. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_static/chartbook-freshness.js +0 -0
  114. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_templates/chart_entry_top.md +0 -0
  115. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/_templates/freshness.md +0 -0
  116. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/charts.md +0 -0
  117. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/contributing.md +0 -0
  118. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/dataframes.md +0 -0
  119. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/diagnostics.md +0 -0
  120. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/models.md +0 -0
  121. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/pipelines.md +0 -0
  122. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/catalog/versions.md +0 -0
  123. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_static/chartbook-freshness.js +0 -0
  124. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_templates/chart_entry_top.md +0 -0
  125. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/_templates/freshness.md +0 -0
  126. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_mkdocs/pipeline/charts.md +0 -0
  127. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_static/chartbook-freshness.js +0 -0
  128. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/chart_entry_bottom.md +0 -0
  129. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/chart_entry_top.md +0 -0
  130. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/dataframe_entry_top.md +0 -0
  131. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/freshness.md +0 -0
  132. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/model_manifest.md +0 -0
  133. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/pipeline_dependencies.md +0 -0
  134. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/pipeline_manifest.md +0 -0
  135. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/_templates/provenance.md +0 -0
  136. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/charts.md +0 -0
  137. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/docs_src_pipeline/conf.py.j2 +0 -0
  138. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/env.py +0 -0
  139. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/gitcache.py +0 -0
  140. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/identity.py +0 -0
  141. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/models.py +0 -0
  142. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/path_validation.py +0 -0
  143. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/__init__.py +0 -0
  144. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_api.py +0 -0
  145. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_config.py +0 -0
  146. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_dual.py +0 -0
  147. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_exceptions.py +0 -0
  148. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_output.py +0 -0
  149. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_overlays.py +0 -0
  150. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_types.py +0 -0
  151. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/_validation.py +0 -0
  152. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/__init__.py +0 -0
  153. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/_base.py +0 -0
  154. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/_matplotlib.py +0 -0
  155. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/backends/_plotly.py +0 -0
  156. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/plotting/styles/chartbook.mplstyle +0 -0
  157. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/publish.py +0 -0
  158. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/run.py +0 -0
  159. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/scaffold.py +0 -0
  160. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/index/v1.json +0 -0
  161. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Catalog.json +0 -0
  162. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Chart.json +0 -0
  163. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Model.json +0 -0
  164. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Note.json +0 -0
  165. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Notebook.json +0 -0
  166. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Pipeline.json +0 -0
  167. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/schemas/chartbook/v1/Series.json +0 -0
  168. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/stores/__init__.py +0 -0
  169. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/versions.py +0 -0
  170. {chartbook-0.2.0 → chartbook-0.3.0}/src/chartbook/writers/__init__.py +0 -0
  171. {chartbook-0.2.0 → chartbook-0.3.0}/template/README.md +0 -0
  172. {chartbook-0.2.0 → chartbook-0.3.0}/template/hooks/pre_gen_project.py +0 -0
  173. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/batch-commit/SKILL.md +0 -0
  174. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.claude/skills/jupytext-notebook/SKILL.md +0 -0
  175. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.env.example +0 -0
  176. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.gitignore +0 -0
  177. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/.latexmkrc +0 -0
  178. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/README.md +0 -0
  179. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/assets/logo.png +0 -0
  180. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/assets/star_network_n_nodes.ipe +0 -0
  181. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/assets/star_network_n_nodes.pdf +0 -0
  182. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/chartbook.toml +0 -0
  183. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/data_manual/data_README.md +0 -0
  184. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/charts/repo_rates.md +0 -0
  185. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/charts/repo_rates_normalized.md +0 -0
  186. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/charts/repo_rates_normalized_w_balance_sheet.md +0 -0
  187. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/dataframes/repo_public.md +0 -0
  188. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/dataframes/repo_public_relative_fed.md +0 -0
  189. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/index_toc.md +0 -0
  190. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview/data_sources.md +0 -0
  191. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview/goals.md +0 -0
  192. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview/methodology.md +0 -0
  193. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/docs_src/site/project_overview.md +0 -0
  194. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/dodo.py +0 -0
  195. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/r_requirements.txt +0 -0
  196. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/bibliography.bib +0 -0
  197. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/jpe.bst +0 -0
  198. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/my_article_header.sty +0 -0
  199. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/my_beamer_header.sty +0 -0
  200. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/my_common_header.sty +0 -0
  201. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/report_example.tex +0 -0
  202. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/report_simple_example.tex +0 -0
  203. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/slides_example.tex +0 -0
  204. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/reports/slides_simple_example.tex +0 -0
  205. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/01_example_notebook_interactive.ipynb.py +0 -0
  206. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/02_example_with_dependencies.ipynb.py +0 -0
  207. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/03_public_repo_summary_charts.ipynb.py +0 -0
  208. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/04_example_regressions.Rmd +0 -0
  209. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/chart_relative_repo_rates.py +0 -0
  210. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_plot.py +0 -0
  211. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_r_plot.r +0 -0
  212. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_stata_plot.do +0 -0
  213. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/example_table.py +0 -0
  214. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/install_packages.r +0 -0
  215. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/load_fed_yield_curve.py +0 -0
  216. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/misc_tools.py +0 -0
  217. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pandas_to_latex_demo.py +0 -0
  218. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_CRSP_Compustat.py +0 -0
  219. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_CRSP_stock.py +0 -0
  220. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_bloomberg.py +0 -0
  221. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_fred.py +0 -0
  222. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_ofr_api_data.py +0 -0
  223. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/pull_public_repo_data.py +0 -0
  224. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/settings.py +0 -0
  225. {chartbook-0.2.0 → chartbook-0.3.0}/template/{{cookiecutter.project_slug}}/src/test_misc_tools.py +0 -0
  226. {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.2.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.4
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.20
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.28; extra == 's3'
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
- # 1.20 has the IO-plugin seam that DuckDB's lazy frames are built on
35
- "polars>=1.20",
36
- "pyarrow",
37
- # The local SQL engine: [sources] with backend = "duckdb", and data.sql()
38
- "duckdb>=1.4", # .pl(lazy=True) arrived in 1.4
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.28",
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.28",
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.28",
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, and in SQL it is ``read_parquet('…')``.
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 parquet files too, so a
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
- In SQL a file is ``read_parquet('…')``, which DuckDB reads as a table,
456
- partitions and all.
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.utils import is_glob_pattern
465
-
466
- from chartbook.resolve import nothing_to_read
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
- import polars as pl
485
-
486
- from chartbook.resolve import nothing_to_read
479
+ from chartbook import formats
487
480
 
488
- said = nothing_to_read(location)
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
- return self.connect().sql(query).pl(lazy=True)
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)}").fetchone()
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
- ).fetchone()
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