esgpull 0.9.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 (166) hide show
  1. {esgpull-0.9.0 → esgpull-0.9.1}/PKG-INFO +18 -3
  2. {esgpull-0.9.0 → esgpull-0.9.1}/README.md +17 -2
  3. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/index.md +4 -0
  4. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/installation.md +17 -36
  5. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/remove.py +9 -3
  6. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/self.py +1 -1
  7. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/update.py +38 -38
  8. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/config.py +1 -1
  9. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/database.py +12 -4
  10. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/fs.py +9 -20
  11. esgpull-0.9.1/esgpull/migrations/versions/0.9.1_update_tables.py +28 -0
  12. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/sql.py +8 -0
  13. {esgpull-0.9.0 → esgpull-0.9.1}/pyproject.toml +43 -49
  14. {esgpull-0.9.0 → esgpull-0.9.1}/requirements-dev.lock +17 -19
  15. {esgpull-0.9.0 → esgpull-0.9.1}/requirements.lock +7 -8
  16. esgpull-0.9.1/tests/cli/test_remove.py +76 -0
  17. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_fs.py +43 -41
  18. {esgpull-0.9.0 → esgpull-0.9.1}/uv.lock +676 -990
  19. esgpull-0.9.0/recipe/meta.yaml +0 -60
  20. esgpull-0.9.0/recipe/recipe.yaml +0 -58
  21. {esgpull-0.9.0 → esgpull-0.9.1}/.github/workflows/ci.yml +0 -0
  22. {esgpull-0.9.0 → esgpull-0.9.1}/.github/workflows/doc.yml +0 -0
  23. {esgpull-0.9.0 → esgpull-0.9.1}/.github/workflows/pypi-publish.yml +0 -0
  24. {esgpull-0.9.0 → esgpull-0.9.1}/.gitignore +0 -0
  25. {esgpull-0.9.0 → esgpull-0.9.1}/.pre-commit-config.yaml +0 -0
  26. {esgpull-0.9.0 → esgpull-0.9.1}/CITATION.cff +0 -0
  27. {esgpull-0.9.0 → esgpull-0.9.1}/LICENSE +0 -0
  28. {esgpull-0.9.0 → esgpull-0.9.1}/alembic.ini +0 -0
  29. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/configuration.md +0 -0
  30. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/download.md +0 -0
  31. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/glossary.md +0 -0
  32. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/download_1.svg +0 -0
  33. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/download_2.svg +0 -0
  34. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/download_3.svg +0 -0
  35. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/download_4.svg +0 -0
  36. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/download_5.svg +0 -0
  37. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/download_6.svg +0 -0
  38. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/intro_1.svg +0 -0
  39. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/intro_2.svg +0 -0
  40. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/intro_3.svg +0 -0
  41. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/intro_4.svg +0 -0
  42. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/intro_5.svg +0 -0
  43. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/intro_6.svg +0 -0
  44. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/quickstart_1.svg +0 -0
  45. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_1.svg +0 -0
  46. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_2.svg +0 -0
  47. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_3.svg +0 -0
  48. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_4.svg +0 -0
  49. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_5.svg +0 -0
  50. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_6.svg +0 -0
  51. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_7.svg +0 -0
  52. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/images/search_ignore.svg +0 -0
  53. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/plugins.md +0 -0
  54. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/queries.md +0 -0
  55. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/quickstart.md +0 -0
  56. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/search.md +0 -0
  57. {esgpull-0.9.0 → esgpull-0.9.1}/docs/docs/stylesheets/extra.css +0 -0
  58. {esgpull-0.9.0 → esgpull-0.9.1}/docs/includes/abbreviations.md +0 -0
  59. {esgpull-0.9.0 → esgpull-0.9.1}/docs/mkdocs.yml +0 -0
  60. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/__init__.py +0 -0
  61. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/auth.py +0 -0
  62. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/__init__.py +0 -0
  63. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/add.py +0 -0
  64. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/autoremove.py +0 -0
  65. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/config.py +0 -0
  66. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/convert.py +0 -0
  67. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/decorators.py +0 -0
  68. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/download.py +0 -0
  69. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/facet.py +0 -0
  70. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/get.py +0 -0
  71. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/install.py +0 -0
  72. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/login.py +0 -0
  73. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/plugins.py +0 -0
  74. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/retry.py +0 -0
  75. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/search.py +0 -0
  76. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/show.py +0 -0
  77. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/status.py +0 -0
  78. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/track.py +0 -0
  79. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/cli/utils.py +0 -0
  80. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/constants.py +0 -0
  81. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/context.py +0 -0
  82. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/download.py +0 -0
  83. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/esgpull.py +0 -0
  84. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/exceptions.py +0 -0
  85. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/graph.py +0 -0
  86. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/install_config.py +0 -0
  87. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/README +0 -0
  88. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/env.py +0 -0
  89. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/script.py.mako +0 -0
  90. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.0_update_tables.py +0 -0
  91. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.1_update_tables.py +0 -0
  92. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.2_update_tables.py +0 -0
  93. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.3_update_tables.py +0 -0
  94. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.4_update_tables.py +0 -0
  95. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.5_update_tables.py +0 -0
  96. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.6_update_tables.py +0 -0
  97. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.7_update_tables.py +0 -0
  98. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.3.8_update_tables.py +0 -0
  99. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.4.0_update_tables.py +0 -0
  100. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.0_update_tables.py +0 -0
  101. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.1_update_tables.py +0 -0
  102. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.2_update_tables.py +0 -0
  103. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.3_update_tables.py +0 -0
  104. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.4_update_tables.py +0 -0
  105. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.5.5_update_tables.py +0 -0
  106. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.0_update_tables.py +0 -0
  107. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.1_update_tables.py +0 -0
  108. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.2_update_tables.py +0 -0
  109. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.3_update_tables.py +0 -0
  110. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.4_update_tables.py +0 -0
  111. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.6.5_update_tables.py +0 -0
  112. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.0_update_tables.py +0 -0
  113. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.1_update_tables.py +0 -0
  114. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.2_update_tables.py +0 -0
  115. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.7.3_update_tables.py +0 -0
  116. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.8.0_update_tables.py +0 -0
  117. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/0.9.0_update_tables.py +0 -0
  118. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/14c72daea083_query_add_column_updated_at.py +0 -0
  119. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/c7c8541fa741_query_add_column_added_at.py +0 -0
  120. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py +0 -0
  121. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py +0 -0
  122. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/__init__.py +0 -0
  123. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/base.py +0 -0
  124. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/dataset.py +0 -0
  125. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/facet.py +0 -0
  126. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/file.py +0 -0
  127. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/options.py +0 -0
  128. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/query.py +0 -0
  129. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/selection.py +0 -0
  130. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/synda_file.py +0 -0
  131. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/tag.py +0 -0
  132. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/models/utils.py +0 -0
  133. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/plugin.py +0 -0
  134. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/processor.py +0 -0
  135. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/py.typed +0 -0
  136. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/result.py +0 -0
  137. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/tui.py +0 -0
  138. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/utils.py +0 -0
  139. {esgpull-0.9.0 → esgpull-0.9.1}/esgpull/version.py +0 -0
  140. {esgpull-0.9.0 → esgpull-0.9.1}/pdm.lock +0 -0
  141. {esgpull-0.9.0 → esgpull-0.9.1}/tests/__init__.py +0 -0
  142. {esgpull-0.9.0 → esgpull-0.9.1}/tests/assets/error_plugin.py +0 -0
  143. {esgpull-0.9.0 → esgpull-0.9.1}/tests/assets/incompatible_plugin.py +0 -0
  144. {esgpull-0.9.0 → esgpull-0.9.1}/tests/assets/priority_test_plugin.py +0 -0
  145. {esgpull-0.9.0 → esgpull-0.9.1}/tests/assets/sample_plugin.py +0 -0
  146. {esgpull-0.9.0 → esgpull-0.9.1}/tests/cli/__init__.py +0 -0
  147. {esgpull-0.9.0 → esgpull-0.9.1}/tests/cli/test_dataset.py +0 -0
  148. {esgpull-0.9.0 → esgpull-0.9.1}/tests/cli/test_parse.py +0 -0
  149. {esgpull-0.9.0 → esgpull-0.9.1}/tests/cli/test_plugin_cli.py +0 -0
  150. {esgpull-0.9.0 → esgpull-0.9.1}/tests/cli/test_show_dates.py +0 -0
  151. {esgpull-0.9.0 → esgpull-0.9.1}/tests/cli/test_update.py +0 -0
  152. {esgpull-0.9.0 → esgpull-0.9.1}/tests/conftest.py +0 -0
  153. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_auth.py +0 -0
  154. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_config.py +0 -0
  155. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_context.py +0 -0
  156. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_dataset.py +0 -0
  157. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_db.py +0 -0
  158. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_esgpull.py +0 -0
  159. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_graph.py +0 -0
  160. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_plugin.py +0 -0
  161. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_processor.py +0 -0
  162. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_query.py +0 -0
  163. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_selection.py +0 -0
  164. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_synda.py +0 -0
  165. {esgpull-0.9.0 → esgpull-0.9.1}/tests/test_utils.py +0 -0
  166. {esgpull-0.9.0 → esgpull-0.9.1}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esgpull
3
- Version: 0.9.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/
@@ -63,14 +63,29 @@ for dataset in datasets:
63
63
 
64
64
  ## Installation
65
65
 
66
- Install `esgpull` using pip or conda:
66
+ `esgpull` is distributed via PyPI:
67
67
 
68
68
  ```shell
69
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
70
83
  ```
71
84
 
72
85
  ```shell
73
- conda install -c conda-forge ipsl::esgpull
86
+ # with pipx
87
+ pipx install esgpull
88
+ esgpull --help
74
89
  ```
75
90
 
76
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
@@ -76,6 +76,10 @@ Have a look at the [Installation page](installation) for more ways to install.
76
76
 
77
77
  Jump directly to the [Quickstart guide](quickstart) to get to know how to use `esgpull`.
78
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
+
79
83
 
80
84
  <!-- [ESGF portal]: https://esgf-node.ipsl.upmc.fr/search/cmip6-ipsl -->
81
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.
@@ -15,11 +15,13 @@ from esgpull.utils import format_size
15
15
  @args.query_id
16
16
  @opts.tag
17
17
  @opts.children
18
+ @opts.yes
18
19
  @opts.verbosity
19
20
  def remove(
20
21
  query_id: str | None,
21
22
  tag: str | None,
22
23
  children: bool,
24
+ yes: bool,
23
25
  verbosity: Verbosity,
24
26
  ) -> None:
25
27
  """
@@ -49,7 +51,7 @@ def remove(
49
51
  graph.add(*queries)
50
52
  esg.ui.print(graph)
51
53
  msg = f"Remove {nb} quer{ies}?"
52
- if not esg.ui.ask(msg, default=True):
54
+ if not yes and not esg.ui.ask(msg, default=True):
53
55
  raise Abort
54
56
  for query in queries:
55
57
  if query.has_files:
@@ -59,14 +61,18 @@ def remove(
59
61
  f":stop_sign: {query.rich_name} is linked"
60
62
  f" to {nb} downloaded files ({format_size(size)})."
61
63
  )
62
- if not esg.ui.ask("Delete anyway?", default=False):
64
+ if not yes and not esg.ui.ask(
65
+ "Delete anyway?", default=False
66
+ ):
63
67
  raise Abort
64
68
  if not children and esg.graph.get_children(query.sha):
65
69
  esg.ui.print(
66
70
  ":stop_sign: Some queries block"
67
71
  f" removal of {query.rich_name}."
68
72
  )
69
- if esg.ui.ask("Show blocking queries?", default=False):
73
+ if not yes and esg.ui.ask(
74
+ "Show blocking queries?", default=False
75
+ ):
70
76
  esg.ui.print(esg.graph.subgraph(query, children=True))
71
77
  raise Exit(1)
72
78
  esg.db.delete(*queries)
@@ -199,7 +199,7 @@ def delete():
199
199
  TempUI.print(f"Deleting {path} from config...")
200
200
  TempUI.print("To remove all files from this install, run:\n")
201
201
  config = Config.load(path=path)
202
- for p in config.paths:
202
+ for p in config.paths.values():
203
203
  if not p.is_relative_to(path):
204
204
  TempUI.print(f"$ rm -rf {p}")
205
205
  TempUI.print(f"$ rm -rf {path}")
@@ -11,7 +11,7 @@ from esgpull.cli.utils import get_queries, init_esgpull, valid_name_tag
11
11
  from esgpull.context import HintsDict, ResultSearch
12
12
  from esgpull.exceptions import UnsetOptionsError
13
13
  from esgpull.models import Dataset, File, FileStatus, Query
14
- from esgpull.tui import Verbosity, logger
14
+ from esgpull.tui import Verbosity
15
15
  from esgpull.utils import format_size
16
16
 
17
17
 
@@ -177,26 +177,37 @@ def update(
177
177
  for qf, qf_files in zip(qfs, files):
178
178
  qf.files = qf_files
179
179
  for qf in qfs:
180
- new_files = [f for f in qf.files if f not in esg.db]
181
- new_datasets = [d for d in qf.datasets if d not in esg.db]
182
- nb_datasets = len(new_datasets)
183
- nb_files = len(new_files)
184
180
  if not qf.query.tracked:
185
181
  esg.db.add(qf.query)
186
182
  continue
187
- elif nb_datasets == nb_files == 0:
188
- esg.ui.print(f"{qf.query.rich_name} is already up-to-date.")
189
- continue
190
- size = sum([file.size for file in new_files])
191
- if size > 0:
192
- queue_msg = " and send new files to download queue"
193
- else:
194
- queue_msg = ""
183
+ with esg.db.commit_context():
184
+ unregistered_datasets = [
185
+ f for f in qf.datasets if f not in esg.db
186
+ ]
187
+ if len(unregistered_datasets) > 0:
188
+ esg.ui.print(
189
+ f"Adding {len(unregistered_datasets)} new datasets to database."
190
+ )
191
+ esg.db.session.add_all(unregistered_datasets)
192
+ files_from_db = [
193
+ esg.db.get(File, f.sha) for f in qf.files if f in esg.db
194
+ ]
195
+ registered_files = [f for f in files_from_db if f is not None]
196
+ unregistered_files = [f for f in qf.files if f not in esg.db]
197
+ if len(unregistered_files) > 0:
198
+ esg.ui.print(
199
+ f"Adding {len(unregistered_files)} new files to database."
200
+ )
201
+ esg.db.session.add_all(unregistered_files)
202
+ files = registered_files + unregistered_files
203
+ not_done_files = [
204
+ file for file in files if file.status != FileStatus.Done
205
+ ]
206
+ download_size = sum(file.size for file in not_done_files)
195
207
  msg = (
196
- f"\n{qf.query.rich_name}: {nb_files} new"
197
- f" files, {nb_datasets} new datasets"
198
- f" ({format_size(size)})."
199
- f"\nUpdate the database{queue_msg}?"
208
+ f"\n{qf.query.rich_name}: {len(not_done_files)} "
209
+ f" files ({format_size(download_size)}) to download."
210
+ f"\nLink to query and send to download queue?"
200
211
  )
201
212
  if yes:
202
213
  choice = "y"
@@ -212,30 +223,19 @@ def update(
212
223
  if choice == "y":
213
224
  legacy = esg.legacy_query
214
225
  has_legacy = legacy.state.persistent
226
+ applied_changes = False
215
227
  with esg.db.commit_context():
216
- for dataset in esg.ui.track(
217
- new_datasets,
218
- description=f"{qf.query.rich_name} (datasets)",
219
- ):
220
- esg.db.session.add(dataset)
221
228
  for file in esg.ui.track(
222
- new_files,
223
- description=f"{qf.query.rich_name} (files)",
229
+ files,
230
+ description=f"{qf.query.rich_name}",
224
231
  ):
225
- file_db = esg.db.get(File, file.sha)
226
- if file_db is None:
227
- if esg.db.has_file_id(file):
228
- logger.error(
229
- "File id already exists in database, "
230
- "there might be an error with its checksum"
231
- f"\n{file}"
232
- )
233
- continue
232
+ if file.status != FileStatus.Done:
234
233
  file.status = FileStatus.Queued
235
- esg.db.session.add(file)
236
- elif has_legacy and legacy in file_db.queries:
237
- esg.db.unlink(query=legacy, file=file_db)
238
- esg.db.link(query=qf.query, file=file)
239
- qf.query.updated_at = datetime.now(timezone.utc)
234
+ if has_legacy and legacy in file.queries:
235
+ _ = esg.db.unlink(query=legacy, file=file)
236
+ changed = esg.db.link(query=qf.query, file=file)
237
+ applied_changes = applied_changes or changed
238
+ if applied_changes:
239
+ qf.query.updated_at = datetime.now(timezone.utc)
240
240
  esg.db.session.add(qf.query)
241
241
  esg.ui.raise_maybe_record(Exit(0))
@@ -138,7 +138,7 @@ class Paths:
138
138
  root = InstallConfig.default
139
139
  return root / "plugins"
140
140
 
141
- def __iter__(self) -> Iterator[Path]:
141
+ def values(self) -> Iterator[Path]:
142
142
  yield self.auth
143
143
  yield self.data
144
144
  yield self.db
@@ -147,11 +147,19 @@ class Database:
147
147
  for item in items:
148
148
  make_transient(item)
149
149
 
150
- def link(self, query: Query, file: File):
151
- self.session.execute(sql.query_file.link(query, file))
150
+ def link(self, query: Query, file: File) -> bool:
151
+ if not self.session.scalar(sql.query_file.is_linked(query, file)):
152
+ self.session.execute(sql.query_file.link(query, file))
153
+ return True
154
+ else:
155
+ return False
152
156
 
153
- def unlink(self, query: Query, file: File):
154
- self.session.execute(sql.query_file.unlink(query, file))
157
+ def unlink(self, query: Query, file: File) -> bool:
158
+ if self.session.scalar(sql.query_file.is_linked(query, file)):
159
+ self.session.execute(sql.query_file.unlink(query, file))
160
+ return True
161
+ else:
162
+ return False
155
163
 
156
164
  def __contains__(self, item: Base | BaseNoSHA) -> bool:
157
165
  mapper = inspect(item.__class__)
@@ -10,7 +10,7 @@ from shutil import copyfile
10
10
  import aiofiles
11
11
  from aiofiles.threadpool.binary import AsyncBufferedIOBase
12
12
 
13
- from esgpull.config import Config
13
+ from esgpull.config import Config, Paths
14
14
  from esgpull.models import File
15
15
  from esgpull.result import Err, Ok, Result
16
16
  from esgpull.tui import logger
@@ -63,45 +63,34 @@ class Digest:
63
63
 
64
64
  @dataclass
65
65
  class Filesystem:
66
- auth: Path
67
- data: Path
68
- db: Path
69
- log: Path
70
- tmp: Path
66
+ paths: Paths
71
67
  disable_checksum: bool = False
72
68
  install: InitVar[bool] = True
73
69
 
74
70
  @staticmethod
75
71
  def from_config(config: Config, install: bool = False) -> Filesystem:
76
72
  return Filesystem(
77
- auth=config.paths.auth,
78
- data=config.paths.data,
79
- db=config.paths.db,
80
- log=config.paths.log,
81
- tmp=config.paths.tmp,
73
+ paths=config.paths,
82
74
  disable_checksum=config.download.disable_checksum,
83
75
  install=install,
84
76
  )
85
77
 
86
78
  def __post_init__(self, install: bool = True) -> None:
87
79
  if install:
88
- self.auth.mkdir(parents=True, exist_ok=True)
89
- self.data.mkdir(parents=True, exist_ok=True)
90
- self.db.mkdir(parents=True, exist_ok=True)
91
- self.log.mkdir(parents=True, exist_ok=True)
92
- self.tmp.mkdir(parents=True, exist_ok=True)
80
+ for path in self.paths.values():
81
+ path.mkdir(parents=True, exist_ok=True)
93
82
 
94
83
  def __getitem__(self, file: File) -> FilePath:
95
84
  if not isinstance(file, File):
96
85
  raise TypeError(file)
97
86
  return FilePath(
98
- drs=self.data / file.local_path / file.filename,
99
- tmp=self.tmp / f"{file.sha}.part",
87
+ drs=self.paths.data / file.local_path / file.filename,
88
+ tmp=self.paths.tmp / f"{file.sha}.part",
100
89
  )
101
90
 
102
91
  def glob_netcdf(self) -> Iterator[Path]:
103
- for path in self.data.glob("**/*.nc"):
104
- yield path.relative_to(self.data)
92
+ for path in self.paths.data.glob("**/*.nc"):
93
+ yield path.relative_to(self.paths.data)
105
94
 
106
95
  def open(self, file: File) -> FileObject:
107
96
  return FileObject(self[file])
@@ -0,0 +1,28 @@
1
+ """update tables
2
+
3
+ Revision ID: 0.9.1
4
+ Revises: 0.9.0
5
+ Create Date: 2025-08-08 10:38:14.204594
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '0.9.1'
14
+ down_revision = '0.9.0'
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 ###
@@ -301,3 +301,11 @@ class query_file:
301
301
  .where(query_file_proxy.c.query_sha == query.sha)
302
302
  .where(query_file_proxy.c.file_sha == file.sha)
303
303
  )
304
+
305
+ @staticmethod
306
+ def is_linked(query: Query, file: File) -> sa.Select[tuple[bool]]:
307
+ return sa.select(
308
+ sa.exists()
309
+ .where(query_file_proxy.c.query_sha == query.sha)
310
+ .where(query_file_proxy.c.file_sha == file.sha)
311
+ )
@@ -4,38 +4,38 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "esgpull"
7
- version = "0.9.0"
7
+ version = "0.9.1"
8
8
  classifiers = [
9
- "License :: OSI Approved :: BSD License",
10
- "Programming Language :: Python :: 3",
11
- "Programming Language :: Python :: 3.10",
12
- "Programming Language :: Python :: 3.11",
13
- "Programming Language :: Python :: 3.12"
9
+ "License :: OSI Approved :: BSD License",
10
+ "Programming Language :: Python :: 3",
11
+ "Programming Language :: Python :: 3.10",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Programming Language :: Python :: 3.12",
14
14
  ]
15
15
  description = "ESGF data discovery, download, replication tool"
16
- authors = [{name = "Sven Rodriguez", email = "srodriguez@ipsl.fr"}]
16
+ authors = [{ name = "Sven Rodriguez", email = "srodriguez@ipsl.fr" }]
17
17
  readme = "README.md"
18
18
  requires-python = ">=3.10"
19
19
  dependencies = [
20
- "MyProxyClient>=2.1.0",
21
- "aiofiles>=22.1.0",
22
- "alembic>=1.8.1",
23
- "click>=8.1.3",
24
- "click-params>=0.4.0",
25
- "httpx>=0.23.0",
26
- "nest-asyncio>=1.5.6",
27
- "pyOpenSSL>=22.1.0",
28
- "pyyaml>=6.0",
29
- "tomlkit>=0.11.5",
30
- "rich>=12.6.0",
31
- "sqlalchemy>=2.0.0b2",
32
- "setuptools>=65.4.1",
33
- "aiostream>=0.4.5",
34
- "attrs>=22.1.0",
35
- "cattrs>=22.2.0",
36
- "platformdirs>=2.6.2",
37
- "pyparsing>=3.0.9",
38
- "packaging>=25.0",
20
+ "MyProxyClient>=2.1.0",
21
+ "aiofiles>=22.1.0",
22
+ "alembic>=1.8.1",
23
+ "click>=8.1.3",
24
+ "click-params>=0.4.0",
25
+ "httpx>=0.23.0",
26
+ "nest-asyncio>=1.5.6",
27
+ "pyOpenSSL>=22.1.0",
28
+ "pyyaml>=6.0",
29
+ "tomlkit>=0.11.5",
30
+ "rich>=12.6.0",
31
+ "sqlalchemy>=2.0.0b2",
32
+ "setuptools>=65.4.1",
33
+ "aiostream>=0.4.5",
34
+ "attrs>=22.1.0",
35
+ "cattrs>=22.2.0",
36
+ "platformdirs>=2.6.2",
37
+ "pyparsing>=3.0.9",
38
+ "packaging>=25.0",
39
39
  ]
40
40
  license.text = "BSD-3-Clause"
41
41
 
@@ -69,20 +69,14 @@ src_paths = ["esgpull", "tests", "migrations", "examples"]
69
69
  ignore_missing_imports = true
70
70
 
71
71
  [tool.pdm.scripts]
72
- doc = {shell = "cd docs && mkdocs serve", help = "Start doc server"}
72
+ doc = { shell = "cd docs && mkdocs serve", help = "Start doc server" }
73
73
 
74
74
  [tool.pytest.ini_options]
75
75
  minversion = "6.2.4"
76
- filterwarnings = [
77
- "ignore::DeprecationWarning"
78
- ]
79
- markers = [
80
- "slow: mark test as slow to run"
81
- ]
76
+ filterwarnings = ["ignore::DeprecationWarning"]
77
+ markers = ["slow: mark test as slow to run"]
82
78
  addopts = "-r aR -n auto --cov=esgpull --cov-config=pyproject.toml --cov-report term-missing:skip-covered --mypy"
83
- testpaths = [
84
- "tests/"
85
- ]
79
+ testpaths = ["tests/"]
86
80
 
87
81
  [tool.ruff]
88
82
  line-length = 79
@@ -94,22 +88,22 @@ extend-select = ["I"]
94
88
  [tool.rye]
95
89
  managed = true
96
90
  dev-dependencies = [
97
- "jupyter-console>=6.6.3",
98
- "typing-extensions>=4.12.2",
99
- "pytest>=8.3.3",
100
- "pytest-cov>=5.0.0",
101
- "mkdocs-material>=9.5.34",
102
- "mypy>=1.11.2",
103
- "types-pyyaml>=6.0.12.20240808",
104
- "types-aiofiles>=24.1.0.20240626",
105
- "pytest-mypy>=0.10.3",
106
- "pytest-xdist>=3.6.1",
107
- "ipdb>=0.13.13",
108
- "orjson>=3.10.7"
91
+ "jupyter-console>=6.6.3",
92
+ "typing-extensions>=4.12.2",
93
+ "pytest>=8.3.3",
94
+ "pytest-cov>=5.0.0",
95
+ "mkdocs-material>=9.5.34",
96
+ "mypy>=1.11.2",
97
+ "types-pyyaml>=6.0.12.20240808",
98
+ "types-aiofiles>=24.1.0.20240626",
99
+ "pytest-mypy>=0.10.3",
100
+ "pytest-xdist>=3.6.1",
101
+ "ipdb>=0.13.13",
102
+ "orjson>=3.10.7",
109
103
  ]
110
104
 
111
105
  [tool.rye.scripts]
112
- esg = {cmd = "esgpull", env-file = ".env"}
106
+ esg = { cmd = "esgpull", env-file = ".env" }
113
107
 
114
108
  [dependency-groups]
115
109
  dev = [