kleinkram 0.43.2.dev20250331124109__py3-none-any.whl → 0.58.0.dev20260110152317__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.
- kleinkram/api/client.py +6 -18
- kleinkram/api/deser.py +152 -1
- kleinkram/api/file_transfer.py +202 -101
- kleinkram/api/pagination.py +11 -2
- kleinkram/api/query.py +10 -10
- kleinkram/api/routes.py +192 -59
- kleinkram/auth.py +108 -7
- kleinkram/cli/_action.py +131 -0
- kleinkram/cli/_download.py +8 -19
- kleinkram/cli/_endpoint.py +2 -4
- kleinkram/cli/_file.py +6 -18
- kleinkram/cli/_file_validator.py +125 -0
- kleinkram/cli/_list.py +5 -15
- kleinkram/cli/_mission.py +24 -28
- kleinkram/cli/_project.py +10 -26
- kleinkram/cli/_run.py +220 -0
- kleinkram/cli/_upload.py +58 -26
- kleinkram/cli/_verify.py +59 -16
- kleinkram/cli/app.py +56 -17
- kleinkram/cli/error_handling.py +1 -3
- kleinkram/config.py +6 -21
- kleinkram/core.py +53 -43
- kleinkram/errors.py +12 -0
- kleinkram/models.py +51 -1
- kleinkram/printing.py +229 -18
- kleinkram/utils.py +10 -24
- kleinkram/wrappers.py +54 -30
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/METADATA +6 -4
- kleinkram-0.58.0.dev20260110152317.dist-info/RECORD +53 -0
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/WHEEL +1 -1
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/top_level.txt +0 -1
- {testing → tests}/backend_fixtures.py +27 -3
- tests/conftest.py +1 -1
- tests/generate_test_data.py +314 -0
- tests/test_config.py +2 -6
- tests/test_core.py +11 -31
- tests/test_end_to_end.py +3 -5
- tests/test_fixtures.py +3 -5
- tests/test_printing.py +9 -11
- tests/test_utils.py +1 -3
- tests/test_wrappers.py +9 -27
- kleinkram-0.43.2.dev20250331124109.dist-info/RECORD +0 -50
- testing/__init__.py +0 -0
- {kleinkram-0.43.2.dev20250331124109.dist-info → kleinkram-0.58.0.dev20260110152317.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import struct
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from rosbags.rosbag1 import Writer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def serialize_string(data):
|
|
11
|
+
"""Serialize a string for ROS1 (std_msgs/String)."""
|
|
12
|
+
encoded = data.encode("utf-8")
|
|
13
|
+
return struct.pack("<I", len(encoded)) + encoded
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def serialize_time(secs, nsecs):
|
|
17
|
+
return struct.pack("<II", secs, nsecs)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def serialize_header(seq, secs, nsecs, frame_id):
|
|
21
|
+
return struct.pack("<I", seq) + serialize_time(secs, nsecs) + serialize_string(frame_id)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def serialize_log(seq, secs, nsecs, frame_id, level, name, msg, file, function, line, topics):
|
|
25
|
+
# rosgraph_msgs/Log
|
|
26
|
+
# Header header
|
|
27
|
+
# byte level
|
|
28
|
+
# string name
|
|
29
|
+
# string msg
|
|
30
|
+
# string file
|
|
31
|
+
# string function
|
|
32
|
+
# uint32 line
|
|
33
|
+
# string[] topics
|
|
34
|
+
data = serialize_header(seq, secs, nsecs, frame_id)
|
|
35
|
+
data += struct.pack("<b", level)
|
|
36
|
+
data += serialize_string(name)
|
|
37
|
+
data += serialize_string(msg)
|
|
38
|
+
data += serialize_string(file)
|
|
39
|
+
data += serialize_string(function)
|
|
40
|
+
data += struct.pack("<I", line)
|
|
41
|
+
data += struct.pack("<I", len(topics))
|
|
42
|
+
for t in topics:
|
|
43
|
+
data += serialize_string(t)
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def serialize_temperature(seq, secs, nsecs, frame_id, temp, variance):
|
|
48
|
+
# sensor_msgs/Temperature
|
|
49
|
+
# Header header
|
|
50
|
+
# float64 temperature
|
|
51
|
+
# float64 variance
|
|
52
|
+
data = serialize_header(seq, secs, nsecs, frame_id)
|
|
53
|
+
data += struct.pack("<dd", temp, variance)
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def serialize_time_reference(seq, secs, nsecs, frame_id, ref_secs, ref_nsecs, source):
|
|
58
|
+
# sensor_msgs/TimeReference
|
|
59
|
+
# Header header
|
|
60
|
+
# time time_ref
|
|
61
|
+
# string source
|
|
62
|
+
data = serialize_header(seq, secs, nsecs, frame_id)
|
|
63
|
+
data += serialize_time(ref_secs, ref_nsecs)
|
|
64
|
+
data += serialize_string(source)
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def serialize_twist_stamped(seq, secs, nsecs, frame_id, linear, angular):
|
|
69
|
+
# geometry_msgs/TwistStamped
|
|
70
|
+
# Header header
|
|
71
|
+
# Twist twist (Vector3 linear, Vector3 angular)
|
|
72
|
+
data = serialize_header(seq, secs, nsecs, frame_id)
|
|
73
|
+
data += struct.pack("<ddd", linear[0], linear[1], linear[2])
|
|
74
|
+
data += struct.pack("<ddd", angular[0], angular[1], angular[2])
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def serialize_tf_message(transforms):
|
|
79
|
+
# tf2_msgs/TFMessage
|
|
80
|
+
# geometry_msgs/TransformStamped[] transforms
|
|
81
|
+
data = struct.pack("<I", len(transforms))
|
|
82
|
+
for t in transforms:
|
|
83
|
+
# TransformStamped
|
|
84
|
+
# Header header
|
|
85
|
+
# string child_frame_id
|
|
86
|
+
# Transform transform (Vector3 translation, Quaternion rotation)
|
|
87
|
+
data += serialize_header(t["seq"], t["secs"], t["nsecs"], t["frame_id"])
|
|
88
|
+
data += serialize_string(t["child_frame_id"])
|
|
89
|
+
data += struct.pack("<ddd", t["tx"], t["ty"], t["tz"])
|
|
90
|
+
data += struct.pack("<dddd", t["rx"], t["ry"], t["rz"], t["rw"])
|
|
91
|
+
return data
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def generate_bag(filename, target_size):
|
|
95
|
+
# Adjust payload to be smaller for small files
|
|
96
|
+
payload_size = 1024
|
|
97
|
+
if target_size > 1024 * 1024:
|
|
98
|
+
payload_size = 1024 * 1024
|
|
99
|
+
|
|
100
|
+
if target_size < 2000: # Very small files
|
|
101
|
+
payload_size = 100
|
|
102
|
+
|
|
103
|
+
payload = "x" * payload_size
|
|
104
|
+
serialized_msg = serialize_string(payload)
|
|
105
|
+
|
|
106
|
+
# Calculate number of messages needed
|
|
107
|
+
# Approximate overhead per message in bag file is ~30-50 bytes (record header) + connection ref
|
|
108
|
+
# We'll assume overhead is small compared to payload for large files, but significant for small ones.
|
|
109
|
+
# We'll just write until we think we are close.
|
|
110
|
+
|
|
111
|
+
msg_size = len(serialized_msg) + 50 # rough estimate including record headers
|
|
112
|
+
num_msgs = int(target_size / msg_size) + 1
|
|
113
|
+
if num_msgs < 1:
|
|
114
|
+
num_msgs = 1
|
|
115
|
+
|
|
116
|
+
print(f"Generating {filename} (~{target_size} bytes) with {num_msgs} messages of payload {payload_size}...")
|
|
117
|
+
|
|
118
|
+
if os.path.exists(filename):
|
|
119
|
+
os.remove(filename)
|
|
120
|
+
|
|
121
|
+
with Writer(filename) as writer:
|
|
122
|
+
# Add a connection
|
|
123
|
+
# msg_def for std_msgs/String is just "string data"
|
|
124
|
+
conn = writer.add_connection(
|
|
125
|
+
topic="/test_topic",
|
|
126
|
+
msgtype="std_msgs/msg/String",
|
|
127
|
+
msgdef="string data",
|
|
128
|
+
md5sum="992ce8a1687cec8c8bd883ec73ca41d1",
|
|
129
|
+
)
|
|
130
|
+
timestamp = 1000
|
|
131
|
+
for i in range(num_msgs):
|
|
132
|
+
writer.write(conn, timestamp + i, serialized_msg)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def generate_frontend_bag(filename):
|
|
136
|
+
print(f"Generating frontend test bag: {filename}")
|
|
137
|
+
if os.path.exists(filename):
|
|
138
|
+
os.remove(filename)
|
|
139
|
+
|
|
140
|
+
with Writer(filename) as writer:
|
|
141
|
+
# 1. rosgraph_msgs/Log
|
|
142
|
+
conn_log = writer.add_connection(
|
|
143
|
+
topic="/rosout",
|
|
144
|
+
msgtype="rosgraph_msgs/msg/Log",
|
|
145
|
+
msgdef="Header header\nbyte level\nstring name\nstring msg\nstring file\nstring function\nuint32 "
|
|
146
|
+
"line\nstring[] topics\n================================================================================\n"
|
|
147
|
+
"MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id",
|
|
148
|
+
md5sum="acffd30cd6b6de30f120938c17c593fb",
|
|
149
|
+
)
|
|
150
|
+
# 2. sensor_msgs/Temperature
|
|
151
|
+
conn_temp = writer.add_connection(
|
|
152
|
+
topic="/sensors/temperature",
|
|
153
|
+
msgtype="sensor_msgs/msg/Temperature",
|
|
154
|
+
msgdef="Header header\nfloat64 temperature\nfloat64 variance"
|
|
155
|
+
"\n================================================================================\n"
|
|
156
|
+
"MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id",
|
|
157
|
+
md5sum="ff71b307acdbe7c871a5a6d7edce2f6e",
|
|
158
|
+
)
|
|
159
|
+
# 3. sensor_msgs/TimeReference
|
|
160
|
+
conn_time = writer.add_connection(
|
|
161
|
+
topic="/time_ref",
|
|
162
|
+
msgtype="sensor_msgs/msg/TimeReference",
|
|
163
|
+
msgdef="Header header\ntime time_ref\nstring source\n"
|
|
164
|
+
"================================================================================\n"
|
|
165
|
+
"MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id",
|
|
166
|
+
md5sum="fded64a0265108ba86c3d38fb11c0c16",
|
|
167
|
+
)
|
|
168
|
+
# 4. geometry_msgs/TwistStamped
|
|
169
|
+
conn_twist = writer.add_connection(
|
|
170
|
+
topic="/cmd_vel",
|
|
171
|
+
msgtype="geometry_msgs/msg/TwistStamped",
|
|
172
|
+
msgdef="Header header\ngeometry_msgs/Twist twist\n"
|
|
173
|
+
"================================================================================\n"
|
|
174
|
+
"MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id\n"
|
|
175
|
+
"================================================================================\n"
|
|
176
|
+
"MSG: geometry_msgs/Twist\nVector3 linear\nVector3 angular\n"
|
|
177
|
+
"================================================================================\n"
|
|
178
|
+
"MSG: geometry_msgs/Vector3\nfloat64 x\nfloat64 y\nfloat64 z",
|
|
179
|
+
md5sum="98d34b0043a2093cf9d9345ab6eef12e",
|
|
180
|
+
)
|
|
181
|
+
# 5. tf2_msgs/TFMessage
|
|
182
|
+
conn_tf = writer.add_connection(
|
|
183
|
+
topic="/tf",
|
|
184
|
+
msgtype="tf2_msgs/msg/TFMessage",
|
|
185
|
+
msgdef="geometry_msgs/TransformStamped[] transforms\n"
|
|
186
|
+
"================================================================================\n"
|
|
187
|
+
"MSG: geometry_msgs/TransformStamped\nHeader header\nstring child_frame_id\n"
|
|
188
|
+
"geometry_msgs/Transform transform\n"
|
|
189
|
+
"================================================================================\n"
|
|
190
|
+
"MSG: std_msgs/Header\nuint32 seq\ntime stamp\nstring frame_id\n"
|
|
191
|
+
"================================================================================\n"
|
|
192
|
+
"MSG: geometry_msgs/Transform\ngeometry_msgs/Vector3 translation\n"
|
|
193
|
+
"geometry_msgs/Quaternion rotation\n"
|
|
194
|
+
"================================================================================\n"
|
|
195
|
+
"MSG: geometry_msgs/Vector3\nfloat64 x\nfloat64 y\nfloat64 z\n"
|
|
196
|
+
"================================================================================\n"
|
|
197
|
+
"MSG: geometry_msgs/Quaternion\nfloat64 x\nfloat64 y\nfloat64 z\nfloat64 w",
|
|
198
|
+
md5sum="94810edda583a504dfda3829e70d7eec",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Write messages
|
|
202
|
+
for i in range(100):
|
|
203
|
+
timestamp = 1000 + i * 100000000 # 100ms steps
|
|
204
|
+
secs = int(timestamp / 1000000000)
|
|
205
|
+
nsecs = timestamp % 1000000000
|
|
206
|
+
|
|
207
|
+
# Log
|
|
208
|
+
writer.write(
|
|
209
|
+
conn_log,
|
|
210
|
+
timestamp,
|
|
211
|
+
serialize_log(
|
|
212
|
+
i,
|
|
213
|
+
secs,
|
|
214
|
+
nsecs,
|
|
215
|
+
"",
|
|
216
|
+
2,
|
|
217
|
+
"test_node",
|
|
218
|
+
f"Log message {i}",
|
|
219
|
+
"test.cpp",
|
|
220
|
+
"main",
|
|
221
|
+
i,
|
|
222
|
+
["/rosout"],
|
|
223
|
+
),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Temperature (sine wave)
|
|
227
|
+
import math
|
|
228
|
+
|
|
229
|
+
temp = 20.0 + 5.0 * math.sin(i * 0.1)
|
|
230
|
+
writer.write(
|
|
231
|
+
conn_temp,
|
|
232
|
+
timestamp,
|
|
233
|
+
serialize_temperature(i, secs, nsecs, "sensor_frame", temp, 0.1),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# TimeReference
|
|
237
|
+
writer.write(
|
|
238
|
+
conn_time,
|
|
239
|
+
timestamp,
|
|
240
|
+
serialize_time_reference(i, secs, nsecs, "time_frame", secs, nsecs, "GPS"),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# TwistStamped (circle)
|
|
244
|
+
writer.write(
|
|
245
|
+
conn_twist,
|
|
246
|
+
timestamp,
|
|
247
|
+
serialize_twist_stamped(i, secs, nsecs, "base_link", [1.0, 0.0, 0.0], [0.0, 0.0, 0.5]),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# TF
|
|
251
|
+
writer.write(
|
|
252
|
+
conn_tf,
|
|
253
|
+
timestamp,
|
|
254
|
+
serialize_tf_message(
|
|
255
|
+
[
|
|
256
|
+
{
|
|
257
|
+
"seq": i,
|
|
258
|
+
"secs": secs,
|
|
259
|
+
"nsecs": nsecs,
|
|
260
|
+
"frame_id": "map",
|
|
261
|
+
"child_frame_id": "base_link",
|
|
262
|
+
"tx": i * 0.1,
|
|
263
|
+
"ty": 0.0,
|
|
264
|
+
"tz": 0.0,
|
|
265
|
+
"rx": 0.0,
|
|
266
|
+
"ry": 0.0,
|
|
267
|
+
"rz": 0.0,
|
|
268
|
+
"rw": 1.0,
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def main():
|
|
276
|
+
data_dir = os.path.join(os.path.dirname(__file__), "data")
|
|
277
|
+
os.makedirs(data_dir, exist_ok=True)
|
|
278
|
+
|
|
279
|
+
files = {
|
|
280
|
+
"10_KB.bag": 10 * 1024,
|
|
281
|
+
"50_KB.bag": 50 * 1024,
|
|
282
|
+
"1_MB.bag": 1 * 1024 * 1024,
|
|
283
|
+
"17_MB.bag": 17 * 1024 * 1024,
|
|
284
|
+
"125_MB.bag": 125 * 1024 * 1024,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for filename, size in files.items():
|
|
288
|
+
filepath = os.path.join(data_dir, filename)
|
|
289
|
+
generate_bag(filepath, size)
|
|
290
|
+
|
|
291
|
+
# Generate backend fixtures
|
|
292
|
+
backend_fixtures_dir = os.path.join(os.path.dirname(__file__), "../../backend/tests/fixtures")
|
|
293
|
+
os.makedirs(backend_fixtures_dir, exist_ok=True)
|
|
294
|
+
generate_bag(os.path.join(backend_fixtures_dir, "test.bag"), 10 * 1024)
|
|
295
|
+
generate_bag(os.path.join(backend_fixtures_dir, "to_delete.bag"), 10 * 1024)
|
|
296
|
+
generate_bag(os.path.join(backend_fixtures_dir, "file1.bag"), 10 * 1024)
|
|
297
|
+
generate_bag(os.path.join(backend_fixtures_dir, "file2.bag"), 10 * 1024)
|
|
298
|
+
generate_bag(os.path.join(backend_fixtures_dir, "move_me.bag"), 10 * 1024)
|
|
299
|
+
generate_bag(os.path.join(backend_fixtures_dir, "move_me.bag"), 10 * 1024)
|
|
300
|
+
generate_bag(os.path.join(backend_fixtures_dir, "state_test.bag"), 10 * 1024)
|
|
301
|
+
generate_frontend_bag(os.path.join(data_dir, "frontend_test.bag"))
|
|
302
|
+
|
|
303
|
+
# Generate dummy MCAP and YAML
|
|
304
|
+
with open(os.path.join(data_dir, "test.yaml"), "w") as f:
|
|
305
|
+
f.write("test: true\nvalue: 123\n")
|
|
306
|
+
|
|
307
|
+
with open(os.path.join(data_dir, "test.mcap"), "wb") as f:
|
|
308
|
+
f.write(b"\x89MCAP\x30\r\n") # Minimal magic bytes
|
|
309
|
+
|
|
310
|
+
print("Done.")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
if __name__ == "__main__":
|
|
314
|
+
main()
|
tests/test_config.py
CHANGED
|
@@ -72,9 +72,7 @@ def test_load_config_default(config_path):
|
|
|
72
72
|
assert config.selected_endpoint == get_env().value
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def test_load_default_config_with_env_var_api_key_specified(
|
|
76
|
-
config_path, set_api_key_env
|
|
77
|
-
):
|
|
75
|
+
def test_load_default_config_with_env_var_api_key_specified(config_path, set_api_key_env):
|
|
78
76
|
assert set_api_key_env is None
|
|
79
77
|
|
|
80
78
|
config = _load_config(path=config_path)
|
|
@@ -87,9 +85,7 @@ def test_load_default_config_with_env_var_api_key_specified(
|
|
|
87
85
|
assert not config_path.exists()
|
|
88
86
|
|
|
89
87
|
|
|
90
|
-
def test_load_default_config_with_env_var_endpoints_specified(
|
|
91
|
-
config_path, set_endpoint_env
|
|
92
|
-
):
|
|
88
|
+
def test_load_default_config_with_env_var_endpoints_specified(config_path, set_endpoint_env):
|
|
93
89
|
assert set_endpoint_env is None
|
|
94
90
|
config = _load_config(path=config_path)
|
|
95
91
|
|
tests/test_core.py
CHANGED
|
@@ -17,43 +17,33 @@ from kleinkram.api.query import MissionQuery
|
|
|
17
17
|
from kleinkram.api.query import ProjectQuery
|
|
18
18
|
from kleinkram.errors import MissionNotFound
|
|
19
19
|
from kleinkram.models import FileVerificationStatus
|
|
20
|
-
from
|
|
20
|
+
from tests.backend_fixtures import DATA_FILES
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@pytest.mark.slow
|
|
24
24
|
def test_upload_create(project):
|
|
25
25
|
mission_name = token_hex(8)
|
|
26
|
-
mission_query = MissionQuery(
|
|
27
|
-
patterns=[mission_name], project_query=ProjectQuery(ids=[project.id])
|
|
28
|
-
)
|
|
26
|
+
mission_query = MissionQuery(patterns=[mission_name], project_query=ProjectQuery(ids=[project.id]))
|
|
29
27
|
|
|
30
28
|
client = AuthenticatedClient()
|
|
31
|
-
kleinkram.core.upload(
|
|
32
|
-
client=client, query=mission_query, file_paths=DATA_FILES, create=True
|
|
33
|
-
)
|
|
29
|
+
kleinkram.core.upload(client=client, query=mission_query, file_paths=DATA_FILES, create=True)
|
|
34
30
|
|
|
35
31
|
mission = list_missions(mission_names=[mission_name])[0]
|
|
36
32
|
assert mission.project_id == project.id
|
|
37
33
|
assert mission.name == mission_name
|
|
38
34
|
|
|
39
35
|
files = list_files(mission_ids=[mission.id])
|
|
40
|
-
assert set([file.name for file in files if file.name.endswith(".bag")]) == set(
|
|
41
|
-
[file.name for file in DATA_FILES]
|
|
42
|
-
)
|
|
36
|
+
assert set([file.name for file in files if file.name.endswith(".bag")]) == set([file.name for file in DATA_FILES])
|
|
43
37
|
|
|
44
38
|
|
|
45
39
|
@pytest.mark.slow
|
|
46
40
|
def test_upload_no_create(project):
|
|
47
41
|
mission_name = token_hex(8)
|
|
48
|
-
mission_query = MissionQuery(
|
|
49
|
-
patterns=[mission_name], project_query=ProjectQuery(ids=[project.id])
|
|
50
|
-
)
|
|
42
|
+
mission_query = MissionQuery(patterns=[mission_name], project_query=ProjectQuery(ids=[project.id]))
|
|
51
43
|
|
|
52
44
|
client = AuthenticatedClient()
|
|
53
45
|
with pytest.raises(MissionNotFound):
|
|
54
|
-
kleinkram.core.upload(
|
|
55
|
-
client=client, query=mission_query, file_paths=DATA_FILES, create=False
|
|
56
|
-
)
|
|
46
|
+
kleinkram.core.upload(client=client, query=mission_query, file_paths=DATA_FILES, create=False)
|
|
57
47
|
|
|
58
48
|
|
|
59
49
|
@pytest.mark.slow
|
|
@@ -64,9 +54,7 @@ def test_upload_to_existing_mission(empty_mission):
|
|
|
64
54
|
kleinkram.core.upload(client=client, query=mission_query, file_paths=DATA_FILES)
|
|
65
55
|
|
|
66
56
|
files = list_files(mission_ids=[empty_mission.id])
|
|
67
|
-
assert set([file.name for file in files if file.name.endswith(".bag")]) == set(
|
|
68
|
-
[file.name for file in DATA_FILES]
|
|
69
|
-
)
|
|
57
|
+
assert set([file.name for file in files if file.name.endswith(".bag")]) == set([file.name for file in DATA_FILES])
|
|
70
58
|
|
|
71
59
|
|
|
72
60
|
@pytest.mark.slow
|
|
@@ -84,9 +72,7 @@ def test_delete_working_as_expected_when_passing_empty_list(mission):
|
|
|
84
72
|
# we need to filter by *.bag to not get flakyness due to conversion
|
|
85
73
|
n_files = len(list_files(mission_ids=[mission.id], file_names=["*.bag"]))
|
|
86
74
|
kleinkram.core.delete_files(client=client, file_ids=[])
|
|
87
|
-
n_files_after_delete = len(
|
|
88
|
-
list_files(mission_ids=[mission.id], file_names=["*.bag"])
|
|
89
|
-
)
|
|
75
|
+
n_files_after_delete = len(list_files(mission_ids=[mission.id], file_names=["*.bag"]))
|
|
90
76
|
assert n_files == n_files_after_delete
|
|
91
77
|
|
|
92
78
|
|
|
@@ -132,9 +118,7 @@ def test_create_update_delete_project():
|
|
|
132
118
|
assert project.description == "test"
|
|
133
119
|
|
|
134
120
|
new_name = token_hex(8)
|
|
135
|
-
kleinkram.core.update_project(
|
|
136
|
-
client=client, project_id=project.id, new_name=new_name, description="new desc"
|
|
137
|
-
)
|
|
121
|
+
kleinkram.core.update_project(client=client, project_id=project.id, new_name=new_name, description="new desc")
|
|
138
122
|
|
|
139
123
|
project = list_projects(project_ids=[project.id])[0]
|
|
140
124
|
assert project.name == new_name
|
|
@@ -162,13 +146,9 @@ def test_verify(mission):
|
|
|
162
146
|
client = AuthenticatedClient()
|
|
163
147
|
query = MissionQuery(ids=[mission.id])
|
|
164
148
|
|
|
165
|
-
verify_status = kleinkram.core.verify(
|
|
166
|
-
client=client, query=query, file_paths=DATA_FILES, skip_hash=True
|
|
167
|
-
)
|
|
149
|
+
verify_status = kleinkram.core.verify(client=client, query=query, file_paths=DATA_FILES, skip_hash=True)
|
|
168
150
|
|
|
169
|
-
assert all(
|
|
170
|
-
status == FileVerificationStatus.UPLAODED for status in verify_status.values()
|
|
171
|
-
)
|
|
151
|
+
assert all(status == FileVerificationStatus.UPLOADED for status in verify_status.values())
|
|
172
152
|
|
|
173
153
|
|
|
174
154
|
@pytest.mark.slow
|
tests/test_end_to_end.py
CHANGED
|
@@ -16,7 +16,7 @@ VERBOSE = True
|
|
|
16
16
|
|
|
17
17
|
CLI = "klein"
|
|
18
18
|
PROJECT_NAME = "automated-testing"
|
|
19
|
-
DATA_PATH = Path(__file__).parent
|
|
19
|
+
DATA_PATH = Path(__file__).parent / "data"
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@pytest.fixture(scope="session")
|
|
@@ -48,9 +48,7 @@ def test_upload_verify_update_download_mission(project, tmp_path, api):
|
|
|
48
48
|
|
|
49
49
|
mission_name = secrets.token_hex(8)
|
|
50
50
|
upload = f"{CLI} upload -p {project.name} -m {mission_name} --create {DATA_PATH.absolute()}/*.bag"
|
|
51
|
-
verify = (
|
|
52
|
-
f"{CLI} verify -p {project.name} -m {mission_name} {DATA_PATH.absolute()}/*.bag"
|
|
53
|
-
)
|
|
51
|
+
verify = f"{CLI} verify -p {project.name} -m {mission_name} {DATA_PATH.absolute()}/*.bag"
|
|
54
52
|
# update = f"{CLI} mission update -p {project.name} -m {mission_name} --metadata {DATA_PATH.absolute()}/metadata.yaml"
|
|
55
53
|
download = f"{CLI} download -p {project.name} -m {mission_name} --dest {tmp_path.absolute()}"
|
|
56
54
|
delete_file = f"{CLI} file delete -p {project.name} -m {mission_name} -f {file_names[0].name} -y"
|
|
@@ -69,7 +67,7 @@ def test_list_files(project, mission, api):
|
|
|
69
67
|
assert run_cmd(f"{CLI} list files -p {project.name}") == 0
|
|
70
68
|
assert run_cmd(f"{CLI} list files -p {project.name} -m {mission.name}") == 0
|
|
71
69
|
assert run_cmd(f"{CLI} list files") == 0
|
|
72
|
-
assert run_cmd(f"{CLI} list files -p {
|
|
70
|
+
assert run_cmd(f"{CLI} list files -p {project.name}") == 0
|
|
73
71
|
assert run_cmd(f'{CLI} list files -p "*" -m "*" "*"') == 0
|
|
74
72
|
|
|
75
73
|
|
tests/test_fixtures.py
CHANGED
|
@@ -5,8 +5,8 @@ import pytest
|
|
|
5
5
|
from kleinkram import list_files
|
|
6
6
|
from kleinkram import list_missions
|
|
7
7
|
from kleinkram import list_projects
|
|
8
|
-
from
|
|
9
|
-
from
|
|
8
|
+
from tests.backend_fixtures import DATA_FILES
|
|
9
|
+
from tests.backend_fixtures import PROJECT_DESCRIPTION
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@pytest.mark.slow
|
|
@@ -22,9 +22,7 @@ def test_mission_fixture(mission, project):
|
|
|
22
22
|
|
|
23
23
|
files = list_files(mission_ids=[mission.id])
|
|
24
24
|
|
|
25
|
-
assert set([file.name for file in files if file.name.endswith(".bag")]) == set(
|
|
26
|
-
[file.name for file in DATA_FILES]
|
|
27
|
-
)
|
|
25
|
+
assert set([file.name for file in files if file.name.endswith(".bag")]) == set([file.name for file in DATA_FILES])
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
@pytest.mark.slow
|
tests/test_printing.py
CHANGED
|
@@ -15,14 +15,14 @@ from kleinkram.printing import parse_metadata_value
|
|
|
15
15
|
def test_format_bytes():
|
|
16
16
|
assert format_bytes(0) == "0 B"
|
|
17
17
|
assert format_bytes(1) == "1 B"
|
|
18
|
-
assert format_bytes(
|
|
19
|
-
assert format_bytes(
|
|
20
|
-
assert format_bytes(
|
|
21
|
-
assert format_bytes(
|
|
22
|
-
assert format_bytes(
|
|
23
|
-
assert format_bytes(
|
|
24
|
-
assert format_bytes(
|
|
25
|
-
assert format_bytes(
|
|
18
|
+
assert format_bytes(999) == "999 B"
|
|
19
|
+
assert format_bytes(1000) == "1.00 KB"
|
|
20
|
+
assert format_bytes(1001) == "1.00 KB"
|
|
21
|
+
assert format_bytes(2000) == "2.00 KB"
|
|
22
|
+
assert format_bytes(10**6) == "1.00 MB"
|
|
23
|
+
assert format_bytes(10**9) == "1.00 GB"
|
|
24
|
+
assert format_bytes(10**12) == "1.00 TB"
|
|
25
|
+
assert format_bytes(10**15) == "1.00 PB"
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def test_add_placeholder_row():
|
|
@@ -48,9 +48,7 @@ def test_parse_metadata_value():
|
|
|
48
48
|
mv = MetadataValue(type_=MetadataValueType.BOOLEAN, value="false")
|
|
49
49
|
assert parse_metadata_value(mv) is False # noqa
|
|
50
50
|
mv = MetadataValue(type_=MetadataValueType.DATE, value="2021-01-01T00:00:00Z")
|
|
51
|
-
assert parse_metadata_value(mv) == datetime.datetime(
|
|
52
|
-
2021, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc
|
|
53
|
-
)
|
|
51
|
+
assert parse_metadata_value(mv) == datetime.datetime(2021, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
|
|
54
52
|
|
|
55
53
|
|
|
56
54
|
@pytest.mark.skip
|
tests/test_utils.py
CHANGED
|
@@ -85,9 +85,7 @@ def test_is_valid_uuid4():
|
|
|
85
85
|
"invalid_sybmols_____.txt",
|
|
86
86
|
id="invalid symbols",
|
|
87
87
|
),
|
|
88
|
-
pytest.param(
|
|
89
|
-
Path(f'{"a" * 100}.txt'), f'{"a" * 40}38bf3e475f.txt', id="too long"
|
|
90
|
-
),
|
|
88
|
+
pytest.param(Path(f'{"a" * 100}.txt'), f'{"a" * 40}38bf3e475f.txt', id="too long"),
|
|
91
89
|
pytest.param(Path(f'{"a" * 50}.txt'), f'{"a" * 50}.txt', id="max length"),
|
|
92
90
|
pytest.param(Path("in/a/folder.txt"), "folder.txt", id="in folder"),
|
|
93
91
|
],
|
tests/test_wrappers.py
CHANGED
|
@@ -12,37 +12,25 @@ from kleinkram.wrappers import _args_to_project_query
|
|
|
12
12
|
|
|
13
13
|
def test_args_to_project_query() -> None:
|
|
14
14
|
assert _args_to_project_query() == ProjectQuery()
|
|
15
|
-
assert _args_to_project_query(project_names=["test"]) == ProjectQuery(
|
|
16
|
-
patterns=["test"]
|
|
17
|
-
)
|
|
15
|
+
assert _args_to_project_query(project_names=["test"]) == ProjectQuery(patterns=["test"])
|
|
18
16
|
|
|
19
17
|
_id = uuid4()
|
|
20
18
|
assert _args_to_project_query(project_ids=[_id]) == ProjectQuery(ids=[_id])
|
|
21
|
-
assert _args_to_project_query(
|
|
22
|
-
project_names=["test"], project_ids=[_id]
|
|
23
|
-
) == ProjectQuery(patterns=["test"], ids=[_id])
|
|
19
|
+
assert _args_to_project_query(project_names=["test"], project_ids=[_id]) == ProjectQuery(patterns=["test"], ids=[_id])
|
|
24
20
|
assert _args_to_project_query(project_ids=[str(_id)]) == ProjectQuery(ids=[_id])
|
|
25
21
|
|
|
26
22
|
|
|
27
23
|
def test_args_to_mission_query() -> None:
|
|
28
24
|
assert _args_to_mission_query() == MissionQuery()
|
|
29
|
-
assert _args_to_mission_query(mission_names=["test"]) == MissionQuery(
|
|
30
|
-
patterns=["test"]
|
|
31
|
-
)
|
|
25
|
+
assert _args_to_mission_query(mission_names=["test"]) == MissionQuery(patterns=["test"])
|
|
32
26
|
|
|
33
27
|
_id = uuid4()
|
|
34
28
|
assert _args_to_mission_query(mission_ids=[_id]) == MissionQuery(ids=[_id])
|
|
35
|
-
assert _args_to_mission_query(
|
|
36
|
-
mission_names=["test"], mission_ids=[_id]
|
|
37
|
-
) == MissionQuery(patterns=["test"], ids=[_id])
|
|
29
|
+
assert _args_to_mission_query(mission_names=["test"], mission_ids=[_id]) == MissionQuery(patterns=["test"], ids=[_id])
|
|
38
30
|
assert _args_to_mission_query(mission_ids=[str(_id)]) == MissionQuery(ids=[_id])
|
|
39
31
|
|
|
40
|
-
assert _args_to_mission_query(project_names=["test"]) == MissionQuery(
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
assert _args_to_mission_query(project_ids=[_id]) == MissionQuery(
|
|
44
|
-
project_query=ProjectQuery(ids=[_id])
|
|
45
|
-
)
|
|
32
|
+
assert _args_to_mission_query(project_names=["test"]) == MissionQuery(project_query=ProjectQuery(patterns=["test"]))
|
|
33
|
+
assert _args_to_mission_query(project_ids=[_id]) == MissionQuery(project_query=ProjectQuery(ids=[_id]))
|
|
46
34
|
|
|
47
35
|
|
|
48
36
|
def test_args_to_file_query() -> None:
|
|
@@ -51,17 +39,11 @@ def test_args_to_file_query() -> None:
|
|
|
51
39
|
|
|
52
40
|
_id = uuid4()
|
|
53
41
|
assert _args_to_file_query(file_ids=[_id]) == FileQuery(ids=[_id])
|
|
54
|
-
assert _args_to_file_query(file_names=["test"], file_ids=[_id]) == FileQuery(
|
|
55
|
-
patterns=["test"], ids=[_id]
|
|
56
|
-
)
|
|
42
|
+
assert _args_to_file_query(file_names=["test"], file_ids=[_id]) == FileQuery(patterns=["test"], ids=[_id])
|
|
57
43
|
assert _args_to_file_query(file_ids=[str(_id)]) == FileQuery(ids=[_id])
|
|
58
44
|
|
|
59
|
-
assert _args_to_file_query(mission_names=["test"]) == FileQuery(
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
assert _args_to_file_query(mission_ids=[_id]) == FileQuery(
|
|
63
|
-
mission_query=MissionQuery(ids=[_id])
|
|
64
|
-
)
|
|
45
|
+
assert _args_to_file_query(mission_names=["test"]) == FileQuery(mission_query=MissionQuery(patterns=["test"]))
|
|
46
|
+
assert _args_to_file_query(mission_ids=[_id]) == FileQuery(mission_query=MissionQuery(ids=[_id]))
|
|
65
47
|
|
|
66
48
|
assert _args_to_file_query(project_names=["test"]) == FileQuery(
|
|
67
49
|
mission_query=MissionQuery(project_query=ProjectQuery(patterns=["test"]))
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
kleinkram/__init__.py,sha256=xIJqTJw2kbCGryGlCeAdpmtR1FTxmrW1MklUNQEaj74,1061
|
|
2
|
-
kleinkram/__main__.py,sha256=B9RiZxfO4jpCmWPUHyKJ7_EoZlEG4sPpH-nz7T_YhhQ,125
|
|
3
|
-
kleinkram/_version.py,sha256=QYJyRTcqFcJj4qWYpqs7WcoOP6jxDMqyvxLY-cD6KcE,129
|
|
4
|
-
kleinkram/auth.py,sha256=XD_rHOyJmYYfO7QJf3TLYH5qXA22gXGWi7PT3jujlVs,2968
|
|
5
|
-
kleinkram/config.py,sha256=nx6uSM5nLP4SKe8b9VAx4KDtCCwtyshXmzbEJcUwpsY,7411
|
|
6
|
-
kleinkram/core.py,sha256=kD2aqN_VhlUGZV4ocij8M_VZCQwFsCf42mJmdupRQVU,8678
|
|
7
|
-
kleinkram/errors.py,sha256=qa98YvhDbLqX60P8bcMcFmHy4HxgYNlSROXud8Kj-P4,965
|
|
8
|
-
kleinkram/main.py,sha256=BTE0mZN__xd46wBhFi6iBlK9eGGQvJ1LdUMsbnysLi0,172
|
|
9
|
-
kleinkram/models.py,sha256=8nJlPrKVLSmehspeuQSFV6nUo76JzehUn6KIZYH1xy4,1832
|
|
10
|
-
kleinkram/printing.py,sha256=_vIjs-lPVgv21ER5q5iYtx44OTs5y8wyd32w27WqocM,12161
|
|
11
|
-
kleinkram/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
kleinkram/types.py,sha256=nfDjj8TB1Jn5vqO0Xg6qhLOuKom9DDhe62BrngqnVGM,185
|
|
13
|
-
kleinkram/utils.py,sha256=AsKZxEGStn03L2tqqiMVCQCrDyl8HhwOfpa3no4dfYc,6767
|
|
14
|
-
kleinkram/wrappers.py,sha256=4xXU43eNnvMG2sssU330MmTLSSRdurOpnZ-zNGOGmt0,11342
|
|
15
|
-
kleinkram/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
kleinkram/api/client.py,sha256=VwuT97_WdbDpcVGwMXB0fRnUoQnUSf7BOP5eXUFokfI,5932
|
|
17
|
-
kleinkram/api/deser.py,sha256=xRpYUFKZ0Luoo7XyAtYblJvprmpjNSZOiFVnFKmOzcM,4819
|
|
18
|
-
kleinkram/api/file_transfer.py,sha256=_uYYJs1iND4YNpg8_-VUo6zu1DuHV8Or2KkGSVAAL0o,15278
|
|
19
|
-
kleinkram/api/pagination.py,sha256=P_zPsBKlMWkmAv-YfUNHaGW-XLB_4U8BDMrKyiDFIXk,1370
|
|
20
|
-
kleinkram/api/query.py,sha256=9Exi4hJR7Ml38_zjAcOvSEoIAxZLlpM6QwwzO9fs5Gk,3293
|
|
21
|
-
kleinkram/api/routes.py,sha256=x0IfzQO5RAC3rohDSpdUNlVOWl221gh4e0fbMKfGrQA,12251
|
|
22
|
-
kleinkram/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
kleinkram/cli/_download.py,sha256=H4YlXJkZE4Md02nzgrO_i8Hsm4ZIejPsxBEKkcn4KHs,2371
|
|
24
|
-
kleinkram/cli/_endpoint.py,sha256=oY0p4bnuHLEDJCXtTmir4AHswcKAygZ8I4IWC3RFcKc,1796
|
|
25
|
-
kleinkram/cli/_file.py,sha256=Q2fLDdUyfHFmdGC6wIxMqgEl0F76qszhzWJrRV5rTBM,2973
|
|
26
|
-
kleinkram/cli/_list.py,sha256=5gI3aIUeKC0_eWPQqdFXSBBFvpkTTJSm31TamHa197c,3090
|
|
27
|
-
kleinkram/cli/_mission.py,sha256=zDFnOozOFckpuREFgIPt1IzG5q3b1bsNxYlWQoHoz5A,5301
|
|
28
|
-
kleinkram/cli/_project.py,sha256=N0C96NC_onCEwTteYp2wgkkwkdJt-1q43LFdqNXfjC8,3398
|
|
29
|
-
kleinkram/cli/_upload.py,sha256=gOhbjbmqhmwW7p6bWlSvI53vLHvBFO9QqD1kdU92I2k,2813
|
|
30
|
-
kleinkram/cli/_verify.py,sha256=0ABVa4U_WzaV36ClR8NsOIG7KAMRlnFmsbtnHhbWVj4,1742
|
|
31
|
-
kleinkram/cli/app.py,sha256=m2qq4z95QllvXnxh3koPp0kq06I5R9etsJV8qSV8TMk,7037
|
|
32
|
-
kleinkram/cli/error_handling.py,sha256=wK3tzeKVSrZm-xmiyzGLnGT2E4TRpyxhaak6GWGP7P8,1921
|
|
33
|
-
testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
testing/backend_fixtures.py,sha256=t5QWwyezHUhxxAlbUuE_eFmpyRaGbnWNNcGPwrO17JM,1571
|
|
35
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
-
tests/conftest.py,sha256=5MLYQOtQoXWl0TRkYntYKNdqpd4hl9m0XTRi5OXanYI,104
|
|
37
|
-
tests/test_config.py,sha256=uvVh1iUSCoZc8YioxS_GjF-J7m4JE2z4XUbwK_4wDV0,5855
|
|
38
|
-
tests/test_core.py,sha256=9oAb-jxWlRI6EzMwe-oStUFpK4R6ad5vzy95gZgAeuk,5623
|
|
39
|
-
tests/test_end_to_end.py,sha256=0W5pUES5hek-pXq4NZtpPZqKTORkGCRsDv5_D3rDMjY,3372
|
|
40
|
-
tests/test_error_handling.py,sha256=qPSMKF1qsAHyUME0-krxbIrk38iGKkhAyAah-KwN4NE,1300
|
|
41
|
-
tests/test_fixtures.py,sha256=UlPmGbEsGvrDPsaStGMRjNvrVPGjCqOB0RMfLJq2VRA,1071
|
|
42
|
-
tests/test_printing.py,sha256=Jz1AjqmqBRjp1JLm6H1oVJyvGaMPlahVXdKnd7UDQFc,2231
|
|
43
|
-
tests/test_query.py,sha256=fExmCKXLA7-9j2S2sF_sbvRX_2s6Cp3a7OTcqE25q9g,3864
|
|
44
|
-
tests/test_utils.py,sha256=eUBYrn3xrcgcaxm1X4fqZaX4tRvkbI6rh6BUbNbu9T0,4784
|
|
45
|
-
tests/test_wrappers.py,sha256=TbcTyO2L7fslbzgfDdcVZkencxNQ8cGPZm_iB6c9d6Q,2673
|
|
46
|
-
kleinkram-0.43.2.dev20250331124109.dist-info/METADATA,sha256=oBBV9v06ABIXRmyZQFocEwD5qO1ICqaYSpT2mOBjxas,2825
|
|
47
|
-
kleinkram-0.43.2.dev20250331124109.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
48
|
-
kleinkram-0.43.2.dev20250331124109.dist-info/entry_points.txt,sha256=SaB2l5aqhSr8gmaMw2kvQU90a8Bnl7PedU8cWYxkfYo,46
|
|
49
|
-
kleinkram-0.43.2.dev20250331124109.dist-info/top_level.txt,sha256=N3-sJagEHu1Tk1X6Dx1X1q0pLDNbDZpLzRxVftvepds,24
|
|
50
|
-
kleinkram-0.43.2.dev20250331124109.dist-info/RECORD,,
|
testing/__init__.py
DELETED
|
File without changes
|
|
File without changes
|