harmony-client 0.1.0__cp312-cp312-macosx_10_9_universal2.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.
@@ -0,0 +1,368 @@
1
+ import asyncio
2
+ import importlib.machinery
3
+ import importlib.util
4
+ import inspect
5
+ import os
6
+ import re
7
+ import site
8
+ import subprocess
9
+ import sys
10
+ import traceback
11
+ import types
12
+ from pathlib import Path
13
+ from typing import Any, Optional
14
+
15
+ import tomli
16
+ from loguru import logger
17
+ from pydantic import Field
18
+ from pydantic_settings import BaseSettings, SettingsConfigDict
19
+
20
+ from harmony_client.runtime.context import RecipeConfig, RecipeContext
21
+ from harmony_client.runtime.data import InputConfig
22
+
23
+
24
+ class RunnerArgs(RecipeConfig, BaseSettings):
25
+ model_config = SettingsConfigDict(env_prefix="ADAPTIVE_", cli_parse_args=True, cli_kebab_case=True)
26
+
27
+ recipe_file: Optional[str] = Field(default=None, description="the python recipe file to execute")
28
+ recipe_file_url: Optional[str] = Field(
29
+ default=None, description="Url of recipe in zip format to download and extract to execute"
30
+ )
31
+
32
+
33
+ def main():
34
+ runner_args = RunnerArgs() # type: ignore
35
+ context = asyncio.run(RecipeContext.from_config(runner_args))
36
+ logger.trace("Loaded config: {}", context.config)
37
+ try:
38
+ if runner_args.recipe_file:
39
+ _load_and_run_recipe(context, runner_args.recipe_file)
40
+ elif runner_args.recipe_file_url:
41
+ recipe_folder = _download_and_extract_recipe(context, runner_args.recipe_file_url)
42
+ _load_and_run_recipe(context, recipe_folder)
43
+ else:
44
+ raise ValueError("recipe_file or recipe_file_url must be provided")
45
+ except Exception as e:
46
+ stack_trace = traceback.format_exc()
47
+ recipe_source = runner_args.recipe_file if runner_args.recipe_file else runner_args.recipe_file_url
48
+ logger.exception(f"Error while running recipe file {recipe_source}", exception=e)
49
+
50
+ try:
51
+ context.job.report_error(stack_trace)
52
+ except Exception as e2:
53
+ logger.error(f"Error while reporting error: {e2}")
54
+ logger.error(f"Stack trace: {traceback.format_exc()}")
55
+
56
+ sys.exit(1)
57
+
58
+
59
+ def _load_and_run_recipe(context: RecipeContext, recipe_path: str):
60
+ entry = Path(recipe_path).resolve()
61
+
62
+ _install_recipe_dependencies(entry)
63
+ # Reload site to pick up .pth files created by editable install
64
+ importlib.reload(site)
65
+
66
+ if entry.is_dir():
67
+ entry_file = entry / "main.py"
68
+ if not entry_file.exists():
69
+ raise FileNotFoundError(f"main.py not found in {entry}")
70
+ pkg_dir = entry
71
+ module_name = "main"
72
+ else:
73
+ if entry.suffix != ".py":
74
+ raise ValueError(f"Expected a Python file or directory, got: {entry}")
75
+ entry_file = entry
76
+ pkg_dir = entry.parent
77
+ module_name = entry.stem
78
+
79
+ # Create a stable synthetic package name tied to the directory
80
+ synthetic_pkg = f"_adhoc_recipe_{abs(hash(str(pkg_dir))) & 0xFFFFFFFF:x}"
81
+
82
+ # Clear any previous loads of this synthetic package in the current process
83
+ for key in list(sys.modules.keys()):
84
+ if key == synthetic_pkg or key.startswith(synthetic_pkg + "."):
85
+ del sys.modules[key]
86
+
87
+ # Build a synthetic namespace package pointing at pkg_dir
88
+ pkg_mod = types.ModuleType(synthetic_pkg)
89
+ pkg_mod.__path__ = [str(pkg_dir)] # allow submodule search in this directory
90
+ pkg_mod.__package__ = synthetic_pkg
91
+ spec_pkg = importlib.machinery.ModuleSpec(synthetic_pkg, loader=None, is_package=True)
92
+ spec_pkg.submodule_search_locations = [str(pkg_dir)]
93
+ pkg_mod.__spec__ = spec_pkg
94
+ sys.modules[synthetic_pkg] = pkg_mod
95
+
96
+ # Load the entry file as a submodule of the synthetic package
97
+ fullname = f"{synthetic_pkg}.{module_name}"
98
+ spec = importlib.util.spec_from_file_location(fullname, str(entry_file))
99
+ if spec is None or spec.loader is None:
100
+ raise ImportError(f"Cannot load spec for {entry_file}")
101
+ module = importlib.util.module_from_spec(spec)
102
+ sys.modules[fullname] = module
103
+ spec.loader.exec_module(module)
104
+
105
+ # get recipe_main function
106
+ functions = inspect.getmembers(module, inspect.isfunction)
107
+ recipe_main_functions = [(name, func) for name, func in functions if getattr(func, "is_recipe_main", False)]
108
+
109
+ if len(recipe_main_functions) == 0:
110
+ logger.warning("No function annotated with @recipe_main")
111
+ return
112
+
113
+ if len(recipe_main_functions) != 1:
114
+ names = [name for (name, _) in recipe_main_functions]
115
+ raise ValueError(f"You must have only one function annotated with @recipe_main. Found {names}")
116
+
117
+ (func_name, func) = recipe_main_functions[0]
118
+ logger.trace("Getting recipe function parameters")
119
+ args = _get_params(func, context)
120
+
121
+ logger.info(f"Executing recipe function {func_name}")
122
+ if inspect.iscoroutinefunction(func):
123
+ asyncio.run(func(*args))
124
+ else:
125
+ func(*args)
126
+ logger.info(f"Recipe {func_name} completed successfully.")
127
+ # Give time for any pending notifications (like register_artifact) to be sent
128
+ import time
129
+
130
+ time.sleep(0.1)
131
+
132
+
133
+ def _download_and_extract_recipe(context: RecipeContext, file_url: str) -> str:
134
+ import tempfile
135
+ import zipfile
136
+
137
+ assert file_url.endswith(".zip"), "Recipe url must point to a zip file"
138
+
139
+ # Download the zip file to a temporary location
140
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_zip:
141
+ temp_zip_path = temp_zip.name
142
+ context.file_storage.download_locally(file_url, temp_zip_path, use_raw_path=True)
143
+
144
+ # Extract to a temporary directory
145
+ temp_dir = tempfile.mkdtemp(prefix="user_recipe")
146
+ with zipfile.ZipFile(temp_zip_path, "r") as zip_ref:
147
+ zip_ref.extractall(temp_dir)
148
+
149
+ # Clean up the temp zip file
150
+ os.unlink(temp_zip_path)
151
+
152
+ recipe_files = [f for f in os.listdir(temp_dir) if f.endswith(".py")]
153
+ if not recipe_files:
154
+ raise FileNotFoundError("No Python recipe file found in the extracted zip")
155
+ main_files = [f for f in recipe_files if f == "main.py"]
156
+ if len(main_files) == 0:
157
+ raise RuntimeError("Recipe zip file must contain a main.py file")
158
+
159
+ return temp_dir
160
+
161
+
162
+ def _parse_script_metadata(file_path: Path) -> Optional[list[str]]:
163
+ """Parse metadata block from a Python script to extract dependencies.
164
+ cf. [python doc](https://packaging.python.org/en/latest/specifications/inline-script-metadata)
165
+
166
+ Args:
167
+ file_path: Path to the Python file to parse
168
+
169
+ Returns:
170
+ List of dependencies if found, None otherwise
171
+ """
172
+ try:
173
+ content = file_path.read_text()
174
+ except Exception as e:
175
+ logger.warning(f"Failed to read file {file_path} for metadata parsing: {e}")
176
+ return None
177
+
178
+ # Regex pattern to match metadata blocks
179
+ metadata_pattern = r"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+?)^# ///$"
180
+ matches = list(re.finditer(metadata_pattern, content))
181
+
182
+ for match in matches:
183
+ metadata_type = match.group("type")
184
+ if metadata_type != "adaptive":
185
+ continue
186
+ metadata_content = match.group("content")
187
+ # Clean up metadata content by removing leading '#' and whitespaces from each line
188
+ cleaned_lines = []
189
+ for line in metadata_content.split("\n"):
190
+ line = line.lstrip("#").strip()
191
+ if line:
192
+ cleaned_lines.append(line)
193
+ metadata_content = "\n".join(cleaned_lines)
194
+
195
+ # Parse metadata content as TOML
196
+ try:
197
+ metadata_dict = tomli.loads(metadata_content)
198
+ except Exception as e:
199
+ logger.warning(f"Failed to parse metadata as TOML: {e}")
200
+ metadata_dict = None
201
+ if metadata_dict and "dependencies" in metadata_dict:
202
+ deps = metadata_dict["dependencies"]
203
+ if isinstance(deps, list):
204
+ return deps
205
+
206
+ return None
207
+
208
+
209
+ INSTALL_TIMEOUT_SECS = int(os.getenv("ADAPTIVE_INSTALL_TIMEOUT_SECS", "300")) # Default 5 minutes
210
+
211
+
212
+ def _install_recipe_dependencies(entry: Path):
213
+ """Install Python dependencies from pyproject.toml, requirements.txt, requirements.in, or script metadata.
214
+
215
+ Args:
216
+ entry: Path to a file or directory. If it's a directory, looks for dependency files.
217
+ If it's a file, parses metadata block for dependencies.
218
+ """
219
+ # Check for pyproject.toml first, then requirements.txt/requirements.in
220
+ pyproject_file = entry / "pyproject.toml"
221
+ requirements_file = entry / "requirements.txt"
222
+ requirements_in_file = entry / "requirements.in"
223
+
224
+ install_kwargs = {
225
+ "check": True,
226
+ "timeout": INSTALL_TIMEOUT_SECS,
227
+ "capture_output": True,
228
+ "text": True,
229
+ }
230
+
231
+ if pyproject_file.exists():
232
+ logger.info(
233
+ f"Found pyproject.toml in {entry}, installing dependencies with uv pip (timeout: {INSTALL_TIMEOUT_SECS}s)"
234
+ )
235
+ try:
236
+ result = subprocess.run(
237
+ ["uv", "pip", "install", "-e", str(entry)],
238
+ **install_kwargs,
239
+ )
240
+ if result.stdout:
241
+ logger.debug(f"Install output: {result.stdout}")
242
+ logger.info("Successfully installed dependencies from pyproject.toml")
243
+ except subprocess.TimeoutExpired as e:
244
+ logger.error(f"Timeout ({INSTALL_TIMEOUT_SECS}s) installing dependencies from pyproject.toml")
245
+ if e.stdout:
246
+ logger.debug(f"Partial stdout: {e.stdout}")
247
+ if e.stderr:
248
+ logger.debug(f"Partial stderr: {e.stderr}")
249
+ raise
250
+ except subprocess.CalledProcessError as e:
251
+ logger.error(f"Failed to install dependencies from pyproject.toml: {e.stderr}")
252
+ if e.stdout:
253
+ logger.debug(f"stdout: {e.stdout}")
254
+ raise
255
+ elif requirements_file.exists():
256
+ logger.info(
257
+ f"Found requirements.txt in {entry}, installing dependencies with uv pip (timeout: {INSTALL_TIMEOUT_SECS}s)"
258
+ )
259
+ try:
260
+ result = subprocess.run(
261
+ ["uv", "pip", "install", "--verbose", "-r", str(requirements_file)], **install_kwargs
262
+ )
263
+ if result.stdout:
264
+ logger.debug(f"Install output: {result.stdout}")
265
+ logger.info("Successfully installed dependencies from requirements.txt")
266
+ except subprocess.TimeoutExpired as e:
267
+ logger.error(f"Timeout ({INSTALL_TIMEOUT_SECS}s) installing dependencies from requirements.txt")
268
+ if e.stdout:
269
+ logger.debug(f"Partial stdout: {e.stdout}")
270
+ if e.stderr:
271
+ logger.debug(f"Partial stderr: {e.stderr}")
272
+ raise
273
+ except subprocess.CalledProcessError as e:
274
+ logger.error(f"Failed to install dependencies from requirements.txt: {e.stderr}")
275
+ if e.stdout:
276
+ logger.debug(f"stdout: {e.stdout}")
277
+ raise
278
+ elif requirements_in_file.exists():
279
+ logger.info(
280
+ f"Found requirements.in in {entry}, compiling to requirements.txt with uv pip (timeout: {INSTALL_TIMEOUT_SECS}s)"
281
+ )
282
+ try:
283
+ # Compile requirements.in to requirements.txt
284
+ result = subprocess.run(
285
+ ["uv", "pip", "compile", str(requirements_in_file), "-o", str(requirements_file)], **install_kwargs
286
+ )
287
+ if result.stdout:
288
+ logger.debug(f"Compile output: {result.stdout}")
289
+ logger.debug("Successfully compiled requirements.in to requirements.txt")
290
+ result = subprocess.run(
291
+ ["uv", "pip", "install", "-r", str(requirements_file)],
292
+ **install_kwargs,
293
+ )
294
+ if result.stdout:
295
+ logger.debug(f"Install output: {result.stdout}")
296
+ logger.info("Successfully installed dependencies from requirements.in")
297
+ except subprocess.TimeoutExpired as e:
298
+ logger.error(f"Timeout ({INSTALL_TIMEOUT_SECS}s) compiling or installing dependencies from requirements.in")
299
+ if e.stdout:
300
+ logger.debug(f"Partial stdout: {e.stdout}")
301
+ if e.stderr:
302
+ logger.debug(f"Partial stderr: {e.stderr}")
303
+ raise
304
+ except subprocess.CalledProcessError as e:
305
+ logger.error(f"Failed to compile or install dependencies from requirements.in: {e.stderr}")
306
+ if e.stdout:
307
+ logger.debug(f"stdout: {e.stdout}")
308
+ raise
309
+ else:
310
+ # Check main file for script metadata
311
+ main_file = entry if entry.is_file() else entry / "main.py"
312
+ if main_file.is_file():
313
+ if main_file.suffix != ".py":
314
+ logger.debug(f"{main_file} is not a Python file, skipping dependency installation")
315
+ return
316
+
317
+ dependencies = _parse_script_metadata(main_file)
318
+ if dependencies:
319
+ logger.info(
320
+ f"Found {len(dependencies)} dependencies in script metadata: {dependencies}, installing them (timeout: {INSTALL_TIMEOUT_SECS}s)..."
321
+ )
322
+ try:
323
+ result = subprocess.run(["uv", "pip", "install"] + dependencies, **install_kwargs)
324
+ if result.stdout:
325
+ logger.debug(f"Install output: {result.stdout}")
326
+ logger.info("Successfully installed dependencies from script metadata")
327
+ except subprocess.TimeoutExpired as e:
328
+ logger.error(f"Timeout ({INSTALL_TIMEOUT_SECS}s) installing dependencies from script metadata")
329
+ if e.stdout:
330
+ logger.debug(f"Partial stdout: {e.stdout}")
331
+ if e.stderr:
332
+ logger.debug(f"Partial stderr: {e.stderr}")
333
+ raise
334
+ except subprocess.CalledProcessError as e:
335
+ logger.error(f"Failed to install dependencies from script metadata: {e.stderr}")
336
+ if e.stdout:
337
+ logger.debug(f"stdout: {e.stdout}")
338
+ raise
339
+ else:
340
+ logger.debug(f"No dependencies found in script metadata for {entry}")
341
+ return
342
+
343
+
344
+ def _get_params(func, context: RecipeContext) -> list[Any]:
345
+ args: list[Any] = []
346
+ sig = inspect.signature(func)
347
+ assert len(sig.parameters.items()) <= 2, "Support only functions with 2 parameters or less"
348
+
349
+ for _, param in sig.parameters.items():
350
+ # Ensure param.annotation is a type before using issubclass
351
+ if isinstance(param.annotation, type):
352
+ if issubclass(param.annotation, RecipeContext):
353
+ args.append(context)
354
+ elif issubclass(param.annotation, InputConfig):
355
+ if context.config.user_input_file:
356
+ user_input = param.annotation.load_from_file(context.config.user_input_file)
357
+ else:
358
+ user_input = param.annotation()
359
+ logger.trace("Loaded user input: {}", user_input)
360
+ args.append(user_input)
361
+ else:
362
+ raise TypeError(f"Parameter '{param.name}' must have a type annotation.")
363
+
364
+ return args
365
+
366
+
367
+ if __name__ == "__main__":
368
+ main()
@@ -0,0 +1,21 @@
1
+ from harmony_client import StageNotifier
2
+
3
+
4
+ class SimpleProgressNotifier:
5
+ def __init__(self, job_notifier: StageNotifier, monitoring_link=None):
6
+ self.job_notifier = job_notifier
7
+ self.monitoring_link = monitoring_link
8
+
9
+ def __enter__(self):
10
+ self.job_notifier.report_progress(1, 0, self.monitoring_link)
11
+ return self
12
+
13
+ def __exit__(self, exc_type, exc_val, exc_tb):
14
+ self.job_notifier.report_progress(1, 1, self.monitoring_link)
15
+
16
+ async def __aenter__(self):
17
+ self.job_notifier.report_progress(1, 0, self.monitoring_link)
18
+ return self
19
+
20
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
21
+ self.job_notifier.report_progress(1, 1, self.monitoring_link)
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: harmony-client
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3.12
6
+ Classifier: Programming Language :: Python :: 3.13
7
+ Classifier: Programming Language :: Python :: Implementation :: CPython
8
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
9
+ Requires-Dist: rich>=13.7.0
10
+ Requires-Dist: datasets>=2.14.0
11
+ Requires-Dist: hf-xet>=1.1.2
12
+ Requires-Dist: loguru>=0.7.2
13
+ Requires-Dist: pydantic-settings>=2.9.1
14
+ Requires-Dist: pydantic>=2.10.5
15
+ Requires-Dist: pybars3>=0.9.7
16
+ Requires-Dist: pysbd>=0.3.4
17
+ Requires-Dist: websockets>=15.0.1
18
+ Requires-Dist: setproctitle>=1.3.3
19
+ Requires-Dist: pydantic-xml>=2.16.0
20
+ Requires-Dist: openai>=1.42.0
21
+ Requires-Dist: pillow>=11.3.0
22
+ Requires-Dist: boto3>=1.40
23
+ Requires-Dist: tomli>=2
24
+ Requires-Dist: aiofiles>=25.1.0
25
+ Requires-Dist: tensorboardx>=2.6.2.2 ; extra == 'all-monitoring'
26
+ Requires-Dist: mlflow>=3.4.0 ; extra == 'all-monitoring'
27
+ Requires-Dist: wandb>=0.19.11 ; extra == 'all-monitoring'
28
+ Requires-Dist: mlflow>=3.4.0 ; extra == 'mlflow'
29
+ Requires-Dist: mlflow-skinny>=3.4.0 ; extra == 'mlflow-skinny'
30
+ Requires-Dist: tensorboardx>=2.6.2.2 ; extra == 'tensorboard'
31
+ Requires-Dist: wandb>=0.19.11 ; extra == 'wandb'
32
+ Provides-Extra: all_monitoring
33
+ Provides-Extra: mlflow
34
+ Provides-Extra: mlflow_skinny
35
+ Provides-Extra: tensorboard
36
+ Provides-Extra: wandb
37
+ Summary: Python client for Adaptive Harmony, with Rust-powered RPC communication
38
+ Requires-Python: >=3.12
@@ -0,0 +1,32 @@
1
+ harmony_client/__init__.py,sha256=i536amUmkWy5jDbyDyZmRg4NZDJT5vUR2SnNSM8-5-c,1684
2
+ harmony_client/file_storage.py,sha256=UOlXohu7eH4J1WV-JuOtJeZxGSBf2ijS8gfVFDwENK4,13400
3
+ harmony_client/logging_table.py,sha256=D32Jk6QXsxUwmpwXHaHdVAZpmcbiFEBbY8YEKpeTHwo,4165
4
+ harmony_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ harmony_client/harmony_client.pyi,sha256=cP1F73XM5OCOYBB8W7Bhlzooh5ER1Yyuv7Pq6mzhQ88,51978
6
+ harmony_client/harmony_client.cpython-312-darwin.so,sha256=XicyzjXRR1HPUaF06npO8HJNuIjigTZ9_9dSrtX1VIg,39368576
7
+ harmony_client/artifacts/__init__.py,sha256=vPUCeEBDIkHMlDAZXwcvruaCG1onOlvwJT_2wdWrdkQ,310
8
+ harmony_client/artifacts/dataset_artifact.py,sha256=c9NZgQ8vuW0pI96c743eMtg48bTBKBBLNuXl-e3Q77c,9573
9
+ harmony_client/artifacts/model_artifact.py,sha256=Ks4o80oaRLUqxExXgWmYaKzS_f7medBc1Nan7EwqaoI,740
10
+ harmony_client/artifacts/custom_artifact.py,sha256=E8-PH6UMQrpwXxnScXUviey_7U_Y9NRatmY9CaUA3iE,1303
11
+ harmony_client/runtime/model_artifact_save.py,sha256=bi9Wp2KwbW9jTqM9oHaFILeIWSc81vihirSCo_Yxdjw,741
12
+ harmony_client/runtime/runner.py,sha256=6XEicGGhlozLZMc4Wk2uyfxkegM_bSWxV-jl-8Bu8vI,15090
13
+ harmony_client/runtime/simple_notifier.py,sha256=kzExKB3g28w5-4-md3dAae4nPIr3Q9fqgg1IgMwXS18,726
14
+ harmony_client/runtime/__init__.py,sha256=r3a1FVwuQh_KzV8bF88OpuX1CO7uOJ24pMO9YYmFK4c,638
15
+ harmony_client/runtime/context.py,sha256=AdIPAJwdfVXztENUW1B8sz720HM9QMGQbwOyhYrDj2o,8389
16
+ harmony_client/runtime/data.py,sha256=WxYS-hNKm9T0-SAyI1rWQTGFekg2Tu_5pNprRomr4xo,2749
17
+ harmony_client/runtime/decorators.py,sha256=QA6cHb1ChuT6GVVDN58ReNG2B4Kj19BZzyh9nSSMFcU,436
18
+ harmony_client/runtime/dto/AdaptiveDataset.py,sha256=DgVcMY7uWA2pvGAsvxlpwe85EqGyfkTK9Tw1SkIRk04,539
19
+ harmony_client/runtime/dto/DatasetSampleFormats.py,sha256=eUsEgCwyKVUiem_POf6q8aYTdPM4MN6GrEZXUJrVIYE,2147
20
+ harmony_client/runtime/dto/AdaptiveGrader.py,sha256=qOJisXiuXJov2cUThLzxo3FEnSkV6DpRtcjd1p2sJHI,1497
21
+ harmony_client/runtime/dto/__init__.py,sha256=ym2-ZRfDfDnaX37gxOMbRCkd8Wxk0RQC16aTyyGUVGQ,60
22
+ harmony_client/runtime/dto/AdaptiveModel.py,sha256=mcN4OS-2FphCnBOjUk36f-i6a6nltly0AAa8CN0DmpA,542
23
+ harmony_client/runtime/dto/base.py,sha256=n61FFqnZVqe0rVdc7giIYnaiTPX7iXt5rBQ6BQlyfuY,126
24
+ harmony_client/internal/__init__.py,sha256=IsboJmHPL7BQef7fI4zK6GqFV0E3jxlxlTPgi6RC-3k,232
25
+ harmony_client/internal/eval_samples_html.py,sha256=x6b6A6KW_64dnlF5gefsNJwzqIprGNWmUmQOOabpGBM,4309
26
+ harmony_client/internal/utils.py,sha256=eIm5kR9LX63c6D9q-EAPw_uNYkRKUcQaBEmu8eOcMjc,316
27
+ harmony_client/parameters/__init__.py,sha256=4s3O5pMiE3Qc_5Zg5ftpMDHNlm34bxRJ-tPR_4NoM5Y,11898
28
+ harmony_client/parameters/dataset_kinds.py,sha256=4lEEsbfKzrBd6OYpm3Sz7VP4-AJGzA3uxAkzmbeBTRE,1144
29
+ harmony_client/parameters/model_kinds.py,sha256=_EiCdHVyIhvPfr7bil6QxrxrEX-71oOttCLP_g7sdI4,255
30
+ harmony_client-0.1.0.dist-info/RECORD,,
31
+ harmony_client-0.1.0.dist-info/WHEEL,sha256=lbcxwgBRtGkRMUv7yZS4hG_MKdTx9FKFqAjhCTANUBM,110
32
+ harmony_client-0.1.0.dist-info/METADATA,sha256=NlzjKVbWRhqHhNsy4dbYZLQmURlvGq0Da2DKO4LHxX0,1480
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp312-cp312-macosx_10_9_universal2