earthscope-cli 1.1.1__tar.gz → 1.1.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.
- {earthscope_cli-1.1.1/src/earthscope_cli.egg-info → earthscope_cli-1.1.2}/PKG-INFO +3 -3
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/pyproject.toml +4 -4
- earthscope_cli-1.1.2/src/earthscope_cli/__init__.py +1 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/common.py +1 -4
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/debug.py +1 -5
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/dropoff/render.py +43 -1
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/dropoff/typer.py +52 -4
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/dropoff/util.py +49 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/login.py +1 -4
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/main.py +1 -4
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/user.py +1 -5
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2/src/earthscope_cli.egg-info}/PKG-INFO +3 -3
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/requires.txt +2 -2
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/tests/test_dropoff.py +167 -4
- earthscope_cli-1.1.1/src/earthscope_cli/__init__.py +0 -1
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/LICENSE +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/README.md +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/setup.cfg +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/setup.py +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli/util.py +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/SOURCES.txt +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/dependency_links.txt +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/entry_points.txt +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/top_level.txt +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/tests/test_cli.py +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/tests/test_login.py +0 -0
- {earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/tests/test_user.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: earthscope-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: A CLI for the EarthScope API
|
|
5
5
|
Author-email: EarthScope <data-help@earthscope.org>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -11,9 +11,9 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.9
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: typer
|
|
14
|
+
Requires-Dist: typer<0.24.0
|
|
15
15
|
Requires-Dist: typer-di>=0.1.3
|
|
16
|
-
Requires-Dist: earthscope-sdk>=1.3.
|
|
16
|
+
Requires-Dist: earthscope-sdk>=1.3.1
|
|
17
17
|
Requires-Dist: rich>=13.8.0
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: black; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "earthscope-cli"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.2"
|
|
8
8
|
description = "A CLI for the EarthScope API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "EarthScope", email = "data-help@earthscope.org" }]
|
|
@@ -18,9 +18,9 @@ classifiers = [
|
|
|
18
18
|
|
|
19
19
|
#Suggestion not to pin dependencies since the package should work in many different python environments
|
|
20
20
|
dependencies = [
|
|
21
|
-
"typer
|
|
21
|
+
"typer<0.24.0",
|
|
22
22
|
"typer-di>=0.1.3",
|
|
23
|
-
"earthscope-sdk>=1.3.
|
|
23
|
+
"earthscope-sdk>=1.3.1",
|
|
24
24
|
"rich>=13.8.0",
|
|
25
25
|
]
|
|
26
26
|
requires-python = ">=3.9"
|
|
@@ -43,7 +43,7 @@ Homepage = "https://gitlab.com/earthscope/public/earthscope-cli"
|
|
|
43
43
|
es = "earthscope_cli.main:app"
|
|
44
44
|
|
|
45
45
|
[tool.bumpver]
|
|
46
|
-
current_version = "1.1.
|
|
46
|
+
current_version = "1.1.2"
|
|
47
47
|
version_pattern = "MAJOR.MINOR.PATCH[PYTAGNUM]"
|
|
48
48
|
commit_message = "chore: bump version {old_version} -> {new_version}"
|
|
49
49
|
commit = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.2"
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
from contextlib import suppress
|
|
2
1
|
from typing import Annotated, Optional
|
|
3
2
|
|
|
4
3
|
import typer
|
|
4
|
+
from rich import print
|
|
5
5
|
from typer_di import Depends
|
|
6
6
|
|
|
7
|
-
with suppress(ModuleNotFoundError):
|
|
8
|
-
from rich import print
|
|
9
|
-
|
|
10
7
|
GLOBAL_HELP_PANEL = "Global Options"
|
|
11
8
|
|
|
12
9
|
|
|
@@ -2,7 +2,7 @@ import threading
|
|
|
2
2
|
from typing import Dict, List, Optional
|
|
3
3
|
|
|
4
4
|
from earthscope_sdk.client.dropoff._multipart_uploader import UploadStatus
|
|
5
|
-
from earthscope_sdk.client.dropoff.models import DropoffObject
|
|
5
|
+
from earthscope_sdk.client.dropoff.models import DropoffObject, DropoffSummary
|
|
6
6
|
from rich import print
|
|
7
7
|
from rich.console import Group
|
|
8
8
|
from rich.live import Live
|
|
@@ -84,6 +84,31 @@ def print_dropoff_objects_table(
|
|
|
84
84
|
print()
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def print_dropoff_summary(summary: DropoffSummary, *, prefix: Optional[str] = None):
|
|
88
|
+
if not summary.status_counts:
|
|
89
|
+
print(f"[yellow]No objects found in prefix '{prefix}'")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
title = "Dropoff Summary"
|
|
93
|
+
if prefix:
|
|
94
|
+
title += f" for {prefix}"
|
|
95
|
+
|
|
96
|
+
table = Table(
|
|
97
|
+
title=title,
|
|
98
|
+
show_header=True,
|
|
99
|
+
title_style="bold magenta",
|
|
100
|
+
header_style="bold magenta",
|
|
101
|
+
)
|
|
102
|
+
table.add_column("Status")
|
|
103
|
+
table.add_column("Count")
|
|
104
|
+
for status in summary.status_counts:
|
|
105
|
+
table.add_row(status.status, str(status.count))
|
|
106
|
+
|
|
107
|
+
print()
|
|
108
|
+
print(table)
|
|
109
|
+
print()
|
|
110
|
+
|
|
111
|
+
|
|
87
112
|
class AggregateBytesColumn(ProgressColumn):
|
|
88
113
|
"""
|
|
89
114
|
Custom column that shows aggregate bytes across all uploads.
|
|
@@ -181,6 +206,23 @@ class CompositeProgressBarColumn(ProgressColumn):
|
|
|
181
206
|
return result
|
|
182
207
|
|
|
183
208
|
|
|
209
|
+
class UploadSummary:
|
|
210
|
+
def __init__(self, total: int, succeeded: int):
|
|
211
|
+
self.total = total
|
|
212
|
+
self.succeeded = succeeded
|
|
213
|
+
self.failed = total - succeeded
|
|
214
|
+
|
|
215
|
+
def print(self):
|
|
216
|
+
color = (
|
|
217
|
+
"green"
|
|
218
|
+
if self.failed == 0
|
|
219
|
+
else ("red" if self.succeeded == 0 else "yellow")
|
|
220
|
+
)
|
|
221
|
+
print(
|
|
222
|
+
f"[{color}]Summary: {self.succeeded}/{self.total} files succeeded, {self.failed} failed"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
184
226
|
class ConcurrentUploadProgressDisplay:
|
|
185
227
|
"""
|
|
186
228
|
Rich progress display for concurrent file uploads.
|
|
@@ -12,13 +12,17 @@ from typer_di import Depends, TyperDI
|
|
|
12
12
|
from earthscope_cli.common import get_sdk_refreshed
|
|
13
13
|
from earthscope_cli.dropoff.render import (
|
|
14
14
|
ConcurrentUploadProgressDisplay,
|
|
15
|
+
UploadSummary,
|
|
15
16
|
print_dropoff_objects_table,
|
|
17
|
+
print_dropoff_summary,
|
|
16
18
|
)
|
|
17
19
|
from earthscope_cli.dropoff.util import (
|
|
18
20
|
DropoffOptions,
|
|
21
|
+
FilterOptions,
|
|
19
22
|
collect_files,
|
|
20
23
|
get_category,
|
|
21
24
|
get_dropoff_options,
|
|
25
|
+
get_filter_options,
|
|
22
26
|
)
|
|
23
27
|
|
|
24
28
|
app = TyperDI()
|
|
@@ -67,10 +71,38 @@ def get_object_history(
|
|
|
67
71
|
print(f"[yellow]No history found for category '{cat_str}' with key '{key}'")
|
|
68
72
|
|
|
69
73
|
|
|
74
|
+
@app.command(name="get-summary")
|
|
75
|
+
def get_summary(
|
|
76
|
+
sdk: Annotated[EarthScopeClient, Depends(get_sdk_refreshed)],
|
|
77
|
+
options: Annotated[DropoffOptions, Depends(get_dropoff_options)],
|
|
78
|
+
filters: Annotated[FilterOptions, Depends(get_filter_options)],
|
|
79
|
+
prefix: Annotated[
|
|
80
|
+
str,
|
|
81
|
+
typer.Option(help="The prefix of the objects to get the summary for"),
|
|
82
|
+
],
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Get a summary of a dropoff prefix.
|
|
86
|
+
|
|
87
|
+
es dropoff get-summary -c <type> --prefix <prefix>
|
|
88
|
+
es dropoff get-summary -c miniseed --prefix demo/
|
|
89
|
+
es dropoff get-summary -c miniseed --prefix demo/ --submitted-after 2025-01-01T00:00:00Z
|
|
90
|
+
es dropoff get-summary -c miniseed --prefix demo/ --submitted-before 2025-12-31T23:59:59Z
|
|
91
|
+
"""
|
|
92
|
+
result = sdk.dropoff.get_summary(
|
|
93
|
+
category=options.category.value,
|
|
94
|
+
prefix=prefix,
|
|
95
|
+
submitted_after=filters.submitted_after,
|
|
96
|
+
submitted_before=filters.submitted_before,
|
|
97
|
+
)
|
|
98
|
+
print_dropoff_summary(result, prefix=prefix)
|
|
99
|
+
|
|
100
|
+
|
|
70
101
|
@app.command(name="list-objects")
|
|
71
102
|
def list_objects(
|
|
72
103
|
sdk: Annotated[EarthScopeClient, Depends(get_sdk_refreshed)],
|
|
73
104
|
options: Annotated[DropoffOptions, Depends(get_dropoff_options)],
|
|
105
|
+
filters: Annotated[FilterOptions, Depends(get_filter_options)],
|
|
74
106
|
prefix: Annotated[
|
|
75
107
|
str,
|
|
76
108
|
typer.Option(help="Filter by object prefix"),
|
|
@@ -83,12 +115,18 @@ def list_objects(
|
|
|
83
115
|
es dropoff list-objects -c miniseed
|
|
84
116
|
es dropoff list-objects -c miniseed --prefix demo/
|
|
85
117
|
es dropoff list-objects -c miniseed --offset 100 --limit 100
|
|
118
|
+
es dropoff list-objects -c miniseed --status FAILED
|
|
119
|
+
es dropoff list-objects -c miniseed --submitted-after 2025-01-01T00:00:00Z
|
|
120
|
+
es dropoff list-objects -c miniseed --submitted-before 2025-12-31T23:59:59Z
|
|
86
121
|
"""
|
|
87
122
|
result = sdk.dropoff.list_objects(
|
|
88
123
|
category=options.category.value,
|
|
89
124
|
prefix=prefix,
|
|
90
125
|
offset=options.offset,
|
|
91
126
|
limit=options.limit,
|
|
127
|
+
submitted_after=filters.submitted_after,
|
|
128
|
+
submitted_before=filters.submitted_before,
|
|
129
|
+
status=filters.status,
|
|
92
130
|
)
|
|
93
131
|
|
|
94
132
|
suffix = ""
|
|
@@ -104,7 +142,9 @@ def list_objects(
|
|
|
104
142
|
next_offset=result.offset + result.limit,
|
|
105
143
|
)
|
|
106
144
|
|
|
107
|
-
print(
|
|
145
|
+
print(
|
|
146
|
+
f"[yellow]No objects matched specified criteria: category='{cat_str}', prefix='{prefix}', offset={options.offset}, limit={options.limit}', submitted_after={filters.submitted_after}, submitted_before={filters.submitted_before}, status={filters.status}'"
|
|
147
|
+
)
|
|
108
148
|
|
|
109
149
|
|
|
110
150
|
@app.command(name="upload")
|
|
@@ -176,22 +216,27 @@ def upload(
|
|
|
176
216
|
don't overwrite each other and maintains meaningful organization in S3.
|
|
177
217
|
"""
|
|
178
218
|
# Parse the input path to get a list of files
|
|
219
|
+
total_collected = 0
|
|
220
|
+
succeeded = 0
|
|
179
221
|
files: list[tuple[Path, str]] = []
|
|
180
222
|
for p, k in collect_files(paths):
|
|
223
|
+
total_collected += 1
|
|
181
224
|
try:
|
|
182
225
|
Validator(p, category=category).validate_all()
|
|
183
226
|
except Exception as e:
|
|
184
227
|
print(f"[red]{e}")
|
|
185
|
-
raise typer.Exit(code=1)
|
|
186
228
|
else:
|
|
187
229
|
files.append((p, k))
|
|
188
230
|
|
|
189
231
|
if not files:
|
|
190
|
-
|
|
232
|
+
paths_str = ", ".join(str(p) for p in paths)
|
|
233
|
+
print(f"[red]No valid {category.value} files found in paths: {paths_str}")
|
|
234
|
+
UploadSummary(total=total_collected, succeeded=succeeded).print()
|
|
191
235
|
raise typer.Exit(code=1)
|
|
192
236
|
|
|
237
|
+
display = ConcurrentUploadProgressDisplay(len(files))
|
|
193
238
|
try:
|
|
194
|
-
with
|
|
239
|
+
with display:
|
|
195
240
|
sdk.dropoff.put_objects(
|
|
196
241
|
objects=files,
|
|
197
242
|
category=category,
|
|
@@ -203,3 +248,6 @@ def upload(
|
|
|
203
248
|
except KeyboardInterrupt:
|
|
204
249
|
print()
|
|
205
250
|
print("[yellow]Upload interrupted. Use the same command to resume the upload.")
|
|
251
|
+
finally:
|
|
252
|
+
succeeded = len(display.completed_files)
|
|
253
|
+
UploadSummary(total=total_collected, succeeded=succeeded).print()
|
|
@@ -18,6 +18,13 @@ class PaginationOptions:
|
|
|
18
18
|
limit: int
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
@dataclass
|
|
22
|
+
class FilterOptions:
|
|
23
|
+
submitted_before: Optional[datetime] = None
|
|
24
|
+
submitted_after: Optional[datetime] = None
|
|
25
|
+
status: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
|
|
21
28
|
@dataclass
|
|
22
29
|
class DropoffOptions(PaginationOptions):
|
|
23
30
|
category: DropoffCategory
|
|
@@ -86,6 +93,48 @@ def get_category(
|
|
|
86
93
|
return DropoffCategory(cat_str)
|
|
87
94
|
|
|
88
95
|
|
|
96
|
+
def get_filter_options(
|
|
97
|
+
submitted_after: Annotated[
|
|
98
|
+
Optional[datetime],
|
|
99
|
+
typer.Option(
|
|
100
|
+
"--submitted-after",
|
|
101
|
+
help="Filter results submitted after this datetime (ISO 8601)",
|
|
102
|
+
formats=[
|
|
103
|
+
"%Y-%m-%dT%H:%M:%S%z",
|
|
104
|
+
"%Y-%m-%dT%H:%M:%SZ",
|
|
105
|
+
"%Y-%m-%d%z",
|
|
106
|
+
"%Y-%m-%dZ",
|
|
107
|
+
],
|
|
108
|
+
),
|
|
109
|
+
] = None,
|
|
110
|
+
submitted_before: Annotated[
|
|
111
|
+
Optional[datetime],
|
|
112
|
+
typer.Option(
|
|
113
|
+
"--submitted-before",
|
|
114
|
+
help="Filter results submitted before this datetime (ISO 8601)",
|
|
115
|
+
formats=[
|
|
116
|
+
"%Y-%m-%dT%H:%M:%S%z",
|
|
117
|
+
"%Y-%m-%dT%H:%M:%SZ",
|
|
118
|
+
"%Y-%m-%d%z",
|
|
119
|
+
"%Y-%m-%dZ",
|
|
120
|
+
],
|
|
121
|
+
),
|
|
122
|
+
] = None,
|
|
123
|
+
status: Annotated[
|
|
124
|
+
Optional[str],
|
|
125
|
+
typer.Option(
|
|
126
|
+
"--status",
|
|
127
|
+
help="Filter results by status",
|
|
128
|
+
),
|
|
129
|
+
] = None,
|
|
130
|
+
) -> FilterOptions:
|
|
131
|
+
return FilterOptions(
|
|
132
|
+
submitted_before=submitted_before,
|
|
133
|
+
submitted_after=submitted_after,
|
|
134
|
+
status=status,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
89
138
|
def get_dropoff_options(
|
|
90
139
|
category: Annotated[DropoffCategory, Depends(get_category)],
|
|
91
140
|
offset: Annotated[
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
from contextlib import suppress
|
|
2
1
|
from textwrap import dedent
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
import typer
|
|
5
|
+
from rich import print
|
|
6
6
|
from typer_di import Depends, TyperDI
|
|
7
7
|
|
|
8
8
|
from earthscope_cli.common import get_sdk
|
|
9
9
|
|
|
10
|
-
with suppress(ModuleNotFoundError):
|
|
11
|
-
from rich import print
|
|
12
|
-
|
|
13
10
|
if TYPE_CHECKING:
|
|
14
11
|
from earthscope_sdk import EarthScopeClient
|
|
15
12
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from contextlib import suppress
|
|
2
1
|
from typing import Annotated
|
|
3
2
|
|
|
4
3
|
import typer
|
|
@@ -10,15 +9,13 @@ from earthscope_sdk.auth.error import (
|
|
|
10
9
|
NoTokensError,
|
|
11
10
|
UnauthorizedError,
|
|
12
11
|
)
|
|
12
|
+
from rich import print
|
|
13
13
|
|
|
14
14
|
from earthscope_cli import __version__ as cli_version
|
|
15
15
|
from earthscope_cli import debug, login, user
|
|
16
16
|
from earthscope_cli.dropoff.typer import app as dropoff_app
|
|
17
17
|
from earthscope_cli.util import ErrorHandlingTyper
|
|
18
18
|
|
|
19
|
-
with suppress(ModuleNotFoundError):
|
|
20
|
-
from rich import print
|
|
21
|
-
|
|
22
19
|
app = ErrorHandlingTyper(
|
|
23
20
|
pretty_exceptions_enable=False,
|
|
24
21
|
)
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
from contextlib import suppress
|
|
2
|
-
|
|
3
1
|
import typer
|
|
2
|
+
from rich import print
|
|
4
3
|
from typer_di import Depends, TyperDI
|
|
5
4
|
|
|
6
5
|
from earthscope_cli import login
|
|
7
6
|
from earthscope_cli.common import get_sdk, get_sdk_refreshed
|
|
8
7
|
|
|
9
|
-
with suppress(ModuleNotFoundError):
|
|
10
|
-
from rich import print
|
|
11
|
-
|
|
12
8
|
app = TyperDI()
|
|
13
9
|
|
|
14
10
|
# Register aliases to login/logout
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: earthscope-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: A CLI for the EarthScope API
|
|
5
5
|
Author-email: EarthScope <data-help@earthscope.org>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -11,9 +11,9 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.9
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: typer
|
|
14
|
+
Requires-Dist: typer<0.24.0
|
|
15
15
|
Requires-Dist: typer-di>=0.1.3
|
|
16
|
-
Requires-Dist: earthscope-sdk>=1.3.
|
|
16
|
+
Requires-Dist: earthscope-sdk>=1.3.1
|
|
17
17
|
Requires-Dist: rich>=13.8.0
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: black; extra == "dev"
|
|
@@ -17,7 +17,10 @@ from types import SimpleNamespace
|
|
|
17
17
|
from unittest.mock import MagicMock, Mock
|
|
18
18
|
|
|
19
19
|
import pytest
|
|
20
|
-
from earthscope_sdk.client.dropoff.models import
|
|
20
|
+
from earthscope_sdk.client.dropoff.models import (
|
|
21
|
+
DropoffCategory,
|
|
22
|
+
DropoffObject,
|
|
23
|
+
)
|
|
21
24
|
|
|
22
25
|
from earthscope_cli.dropoff.typer import app
|
|
23
26
|
from earthscope_cli.dropoff.util import collect_files, key_from_path, safe_str
|
|
@@ -288,14 +291,15 @@ class TestUploadCommand:
|
|
|
288
291
|
mock_sdk = mock_sdk_context_manager(monkeypatch)
|
|
289
292
|
|
|
290
293
|
# Run command
|
|
294
|
+
cat = "miniseed"
|
|
291
295
|
result = runner.invoke(
|
|
292
296
|
app,
|
|
293
|
-
["upload", "-c",
|
|
297
|
+
["upload", "-c", cat, str(empty_dir)],
|
|
294
298
|
)
|
|
295
299
|
|
|
296
300
|
# Verify exit code indicates error
|
|
297
301
|
assert result.exit_code == 1
|
|
298
|
-
assert "No files found" in result.stdout
|
|
302
|
+
assert f"No valid {cat} files found" in result.stdout
|
|
299
303
|
|
|
300
304
|
# Verify SDK upload was not called
|
|
301
305
|
mock_sdk.dropoff.put_objects.assert_not_called()
|
|
@@ -338,6 +342,9 @@ class TestListObjectsCommand:
|
|
|
338
342
|
prefix="",
|
|
339
343
|
offset=0,
|
|
340
344
|
limit=100,
|
|
345
|
+
submitted_after=None,
|
|
346
|
+
submitted_before=None,
|
|
347
|
+
status=None,
|
|
341
348
|
)
|
|
342
349
|
|
|
343
350
|
# Verify output contains file name
|
|
@@ -368,6 +375,9 @@ class TestListObjectsCommand:
|
|
|
368
375
|
prefix="demo/",
|
|
369
376
|
offset=0,
|
|
370
377
|
limit=100,
|
|
378
|
+
submitted_after=None,
|
|
379
|
+
submitted_before=None,
|
|
380
|
+
status=None,
|
|
371
381
|
)
|
|
372
382
|
|
|
373
383
|
def test_list_objects_with_pagination(self, monkeypatch):
|
|
@@ -395,6 +405,159 @@ class TestListObjectsCommand:
|
|
|
395
405
|
prefix="",
|
|
396
406
|
offset=50,
|
|
397
407
|
limit=25,
|
|
408
|
+
submitted_after=None,
|
|
409
|
+
submitted_before=None,
|
|
410
|
+
status=None,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
@pytest.mark.parametrize(
|
|
414
|
+
"flag,value,expected_dt",
|
|
415
|
+
[
|
|
416
|
+
# --submitted-after formats
|
|
417
|
+
(
|
|
418
|
+
"--submitted-after",
|
|
419
|
+
"2026-01-15T12:30:45Z",
|
|
420
|
+
datetime(2026, 1, 15, 12, 30, 45, tzinfo=timezone.utc),
|
|
421
|
+
),
|
|
422
|
+
(
|
|
423
|
+
"--submitted-after",
|
|
424
|
+
"2026-01-15T12:30:45+00:00",
|
|
425
|
+
datetime(2026, 1, 15, 12, 30, 45, tzinfo=timezone.utc),
|
|
426
|
+
),
|
|
427
|
+
(
|
|
428
|
+
"--submitted-after",
|
|
429
|
+
"2026-02-21Z",
|
|
430
|
+
datetime(2026, 2, 21, 0, 0, 0, tzinfo=timezone.utc),
|
|
431
|
+
),
|
|
432
|
+
# --submitted-before formats
|
|
433
|
+
(
|
|
434
|
+
"--submitted-before",
|
|
435
|
+
"2026-03-01T06:00:00Z",
|
|
436
|
+
datetime(2026, 3, 1, 6, 0, 0, tzinfo=timezone.utc),
|
|
437
|
+
),
|
|
438
|
+
(
|
|
439
|
+
"--submitted-before",
|
|
440
|
+
"2026-03-01T06:00:00+00:00",
|
|
441
|
+
datetime(2026, 3, 1, 6, 0, 0, tzinfo=timezone.utc),
|
|
442
|
+
),
|
|
443
|
+
(
|
|
444
|
+
"--submitted-before",
|
|
445
|
+
"2026-03-01Z",
|
|
446
|
+
datetime(2026, 3, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
447
|
+
),
|
|
448
|
+
],
|
|
449
|
+
)
|
|
450
|
+
def test_list_objects_timestamp_parsing(
|
|
451
|
+
self, monkeypatch, flag, value, expected_dt
|
|
452
|
+
):
|
|
453
|
+
"""Test that timestamp strings are correctly parsed and passed to the SDK."""
|
|
454
|
+
mock_sdk = mock_sdk_context_manager(monkeypatch)
|
|
455
|
+
mock_sdk.dropoff.list_objects = Mock(
|
|
456
|
+
return_value=SimpleNamespace(items=[], has_next=False, offset=0, limit=100)
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
result = runner.invoke(
|
|
460
|
+
app,
|
|
461
|
+
["list-objects", "-c", "miniseed", flag, value],
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
assert result.exit_code == 0, (
|
|
465
|
+
f"Command failed: {result.stdout}\n{result.exception}"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
_, kwargs = mock_sdk.dropoff.list_objects.call_args
|
|
469
|
+
if flag == "--submitted-after":
|
|
470
|
+
assert kwargs["submitted_after"] == expected_dt
|
|
471
|
+
assert kwargs["submitted_before"] is None
|
|
472
|
+
else:
|
|
473
|
+
assert kwargs["submitted_before"] == expected_dt
|
|
474
|
+
assert kwargs["submitted_after"] is None
|
|
475
|
+
|
|
476
|
+
def test_list_objects_with_both_timestamps(self, monkeypatch):
|
|
477
|
+
"""Test that both --submitted-after and --submitted-before are forwarded together."""
|
|
478
|
+
mock_sdk = mock_sdk_context_manager(monkeypatch)
|
|
479
|
+
mock_sdk.dropoff.list_objects = Mock(
|
|
480
|
+
return_value=SimpleNamespace(items=[], has_next=False, offset=0, limit=100)
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
result = runner.invoke(
|
|
484
|
+
app,
|
|
485
|
+
[
|
|
486
|
+
"list-objects",
|
|
487
|
+
"-c",
|
|
488
|
+
"miniseed",
|
|
489
|
+
"--submitted-after",
|
|
490
|
+
"2026-01-01T00:00:00Z",
|
|
491
|
+
"--submitted-before",
|
|
492
|
+
"2026-02-01T00:00:00Z",
|
|
493
|
+
],
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
assert result.exit_code == 0, (
|
|
497
|
+
f"Command failed: {result.stdout}\n{result.exception}"
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
mock_sdk.dropoff.list_objects.assert_called_once_with(
|
|
501
|
+
category="miniseed",
|
|
502
|
+
prefix="",
|
|
503
|
+
offset=0,
|
|
504
|
+
limit=100,
|
|
505
|
+
submitted_after=datetime(2026, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
506
|
+
submitted_before=datetime(2026, 2, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
507
|
+
status=None,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
def test_list_objects_invalid_timestamp(self, monkeypatch):
|
|
511
|
+
"""Test that an invalid timestamp string causes a non-zero exit."""
|
|
512
|
+
mock_sdk = mock_sdk_context_manager(monkeypatch)
|
|
513
|
+
mock_sdk.dropoff.list_objects = Mock(
|
|
514
|
+
return_value=SimpleNamespace(items=[], has_next=False, offset=0, limit=100)
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
result = runner.invoke(
|
|
518
|
+
app,
|
|
519
|
+
["list-objects", "-c", "miniseed", "--submitted-after", "not-a-date"],
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
assert result.exit_code != 0
|
|
523
|
+
mock_sdk.dropoff.list_objects.assert_not_called()
|
|
524
|
+
|
|
525
|
+
def test_list_objects_all_filters_combined(self, monkeypatch):
|
|
526
|
+
"""Test that all three new filters can be used together."""
|
|
527
|
+
mock_sdk = mock_sdk_context_manager(monkeypatch)
|
|
528
|
+
mock_sdk.dropoff.list_objects = Mock(
|
|
529
|
+
return_value=SimpleNamespace(items=[], has_next=False, offset=0, limit=100)
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
result = runner.invoke(
|
|
533
|
+
app,
|
|
534
|
+
[
|
|
535
|
+
"list-objects",
|
|
536
|
+
"-c",
|
|
537
|
+
"miniseed",
|
|
538
|
+
"--prefix",
|
|
539
|
+
"demo/",
|
|
540
|
+
"--submitted-after",
|
|
541
|
+
"2026-01-01T00:00:00Z",
|
|
542
|
+
"--submitted-before",
|
|
543
|
+
"2026-02-01Z",
|
|
544
|
+
"--status",
|
|
545
|
+
"ACCEPTED",
|
|
546
|
+
],
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
assert result.exit_code == 0, (
|
|
550
|
+
f"Command failed: {result.stdout}\n{result.exception}"
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
mock_sdk.dropoff.list_objects.assert_called_once_with(
|
|
554
|
+
category="miniseed",
|
|
555
|
+
prefix="demo/",
|
|
556
|
+
offset=0,
|
|
557
|
+
limit=100,
|
|
558
|
+
submitted_after=datetime(2026, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
559
|
+
submitted_before=datetime(2026, 2, 1, 0, 0, 0, tzinfo=timezone.utc),
|
|
560
|
+
status="ACCEPTED",
|
|
398
561
|
)
|
|
399
562
|
|
|
400
563
|
def test_list_objects_empty_result(self, monkeypatch):
|
|
@@ -420,7 +583,7 @@ class TestListObjectsCommand:
|
|
|
420
583
|
assert result.exit_code == 0
|
|
421
584
|
|
|
422
585
|
# Verify message about no objects
|
|
423
|
-
assert "No objects
|
|
586
|
+
assert "No objects matched" in result.stdout
|
|
424
587
|
|
|
425
588
|
def test_list_objects_has_next_indicator(self, monkeypatch):
|
|
426
589
|
"""Test that pagination indicators are shown when has_next is True."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{earthscope_cli-1.1.1 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|