humalab 0.0.4__py3-none-any.whl → 0.0.5__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 humalab might be problematic. Click here for more details.

@@ -114,11 +114,23 @@ class HumaLabApiClient:
114
114
  def delete(self, endpoint: str, **kwargs) -> requests.Response:
115
115
  """Make a DELETE request."""
116
116
  return self._make_request("DELETE", endpoint, **kwargs)
117
-
117
+
118
+ # User Authentication API methods
119
+ def validate_token(self) -> Dict[str, Any]:
120
+ """
121
+ Validate JWT token and return user info.
122
+
123
+ Returns:
124
+ User information from the validated token
125
+ """
126
+ response = self.get("/api-key/validate")
127
+ return response.json()
128
+
118
129
  # Convenience methods for common API operations
119
130
 
120
131
  def get_resources(
121
- self,
132
+ self,
133
+ project_name: str,
122
134
  resource_types: Optional[str] = None,
123
135
  limit: int = 20,
124
136
  offset: int = 0,
@@ -126,89 +138,98 @@ class HumaLabApiClient:
126
138
  ) -> Dict[str, Any]:
127
139
  """
128
140
  Get list of all resources.
129
-
141
+
130
142
  Args:
143
+ project_name: Project name (required)
131
144
  resource_types: Comma-separated resource types to filter by
132
145
  limit: Maximum number of resources to return
133
146
  offset: Number of resources to skip
134
147
  latest_only: If true, only return latest version of each resource
135
-
148
+
136
149
  Returns:
137
150
  Resource list with pagination info
138
151
  """
139
152
  params = {
153
+ "project_name": project_name,
140
154
  "limit": limit,
141
155
  "offset": offset,
142
156
  "latest_only": latest_only
143
157
  }
144
158
  if resource_types:
145
159
  params["resource_types"] = resource_types
146
-
160
+
147
161
  response = self.get("/resources", params=params)
148
162
  return response.json()
149
163
 
150
- def get_resource(self, name: str, version: Optional[int] = None) -> Dict[str, Any]:
164
+ def get_resource(self, name: str, project_name: str, version: Optional[int] = None) -> Dict[str, Any]:
151
165
  """
152
166
  Get a specific resource.
153
-
167
+
154
168
  Args:
155
169
  name: Resource name
156
- version: Specific version (defaults to latest)
157
-
170
+ project_name: Project name (required)
171
+ version: Optional specific version (defaults to latest)
172
+
158
173
  Returns:
159
174
  Resource data
160
175
  """
161
176
  if version is not None:
162
177
  endpoint = f"/resources/{name}/{version}"
178
+ params = {"project_name": project_name}
163
179
  else:
164
180
  endpoint = f"/resources/{name}"
165
-
166
- response = self.get(endpoint)
181
+ params = {"project_name": project_name}
182
+
183
+ response = self.get(endpoint, params=params)
167
184
  return response.json()
168
185
 
169
186
  def download_resource(
170
- self,
171
- name: str,
187
+ self,
188
+ name: str,
189
+ project_name: str,
172
190
  version: Optional[int] = None
173
191
  ) -> bytes:
174
192
  """
175
193
  Download a resource file.
176
-
194
+
177
195
  Args:
178
196
  name: Resource name
179
- version: Specific version (defaults to latest)
180
-
197
+ project_name: Project name (required)
198
+ version: Optional specific version (defaults to latest)
199
+
181
200
  Returns:
182
201
  Resource file content as bytes
183
202
  """
203
+ endpoint = f"/resources/{name}/download"
204
+ params = {"project_name": project_name}
184
205
  if version is not None:
185
- endpoint = f"/resources/{name}/download?version={version}"
186
- else:
187
- endpoint = f"/resources/{name}/download"
206
+ params["version"] = str(version)
188
207
 
189
- response = self.get(endpoint)
208
+ response = self.get(endpoint, params=params)
190
209
  return response.content
191
210
 
192
211
  def upload_resource(
193
- self,
194
- name: str,
195
- file_path: str,
212
+ self,
213
+ name: str,
214
+ file_path: str,
196
215
  resource_type: str,
216
+ project_name: str,
197
217
  description: Optional[str] = None,
198
218
  filename: Optional[str] = None,
199
219
  allow_duplicate_name: bool = False
200
220
  ) -> Dict[str, Any]:
201
221
  """
202
222
  Upload a resource file.
203
-
223
+
204
224
  Args:
205
225
  name: Resource name
206
226
  file_path: Path to file to upload
207
227
  resource_type: Type of resource (urdf, mjcf, etc.)
228
+ project_name: Project name (required)
208
229
  description: Optional description
209
230
  filename: Optional custom filename
210
231
  allow_duplicate_name: Allow creating new version with existing name
211
-
232
+
212
233
  Returns:
213
234
  Created resource data
214
235
  """
@@ -219,12 +240,13 @@ class HumaLabApiClient:
219
240
  data['description'] = description
220
241
  if filename:
221
242
  data['filename'] = filename
222
-
243
+
223
244
  params = {
224
245
  'resource_type': resource_type,
246
+ 'project_name': project_name,
225
247
  'allow_duplicate_name': allow_duplicate_name
226
248
  }
227
-
249
+
228
250
  response = self.post(f"/resources/{name}/upload", files=files, params=params)
229
251
  return response.json()
230
252
 
@@ -232,42 +254,511 @@ class HumaLabApiClient:
232
254
  """Get list of all available resource types."""
233
255
  response = self.get("/resources/types")
234
256
  return response.json()
257
+
258
+ def get_scenarios(
259
+ self,
260
+ project_name: str,
261
+ skip: int = 0,
262
+ limit: int = 10,
263
+ include_inactive: bool = False,
264
+ search: Optional[str] = None,
265
+ status_filter: Optional[str] = None
266
+ ) -> Dict[str, Any]:
267
+ """
268
+ Get list of scenarios with pagination and filtering.
269
+
270
+ Args:
271
+ project_name: Project name (required)
272
+ skip: Number of scenarios to skip for pagination
273
+ limit: Maximum number of scenarios to return (1-100)
274
+ include_inactive: Include inactive scenarios in results
275
+ search: Search term to filter by name, description, or UUID
276
+ status_filter: Filter by specific status
277
+
278
+ Returns:
279
+ Paginated list of scenarios
280
+ """
281
+ params = {
282
+ "project_name": project_name,
283
+ "skip": skip,
284
+ "limit": limit,
285
+ "include_inactive": include_inactive
286
+ }
287
+ if search:
288
+ params["search"] = search
289
+ if status_filter:
290
+ params["status_filter"] = status_filter
291
+
292
+ response = self.get("/scenarios", params=params)
293
+ return response.json()
235
294
 
236
- def delete_resource(self, name: str, version: Optional[int] = None) -> None:
295
+ def get_scenario(self, uuid: str, project_name: str, version: Optional[int] = None) -> Dict[str, Any]:
237
296
  """
238
- Delete a resource.
239
-
297
+ Get a specific scenario.
298
+
240
299
  Args:
241
- name: Resource name
242
- version: Specific version to delete (if None, deletes all versions)
300
+ uuid: Scenario UUID
301
+ project_name: Project name (required)
302
+ version: Optional specific version (defaults to latest)
303
+
304
+ Returns:
305
+ Scenario data
243
306
  """
307
+ endpoint = f"/scenarios/{uuid}"
308
+ params = {"project_name": project_name}
244
309
  if version is not None:
245
- endpoint = f"/resources/{name}/{version}"
246
- else:
247
- endpoint = f"/resources/{name}"
310
+ params["scenario_version"] = str(version)
311
+
312
+ response = self.get(endpoint, params=params)
313
+ return response.json()
314
+
315
+ def create_scenario(
316
+ self,
317
+ name: str,
318
+ project_name: str,
319
+ description: Optional[str] = None,
320
+ yaml_content: Optional[str] = None
321
+ ) -> Dict[str, Any]:
322
+ """
323
+ Create a new scenario.
324
+
325
+ Args:
326
+ name: Scenario name
327
+ project_name: Project name to organize the scenario (required)
328
+ description: Optional scenario description
329
+ yaml_content: Optional YAML content defining the scenario
330
+
331
+ Returns:
332
+ Created scenario data with UUID and version
333
+
334
+ Raises:
335
+ HTTPException: If scenario name already exists for the project
336
+ """
337
+ data = {
338
+ "name": name,
339
+ "project_name": project_name
340
+ }
341
+ if description:
342
+ data["description"] = description
343
+ if yaml_content:
344
+ data["yaml_content"] = yaml_content
345
+
346
+ response = self.post("/scenarios", data=data)
347
+ return response.json()
348
+
349
+ # Run CI API methods
350
+
351
+ def create_project(self, name: str, description: Optional[str] = None) -> Dict[str, Any]:
352
+ """
353
+ Create a new project.
248
354
 
249
- self.delete(endpoint)
355
+ Args:
356
+ name: Project name
357
+ description: Optional project description
358
+
359
+ Returns:
360
+ Created project data
361
+ """
362
+ data = {"name": name}
363
+ if description:
364
+ data["description"] = description
365
+
366
+ response = self.post("/projects", data=data)
367
+ return response.json()
250
368
 
251
- def get_scenarios(self) -> List[Dict[str, Any]]:
252
- """Get list of all scenarios."""
253
- response = self.get("/scenarios")
369
+ def get_projects(
370
+ self,
371
+ limit: int = 20,
372
+ offset: int = 0
373
+ ) -> Dict[str, Any]:
374
+ """
375
+ Get list of projects.
376
+
377
+ Args:
378
+ limit: Maximum number of projects to return
379
+ offset: Number of projects to skip
380
+
381
+ Returns:
382
+ Project list with pagination info
383
+ """
384
+ params = {"limit": limit, "offset": offset}
385
+ response = self.get("/projects", params=params)
254
386
  return response.json()
255
387
 
256
- def get_scenario(self, uuid: str, version: Optional[int] = None) -> Dict[str, Any]:
388
+ def get_project(self, name: str) -> Dict[str, Any]:
257
389
  """
258
- Get a specific scenario.
390
+ Get a specific project.
259
391
 
260
392
  Args:
261
- uuid: Scenario UUID
262
- version: Specific version (defaults to latest)
393
+ name: Project name
263
394
 
264
395
  Returns:
265
- Scenario data
396
+ Project data
266
397
  """
267
- if version is not None:
268
- endpoint = f"/scenarios/{uuid}/{version}"
269
- else:
270
- endpoint = f"/scenarios/{uuid}"
398
+ response = self.get(f"/projects/{name}")
399
+ return response.json()
400
+
401
+ def update_project(self, name: str, description: Optional[str] = None) -> Dict[str, Any]:
402
+ """
403
+ Update a project.
404
+
405
+ Args:
406
+ name: Project name
407
+ description: Optional new description
408
+
409
+ Returns:
410
+ Updated project data
411
+ """
412
+ data = {}
413
+ if description is not None:
414
+ data["description"] = description
415
+
416
+ response = self.put(f"/projects/{name}", data=data)
417
+ return response.json()
418
+
419
+ def create_run(
420
+ self,
421
+ name: str,
422
+ project_name: str,
423
+ description: Optional[str] = None,
424
+ arguments: Optional[List[Dict[str, str]]] = None,
425
+ tags: Optional[List[str]] = None
426
+ ) -> Dict[str, Any]:
427
+ """
428
+ Create a new run.
271
429
 
272
- response = self.get(endpoint)
430
+ Args:
431
+ name: Run name
432
+ project_name: Project name
433
+ description: Optional run description
434
+ arguments: Optional list of key-value arguments
435
+ tags: Optional list of tags
436
+
437
+ Returns:
438
+ Created run data with runId
439
+ """
440
+ data = {
441
+ "name": name,
442
+ "project_name": project_name,
443
+ "arguments": arguments or [],
444
+ "tags": tags or []
445
+ }
446
+ if description:
447
+ data["description"] = description
448
+
449
+ response = self.post("/runs", data=data)
450
+ return response.json()
451
+
452
+ def get_runs(
453
+ self,
454
+ project_name: Optional[str],
455
+ status: Optional[str] = None,
456
+ tags: Optional[List[str]] = None,
457
+ limit: int = 20,
458
+ offset: int = 0
459
+ ) -> Dict[str, Any]:
460
+ """
461
+ Get list of runs.
462
+
463
+ Args:
464
+ project_name: Filter by project name
465
+ status: Filter by status (running, finished, failed, killed)
466
+ tags: Filter by tags
467
+ limit: Maximum number of runs to return
468
+ offset: Number of runs to skip
469
+
470
+ Returns:
471
+ Run list with pagination info
472
+ """
473
+ params = {"limit": limit, "offset": offset}
474
+ if not project_name:
475
+ raise ValueError("project_name is required to get runs.")
476
+ params["project_name"] = project_name
477
+ if status:
478
+ params["status"] = status
479
+ if tags:
480
+ params["tags"] = ",".join(tags)
481
+
482
+ response = self.get("/runs", params=params)
483
+ return response.json()
484
+
485
+ def get_run(self, run_id: str) -> Dict[str, Any]:
486
+ """
487
+ Get a specific run.
488
+
489
+ Args:
490
+ run_id: Run ID
491
+
492
+ Returns:
493
+ Run data
494
+ """
495
+ response = self.get(f"/runs/{run_id}")
496
+ return response.json()
497
+
498
+ def update_run(
499
+ self,
500
+ run_id: str,
501
+ name: Optional[str] = None,
502
+ description: Optional[str] = None,
503
+ status: Optional[str] = None,
504
+ arguments: Optional[List[Dict[str, str]]] = None,
505
+ tags: Optional[List[str]] = None
506
+ ) -> Dict[str, Any]:
507
+ """
508
+ Update a run.
509
+
510
+ Args:
511
+ run_id: Run ID
512
+ name: Optional new name
513
+ description: Optional new description
514
+ status: Optional new status
515
+ arguments: Optional new arguments
516
+ tags: Optional new tags
517
+
518
+ Returns:
519
+ Updated run data
520
+ """
521
+ data = {}
522
+ if name is not None:
523
+ data["name"] = name
524
+ if description is not None:
525
+ data["description"] = description
526
+ if status is not None:
527
+ data["status"] = status
528
+ if arguments is not None:
529
+ data["arguments"] = arguments
530
+ if tags is not None:
531
+ data["tags"] = tags
532
+
533
+ response = self.put(f"/runs/{run_id}", data=data)
534
+ return response.json()
535
+
536
+ def create_episode(
537
+ self,
538
+ run_id: str,
539
+ episode_name: str,
540
+ status: Optional[str] = None
541
+ ) -> Dict[str, Any]:
542
+ """
543
+ Create a new episode.
544
+
545
+ Args:
546
+ run_id: Run ID
547
+ episode_name: Episode name
548
+ status: Optional episode status
549
+
550
+ Returns:
551
+ Created episode data
552
+ """
553
+ data = {
554
+ "episode_name": episode_name,
555
+ "run_id": run_id
556
+ }
557
+ if status:
558
+ data["status"] = status
559
+
560
+ response = self.post("/episodes", data=data)
561
+ return response.json()
562
+
563
+ def get_episodes(
564
+ self,
565
+ run_id: Optional[str] = None,
566
+ status: Optional[str] = None,
567
+ limit: int = 20,
568
+ offset: int = 0
569
+ ) -> Dict[str, Any]:
570
+ """
571
+ Get list of episodes.
572
+
573
+ Args:
574
+ run_id: Filter by run ID
575
+ status: Filter by status
576
+ limit: Maximum number of episodes to return
577
+ offset: Number of episodes to skip
578
+
579
+ Returns:
580
+ Episode list with pagination info
581
+ """
582
+ params = {"limit": limit, "offset": offset}
583
+ if run_id:
584
+ params["run_id"] = run_id
585
+ if status:
586
+ params["status"] = status
587
+
588
+ response = self.get("/episodes", params=params)
589
+ return response.json()
590
+
591
+ def get_episode(self, run_id: str, episode_name: str) -> Dict[str, Any]:
592
+ """
593
+ Get a specific episode.
594
+
595
+ Args:
596
+ run_id: Run ID
597
+ episode_name: Episode name
598
+
599
+ Returns:
600
+ Episode data
601
+ """
602
+ response = self.get(f"/episodes/{run_id}/{episode_name}")
603
+ return response.json()
604
+
605
+ def update_episode(
606
+ self,
607
+ run_id: str,
608
+ episode_name: str,
609
+ status: Optional[str] = None
610
+ ) -> Dict[str, Any]:
611
+ """
612
+ Update an episode.
613
+
614
+ Args:
615
+ run_id: Run ID
616
+ episode_name: Episode name
617
+ status: Optional new status
618
+
619
+ Returns:
620
+ Updated episode data
621
+ """
622
+ data = {}
623
+ if status is not None:
624
+ data["status"] = status
625
+
626
+ response = self.put(f"/episodes/{run_id}/{episode_name}", data=data)
627
+ return response.json()
628
+
629
+ def delete_episode(self, run_id: str, episode_name: str) -> None:
630
+ """
631
+ Delete an episode.
632
+
633
+ Args:
634
+ run_id: Run ID
635
+ episode_name: Episode name
636
+ """
637
+ self.delete(f"/episodes/{run_id}/{episode_name}")
638
+
639
+ def upload_blob(
640
+ self,
641
+ artifact_key: str,
642
+ run_id: str,
643
+ file_path: str,
644
+ artifact_type: str,
645
+ episode_name: Optional[str] = None,
646
+ description: Optional[str] = None
647
+ ) -> Dict[str, Any]:
648
+ """
649
+ Upload a blob artifact (image/video).
650
+
651
+ Args:
652
+ artifact_key: Artifact key identifier
653
+ run_id: Run ID
654
+ file_path: Path to file to upload
655
+ artifact_type: Type of artifact ('image' or 'video')
656
+ episode_name: Optional episode name (None for run-level artifacts)
657
+ description: Optional description
658
+
659
+ Returns:
660
+ Created artifact data
661
+ """
662
+ with open(file_path, 'rb') as f:
663
+ files = {'file': f}
664
+ form_data = {
665
+ 'artifact_key': artifact_key,
666
+ 'run_id': run_id,
667
+ 'artifact_type': artifact_type
668
+ }
669
+ if episode_name:
670
+ form_data['episode_name'] = episode_name
671
+ if description:
672
+ form_data['description'] = description
673
+
674
+ response = self.post("/artifacts/blob/upload", files=files, data=form_data)
675
+ return response.json()
676
+
677
+ def upsert_metrics(
678
+ self,
679
+ artifact_key: str,
680
+ run_id: str,
681
+ metric_type: str,
682
+ metric_data: List[Dict[str, Any]],
683
+ episode_name: Optional[str] = None,
684
+ description: Optional[str] = None
685
+ ) -> Dict[str, Any]:
686
+ """
687
+ Upsert metrics artifact (create or append).
688
+
689
+ Args:
690
+ artifact_key: Artifact key identifier
691
+ run_id: Run ID
692
+ metric_type: Type of metric display ('line', 'bar', 'scatter', 'gauge', 'counter')
693
+ metric_data: List of metric data points with 'key', 'values', 'timestamp'
694
+ episode_name: Optional episode name (None for run-level artifacts)
695
+ description: Optional description
696
+
697
+ Returns:
698
+ Created/updated artifact data
699
+ """
700
+ data = {
701
+ "artifact_key": artifact_key,
702
+ "run_id": run_id,
703
+ "metric_type": metric_type,
704
+ "metric_data": metric_data
705
+ }
706
+ if episode_name:
707
+ data["episode_name"] = episode_name
708
+ if description:
709
+ data["description"] = description
710
+
711
+ response = self.post("/artifacts/metrics", data=data)
712
+ return response.json()
713
+
714
+ def get_artifacts(
715
+ self,
716
+ run_id: Optional[str] = None,
717
+ episode_name: Optional[str] = None,
718
+ artifact_type: Optional[str] = None,
719
+ limit: int = 20,
720
+ offset: int = 0
721
+ ) -> Dict[str, Any]:
722
+ """
723
+ Get list of artifacts.
724
+
725
+ Args:
726
+ run_id: Filter by run ID
727
+ episode_name: Filter by episode name
728
+ artifact_type: Filter by artifact type
729
+ limit: Maximum number of artifacts to return
730
+ offset: Number of artifacts to skip
731
+
732
+ Returns:
733
+ Artifact list with pagination info
734
+ """
735
+ params = {"limit": limit, "offset": offset}
736
+ if run_id:
737
+ params["run_id"] = run_id
738
+ if episode_name:
739
+ params["episode_name"] = episode_name
740
+ if artifact_type:
741
+ params["artifact_type"] = artifact_type
742
+
743
+ response = self.get("/artifacts", params=params)
744
+ return response.json()
745
+
746
+ def get_artifact(
747
+ self,
748
+ run_id: str,
749
+ episode_name: str,
750
+ artifact_key: str
751
+ ) -> Dict[str, Any]:
752
+ """
753
+ Get a specific artifact.
754
+
755
+ Args:
756
+ run_id: Run ID
757
+ episode_name: Episode name
758
+ artifact_key: Artifact key
759
+
760
+ Returns:
761
+ Artifact data
762
+ """
763
+ response = self.get(f"/artifacts/{run_id}/{episode_name}/{artifact_key}")
273
764
  return response.json()