esgpull 0.6.3__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.
Files changed (80) hide show
  1. esgpull/__init__.py +12 -0
  2. esgpull/auth.py +181 -0
  3. esgpull/cli/__init__.py +73 -0
  4. esgpull/cli/add.py +103 -0
  5. esgpull/cli/autoremove.py +38 -0
  6. esgpull/cli/config.py +116 -0
  7. esgpull/cli/convert.py +285 -0
  8. esgpull/cli/decorators.py +342 -0
  9. esgpull/cli/download.py +74 -0
  10. esgpull/cli/facet.py +23 -0
  11. esgpull/cli/get.py +28 -0
  12. esgpull/cli/install.py +85 -0
  13. esgpull/cli/link.py +105 -0
  14. esgpull/cli/login.py +56 -0
  15. esgpull/cli/remove.py +73 -0
  16. esgpull/cli/retry.py +43 -0
  17. esgpull/cli/search.py +201 -0
  18. esgpull/cli/self.py +238 -0
  19. esgpull/cli/show.py +66 -0
  20. esgpull/cli/status.py +67 -0
  21. esgpull/cli/track.py +87 -0
  22. esgpull/cli/update.py +184 -0
  23. esgpull/cli/utils.py +247 -0
  24. esgpull/config.py +410 -0
  25. esgpull/constants.py +56 -0
  26. esgpull/context.py +724 -0
  27. esgpull/database.py +161 -0
  28. esgpull/download.py +162 -0
  29. esgpull/esgpull.py +447 -0
  30. esgpull/exceptions.py +167 -0
  31. esgpull/fs.py +253 -0
  32. esgpull/graph.py +460 -0
  33. esgpull/install_config.py +185 -0
  34. esgpull/migrations/README +1 -0
  35. esgpull/migrations/env.py +82 -0
  36. esgpull/migrations/script.py.mako +24 -0
  37. esgpull/migrations/versions/0.3.0_update_tables.py +170 -0
  38. esgpull/migrations/versions/0.3.1_update_tables.py +25 -0
  39. esgpull/migrations/versions/0.3.2_update_tables.py +26 -0
  40. esgpull/migrations/versions/0.3.3_update_tables.py +25 -0
  41. esgpull/migrations/versions/0.3.4_update_tables.py +25 -0
  42. esgpull/migrations/versions/0.3.5_update_tables.py +25 -0
  43. esgpull/migrations/versions/0.3.6_update_tables.py +26 -0
  44. esgpull/migrations/versions/0.3.7_update_tables.py +26 -0
  45. esgpull/migrations/versions/0.3.8_update_tables.py +26 -0
  46. esgpull/migrations/versions/0.4.0_update_tables.py +25 -0
  47. esgpull/migrations/versions/0.5.0_update_tables.py +26 -0
  48. esgpull/migrations/versions/0.5.1_update_tables.py +26 -0
  49. esgpull/migrations/versions/0.5.2_update_tables.py +25 -0
  50. esgpull/migrations/versions/0.5.3_update_tables.py +26 -0
  51. esgpull/migrations/versions/0.5.4_update_tables.py +25 -0
  52. esgpull/migrations/versions/0.5.5_update_tables.py +25 -0
  53. esgpull/migrations/versions/0.6.0_update_tables.py +25 -0
  54. esgpull/migrations/versions/0.6.1_update_tables.py +25 -0
  55. esgpull/migrations/versions/0.6.2_update_tables.py +25 -0
  56. esgpull/migrations/versions/0.6.3_update_tables.py +25 -0
  57. esgpull/models/__init__.py +31 -0
  58. esgpull/models/base.py +50 -0
  59. esgpull/models/dataset.py +34 -0
  60. esgpull/models/facet.py +18 -0
  61. esgpull/models/file.py +65 -0
  62. esgpull/models/options.py +164 -0
  63. esgpull/models/query.py +481 -0
  64. esgpull/models/selection.py +201 -0
  65. esgpull/models/sql.py +258 -0
  66. esgpull/models/synda_file.py +85 -0
  67. esgpull/models/tag.py +19 -0
  68. esgpull/models/utils.py +54 -0
  69. esgpull/presets.py +13 -0
  70. esgpull/processor.py +172 -0
  71. esgpull/py.typed +0 -0
  72. esgpull/result.py +53 -0
  73. esgpull/tui.py +346 -0
  74. esgpull/utils.py +54 -0
  75. esgpull/version.py +1 -0
  76. esgpull-0.6.3.dist-info/METADATA +110 -0
  77. esgpull-0.6.3.dist-info/RECORD +80 -0
  78. esgpull-0.6.3.dist-info/WHEEL +4 -0
  79. esgpull-0.6.3.dist-info/entry_points.txt +3 -0
  80. esgpull-0.6.3.dist-info/licenses/LICENSE +28 -0
esgpull/cli/facet.py ADDED
@@ -0,0 +1,23 @@
1
+ import click
2
+ from click.exceptions import Abort
3
+
4
+ from esgpull.cli.decorators import args, opts
5
+ from esgpull.cli.utils import init_esgpull
6
+ from esgpull.models import sql
7
+ from esgpull.tui import Verbosity
8
+
9
+
10
+ @click.command()
11
+ @args.key
12
+ @opts.verbosity
13
+ def facet(
14
+ key: str | None,
15
+ verbosity: Verbosity,
16
+ ):
17
+ esg = init_esgpull(verbosity)
18
+ with esg.ui.logging("facet", onraise=Abort):
19
+ if key is None:
20
+ results = esg.db.scalars(sql.facet.names())
21
+ else:
22
+ results = esg.db.scalars(sql.facet.values(key))
23
+ esg.ui.print(sorted(results))
esgpull/cli/get.py ADDED
@@ -0,0 +1,28 @@
1
+ import click
2
+ import rich
3
+ from click_params import PUBLIC_URL
4
+
5
+ from esgpull.context import Context
6
+
7
+
8
+ @click.command()
9
+ @click.option("--metadata", "-m", is_flag=True, default=False)
10
+ @click.argument("url", nargs=1, type=PUBLIC_URL)
11
+ def get(
12
+ url: str,
13
+ metadata: bool, # display file metadata, no download
14
+ ):
15
+ """
16
+ Steps:
17
+ 1. validate url (version+filename)
18
+ 2. create Context
19
+ 3. fetch File from Context
20
+ 4. search database (if exist -> [Y/N])
21
+ 5. check file exists in output directory (if exist -> [Y/N]) -> rename
22
+ 6. download
23
+ """
24
+ rich.print(url)
25
+ ctx = Context()
26
+ print(ctx)
27
+
28
+ raise NotImplementedError()
esgpull/cli/install.py ADDED
@@ -0,0 +1,85 @@
1
+ # from __future__ import annotations
2
+
3
+ # import click
4
+ # from click.exceptions import Abort, Exit
5
+
6
+ # from esgpull import Esgpull
7
+ # from esgpull.cli.decorators import args, opts
8
+ # from esgpull.cli.utils import load_facets
9
+ # from esgpull.db.models import File
10
+ # from esgpull.tui import Verbosity
11
+ # from esgpull.utils import format_size
12
+
13
+
14
+ # @click.command()
15
+ # @opts.distrib
16
+ # @opts.dry_run
17
+ # @opts.force
18
+ # @opts.replica
19
+ # @opts.since
20
+ # @opts.selection_file
21
+ # @opts.verbosity
22
+ # @args.facets
23
+ # def install(
24
+ # facets: list[str],
25
+ # distrib: bool,
26
+ # dry_run: bool,
27
+ # force: bool,
28
+ # replica: bool | None,
29
+ # selection_file: str | None,
30
+ # since: str | None,
31
+ # verbosity: Verbosity,
32
+ # ) -> None:
33
+ # esg = Esgpull(verbosity=verbosity)
34
+ # with (
35
+ # esg.context(
36
+ # distrib=distrib,
37
+ # latest=True,
38
+ # since=since,
39
+ # replica=replica,
40
+ # ) as ctx,
41
+ # esg.ui.logging("install", onraise=Abort),
42
+ # ):
43
+ # load_facets(ctx.query, facets, selection_file)
44
+ # if not ctx.query.dump():
45
+ # raise click.UsageError("No search terms provided.")
46
+ # hits = ctx.file_hits
47
+ # nb_files = sum(hits)
48
+ # esg.ui.print(f"Found {nb_files} files.")
49
+ # if nb_files > 500 and distrib:
50
+ # # Enable better distrib
51
+ # ctx.index_nodes = esg.fetch_index_nodes()
52
+ # if dry_run:
53
+ # queries = ctx._build_queries_search(
54
+ # hits, file=True, max_results=nb_files, offset=0
55
+ # )
56
+ # esg.ui.print(queries)
57
+ # raise Exit(0)
58
+ # if not force and nb_files > 5000:
59
+ # nb_req = nb_files // esg.config.api.page_limit
60
+ # message = f"{nb_req} requests will be send to ESGF. Continue?"
61
+ # if not esg.ui.ask(message, default=True):
62
+ # raise Abort
63
+ # results = ctx.search(
64
+ # file=True,
65
+ # max_results=None,
66
+ # offset=0,
67
+ # hits=hits,
68
+ # )
69
+ # files = [File.from_dict(result) for result in results]
70
+ # total_size = sum([file.size for file in files])
71
+ # esg.ui.print(f"Total size: {format_size(total_size)}")
72
+ # if not force:
73
+ # if not esg.ui.ask("Continue?", default=True):
74
+ # raise Abort
75
+ # to_download, already_on_disk = esg.install(*files)
76
+ # if to_download:
77
+ # nb = len(to_download)
78
+ # s = "s" if nb > 1 else ""
79
+ # esg.ui.print(f"Installed {nb} new file{s} ready for download.")
80
+ # if already_on_disk:
81
+ # nb = len(already_on_disk)
82
+ # s = "s" if nb > 1 else ""
83
+ # esg.ui.print(f"Tracking {nb} file{s} already downloaded.")
84
+ # if not to_download and not already_on_disk:
85
+ # esg.ui.print("All files are already in the database.")
esgpull/cli/link.py ADDED
@@ -0,0 +1,105 @@
1
+ # from __future__ import annotations
2
+
3
+ # import click
4
+ # from click.exceptions import Abort, Exit
5
+
6
+ # from esgpull import Esgpull
7
+ # from esgpull.cli.decorators import args, opts
8
+ # from esgpull.cli.utils import get_queries, valid_name_tag
9
+ # from esgpull.graph import Graph
10
+ # from esgpull.tui import Verbosity
11
+
12
+
13
+ # @click.command()
14
+ # @args.query_id
15
+ # @opts.tag
16
+ # @opts.children
17
+ # @opts.verbosity
18
+ # def link(
19
+ # query_id: str | None,
20
+ # tag: str | None,
21
+ # children: bool,
22
+ # verbosity: Verbosity,
23
+ # ) -> None:
24
+ # """
25
+ # Link known files to query/tag.
26
+ # Offline mode to avoid searching esgf ?
27
+ # """
28
+ # esg = Esgpull(verbosity=verbosity)
29
+ # with esg.ui.logging("remove", onraise=Abort):
30
+ # if query_id is None and tag is None:
31
+ # raise click.UsageError("No query or tag provided.")
32
+ # if not valid_name_tag(esg.graph, esg.ui, query_id, tag):
33
+ # raise Exit(1)
34
+ # queries = get_queries(
35
+ # esg.graph,
36
+ # query_id,
37
+ # tag,
38
+ # children=children,
39
+ # )
40
+ # nb = len(queries)
41
+ # ies = "ies" if nb > 1 else "y"
42
+ # graph = Graph(None)
43
+ # graph.add(*queries)
44
+ # esg.ui.print(graph)
45
+ # msg = f"Remove {nb} quer{ies}?"
46
+ # if not esg.ui.ask(msg, default=True):
47
+ # raise Abort
48
+ # for query in queries:
49
+ # if not children and esg.graph.get_children(query.sha):
50
+ # esg.ui.print(
51
+ # ":stop_sign: Some queries block "
52
+ # f"removal of {query.rich_name}."
53
+ # )
54
+ # if esg.ui.ask("Show blocking queries?", default=False):
55
+ # esg.ui.print(esg.graph.subgraph(query, children=True))
56
+ # raise Exit(1)
57
+ # esg.db.delete(*queries)
58
+ # esg.ui.print(":+1:")
59
+
60
+
61
+ # @click.command()
62
+ # @args.query_id
63
+ # @opts.tag
64
+ # @opts.children
65
+ # @opts.verbosity
66
+ # def unlink(
67
+ # query_id: str | None,
68
+ # tag: str | None,
69
+ # children: bool,
70
+ # verbosity: Verbosity,
71
+ # ) -> None:
72
+ # """
73
+ # Unlink files that were associated to query/tag
74
+ # """
75
+ # esg = Esgpull(verbosity=verbosity)
76
+ # with esg.ui.logging("remove", onraise=Abort):
77
+ # if query_id is None and tag is None:
78
+ # raise click.UsageError("No query or tag provided.")
79
+ # if not valid_name_tag(esg.graph, esg.ui, query_id, tag):
80
+ # raise Exit(1)
81
+ # queries = get_queries(
82
+ # esg.graph,
83
+ # query_id,
84
+ # tag,
85
+ # children=children,
86
+ # )
87
+ # nb = len(queries)
88
+ # ies = "ies" if nb > 1 else "y"
89
+ # graph = Graph(None)
90
+ # graph.add(*queries)
91
+ # esg.ui.print(graph)
92
+ # msg = f"Remove {nb} quer{ies}?"
93
+ # if not esg.ui.ask(msg, default=True):
94
+ # raise Abort
95
+ # for query in queries:
96
+ # if not children and esg.graph.get_children(query.sha):
97
+ # esg.ui.print(
98
+ # ":stop_sign: Some queries block "
99
+ # f"removal of {query.rich_name}."
100
+ # )
101
+ # if esg.ui.ask("Show blocking queries?", default=False):
102
+ # esg.ui.print(esg.graph.subgraph(query, children=True))
103
+ # raise Exit(1)
104
+ # esg.db.delete(*queries)
105
+ # esg.ui.print(":+1:")
esgpull/cli/login.py ADDED
@@ -0,0 +1,56 @@
1
+ import click
2
+ from click.exceptions import Abort
3
+
4
+ from esgpull.auth import Auth, AuthStatus, Credentials
5
+ from esgpull.cli.decorators import opts
6
+ from esgpull.cli.utils import init_esgpull
7
+ from esgpull.constants import PROVIDERS
8
+ from esgpull.tui import Verbosity
9
+
10
+
11
+ @click.command()
12
+ @opts.verbosity
13
+ @opts.force
14
+ def login(verbosity: Verbosity, force: bool):
15
+ """
16
+ OpenID authentication and certificates renewal
17
+
18
+ The first call to `login` is a prompt asking for provider/username/password.
19
+
20
+ Subsequent calls check whether the login certificates are valid, renewing them if needed.
21
+ Renewal can be forced using the `--force` flag.
22
+ """
23
+ esg = init_esgpull(verbosity)
24
+ with esg.ui.logging("login", onraise=Abort):
25
+ cred_file = esg.config.paths.auth / esg.config.credentials.filename
26
+ if not cred_file.is_file():
27
+ esg.ui.print("No credentials found.")
28
+ choices = []
29
+ providers = list(PROVIDERS)
30
+ for i, provider in enumerate(providers):
31
+ choices.append(str(i))
32
+ esg.ui.print(f" [{i}] [i green]{provider}[/]")
33
+ provider_idx = esg.ui.choice(
34
+ "Select a provider",
35
+ choices=choices,
36
+ show_choices=False,
37
+ )
38
+ provider = providers[int(provider_idx)]
39
+ user = esg.ui.prompt("User")
40
+ password = esg.ui.prompt("Password", password=True)
41
+ credentials = Credentials(provider, user, password)
42
+ credentials.write(cred_file)
43
+ esg.auth = Auth.from_config(esg.config, credentials)
44
+ renew = force
45
+ status = esg.auth.status
46
+ status_name = status.value[0]
47
+ status_color = status.value[1]
48
+ esg.ui.print(f"Certificates are [{status_color}]{status_name}[/].")
49
+ if esg.auth.status == AuthStatus.Expired:
50
+ renew = renew or esg.ui.ask("Renew?")
51
+ elif esg.auth.status == AuthStatus.Missing:
52
+ renew = True
53
+ if renew:
54
+ with esg.ui.spinner("Renewing certificates"):
55
+ esg.auth.renew()
56
+ esg.ui.print(":+1: Renewed successfully")
esgpull/cli/remove.py ADDED
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+ from click.exceptions import Abort, Exit
5
+
6
+ from esgpull.cli.decorators import args, opts
7
+ from esgpull.cli.utils import get_queries, init_esgpull, valid_name_tag
8
+ from esgpull.graph import Graph
9
+ from esgpull.models import FileStatus
10
+ from esgpull.tui import Verbosity
11
+ from esgpull.utils import format_size
12
+
13
+
14
+ @click.command()
15
+ @args.query_id
16
+ @opts.tag
17
+ @opts.children
18
+ @opts.verbosity
19
+ def remove(
20
+ query_id: str | None,
21
+ tag: str | None,
22
+ children: bool,
23
+ verbosity: Verbosity,
24
+ ) -> None:
25
+ """
26
+ Remove queries from the database
27
+
28
+ A query required by other queries cannot be removed.
29
+
30
+ Upon removal, no files are deleted from the database nor from the filesystem, only links to that query are deleted.
31
+
32
+ There is currently no mechanism to delete "orphaned" files, as this functionality should be designed carefully and rooted in practical needs.
33
+ """
34
+ esg = init_esgpull(verbosity)
35
+ with esg.ui.logging("remove", onraise=Abort):
36
+ if query_id is None and tag is None:
37
+ raise click.UsageError("No query or tag provided.")
38
+ if not valid_name_tag(esg.graph, esg.ui, query_id, tag):
39
+ raise Exit(1)
40
+ queries = get_queries(
41
+ esg.graph,
42
+ query_id,
43
+ tag,
44
+ children=children,
45
+ )
46
+ nb = len(queries)
47
+ ies = "ies" if nb > 1 else "y"
48
+ graph = Graph(None)
49
+ graph.add(*queries)
50
+ esg.ui.print(graph)
51
+ msg = f"Remove {nb} quer{ies}?"
52
+ if not esg.ui.ask(msg, default=True):
53
+ raise Abort
54
+ for query in queries:
55
+ if query.has_files:
56
+ nb, size = query.files_count_size(FileStatus.Done)
57
+ if nb:
58
+ esg.ui.print(
59
+ f":stop_sign: {query.rich_name} is linked"
60
+ f" to {nb} downloaded files ({format_size(size)})."
61
+ )
62
+ if not esg.ui.ask("Delete anyway?", default=False):
63
+ raise Abort
64
+ if not children and esg.graph.get_children(query.sha):
65
+ esg.ui.print(
66
+ ":stop_sign: Some queries block"
67
+ f" removal of {query.rich_name}."
68
+ )
69
+ if esg.ui.ask("Show blocking queries?", default=False):
70
+ esg.ui.print(esg.graph.subgraph(query, children=True))
71
+ raise Exit(1)
72
+ esg.db.delete(*queries)
73
+ esg.ui.print(":+1:")
esgpull/cli/retry.py ADDED
@@ -0,0 +1,43 @@
1
+ from collections import Counter
2
+ from collections.abc import Sequence
3
+
4
+ import click
5
+ from click.exceptions import Abort, Exit
6
+
7
+ from esgpull.cli.decorators import args, opts
8
+ from esgpull.cli.utils import init_esgpull
9
+ from esgpull.models import FileStatus, sql
10
+ from esgpull.tui import Verbosity
11
+
12
+
13
+ @click.command()
14
+ @args.status
15
+ @opts.verbosity
16
+ def retry(
17
+ status: Sequence[FileStatus],
18
+ verbosity: Verbosity,
19
+ ):
20
+ """
21
+ Re-queue failed and cancelled downloads
22
+ """
23
+ if not status:
24
+ status = FileStatus.retryable()
25
+ esg = init_esgpull(verbosity)
26
+ with esg.ui.logging("retry", onraise=Abort):
27
+ assert FileStatus.Done not in status
28
+ assert FileStatus.Queued not in status
29
+ files = list(esg.db.scalars(sql.file.with_status(*status)))
30
+ status_str = "/".join(f"[bold red]{s.value}[/]" for s in status)
31
+ if not files:
32
+ esg.ui.print(f"No {status_str} files found.")
33
+ raise Exit(0)
34
+ counts = Counter(file.status for file in files)
35
+ for file in files:
36
+ file.status = FileStatus.Queued
37
+ esg.db.add(*files)
38
+ msg = "Sent back to the queue: "
39
+ msg += ", ".join(
40
+ f"{count} [bold red]{status.value}[/]"
41
+ for status, count in counts.items()
42
+ )
43
+ esg.ui.print(msg)
esgpull/cli/search.py ADDED
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+
5
+ import click
6
+ from click.exceptions import Abort, Exit
7
+
8
+ from esgpull.cli.decorators import args, groups, opts
9
+ from esgpull.cli.utils import filter_keys, init_esgpull, parse_query, totable
10
+ from esgpull.exceptions import PageIndexError
11
+ from esgpull.graph import Graph
12
+ from esgpull.models import Query
13
+ from esgpull.tui import Verbosity
14
+
15
+
16
+ @click.command()
17
+ @args.facets
18
+ @groups.query_def
19
+ @groups.query_date
20
+ @groups.display
21
+ @opts.dry_run
22
+ @opts.file
23
+ @opts.facets_hints
24
+ @opts.hints
25
+ @groups.json_yaml
26
+ @opts.detail
27
+ @opts.show
28
+ @opts.yes
29
+ @opts.record
30
+ @opts.verbosity
31
+ # @opts.selection_file
32
+ def search(
33
+ facets: list[str],
34
+ ## query_def
35
+ tags: list[str],
36
+ require: str | None,
37
+ distrib: str | None,
38
+ latest: str | None,
39
+ replica: str | None,
40
+ retracted: str | None,
41
+ date_from: datetime | None,
42
+ date_to: datetime | None,
43
+ ## display
44
+ _all: bool,
45
+ zero: bool,
46
+ page: int,
47
+ ## json_yaml
48
+ json: bool,
49
+ yaml: bool,
50
+ ## ungrouped
51
+ show: bool,
52
+ detail: int | None,
53
+ dry_run: bool,
54
+ file: bool,
55
+ facets_hints: bool,
56
+ hints: list[str] | None,
57
+ yes: bool,
58
+ # selection_file: str | None,
59
+ record: bool,
60
+ verbosity: Verbosity,
61
+ ) -> None:
62
+ """
63
+ Search datasets and files on ESGF
64
+
65
+ More info
66
+ """
67
+ esg = init_esgpull(verbosity, safe=False, record=record)
68
+ with esg.ui.logging("search", onraise=Abort):
69
+ query = parse_query(
70
+ facets=facets,
71
+ tags=tags,
72
+ require=require,
73
+ distrib=distrib,
74
+ latest=latest,
75
+ replica=replica,
76
+ retracted=retracted,
77
+ )
78
+ query.compute_sha()
79
+ esg.graph.resolve_require(query)
80
+ if show:
81
+ if json:
82
+ esg.ui.print(query.asdict(), json=True)
83
+ elif yaml:
84
+ esg.ui.print(query.asdict(), yaml=True)
85
+ else:
86
+ try:
87
+ graph = esg.graph.subgraph(query, parents=True)
88
+ esg.ui.print(graph)
89
+ except KeyError:
90
+ esg.ui.print(query)
91
+ esg.ui.raise_maybe_record(Exit(0))
92
+ esg.graph.add(query, force=True)
93
+ query = esg.graph.expand(query.sha)
94
+ hits = esg.context.hits(
95
+ query,
96
+ file=file,
97
+ date_from=date_from,
98
+ date_to=date_to,
99
+ )
100
+ nb = sum(hits)
101
+ page_size = esg.config.cli.page_size
102
+ if detail is not None:
103
+ page_size = 1
104
+ page = detail
105
+ nb_pages = (nb // page_size) or 1
106
+ offset = page * page_size
107
+ max_hits = min(page_size, nb - offset)
108
+ if page > nb_pages:
109
+ raise PageIndexError(page, nb_pages)
110
+ elif zero:
111
+ max_hits = 0
112
+ elif _all:
113
+ offset = 0
114
+ max_hits = nb
115
+ ids = range(offset, offset + max_hits)
116
+ if dry_run:
117
+ search_results = esg.context.prepare_search(
118
+ query,
119
+ file=file,
120
+ hits=hits,
121
+ offset=offset,
122
+ max_hits=max_hits,
123
+ date_from=date_from,
124
+ date_to=date_to,
125
+ )
126
+ for result in search_results:
127
+ esg.ui.print(result.request.url)
128
+ esg.ui.raise_maybe_record(Exit(0))
129
+ if facets_hints:
130
+ not_distrib_query = query << Query(options=dict(distrib=False))
131
+ facet_counts = esg.context.hints(
132
+ not_distrib_query,
133
+ file=file,
134
+ facets=["*"],
135
+ date_from=date_from,
136
+ date_to=date_to,
137
+ )
138
+ esg.ui.print(list(facet_counts[0]), json=True)
139
+ esg.ui.raise_maybe_record(Exit(0))
140
+ if hints is not None:
141
+ facet_counts = esg.context.hints(
142
+ query,
143
+ file=file,
144
+ facets=hints,
145
+ date_from=date_from,
146
+ date_to=date_to,
147
+ )
148
+ esg.ui.print(facet_counts, json=True)
149
+ esg.ui.raise_maybe_record(Exit(0))
150
+ if max_hits > 200 and not yes:
151
+ nb_req = max_hits // esg.config.api.page_limit
152
+ message = f"{nb_req} requests will be sent to ESGF. Send anyway?"
153
+ if not esg.ui.ask(message, default=True):
154
+ esg.ui.raise_maybe_record(Abort)
155
+ if detail is not None:
156
+ queries = esg.context.search_as_queries(
157
+ query,
158
+ file=file,
159
+ hits=hits,
160
+ offset=offset,
161
+ max_hits=max_hits,
162
+ keep_duplicates=True,
163
+ date_from=date_from,
164
+ date_to=date_to,
165
+ )
166
+ if json:
167
+ selections = [q.selection.asdict() for q in queries]
168
+ esg.ui.print(selections, json=True)
169
+ elif yaml:
170
+ selections = [q.selection.asdict() for q in queries]
171
+ esg.ui.print(selections, yaml=True)
172
+ else:
173
+ graph = Graph(None)
174
+ graph.add(*queries, clone=False)
175
+ esg.ui.print(graph)
176
+ esg.ui.raise_maybe_record(Exit(0))
177
+ results = esg.context.search(
178
+ query,
179
+ file=file,
180
+ hits=hits,
181
+ offset=offset,
182
+ max_hits=max_hits,
183
+ keep_duplicates=True,
184
+ date_from=date_from,
185
+ date_to=date_to,
186
+ )
187
+ if json:
188
+ esg.ui.print([f.asdict() for f in results], json=True)
189
+ elif yaml:
190
+ esg.ui.print([f.asdict() for f in results], yaml=True)
191
+ else:
192
+ f_or_d = "file" if file else "dataset"
193
+ s = "s" if nb != 1 else ""
194
+ esg.ui.print(f"Found {nb} {f_or_d}{s}.")
195
+ if results:
196
+ unique_ids = {r.master_id for r in results}
197
+ unique_nodes = {(r.master_id, r.data_node) for r in results}
198
+ needs_data_node = len(unique_nodes) > len(unique_ids)
199
+ docs = filter_keys(results, ids=ids, data_node=needs_data_node)
200
+ esg.ui.print(totable(docs))
201
+ esg.ui.raise_maybe_record(Exit(0))