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.
- {esgpull-0.8.0 → esgpull-0.9.1}/PKG-INFO +19 -3
- {esgpull-0.8.0 → esgpull-0.9.1}/README.md +17 -2
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/configuration.md +13 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/glossary.md +3 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/index.md +5 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/installation.md +17 -36
- esgpull-0.9.1/docs/docs/plugins.md +283 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/quickstart.md +12 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/mkdocs.yml +1 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/__init__.py +2 -2
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/add.py +7 -1
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/config.py +5 -21
- esgpull-0.9.1/esgpull/cli/plugins.py +398 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/remove.py +9 -3
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/self.py +1 -1
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/update.py +78 -35
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/utils.py +16 -1
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/config.py +83 -26
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/constants.py +3 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/context.py +9 -9
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/database.py +21 -7
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/download.py +3 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/esgpull.py +49 -5
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/fs.py +9 -20
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/graph.py +1 -1
- esgpull-0.9.1/esgpull/migrations/versions/0.9.0_update_tables.py +28 -0
- esgpull-0.9.1/esgpull/migrations/versions/0.9.1_update_tables.py +28 -0
- esgpull-0.9.1/esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py +32 -0
- esgpull-0.9.1/esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py +39 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/__init__.py +2 -1
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/base.py +31 -14
- esgpull-0.9.1/esgpull/models/dataset.py +77 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/query.py +58 -14
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/sql.py +48 -9
- esgpull-0.9.1/esgpull/plugin.py +574 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/processor.py +3 -3
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/tui.py +23 -1
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/utils.py +5 -1
- esgpull-0.9.1/pyproject.toml +115 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/requirements-dev.lock +41 -38
- {esgpull-0.8.0 → esgpull-0.9.1}/requirements.lock +19 -16
- esgpull-0.9.1/tests/assets/error_plugin.py +19 -0
- esgpull-0.9.1/tests/assets/incompatible_plugin.py +27 -0
- esgpull-0.9.1/tests/assets/priority_test_plugin.py +29 -0
- esgpull-0.9.1/tests/assets/sample_plugin.py +42 -0
- esgpull-0.9.1/tests/cli/test_dataset.py +157 -0
- esgpull-0.9.1/tests/cli/test_plugin_cli.py +246 -0
- esgpull-0.9.1/tests/cli/test_remove.py +76 -0
- esgpull-0.9.1/tests/test_dataset.py +71 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_fs.py +43 -41
- esgpull-0.9.1/tests/test_plugin.py +434 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/utils.py +1 -1
- esgpull-0.9.1/uv.lock +1508 -0
- esgpull-0.8.0/esgpull/cli/datasets.py +0 -78
- esgpull-0.8.0/esgpull/models/dataset.py +0 -34
- esgpull-0.8.0/pyproject.toml +0 -111
- esgpull-0.8.0/recipe/meta.yaml +0 -60
- esgpull-0.8.0/recipe/recipe.yaml +0 -58
- {esgpull-0.8.0 → esgpull-0.9.1}/.github/workflows/ci.yml +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/.github/workflows/doc.yml +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/.github/workflows/pypi-publish.yml +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/.gitignore +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/.pre-commit-config.yaml +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/CITATION.cff +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/LICENSE +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/alembic.ini +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/download.md +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_1.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_2.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_3.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_4.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_5.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/download_6.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_1.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_2.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_3.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_4.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_5.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/intro_6.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/quickstart_1.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_1.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_2.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_3.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_4.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_5.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_6.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_7.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/images/search_ignore.svg +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/queries.md +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/search.md +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/docs/stylesheets/extra.css +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/docs/includes/abbreviations.md +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/__init__.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/auth.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/autoremove.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/convert.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/decorators.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/download.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/facet.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/get.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/install.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/login.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/retry.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/search.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/show.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/status.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/cli/track.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/exceptions.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/install_config.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/README +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/env.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/script.py.mako +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.0_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.1_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.2_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.3_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.4_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.5_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.6_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.7_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.8_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.4.0_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.0_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.1_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.2_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.3_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.4_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.5_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.0_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.1_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.2_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.3_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.4_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.5_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.0_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.1_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.2_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.3_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.8.0_update_tables.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/14c72daea083_query_add_column_updated_at.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/migrations/versions/c7c8541fa741_query_add_column_added_at.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/facet.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/file.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/options.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/selection.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/synda_file.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/tag.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/models/utils.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/py.typed +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/result.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/esgpull/version.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/pdm.lock +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/__init__.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/__init__.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/test_parse.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/test_show_dates.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/cli/test_update.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/conftest.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_auth.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_config.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_context.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_db.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_esgpull.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_graph.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_processor.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_query.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_selection.py +0 -0
- {esgpull-0.8.0 → esgpull-0.9.1}/tests/test_synda.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
-
##
|
|
10
|
+
## Installation
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
`esgpull` is distributed via PyPI:
|
|
13
13
|
|
|
14
|
-
```
|
|
15
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
```shell
|
|
23
|
+
# with uv
|
|
24
|
+
uv tool install esgpull
|
|
25
|
+
esgpull --help
|
|
35
26
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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,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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|