humalab 0.0.1__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.
- humalab/__init__.py +9 -0
- humalab/assets/__init__.py +0 -0
- humalab/assets/archive.py +101 -0
- humalab/assets/resource_file.py +28 -0
- humalab/assets/resource_handler.py +175 -0
- humalab/constants.py +7 -0
- humalab/dists/__init__.py +17 -0
- humalab/dists/bernoulli.py +44 -0
- humalab/dists/categorical.py +49 -0
- humalab/dists/discrete.py +56 -0
- humalab/dists/distribution.py +38 -0
- humalab/dists/gaussian.py +49 -0
- humalab/dists/log_uniform.py +49 -0
- humalab/dists/truncated_gaussian.py +64 -0
- humalab/dists/uniform.py +49 -0
- humalab/humalab.py +149 -0
- humalab/humalab_api_client.py +273 -0
- humalab/humalab_config.py +86 -0
- humalab/humalab_test.py +510 -0
- humalab/metrics/__init__.py +11 -0
- humalab/metrics/dist_metric.py +22 -0
- humalab/metrics/metric.py +129 -0
- humalab/metrics/summary.py +54 -0
- humalab/run.py +214 -0
- humalab/scenario.py +225 -0
- humalab/scenario_test.py +911 -0
- humalab-0.0.1.dist-info/METADATA +43 -0
- humalab-0.0.1.dist-info/RECORD +32 -0
- humalab-0.0.1.dist-info/WHEEL +5 -0
- humalab-0.0.1.dist-info/entry_points.txt +2 -0
- humalab-0.0.1.dist-info/licenses/LICENSE +21 -0
- humalab-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from humalab_sdk.dists.distribution import Distribution
|
|
2
|
+
from typing import Any
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TruncatedGaussian(Distribution):
|
|
7
|
+
def __init__(self,
|
|
8
|
+
generator: np.random.Generator,
|
|
9
|
+
loc: float | Any,
|
|
10
|
+
scale: float | Any,
|
|
11
|
+
low: float | Any,
|
|
12
|
+
high: float | Any,
|
|
13
|
+
size: int | tuple[int, ...] | None = None) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Initialize the truncated Gaussian (normal) distribution.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
generator (np.random.Generator): The random number generator.
|
|
19
|
+
loc (float | Any): The mean of the distribution.
|
|
20
|
+
scale (float | Any): The standard deviation of the distribution.
|
|
21
|
+
low (float | Any): The lower truncation bound.
|
|
22
|
+
high (float | Any): The upper truncation bound.
|
|
23
|
+
size (int | tuple[int, ...] | None): The size of the output.
|
|
24
|
+
"""
|
|
25
|
+
super().__init__(generator=generator)
|
|
26
|
+
self._loc = loc
|
|
27
|
+
self._scale = scale
|
|
28
|
+
self._low = low
|
|
29
|
+
self._high = high
|
|
30
|
+
self._size = size
|
|
31
|
+
|
|
32
|
+
def _sample(self) -> int | float | np.ndarray:
|
|
33
|
+
samples = self._generator.normal(loc=self._loc, scale=self._scale, size=self._size)
|
|
34
|
+
mask = (samples < self._low) | (samples > self._high)
|
|
35
|
+
while np.any(mask):
|
|
36
|
+
samples[mask] = self._generator.normal(loc=self._loc, scale=self._scale, size=np.sum(mask))
|
|
37
|
+
mask = (samples < self._low) | (samples > self._high)
|
|
38
|
+
return samples
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return f"TruncatedGaussian(loc={self._loc}, scale={self._scale}, low={self._low}, high={self._high}, size={self._size})"
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def create(generator: np.random.Generator,
|
|
45
|
+
loc: float | Any,
|
|
46
|
+
scale: float | Any,
|
|
47
|
+
low: float | Any,
|
|
48
|
+
high: float | Any,
|
|
49
|
+
size: int | tuple[int, ...] | None = None) -> 'TruncatedGaussian':
|
|
50
|
+
"""
|
|
51
|
+
Create a truncated Gaussian (normal) distribution.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
generator (np.random.Generator): The random number generator.
|
|
55
|
+
loc (float | Any): The mean of the distribution.
|
|
56
|
+
scale (float | Any): The standard deviation of the distribution.
|
|
57
|
+
low (float | Any): The lower truncation bound.
|
|
58
|
+
high (float | Any): The upper truncation bound.
|
|
59
|
+
size (int | tuple[int, ...] | None): The size of the output.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
TruncatedGaussian: The created truncated Gaussian distribution.
|
|
63
|
+
"""
|
|
64
|
+
return TruncatedGaussian(generator=generator, loc=loc, scale=scale, low=low, high=high, size=size)
|
humalab/dists/uniform.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from humalab_sdk.dists.distribution import Distribution
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
class Uniform(Distribution):
|
|
7
|
+
def __init__(self,
|
|
8
|
+
generator: np.random.Generator,
|
|
9
|
+
low: float | Any,
|
|
10
|
+
high: float | Any,
|
|
11
|
+
size: int | tuple[int, ...] | None = None, ) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Initialize the uniform distribution.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
generator (np.random.Generator): The random number generator.
|
|
17
|
+
low (float | Any): The lower bound (inclusive).
|
|
18
|
+
high (float | Any): The upper bound (exclusive).
|
|
19
|
+
size (int | tuple[int, ...] | None): The size of the output.
|
|
20
|
+
"""
|
|
21
|
+
super().__init__(generator=generator)
|
|
22
|
+
self._low = np.array(low)
|
|
23
|
+
self._high = np.array(high)
|
|
24
|
+
self._size = size
|
|
25
|
+
|
|
26
|
+
def _sample(self) -> int | float | np.ndarray:
|
|
27
|
+
return self._generator.uniform(self._low, self._high, size=self._size)
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return f"Uniform(low={self._low}, high={self._high}, size={self._size})"
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def create(generator: np.random.Generator,
|
|
34
|
+
low: float | Any,
|
|
35
|
+
high: float | Any,
|
|
36
|
+
size: int | tuple[int, ...] | None = None) -> 'Uniform':
|
|
37
|
+
"""
|
|
38
|
+
Create a uniform distribution.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
generator (np.random.Generator): The random number generator.
|
|
42
|
+
low (float | Any): The lower bound (inclusive).
|
|
43
|
+
high (float | Any): The upper bound (exclusive).
|
|
44
|
+
size (int | tuple[int, ...] | None): The size of the output.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Uniform: The created uniform distribution.
|
|
48
|
+
"""
|
|
49
|
+
return Uniform(generator=generator, low=low, high=high, size=size)
|
humalab/humalab.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from humalab_sdk.run import Run
|
|
3
|
+
from humalab_sdk.humalab_config import HumalabConfig
|
|
4
|
+
from humalab_sdk.humalab_api_client import HumaLabApiClient
|
|
5
|
+
from humalab_sdk.constants import EpisodeStatus
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from collections.abc import Generator
|
|
11
|
+
|
|
12
|
+
from humalab_sdk.scenario import Scenario
|
|
13
|
+
|
|
14
|
+
_cur_run: Run | None = None
|
|
15
|
+
|
|
16
|
+
def _pull_scenario(client: HumaLabApiClient,
|
|
17
|
+
scenario: str | list | dict | None = None,
|
|
18
|
+
scenario_id: str | None = None,) -> str | list | dict | None:
|
|
19
|
+
if scenario_id is not None:
|
|
20
|
+
scenario_response = client.get_scenario(uuid=scenario_id)
|
|
21
|
+
return scenario_response["yaml_content"]
|
|
22
|
+
return scenario
|
|
23
|
+
|
|
24
|
+
@contextmanager
|
|
25
|
+
def init(entity: str | None = None,
|
|
26
|
+
project: str | None = None,
|
|
27
|
+
name: str | None = None,
|
|
28
|
+
description: str | None = None,
|
|
29
|
+
id: str | None = None,
|
|
30
|
+
tags: list[str] | None = None,
|
|
31
|
+
scenario: str | list | dict | None = None,
|
|
32
|
+
scenario_id: str | None = None,
|
|
33
|
+
base_url: str | None = None,
|
|
34
|
+
api_key: str | None = None,
|
|
35
|
+
seed: int | None=None,
|
|
36
|
+
timeout: float | None = None,
|
|
37
|
+
num_env: int | None = None
|
|
38
|
+
) -> Generator[Run, None, None]:
|
|
39
|
+
global _cur_run
|
|
40
|
+
run = None
|
|
41
|
+
try:
|
|
42
|
+
humalab_config = HumalabConfig()
|
|
43
|
+
entity = entity or humalab_config.entity
|
|
44
|
+
project = project or "default"
|
|
45
|
+
name = name or ""
|
|
46
|
+
description = description or ""
|
|
47
|
+
id = id or str(uuid.uuid4())
|
|
48
|
+
|
|
49
|
+
base_url = base_url or humalab_config.base_url
|
|
50
|
+
api_key = api_key or humalab_config.api_key
|
|
51
|
+
timeout = timeout or humalab_config.timeout
|
|
52
|
+
|
|
53
|
+
api_client = HumaLabApiClient(base_url=base_url,
|
|
54
|
+
api_key=api_key,
|
|
55
|
+
timeout=timeout)
|
|
56
|
+
final_scenario = _pull_scenario(client=api_client,
|
|
57
|
+
scenario=scenario,
|
|
58
|
+
scenario_id=scenario_id)
|
|
59
|
+
scenario_inst = Scenario()
|
|
60
|
+
scenario_inst.init(run_id=id,
|
|
61
|
+
scenario=final_scenario,
|
|
62
|
+
seed=seed,
|
|
63
|
+
episode_id=str(uuid.uuid4()),
|
|
64
|
+
num_env=num_env)
|
|
65
|
+
|
|
66
|
+
run = Run(
|
|
67
|
+
entity=entity,
|
|
68
|
+
project=project,
|
|
69
|
+
name=name,
|
|
70
|
+
description=description,
|
|
71
|
+
id=id,
|
|
72
|
+
tags=tags,
|
|
73
|
+
scenario=scenario_inst,
|
|
74
|
+
)
|
|
75
|
+
_cur_run = run
|
|
76
|
+
yield run
|
|
77
|
+
finally:
|
|
78
|
+
if run:
|
|
79
|
+
run.finish()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def finish(status: EpisodeStatus = EpisodeStatus.PASS,
|
|
83
|
+
quiet: bool | None = None) -> None:
|
|
84
|
+
global _cur_run
|
|
85
|
+
if _cur_run:
|
|
86
|
+
_cur_run.finish(status=status, quiet=quiet)
|
|
87
|
+
|
|
88
|
+
def login(api_key: str | None = None,
|
|
89
|
+
relogin: bool | None = None,
|
|
90
|
+
host: str | None = None,
|
|
91
|
+
force: bool | None = None,
|
|
92
|
+
timeout: float | None = None) -> bool:
|
|
93
|
+
# TODO: Validate api_key against host given.
|
|
94
|
+
# and retrieve entity information.
|
|
95
|
+
humalab_config = HumalabConfig()
|
|
96
|
+
humalab_config.api_key = api_key or humalab_config.api_key
|
|
97
|
+
humalab_config.base_url = host or humalab_config.base_url
|
|
98
|
+
humalab_config.timeout = timeout or humalab_config.timeout
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
login(api_key="GSdIImnRJs1TQRpkN74PyIVHhX8_PISLOI9NVF6uO94",
|
|
104
|
+
host="http://localhost:8000")
|
|
105
|
+
|
|
106
|
+
with init(entity="default",
|
|
107
|
+
project="test",
|
|
108
|
+
name="my first run",
|
|
109
|
+
description="testing the humalab sdk",
|
|
110
|
+
tags=["tag1", "tag2"],
|
|
111
|
+
scenario_id="cb9668c6-99fe-490c-a97c-e8c1f06b54a6",
|
|
112
|
+
num_env=None) as run:
|
|
113
|
+
print(f"Run ID: {run.id}")
|
|
114
|
+
print(f"Run Name: {run.name}")
|
|
115
|
+
print(f"Run Description: {run.description}")
|
|
116
|
+
print(f"Run Tags: {run.tags}")
|
|
117
|
+
print(f"Run Scenario YAML:\n{run.scenario.yaml}")
|
|
118
|
+
|
|
119
|
+
scenario = run.scenario
|
|
120
|
+
# Simulate some operations
|
|
121
|
+
print("CUP position: ", scenario.scenario.cup.position)
|
|
122
|
+
print("CUP orientation: ", scenario.scenario.cup.orientation)
|
|
123
|
+
print("Asset: ", scenario.scenario.cup.asset)
|
|
124
|
+
print("Friction: ", scenario.scenario.cup.friction)
|
|
125
|
+
print("Num Objects: ", scenario.scenario.num_objects)
|
|
126
|
+
scenario.reset()
|
|
127
|
+
print("======================SCENARIO RESET==========================")
|
|
128
|
+
print("CUP position: ", scenario.scenario.cup.position)
|
|
129
|
+
print("CUP orientation: ", scenario.scenario.cup.orientation)
|
|
130
|
+
print("Asset: ", scenario.scenario.cup.asset)
|
|
131
|
+
print("Friction: ", scenario.scenario.cup.friction)
|
|
132
|
+
print("Num Objects: ", scenario.scenario.num_objects)
|
|
133
|
+
|
|
134
|
+
humalab_config = HumalabConfig()
|
|
135
|
+
base_url = humalab_config.base_url
|
|
136
|
+
api_key = humalab_config.api_key
|
|
137
|
+
timeout = humalab_config.timeout
|
|
138
|
+
|
|
139
|
+
api_client = HumaLabApiClient(base_url=base_url,
|
|
140
|
+
api_key=api_key,
|
|
141
|
+
timeout=timeout)
|
|
142
|
+
resource = api_client.get_resource(name="lerobot", version=1)
|
|
143
|
+
print("Resource metadata: ", resource)
|
|
144
|
+
file_content = api_client.download_resource(name="lerobot")
|
|
145
|
+
filename = os.path.basename(resource['resource_url'])
|
|
146
|
+
filename = os.path.join(humalab_config.workspace_path, filename)
|
|
147
|
+
with open(filename, "wb") as f:
|
|
148
|
+
f.write(file_content)
|
|
149
|
+
print(f"Resource file downloaded: {filename}")
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""HTTP client for accessing HumaLab service APIs with API key authentication."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import requests
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
from urllib.parse import urljoin
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HumaLabApiClient:
|
|
10
|
+
"""HTTP client for making authenticated requests to HumaLab service APIs."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
base_url: str | None = None,
|
|
15
|
+
api_key: str | None = None,
|
|
16
|
+
timeout: float | None = None
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Initialize the HumaLab API client.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
base_url: Base URL for the HumaLab service (defaults to localhost:8000)
|
|
23
|
+
api_key: API key for authentication (defaults to HUMALAB_API_KEY env var)
|
|
24
|
+
timeout: Request timeout in seconds
|
|
25
|
+
"""
|
|
26
|
+
self.base_url = base_url or os.getenv("HUMALAB_SERVICE_URL", "http://localhost:8000")
|
|
27
|
+
self.api_key = api_key or os.getenv("HUMALAB_API_KEY")
|
|
28
|
+
self.timeout = timeout or 30.0 # Default timeout of 30 seconds
|
|
29
|
+
|
|
30
|
+
# Ensure base_url ends without trailing slash
|
|
31
|
+
self.base_url = self.base_url.rstrip('/')
|
|
32
|
+
|
|
33
|
+
if not self.api_key:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"API key is required. Set HUMALAB_API_KEY environment variable "
|
|
36
|
+
"or pass api_key parameter to HumaLabApiClient constructor."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
40
|
+
"""Get common headers for API requests."""
|
|
41
|
+
return {
|
|
42
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
"User-Agent": "HumaLab-SDK/1.0"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def _make_request(
|
|
48
|
+
self,
|
|
49
|
+
method: str,
|
|
50
|
+
endpoint: str,
|
|
51
|
+
data: Optional[Dict[str, Any]] = None,
|
|
52
|
+
params: Optional[Dict[str, Any]] = None,
|
|
53
|
+
files: Optional[Dict[str, Any]] = None,
|
|
54
|
+
**kwargs
|
|
55
|
+
) -> requests.Response:
|
|
56
|
+
"""
|
|
57
|
+
Make an HTTP request to the HumaLab service.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
61
|
+
endpoint: API endpoint (will be joined with base_url)
|
|
62
|
+
data: JSON data for request body
|
|
63
|
+
params: Query parameters
|
|
64
|
+
files: Files for multipart upload
|
|
65
|
+
**kwargs: Additional arguments passed to requests
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
requests.Response object
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
requests.exceptions.RequestException: For HTTP errors
|
|
72
|
+
"""
|
|
73
|
+
url = urljoin(self.base_url + "/", endpoint.lstrip('/'))
|
|
74
|
+
headers = self._get_headers()
|
|
75
|
+
|
|
76
|
+
# If files are being uploaded, don't set Content-Type (let requests handle it)
|
|
77
|
+
if files:
|
|
78
|
+
headers.pop("Content-Type", None)
|
|
79
|
+
|
|
80
|
+
response = requests.request(
|
|
81
|
+
method=method,
|
|
82
|
+
url=url,
|
|
83
|
+
json=data,
|
|
84
|
+
params=params,
|
|
85
|
+
files=files,
|
|
86
|
+
headers=headers,
|
|
87
|
+
timeout=self.timeout,
|
|
88
|
+
**kwargs
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Raise an exception for HTTP error responses
|
|
92
|
+
response.raise_for_status()
|
|
93
|
+
|
|
94
|
+
return response
|
|
95
|
+
|
|
96
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs) -> requests.Response:
|
|
97
|
+
"""Make a GET request."""
|
|
98
|
+
return self._make_request("GET", endpoint, params=params, **kwargs)
|
|
99
|
+
|
|
100
|
+
def post(
|
|
101
|
+
self,
|
|
102
|
+
endpoint: str,
|
|
103
|
+
data: Optional[Dict[str, Any]] = None,
|
|
104
|
+
files: Optional[Dict[str, Any]] = None,
|
|
105
|
+
**kwargs
|
|
106
|
+
) -> requests.Response:
|
|
107
|
+
"""Make a POST request."""
|
|
108
|
+
return self._make_request("POST", endpoint, data=data, files=files, **kwargs)
|
|
109
|
+
|
|
110
|
+
def put(self, endpoint: str, data: Optional[Dict[str, Any]] = None, **kwargs) -> requests.Response:
|
|
111
|
+
"""Make a PUT request."""
|
|
112
|
+
return self._make_request("PUT", endpoint, data=data, **kwargs)
|
|
113
|
+
|
|
114
|
+
def delete(self, endpoint: str, **kwargs) -> requests.Response:
|
|
115
|
+
"""Make a DELETE request."""
|
|
116
|
+
return self._make_request("DELETE", endpoint, **kwargs)
|
|
117
|
+
|
|
118
|
+
# Convenience methods for common API operations
|
|
119
|
+
|
|
120
|
+
def get_resources(
|
|
121
|
+
self,
|
|
122
|
+
resource_types: Optional[str] = None,
|
|
123
|
+
limit: int = 20,
|
|
124
|
+
offset: int = 0,
|
|
125
|
+
latest_only: bool = False
|
|
126
|
+
) -> Dict[str, Any]:
|
|
127
|
+
"""
|
|
128
|
+
Get list of all resources.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
resource_types: Comma-separated resource types to filter by
|
|
132
|
+
limit: Maximum number of resources to return
|
|
133
|
+
offset: Number of resources to skip
|
|
134
|
+
latest_only: If true, only return latest version of each resource
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Resource list with pagination info
|
|
138
|
+
"""
|
|
139
|
+
params = {
|
|
140
|
+
"limit": limit,
|
|
141
|
+
"offset": offset,
|
|
142
|
+
"latest_only": latest_only
|
|
143
|
+
}
|
|
144
|
+
if resource_types:
|
|
145
|
+
params["resource_types"] = resource_types
|
|
146
|
+
|
|
147
|
+
response = self.get("/resources", params=params)
|
|
148
|
+
return response.json()
|
|
149
|
+
|
|
150
|
+
def get_resource(self, name: str, version: Optional[int] = None) -> Dict[str, Any]:
|
|
151
|
+
"""
|
|
152
|
+
Get a specific resource.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
name: Resource name
|
|
156
|
+
version: Specific version (defaults to latest)
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Resource data
|
|
160
|
+
"""
|
|
161
|
+
if version is not None:
|
|
162
|
+
endpoint = f"/resources/{name}/{version}"
|
|
163
|
+
else:
|
|
164
|
+
endpoint = f"/resources/{name}"
|
|
165
|
+
|
|
166
|
+
response = self.get(endpoint)
|
|
167
|
+
return response.json()
|
|
168
|
+
|
|
169
|
+
def download_resource(
|
|
170
|
+
self,
|
|
171
|
+
name: str,
|
|
172
|
+
version: Optional[int] = None
|
|
173
|
+
) -> bytes:
|
|
174
|
+
"""
|
|
175
|
+
Download a resource file.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
name: Resource name
|
|
179
|
+
version: Specific version (defaults to latest)
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Resource file content as bytes
|
|
183
|
+
"""
|
|
184
|
+
if version is not None:
|
|
185
|
+
endpoint = f"/resources/{name}/download?version={version}"
|
|
186
|
+
else:
|
|
187
|
+
endpoint = f"/resources/{name}/download"
|
|
188
|
+
|
|
189
|
+
response = self.get(endpoint)
|
|
190
|
+
return response.content
|
|
191
|
+
|
|
192
|
+
def upload_resource(
|
|
193
|
+
self,
|
|
194
|
+
name: str,
|
|
195
|
+
file_path: str,
|
|
196
|
+
resource_type: str,
|
|
197
|
+
description: Optional[str] = None,
|
|
198
|
+
filename: Optional[str] = None,
|
|
199
|
+
allow_duplicate_name: bool = False
|
|
200
|
+
) -> Dict[str, Any]:
|
|
201
|
+
"""
|
|
202
|
+
Upload a resource file.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
name: Resource name
|
|
206
|
+
file_path: Path to file to upload
|
|
207
|
+
resource_type: Type of resource (urdf, mjcf, etc.)
|
|
208
|
+
description: Optional description
|
|
209
|
+
filename: Optional custom filename
|
|
210
|
+
allow_duplicate_name: Allow creating new version with existing name
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Created resource data
|
|
214
|
+
"""
|
|
215
|
+
with open(file_path, 'rb') as f:
|
|
216
|
+
files = {'file': f}
|
|
217
|
+
data = {}
|
|
218
|
+
if description:
|
|
219
|
+
data['description'] = description
|
|
220
|
+
if filename:
|
|
221
|
+
data['filename'] = filename
|
|
222
|
+
|
|
223
|
+
params = {
|
|
224
|
+
'resource_type': resource_type,
|
|
225
|
+
'allow_duplicate_name': allow_duplicate_name
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
response = self.post(f"/resources/{name}/upload", files=files, params=params)
|
|
229
|
+
return response.json()
|
|
230
|
+
|
|
231
|
+
def get_resource_types(self) -> List[str]:
|
|
232
|
+
"""Get list of all available resource types."""
|
|
233
|
+
response = self.get("/resources/types")
|
|
234
|
+
return response.json()
|
|
235
|
+
|
|
236
|
+
def delete_resource(self, name: str, version: Optional[int] = None) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Delete a resource.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
name: Resource name
|
|
242
|
+
version: Specific version to delete (if None, deletes all versions)
|
|
243
|
+
"""
|
|
244
|
+
if version is not None:
|
|
245
|
+
endpoint = f"/resources/{name}/{version}"
|
|
246
|
+
else:
|
|
247
|
+
endpoint = f"/resources/{name}"
|
|
248
|
+
|
|
249
|
+
self.delete(endpoint)
|
|
250
|
+
|
|
251
|
+
def get_scenarios(self) -> List[Dict[str, Any]]:
|
|
252
|
+
"""Get list of all scenarios."""
|
|
253
|
+
response = self.get("/scenarios")
|
|
254
|
+
return response.json()
|
|
255
|
+
|
|
256
|
+
def get_scenario(self, uuid: str, version: Optional[int] = None) -> Dict[str, Any]:
|
|
257
|
+
"""
|
|
258
|
+
Get a specific scenario.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
uuid: Scenario UUID
|
|
262
|
+
version: Specific version (defaults to latest)
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Scenario data
|
|
266
|
+
"""
|
|
267
|
+
if version is not None:
|
|
268
|
+
endpoint = f"/scenarios/{uuid}/{version}"
|
|
269
|
+
else:
|
|
270
|
+
endpoint = f"/scenarios/{uuid}"
|
|
271
|
+
|
|
272
|
+
response = self.get(endpoint)
|
|
273
|
+
return response.json()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import yaml
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
class HumalabConfig:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self._config = {
|
|
8
|
+
"workspace_path": "",
|
|
9
|
+
"entity": "",
|
|
10
|
+
"base_url": "",
|
|
11
|
+
"api_key": "",
|
|
12
|
+
"timeout": 30.0,
|
|
13
|
+
}
|
|
14
|
+
self._workspace_path = ""
|
|
15
|
+
self._entity = ""
|
|
16
|
+
self._base_url = ""
|
|
17
|
+
self._api_key = ""
|
|
18
|
+
self._timeout = 30.0
|
|
19
|
+
self._load_config()
|
|
20
|
+
|
|
21
|
+
def _load_config(self):
|
|
22
|
+
home_path = Path.home()
|
|
23
|
+
config_path = home_path / ".humalab" / "config.yaml"
|
|
24
|
+
if not config_path.exists():
|
|
25
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
config_path.touch()
|
|
27
|
+
with open(config_path, "r") as f:
|
|
28
|
+
self._config = yaml.safe_load(f)
|
|
29
|
+
self._workspace_path = os.path.expanduser(self._config["workspace_path"]) if self._config and "workspace_path" in self._config else home_path
|
|
30
|
+
self._entity = self._config["entity"] if self._config and "entity" in self._config else ""
|
|
31
|
+
self._base_url = self._config["base_url"] if self._config and "base_url" in self._config else ""
|
|
32
|
+
self._api_key = self._config["api_key"] if self._config and "api_key" in self._config else ""
|
|
33
|
+
self._timeout = self._config["timeout"] if self._config and "timeout" in self._config else 30.0
|
|
34
|
+
|
|
35
|
+
def _save(self) -> None:
|
|
36
|
+
yaml.dump(self._config, open(Path.home() / ".humalab" / "config.yaml", "w"))
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def workspace_path(self) -> str:
|
|
40
|
+
return str(self._workspace_path)
|
|
41
|
+
|
|
42
|
+
@workspace_path.setter
|
|
43
|
+
def workspace_path(self, path: str) -> None:
|
|
44
|
+
self._workspace_path = path
|
|
45
|
+
self._config["workspace_path"] = path
|
|
46
|
+
self._save()
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def entity(self) -> str:
|
|
50
|
+
return str(self._entity)
|
|
51
|
+
|
|
52
|
+
@entity.setter
|
|
53
|
+
def entity(self, entity: str) -> None:
|
|
54
|
+
self._entity = entity
|
|
55
|
+
self._config["entity"] = entity
|
|
56
|
+
self._save()
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def base_url(self) -> str:
|
|
60
|
+
return str(self._base_url)
|
|
61
|
+
|
|
62
|
+
@base_url.setter
|
|
63
|
+
def base_url(self, base_url: str) -> None:
|
|
64
|
+
self._base_url = base_url
|
|
65
|
+
self._config["base_url"] = base_url
|
|
66
|
+
self._save()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def api_key(self) -> str:
|
|
70
|
+
return str(self._api_key)
|
|
71
|
+
|
|
72
|
+
@api_key.setter
|
|
73
|
+
def api_key(self, api_key: str) -> None:
|
|
74
|
+
self._api_key = api_key
|
|
75
|
+
self._config["api_key"] = api_key
|
|
76
|
+
self._save()
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def timeout(self) -> float:
|
|
80
|
+
return self._timeout
|
|
81
|
+
|
|
82
|
+
@timeout.setter
|
|
83
|
+
def timeout(self, timeout: float) -> None:
|
|
84
|
+
self._timeout = timeout
|
|
85
|
+
self._config["timeout"] = timeout
|
|
86
|
+
self._save()
|