esgpull 0.8.0__tar.gz → 0.9.1__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 (169) hide show
  1. {esgpull-0.8.0 → esgpull-0.9.1}/PKG-INFO +19 -3
  2. {esgpull-0.8.0 → esgpull-0.9.1}/README.md +17 -2
  3. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/configuration.md +13 -0
  4. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/glossary.md +3 -0
  5. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/index.md +5 -0
  6. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/installation.md +17 -36
  7. esgpull-0.9.1/docs/docs/plugins.md +283 -0
  8. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/quickstart.md +12 -0
  9. {esgpull-0.8.0 → esgpull-0.9.1}/docs/mkdocs.yml +1 -0
  10. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/__init__.py +2 -2
  11. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/add.py +7 -1
  12. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/config.py +5 -21
  13. esgpull-0.9.1/esgpull/cli/plugins.py +398 -0
  14. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/remove.py +9 -3
  15. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/self.py +1 -1
  16. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/update.py +78 -35
  17. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/utils.py +16 -1
  18. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/config.py +83 -26
  19. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/constants.py +3 -0
  20. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/context.py +9 -9
  21. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/database.py +21 -7
  22. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/download.py +3 -0
  23. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/esgpull.py +49 -5
  24. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/fs.py +9 -20
  25. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/graph.py +1 -1
  26. esgpull-0.9.1/esgpull/migrations/versions/0.9.0_update_tables.py +28 -0
  27. esgpull-0.9.1/esgpull/migrations/versions/0.9.1_update_tables.py +28 -0
  28. esgpull-0.9.1/esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py +32 -0
  29. esgpull-0.9.1/esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py +39 -0
  30. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/__init__.py +2 -1
  31. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/base.py +31 -14
  32. esgpull-0.9.1/esgpull/models/dataset.py +77 -0
  33. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/query.py +58 -14
  34. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/sql.py +48 -9
  35. esgpull-0.9.1/esgpull/plugin.py +574 -0
  36. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/processor.py +3 -3
  37. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/tui.py +23 -1
  38. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/utils.py +5 -1
  39. esgpull-0.9.1/pyproject.toml +115 -0
  40. {esgpull-0.8.0 → esgpull-0.9.1}/requirements-dev.lock +41 -38
  41. {esgpull-0.8.0 → esgpull-0.9.1}/requirements.lock +19 -16
  42. esgpull-0.9.1/tests/assets/error_plugin.py +19 -0
  43. esgpull-0.9.1/tests/assets/incompatible_plugin.py +27 -0
  44. esgpull-0.9.1/tests/assets/priority_test_plugin.py +29 -0
  45. esgpull-0.9.1/tests/assets/sample_plugin.py +42 -0
  46. esgpull-0.9.1/tests/cli/test_dataset.py +157 -0
  47. esgpull-0.9.1/tests/cli/test_plugin_cli.py +246 -0
  48. esgpull-0.9.1/tests/cli/test_remove.py +76 -0
  49. esgpull-0.9.1/tests/test_dataset.py +71 -0
  50. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_fs.py +43 -41
  51. esgpull-0.9.1/tests/test_plugin.py +434 -0
  52. {esgpull-0.8.0 → esgpull-0.9.1}/tests/utils.py +1 -1
  53. esgpull-0.9.1/uv.lock +1508 -0
  54. esgpull-0.8.0/esgpull/cli/datasets.py +0 -78
  55. esgpull-0.8.0/esgpull/models/dataset.py +0 -34
  56. esgpull-0.8.0/pyproject.toml +0 -111
  57. esgpull-0.8.0/recipe/meta.yaml +0 -60
  58. esgpull-0.8.0/recipe/recipe.yaml +0 -58
  59. {esgpull-0.8.0 → esgpull-0.9.1}/.github/workflows/ci.yml +0 -0
  60. {esgpull-0.8.0 → esgpull-0.9.1}/.github/workflows/doc.yml +0 -0
  61. {esgpull-0.8.0 → esgpull-0.9.1}/.github/workflows/pypi-publish.yml +0 -0
  62. {esgpull-0.8.0 → esgpull-0.9.1}/.gitignore +0 -0
  63. {esgpull-0.8.0 → esgpull-0.9.1}/.pre-commit-config.yaml +0 -0
  64. {esgpull-0.8.0 → esgpull-0.9.1}/CITATION.cff +0 -0
  65. {esgpull-0.8.0 → esgpull-0.9.1}/LICENSE +0 -0
  66. {esgpull-0.8.0 → esgpull-0.9.1}/alembic.ini +0 -0
  67. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/download.md +0 -0
  68. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_1.svg +0 -0
  69. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_2.svg +0 -0
  70. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_3.svg +0 -0
  71. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_4.svg +0 -0
  72. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_5.svg +0 -0
  73. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_6.svg +0 -0
  74. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_1.svg +0 -0
  75. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_2.svg +0 -0
  76. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_3.svg +0 -0
  77. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_4.svg +0 -0
  78. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_5.svg +0 -0
  79. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_6.svg +0 -0
  80. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/quickstart_1.svg +0 -0
  81. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_1.svg +0 -0
  82. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_2.svg +0 -0
  83. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_3.svg +0 -0
  84. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_4.svg +0 -0
  85. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_5.svg +0 -0
  86. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_6.svg +0 -0
  87. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_7.svg +0 -0
  88. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_ignore.svg +0 -0
  89. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/queries.md +0 -0
  90. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/search.md +0 -0
  91. {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/stylesheets/extra.css +0 -0
  92. {esgpull-0.8.0 → esgpull-0.9.1}/docs/includes/abbreviations.md +0 -0
  93. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/__init__.py +0 -0
  94. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/auth.py +0 -0
  95. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/autoremove.py +0 -0
  96. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/convert.py +0 -0
  97. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/decorators.py +0 -0
  98. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/download.py +0 -0
  99. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/facet.py +0 -0
  100. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/get.py +0 -0
  101. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/install.py +0 -0
  102. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/login.py +0 -0
  103. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/retry.py +0 -0
  104. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/search.py +0 -0
  105. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/show.py +0 -0
  106. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/status.py +0 -0
  107. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/track.py +0 -0
  108. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/exceptions.py +0 -0
  109. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/install_config.py +0 -0
  110. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/README +0 -0
  111. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/env.py +0 -0
  112. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/script.py.mako +0 -0
  113. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.0_update_tables.py +0 -0
  114. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.1_update_tables.py +0 -0
  115. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.2_update_tables.py +0 -0
  116. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.3_update_tables.py +0 -0
  117. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.4_update_tables.py +0 -0
  118. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.5_update_tables.py +0 -0
  119. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.6_update_tables.py +0 -0
  120. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.7_update_tables.py +0 -0
  121. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.8_update_tables.py +0 -0
  122. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.4.0_update_tables.py +0 -0
  123. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.0_update_tables.py +0 -0
  124. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.1_update_tables.py +0 -0
  125. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.2_update_tables.py +0 -0
  126. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.3_update_tables.py +0 -0
  127. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.4_update_tables.py +0 -0
  128. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.5_update_tables.py +0 -0
  129. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.0_update_tables.py +0 -0
  130. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.1_update_tables.py +0 -0
  131. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.2_update_tables.py +0 -0
  132. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.3_update_tables.py +0 -0
  133. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.4_update_tables.py +0 -0
  134. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.5_update_tables.py +0 -0
  135. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.0_update_tables.py +0 -0
  136. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.1_update_tables.py +0 -0
  137. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.2_update_tables.py +0 -0
  138. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.3_update_tables.py +0 -0
  139. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.8.0_update_tables.py +0 -0
  140. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/14c72daea083_query_add_column_updated_at.py +0 -0
  141. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/c7c8541fa741_query_add_column_added_at.py +0 -0
  142. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/facet.py +0 -0
  143. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/file.py +0 -0
  144. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/options.py +0 -0
  145. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/selection.py +0 -0
  146. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/synda_file.py +0 -0
  147. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/tag.py +0 -0
  148. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/utils.py +0 -0
  149. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/py.typed +0 -0
  150. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/result.py +0 -0
  151. {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/version.py +0 -0
  152. {esgpull-0.8.0 → esgpull-0.9.1}/pdm.lock +0 -0
  153. {esgpull-0.8.0 → esgpull-0.9.1}/tests/__init__.py +0 -0
  154. {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/__init__.py +0 -0
  155. {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/test_parse.py +0 -0
  156. {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/test_show_dates.py +0 -0
  157. {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/test_update.py +0 -0
  158. {esgpull-0.8.0 → esgpull-0.9.1}/tests/conftest.py +0 -0
  159. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_auth.py +0 -0
  160. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_config.py +0 -0
  161. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_context.py +0 -0
  162. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_db.py +0 -0
  163. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_esgpull.py +0 -0
  164. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_graph.py +0 -0
  165. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_processor.py +0 -0
  166. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_query.py +0 -0
  167. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_selection.py +0 -0
  168. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_synda.py +0 -0
  169. {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esgpull
3
- Version: 0.8.0
3
+ Version: 0.9.1
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
@@ -62,14 +63,29 @@ for dataset in datasets:
62
63
 
63
64
  ## Installation
64
65
 
65
- Install `esgpull` using pip or conda:
66
+ `esgpull` is distributed via PyPI:
66
67
 
67
68
  ```shell
68
69
  pip install esgpull
70
+ esgpull --help
71
+ ```
72
+
73
+ For isolated installation, [`uv`](https://github.com/astral-sh/uv) or
74
+ [`pipx`](https://github.com/pypa/pipx) are recommended:
75
+
76
+ ```shell
77
+ # with uv
78
+ uv tool install esgpull
79
+ esgpull --help
80
+
81
+ # alternatively, uvx enables running without explicit installation (comes with uv)
82
+ uvx esgpull --help
69
83
  ```
70
84
 
71
85
  ```shell
72
- conda install -c conda-forge ipsl::esgpull
86
+ # with pipx
87
+ pipx install esgpull
88
+ esgpull --help
73
89
  ```
74
90
 
75
91
  ## Usage
@@ -27,14 +27,29 @@ for dataset in datasets:
27
27
 
28
28
  ## Installation
29
29
 
30
- Install `esgpull` using pip or conda:
30
+ `esgpull` is distributed via PyPI:
31
31
 
32
32
  ```shell
33
33
  pip install esgpull
34
+ esgpull --help
35
+ ```
36
+
37
+ For isolated installation, [`uv`](https://github.com/astral-sh/uv) or
38
+ [`pipx`](https://github.com/pypa/pipx) are recommended:
39
+
40
+ ```shell
41
+ # with uv
42
+ uv tool install esgpull
43
+ esgpull --help
44
+
45
+ # alternatively, uvx enables running without explicit installation (comes with uv)
46
+ uvx esgpull --help
34
47
  ```
35
48
 
36
49
  ```shell
37
- conda install -c conda-forge ipsl::esgpull
50
+ # with pipx
51
+ pipx install esgpull
52
+ esgpull --help
38
53
  ```
39
54
 
40
55
  ## Usage
@@ -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
 
@@ -75,6 +76,10 @@ Have a look at the [Installation page](installation) for more ways to install.
75
76
 
76
77
  Jump directly to the [Quickstart guide](quickstart) to get to know how to use `esgpull`.
77
78
 
79
+ ## How to Cite
80
+
81
+ If you use esgpull in your research, please cite it using the information provided in our [CITATION.cff](https://github.com/ESGF/esgf-download/blob/main/CITATION.cff) file.
82
+
78
83
 
79
84
  <!-- [ESGF portal]: https://esgf-node.ipsl.upmc.fr/search/cmip6-ipsl -->
80
85
  [ESGF Search API]: https://esgf.github.io/esg-search/ESGF_Search_RESTful_API.html
@@ -7,50 +7,31 @@ This document covers a few ways to install `esgpull`, a necessary first step int
7
7
  !!! note "Supporting lower python versions could be done with future releases, do not hesitate to ask for it."
8
8
 
9
9
 
10
- ## Installing with conda / mamba
10
+ ## Installation
11
11
 
12
- To install `esgpull` in a new `conda` environment, run:
12
+ `esgpull` is distributed via PyPI:
13
13
 
14
- ```sh
15
- conda create --name my_env_name esgpull --channel ipsl --channel conda-forge
16
- ```
17
-
18
- For `mamba` users:
19
-
20
- ```sh
21
- mamba create --name my_env_name esgpull --channel ipsl --channel conda-forge
22
- ```
23
-
24
- You can start using `esgpull` after activating the environment:
25
-
26
- ```sh
27
- conda activate my_env_name
28
- esgpull --version
14
+ ```shell
15
+ pip install esgpull
16
+ esgpull --help
29
17
  ```
30
18
 
19
+ For isolated installation, [`uv`](https://github.com/astral-sh/uv) or
20
+ [`pipx`](https://github.com/pypa/pipx) are recommended:
31
21
 
32
- ## Install with pip
33
-
34
- Make sure your python version meets the requirements (>=3.10), then you can run:
22
+ ```shell
23
+ # with uv
24
+ uv tool install esgpull
25
+ esgpull --help
35
26
 
36
- ```shell title="Install esgpull from pip"
37
- pip install git+https://github.com/ESGF/esgf-download
27
+ # alternatively, uvx enables running without explicit installation (comes with uv)
28
+ uvx esgpull --help
38
29
  ```
39
30
 
40
-
41
- ## Install from source
42
-
43
- Esgpull is developed and maintained on GitHub, you can clone the public repository with:
44
-
45
31
  ```shell
46
- git clone https://github.com/ESGF/esgf-download
47
- ```
48
-
49
- And then install with `pip`:
50
-
51
- ```
52
- cd esg-pull
53
- python -m pip install .
32
+ # with pipx
33
+ pipx install esgpull
34
+ esgpull --help
54
35
  ```
55
36
 
56
37
 
@@ -93,4 +74,4 @@ Installing `esgpull` is the first step to using it, but not the only one.
93
74
 
94
75
  ## Configuration
95
76
 
96
- `esgpull` is highly configurable, and it is recommended to take a look at the [configuration](configuration.md) page to learn more about it.
77
+ `esgpull` is highly configurable, and it is recommended to take a look at the [configuration](configuration.md) page to learn more about it.
@@ -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