esgpull 0.9.1__tar.gz → 0.9.2__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 (165) hide show
  1. {esgpull-0.9.1 → esgpull-0.9.2}/PKG-INFO +1 -1
  2. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/plugins.py +1 -1
  3. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/search.py +21 -8
  4. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/context.py +38 -9
  5. esgpull-0.9.2/esgpull/migrations/versions/0.9.2_update_tables.py +28 -0
  6. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/plugin.py +22 -19
  7. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/utils.py +0 -17
  8. {esgpull-0.9.1 → esgpull-0.9.2}/pyproject.toml +1 -1
  9. {esgpull-0.9.1 → esgpull-0.9.2}/requirements-dev.lock +23 -22
  10. {esgpull-0.9.1 → esgpull-0.9.2}/requirements.lock +8 -8
  11. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_context.py +43 -2
  12. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_utils.py +1 -9
  13. {esgpull-0.9.1 → esgpull-0.9.2}/uv.lock +1 -1
  14. {esgpull-0.9.1 → esgpull-0.9.2}/.github/workflows/ci.yml +0 -0
  15. {esgpull-0.9.1 → esgpull-0.9.2}/.github/workflows/doc.yml +0 -0
  16. {esgpull-0.9.1 → esgpull-0.9.2}/.github/workflows/pypi-publish.yml +0 -0
  17. {esgpull-0.9.1 → esgpull-0.9.2}/.gitignore +0 -0
  18. {esgpull-0.9.1 → esgpull-0.9.2}/.pre-commit-config.yaml +0 -0
  19. {esgpull-0.9.1 → esgpull-0.9.2}/CITATION.cff +0 -0
  20. {esgpull-0.9.1 → esgpull-0.9.2}/LICENSE +0 -0
  21. {esgpull-0.9.1 → esgpull-0.9.2}/README.md +0 -0
  22. {esgpull-0.9.1 → esgpull-0.9.2}/alembic.ini +0 -0
  23. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/configuration.md +0 -0
  24. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/download.md +0 -0
  25. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/glossary.md +0 -0
  26. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/download_1.svg +0 -0
  27. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/download_2.svg +0 -0
  28. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/download_3.svg +0 -0
  29. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/download_4.svg +0 -0
  30. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/download_5.svg +0 -0
  31. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/download_6.svg +0 -0
  32. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/intro_1.svg +0 -0
  33. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/intro_2.svg +0 -0
  34. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/intro_3.svg +0 -0
  35. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/intro_4.svg +0 -0
  36. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/intro_5.svg +0 -0
  37. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/intro_6.svg +0 -0
  38. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/quickstart_1.svg +0 -0
  39. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_1.svg +0 -0
  40. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_2.svg +0 -0
  41. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_3.svg +0 -0
  42. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_4.svg +0 -0
  43. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_5.svg +0 -0
  44. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_6.svg +0 -0
  45. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_7.svg +0 -0
  46. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/images/search_ignore.svg +0 -0
  47. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/index.md +0 -0
  48. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/installation.md +0 -0
  49. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/plugins.md +0 -0
  50. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/queries.md +0 -0
  51. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/quickstart.md +0 -0
  52. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/search.md +0 -0
  53. {esgpull-0.9.1 → esgpull-0.9.2}/docs/docs/stylesheets/extra.css +0 -0
  54. {esgpull-0.9.1 → esgpull-0.9.2}/docs/includes/abbreviations.md +0 -0
  55. {esgpull-0.9.1 → esgpull-0.9.2}/docs/mkdocs.yml +0 -0
  56. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/__init__.py +0 -0
  57. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/auth.py +0 -0
  58. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/__init__.py +0 -0
  59. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/add.py +0 -0
  60. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/autoremove.py +0 -0
  61. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/config.py +0 -0
  62. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/convert.py +0 -0
  63. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/decorators.py +0 -0
  64. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/download.py +0 -0
  65. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/facet.py +0 -0
  66. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/get.py +0 -0
  67. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/install.py +0 -0
  68. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/login.py +0 -0
  69. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/remove.py +0 -0
  70. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/retry.py +0 -0
  71. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/self.py +0 -0
  72. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/show.py +0 -0
  73. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/status.py +0 -0
  74. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/track.py +0 -0
  75. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/update.py +0 -0
  76. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/cli/utils.py +0 -0
  77. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/config.py +0 -0
  78. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/constants.py +0 -0
  79. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/database.py +0 -0
  80. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/download.py +0 -0
  81. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/esgpull.py +0 -0
  82. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/exceptions.py +0 -0
  83. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/fs.py +0 -0
  84. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/graph.py +0 -0
  85. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/install_config.py +0 -0
  86. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/README +0 -0
  87. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/env.py +0 -0
  88. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/script.py.mako +0 -0
  89. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.0_update_tables.py +0 -0
  90. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.1_update_tables.py +0 -0
  91. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.2_update_tables.py +0 -0
  92. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.3_update_tables.py +0 -0
  93. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.4_update_tables.py +0 -0
  94. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.5_update_tables.py +0 -0
  95. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.6_update_tables.py +0 -0
  96. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.7_update_tables.py +0 -0
  97. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.3.8_update_tables.py +0 -0
  98. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.4.0_update_tables.py +0 -0
  99. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.5.0_update_tables.py +0 -0
  100. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.5.1_update_tables.py +0 -0
  101. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.5.2_update_tables.py +0 -0
  102. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.5.3_update_tables.py +0 -0
  103. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.5.4_update_tables.py +0 -0
  104. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.5.5_update_tables.py +0 -0
  105. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.6.0_update_tables.py +0 -0
  106. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.6.1_update_tables.py +0 -0
  107. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.6.2_update_tables.py +0 -0
  108. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.6.3_update_tables.py +0 -0
  109. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.6.4_update_tables.py +0 -0
  110. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.6.5_update_tables.py +0 -0
  111. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.7.0_update_tables.py +0 -0
  112. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.7.1_update_tables.py +0 -0
  113. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.7.2_update_tables.py +0 -0
  114. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.7.3_update_tables.py +0 -0
  115. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.8.0_update_tables.py +0 -0
  116. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.9.0_update_tables.py +0 -0
  117. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/0.9.1_update_tables.py +0 -0
  118. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/14c72daea083_query_add_column_updated_at.py +0 -0
  119. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/c7c8541fa741_query_add_column_added_at.py +0 -0
  120. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py +0 -0
  121. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py +0 -0
  122. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/__init__.py +0 -0
  123. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/base.py +0 -0
  124. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/dataset.py +0 -0
  125. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/facet.py +0 -0
  126. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/file.py +0 -0
  127. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/options.py +0 -0
  128. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/query.py +0 -0
  129. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/selection.py +0 -0
  130. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/sql.py +0 -0
  131. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/synda_file.py +0 -0
  132. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/tag.py +0 -0
  133. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/models/utils.py +0 -0
  134. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/processor.py +0 -0
  135. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/py.typed +0 -0
  136. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/result.py +0 -0
  137. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/tui.py +0 -0
  138. {esgpull-0.9.1 → esgpull-0.9.2}/esgpull/version.py +0 -0
  139. {esgpull-0.9.1 → esgpull-0.9.2}/pdm.lock +0 -0
  140. {esgpull-0.9.1 → esgpull-0.9.2}/tests/__init__.py +0 -0
  141. {esgpull-0.9.1 → esgpull-0.9.2}/tests/assets/error_plugin.py +0 -0
  142. {esgpull-0.9.1 → esgpull-0.9.2}/tests/assets/incompatible_plugin.py +0 -0
  143. {esgpull-0.9.1 → esgpull-0.9.2}/tests/assets/priority_test_plugin.py +0 -0
  144. {esgpull-0.9.1 → esgpull-0.9.2}/tests/assets/sample_plugin.py +0 -0
  145. {esgpull-0.9.1 → esgpull-0.9.2}/tests/cli/__init__.py +0 -0
  146. {esgpull-0.9.1 → esgpull-0.9.2}/tests/cli/test_dataset.py +0 -0
  147. {esgpull-0.9.1 → esgpull-0.9.2}/tests/cli/test_parse.py +0 -0
  148. {esgpull-0.9.1 → esgpull-0.9.2}/tests/cli/test_plugin_cli.py +0 -0
  149. {esgpull-0.9.1 → esgpull-0.9.2}/tests/cli/test_remove.py +0 -0
  150. {esgpull-0.9.1 → esgpull-0.9.2}/tests/cli/test_show_dates.py +0 -0
  151. {esgpull-0.9.1 → esgpull-0.9.2}/tests/cli/test_update.py +0 -0
  152. {esgpull-0.9.1 → esgpull-0.9.2}/tests/conftest.py +0 -0
  153. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_auth.py +0 -0
  154. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_config.py +0 -0
  155. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_dataset.py +0 -0
  156. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_db.py +0 -0
  157. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_esgpull.py +0 -0
  158. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_fs.py +0 -0
  159. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_graph.py +0 -0
  160. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_plugin.py +0 -0
  161. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_processor.py +0 -0
  162. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_query.py +0 -0
  163. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_selection.py +0 -0
  164. {esgpull-0.9.1 → esgpull-0.9.2}/tests/test_synda.py +0 -0
  165. {esgpull-0.9.1 → esgpull-0.9.2}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esgpull
3
- Version: 0.9.1
3
+ Version: 0.9.2
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/
@@ -337,7 +337,7 @@ from datetime import datetime
337
337
  from pathlib import Path
338
338
  from logging import Logger
339
339
 
340
- from esgpull.models import File, Query
340
+ from esgpull.models import Dataset, File, Query
341
341
  from esgpull.plugin import Event, on
342
342
 
343
343
  # Specify version compatibility (optional)
@@ -7,6 +7,7 @@ from click.exceptions import Abort, Exit
7
7
 
8
8
  from esgpull.cli.decorators import args, groups, opts
9
9
  from esgpull.cli.utils import filter_keys, init_esgpull, parse_query, totable
10
+ from esgpull.context import IndexNode
10
11
  from esgpull.exceptions import PageIndexError
11
12
  from esgpull.graph import Graph
12
13
  from esgpull.models import Query
@@ -135,14 +136,26 @@ def search(
135
136
  esg.ui.raise_maybe_record(Exit(0))
136
137
  if facets_hints:
137
138
  not_distrib_query = query << Query(options=dict(distrib=False))
138
- facet_counts = esg.context.hints(
139
- not_distrib_query,
140
- file=file,
141
- facets=["*"],
142
- date_from=date_from,
143
- date_to=date_to,
144
- )
145
- esg.ui.print(list(facet_counts[0]), json=True)
139
+ index = IndexNode(esg.config.api.index_node)
140
+ if index.is_bridge():
141
+ first_file_result = esg.context.search_as_queries(
142
+ not_distrib_query,
143
+ file=True,
144
+ max_hits=1,
145
+ date_from=date_from,
146
+ date_to=date_to,
147
+ )
148
+ first_file = first_file_result[0].selection.asdict()
149
+ esg.ui.print(list(first_file), json=True)
150
+ else:
151
+ facet_counts = esg.context.hints(
152
+ not_distrib_query,
153
+ file=file,
154
+ facets=["*"],
155
+ date_from=date_from,
156
+ date_to=date_to,
157
+ )
158
+ esg.ui.print(list(facet_counts[0]), json=True)
146
159
  esg.ui.raise_maybe_record(Exit(0))
147
160
  if hints is not None:
148
161
  facet_counts = esg.context.hints(
@@ -7,6 +7,7 @@ from collections.abc import AsyncIterator, Callable, Coroutine, Sequence
7
7
  from dataclasses import dataclass, field
8
8
  from datetime import datetime
9
9
  from typing import Any, TypeAlias, TypeVar
10
+ from urllib.parse import urlparse
10
11
 
11
12
  if sys.version_info < (3, 11):
12
13
  from exceptiongroup import BaseExceptionGroup
@@ -18,7 +19,7 @@ from esgpull.config import Config
18
19
  from esgpull.exceptions import SolrUnstableQueryError
19
20
  from esgpull.models import DatasetRecord, File, Query
20
21
  from esgpull.tui import logger
21
- from esgpull.utils import format_date_iso, index2url, sync
22
+ from esgpull.utils import format_date_iso, sync
22
23
 
23
24
  # workaround for notebooks with running event loop
24
25
  if asyncio.get_event_loop().is_running():
@@ -39,6 +40,29 @@ DangerousFacets = {
39
40
  }
40
41
 
41
42
 
43
+ @dataclass
44
+ class IndexNode:
45
+ value: str
46
+
47
+ def is_bridge(self) -> bool:
48
+ return "esgf-1-5-bridge" in self.value
49
+
50
+ @property
51
+ def url(self) -> str:
52
+ parsed = urlparse(self.value)
53
+ result: str
54
+ match (parsed.scheme, parsed.netloc, parsed.path, self.is_bridge()):
55
+ case ("", "", path, True):
56
+ result = "https://" + parsed.path
57
+ case ("", "", path, False):
58
+ result = "https://" + parsed.path + "/esg-search/search"
59
+ case _:
60
+ result = self.value
61
+ if "." not in result:
62
+ raise ValueError(self.value)
63
+ return result
64
+
65
+
42
66
  @dataclass
43
67
  class Result:
44
68
  query: Query
@@ -70,12 +94,12 @@ class Result:
70
94
  "format": "application/solr+json",
71
95
  # "from": self.since,
72
96
  }
73
- if index_url is None:
74
- index_url = index2url(index_node)
75
- if fields_param is not None:
76
- params["fields"] = ",".join(fields_param)
77
- else:
78
- params["fields"] = "instance_id"
97
+ index = IndexNode(value=index_url or index_node)
98
+ if not index.is_bridge():
99
+ if fields_param is not None:
100
+ params["fields"] = ",".join(fields_param)
101
+ else:
102
+ params["fields"] = "instance_id"
79
103
  if date_from is not None:
80
104
  params["from"] = format_date_iso(date_from)
81
105
  if date_to is not None:
@@ -101,15 +125,20 @@ class Result:
101
125
  else:
102
126
  if len(values) > 1:
103
127
  value_term = f"({value_term})"
104
- solr_terms.append(f"{name}:{value_term}")
128
+ if name.startswith("!"):
129
+ solr_terms.append(f"(NOT {name[1:]}:{value_term})")
130
+ else:
131
+ solr_terms.append(f"{name}:{value_term}")
105
132
  if solr_terms:
106
133
  params["query"] = " AND ".join(solr_terms)
107
134
  for name, option in self.query.options.items(use_default=True):
108
135
  if option.is_bool():
109
136
  params[name] = option.name
137
+ if index.is_bridge():
138
+ _ = params.pop("retracted", None) # not supported in bridge API
110
139
  if params.get("distrib") == "true" and facets_star:
111
140
  raise SolrUnstableQueryError(pretty_repr(self.query))
112
- self.request = Request("GET", index_url, params=params)
141
+ self.request = Request("GET", index.url, params=params)
113
142
 
114
143
  def to(self, subtype: type[RT]) -> RT:
115
144
  result: RT = subtype(self.query, self.file)
@@ -0,0 +1,28 @@
1
+ """update tables
2
+
3
+ Revision ID: 0.9.2
4
+ Revises: 0.9.1
5
+ Create Date: 2025-09-04 16:56:29.263007
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '0.9.2'
14
+ down_revision = '0.9.1'
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 ###
@@ -151,7 +151,11 @@ class PluginConfig:
151
151
  enabled: set[str] = field(default_factory=set)
152
152
  disabled: set[str] = field(default_factory=set)
153
153
  plugins: dict[str, dict[str, Any]] = field(default_factory=dict)
154
- _raw: dict[str, dict[str, Any]] = field(default_factory=dict)
154
+ _raw: tomlkit.TOMLDocument = field(default_factory=tomlkit.TOMLDocument)
155
+
156
+ def __post_init__(self):
157
+ if "plugins" not in self._raw:
158
+ self._raw["plugins"] = {}
155
159
 
156
160
 
157
161
  class PluginManager:
@@ -199,12 +203,13 @@ class PluginManager:
199
203
 
200
204
  try:
201
205
  with open(self.config_path, "r") as f:
202
- raw = tomlkit.parse(f.read())
203
- self.config.enabled = set(raw.get("enabled", []))
204
- self.config.disabled = set(raw.get("disabled", []))
205
- self.config.plugins = raw.get("plugins", {})
206
- # Store the raw plugin configuration to preserve what's on disk
207
- self.config._raw = dict(raw.get("plugins", {}))
206
+ raw = tomlkit.load(f)
207
+ unwrap = raw.unwrap()
208
+ self.config.enabled = set(unwrap.get("enabled", []))
209
+ self.config.disabled = set(unwrap.get("disabled", []))
210
+ self.config.plugins = unwrap.get("plugins", {})
211
+ # Store the raw plugin configuration to preserve what's on disk
212
+ self.config._raw = raw
208
213
  except Exception as e:
209
214
  logger.error(f"Failed to load plugin config: {e}")
210
215
 
@@ -219,18 +224,16 @@ class PluginManager:
219
224
  return
220
225
 
221
226
  try:
222
- doc = tomlkit.document()
227
+ doc = self.config._raw
223
228
  doc["enabled"] = list(self.config.enabled)
224
229
  doc["disabled"] = list(self.config.disabled)
225
230
 
226
231
  # For plugins section, handle differently based on generate_full_config flag
227
232
  if generate_full_config:
228
233
  doc["plugins"] = self.config.plugins
229
- else:
230
- doc["plugins"] = self.config._raw
231
234
 
232
235
  with open(self.config_path, "w") as f:
233
- f.write(tomlkit.dumps(doc))
236
+ tomlkit.dump(doc, f)
234
237
  except Exception as e:
235
238
  logger.error(f"Failed to save plugin config: {e}")
236
239
 
@@ -464,8 +467,8 @@ class PluginManager:
464
467
  # Make sure plugin exists in both plugins and _raw dicts
465
468
  if plugin_name not in self.config.plugins:
466
469
  self.config.plugins[plugin_name] = {}
467
- if plugin_name not in self.config._raw:
468
- self.config._raw[plugin_name] = {}
470
+ if plugin_name not in self.config._raw["plugins"]:
471
+ self.config._raw["plugins"][plugin_name] = {}
469
472
 
470
473
  # Update the value in both places
471
474
  if key in self.config.plugins[plugin_name]:
@@ -473,7 +476,7 @@ class PluginManager:
473
476
  new_value = cast_value(old_value, value, key)
474
477
  self.config.plugins[plugin_name][key] = new_value
475
478
  # Also update in _raw to keep in sync
476
- self.config._raw[plugin_name][key] = new_value
479
+ self.config._raw["plugins"][plugin_name][key] = new_value
477
480
  else:
478
481
  raise KeyError(key, self.config.plugins[plugin_name])
479
482
 
@@ -497,8 +500,8 @@ class PluginManager:
497
500
  # Make sure the plugin exists in both configs
498
501
  if plugin_name not in self.config.plugins:
499
502
  self.config.plugins[plugin_name] = {}
500
- if plugin_name not in self.config._raw:
501
- self.config._raw[plugin_name] = {}
503
+ if plugin_name not in self.config._raw["plugins"]:
504
+ self.config._raw["plugins"][plugin_name] = {}
502
505
 
503
506
  # Remove the key from both configs
504
507
  if key in self.config.plugins[plugin_name]:
@@ -508,10 +511,10 @@ class PluginManager:
508
511
 
509
512
  # Also remove from _raw if it exists
510
513
  if (
511
- plugin_name in self.config._raw
512
- and key in self.config._raw[plugin_name]
514
+ plugin_name in self.config._raw["plugins"]
515
+ and key in self.config._raw["plugins"][plugin_name]
513
516
  ):
514
- self.config._raw[plugin_name].pop(key)
517
+ self.config._raw["plugins"][plugin_name].pop(key)
515
518
 
516
519
  self.write_config()
517
520
 
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  import datetime
3
3
  from typing import Callable, Coroutine, TypeVar
4
- from urllib.parse import urlparse
5
4
 
6
5
  from rich.filesize import _to_str
7
6
 
@@ -52,19 +51,3 @@ def format_date_iso(
52
51
  date: str | datetime.datetime, fmt: str = "%Y-%m-%d"
53
52
  ) -> str:
54
53
  return parse_date(date, fmt).replace(microsecond=0).isoformat() + "Z"
55
-
56
-
57
- def url2index(url: str) -> str:
58
- parsed = urlparse(url)
59
- if parsed.netloc == "":
60
- return parsed.path
61
- else:
62
- return parsed.netloc
63
-
64
-
65
- def index2url(index: str) -> str:
66
- url = "https://" + url2index(index)
67
- if "esgf-1-5-bridge" in index:
68
- return url
69
- else:
70
- return url + "/esg-search/search"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "esgpull"
7
- version = "0.9.1"
7
+ version = "0.9.2"
8
8
  classifiers = [
9
9
  "License :: OSI Approved :: BSD License",
10
10
  "Programming Language :: Python :: 3",
@@ -14,7 +14,7 @@ aiofiles==24.1.0
14
14
  # via esgpull
15
15
  aiostream==0.7.0
16
16
  # via esgpull
17
- alembic==1.16.4
17
+ alembic==1.16.5
18
18
  # via esgpull
19
19
  anyio==4.10.0
20
20
  # via httpx
@@ -27,7 +27,7 @@ babel==2.17.0
27
27
  # via mkdocs-material
28
28
  backrefs==5.9
29
29
  # via mkdocs-material
30
- cattrs==25.1.1
30
+ cattrs==25.2.0
31
31
  # via esgpull
32
32
  certifi==2025.8.3
33
33
  # via httpcore
@@ -35,21 +35,22 @@ certifi==2025.8.3
35
35
  # via requests
36
36
  cffi==1.17.1
37
37
  # via cryptography
38
- charset-normalizer==3.4.2
38
+ charset-normalizer==3.4.3
39
39
  # via requests
40
40
  click==8.2.1
41
41
  # via click-params
42
42
  # via esgpull
43
43
  # via mkdocs
44
+ # via mkdocs-material
44
45
  click-params==0.5.0
45
46
  # via esgpull
46
47
  colorama==0.4.6
47
48
  # via mkdocs-material
48
49
  comm==0.2.3
49
50
  # via ipykernel
50
- coverage==7.10.2
51
+ coverage==7.10.6
51
52
  # via pytest-cov
52
- cryptography==45.0.6
53
+ cryptography==45.0.7
53
54
  # via pyopenssl
54
55
  debugpy==1.8.16
55
56
  # via ipykernel
@@ -65,9 +66,9 @@ exceptiongroup==1.3.0
65
66
  # via pytest
66
67
  execnet==2.1.1
67
68
  # via pytest-xdist
68
- executing==2.2.0
69
+ executing==2.2.1
69
70
  # via stack-data
70
- filelock==3.18.0
71
+ filelock==3.19.1
71
72
  # via pytest-mypy
72
73
  ghp-import==2.1.0
73
74
  # via mkdocs
@@ -111,7 +112,7 @@ markdown==3.8.2
111
112
  # via mkdocs
112
113
  # via mkdocs-material
113
114
  # via pymdown-extensions
114
- markdown-it-py==3.0.0
115
+ markdown-it-py==4.0.0
115
116
  # via rich
116
117
  markupsafe==3.0.2
117
118
  # via jinja2
@@ -129,7 +130,7 @@ mkdocs==1.6.1
129
130
  # via mkdocs-material
130
131
  mkdocs-get-deps==0.2.0
131
132
  # via mkdocs
132
- mkdocs-material==9.6.16
133
+ mkdocs-material==9.6.18
133
134
  mkdocs-material-extensions==1.3.1
134
135
  # via mkdocs-material
135
136
  myproxyclient==2.1.1
@@ -141,7 +142,7 @@ mypy-extensions==1.1.0
141
142
  nest-asyncio==1.6.0
142
143
  # via esgpull
143
144
  # via ipykernel
144
- orjson==3.11.1
145
+ orjson==3.11.3
145
146
  packaging==25.0
146
147
  # via esgpull
147
148
  # via ipykernel
@@ -149,21 +150,21 @@ packaging==25.0
149
150
  # via pytest
150
151
  paginate==0.5.7
151
152
  # via mkdocs-material
152
- parso==0.8.4
153
+ parso==0.8.5
153
154
  # via jedi
154
155
  pathspec==0.12.1
155
156
  # via mkdocs
156
157
  # via mypy
157
158
  pexpect==4.9.0
158
159
  # via ipython
159
- platformdirs==4.3.8
160
+ platformdirs==4.4.0
160
161
  # via esgpull
161
162
  # via jupyter-core
162
163
  # via mkdocs-get-deps
163
164
  pluggy==1.6.0
164
165
  # via pytest
165
166
  # via pytest-cov
166
- prompt-toolkit==3.0.51
167
+ prompt-toolkit==3.0.52
167
168
  # via ipython
168
169
  # via jupyter-console
169
170
  psutil==7.0.0
@@ -187,7 +188,7 @@ pyopenssl==25.1.0
187
188
  # via myproxyclient
188
189
  pyparsing==3.2.3
189
190
  # via esgpull
190
- pytest==8.4.1
191
+ pytest==8.4.2
191
192
  # via pytest-cov
192
193
  # via pytest-mypy
193
194
  # via pytest-xdist
@@ -205,11 +206,11 @@ pyyaml==6.0.2
205
206
  # via pyyaml-env-tag
206
207
  pyyaml-env-tag==1.1
207
208
  # via mkdocs
208
- pyzmq==27.0.1
209
+ pyzmq==27.0.2
209
210
  # via ipykernel
210
211
  # via jupyter-client
211
212
  # via jupyter-console
212
- requests==2.32.4
213
+ requests==2.32.5
213
214
  # via mkdocs-material
214
215
  rich==14.1.0
215
216
  # via esgpull
@@ -220,7 +221,7 @@ six==1.17.0
220
221
  # via python-dateutil
221
222
  sniffio==1.3.1
222
223
  # via anyio
223
- sqlalchemy==2.0.42
224
+ sqlalchemy==2.0.43
224
225
  # via alembic
225
226
  # via esgpull
226
227
  stack-data==0.6.3
@@ -233,7 +234,7 @@ tomli==2.2.1
233
234
  # via pytest
234
235
  tomlkit==0.13.3
235
236
  # via esgpull
236
- tornado==6.5.1
237
+ tornado==6.5.2
237
238
  # via ipykernel
238
239
  # via jupyter-client
239
240
  traitlets==5.14.3
@@ -243,9 +244,9 @@ traitlets==5.14.3
243
244
  # via jupyter-console
244
245
  # via jupyter-core
245
246
  # via matplotlib-inline
246
- types-aiofiles==24.1.0.20250801
247
- types-pyyaml==6.0.12.20250516
248
- typing-extensions==4.14.1
247
+ types-aiofiles==24.1.0.20250822
248
+ types-pyyaml==6.0.12.20250822
249
+ typing-extensions==4.15.0
249
250
  # via aiostream
250
251
  # via alembic
251
252
  # via anyio
@@ -263,5 +264,5 @@ watchdog==6.0.0
263
264
  # via mkdocs
264
265
  wcwidth==0.2.13
265
266
  # via prompt-toolkit
266
- wrapt==1.17.2
267
+ wrapt==1.17.3
267
268
  # via deprecated
@@ -14,14 +14,14 @@ aiofiles==24.1.0
14
14
  # via esgpull
15
15
  aiostream==0.7.0
16
16
  # via esgpull
17
- alembic==1.16.4
17
+ alembic==1.16.5
18
18
  # via esgpull
19
19
  anyio==4.10.0
20
20
  # via httpx
21
21
  attrs==25.3.0
22
22
  # via cattrs
23
23
  # via esgpull
24
- cattrs==25.1.1
24
+ cattrs==25.2.0
25
25
  # via esgpull
26
26
  certifi==2025.8.3
27
27
  # via httpcore
@@ -33,7 +33,7 @@ click==8.2.1
33
33
  # via esgpull
34
34
  click-params==0.5.0
35
35
  # via esgpull
36
- cryptography==45.0.6
36
+ cryptography==45.0.7
37
37
  # via pyopenssl
38
38
  deprecated==1.2.18
39
39
  # via click-params
@@ -53,7 +53,7 @@ idna==3.10
53
53
  # via httpx
54
54
  mako==1.3.10
55
55
  # via alembic
56
- markdown-it-py==3.0.0
56
+ markdown-it-py==4.0.0
57
57
  # via rich
58
58
  markupsafe==3.0.2
59
59
  # via mako
@@ -65,7 +65,7 @@ nest-asyncio==1.6.0
65
65
  # via esgpull
66
66
  packaging==25.0
67
67
  # via esgpull
68
- platformdirs==4.3.8
68
+ platformdirs==4.4.0
69
69
  # via esgpull
70
70
  pycparser==2.22
71
71
  # via cffi
@@ -86,14 +86,14 @@ six==1.17.0
86
86
  # via myproxyclient
87
87
  sniffio==1.3.1
88
88
  # via anyio
89
- sqlalchemy==2.0.42
89
+ sqlalchemy==2.0.43
90
90
  # via alembic
91
91
  # via esgpull
92
92
  tomli==2.2.1
93
93
  # via alembic
94
94
  tomlkit==0.13.3
95
95
  # via esgpull
96
- typing-extensions==4.14.1
96
+ typing-extensions==4.15.0
97
97
  # via aiostream
98
98
  # via alembic
99
99
  # via anyio
@@ -103,5 +103,5 @@ typing-extensions==4.14.1
103
103
  # via sqlalchemy
104
104
  validators==0.22.0
105
105
  # via click-params
106
- wrapt==1.17.2
106
+ wrapt==1.17.3
107
107
  # via deprecated
@@ -3,7 +3,7 @@ from time import perf_counter
3
3
 
4
4
  import pytest
5
5
 
6
- from esgpull.context import Context
6
+ from esgpull.context import Context, IndexNode
7
7
  from esgpull.models import Query
8
8
 
9
9
 
@@ -104,6 +104,10 @@ class Timer:
104
104
 
105
105
 
106
106
  @pytest.mark.slow
107
+ @pytest.mark.xfail(
108
+ raises=ValueError,
109
+ reason="ESGF bridge API gives index_node values that are not valid URLs",
110
+ )
107
111
  def test_search_distributed(ctx):
108
112
  query = Query()
109
113
  # ctx.config.api.http_timeout = 60
@@ -138,7 +142,12 @@ def test_search_distributed(ctx):
138
142
 
139
143
 
140
144
  def test_ipsl_hits_between_1_and_2_million(ctx, cmip6_ipsl):
141
- assert 1_000_000 < ctx.hits(cmip6_ipsl, file=False)[0] < 2_000_000
145
+ hits = ctx.hits(
146
+ cmip6_ipsl,
147
+ file=False,
148
+ index_node="esgf-node.ipsl.upmc.fr",
149
+ )
150
+ assert 1_000_000 < hits[0] < 2_000_000
142
151
 
143
152
 
144
153
  def test_more_files_than_datasets(ctx, query):
@@ -168,3 +177,35 @@ def test_ignore_facet_hits(ctx):
168
177
  hits_not_ipsl = ctx.hits(query_not_ipsl, file=False)[0]
169
178
  assert all(hits > 0 for hits in [hits_all, hits_ipsl, hits_not_ipsl])
170
179
  assert hits_all == hits_ipsl + hits_not_ipsl
180
+
181
+
182
+ @pytest.mark.parametrize(
183
+ "index,url,is_bridge",
184
+ [
185
+ (
186
+ "esgf-node.ipsl.upmc.fr",
187
+ "https://esgf-node.ipsl.upmc.fr/esg-search/search",
188
+ False,
189
+ ),
190
+ (
191
+ "https://esgf-node.ipsl.upmc.fr/esg-search/search",
192
+ "https://esgf-node.ipsl.upmc.fr/esg-search/search",
193
+ False,
194
+ ),
195
+ (
196
+ "esgf-node.ornl.gov/esgf-1-5-bridge",
197
+ "https://esgf-node.ornl.gov/esgf-1-5-bridge",
198
+ True,
199
+ ),
200
+ (
201
+ "https://esgf-node.ornl.gov/esgf-1-5-bridge",
202
+ "https://esgf-node.ornl.gov/esgf-1-5-bridge",
203
+ True,
204
+ ),
205
+ ],
206
+ )
207
+ def test_index2url(index: str, url: str, is_bridge: bool):
208
+ for value in (index, url):
209
+ index_node = IndexNode(value=value)
210
+ assert index_node.url == url
211
+ assert index_node.is_bridge() == is_bridge
@@ -2,10 +2,7 @@ from datetime import datetime
2
2
 
3
3
  import pytest
4
4
 
5
- from esgpull.utils import format_date, format_date_iso, index2url, parse_date
6
-
7
- ESGF_INDEX = "esgf-node.ipsl.upmc.fr"
8
- ESGF_URL = "https://esgf-node.ipsl.upmc.fr/esg-search/search"
5
+ from esgpull.utils import format_date, format_date_iso, parse_date
9
6
 
10
7
 
11
8
  def test_parse_date():
@@ -40,8 +37,3 @@ def test_format_date_iso():
40
37
  format_date_iso("20220101")
41
38
  with pytest.raises(ValueError):
42
39
  format_date_iso(20220101)
43
-
44
-
45
- def test_index2url():
46
- assert index2url(ESGF_INDEX) == ESGF_URL
47
- assert index2url(ESGF_URL) == ESGF_URL
@@ -370,7 +370,7 @@ wheels = [
370
370
 
371
371
  [[package]]
372
372
  name = "esgpull"
373
- version = "0.9.1"
373
+ version = "0.9.2"
374
374
  source = { editable = "." }
375
375
  dependencies = [
376
376
  { name = "aiofiles" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes