esgpull 0.9.4__tar.gz → 0.9.5__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.9.4 → esgpull-0.9.5}/.github/workflows/ci.yml +4 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/PKG-INFO +3 -3
- {esgpull-0.9.4 → esgpull-0.9.5}/README.md +1 -1
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/context.py +8 -3
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/esgpull.py +10 -5
- esgpull-0.9.5/esgpull/migrations/versions/0.9.5_update_tables.py +28 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/tui.py +11 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/pyproject.toml +2 -2
- {esgpull-0.9.4 → esgpull-0.9.5}/requirements-dev.lock +20 -20
- {esgpull-0.9.4 → esgpull-0.9.5}/requirements.lock +10 -10
- esgpull-0.9.5/tests/cli/test_download.py +50 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_context.py +86 -7
- {esgpull-0.9.4 → esgpull-0.9.5}/uv.lock +2 -2
- {esgpull-0.9.4 → esgpull-0.9.5}/.github/workflows/doc.yml +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/.github/workflows/pypi-publish.yml +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/.gitignore +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/.pre-commit-config.yaml +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/CITATION.cff +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/LICENSE +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/alembic.ini +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/configuration.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/download.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/glossary.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/download_1.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/download_2.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/download_3.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/download_4.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/download_5.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/download_6.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/intro_1.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/intro_2.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/intro_3.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/intro_4.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/intro_5.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/intro_6.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/quickstart_1.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_1.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_2.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_3.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_4.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_5.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_6.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_7.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/images/search_ignore.svg +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/index.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/installation.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/plugins.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/queries.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/quickstart.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/search.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/docs/stylesheets/extra.css +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/includes/abbreviations.md +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/docs/mkdocs.yml +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/__init__.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/__init__.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/add.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/autoremove.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/config.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/convert.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/decorators.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/download.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/facet.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/get.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/index_nodes.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/install.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/plugins.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/remove.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/retry.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/search.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/self.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/show.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/status.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/track.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/update.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/cli/utils.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/config.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/constants.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/database.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/download.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/exceptions.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/fs.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/graph.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/install_config.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/README +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/env.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/script.py.mako +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.0_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.1_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.2_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.3_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.4_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.5_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.6_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.7_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.3.8_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.4.0_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.5.0_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.5.1_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.5.2_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.5.3_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.5.4_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.5.5_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.6.0_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.6.1_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.6.2_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.6.3_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.6.4_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.6.5_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.7.0_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.7.1_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.7.2_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.7.3_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.8.0_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.9.0_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.9.1_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.9.2_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.9.3_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/0.9.4_update_tables.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/14c72daea083_query_add_column_updated_at.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/c7c8541fa741_query_add_column_added_at.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/__init__.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/base.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/dataset.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/facet.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/file.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/options.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/query.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/selection.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/sql.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/synda_file.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/tag.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/models/utils.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/plugin.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/processor.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/py.typed +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/result.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/utils.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/esgpull/version.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/pdm.lock +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/__init__.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/assets/error_plugin.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/assets/incompatible_plugin.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/assets/priority_test_plugin.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/assets/sample_plugin.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/cli/__init__.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/cli/test_dataset.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/cli/test_parse.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/cli/test_plugin_cli.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/cli/test_remove.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/cli/test_show_dates.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/cli/test_update.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/conftest.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_config.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_dataset.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_db.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_esgpull.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_fs.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_graph.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_plugin.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_processor.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_query.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_selection.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_synda.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/test_utils.py +0 -0
- {esgpull-0.9.4 → esgpull-0.9.5}/tests/utils.py +0 -0
|
@@ -9,6 +9,8 @@ on:
|
|
|
9
9
|
- esgpull/**
|
|
10
10
|
- tests/**
|
|
11
11
|
- .github/workflows/ci.yml
|
|
12
|
+
- pyproject.toml
|
|
13
|
+
- uv.lock
|
|
12
14
|
pull_request:
|
|
13
15
|
branches:
|
|
14
16
|
- main
|
|
@@ -17,6 +19,8 @@ on:
|
|
|
17
19
|
- esgpull/**
|
|
18
20
|
- tests/**
|
|
19
21
|
- .github/workflows/ci.yml
|
|
22
|
+
- pyproject.toml
|
|
23
|
+
- uv.lock
|
|
20
24
|
|
|
21
25
|
jobs:
|
|
22
26
|
tests:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: esgpull
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5
|
|
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/
|
|
@@ -31,13 +31,13 @@ Requires-Dist: pyparsing>=3.0.9
|
|
|
31
31
|
Requires-Dist: pyyaml>=6.0
|
|
32
32
|
Requires-Dist: rich>=12.6.0
|
|
33
33
|
Requires-Dist: setuptools>=65.4.1
|
|
34
|
-
Requires-Dist: sqlalchemy
|
|
34
|
+
Requires-Dist: sqlalchemy<2.1,>=2.0.0b2
|
|
35
35
|
Requires-Dist: tomlkit>=0.11.5
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
|
|
38
38
|
# esgpull - ESGF data management utility
|
|
39
39
|
|
|
40
|
-
[](https://github.com/astral-sh/uv)
|
|
41
41
|
|
|
42
42
|
`esgpull` is a tool that simplifies usage of the [ESGF Search API](https://esgf.github.io/esg-search/ESGF_Search_RESTful_API.html) for data discovery, and manages procedures related to downloading and storing files from ESGF.
|
|
43
43
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# esgpull - ESGF data management utility
|
|
2
2
|
|
|
3
|
-
[](https://github.com/astral-sh/uv)
|
|
4
4
|
|
|
5
5
|
`esgpull` is a tool that simplifies usage of the [ESGF Search API](https://esgf.github.io/esg-search/ESGF_Search_RESTful_API.html) for data discovery, and manages procedures related to downloading and storing files from ESGF.
|
|
6
6
|
|
|
@@ -590,6 +590,7 @@ class Context:
|
|
|
590
590
|
) -> list[File]:
|
|
591
591
|
files: list[File] = []
|
|
592
592
|
shas: set[str] = set()
|
|
593
|
+
file_ids: set[str] = set()
|
|
593
594
|
async for result in self._fetch(*results):
|
|
594
595
|
files_result = result.to(ResultFiles)
|
|
595
596
|
files_result.process()
|
|
@@ -597,9 +598,13 @@ class Context:
|
|
|
597
598
|
for file in files_result.data:
|
|
598
599
|
if not keep_duplicates and file.sha in shas:
|
|
599
600
|
logger.debug(f"Duplicate file {file.file_id}")
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
601
|
+
continue
|
|
602
|
+
if not keep_duplicates and file.file_id in file_ids:
|
|
603
|
+
logger.debug(f"Duplicate file_id {file.file_id}")
|
|
604
|
+
continue
|
|
605
|
+
files.append(file)
|
|
606
|
+
shas.add(file.sha)
|
|
607
|
+
file_ids.add(file.file_id)
|
|
603
608
|
return files
|
|
604
609
|
|
|
605
610
|
async def _search_as_queries(
|
|
@@ -54,7 +54,7 @@ from esgpull.plugin import (
|
|
|
54
54
|
)
|
|
55
55
|
from esgpull.processor import Processor
|
|
56
56
|
from esgpull.result import Err, Ok, Result
|
|
57
|
-
from esgpull.tui import UI, DummyLive, Verbosity, logger
|
|
57
|
+
from esgpull.tui import UI, DummyLive, ErrorCountColumn, Verbosity, logger
|
|
58
58
|
from esgpull.utils import format_size
|
|
59
59
|
|
|
60
60
|
|
|
@@ -387,6 +387,7 @@ class Esgpull:
|
|
|
387
387
|
SpinnerColumn(),
|
|
388
388
|
MofNCompleteColumn(),
|
|
389
389
|
TimeRemainingColumn(compact=True, elapsed_when_finished=True),
|
|
390
|
+
ErrorCountColumn(),
|
|
390
391
|
)
|
|
391
392
|
file_columns: list[str | ProgressColumn] = [
|
|
392
393
|
TextColumn("[cyan][{task.id}] [b blue]{task.fields[sha]}"),
|
|
@@ -434,7 +435,9 @@ class Esgpull:
|
|
|
434
435
|
if use_db:
|
|
435
436
|
self.db.add(*processor.files)
|
|
436
437
|
queue_size = len(processor.tasks)
|
|
437
|
-
main_task_id = main_progress.add_task(
|
|
438
|
+
main_task_id = main_progress.add_task(
|
|
439
|
+
"", total=queue_size, nb_errors=0
|
|
440
|
+
)
|
|
438
441
|
# TODO: rename ? installed/downloaded/completed/...
|
|
439
442
|
files: list[File] = []
|
|
440
443
|
errors: list[Err] = []
|
|
@@ -475,11 +478,13 @@ class Esgpull:
|
|
|
475
478
|
)
|
|
476
479
|
case Err(_, err):
|
|
477
480
|
queue_size -= 1
|
|
478
|
-
main_progress.update(
|
|
479
|
-
main_task_id, total=queue_size
|
|
480
|
-
)
|
|
481
481
|
result.data.file.status = FileStatus.Error
|
|
482
482
|
errors.append(result)
|
|
483
|
+
main_progress.update(
|
|
484
|
+
main_task_id,
|
|
485
|
+
total=queue_size,
|
|
486
|
+
nb_errors=len(errors),
|
|
487
|
+
)
|
|
483
488
|
emit(
|
|
484
489
|
Event.file_error,
|
|
485
490
|
file=result.data.file,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""update tables
|
|
2
|
+
|
|
3
|
+
Revision ID: 0.9.5
|
|
4
|
+
Revises: 0.9.4
|
|
5
|
+
Create Date: 2026-02-04 15:52:02.010431
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = '0.9.5'
|
|
14
|
+
down_revision = '0.9.4'
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
21
|
+
pass
|
|
22
|
+
# ### end Alembic commands ###
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def downgrade() -> None:
|
|
26
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
27
|
+
pass
|
|
28
|
+
# ### end Alembic commands ###
|
|
@@ -101,6 +101,17 @@ class ExceptionToDebugFilter(logging.Filter):
|
|
|
101
101
|
return True
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
class ErrorCountColumn(ProgressColumn):
|
|
105
|
+
def render(self, task):
|
|
106
|
+
nb_errors = task.fields.get("nb_errors", 0)
|
|
107
|
+
|
|
108
|
+
if nb_errors == 0:
|
|
109
|
+
return Text("")
|
|
110
|
+
|
|
111
|
+
suffix = "" if nb_errors == 1 else "s"
|
|
112
|
+
return Text(f"({nb_errors} download{suffix} failed)", style="red")
|
|
113
|
+
|
|
114
|
+
|
|
104
115
|
@define
|
|
105
116
|
class UI:
|
|
106
117
|
path: Path = field(converter=Path)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "esgpull"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.5"
|
|
8
8
|
classifiers = [
|
|
9
9
|
"License :: OSI Approved :: BSD License",
|
|
10
10
|
"Programming Language :: Python :: 3",
|
|
@@ -27,7 +27,7 @@ dependencies = [
|
|
|
27
27
|
"pyyaml>=6.0",
|
|
28
28
|
"tomlkit>=0.11.5",
|
|
29
29
|
"rich>=12.6.0",
|
|
30
|
-
"sqlalchemy>=2.0.0b2",
|
|
30
|
+
"sqlalchemy>=2.0.0b2,<2.1",
|
|
31
31
|
"setuptools>=65.4.1",
|
|
32
32
|
"aiostream>=0.4.5",
|
|
33
33
|
"attrs>=22.1.0",
|
|
@@ -14,7 +14,7 @@ aiofiles==25.1.0
|
|
|
14
14
|
# via esgpull
|
|
15
15
|
aiostream==0.7.1
|
|
16
16
|
# via esgpull
|
|
17
|
-
alembic==1.18.
|
|
17
|
+
alembic==1.18.3
|
|
18
18
|
# via esgpull
|
|
19
19
|
annotated-types==0.7.0
|
|
20
20
|
# via pydantic
|
|
@@ -25,7 +25,7 @@ asttokens==3.0.1
|
|
|
25
25
|
attrs==25.4.0
|
|
26
26
|
# via cattrs
|
|
27
27
|
# via esgpull
|
|
28
|
-
babel==2.
|
|
28
|
+
babel==2.18.0
|
|
29
29
|
# via mkdocs-material
|
|
30
30
|
backrefs==6.1
|
|
31
31
|
# via mkdocs-material
|
|
@@ -49,11 +49,11 @@ colorama==0.4.6
|
|
|
49
49
|
# via mkdocs-material
|
|
50
50
|
comm==0.2.3
|
|
51
51
|
# via ipykernel
|
|
52
|
-
coverage==7.13.
|
|
52
|
+
coverage==7.13.3
|
|
53
53
|
# via pytest-cov
|
|
54
|
-
cryptography==46.0.
|
|
54
|
+
cryptography==46.0.4
|
|
55
55
|
# via pyopenssl
|
|
56
|
-
debugpy==1.8.
|
|
56
|
+
debugpy==1.8.20
|
|
57
57
|
# via ipykernel
|
|
58
58
|
decorator==5.2.1
|
|
59
59
|
# via ipdb
|
|
@@ -73,7 +73,7 @@ filelock==3.20.3
|
|
|
73
73
|
# via pytest-mypy
|
|
74
74
|
ghp-import==2.1.0
|
|
75
75
|
# via mkdocs
|
|
76
|
-
greenlet==3.3.
|
|
76
|
+
greenlet==3.3.1
|
|
77
77
|
# via sqlalchemy
|
|
78
78
|
h11==0.16.0
|
|
79
79
|
# via httpcore
|
|
@@ -107,11 +107,11 @@ jupyter-core==5.9.1
|
|
|
107
107
|
# via ipykernel
|
|
108
108
|
# via jupyter-client
|
|
109
109
|
# via jupyter-console
|
|
110
|
-
librt==0.7.
|
|
110
|
+
librt==0.7.8
|
|
111
111
|
# via mypy
|
|
112
112
|
mako==1.3.10
|
|
113
113
|
# via alembic
|
|
114
|
-
markdown==3.10
|
|
114
|
+
markdown==3.10.1
|
|
115
115
|
# via mkdocs
|
|
116
116
|
# via mkdocs-material
|
|
117
117
|
# via pymdown-extensions
|
|
@@ -143,8 +143,8 @@ mypy-extensions==1.1.0
|
|
|
143
143
|
nest-asyncio==1.6.0
|
|
144
144
|
# via esgpull
|
|
145
145
|
# via ipykernel
|
|
146
|
-
orjson==3.11.
|
|
147
|
-
packaging==
|
|
146
|
+
orjson==3.11.7
|
|
147
|
+
packaging==26.0
|
|
148
148
|
# via esgpull
|
|
149
149
|
# via ipykernel
|
|
150
150
|
# via mkdocs
|
|
@@ -153,7 +153,7 @@ paginate==0.5.7
|
|
|
153
153
|
# via mkdocs-material
|
|
154
154
|
parso==0.8.5
|
|
155
155
|
# via jedi
|
|
156
|
-
pathspec==1.0.
|
|
156
|
+
pathspec==1.0.4
|
|
157
157
|
# via mkdocs
|
|
158
158
|
# via mypy
|
|
159
159
|
pexpect==4.9.0
|
|
@@ -168,13 +168,13 @@ pluggy==1.6.0
|
|
|
168
168
|
prompt-toolkit==3.0.52
|
|
169
169
|
# via ipython
|
|
170
170
|
# via jupyter-console
|
|
171
|
-
psutil==7.2.
|
|
171
|
+
psutil==7.2.2
|
|
172
172
|
# via ipykernel
|
|
173
173
|
ptyprocess==0.7.0
|
|
174
174
|
# via pexpect
|
|
175
175
|
pure-eval==0.2.3
|
|
176
176
|
# via stack-data
|
|
177
|
-
pycparser==
|
|
177
|
+
pycparser==3.0
|
|
178
178
|
# via cffi
|
|
179
179
|
pydantic==2.12.5
|
|
180
180
|
# via esgpull
|
|
@@ -189,11 +189,11 @@ pygments==2.19.2
|
|
|
189
189
|
# via mkdocs-material
|
|
190
190
|
# via pytest
|
|
191
191
|
# via rich
|
|
192
|
-
pymdown-extensions==10.20
|
|
192
|
+
pymdown-extensions==10.20.1
|
|
193
193
|
# via mkdocs-material
|
|
194
194
|
pyopenssl==25.3.0
|
|
195
195
|
# via esgpull
|
|
196
|
-
pyparsing==3.3.
|
|
196
|
+
pyparsing==3.3.2
|
|
197
197
|
# via esgpull
|
|
198
198
|
pytest==9.0.2
|
|
199
199
|
# via pytest-cov
|
|
@@ -221,13 +221,13 @@ pyzmq==27.1.0
|
|
|
221
221
|
# via jupyter-console
|
|
222
222
|
requests==2.32.5
|
|
223
223
|
# via mkdocs-material
|
|
224
|
-
rich==14.2
|
|
224
|
+
rich==14.3.2
|
|
225
225
|
# via esgpull
|
|
226
|
-
setuptools==80.
|
|
226
|
+
setuptools==80.10.2
|
|
227
227
|
# via esgpull
|
|
228
228
|
six==1.17.0
|
|
229
229
|
# via python-dateutil
|
|
230
|
-
sqlalchemy==2.0.
|
|
230
|
+
sqlalchemy==2.0.46
|
|
231
231
|
# via alembic
|
|
232
232
|
# via esgpull
|
|
233
233
|
stack-data==0.6.3
|
|
@@ -275,7 +275,7 @@ validators==0.22.0
|
|
|
275
275
|
# via click-params
|
|
276
276
|
watchdog==6.0.0
|
|
277
277
|
# via mkdocs
|
|
278
|
-
wcwidth==0.
|
|
278
|
+
wcwidth==0.5.3
|
|
279
279
|
# via prompt-toolkit
|
|
280
|
-
wrapt==2.
|
|
280
|
+
wrapt==2.1.1
|
|
281
281
|
# via deprecated
|
|
@@ -14,7 +14,7 @@ aiofiles==25.1.0
|
|
|
14
14
|
# via esgpull
|
|
15
15
|
aiostream==0.7.1
|
|
16
16
|
# via esgpull
|
|
17
|
-
alembic==1.18.
|
|
17
|
+
alembic==1.18.3
|
|
18
18
|
# via esgpull
|
|
19
19
|
annotated-types==0.7.0
|
|
20
20
|
# via pydantic
|
|
@@ -35,14 +35,14 @@ click==8.3.1
|
|
|
35
35
|
# via esgpull
|
|
36
36
|
click-params==0.5.0
|
|
37
37
|
# via esgpull
|
|
38
|
-
cryptography==46.0.
|
|
38
|
+
cryptography==46.0.4
|
|
39
39
|
# via pyopenssl
|
|
40
40
|
deprecated==1.3.1
|
|
41
41
|
# via click-params
|
|
42
42
|
exceptiongroup==1.3.1
|
|
43
43
|
# via anyio
|
|
44
44
|
# via cattrs
|
|
45
|
-
greenlet==3.3.
|
|
45
|
+
greenlet==3.3.1
|
|
46
46
|
# via sqlalchemy
|
|
47
47
|
h11==0.16.0
|
|
48
48
|
# via httpcore
|
|
@@ -63,11 +63,11 @@ mdurl==0.1.2
|
|
|
63
63
|
# via markdown-it-py
|
|
64
64
|
nest-asyncio==1.6.0
|
|
65
65
|
# via esgpull
|
|
66
|
-
packaging==
|
|
66
|
+
packaging==26.0
|
|
67
67
|
# via esgpull
|
|
68
68
|
platformdirs==4.5.1
|
|
69
69
|
# via esgpull
|
|
70
|
-
pycparser==
|
|
70
|
+
pycparser==3.0
|
|
71
71
|
# via cffi
|
|
72
72
|
pydantic==2.12.5
|
|
73
73
|
# via esgpull
|
|
@@ -80,17 +80,17 @@ pygments==2.19.2
|
|
|
80
80
|
# via rich
|
|
81
81
|
pyopenssl==25.3.0
|
|
82
82
|
# via esgpull
|
|
83
|
-
pyparsing==3.3.
|
|
83
|
+
pyparsing==3.3.2
|
|
84
84
|
# via esgpull
|
|
85
85
|
python-dotenv==1.2.1
|
|
86
86
|
# via pydantic-settings
|
|
87
87
|
pyyaml==6.0.3
|
|
88
88
|
# via esgpull
|
|
89
|
-
rich==14.2
|
|
89
|
+
rich==14.3.2
|
|
90
90
|
# via esgpull
|
|
91
|
-
setuptools==80.
|
|
91
|
+
setuptools==80.10.2
|
|
92
92
|
# via esgpull
|
|
93
|
-
sqlalchemy==2.0.
|
|
93
|
+
sqlalchemy==2.0.46
|
|
94
94
|
# via alembic
|
|
95
95
|
# via esgpull
|
|
96
96
|
tomli==2.4.0
|
|
@@ -114,5 +114,5 @@ typing-inspection==0.4.2
|
|
|
114
114
|
# via pydantic-settings
|
|
115
115
|
validators==0.22.0
|
|
116
116
|
# via click-params
|
|
117
|
-
wrapt==2.
|
|
117
|
+
wrapt==2.1.1
|
|
118
118
|
# via deprecated
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from click.testing import CliRunner
|
|
4
|
+
|
|
5
|
+
from esgpull import Esgpull
|
|
6
|
+
from esgpull.cli.download import download
|
|
7
|
+
from esgpull.config import Config
|
|
8
|
+
from esgpull.models import File, FileStatus, Query
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_download_errors_in_progress(root: Path, config: Config):
|
|
12
|
+
# Configure the environment
|
|
13
|
+
config.generate(overwrite=True)
|
|
14
|
+
runner = CliRunner()
|
|
15
|
+
esg = Esgpull(root)
|
|
16
|
+
|
|
17
|
+
# Create a query without updating (no datasets will be created)
|
|
18
|
+
query = Query()
|
|
19
|
+
query.compute_sha()
|
|
20
|
+
query.track(query.options.default())
|
|
21
|
+
|
|
22
|
+
# Manually create files
|
|
23
|
+
failing_file = File(
|
|
24
|
+
file_id="failing_file",
|
|
25
|
+
dataset_id="test_dataset_id",
|
|
26
|
+
master_id="test_master_id",
|
|
27
|
+
url="https://nonexistent.path/to/file.nc",
|
|
28
|
+
version="1.0",
|
|
29
|
+
filename="failing.nc",
|
|
30
|
+
local_path="test/failing",
|
|
31
|
+
data_node="test_node",
|
|
32
|
+
checksum="12345",
|
|
33
|
+
checksum_type="SHA256",
|
|
34
|
+
size=1000,
|
|
35
|
+
status=FileStatus.Queued,
|
|
36
|
+
)
|
|
37
|
+
failing_file.compute_sha()
|
|
38
|
+
|
|
39
|
+
# Add files to query
|
|
40
|
+
query.files.append(failing_file)
|
|
41
|
+
|
|
42
|
+
# Add query to database
|
|
43
|
+
esg.db.add(query)
|
|
44
|
+
|
|
45
|
+
# Run the download instruction in the CLI
|
|
46
|
+
result_download = runner.invoke(download)
|
|
47
|
+
|
|
48
|
+
# Parse the output to extract
|
|
49
|
+
output = result_download.output
|
|
50
|
+
assert "download failed" in output
|
|
@@ -304,7 +304,11 @@ def test_hits_never_empty(
|
|
|
304
304
|
("index_node", "exc"),
|
|
305
305
|
## TODO: test bridge, but it is super slow
|
|
306
306
|
[
|
|
307
|
-
(
|
|
307
|
+
pytest.param(
|
|
308
|
+
IPSL_NODE,
|
|
309
|
+
does_not_raise(),
|
|
310
|
+
marks=pytest.mark.xfail(reason="unstable"),
|
|
311
|
+
),
|
|
308
312
|
(CEDA_NODE, does_not_raise()),
|
|
309
313
|
("https://github.com", pytest.raises(Exception)),
|
|
310
314
|
("not_a_real.url", pytest.raises(Exception)),
|
|
@@ -337,7 +341,7 @@ def test_bridge_exact_match_params(ctx):
|
|
|
337
341
|
|
|
338
342
|
|
|
339
343
|
def test_bridge_wildcard_query_param(ctx):
|
|
340
|
-
query = Query(selection=dict(source_id=
|
|
344
|
+
query = Query(selection=dict(source_id="CESM*", variable_id="tas*"))
|
|
341
345
|
result = ctx.prepare_hits(
|
|
342
346
|
query,
|
|
343
347
|
file=False,
|
|
@@ -348,12 +352,12 @@ def test_bridge_wildcard_query_param(ctx):
|
|
|
348
352
|
assert "query" in params
|
|
349
353
|
assert "source_id" not in params
|
|
350
354
|
assert "variable_id" not in params
|
|
351
|
-
assert
|
|
352
|
-
assert
|
|
355
|
+
assert "source_id:CESM*" in params["query"]
|
|
356
|
+
assert "variable_id:tas*" in params["query"]
|
|
353
357
|
|
|
354
358
|
|
|
355
359
|
def test_bridge_mixed_exact_wildcard(ctx):
|
|
356
|
-
query = Query(selection=dict(source_id="CESM2", variable_id=
|
|
360
|
+
query = Query(selection=dict(source_id="CESM2", variable_id="tas*"))
|
|
357
361
|
result = ctx.prepare_hits(
|
|
358
362
|
query,
|
|
359
363
|
file=False,
|
|
@@ -364,7 +368,7 @@ def test_bridge_mixed_exact_wildcard(ctx):
|
|
|
364
368
|
assert "source_id" in params
|
|
365
369
|
assert params["source_id"] == "CESM2"
|
|
366
370
|
assert "query" in params
|
|
367
|
-
assert
|
|
371
|
+
assert "variable_id:tas*" in params["query"]
|
|
368
372
|
assert "variable_id" not in params
|
|
369
373
|
|
|
370
374
|
|
|
@@ -424,6 +428,81 @@ def test_solr_unchanged(ctx):
|
|
|
424
428
|
params = dict(result.request.url.params.items())
|
|
425
429
|
|
|
426
430
|
assert "query" in params
|
|
427
|
-
assert params[
|
|
431
|
+
assert params["query"] == "source_id:CESM2 AND variable_id:tas"
|
|
428
432
|
assert "source_id" not in params
|
|
429
433
|
assert "variable_id" not in params
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def test_files_skips_duplicate_file_ids(ctx):
|
|
437
|
+
"""Test that _files skips duplicate file_ids to prevent DB constraint violations."""
|
|
438
|
+
import asyncio
|
|
439
|
+
from unittest.mock import patch, MagicMock
|
|
440
|
+
from esgpull.context import ResultFiles
|
|
441
|
+
|
|
442
|
+
# Create Solr response docs that will be deserialized by File.serialize()
|
|
443
|
+
# Two files with same file_id (dataset.v1.file.nc) but different checksums
|
|
444
|
+
solr_docs = [
|
|
445
|
+
{
|
|
446
|
+
"instance_id": "dataset.v1.file.nc|node1.com",
|
|
447
|
+
"dataset_id": "dataset.v1|node1.com",
|
|
448
|
+
"title": "file.nc",
|
|
449
|
+
"url": "https://node1.com/file.nc|application/netcdf",
|
|
450
|
+
"data_node": "node1.com",
|
|
451
|
+
"checksum": "abc123",
|
|
452
|
+
"checksum_type": "SHA256",
|
|
453
|
+
"size": 1000,
|
|
454
|
+
"directory_format_template_": "%(root)s/v1", # Simple template
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
"instance_id": "dataset.v1.file.nc|node2.com",
|
|
458
|
+
"dataset_id": "dataset.v1|node2.com",
|
|
459
|
+
"title": "file.nc",
|
|
460
|
+
"url": "https://node2.com/file.nc|application/netcdf",
|
|
461
|
+
"data_node": "node2.com",
|
|
462
|
+
"checksum": "def456", # Different checksum!
|
|
463
|
+
"checksum_type": "SHA256",
|
|
464
|
+
"size": 1000,
|
|
465
|
+
"directory_format_template_": "%(root)s/v1", # Simple template
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"instance_id": "dataset.v1.other.nc|node1.com",
|
|
469
|
+
"dataset_id": "dataset.v1|node1.com",
|
|
470
|
+
"title": "other.nc",
|
|
471
|
+
"url": "https://node1.com/other.nc|application/netcdf",
|
|
472
|
+
"data_node": "node1.com",
|
|
473
|
+
"checksum": "xyz789",
|
|
474
|
+
"checksum_type": "SHA256",
|
|
475
|
+
"size": 2000,
|
|
476
|
+
"directory_format_template_": "%(root)s/v1", # Simple template
|
|
477
|
+
},
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
# Create a ResultFiles with proper JSON that will be parsed by process()
|
|
481
|
+
query = Query()
|
|
482
|
+
result = ResultFiles(query=query, file=True)
|
|
483
|
+
result.request = MagicMock()
|
|
484
|
+
result.json = {"response": {"docs": solr_docs}}
|
|
485
|
+
|
|
486
|
+
# Mock _fetch to yield our result
|
|
487
|
+
async def mock_fetch(*results):
|
|
488
|
+
yield result
|
|
489
|
+
|
|
490
|
+
async def run_test():
|
|
491
|
+
with patch.object(ctx, "_fetch", mock_fetch):
|
|
492
|
+
return await ctx._files(result, keep_duplicates=False)
|
|
493
|
+
|
|
494
|
+
result_files = asyncio.run(run_test())
|
|
495
|
+
|
|
496
|
+
# Should only have 2 files (second one with duplicate file_id is skipped)
|
|
497
|
+
assert len(result_files) == 2
|
|
498
|
+
|
|
499
|
+
# Verify the file_ids in the result
|
|
500
|
+
file_ids = {f.file_id for f in result_files}
|
|
501
|
+
assert file_ids == {"dataset.v1.file.nc", "dataset.v1.other.nc"}
|
|
502
|
+
|
|
503
|
+
# The first file with dataset.v1.file.nc should be kept (abc123 checksum)
|
|
504
|
+
duplicate_files = [
|
|
505
|
+
f for f in result_files if f.file_id == "dataset.v1.file.nc"
|
|
506
|
+
]
|
|
507
|
+
assert len(duplicate_files) == 1
|
|
508
|
+
assert duplicate_files[0].checksum == "abc123"
|
|
@@ -379,7 +379,7 @@ wheels = [
|
|
|
379
379
|
|
|
380
380
|
[[package]]
|
|
381
381
|
name = "esgpull"
|
|
382
|
-
version = "0.9.
|
|
382
|
+
version = "0.9.5"
|
|
383
383
|
source = { editable = "." }
|
|
384
384
|
dependencies = [
|
|
385
385
|
{ name = "aiofiles" },
|
|
@@ -433,7 +433,7 @@ requires-dist = [
|
|
|
433
433
|
{ name = "pyyaml", specifier = ">=6.0" },
|
|
434
434
|
{ name = "rich", specifier = ">=12.6.0" },
|
|
435
435
|
{ name = "setuptools", specifier = ">=65.4.1" },
|
|
436
|
-
{ name = "sqlalchemy", specifier = ">=2.0.0b2" },
|
|
436
|
+
{ name = "sqlalchemy", specifier = ">=2.0.0b2,<2.1" },
|
|
437
437
|
{ name = "tomlkit", specifier = ">=0.11.5" },
|
|
438
438
|
]
|
|
439
439
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{esgpull-0.9.4 → esgpull-0.9.5}/esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|