synthgraph-sdk 0.1.0__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.
synthgraph/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ from .client import SynthGraphClient
2
+ from .config import SynthGraphConfig
3
+ from .http import SynthGraphHTTPClient, SynthGraphHTTPError
4
+ from .models import (
5
+ AssetReference,
6
+ DataReference,
7
+ DatasetReference,
8
+ Experiment,
9
+ GenerationRun,
10
+ GenerationStatus,
11
+ Generator,
12
+ Project,
13
+ Reproducibility,
14
+ )
15
+ from .projects import ProjectsAPI
16
+
17
+ __version__ = "0.1.0"
18
+
19
+ __all__ = [
20
+ "AssetReference",
21
+ "DataReference",
22
+ "DatasetReference",
23
+ "Experiment",
24
+ "GenerationRun",
25
+ "GenerationStatus",
26
+ "Generator",
27
+ "Project",
28
+ "ProjectsAPI",
29
+ "Reproducibility",
30
+ "SynthGraphClient",
31
+ "SynthGraphConfig",
32
+ "SynthGraphHTTPClient",
33
+ "SynthGraphHTTPError",
34
+ ]
synthgraph/client.py ADDED
@@ -0,0 +1,69 @@
1
+ from types import TracebackType
2
+ from typing import Any, Self
3
+
4
+ from .config import SynthGraphConfig
5
+ from .experiments import ExperimentsAPI
6
+ from .generations import GenerationsAPI
7
+ from .http import SynthGraphHTTPClient
8
+ from .projects import ProjectsAPI
9
+
10
+
11
+ class SynthGraphClient:
12
+ """Public client for interacting with the SynthGraph API."""
13
+
14
+ def __init__(
15
+ self,
16
+ api_key: str | None = None,
17
+ *,
18
+ api_url: str | None = None,
19
+ timeout: float | None = None,
20
+ config: SynthGraphConfig | None = None,
21
+ transport: Any | None = None,
22
+ ) -> None:
23
+ if config is not None:
24
+ if any(
25
+ value is not None
26
+ for value in (api_key, api_url, timeout)
27
+ ):
28
+ raise ValueError(
29
+ "config cannot be combined with api_key, api_url, "
30
+ "or timeout"
31
+ )
32
+ self.config = config
33
+ else:
34
+ config_kwargs: dict[str, Any] = {}
35
+
36
+ if api_key is not None:
37
+ config_kwargs["api_key"] = api_key
38
+
39
+ if api_url is not None:
40
+ config_kwargs["api_url"] = api_url
41
+
42
+ if timeout is not None:
43
+ config_kwargs["timeout"] = timeout
44
+
45
+ self.config = SynthGraphConfig(**config_kwargs)
46
+
47
+ self._http = SynthGraphHTTPClient(
48
+ self.config,
49
+ transport=transport,
50
+ )
51
+
52
+ self.projects = ProjectsAPI(self._http)
53
+ self.experiments = ExperimentsAPI(self._http)
54
+ self.generations = GenerationsAPI(self._http)
55
+
56
+ def close(self) -> None:
57
+ """Close the underlying HTTP client."""
58
+ self._http.close()
59
+
60
+ def __enter__(self) -> Self:
61
+ return self
62
+
63
+ def __exit__(
64
+ self,
65
+ exc_type: type[BaseException] | None,
66
+ exc_value: BaseException | None,
67
+ traceback: TracebackType | None,
68
+ ) -> None:
69
+ self.close()
synthgraph/config.py ADDED
@@ -0,0 +1,23 @@
1
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
2
+
3
+
4
+ class SynthGraphConfig(BaseModel):
5
+ """Configuration for communicating with the SynthGraph backend."""
6
+
7
+ model_config = ConfigDict(frozen=True)
8
+
9
+ api_url: str = Field(
10
+ default="https://synthgraph.onrender.com",
11
+ min_length=1,
12
+ )
13
+ api_key: str | None = None
14
+ timeout: float = Field(
15
+ default=30.0,
16
+ gt=0,
17
+ )
18
+
19
+ @field_validator("api_url")
20
+ @classmethod
21
+ def normalize_api_url(cls, value: str) -> str:
22
+ """Remove trailing slashes from the API URL."""
23
+ return value.rstrip("/")
@@ -0,0 +1,52 @@
1
+ from typing import Any
2
+
3
+ from .http import SynthGraphHTTPClient
4
+ from .models import Experiment
5
+
6
+
7
+ class ExperimentsAPI:
8
+ """API operations for SynthGraph experiments."""
9
+
10
+ def __init__(self, http: SynthGraphHTTPClient) -> None:
11
+ self._http = http
12
+
13
+ def create(
14
+ self,
15
+ *,
16
+ project_id: str,
17
+ name: str,
18
+ description: str | None = None,
19
+ ) -> Experiment:
20
+ """Create a new experiment within a project."""
21
+ payload: dict[str, Any] = {
22
+ "name": name,
23
+ }
24
+
25
+ if description is not None:
26
+ payload["description"] = description
27
+
28
+ data = self._http.post(
29
+ f"/projects/{project_id}/experiments",
30
+ json=payload,
31
+ )
32
+
33
+ return Experiment.model_validate(data)
34
+
35
+ def get(self, experiment_id: str) -> Experiment:
36
+ """Retrieve an experiment by ID."""
37
+ data = self._http.get(
38
+ f"/experiments/{experiment_id}",
39
+ )
40
+
41
+ return Experiment.model_validate(data)
42
+
43
+ def list(self, *, project_id: str) -> list[Experiment]:
44
+ """List experiments belonging to a project."""
45
+ data = self._http.get_list(
46
+ f"/projects/{project_id}/experiments",
47
+ )
48
+
49
+ return [
50
+ Experiment.model_validate(item)
51
+ for item in data
52
+ ]
@@ -0,0 +1,124 @@
1
+ from typing import Any
2
+
3
+ from .http import SynthGraphHTTPClient
4
+ from .models import (
5
+ AssetReference,
6
+ DatasetReference,
7
+ GenerationRun,
8
+ GenerationStatus,
9
+ Generator,
10
+ Reproducibility,
11
+ )
12
+
13
+
14
+ class GenerationsAPI:
15
+ """API operations for generation runs."""
16
+
17
+ def __init__(self, http: SynthGraphHTTPClient) -> None:
18
+ self._http = http
19
+
20
+ def create(
21
+ self,
22
+ *,
23
+ experiment_id: str,
24
+ name: str,
25
+ generator: Generator,
26
+ parameters: dict[str, Any],
27
+ reproducibility: Reproducibility,
28
+ inputs: list[str | AssetReference | DatasetReference] | None = None,
29
+ outputs: list[str | AssetReference | DatasetReference] | None = None,
30
+ ) -> GenerationRun:
31
+ """Create a new generation run within an experiment."""
32
+ payload = {
33
+ "name": name,
34
+ "generator": generator.model_dump(
35
+ mode="json",
36
+ exclude_none=True,
37
+ ),
38
+ "parameters": parameters,
39
+ "reproducibility": reproducibility.model_dump(
40
+ mode="json",
41
+ exclude_none=True,
42
+ exclude_defaults=True,
43
+ ),
44
+ "inputs": [
45
+ reference.model_dump(mode="json")
46
+ if isinstance(
47
+ reference,
48
+ (AssetReference, DatasetReference),
49
+ )
50
+ else {"id": reference}
51
+ for reference in (inputs or [])
52
+ ],
53
+ "outputs": [
54
+ reference.model_dump(mode="json")
55
+ if isinstance(
56
+ reference,
57
+ (AssetReference, DatasetReference),
58
+ )
59
+ else {"id": reference}
60
+ for reference in (outputs or [])
61
+ ],
62
+ }
63
+
64
+ data = self._http.post(
65
+ f"/experiments/{experiment_id}/generations",
66
+ json=payload,
67
+ )
68
+
69
+ return GenerationRun.model_validate(data)
70
+
71
+ def get(self, generation_id: str) -> GenerationRun:
72
+ """Retrieve a generation run by ID."""
73
+ data = self._http.get(
74
+ f"/generations/{generation_id}",
75
+ )
76
+
77
+ return GenerationRun.model_validate(data)
78
+
79
+ def list(self, *, experiment_id: str) -> list[GenerationRun]:
80
+ """List generation runs belonging to an experiment."""
81
+ data = self._http.get_list(
82
+ f"/experiments/{experiment_id}/generations",
83
+ )
84
+
85
+ return [
86
+ GenerationRun.model_validate(item)
87
+ for item in data
88
+ ]
89
+
90
+ def start(self, generation_id: str) -> GenerationRun:
91
+ """Mark a generation run as running."""
92
+ return self._update_status(
93
+ generation_id,
94
+ GenerationStatus.RUNNING,
95
+ )
96
+
97
+ def complete(self, generation_id: str) -> GenerationRun:
98
+ """Mark a generation run as completed."""
99
+ return self._update_status(
100
+ generation_id,
101
+ GenerationStatus.COMPLETED,
102
+ )
103
+
104
+ def fail(self, generation_id: str) -> GenerationRun:
105
+ """Mark a generation run as failed."""
106
+ return self._update_status(
107
+ generation_id,
108
+ GenerationStatus.FAILED,
109
+ )
110
+
111
+ def _update_status(
112
+ self,
113
+ generation_id: str,
114
+ status: GenerationStatus,
115
+ ) -> GenerationRun:
116
+ """Update the lifecycle status of a generation run."""
117
+ data = self._http.patch(
118
+ f"/generations/{generation_id}",
119
+ json={
120
+ "status": status.value,
121
+ },
122
+ )
123
+
124
+ return GenerationRun.model_validate(data)
synthgraph/http.py ADDED
@@ -0,0 +1,174 @@
1
+ from types import TracebackType
2
+ from typing import Any, Self
3
+
4
+ import httpx
5
+
6
+ from .config import SynthGraphConfig
7
+
8
+
9
+ class SynthGraphHTTPError(Exception):
10
+ """Raised when a SynthGraph API request fails."""
11
+
12
+ def __init__(self, status_code: int, message: str) -> None:
13
+ self.status_code = status_code
14
+ self.message = message
15
+
16
+ super().__init__(
17
+ f"SynthGraph API request failed with status "
18
+ f"{status_code}: {message}"
19
+ )
20
+
21
+
22
+ class SynthGraphHTTPClient:
23
+ """Low-level HTTP client for the SynthGraph API."""
24
+
25
+ def __init__(
26
+ self,
27
+ config: SynthGraphConfig,
28
+ *,
29
+ transport: httpx.BaseTransport | None = None,
30
+ ) -> None:
31
+ self.config = config
32
+
33
+ headers = {
34
+ "Accept": "application/json",
35
+ "Content-Type": "application/json",
36
+ }
37
+
38
+ if config.api_key is not None:
39
+ headers["Authorization"] = f"Bearer {config.api_key}"
40
+
41
+ self._client = httpx.Client(
42
+ base_url=config.api_url.rstrip("/"),
43
+ headers=headers,
44
+ timeout=config.timeout,
45
+ transport=transport,
46
+ )
47
+
48
+ def get(self, path: str) -> dict[str, Any]:
49
+ """Send a GET request expecting a JSON object response."""
50
+ response = self._client.get(path)
51
+
52
+ return self._handle_response(response)
53
+
54
+ def get_list(self, path: str) -> list[dict[str, Any]]:
55
+ """Send a GET request expecting a JSON array response."""
56
+ response = self._client.get(path)
57
+
58
+ return self._handle_list_response(response)
59
+
60
+ def post(
61
+ self,
62
+ path: str,
63
+ *,
64
+ json: dict[str, Any] | None = None,
65
+ ) -> dict[str, Any]:
66
+ """Send a POST request, optionally with a JSON body."""
67
+ if json is None:
68
+ response = self._client.post(path)
69
+ else:
70
+ response = self._client.post(path, json=json)
71
+
72
+ return self._handle_response(response)
73
+
74
+ def patch(
75
+ self,
76
+ path: str,
77
+ *,
78
+ json: dict[str, Any] | None = None,
79
+ ) -> dict[str, Any]:
80
+ """Send a PATCH request, optionally with a JSON body."""
81
+ if json is None:
82
+ response = self._client.patch(path)
83
+ else:
84
+ response = self._client.patch(path, json=json)
85
+
86
+ return self._handle_response(response)
87
+
88
+ def close(self) -> None:
89
+ """Close the underlying HTTP client."""
90
+ self._client.close()
91
+
92
+ def __enter__(self) -> Self:
93
+ return self
94
+
95
+ def __exit__(
96
+ self,
97
+ exc_type: type[BaseException] | None,
98
+ exc_value: BaseException | None,
99
+ traceback: TracebackType | None,
100
+ ) -> None:
101
+ self.close()
102
+
103
+ @staticmethod
104
+ def _handle_response(
105
+ response: httpx.Response,
106
+ ) -> dict[str, Any]:
107
+ """Validate and decode an API object response."""
108
+ if response.is_error:
109
+ raise SynthGraphHTTPClient._create_http_error(response)
110
+
111
+ if not response.content:
112
+ return {}
113
+
114
+ data = response.json()
115
+
116
+ if not isinstance(data, dict):
117
+ raise SynthGraphHTTPError(
118
+ status_code=response.status_code,
119
+ message="API response must be a JSON object",
120
+ )
121
+
122
+ return data
123
+
124
+ @staticmethod
125
+ def _handle_list_response(
126
+ response: httpx.Response,
127
+ ) -> list[dict[str, Any]]:
128
+ """Validate and decode an API list response."""
129
+ if response.is_error:
130
+ raise SynthGraphHTTPClient._create_http_error(response)
131
+
132
+ if not response.content:
133
+ return []
134
+
135
+ data = response.json()
136
+
137
+ if not isinstance(data, list):
138
+ raise SynthGraphHTTPError(
139
+ status_code=response.status_code,
140
+ message="API response must be a JSON array",
141
+ )
142
+
143
+ if not all(isinstance(item, dict) for item in data):
144
+ raise SynthGraphHTTPError(
145
+ status_code=response.status_code,
146
+ message="API response array must contain JSON objects",
147
+ )
148
+
149
+ return data
150
+
151
+ @staticmethod
152
+ def _create_http_error(
153
+ response: httpx.Response,
154
+ ) -> SynthGraphHTTPError:
155
+ """Create a consistent API error from an HTTP response."""
156
+ try:
157
+ data = response.json()
158
+
159
+ if isinstance(data, dict):
160
+ message = str(
161
+ data.get(
162
+ "detail",
163
+ data.get("message", response.text),
164
+ )
165
+ )
166
+ else:
167
+ message = response.text
168
+ except ValueError:
169
+ message = response.text
170
+
171
+ return SynthGraphHTTPError(
172
+ status_code=response.status_code,
173
+ message=message,
174
+ )
@@ -0,0 +1,16 @@
1
+ from .experiment import Experiment
2
+ from .generation import GenerationRun, GenerationStatus, Generator, Reproducibility
3
+ from .project import Project
4
+ from .reference import AssetReference, DataReference, DatasetReference
5
+
6
+ __all__ = [
7
+ "AssetReference",
8
+ "DataReference",
9
+ "DatasetReference",
10
+ "Experiment",
11
+ "GenerationRun",
12
+ "GenerationStatus",
13
+ "Generator",
14
+ "Project",
15
+ "Reproducibility",
16
+ ]
@@ -0,0 +1,16 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class Experiment(BaseModel):
7
+ """A research experiment belonging to a SynthGraph project."""
8
+
9
+ model_config = ConfigDict(frozen=True)
10
+
11
+ id: str
12
+ project_id: str
13
+ name: str = Field(min_length=1)
14
+ description: str | None = None
15
+ created_at: datetime
16
+ updated_at: datetime | None = None
@@ -0,0 +1,76 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ from .reference import DataReference
8
+
9
+
10
+ class GenerationStatus(str, Enum):
11
+ """Lifecycle state of a generation run."""
12
+
13
+ PENDING = "pending"
14
+ RUNNING = "running"
15
+ COMPLETED = "completed"
16
+ FAILED = "failed"
17
+
18
+
19
+ class Generator(BaseModel):
20
+ """Information about the tool that produced a generation."""
21
+
22
+ model_config = ConfigDict(frozen=True)
23
+
24
+ name: str = Field(min_length=1)
25
+ version: str | None = None
26
+ type: str | None = None
27
+
28
+
29
+ class Reproducibility(BaseModel):
30
+ """Information required to reproduce a generation."""
31
+
32
+ model_config = ConfigDict(frozen=True)
33
+
34
+ seed: int | None = Field(
35
+ default=None,
36
+ exclude_if=lambda value: value is None,
37
+ )
38
+ code_version: str | None = Field(
39
+ default=None,
40
+ exclude_if=lambda value: value is None,
41
+ )
42
+ environment: dict[str, Any] = Field(
43
+ default_factory=dict,
44
+ exclude_if=lambda value: not value,
45
+ )
46
+ configuration_hash: str | None = Field(
47
+ default=None,
48
+ exclude_if=lambda value: value is None,
49
+ )
50
+
51
+
52
+ class GenerationRun(BaseModel):
53
+ """A single synthetic data generation execution."""
54
+
55
+ model_config = ConfigDict(frozen=True)
56
+
57
+ id: str = Field(min_length=1)
58
+ experiment_id: str = Field(min_length=1)
59
+ name: str = Field(min_length=1)
60
+ description: str | None = None
61
+
62
+ generator: Generator
63
+ parameters: dict[str, Any]
64
+
65
+ reproducibility: Reproducibility
66
+
67
+ inputs: list[DataReference] = Field(default_factory=list)
68
+ outputs: list[DataReference] = Field(default_factory=list)
69
+
70
+ status: GenerationStatus
71
+
72
+ started_at: datetime | None = None
73
+ completed_at: datetime | None = None
74
+ created_at: datetime
75
+
76
+ metadata: dict[str, Any] = Field(default_factory=dict)
@@ -0,0 +1,15 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class Project(BaseModel):
7
+ """A top-level SynthGraph research project."""
8
+
9
+ model_config = ConfigDict(frozen=True)
10
+
11
+ id: str
12
+ name: str = Field(min_length=1)
13
+ description: str | None = None
14
+ created_at: datetime
15
+ updated_at: datetime | None = None
@@ -0,0 +1,27 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class DataReference(BaseModel):
7
+ """Reference to data managed outside or inside SynthGraph."""
8
+
9
+ model_config = ConfigDict(frozen=True)
10
+
11
+ id: str = Field(min_length=1)
12
+ uri: str = Field(min_length=1)
13
+ name: str = Field(min_length=1)
14
+ metadata: dict[str, Any] = Field(default_factory=dict)
15
+
16
+
17
+ class AssetReference(DataReference):
18
+ """Reference to a source or generated asset."""
19
+
20
+ type: str | None = None
21
+
22
+
23
+ class DatasetReference(DataReference):
24
+ """Reference to a source or generated dataset."""
25
+
26
+ format: str | None = None
27
+ size: int | None = Field(default=None, ge=0)
synthgraph/projects.py ADDED
@@ -0,0 +1,40 @@
1
+ from typing import Any
2
+
3
+ from .http import SynthGraphHTTPClient
4
+ from .models import Project
5
+
6
+
7
+ class ProjectsAPI:
8
+ """API operations for SynthGraph projects."""
9
+
10
+ def __init__(self, http: SynthGraphHTTPClient) -> None:
11
+ self._http = http
12
+
13
+ def create(
14
+ self,
15
+ *,
16
+ name: str,
17
+ description: str | None = None,
18
+ ) -> Project:
19
+ """Create a new SynthGraph project."""
20
+ payload: dict[str, Any] = {
21
+ "name": name,
22
+ }
23
+
24
+ if description is not None:
25
+ payload["description"] = description
26
+
27
+ data = self._http.post(
28
+ "/projects",
29
+ json=payload,
30
+ )
31
+
32
+ return Project.model_validate(data)
33
+
34
+ def get(self, project_id: str) -> Project:
35
+ """Retrieve a SynthGraph project by ID."""
36
+ data = self._http.get(
37
+ f"/projects/{project_id}",
38
+ )
39
+
40
+ return Project.model_validate(data)
@@ -0,0 +1,3 @@
1
+ from .json import model_to_dict, model_to_json
2
+
3
+ __all__ = ["model_to_dict", "model_to_json"]
@@ -0,0 +1,14 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ def model_to_dict(model: BaseModel) -> dict[str, Any]:
8
+ """Convert a SynthGraph model into JSON-compatible Python data."""
9
+ return model.model_dump(mode="json")
10
+
11
+
12
+ def model_to_json(model: BaseModel) -> str:
13
+ """Convert a SynthGraph model into a JSON string."""
14
+ return json.dumps(model_to_dict(model))
@@ -0,0 +1,322 @@
1
+ Metadata-Version: 2.5
2
+ Name: synthgraph-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for SynthGraph experiment provenance and lineage tracking.
5
+ Author: SynthGraph
6
+ License: Apache-2.0
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: httpx<1.0,>=0.27
9
+ Requires-Dist: pydantic<3.0,>=2.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: mypy<2.0,>=1.11; extra == 'dev'
12
+ Requires-Dist: pytest-cov<6.0,>=5.0; extra == 'dev'
13
+ Requires-Dist: pytest<9.0,>=8.0; extra == 'dev'
14
+ Requires-Dist: ruff<1.0,>=0.6; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # SynthGraph Python SDK
18
+
19
+ The official Python SDK for SynthGraph.
20
+
21
+ SynthGraph allows researchers to capture experiment provenance and lineage from
22
+ their existing research environments without moving their workflows into the
23
+ SynthGraph platform.
24
+
25
+ ## Status
26
+
27
+ Early development.
28
+
29
+ The SDK is currently under active development and the public API is not yet
30
+ stable.
31
+
32
+ ## Planned v1.0 capabilities
33
+
34
+ - Project and experiment management
35
+ - Synthetic-data generation provenance
36
+ - Dataset references
37
+ - Training-run provenance
38
+ - Evaluation metrics
39
+ - Asset references
40
+ - Code and environment metadata
41
+ - Experiment lineage
42
+ - Cloud and self-hosted SynthGraph deployments
43
+
44
+ ## Installation
45
+
46
+ Install the SDK from the project directory:
47
+
48
+ ```bash
49
+ pip install -e .
50
+ ```
51
+
52
+ For development:
53
+
54
+ ```bash
55
+ pip install -e ".[dev]"
56
+ ```
57
+
58
+ ## Quick start
59
+
60
+ A typical SynthGraph workflow is:
61
+
62
+ ```text
63
+ Project
64
+ └── Experiment
65
+ └── Generation
66
+ ```
67
+
68
+ ### Create a project
69
+
70
+ ```python
71
+ from synthgraph import SynthGraphClient
72
+
73
+ with SynthGraphClient(api_key="your-api-key") as client:
74
+ project = client.projects.create(
75
+ name="Synthetic Research",
76
+ description="Synthetic-data research project",
77
+ )
78
+
79
+ print(project.id)
80
+ print(project.name)
81
+ ```
82
+
83
+ ### Create an experiment
84
+
85
+ Experiments belong to projects:
86
+
87
+ ```python
88
+ from synthgraph import SynthGraphClient
89
+
90
+ with SynthGraphClient(api_key="your-api-key") as client:
91
+ experiment = client.experiments.create(
92
+ project_id="project_123",
93
+ name="Rainy Scene Study",
94
+ description="Study of synthetic rainy-scene generation",
95
+ )
96
+
97
+ print(experiment.id)
98
+ ```
99
+
100
+ ### Record a synthetic-data generation
101
+
102
+ Use `Generator` to describe the software or tool that produced the result,
103
+ and `Reproducibility` to capture information needed to reproduce the run.
104
+
105
+ ```python
106
+ from synthgraph import (
107
+ Generator,
108
+ Reproducibility,
109
+ SynthGraphClient,
110
+ )
111
+
112
+ with SynthGraphClient(api_key="your-api-key") as client:
113
+ generation = client.generations.create(
114
+ experiment_id="experiment_123",
115
+ name="Rainy Scene Generation",
116
+ generator=Generator(
117
+ name="blender",
118
+ version="4.2.0",
119
+ type="3d_renderer",
120
+ ),
121
+ parameters={
122
+ "samples": 512,
123
+ "weather": "rain",
124
+ },
125
+ reproducibility=Reproducibility(
126
+ seed=42,
127
+ ),
128
+ )
129
+
130
+ print(generation.id)
131
+ print(generation.status)
132
+ ```
133
+
134
+ ### Record input and output references
135
+
136
+ Generations can reference assets and datasets without moving the underlying
137
+ data into SynthGraph.
138
+
139
+ ```python
140
+ from synthgraph import (
141
+ AssetReference,
142
+ DatasetReference,
143
+ Generator,
144
+ Reproducibility,
145
+ SynthGraphClient,
146
+ )
147
+
148
+ input_asset = AssetReference(
149
+ id="asset_123",
150
+ uri="file:///data/model.blend",
151
+ name="model.blend",
152
+ type="3d_model",
153
+ )
154
+
155
+ input_dataset = DatasetReference(
156
+ id="dataset_123",
157
+ uri="s3://bucket/input-scenes",
158
+ name="input-scenes",
159
+ format="image",
160
+ )
161
+
162
+ output_dataset = DatasetReference(
163
+ id="dataset_456",
164
+ uri="s3://bucket/output-scenes",
165
+ name="output-scenes",
166
+ format="image",
167
+ )
168
+
169
+ with SynthGraphClient(api_key="your-api-key") as client:
170
+ generation = client.generations.create(
171
+ experiment_id="experiment_123",
172
+ name="Referenced Generation",
173
+ generator=Generator(name="blender", version="4.2.0"),
174
+ parameters={
175
+ "samples": 512,
176
+ },
177
+ reproducibility=Reproducibility(seed=42),
178
+ inputs=[input_asset, input_dataset],
179
+ outputs=[output_dataset],
180
+ )
181
+ ```
182
+
183
+ References contain metadata about the data location and identity. The SDK
184
+ does not upload the referenced files or datasets.
185
+
186
+ ## Reading existing resources
187
+
188
+ Resources can be retrieved by ID.
189
+
190
+ ### Get a project
191
+
192
+ ```python
193
+ with SynthGraphClient(api_key="your-api-key") as client:
194
+ project = client.projects.get("project_123")
195
+ ```
196
+
197
+ ### Get an experiment
198
+
199
+ ```python
200
+ with SynthGraphClient(api_key="your-api-key") as client:
201
+ experiment = client.experiments.get("experiment_123")
202
+ ```
203
+
204
+ ### Get a generation
205
+
206
+ ```python
207
+ with SynthGraphClient(api_key="your-api-key") as client:
208
+ generation = client.generations.get("generation_123")
209
+ print(generation.status)
210
+ ```
211
+
212
+ ## Listing resources
213
+
214
+ ### List experiments
215
+
216
+ ```python
217
+ with SynthGraphClient(api_key="your-api-key") as client:
218
+ experiments = client.experiments.list(
219
+ project_id="project_123",
220
+ )
221
+
222
+ for experiment in experiments:
223
+ print(experiment.id, experiment.name)
224
+ ```
225
+
226
+ ### List generations
227
+
228
+ ```python
229
+ with SynthGraphClient(api_key="your-api-key") as client:
230
+ generations = client.generations.list(
231
+ experiment_id="experiment_123",
232
+ )
233
+
234
+ for generation in generations:
235
+ print(generation.id, generation.status)
236
+ ```
237
+
238
+ ## Configuration
239
+
240
+ The client can be configured directly:
241
+
242
+ ```python
243
+ from synthgraph import SynthGraphClient
244
+
245
+ client = SynthGraphClient(
246
+ api_key="your-api-key",
247
+ api_url="https://api.example.com",
248
+ timeout=30.0,
249
+ )
250
+ ```
251
+
252
+ Or through `SynthGraphConfig`:
253
+
254
+ ```python
255
+ from synthgraph import SynthGraphClient, SynthGraphConfig
256
+
257
+ config = SynthGraphConfig(
258
+ api_key="your-api-key",
259
+ api_url="https://api.example.com",
260
+ timeout=30.0,
261
+ )
262
+
263
+ with SynthGraphClient(config=config) as client:
264
+ project = client.projects.get("project_123")
265
+ ```
266
+
267
+ Do not combine `config` with `api_key`, `api_url`, or `timeout` on the same
268
+ `SynthGraphClient` instance.
269
+
270
+ ## Development
271
+
272
+ From this directory:
273
+
274
+ ```bash
275
+ pip install -e ".[dev]"
276
+ ```
277
+
278
+ Run tests:
279
+
280
+ ```bash
281
+ pytest
282
+ ```
283
+
284
+ Run linting:
285
+
286
+ ```bash
287
+ ruff check .
288
+ ```
289
+
290
+ Run type checking:
291
+
292
+ ```bash
293
+ mypy src
294
+ ```
295
+
296
+ ## Current SDK surface
297
+
298
+ The current public client exposes:
299
+
300
+ ```python
301
+ with SynthGraphClient(...) as client:
302
+ client.projects
303
+ client.experiments
304
+ client.generations
305
+ ```
306
+
307
+ The SDK currently supports:
308
+
309
+ - Project creation, retrieval, and listing
310
+ - Experiment creation, retrieval, and listing
311
+ - Generation creation, retrieval, and listing
312
+ - Asset references
313
+ - Dataset references
314
+ - Generator metadata
315
+ - Reproducibility metadata
316
+ - HTTP error handling
317
+ - Custom API URLs
318
+ - Custom HTTP transports for testing
319
+
320
+ ## License
321
+
322
+ Apache-2.0
@@ -0,0 +1,17 @@
1
+ synthgraph/__init__.py,sha256=IrMm4zmbCNo-d0D4fTJqhyevDs7Pf9jeTv0n0xUUuOY,736
2
+ synthgraph/client.py,sha256=DsMTlD0OTSu1W5jLryQM1Ex1tpNPKL1nYgRxzUJMVuE,2023
3
+ synthgraph/config.py,sha256=dIKy6c6x8LfNDljDjz7PafYqSXmlAqdlBOm-HNY9HXY,639
4
+ synthgraph/experiments.py,sha256=QhPXf2qJKdclj5ab4F8V-89JUhiNlqz4-pCPHkTNk2w,1383
5
+ synthgraph/generations.py,sha256=2EzBPdmaDSezz0dh28FL3MASkbVTphATOxI2h90qyog,3762
6
+ synthgraph/http.py,sha256=kLnLZNm4FCjsS98xGeZIq9XeTAH4iAADf4v20RfKH4I,4991
7
+ synthgraph/projects.py,sha256=jXRs207l_ZM70sYvTUIhAWZ9DfD9irjaDCHfY0LqpW0,974
8
+ synthgraph/models/__init__.py,sha256=4kgit23Hx2ro0QUPSdhEn0cc2A3Las_EqRuyL_AM5v4,433
9
+ synthgraph/models/experiment.py,sha256=02kJR3f_2M2wOQ1WbRCE3naB93FNLv8ls6Ld4cV1P-s,405
10
+ synthgraph/models/generation.py,sha256=xZEEfAZiNqxliKmOuf5a-TGpmodl_GQAEuOYTWMc6Aw,1958
11
+ synthgraph/models/project.py,sha256=A9vQb_hgIUzaoc--gXTzOYJNkkABqGM6Br5K0G5bOjA,365
12
+ synthgraph/models/reference.py,sha256=V57a0wd_Q68Ix28BvimU9yYhiUT6OrFl0efFMypuaxU,703
13
+ synthgraph/serialization/__init__.py,sha256=So31eYUfXRWX9Ys3TxQQ_f7plNrpQZG6zrmHkhUTpHo,94
14
+ synthgraph/serialization/json.py,sha256=CnEi4BSKYqJzTObUvxcBHu0sxBAhFxlxdwxy1ALO-p8,395
15
+ synthgraph_sdk-0.1.0.dist-info/METADATA,sha256=XxiZbJhTKdQqyfPWwduuVxfE_ECWpdfmWiLTqD8R8a4,6687
16
+ synthgraph_sdk-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
17
+ synthgraph_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any