kleinkram 0.36.3.dev20241113174857__py3-none-any.whl → 0.37.0__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.
Potentially problematic release.
This version of kleinkram might be problematic. Click here for more details.
- kleinkram/__init__.py +6 -0
- kleinkram/__main__.py +6 -0
- kleinkram/_version.py +6 -0
- kleinkram/api/__init__.py +0 -0
- kleinkram/api/client.py +65 -0
- kleinkram/api/file_transfer.py +337 -0
- kleinkram/api/routes.py +460 -0
- kleinkram/app.py +180 -0
- kleinkram/auth.py +96 -0
- kleinkram/commands/__init__.py +1 -0
- kleinkram/commands/download.py +103 -0
- kleinkram/commands/endpoint.py +62 -0
- kleinkram/commands/list.py +93 -0
- kleinkram/commands/mission.py +57 -0
- kleinkram/commands/project.py +24 -0
- kleinkram/commands/upload.py +138 -0
- kleinkram/commands/verify.py +117 -0
- kleinkram/config.py +171 -0
- kleinkram/consts.py +8 -1
- kleinkram/core.py +14 -0
- kleinkram/enums.py +10 -0
- kleinkram/errors.py +59 -0
- kleinkram/main.py +6 -489
- kleinkram/models.py +186 -0
- kleinkram/utils.py +179 -0
- {kleinkram-0.36.3.dev20241113174857.dist-info/licenses → kleinkram-0.37.0.dist-info}/LICENSE +1 -1
- kleinkram-0.37.0.dist-info/METADATA +113 -0
- kleinkram-0.37.0.dist-info/RECORD +33 -0
- {kleinkram-0.36.3.dev20241113174857.dist-info → kleinkram-0.37.0.dist-info}/WHEEL +2 -1
- kleinkram-0.37.0.dist-info/entry_points.txt +2 -0
- kleinkram-0.37.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_utils.py +153 -0
- kleinkram/api_client.py +0 -63
- kleinkram/auth/auth.py +0 -160
- kleinkram/endpoint/endpoint.py +0 -58
- kleinkram/error_handling.py +0 -177
- kleinkram/file/file.py +0 -144
- kleinkram/helper.py +0 -272
- kleinkram/mission/mission.py +0 -310
- kleinkram/project/project.py +0 -138
- kleinkram/queue/queue.py +0 -8
- kleinkram/tag/tag.py +0 -71
- kleinkram/topic/topic.py +0 -55
- kleinkram/user/user.py +0 -75
- kleinkram-0.36.3.dev20241113174857.dist-info/METADATA +0 -24
- kleinkram-0.36.3.dev20241113174857.dist-info/RECORD +0 -20
- kleinkram-0.36.3.dev20241113174857.dist-info/entry_points.txt +0 -2
kleinkram/config.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict
|
|
10
|
+
from typing import NamedTuple
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from kleinkram._version import __local__
|
|
14
|
+
from kleinkram._version import __version__
|
|
15
|
+
from kleinkram.errors import CorruptedConfigFile
|
|
16
|
+
from kleinkram.errors import InvalidConfigFile
|
|
17
|
+
|
|
18
|
+
CONFIG_PATH = Path().home() / ".kleinkram.json"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Environment(Enum):
|
|
22
|
+
LOCAL = "local"
|
|
23
|
+
DEV = "dev"
|
|
24
|
+
PROD = "prod"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DEFAULT_API = {
|
|
28
|
+
Environment.LOCAL: "http://localhost:3000",
|
|
29
|
+
Environment.DEV: "https://api.datasets.dev.leggedrobotics.com",
|
|
30
|
+
Environment.PROD: "https://api.datasets.leggedrobotics.com",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
LOCAL_S3 = "http://localhost:9000"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_env() -> Environment:
|
|
37
|
+
if __local__:
|
|
38
|
+
return Environment.LOCAL
|
|
39
|
+
if "dev" in __version__:
|
|
40
|
+
return Environment.DEV
|
|
41
|
+
return Environment.PROD
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_default_endpoints() -> str:
|
|
45
|
+
env = get_env()
|
|
46
|
+
return DEFAULT_API[env]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Credentials(NamedTuple):
|
|
50
|
+
auth_token: Optional[str] = None
|
|
51
|
+
refresh_token: Optional[str] = None
|
|
52
|
+
cli_key: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
JSON_ENDPOINT_KEY = "endpoint"
|
|
56
|
+
JSON_CREDENTIALS_KEY = "credentials"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Config:
|
|
60
|
+
endpoint: str
|
|
61
|
+
credentials: Dict[str, Credentials]
|
|
62
|
+
|
|
63
|
+
def __init__(self, overwrite: bool = False) -> None:
|
|
64
|
+
default_endpoint = get_default_endpoints()
|
|
65
|
+
|
|
66
|
+
self.credentials = {}
|
|
67
|
+
self.endpoint = default_endpoint
|
|
68
|
+
|
|
69
|
+
if not CONFIG_PATH.exists():
|
|
70
|
+
self.save()
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
self._read_config()
|
|
74
|
+
except (InvalidConfigFile, CorruptedConfigFile):
|
|
75
|
+
if not overwrite:
|
|
76
|
+
self.credentials = {}
|
|
77
|
+
self.endpoint = default_endpoint
|
|
78
|
+
self.save()
|
|
79
|
+
else:
|
|
80
|
+
raise
|
|
81
|
+
|
|
82
|
+
def _read_config(self) -> None:
|
|
83
|
+
with open(CONFIG_PATH, "r") as file:
|
|
84
|
+
try:
|
|
85
|
+
content = json.load(file)
|
|
86
|
+
except Exception:
|
|
87
|
+
raise CorruptedConfigFile
|
|
88
|
+
|
|
89
|
+
endpoint = content.get(JSON_ENDPOINT_KEY, None)
|
|
90
|
+
if not isinstance(endpoint, str):
|
|
91
|
+
raise InvalidConfigFile
|
|
92
|
+
|
|
93
|
+
credentials = content.get(JSON_CREDENTIALS_KEY, None)
|
|
94
|
+
if not isinstance(credentials, dict):
|
|
95
|
+
raise InvalidConfigFile
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
parsed_creds = {}
|
|
99
|
+
for ep, creds in credentials.items():
|
|
100
|
+
parsed_creds[ep] = Credentials(**creds)
|
|
101
|
+
except Exception:
|
|
102
|
+
raise InvalidConfigFile
|
|
103
|
+
|
|
104
|
+
self.endpoint = endpoint
|
|
105
|
+
self.credentials = parsed_creds
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def has_cli_key(self) -> bool:
|
|
109
|
+
if self.endpoint not in self.credentials:
|
|
110
|
+
return False
|
|
111
|
+
return self.credentials[self.endpoint].cli_key is not None
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def has_refresh_token(self) -> bool:
|
|
115
|
+
if self.endpoint not in self.credentials:
|
|
116
|
+
return False
|
|
117
|
+
return self.credentials[self.endpoint].refresh_token is not None
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def auth_token(self) -> Optional[str]:
|
|
121
|
+
return self.credentials[self.endpoint].auth_token
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def refresh_token(self) -> Optional[str]:
|
|
125
|
+
return self.credentials[self.endpoint].refresh_token
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def cli_key(self) -> Optional[str]:
|
|
129
|
+
return self.credentials[self.endpoint].cli_key
|
|
130
|
+
|
|
131
|
+
def save(self) -> None:
|
|
132
|
+
serialized_tokens = {}
|
|
133
|
+
for endpoint, auth in self.credentials.items():
|
|
134
|
+
serialized_tokens[endpoint] = auth._asdict()
|
|
135
|
+
|
|
136
|
+
data = {
|
|
137
|
+
JSON_ENDPOINT_KEY: self.endpoint,
|
|
138
|
+
JSON_CREDENTIALS_KEY: serialized_tokens,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# atomically write to file
|
|
142
|
+
fd, tmp_path = tempfile.mkstemp()
|
|
143
|
+
with open(fd, "w") as file:
|
|
144
|
+
json.dump(data, file)
|
|
145
|
+
|
|
146
|
+
os.replace(tmp_path, CONFIG_PATH)
|
|
147
|
+
|
|
148
|
+
def clear_credentials(self, all: bool = False) -> None:
|
|
149
|
+
if all:
|
|
150
|
+
self.credentials = {}
|
|
151
|
+
elif self.endpoint in self.credentials:
|
|
152
|
+
del self.credentials[self.endpoint]
|
|
153
|
+
self.save()
|
|
154
|
+
|
|
155
|
+
def save_credentials(self, creds: Credentials) -> None:
|
|
156
|
+
self.credentials[self.endpoint] = creds
|
|
157
|
+
self.save()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class _SharedState:
|
|
162
|
+
log_file: Optional[Path] = None
|
|
163
|
+
verbose: bool = True
|
|
164
|
+
debug: bool = False
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
SHARED_STATE = _SharedState()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_shared_state() -> _SharedState:
|
|
171
|
+
return SHARED_STATE
|
kleinkram/consts.py
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
LOCAL_API_URL = "http://localhost:3000"
|
|
4
|
+
LOCAL_S3_URL = "http://localhost:9000"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
API_URL = "http://localhost:3000"
|
|
8
|
+
# API_URL = "https://api.datasets.leggedrobotics.com"
|
kleinkram/core.py
ADDED
kleinkram/enums.py
ADDED
kleinkram/errors.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
LOGIN_MESSAGE = "Please login using `klein login`."
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InvalidMissionSpec(Exception): ...
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InvalidFileSpec(Exception): ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MissionExists(Exception): ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MissionDoesNotExist(Exception): ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NoPermission(Exception): ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AccessDeniedException(Exception):
|
|
22
|
+
def __init__(self, message: str, api_error: str):
|
|
23
|
+
self.message = message
|
|
24
|
+
self.api_error = api_error
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NotAuthenticatedException(Exception):
|
|
28
|
+
def __init__(self, endpoint: str):
|
|
29
|
+
message = (
|
|
30
|
+
f"You are not authenticated on endpoint '{endpoint}'.\n{LOGIN_MESSAGE}"
|
|
31
|
+
)
|
|
32
|
+
super().__init__(message)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CorruptedFile(Exception): ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NameIsValidUUID(Exception): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class UploadFailed(Exception): ...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InvalidCLIVersion(Exception): ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FileTypeNotSupported(Exception): ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class InvalidConfigFile(Exception):
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
super().__init__("Invalid config file.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CorruptedConfigFile(Exception):
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
super().__init__(
|
|
58
|
+
"Config file is corrupted.\nPlease run `klein login` to re-authenticate."
|
|
59
|
+
)
|