esgpull 0.7.3__tar.gz → 0.9.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 (168) hide show
  1. esgpull-0.9.0/CITATION.cff +33 -0
  2. {esgpull-0.7.3 → esgpull-0.9.0}/PKG-INFO +11 -2
  3. {esgpull-0.7.3 → esgpull-0.9.0}/README.md +8 -0
  4. {esgpull-0.7.3 → esgpull-0.9.0}/alembic.ini +0 -6
  5. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/configuration.md +13 -0
  6. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/glossary.md +3 -0
  7. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/index.md +1 -0
  8. esgpull-0.9.0/docs/docs/plugins.md +283 -0
  9. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/quickstart.md +12 -0
  10. {esgpull-0.7.3 → esgpull-0.9.0}/docs/mkdocs.yml +1 -0
  11. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/__init__.py +2 -2
  12. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/add.py +7 -1
  13. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/config.py +5 -21
  14. esgpull-0.9.0/esgpull/cli/plugins.py +398 -0
  15. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/show.py +29 -0
  16. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/status.py +6 -4
  17. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/update.py +72 -18
  18. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/utils.py +16 -1
  19. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/config.py +83 -25
  20. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/constants.py +3 -0
  21. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/context.py +15 -15
  22. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/database.py +8 -2
  23. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/download.py +3 -0
  24. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/esgpull.py +49 -5
  25. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/graph.py +1 -1
  26. esgpull-0.9.0/esgpull/migrations/versions/0.8.0_update_tables.py +28 -0
  27. esgpull-0.9.0/esgpull/migrations/versions/0.9.0_update_tables.py +28 -0
  28. esgpull-0.9.0/esgpull/migrations/versions/14c72daea083_query_add_column_updated_at.py +36 -0
  29. esgpull-0.9.0/esgpull/migrations/versions/c7c8541fa741_query_add_column_added_at.py +37 -0
  30. esgpull-0.9.0/esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py +32 -0
  31. esgpull-0.9.0/esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py +39 -0
  32. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/__init__.py +2 -1
  33. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/base.py +31 -14
  34. esgpull-0.9.0/esgpull/models/dataset.py +77 -0
  35. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/options.py +1 -1
  36. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/query.py +98 -15
  37. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/sql.py +40 -9
  38. esgpull-0.9.0/esgpull/plugin.py +574 -0
  39. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/processor.py +3 -3
  40. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/tui.py +23 -1
  41. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/utils.py +19 -3
  42. {esgpull-0.7.3 → esgpull-0.9.0}/pyproject.toml +12 -2
  43. {esgpull-0.7.3 → esgpull-0.9.0}/requirements-dev.lock +73 -68
  44. {esgpull-0.7.3 → esgpull-0.9.0}/requirements.lock +34 -28
  45. esgpull-0.9.0/tests/assets/error_plugin.py +19 -0
  46. esgpull-0.9.0/tests/assets/incompatible_plugin.py +27 -0
  47. esgpull-0.9.0/tests/assets/priority_test_plugin.py +29 -0
  48. esgpull-0.9.0/tests/assets/sample_plugin.py +42 -0
  49. esgpull-0.9.0/tests/cli/test_dataset.py +157 -0
  50. esgpull-0.9.0/tests/cli/test_plugin_cli.py +246 -0
  51. esgpull-0.9.0/tests/cli/test_show_dates.py +61 -0
  52. esgpull-0.9.0/tests/cli/test_update.py +104 -0
  53. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_context.py +1 -0
  54. esgpull-0.9.0/tests/test_dataset.py +71 -0
  55. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_db.py +2 -1
  56. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_graph.py +70 -30
  57. esgpull-0.9.0/tests/test_plugin.py +434 -0
  58. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_query.py +36 -7
  59. esgpull-0.9.0/tests/test_utils.py +47 -0
  60. esgpull-0.9.0/tests/utils.py +11 -0
  61. esgpull-0.9.0/uv.lock +1822 -0
  62. esgpull-0.7.3/esgpull/cli/datasets.py +0 -78
  63. esgpull-0.7.3/esgpull/models/dataset.py +0 -34
  64. esgpull-0.7.3/tests/cli/test_update.py +0 -36
  65. esgpull-0.7.3/tests/test_utils.py +0 -23
  66. {esgpull-0.7.3 → esgpull-0.9.0}/.github/workflows/ci.yml +0 -0
  67. {esgpull-0.7.3 → esgpull-0.9.0}/.github/workflows/doc.yml +0 -0
  68. {esgpull-0.7.3 → esgpull-0.9.0}/.github/workflows/pypi-publish.yml +0 -0
  69. {esgpull-0.7.3 → esgpull-0.9.0}/.gitignore +0 -0
  70. {esgpull-0.7.3 → esgpull-0.9.0}/.pre-commit-config.yaml +0 -0
  71. {esgpull-0.7.3 → esgpull-0.9.0}/LICENSE +0 -0
  72. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/download.md +0 -0
  73. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/download_1.svg +0 -0
  74. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/download_2.svg +0 -0
  75. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/download_3.svg +0 -0
  76. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/download_4.svg +0 -0
  77. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/download_5.svg +0 -0
  78. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/download_6.svg +0 -0
  79. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/intro_1.svg +0 -0
  80. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/intro_2.svg +0 -0
  81. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/intro_3.svg +0 -0
  82. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/intro_4.svg +0 -0
  83. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/intro_5.svg +0 -0
  84. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/intro_6.svg +0 -0
  85. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/quickstart_1.svg +0 -0
  86. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_1.svg +0 -0
  87. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_2.svg +0 -0
  88. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_3.svg +0 -0
  89. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_4.svg +0 -0
  90. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_5.svg +0 -0
  91. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_6.svg +0 -0
  92. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_7.svg +0 -0
  93. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/images/search_ignore.svg +0 -0
  94. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/installation.md +0 -0
  95. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/queries.md +0 -0
  96. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/search.md +0 -0
  97. {esgpull-0.7.3 → esgpull-0.9.0}/docs/docs/stylesheets/extra.css +0 -0
  98. {esgpull-0.7.3 → esgpull-0.9.0}/docs/includes/abbreviations.md +0 -0
  99. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/__init__.py +0 -0
  100. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/auth.py +0 -0
  101. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/autoremove.py +0 -0
  102. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/convert.py +0 -0
  103. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/decorators.py +0 -0
  104. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/download.py +0 -0
  105. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/facet.py +0 -0
  106. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/get.py +0 -0
  107. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/install.py +0 -0
  108. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/login.py +0 -0
  109. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/remove.py +0 -0
  110. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/retry.py +0 -0
  111. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/search.py +0 -0
  112. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/self.py +0 -0
  113. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/cli/track.py +0 -0
  114. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/exceptions.py +0 -0
  115. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/fs.py +0 -0
  116. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/install_config.py +0 -0
  117. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/README +0 -0
  118. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/env.py +0 -0
  119. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/script.py.mako +0 -0
  120. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.0_update_tables.py +0 -0
  121. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.1_update_tables.py +0 -0
  122. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.2_update_tables.py +0 -0
  123. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.3_update_tables.py +0 -0
  124. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.4_update_tables.py +0 -0
  125. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.5_update_tables.py +0 -0
  126. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.6_update_tables.py +0 -0
  127. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.7_update_tables.py +0 -0
  128. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.3.8_update_tables.py +0 -0
  129. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.4.0_update_tables.py +0 -0
  130. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.5.0_update_tables.py +0 -0
  131. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.5.1_update_tables.py +0 -0
  132. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.5.2_update_tables.py +0 -0
  133. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.5.3_update_tables.py +0 -0
  134. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.5.4_update_tables.py +0 -0
  135. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.5.5_update_tables.py +0 -0
  136. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.6.0_update_tables.py +0 -0
  137. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.6.1_update_tables.py +0 -0
  138. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.6.2_update_tables.py +0 -0
  139. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.6.3_update_tables.py +0 -0
  140. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.6.4_update_tables.py +0 -0
  141. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.6.5_update_tables.py +0 -0
  142. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.7.0_update_tables.py +0 -0
  143. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.7.1_update_tables.py +0 -0
  144. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.7.2_update_tables.py +0 -0
  145. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/migrations/versions/0.7.3_update_tables.py +0 -0
  146. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/facet.py +0 -0
  147. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/file.py +0 -0
  148. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/selection.py +0 -0
  149. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/synda_file.py +0 -0
  150. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/tag.py +0 -0
  151. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/models/utils.py +0 -0
  152. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/py.typed +0 -0
  153. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/result.py +0 -0
  154. {esgpull-0.7.3 → esgpull-0.9.0}/esgpull/version.py +0 -0
  155. {esgpull-0.7.3 → esgpull-0.9.0}/pdm.lock +0 -0
  156. {esgpull-0.7.3 → esgpull-0.9.0}/recipe/meta.yaml +0 -0
  157. {esgpull-0.7.3 → esgpull-0.9.0}/recipe/recipe.yaml +0 -0
  158. {esgpull-0.7.3 → esgpull-0.9.0}/tests/__init__.py +0 -0
  159. {esgpull-0.7.3 → esgpull-0.9.0}/tests/cli/__init__.py +0 -0
  160. {esgpull-0.7.3 → esgpull-0.9.0}/tests/cli/test_parse.py +0 -0
  161. {esgpull-0.7.3 → esgpull-0.9.0}/tests/conftest.py +0 -0
  162. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_auth.py +0 -0
  163. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_config.py +0 -0
  164. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_esgpull.py +0 -0
  165. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_fs.py +0 -0
  166. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_processor.py +0 -0
  167. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_selection.py +0 -0
  168. {esgpull-0.7.3 → esgpull-0.9.0}/tests/test_synda.py +0 -0
@@ -0,0 +1,33 @@
1
+ # This CITATION.cff file was generated with cffinit.
2
+
3
+ cff-version: 1.2.0
4
+ title: esgpull
5
+ message: ESGF command line download tool esgpull
6
+ type: software
7
+ authors:
8
+ - given-names: Sven
9
+ family-names: Rodriguez
10
+ email: sven.rodriguez@ipsl.fr
11
+ affiliation: Sorbonne University
12
+ - given-names: Atef
13
+ family-names: Ben Nasser
14
+ email: abennasser@ipsl.fr
15
+ affiliation: Centre National de Recherche Scientifique
16
+ orcid: 'https://orcid.org/0000-0001-6948-8735'
17
+ - given-names: Guillaume
18
+ family-names: Levavasseur
19
+ email: glipsl@ipsl.fr
20
+ affiliation: Sorbonne University
21
+ orcid: 'https://orcid.org/0000-0002-0801-0890'
22
+ repository-code: 'https://github.com/ESGF/esgf-download.git'
23
+ url: 'https://esgf.github.io/esgf-download/'
24
+ abstract: >-
25
+ esgpull is a modern ESGF data management tool, bundled
26
+ with a custom asynchronous interface with the ESGF Search
27
+ API. It handles scanning, downloading and updating
28
+ datasets, files and queries from ESGF.
29
+ license: BSD-3-Clause
30
+ commit: 762494e
31
+ version: 0.7.4
32
+ doi: 10.5281/zenodo.14228984
33
+ date-released: '2024-11-27'
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: esgpull
3
- Version: 0.7.3
3
+ Version: 0.9.0
4
4
  Summary: ESGF data discovery, download, replication tool
5
5
  Project-URL: Repository, https://github.com/ESGF/esgf-download
6
6
  Project-URL: Documentation, https://esgf.github.io/esgf-download/
@@ -23,6 +23,7 @@ Requires-Dist: click>=8.1.3
23
23
  Requires-Dist: httpx>=0.23.0
24
24
  Requires-Dist: myproxyclient>=2.1.0
25
25
  Requires-Dist: nest-asyncio>=1.5.6
26
+ Requires-Dist: packaging>=25.0
26
27
  Requires-Dist: platformdirs>=2.6.2
27
28
  Requires-Dist: pyopenssl>=22.1.0
28
29
  Requires-Dist: pyparsing>=3.0.9
@@ -99,3 +100,11 @@ Commands:
99
100
  untrack Untrack queries
100
101
  update Fetch files, link files <-> queries, send files to download...
101
102
  ```
103
+
104
+ ## Useful links
105
+ * [ESGF Webinar: An Introduction to esgpull, A Replacement for Synda](https://www.youtube.com/watch?v=xv2RVMd1iCA)
106
+
107
+
108
+ ## Contributions
109
+
110
+ You can use the common github workflow (through pull requests and issues) to contribute.
@@ -64,3 +64,11 @@ Commands:
64
64
  untrack Untrack queries
65
65
  update Fetch files, link files <-> queries, send files to download...
66
66
  ```
67
+
68
+ ## Useful links
69
+ * [ESGF Webinar: An Introduction to esgpull, A Replacement for Synda](https://www.youtube.com/watch?v=xv2RVMd1iCA)
70
+
71
+
72
+ ## Contributions
73
+
74
+ You can use the common github workflow (through pull requests and issues) to contribute.
@@ -3,12 +3,6 @@ script_location = esgpull/migrations
3
3
  prepend_sys_path = .
4
4
  version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
5
5
 
6
- [post_write_hooks]
7
- hooks = black
8
- black.type = console_scripts
9
- black.entrypoint = black
10
- black.options = -l 79 REVISION_SCRIPT_FILENAME
11
-
12
6
  # Logging configuration
13
7
  [loggers]
14
8
  keys = root,sqlalchemy,alembic
@@ -41,6 +41,9 @@ distrib = "false"
41
41
  latest = "true"
42
42
  replica = "none"
43
43
  retracted = "false"
44
+
45
+ [plugins]
46
+ enabled = "false"
44
47
  ```
45
48
 
46
49
  To modify a config item from the command line, the dot-separated path to that item must
@@ -124,3 +127,13 @@ Certificates are missing.
124
127
 
125
128
  The credentials will then be saved under the ``~/.esgpull/auth`` directory, within
126
129
  ``credentials.toml``, which can then be used for future sessions.
130
+
131
+ ## Plugins
132
+
133
+ Plugin functionality is disabled by default and can be enabled with:
134
+
135
+ ```shell
136
+ $ esgpull config plugins.enabled true
137
+ ```
138
+
139
+ Plugin-specific configurations are managed separately through the `esgpull plugins config` command and stored in a dedicated `plugins.toml` file. See the [Plugins page](plugins) for more information.
@@ -3,3 +3,6 @@ __Dataset__
3
3
 
4
4
  __Facet__
5
5
  : basic element of a dataset's _metadata_. Pair of strings in the form `name:value`, equivalent to a python dictionary's item.
6
+
7
+ __Plugin__
8
+ : custom code that extends `esgpull` functionality by responding to specific events.
@@ -10,6 +10,7 @@ It handles scanning, downloading and updating **datasets**, **files** and *queri
10
10
  - Simple syntax for fast data exploration
11
11
  - Asynchronous download
12
12
  - Highly configurable
13
+ - Plugin system for extensibility
13
14
 
14
15
  !!! tip "Search datasets"
15
16
 
@@ -0,0 +1,283 @@
1
+ # Plugins
2
+
3
+ `esgpull` includes a plugin system that allows you to extend functionality by running custom code when specific events occur during data operations.
4
+
5
+ ## Available events
6
+
7
+ - **file_complete**: A file download completes successfully
8
+ - **file_error**: A file download fails
9
+ - **dataset_complete**: All files from a dataset are downloaded
10
+
11
+ Each event handler receives only the parameters relevant to that specific event.
12
+
13
+ ## Enable plugins
14
+
15
+ Plugins are disabled by default. Enable them using the main config command:
16
+
17
+ ```shell
18
+ $ esgpull config plugins.enabled true
19
+ ```
20
+
21
+ ```shell
22
+ [plugins]
23
+ enabled = true
24
+
25
+ Previous value: False
26
+ ```
27
+
28
+
29
+ ## Plugin management
30
+
31
+ ### List available plugins
32
+
33
+ ```shell
34
+ $ esgpull plugins ls
35
+ ```
36
+
37
+ ```shell
38
+ plugin │ event │ function
39
+ ══════════════════════╪══════════════════╪═════════════════════════
40
+ 🟢 notification │ file_complete │ notify_download
41
+ │ file_error │ notify_error
42
+ 🔴 checksum_verify │ file_complete │ verify_checksum
43
+ 🔴 archive_backup │ file_complete │ backup_file
44
+ │ dataset_complete │ backup_dataset
45
+ ```
46
+
47
+ This detailed information can also be shown with JSON format with `--json`.
48
+
49
+ ### Create a new plugin
50
+
51
+ Create a plugin template with all available events:
52
+
53
+ ```shell
54
+ $ esgpull plugins create -n notification
55
+ ```
56
+
57
+ ```shell
58
+ Plugin template created at: /path/to/esgpull/plugins/notification.py
59
+ Edit the file to implement your custom plugin logic.
60
+ ```
61
+
62
+ Create a plugin for specific events only:
63
+
64
+ ```shell
65
+ $ esgpull plugins create -n notification file_complete file_error
66
+ ```
67
+
68
+ ```shell
69
+ Plugin template created at: /path/to/esgpull/plugins/notification.py
70
+ Edit the file to implement your custom plugin logic.
71
+ ```
72
+
73
+ ### Enable and disable plugins
74
+
75
+ ```shell
76
+ $ esgpull plugins enable notification
77
+ ```
78
+
79
+ ```shell
80
+ Plugin 'notification' enabled.
81
+ ```
82
+
83
+ ```shell
84
+ $ esgpull plugins disable notification
85
+ ```
86
+
87
+ ```shell
88
+ Plugin 'notification' disabled.
89
+ ```
90
+
91
+
92
+ ### Test plugins
93
+
94
+ Test plugins by triggering one event with sample data:
95
+
96
+ ```shell
97
+ $ esgpull plugins test file_complete
98
+ ```
99
+
100
+ ```shell
101
+ [2025-06-19 17:33:09] INFO esgpull.plugins.notification
102
+ ✅ Downloaded: tas_Amon_CESM2_historical_r1i1p1f1_gn_185001-201412.nc
103
+ Size: 524288000 bytes
104
+
105
+ [2025-06-19 17:33:09] INFO esgpull.plugins.checksum_verify
106
+ ✓ Checksum verified for tas_Amon_CESM2_historical_r1i1p1f1_gn_185001-201412.nc
107
+ ```
108
+
109
+ This is the primary debugging tool for plugin development. Use it to verify handlers work correctly before running actual downloads or updates.
110
+
111
+ ## Plugin example
112
+
113
+ Here's a simple notification plugin that sends a message when files are downloaded:
114
+
115
+ ```python title="plugins/notification.py"
116
+ import pathlib
117
+ import logging
118
+ from esgpull.plugin import Event, on
119
+ import esgpull.models
120
+
121
+ @on(Event.file_complete, priority="normal")
122
+ def notify_download(file: esgpull.models.File, destination: pathlib.Path, logger: logging.Logger):
123
+ """Send notification when a file is downloaded."""
124
+ print(f"✅ Downloaded: {file.filename}")
125
+ print(f" Size: {file.size} bytes")
126
+ logger.info(f"Notified download: {file.filename}")
127
+
128
+ @on(Event.file_error, priority="normal")
129
+ def notify_error(file: esgpull.models.File, exception: Exception, logger: logging.Logger):
130
+ """Send notification when a download fails."""
131
+ print(f"❌ Failed: {file.filename}")
132
+ print(f" Error: {exception}")
133
+ logger.error(f"Notified error: {file.filename}")
134
+ ```
135
+
136
+ ## Plugin configuration
137
+
138
+ Plugins are configured via an optional `Config` class in the plugin code. The `Config` class attributes define parameters and must include default values.
139
+
140
+ Plugin configuration is stored separately from esgpull's main config file. This file contains a manifest of enabled/disabled plugins and their custom configurations (which override the `Config` class defaults). Use the `esgpull plugins config` subcommand to manage these settings.
141
+
142
+ Let's extend our notification plugin with configuration:
143
+
144
+ ```python title="plugins/notification.py"
145
+ import pathlib
146
+ import logging
147
+ from esgpull.plugin import Event, on
148
+ import esgpull.models
149
+
150
+ class Config:
151
+ enabled = True
152
+ email_address = "user@example.com"
153
+ include_size = True
154
+ error_alerts = True
155
+
156
+ @on(Event.file_complete, priority="normal")
157
+ def notify_download(file: esgpull.models.File, destination: pathlib.Path, logger: logging.Logger):
158
+ """Send notification when a file is downloaded."""
159
+ if Config.enabled:
160
+ print(f"✅ Downloaded: {file.filename}")
161
+ if Config.include_size:
162
+ print(f" Size: {file.size} bytes")
163
+ logger.info(f"Notified download to {Config.email_address}: {file.filename}")
164
+
165
+ @on(Event.file_error, priority="normal")
166
+ def notify_error(file: esgpull.models.File, exception: Exception, logger: logging.Logger):
167
+ """Send notification when a download fails."""
168
+ if Config.enabled and Config.error_alerts:
169
+ print(f"❌ Failed: {file.filename}")
170
+ print(f" Error: {exception}")
171
+ logger.error(f"Notified error to {Config.email_address}: {file.filename}")
172
+ ```
173
+
174
+ View all plugin configurations:
175
+ ```shell
176
+ $ esgpull plugins config
177
+ ```
178
+
179
+ ```shell
180
+ ─ /path/to/esgpull/plugins.toml ─
181
+ [notification]
182
+ enabled = true
183
+ email_address = "admin@myproject.org"
184
+ include_size = true
185
+ error_alerts = true
186
+
187
+ [checksum_verify]
188
+ enabled = false
189
+ algorithm = "sha256"
190
+
191
+ [archive_backup]
192
+ enabled = false
193
+ archive_path = "/backup/esgf"
194
+ ```
195
+
196
+ View specific plugin configuration:
197
+ ```shell
198
+ $ esgpull plugins config notification
199
+ ```
200
+
201
+ ```shell
202
+ [notification]
203
+ enabled = true
204
+ email_address = "admin@myproject.org"
205
+ include_size = true
206
+ error_alerts = true
207
+ ```
208
+
209
+ View a specific configuration value:
210
+ ```shell
211
+ $ esgpull plugins config notification.email_address
212
+ ```
213
+
214
+ ```shell
215
+ [notification]
216
+ email_address = "admin@myproject.org"
217
+ ```
218
+
219
+ Set a configuration value:
220
+ ```shell
221
+ $ esgpull plugins config notification.email_address alerts@newdomain.com
222
+ ```
223
+
224
+ ```shell
225
+ [notification]
226
+ email_address = "alerts@newdomain.com"
227
+
228
+ Previous value: admin@myproject.org
229
+ ```
230
+
231
+ Reset to default value:
232
+ ```shell
233
+ $ esgpull plugins config notification.email_address --default
234
+ ```
235
+
236
+ ```shell
237
+ 👍 Config reset to default for notification.email_address
238
+ ```
239
+
240
+ ### Config class details
241
+
242
+ The `Config` class supports these data types:
243
+
244
+ - **Strings** (`str`): Text values like `"INFO"` or `"user@example.com"`
245
+ - **Integers** (`int`): Whole numbers like `3` or `100`
246
+ - **Floats** (`float`): Decimal numbers like `0.5` or `10.75`
247
+ - **Booleans** (`bool`): `True` or `False` values
248
+
249
+ ```python
250
+ class Config:
251
+ """Example showing all supported data types"""
252
+ # String configuration
253
+ log_level = "INFO"
254
+ email_address = "user@example.com"
255
+
256
+ # Integer configuration
257
+ max_retries = 3
258
+ timeout_seconds = 30
259
+
260
+ # Float configuration
261
+ threshold = 0.75
262
+ delay_factor = 1.5
263
+
264
+ # Boolean configuration
265
+ notifications_enabled = True
266
+ debug_mode = False
267
+ ```
268
+
269
+ **Important limitations:**
270
+ - Lists and dictionaries can be used in the `Config` class, but cannot be configured through the CLI `esgpull plugins config` command
271
+ - For list/dictionary attributes, you must edit the `plugins.toml` file directly
272
+ - Only simple scalar values (strings, integers, floats, booleans) can be managed via CLI commands
273
+ - Configuration values are automatically type-checked when modified through CLI
274
+
275
+ ### Configuration management
276
+
277
+ Plugin configurations are stored separately from the main `esgpull` configuration in a `plugins.toml` file. Only plugins with a `Config` class can be configured.
278
+
279
+ The configuration system follows these principles:
280
+
281
+ - **Defaults in code**: Class attributes define default values
282
+ - **Overrides in file**: Only explicitly changed values are saved to `plugins.toml`
283
+ - **Type safety**: Values are automatically converted and validated based on the default type
@@ -87,5 +87,17 @@ $ esgpull status
87
87
 
88
88
  Loop at the [download page](../download) for more information.
89
89
 
90
+ ## Extending with plugins
91
+
92
+ `esgpull` can be extended with custom plugins that respond to download events:
93
+
94
+ ```shell
95
+ $ esgpull config plugins.enabled true
96
+ $ esgpull plugins create -n notification file_complete
97
+ $ esgpull plugins enable notification
98
+ ```
99
+
100
+ See the [Plugins page](../plugins) for more details on creating and managing plugins.
101
+
90
102
 
91
103
  [ESGF Search API]: https://esgf.github.io/esg-search/ESGF_Search_RESTful_API.html
@@ -7,6 +7,7 @@ nav:
7
7
  # - Queries: queries.md
8
8
  - Data discovery: search.md
9
9
  - Download: download.md
10
+ - Plugins: plugins.md
10
11
  - Glossary: glossary.md
11
12
  # - Usage guide: usage.md
12
13
  # - API Reference: reference.md
@@ -7,9 +7,9 @@ from esgpull import __version__
7
7
  from esgpull.cli.add import add
8
8
  from esgpull.cli.config import config
9
9
  from esgpull.cli.convert import convert
10
- from esgpull.cli.datasets import datasets
11
10
  from esgpull.cli.download import download
12
11
  from esgpull.cli.login import login
12
+ from esgpull.cli.plugins import plugins
13
13
  from esgpull.cli.remove import remove
14
14
  from esgpull.cli.retry import retry
15
15
  from esgpull.cli.search import search
@@ -35,13 +35,13 @@ SUBCOMMANDS: list[click.Command] = [
35
35
  # autoremove,
36
36
  config,
37
37
  convert,
38
- datasets,
39
38
  download,
40
39
  # facet,
41
40
  # get,
42
41
  self,
43
42
  # install,
44
43
  login,
44
+ plugins,
45
45
  remove,
46
46
  retry,
47
47
  search,
@@ -89,6 +89,7 @@ def add(
89
89
  esg.ui.print(subgraph)
90
90
  empty = Query()
91
91
  empty.compute_sha()
92
+ next_steps_queries = []
92
93
  for query in queries:
93
94
  query.compute_sha()
94
95
  esg.graph.resolve_require(query)
@@ -99,7 +100,8 @@ def add(
99
100
  esg.ui.print(f"Skipping existing query: {query.rich_name}")
100
101
  else:
101
102
  esg.graph.add(query)
102
- esg.ui.print(f"New query added: {query.rich_name}")
103
+ if query.tracked:
104
+ next_steps_queries.append(query)
103
105
  new_queries = esg.graph.merge()
104
106
  nb = len(new_queries)
105
107
  ies = "ies" if nb > 1 else "y"
@@ -107,4 +109,8 @@ def add(
107
109
  esg.ui.print(f":+1: {nb} new quer{ies} added.")
108
110
  else:
109
111
  esg.ui.print(":stop_sign: No new query was added.")
112
+ if next_steps_queries:
113
+ esg.ui.print("\nNext steps:\n")
114
+ for query in next_steps_queries:
115
+ esg.ui.print(f"\tesgpull update {query.sha[:6]}")
110
116
  esg.ui.raise_maybe_record(Exit(0))
@@ -4,26 +4,11 @@ import click
4
4
  from click.exceptions import Abort, BadOptionUsage, Exit
5
5
 
6
6
  from esgpull.cli.decorators import args, opts
7
- from esgpull.cli.utils import init_esgpull
7
+ from esgpull.cli.utils import extract_subdict, init_esgpull
8
8
  from esgpull.config import ConfigKind
9
9
  from esgpull.tui import Verbosity
10
10
 
11
11
 
12
- def extract_command(doc: dict, key: str | None) -> dict:
13
- if key is None:
14
- return doc
15
- for part in key.split("."):
16
- if not part:
17
- raise KeyError(key)
18
- elif part in doc:
19
- doc = doc[part]
20
- else:
21
- raise KeyError(part)
22
- for part in key.split(".")[::-1]:
23
- doc = {part: doc}
24
- return doc
25
-
26
-
27
12
  @click.command()
28
13
  @args.key
29
14
  @args.value
@@ -73,7 +58,7 @@ def config(
73
58
  )
74
59
  kind = esg.config.kind
75
60
  old_value = esg.config.update_item(key, value, empty_ok=True)
76
- info = extract_command(esg.config.dump(), key)
61
+ info = extract_subdict(esg.config.dump(), key)
77
62
  esg.config.write()
78
63
  esg.ui.print(info, toml=True)
79
64
  if kind == ConfigKind.NoFile:
@@ -86,12 +71,12 @@ def config(
86
71
  elif key is not None:
87
72
  if default:
88
73
  old_value = esg.config.set_default(key)
89
- info = extract_command(esg.config.dump(), key)
74
+ info = extract_subdict(esg.config.dump(), key)
90
75
  esg.config.write()
91
76
  esg.ui.print(info, toml=True)
92
77
  esg.ui.print(f"Previous value: {old_value}")
93
78
  else:
94
- info = extract_command(esg.config.dump(), key)
79
+ info = extract_subdict(esg.config.dump(), key)
95
80
  esg.ui.print(info, toml=True)
96
81
  elif generate:
97
82
  overwrite = False
@@ -102,8 +87,7 @@ def config(
102
87
  )
103
88
  esg.ui.raise_maybe_record(Exit(0))
104
89
  elif esg.config.kind == ConfigKind.Partial and esg.ui.ask(
105
- "A config file already exists,"
106
- " fill it with missing defaults?",
90
+ "A config file already exists, fill it with missing defaults?",
107
91
  default=False,
108
92
  ):
109
93
  overwrite = True