esgpull 0.9.3__py3-none-any.whl → 0.9.5__py3-none-any.whl

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/context.py CHANGED
@@ -134,18 +134,46 @@ class Result:
134
134
  solr_terms: list[str] = []
135
135
  for name, values in self.query.selection.items():
136
136
  if index.is_bridge():
137
- value_term = " ".join(quote_str(v) for v in values)
137
+ if name == "query":
138
+ solr_terms.append(" ".join(values))
139
+ elif name.startswith("!"):
140
+ value_term = " ".join(quote_str(v) for v in values)
141
+ if len(values) > 1:
142
+ value_term = f"({value_term})"
143
+ solr_terms.append(f"NOT ({name[1:]}:{value_term})")
144
+ else:
145
+ has_wildcard = any("*" in v for v in values)
146
+ no_wildcard = any("*" not in v for v in values)
147
+
148
+ if has_wildcard and no_wildcard:
149
+ logger.warning(
150
+ (
151
+ f"Facet {name} has mixed wildcard/non-wildcard values. "
152
+ "Non-wildcard values may match partially."
153
+ )
154
+ )
155
+ value_term = " ".join(quote_str(v) for v in values)
156
+ if len(values) > 1:
157
+ value_term = f"({value_term})"
158
+ solr_terms.append(f"{name}:{value_term}")
159
+ elif has_wildcard:
160
+ value_term = " ".join(quote_str(v) for v in values)
161
+ if len(values) > 1:
162
+ value_term = f"({value_term})"
163
+ solr_terms.append(f"{name}:{value_term}")
164
+ else:
165
+ params[name] = ",".join(values)
138
166
  else:
139
167
  value_term = " ".join(values)
140
- if name == "query": # freetext case
141
- solr_terms.append(value_term)
142
- else:
143
- if len(values) > 1:
144
- value_term = f"({value_term})"
145
- if name.startswith("!"):
146
- solr_terms.append(f"NOT ({name[1:]}:{value_term})")
168
+ if name == "query":
169
+ solr_terms.append(value_term)
147
170
  else:
148
- solr_terms.append(f"{name}:{value_term}")
171
+ if len(values) > 1:
172
+ value_term = f"({value_term})"
173
+ if name.startswith("!"):
174
+ solr_terms.append(f"NOT ({name[1:]}:{value_term})")
175
+ else:
176
+ solr_terms.append(f"{name}:{value_term}")
149
177
  if solr_terms:
150
178
  params["query"] = " AND ".join(solr_terms)
151
179
  for name, option in self.query.options.items(use_default=True):
@@ -562,6 +590,7 @@ class Context:
562
590
  ) -> list[File]:
563
591
  files: list[File] = []
564
592
  shas: set[str] = set()
593
+ file_ids: set[str] = set()
565
594
  async for result in self._fetch(*results):
566
595
  files_result = result.to(ResultFiles)
567
596
  files_result.process()
@@ -569,9 +598,13 @@ class Context:
569
598
  for file in files_result.data:
570
599
  if not keep_duplicates and file.sha in shas:
571
600
  logger.debug(f"Duplicate file {file.file_id}")
572
- else:
573
- files.append(file)
574
- shas.add(file.sha)
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)
575
608
  return files
576
609
 
577
610
  async def _search_as_queries(
esgpull/esgpull.py CHANGED
@@ -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("", total=queue_size)
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.4
4
+ Revises: 0.9.3
5
+ Create Date: 2026-01-14 10:49:04.536961
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '0.9.4'
14
+ down_revision = '0.9.3'
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 ###
@@ -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 ###
esgpull/tui.py CHANGED
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esgpull
3
- Version: 0.9.3
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>=2.0.0b2
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
- [![Rye](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/rye/main/artwork/badge.json)](https://rye.astral.sh)
40
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](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,10 +1,10 @@
1
1
  esgpull/__init__.py,sha256=XItFDIMNmFUNNcKtUgXdfmGwUIWt4AAv0a4mZkfj5P8,240
2
2
  esgpull/config.py,sha256=APA3IqYyz_TY2FAm_TNSLsFIkOxmdGnbFLnd2H2YXIs,12042
3
3
  esgpull/constants.py,sha256=VlgRJDth5aDjyTopJ2W2JU83353XcRf18RIFAZt7yck,1263
4
- esgpull/context.py,sha256=JEnIQGF1owl56n0azF8K3GNIU8TgeMu31NSfTqVAGQg,25123
4
+ esgpull/context.py,sha256=nF0ihtfLRw1mm6gIZHYLO_vK-Ga3vgr1I4Qy42Wi2Yo,26819
5
5
  esgpull/database.py,sha256=1wGeNbJp0gOLo5Q1N53JfiwVbZ8nfvZVkKlvEaJwOpU,6716
6
6
  esgpull/download.py,sha256=aR2c_SOuZtgX7tI2a9_N4Mn86ABq1k7Mxq_BdojFrP4,5600
7
- esgpull/esgpull.py,sha256=mqvrXnyCzTSU3j_u5pYidpLs2j86TEFDPX6MhCnNfMs,19458
7
+ esgpull/esgpull.py,sha256=2rBUuKuTBau8fpOA0mRwsFvmVrFAjXX42KKIyfMvpsM,19631
8
8
  esgpull/exceptions.py,sha256=wgLyhyIITdusNucPjnnURJX1Jxv1VVIr9PzJV_77qhg,3275
9
9
  esgpull/fs.py,sha256=sc7Af2E3yh3V9KVuSPSXFBuFtlQ3L99UZmS1ZJuiBeM,7280
10
10
  esgpull/graph.py,sha256=Yl2VuF8PNn0R5xRyEK58Q1Xlx8B1PfhbTwt1JftFDro,15929
@@ -13,7 +13,7 @@ esgpull/plugin.py,sha256=t6iejis65UREQ71gnj1-gVFhlrGt1z1n3SpJ6KiP57E,18904
13
13
  esgpull/processor.py,sha256=ubhlKMMOY_ieZEcN9zcDPlmZxQDXqb2QVmeCwWo7zOE,5376
14
14
  esgpull/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  esgpull/result.py,sha256=f64l9gPFpFWgctmHVYrNWJvuXXB1sxpxXzJEIssLXxc,1020
16
- esgpull/tui.py,sha256=XJs8AUm28YLWl3ephpxY8jHFvG7HrIr08u3RdYvABdE,11338
16
+ esgpull/tui.py,sha256=7V3Qam7jOdB2CEI-j-ioMbWb28yvFEzqwexUGhZgolo,11639
17
17
  esgpull/utils.py,sha256=pLMQfY0p2oNIFyCZhHBD74BOAJtmt9QV6dTJ79VsfF8,1213
18
18
  esgpull/version.py,sha256=IHT4mKrIr8eV-C3HtmIVD85iGVH25n2ohoff31kaJ1A,93
19
19
  esgpull/cli/__init__.py,sha256=W0q_eyhvtPgyX5cXKSDDLUUrskGLzgNoJ9VCY9fJ6pY,1701
@@ -71,6 +71,8 @@ esgpull/migrations/versions/0.9.0_update_tables.py,sha256=nXfPiyuseD5BXvu59zkeST
71
71
  esgpull/migrations/versions/0.9.1_update_tables.py,sha256=ITewU2qgdCwhorsl2d_t7ENt_6KecG3z5xfRr_LwrtY,541
72
72
  esgpull/migrations/versions/0.9.2_update_tables.py,sha256=aux--HPA_jUtvk7Zb2ZRXRhnRLT-whqZ0lLWt8Apyac,541
73
73
  esgpull/migrations/versions/0.9.3_update_tables.py,sha256=-k1CneOa7OTO6xPZ8uR0ccy-4B8TU3kM12-oYKFEOlA,541
74
+ esgpull/migrations/versions/0.9.4_update_tables.py,sha256=tvvXvov7ggXO9A8B5399bixCuM9fSni1SHkJXa5f5II,541
75
+ esgpull/migrations/versions/0.9.5_update_tables.py,sha256=pmLJeJus9-Dmj0geKCpHqrFxlEMLMhPZ_zB7oBLm5rQ,541
74
76
  esgpull/migrations/versions/14c72daea083_query_add_column_updated_at.py,sha256=MKqz0tfwGwRkgP4QDd-cpUmXCVr4tM_wlC2BfxqJ1_w,1031
75
77
  esgpull/migrations/versions/c7c8541fa741_query_add_column_added_at.py,sha256=Al_o7fDmoRqc9vBCQgtgrNbSPIOBxdMZ5T-ztakqVeY,1033
76
78
  esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py,sha256=0vJvttugWmgKns4g-K4i3EU6eid2Z_K2e3H6Ktevf7c,860
@@ -87,8 +89,8 @@ esgpull/models/sql.py,sha256=K8Nre5HKFPjkRzUUW6p6Qk7aG8upbw8C3pnmCFlg7d8,8942
87
89
  esgpull/models/synda_file.py,sha256=6o5unPhzVJGnbpA2MxcS0r-hrBwocHYVnLrqjSGtmuk,2387
88
90
  esgpull/models/tag.py,sha256=5CQDB9rAeCqog63ec9LPFN46HOFNkHPy-maY4gkBQ3E,461
89
91
  esgpull/models/utils.py,sha256=exwlIlIKYjhhfUE82w1kU_HeSQOSY97PTvpkhW0udMA,1631
90
- esgpull-0.9.3.dist-info/METADATA,sha256=0wlDuo4TCJskBSO9v4FRuoLTAm7531jOPJDQAQJWBP4,3818
91
- esgpull-0.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- esgpull-0.9.3.dist-info/entry_points.txt,sha256=vyh7HvFrCp4iyMrTkDoSF3weaYrlNj2OJe0Fq5q4QB4,45
93
- esgpull-0.9.3.dist-info/licenses/LICENSE,sha256=lUqGPGWDHHxjkUDuYgjLLY2XQXXn_EHU7fnrQWHGugc,1540
94
- esgpull-0.9.3.dist-info/RECORD,,
92
+ esgpull-0.9.5.dist-info/METADATA,sha256=KHIghvsCaNoJXZNOj6-MXNDZeQiGnLfI_-WesaZ8I7c,3833
93
+ esgpull-0.9.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
94
+ esgpull-0.9.5.dist-info/entry_points.txt,sha256=vyh7HvFrCp4iyMrTkDoSF3weaYrlNj2OJe0Fq5q4QB4,45
95
+ esgpull-0.9.5.dist-info/licenses/LICENSE,sha256=lUqGPGWDHHxjkUDuYgjLLY2XQXXn_EHU7fnrQWHGugc,1540
96
+ esgpull-0.9.5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any