earthscope-cli 1.1.0__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.2/PKG-INFO +121 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/pyproject.toml +7 -7
- earthscope_cli-1.1.2/src/earthscope_cli/__init__.py +1 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/common.py +1 -4
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/debug.py +1 -5
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/dropoff/render.py +43 -1
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/dropoff/typer.py +52 -4
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/dropoff/util.py +49 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/login.py +1 -4
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/main.py +1 -4
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/user.py +1 -5
- earthscope_cli-1.1.2/src/earthscope_cli.egg-info/PKG-INFO +121 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/requires.txt +3 -3
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/tests/test_dropoff.py +167 -4
- earthscope_cli-1.1.0/PKG-INFO +0 -323
- earthscope_cli-1.1.0/src/earthscope_cli/__init__.py +0 -1
- earthscope_cli-1.1.0/src/earthscope_cli.egg-info/PKG-INFO +0 -323
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/LICENSE +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/README.md +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/setup.cfg +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/setup.py +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli/util.py +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/SOURCES.txt +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/dependency_links.txt +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/entry_points.txt +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/src/earthscope_cli.egg-info/top_level.txt +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/tests/test_cli.py +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/tests/test_login.py +0 -0
- {earthscope_cli-1.1.0 → earthscope_cli-1.1.2}/tests/test_user.py +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: earthscope-cli
|
|
3
|
+
Version: 1.1.2
|
|
4
|
+
Summary: A CLI for the EarthScope API
|
|
5
|
+
Author-email: EarthScope <data-help@earthscope.org>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/earthscope/public/earthscope-cli
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: typer<0.24.0
|
|
15
|
+
Requires-Dist: typer-di>=0.1.3
|
|
16
|
+
Requires-Dist: earthscope-sdk>=1.3.1
|
|
17
|
+
Requires-Dist: rich>=13.8.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: black; extra == "dev"
|
|
20
|
+
Requires-Dist: bumpver; extra == "dev"
|
|
21
|
+
Requires-Dist: build; extra == "dev"
|
|
22
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: twine>=6.0.1; extra == "dev"
|
|
25
|
+
Requires-Dist: pip-tools; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# EarthScope CLI
|
|
29
|
+
|
|
30
|
+
A CLI for interacting with EarthScope's APIs
|
|
31
|
+
|
|
32
|
+
## Getting Started
|
|
33
|
+
|
|
34
|
+
### Requirements
|
|
35
|
+
|
|
36
|
+
To use the CLI you must have:
|
|
37
|
+
|
|
38
|
+
- Registered an account with Earthscope ([sign up now](https://earthscope.org/user/login)). See [here](https://www.earthscope.org/data/authentication) for more information
|
|
39
|
+
- Python >= 3.9
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
|
|
43
|
+
Install from PyPI
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
pip install earthscope-cli
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Usage
|
|
50
|
+
|
|
51
|
+
A new `es` command is available in your terminal. Use `--help` with any command to explore commands and options.
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
es --help
|
|
55
|
+
es user --help
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Login to your EarthScope account
|
|
59
|
+
|
|
60
|
+
```shell
|
|
61
|
+
es login
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will open your browser to a confirmation page with the same code shown on your command line.
|
|
65
|
+
If you are on a device that does not have a web browser, you can copy the displayed url in a browser on another device (personal computer, mobile device, etc...) and complete the confirmation there.
|
|
66
|
+
|
|
67
|
+
The `es login` command will save your token locally. If this token is deleted, you will need to re-authenticate (login) to retrieve your token again.
|
|
68
|
+
|
|
69
|
+
#### Get your access token
|
|
70
|
+
|
|
71
|
+
```shell
|
|
72
|
+
es user get-access-token
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `get-access-token` command will display your access token. If your access token is close to expiration or expired,
|
|
76
|
+
the default behavior is to automatically refresh your token.
|
|
77
|
+
|
|
78
|
+
If you want to manually refresh your token:
|
|
79
|
+
|
|
80
|
+
```shell
|
|
81
|
+
es user refresh-access-token
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Never share your tokens. If you think your token has been compromised, please revoke your refresh token and re-authenticate (login):
|
|
85
|
+
|
|
86
|
+
```shell
|
|
87
|
+
es user revoke-refresh-token
|
|
88
|
+
es login
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Get your user profile
|
|
92
|
+
|
|
93
|
+
```shell
|
|
94
|
+
es user get-profile
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
For detailed usage examples, authentication guides, and advanced features, see the [full documentation](https://docs.earthscope.org/projects/CLI).
|
|
100
|
+
|
|
101
|
+
## EarthScope SDK
|
|
102
|
+
|
|
103
|
+
If you would like to use EarthScope APIs from python, please use the [earthscope-sdk](https://gitlab.com/earthscope/public/earthscope-sdk/) directly.
|
|
104
|
+
|
|
105
|
+
## FAQ/troubleshooting
|
|
106
|
+
|
|
107
|
+
- **How long does my access token last?**
|
|
108
|
+
- Your access token lasts 24 hours. Your refresh token can be used to refresh your access token.
|
|
109
|
+
- **How long does my refresh token last?**
|
|
110
|
+
- Your refresh token will never expire - unless you are inactive (do not use it) for one year.
|
|
111
|
+
If it does expire, you will need to re-authenticate to get a new access and refresh token.
|
|
112
|
+
- **What is a refresh token and how does the CLI use it?**
|
|
113
|
+
- A refresh token is a special token that is used to renew your access token without you needing to log in again.
|
|
114
|
+
The refresh token is obtained from your access token, and using the `es user get-access-token` command will automatically
|
|
115
|
+
renew your access token if it is close to expiration. You can 'manually' refresh your access token by using the command `es user refresh-access-token`.
|
|
116
|
+
If your access token is compromised, you can revoke your refresh token using `es user revoke-refresh-token`. Once your access token expires,
|
|
117
|
+
it can no longer be renewed and you will need to re-login.
|
|
118
|
+
- **Should I hard-code my access token into my script?**
|
|
119
|
+
- No. We recommend you use the cli commands to retrieve your access tokens in your scripts.
|
|
120
|
+
This way your access token will not be compromised by anyone viewing your script.
|
|
121
|
+
The access token only lasts 24 hours and cannot be used afterwards unless refreshed.
|
|
@@ -4,13 +4,13 @@ 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" }]
|
|
11
|
-
license =
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
12
13
|
classifiers = [
|
|
13
|
-
"License :: OSI Approved :: Apache Software License",
|
|
14
14
|
"Programming Language :: Python",
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Operating System :: OS Independent",
|
|
@@ -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"
|
|
@@ -32,7 +32,7 @@ dev = [
|
|
|
32
32
|
"build",
|
|
33
33
|
"pre-commit",
|
|
34
34
|
"pytest",
|
|
35
|
-
"twine",
|
|
35
|
+
"twine>=6.0.1",
|
|
36
36
|
"pip-tools",
|
|
37
37
|
]
|
|
38
38
|
|
|
@@ -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
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: earthscope-cli
|
|
3
|
+
Version: 1.1.2
|
|
4
|
+
Summary: A CLI for the EarthScope API
|
|
5
|
+
Author-email: EarthScope <data-help@earthscope.org>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/earthscope/public/earthscope-cli
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: typer<0.24.0
|
|
15
|
+
Requires-Dist: typer-di>=0.1.3
|
|
16
|
+
Requires-Dist: earthscope-sdk>=1.3.1
|
|
17
|
+
Requires-Dist: rich>=13.8.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: black; extra == "dev"
|
|
20
|
+
Requires-Dist: bumpver; extra == "dev"
|
|
21
|
+
Requires-Dist: build; extra == "dev"
|
|
22
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: twine>=6.0.1; extra == "dev"
|
|
25
|
+
Requires-Dist: pip-tools; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# EarthScope CLI
|
|
29
|
+
|
|
30
|
+
A CLI for interacting with EarthScope's APIs
|
|
31
|
+
|
|
32
|
+
## Getting Started
|
|
33
|
+
|
|
34
|
+
### Requirements
|
|
35
|
+
|
|
36
|
+
To use the CLI you must have:
|
|
37
|
+
|
|
38
|
+
- Registered an account with Earthscope ([sign up now](https://earthscope.org/user/login)). See [here](https://www.earthscope.org/data/authentication) for more information
|
|
39
|
+
- Python >= 3.9
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
|
|
43
|
+
Install from PyPI
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
pip install earthscope-cli
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Usage
|
|
50
|
+
|
|
51
|
+
A new `es` command is available in your terminal. Use `--help` with any command to explore commands and options.
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
es --help
|
|
55
|
+
es user --help
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Login to your EarthScope account
|
|
59
|
+
|
|
60
|
+
```shell
|
|
61
|
+
es login
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will open your browser to a confirmation page with the same code shown on your command line.
|
|
65
|
+
If you are on a device that does not have a web browser, you can copy the displayed url in a browser on another device (personal computer, mobile device, etc...) and complete the confirmation there.
|
|
66
|
+
|
|
67
|
+
The `es login` command will save your token locally. If this token is deleted, you will need to re-authenticate (login) to retrieve your token again.
|
|
68
|
+
|
|
69
|
+
#### Get your access token
|
|
70
|
+
|
|
71
|
+
```shell
|
|
72
|
+
es user get-access-token
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `get-access-token` command will display your access token. If your access token is close to expiration or expired,
|
|
76
|
+
the default behavior is to automatically refresh your token.
|
|
77
|
+
|
|
78
|
+
If you want to manually refresh your token:
|
|
79
|
+
|
|
80
|
+
```shell
|
|
81
|
+
es user refresh-access-token
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Never share your tokens. If you think your token has been compromised, please revoke your refresh token and re-authenticate (login):
|
|
85
|
+
|
|
86
|
+
```shell
|
|
87
|
+
es user revoke-refresh-token
|
|
88
|
+
es login
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Get your user profile
|
|
92
|
+
|
|
93
|
+
```shell
|
|
94
|
+
es user get-profile
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
For detailed usage examples, authentication guides, and advanced features, see the [full documentation](https://docs.earthscope.org/projects/CLI).
|
|
100
|
+
|
|
101
|
+
## EarthScope SDK
|
|
102
|
+
|
|
103
|
+
If you would like to use EarthScope APIs from python, please use the [earthscope-sdk](https://gitlab.com/earthscope/public/earthscope-sdk/) directly.
|
|
104
|
+
|
|
105
|
+
## FAQ/troubleshooting
|
|
106
|
+
|
|
107
|
+
- **How long does my access token last?**
|
|
108
|
+
- Your access token lasts 24 hours. Your refresh token can be used to refresh your access token.
|
|
109
|
+
- **How long does my refresh token last?**
|
|
110
|
+
- Your refresh token will never expire - unless you are inactive (do not use it) for one year.
|
|
111
|
+
If it does expire, you will need to re-authenticate to get a new access and refresh token.
|
|
112
|
+
- **What is a refresh token and how does the CLI use it?**
|
|
113
|
+
- A refresh token is a special token that is used to renew your access token without you needing to log in again.
|
|
114
|
+
The refresh token is obtained from your access token, and using the `es user get-access-token` command will automatically
|
|
115
|
+
renew your access token if it is close to expiration. You can 'manually' refresh your access token by using the command `es user refresh-access-token`.
|
|
116
|
+
If your access token is compromised, you can revoke your refresh token using `es user revoke-refresh-token`. Once your access token expires,
|
|
117
|
+
it can no longer be renewed and you will need to re-login.
|
|
118
|
+
- **Should I hard-code my access token into my script?**
|
|
119
|
+
- No. We recommend you use the cli commands to retrieve your access tokens in your scripts.
|
|
120
|
+
This way your access token will not be compromised by anyone viewing your script.
|
|
121
|
+
The access token only lasts 24 hours and cannot be used afterwards unless refreshed.
|