kleinkram 0.51.0.dev20251002115350__py3-none-any.whl → 0.51.0.dev20251003120233__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/api/deser.py +20 -2
- kleinkram/api/file_transfer.py +14 -13
- kleinkram/api/routes.py +97 -15
- kleinkram/cli/_mission.py +16 -4
- kleinkram/core.py +5 -2
- kleinkram/errors.py +2 -0
- kleinkram/models.py +1 -0
- kleinkram/printing.py +2 -1
- {kleinkram-0.51.0.dev20251002115350.dist-info → kleinkram-0.51.0.dev20251003120233.dist-info}/METADATA +1 -1
- {kleinkram-0.51.0.dev20251002115350.dist-info → kleinkram-0.51.0.dev20251003120233.dist-info}/RECORD +13 -13
- {kleinkram-0.51.0.dev20251002115350.dist-info → kleinkram-0.51.0.dev20251003120233.dist-info}/WHEEL +0 -0
- {kleinkram-0.51.0.dev20251002115350.dist-info → kleinkram-0.51.0.dev20251003120233.dist-info}/entry_points.txt +0 -0
- {kleinkram-0.51.0.dev20251002115350.dist-info → kleinkram-0.51.0.dev20251003120233.dist-info}/top_level.txt +0 -0
kleinkram/api/deser.py
CHANGED
|
@@ -7,12 +7,13 @@ from typing import Dict
|
|
|
7
7
|
from typing import Literal
|
|
8
8
|
from typing import NewType
|
|
9
9
|
from typing import Tuple
|
|
10
|
+
from typing import List
|
|
10
11
|
from uuid import UUID
|
|
11
12
|
|
|
12
13
|
import dateutil.parser
|
|
13
14
|
|
|
14
15
|
from kleinkram.errors import ParsingError
|
|
15
|
-
from kleinkram.models import File
|
|
16
|
+
from kleinkram.models import File, MetadataValue
|
|
16
17
|
from kleinkram.models import FileState
|
|
17
18
|
from kleinkram.models import Mission
|
|
18
19
|
from kleinkram.models import Project
|
|
@@ -51,6 +52,7 @@ class MissionObjectKeys(str, Enum):
|
|
|
51
52
|
DESCRIPTION = "description"
|
|
52
53
|
CREATED_AT = "createdAt"
|
|
53
54
|
UPDATED_AT = "updatedAt"
|
|
55
|
+
TAGS = "tags"
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
class ProjectObjectKeys(str, Enum):
|
|
@@ -59,6 +61,7 @@ class ProjectObjectKeys(str, Enum):
|
|
|
59
61
|
DESCRIPTION = "description"
|
|
60
62
|
CREATED_AT = "createdAt"
|
|
61
63
|
UPDATED_AT = "updatedAt"
|
|
64
|
+
REQUIRED_TAGS = "requiredTags"
|
|
62
65
|
|
|
63
66
|
|
|
64
67
|
def _get_nested_info(data, key: Literal["mission", "project"]) -> Tuple[UUID, str]:
|
|
@@ -82,6 +85,19 @@ def _parse_file_state(state: str) -> FileState:
|
|
|
82
85
|
except ValueError as e:
|
|
83
86
|
raise ParsingError(f"error parsing file state: {state}") from e
|
|
84
87
|
|
|
88
|
+
def _parse_metadata(tags: List[Dict]) -> Dict[str, MetadataValue]:
|
|
89
|
+
result = {}
|
|
90
|
+
try:
|
|
91
|
+
for tag in tags:
|
|
92
|
+
entry = {tag.get("name"): MetadataValue(tag.get("valueAsString"), tag.get("datatype"))}
|
|
93
|
+
result.update(entry)
|
|
94
|
+
return result
|
|
95
|
+
except ValueError as e:
|
|
96
|
+
raise ParsingError(f"error parsing metadata: {e}") from e
|
|
97
|
+
|
|
98
|
+
def _parse_required_tags(tags: List[Dict]) -> list[str]:
|
|
99
|
+
return list(_parse_metadata(tags).keys())
|
|
100
|
+
|
|
85
101
|
|
|
86
102
|
def _parse_project(project_object: ProjectObject) -> Project:
|
|
87
103
|
try:
|
|
@@ -90,6 +106,7 @@ def _parse_project(project_object: ProjectObject) -> Project:
|
|
|
90
106
|
description = project_object[ProjectObjectKeys.DESCRIPTION]
|
|
91
107
|
created_at = _parse_datetime(project_object[ProjectObjectKeys.CREATED_AT])
|
|
92
108
|
updated_at = _parse_datetime(project_object[ProjectObjectKeys.UPDATED_AT])
|
|
109
|
+
required_tags = _parse_required_tags(project_object[ProjectObjectKeys.REQUIRED_TAGS])
|
|
93
110
|
except Exception as e:
|
|
94
111
|
raise ParsingError(f"error parsing project: {project_object}") from e
|
|
95
112
|
return Project(
|
|
@@ -98,6 +115,7 @@ def _parse_project(project_object: ProjectObject) -> Project:
|
|
|
98
115
|
description=description,
|
|
99
116
|
created_at=created_at,
|
|
100
117
|
updated_at=updated_at,
|
|
118
|
+
required_tags=required_tags,
|
|
101
119
|
)
|
|
102
120
|
|
|
103
121
|
|
|
@@ -107,7 +125,7 @@ def _parse_mission(mission: MissionObject) -> Mission:
|
|
|
107
125
|
name = mission[MissionObjectKeys.NAME]
|
|
108
126
|
created_at = _parse_datetime(mission[MissionObjectKeys.CREATED_AT])
|
|
109
127
|
updated_at = _parse_datetime(mission[MissionObjectKeys.UPDATED_AT])
|
|
110
|
-
metadata =
|
|
128
|
+
metadata = _parse_metadata(mission[MissionObjectKeys.TAGS])
|
|
111
129
|
|
|
112
130
|
project_id, project_name = _get_nested_info(mission, PROJECT)
|
|
113
131
|
|
kleinkram/api/file_transfer.py
CHANGED
|
@@ -572,19 +572,20 @@ def upload_files(
|
|
|
572
572
|
|
|
573
573
|
avg_speed_bps = total_uploaded_bytes / elapsed_time if elapsed_time > 0 else 0
|
|
574
574
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
575
|
+
if verbose:
|
|
576
|
+
console.print(f"Upload took {elapsed_time:.2f} seconds")
|
|
577
|
+
console.print(f"Total uploaded: {format_bytes(total_uploaded_bytes)}")
|
|
578
|
+
console.print(f"Average speed: {format_bytes(avg_speed_bps, speed=True)}")
|
|
579
|
+
|
|
580
|
+
if failed_files > 0:
|
|
581
|
+
console.print(
|
|
582
|
+
f"\nUploaded {len(files) - failed_files - skipped_files} files, {skipped_files} skipped, {failed_files} uploads failed",
|
|
583
|
+
style="red",
|
|
584
|
+
)
|
|
585
|
+
else:
|
|
586
|
+
console.print(
|
|
587
|
+
f"\nUploaded {len(files) - skipped_files} files, {skipped_files} skipped"
|
|
588
|
+
)
|
|
588
589
|
|
|
589
590
|
|
|
590
591
|
def download_files(
|
kleinkram/api/routes.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import tempfile
|
|
4
5
|
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
from typing import Any
|
|
6
8
|
from typing import Dict
|
|
7
9
|
from typing import Generator
|
|
@@ -40,10 +42,11 @@ from kleinkram.errors import MissionExists
|
|
|
40
42
|
from kleinkram.errors import MissionNotFound
|
|
41
43
|
from kleinkram.errors import ProjectExists
|
|
42
44
|
from kleinkram.errors import ProjectNotFound
|
|
45
|
+
from kleinkram.errors import MissionValidationError
|
|
43
46
|
from kleinkram.models import File
|
|
44
47
|
from kleinkram.models import Mission
|
|
45
48
|
from kleinkram.models import Project
|
|
46
|
-
from kleinkram.utils import is_valid_uuid4
|
|
49
|
+
from kleinkram.utils import is_valid_uuid4, split_args
|
|
47
50
|
|
|
48
51
|
__all__ = [
|
|
49
52
|
"_get_api_version",
|
|
@@ -220,6 +223,28 @@ def _mission_name_is_available(
|
|
|
220
223
|
return False
|
|
221
224
|
|
|
222
225
|
|
|
226
|
+
def _validate_mission_name(
|
|
227
|
+
client: AuthenticatedClient, project_id: UUID, mission_name: str
|
|
228
|
+
) -> None:
|
|
229
|
+
if not _mission_name_is_available(client, mission_name, project_id):
|
|
230
|
+
raise MissionExists(
|
|
231
|
+
f"Mission with name: `{mission_name}` already exists"
|
|
232
|
+
f" in project: {project_id}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if is_valid_uuid4(mission_name):
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"Mission name: `{mission_name}` is a valid UUIDv4, "
|
|
238
|
+
"mission names must not be valid UUIDv4's"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if mission_name.endswith(" "):
|
|
242
|
+
raise ValueError(
|
|
243
|
+
"A mission name cannot end with a whitespace. "
|
|
244
|
+
f"The given mission name was \'{mission_name}\'"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
223
248
|
def _project_name_is_available(client: AuthenticatedClient, project_name: str) -> bool:
|
|
224
249
|
project_query = ProjectQuery(patterns=[project_name])
|
|
225
250
|
try:
|
|
@@ -228,6 +253,47 @@ def _project_name_is_available(client: AuthenticatedClient, project_name: str) -
|
|
|
228
253
|
return True
|
|
229
254
|
return False
|
|
230
255
|
|
|
256
|
+
def _validate_mission_created(client: AuthenticatedClient, project_id: str, mission_name: str) -> None:
|
|
257
|
+
"""
|
|
258
|
+
validate that a mission is successfully created
|
|
259
|
+
"""
|
|
260
|
+
mission_ids, mission_patterns = split_args([mission_name])
|
|
261
|
+
project_ids, project_patterns = split_args([project_id])
|
|
262
|
+
|
|
263
|
+
project_query = ProjectQuery(ids=project_ids, patterns=project_patterns)
|
|
264
|
+
mission_query = MissionQuery(
|
|
265
|
+
ids=mission_ids,
|
|
266
|
+
patterns=mission_patterns,
|
|
267
|
+
project_query=project_query,
|
|
268
|
+
)
|
|
269
|
+
try:
|
|
270
|
+
with tempfile.NamedTemporaryFile(suffix=".mcap", delete=False) as tmp:
|
|
271
|
+
tmp.write(b"dummy content")
|
|
272
|
+
tmp_path = Path(tmp.name)
|
|
273
|
+
|
|
274
|
+
kleinkram.core.upload(
|
|
275
|
+
client=client,
|
|
276
|
+
query=mission_query,
|
|
277
|
+
file_paths=[tmp_path],
|
|
278
|
+
verbose=False,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
file_query = FileQuery(
|
|
282
|
+
ids=[],
|
|
283
|
+
patterns=[tmp_path.name],
|
|
284
|
+
mission_query=mission_query,
|
|
285
|
+
)
|
|
286
|
+
file_parsed = get_file(client, file_query)
|
|
287
|
+
|
|
288
|
+
kleinkram.core.delete_files(client=client, file_ids=[file_parsed.id])
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
raise MissionValidationError(f"Mission validation failed: {e}")
|
|
292
|
+
|
|
293
|
+
finally:
|
|
294
|
+
if tmp_path.exists():
|
|
295
|
+
tmp_path.unlink()
|
|
296
|
+
|
|
231
297
|
|
|
232
298
|
def _create_mission(
|
|
233
299
|
client: AuthenticatedClient,
|
|
@@ -236,6 +302,7 @@ def _create_mission(
|
|
|
236
302
|
*,
|
|
237
303
|
metadata: Optional[Dict[str, str]] = None,
|
|
238
304
|
ignore_missing_tags: bool = False,
|
|
305
|
+
required_tags: Optional[List[str]] = None,
|
|
239
306
|
) -> UUID:
|
|
240
307
|
"""\
|
|
241
308
|
creates a new mission with the given name and project_id
|
|
@@ -246,16 +313,11 @@ def _create_mission(
|
|
|
246
313
|
if metadata is None:
|
|
247
314
|
metadata = {}
|
|
248
315
|
|
|
249
|
-
|
|
250
|
-
raise MissionExists(
|
|
251
|
-
f"Mission with name: `{mission_name}` already exists"
|
|
252
|
-
f" in project: {project_id}"
|
|
253
|
-
)
|
|
316
|
+
_validate_mission_name(client, project_id, mission_name)
|
|
254
317
|
|
|
255
|
-
if
|
|
256
|
-
raise
|
|
257
|
-
f"Mission
|
|
258
|
-
"mission names must not be valid UUIDv4's"
|
|
318
|
+
if required_tags and not set(required_tags).issubset(metadata.keys()):
|
|
319
|
+
raise InvalidMissionMetadata(
|
|
320
|
+
f"Mission tags `{required_tags}` are required but missing from metadata: {metadata}"
|
|
259
321
|
)
|
|
260
322
|
|
|
261
323
|
# we need to translate tag keys to tag type ids
|
|
@@ -267,9 +329,9 @@ def _create_mission(
|
|
|
267
329
|
"tags": {str(k): v for k, v in tags.items()},
|
|
268
330
|
"ignoreTags": ignore_missing_tags,
|
|
269
331
|
}
|
|
270
|
-
|
|
271
332
|
resp = client.post(CREATE_MISSION, json=payload)
|
|
272
333
|
resp.raise_for_status()
|
|
334
|
+
_validate_mission_created(client, str(project_id), mission_name)
|
|
273
335
|
|
|
274
336
|
return UUID(resp.json()["uuid"], version=4)
|
|
275
337
|
|
|
@@ -288,18 +350,37 @@ def _create_project(
|
|
|
288
350
|
return UUID(resp.json()["uuid"], version=4)
|
|
289
351
|
|
|
290
352
|
|
|
353
|
+
def _validate_tag_value(
|
|
354
|
+
tag_value, tag_datatype
|
|
355
|
+
) -> None:
|
|
356
|
+
if tag_datatype == "NUMBER":
|
|
357
|
+
try:
|
|
358
|
+
float(tag_value)
|
|
359
|
+
except:
|
|
360
|
+
raise InvalidMissionMetadata(f"Value '{tag_value}' is not a valid NUMBER")
|
|
361
|
+
elif tag_datatype == "BOOLEAN":
|
|
362
|
+
if tag_value.lower() not in {"true", "false"}:
|
|
363
|
+
raise InvalidMissionMetadata(f"Value '{tag_value}' is not a valid BOOLEAN (expected 'true' or 'false')")
|
|
364
|
+
else:
|
|
365
|
+
pass # any string is fine
|
|
366
|
+
#TODO: add check for LOCATION tag datatype
|
|
367
|
+
|
|
368
|
+
|
|
291
369
|
def _get_metadata_type_id_by_name(
|
|
292
370
|
client: AuthenticatedClient, tag_name: str
|
|
293
|
-
) -> Optional[UUID]:
|
|
371
|
+
) -> Tuple[Optional[UUID], str]:
|
|
294
372
|
resp = client.get(TAG_TYPE_BY_NAME, params={"name": tag_name, "take": 1})
|
|
295
373
|
|
|
296
374
|
if resp.status_code in (403, 404):
|
|
297
375
|
return None
|
|
298
376
|
|
|
299
377
|
resp.raise_for_status()
|
|
378
|
+
try:
|
|
379
|
+
data = resp.json()['data'][0]
|
|
380
|
+
except IndexError:
|
|
381
|
+
return None, None
|
|
300
382
|
|
|
301
|
-
data =
|
|
302
|
-
return UUID(data["uuid"], version=4)
|
|
383
|
+
return UUID(data["uuid"], version=4), data["datatype"]
|
|
303
384
|
|
|
304
385
|
|
|
305
386
|
def _get_tags_map(
|
|
@@ -309,9 +390,10 @@ def _get_tags_map(
|
|
|
309
390
|
# why are we using metadata type ids as keys???
|
|
310
391
|
ret = {}
|
|
311
392
|
for key, val in metadata.items():
|
|
312
|
-
metadata_type_id = _get_metadata_type_id_by_name(client, key)
|
|
393
|
+
metadata_type_id, tag_datatype = _get_metadata_type_id_by_name(client, key)
|
|
313
394
|
if metadata_type_id is None:
|
|
314
395
|
raise InvalidMissionMetadata(f"metadata field: {key} does not exist")
|
|
396
|
+
_validate_tag_value(val, tag_datatype)
|
|
315
397
|
ret[metadata_type_id] = val
|
|
316
398
|
return ret
|
|
317
399
|
|
kleinkram/cli/_mission.py
CHANGED
|
@@ -13,6 +13,7 @@ from kleinkram.api.query import ProjectQuery
|
|
|
13
13
|
from kleinkram.api.routes import get_mission
|
|
14
14
|
from kleinkram.api.routes import get_project
|
|
15
15
|
from kleinkram.config import get_shared_state
|
|
16
|
+
from kleinkram.errors import InvalidMissionQuery
|
|
16
17
|
from kleinkram.printing import print_mission_info
|
|
17
18
|
from kleinkram.utils import load_metadata
|
|
18
19
|
from kleinkram.utils import split_args
|
|
@@ -45,13 +46,16 @@ def create(
|
|
|
45
46
|
metadata_dct = load_metadata(Path(metadata)) if metadata else {} # noqa
|
|
46
47
|
|
|
47
48
|
client = AuthenticatedClient()
|
|
48
|
-
|
|
49
|
+
project = get_project(client, project_query)
|
|
50
|
+
project_id = project.id
|
|
51
|
+
project_required_tags = project.required_tags
|
|
49
52
|
mission_id = kleinkram.api.routes._create_mission(
|
|
50
53
|
client,
|
|
51
54
|
project_id,
|
|
52
55
|
mission_name,
|
|
53
56
|
metadata=metadata_dct,
|
|
54
57
|
ignore_missing_tags=ignore_missing_tags,
|
|
58
|
+
required_tags=project_required_tags,
|
|
55
59
|
)
|
|
56
60
|
|
|
57
61
|
mission_parsed = get_mission(client, MissionQuery(ids=[mission_id]))
|
|
@@ -120,9 +124,6 @@ def delete(
|
|
|
120
124
|
False, "--confirm", "-y", "--yes", help="confirm deletion"
|
|
121
125
|
),
|
|
122
126
|
) -> None:
|
|
123
|
-
if not confirm:
|
|
124
|
-
typer.confirm(f"delete {project} {mission}", abort=True)
|
|
125
|
-
|
|
126
127
|
project_ids, project_patterns = split_args([project] if project else [])
|
|
127
128
|
project_query = ProjectQuery(ids=project_ids, patterns=project_patterns)
|
|
128
129
|
|
|
@@ -132,9 +133,20 @@ def delete(
|
|
|
132
133
|
patterns=mission_patterns,
|
|
133
134
|
project_query=project_query,
|
|
134
135
|
)
|
|
136
|
+
if mission_patterns and not (project_patterns or project_ids):
|
|
137
|
+
raise InvalidMissionQuery(
|
|
138
|
+
"Mission query does not uniquely determine mission. "
|
|
139
|
+
"Project name or id must be specified when deleting by mission name"
|
|
140
|
+
)
|
|
135
141
|
|
|
136
142
|
client = AuthenticatedClient()
|
|
137
143
|
mission_parsed = get_mission(client, mission_query)
|
|
144
|
+
if not confirm:
|
|
145
|
+
if project:
|
|
146
|
+
typer.confirm(f"delete {project} {mission}", abort=True)
|
|
147
|
+
else:
|
|
148
|
+
typer.confirm(f"delete {mission_parsed.name} {mission}", abort=True)
|
|
149
|
+
|
|
138
150
|
kleinkram.core.delete_mission(client=client, mission_id=mission_parsed.id)
|
|
139
151
|
|
|
140
152
|
|
kleinkram/core.py
CHANGED
|
@@ -107,9 +107,11 @@ def upload(
|
|
|
107
107
|
|
|
108
108
|
if create and mission is None:
|
|
109
109
|
# check if project exists and get its id at the same time
|
|
110
|
-
|
|
110
|
+
project = kleinkram.api.routes.get_project(
|
|
111
111
|
client, query=query.project_query
|
|
112
|
-
)
|
|
112
|
+
)
|
|
113
|
+
project_id = project.id
|
|
114
|
+
project_required_tags = project.required_tags
|
|
113
115
|
mission_name = check_mission_query_is_creatable(query)
|
|
114
116
|
kleinkram.api.routes._create_mission(
|
|
115
117
|
client,
|
|
@@ -117,6 +119,7 @@ def upload(
|
|
|
117
119
|
mission_name,
|
|
118
120
|
metadata=metadata or {},
|
|
119
121
|
ignore_missing_tags=ignore_missing_metadata,
|
|
122
|
+
required_tags=project_required_tags,
|
|
120
123
|
)
|
|
121
124
|
mission = kleinkram.api.routes.get_mission(client, query)
|
|
122
125
|
|
kleinkram/errors.py
CHANGED
kleinkram/models.py
CHANGED
kleinkram/printing.py
CHANGED
|
@@ -233,7 +233,7 @@ def file_info_table(file: File) -> Table:
|
|
|
233
233
|
|
|
234
234
|
|
|
235
235
|
def mission_info_table(
|
|
236
|
-
mission: Mission, print_metadata: bool =
|
|
236
|
+
mission: Mission, print_metadata: bool = True
|
|
237
237
|
) -> Tuple[Table, ...]:
|
|
238
238
|
table = Table("k", "v", title=f"mission info: {mission.name}", show_header=False)
|
|
239
239
|
|
|
@@ -269,6 +269,7 @@ def project_info_table(project: Project) -> Table:
|
|
|
269
269
|
table.add_row("description", project.description)
|
|
270
270
|
table.add_row("created", str(project.created_at))
|
|
271
271
|
table.add_row("updated", str(project.updated_at))
|
|
272
|
+
table.add_row("required tags", ", ".join(project.required_tags))
|
|
272
273
|
|
|
273
274
|
return table
|
|
274
275
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kleinkram
|
|
3
|
-
Version: 0.51.0.
|
|
3
|
+
Version: 0.51.0.dev20251003120233
|
|
4
4
|
Summary: give me your bags
|
|
5
5
|
Author: Cyrill Püntener, Dominique Garmier, Johann Schwabe
|
|
6
6
|
Author-email: pucyril@ethz.ch, dgarmier@ethz.ch, jschwab@ethz.ch
|
{kleinkram-0.51.0.dev20251002115350.dist-info → kleinkram-0.51.0.dev20251003120233.dist-info}/RECORD
RENAMED
|
@@ -3,28 +3,28 @@ kleinkram/__main__.py,sha256=B9RiZxfO4jpCmWPUHyKJ7_EoZlEG4sPpH-nz7T_YhhQ,125
|
|
|
3
3
|
kleinkram/_version.py,sha256=QYJyRTcqFcJj4qWYpqs7WcoOP6jxDMqyvxLY-cD6KcE,129
|
|
4
4
|
kleinkram/auth.py,sha256=PdSYZZO8AauNLZbn9PBgPM3o-O_nwoOKTj94EGnPRE8,3003
|
|
5
5
|
kleinkram/config.py,sha256=nx6uSM5nLP4SKe8b9VAx4KDtCCwtyshXmzbEJcUwpsY,7411
|
|
6
|
-
kleinkram/core.py,sha256=
|
|
7
|
-
kleinkram/errors.py,sha256=
|
|
6
|
+
kleinkram/core.py,sha256=rAMHiH9uwaNcAbVo5NVMl40ArC0Jdp3M9aN2aEBMOCg,9750
|
|
7
|
+
kleinkram/errors.py,sha256=270MC1V-2DDPpER3Dq2F6_aVGDrv4yaYX-oxsqyE1K0,1011
|
|
8
8
|
kleinkram/main.py,sha256=BTE0mZN__xd46wBhFi6iBlK9eGGQvJ1LdUMsbnysLi0,172
|
|
9
|
-
kleinkram/models.py,sha256=
|
|
10
|
-
kleinkram/printing.py,sha256=
|
|
9
|
+
kleinkram/models.py,sha256=0C_TharLDHA4RCe6Plas9N_uO_teN1Z4iP70WljOAfs,1899
|
|
10
|
+
kleinkram/printing.py,sha256=9o4UQq9MYkGwMIlTchbdMLjUROdJWB100Lq1b3OFfko,12280
|
|
11
11
|
kleinkram/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
kleinkram/types.py,sha256=nfDjj8TB1Jn5vqO0Xg6qhLOuKom9DDhe62BrngqnVGM,185
|
|
13
13
|
kleinkram/utils.py,sha256=AtaTvEQ0TrGaQtZylwniE9l1u7_IRYigLT2bc_jc-lQ,6790
|
|
14
14
|
kleinkram/wrappers.py,sha256=ZScoEov5Q6D2rvaJJ8E-4f58P_NGWrGc9mRPYxSqOC0,13127
|
|
15
15
|
kleinkram/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
kleinkram/api/client.py,sha256=VwuT97_WdbDpcVGwMXB0fRnUoQnUSf7BOP5eXUFokfI,5932
|
|
17
|
-
kleinkram/api/deser.py,sha256=
|
|
18
|
-
kleinkram/api/file_transfer.py,sha256=
|
|
17
|
+
kleinkram/api/deser.py,sha256=ypAxYi6pTxp6PSNsm9i3oaT1kdri9XxEN0_SnSPzbVw,5517
|
|
18
|
+
kleinkram/api/file_transfer.py,sha256=Ija34JXaszZR7_hvb08aVzq-DB2KG3ze-qqb7zjrchQ,19985
|
|
19
19
|
kleinkram/api/pagination.py,sha256=P_zPsBKlMWkmAv-YfUNHaGW-XLB_4U8BDMrKyiDFIXk,1370
|
|
20
20
|
kleinkram/api/query.py,sha256=9Exi4hJR7Ml38_zjAcOvSEoIAxZLlpM6QwwzO9fs5Gk,3293
|
|
21
|
-
kleinkram/api/routes.py,sha256=
|
|
21
|
+
kleinkram/api/routes.py,sha256=VKxwaESg-HdUP5urqUYMr52VLpPD9EuhHEWK7CR-skw,15052
|
|
22
22
|
kleinkram/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
kleinkram/cli/_download.py,sha256=e0fDyp_CFOdbKIUGKmtITvAVINa6STYJk5w5QlElXSs,2394
|
|
24
24
|
kleinkram/cli/_endpoint.py,sha256=oY0p4bnuHLEDJCXtTmir4AHswcKAygZ8I4IWC3RFcKc,1796
|
|
25
25
|
kleinkram/cli/_file.py,sha256=Q2fLDdUyfHFmdGC6wIxMqgEl0F76qszhzWJrRV5rTBM,2973
|
|
26
26
|
kleinkram/cli/_list.py,sha256=5gI3aIUeKC0_eWPQqdFXSBBFvpkTTJSm31TamHa197c,3090
|
|
27
|
-
kleinkram/cli/_mission.py,sha256=
|
|
27
|
+
kleinkram/cli/_mission.py,sha256=3ZMPRlPZIvJwmFQqeXu6N8DcmYtSVGj4xWHuAdKAlsc,5845
|
|
28
28
|
kleinkram/cli/_project.py,sha256=N0C96NC_onCEwTteYp2wgkkwkdJt-1q43LFdqNXfjC8,3398
|
|
29
29
|
kleinkram/cli/_upload.py,sha256=8gKg_QIFUJh4Sz06EEX3gQoa3Rl41KTdZWqtsntypJU,3531
|
|
30
30
|
kleinkram/cli/_verify.py,sha256=n9QThY0JnqaIqw6udYXdRQGcpUl2lIbFXGQIgpTnDPE,2112
|
|
@@ -43,8 +43,8 @@ tests/test_printing.py,sha256=kPzpIQOtQJ9yQ32mM8cMGDVOGsbrZZLQhfsXN1Pe68Q,2231
|
|
|
43
43
|
tests/test_query.py,sha256=fExmCKXLA7-9j2S2sF_sbvRX_2s6Cp3a7OTcqE25q9g,3864
|
|
44
44
|
tests/test_utils.py,sha256=eUBYrn3xrcgcaxm1X4fqZaX4tRvkbI6rh6BUbNbu9T0,4784
|
|
45
45
|
tests/test_wrappers.py,sha256=TbcTyO2L7fslbzgfDdcVZkencxNQ8cGPZm_iB6c9d6Q,2673
|
|
46
|
-
kleinkram-0.51.0.
|
|
47
|
-
kleinkram-0.51.0.
|
|
48
|
-
kleinkram-0.51.0.
|
|
49
|
-
kleinkram-0.51.0.
|
|
50
|
-
kleinkram-0.51.0.
|
|
46
|
+
kleinkram-0.51.0.dev20251003120233.dist-info/METADATA,sha256=wl07qC6n8Kv_fKqgQ-15o6dI40ydyUOYRK23ZIYRgcc,2846
|
|
47
|
+
kleinkram-0.51.0.dev20251003120233.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
48
|
+
kleinkram-0.51.0.dev20251003120233.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
|
|
49
|
+
kleinkram-0.51.0.dev20251003120233.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
|
|
50
|
+
kleinkram-0.51.0.dev20251003120233.dist-info/RECORD,,
|
{kleinkram-0.51.0.dev20251002115350.dist-info → kleinkram-0.51.0.dev20251003120233.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|