metafold 0.13.dev4__tar.gz → 0.13.1__tar.gz
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.
- {metafold-0.13.dev4 → metafold-0.13.1}/PKG-INFO +1 -1
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/jobs.py +0 -76
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/simulation/compression_simulation.py +44 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/simulation/run_experiment.py +11 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold.egg-info/PKG-INFO +1 -1
- {metafold-0.13.dev4 → metafold-0.13.1}/pyproject.toml +1 -1
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_compression_simulation.py +78 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_jobs.py +0 -137
- {metafold-0.13.dev4 → metafold-0.13.1}/LICENSE +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/README.md +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/__init__.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/api.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/assets.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/auth.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/client.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/exceptions.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/materials.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/projects.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/simulation/__init__.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/simulation/compression_experiment.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/utils.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold/workflows.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold.egg-info/SOURCES.txt +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold.egg-info/dependency_links.txt +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold.egg-info/requires.txt +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/metafold.egg-info/top_level.txt +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/setup.cfg +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_assets.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_compession_experiment.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_materials.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_projects.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_run_experiment.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_shear_simulation.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_utils.py +0 -0
- {metafold-0.13.dev4 → metafold-0.13.1}/tests/test_workflows.py +0 -0
|
@@ -135,82 +135,6 @@ class JobsEndpoint:
|
|
|
135
135
|
r: Response = self._client.get(url)
|
|
136
136
|
return Job(**r.json())
|
|
137
137
|
|
|
138
|
-
def run(
|
|
139
|
-
self, type: str, params: dict[str, Any],
|
|
140
|
-
name: str | None = None,
|
|
141
|
-
timeout: int | float = 120,
|
|
142
|
-
project_id: str | None = None,
|
|
143
|
-
) -> Job:
|
|
144
|
-
"""Dispatch a new job and wait for a result.
|
|
145
|
-
|
|
146
|
-
See Metafold API docs for the full list of jobs.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
type: Job type.
|
|
150
|
-
params: Job parameters.
|
|
151
|
-
name: Optional job name.
|
|
152
|
-
timeout: Time in seconds to wait for a result.
|
|
153
|
-
project_id: Job project ID.
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
Completed job resource.
|
|
157
|
-
"""
|
|
158
|
-
url = self.run_status(type, params, name=name, project_id=project_id)
|
|
159
|
-
try:
|
|
160
|
-
r: Response = self.poll(url, timeout)
|
|
161
|
-
except PollTimeout as e:
|
|
162
|
-
raise RuntimeError(
|
|
163
|
-
f"Job '{name or type}' failed to complete within {timeout} seconds"
|
|
164
|
-
) from e
|
|
165
|
-
return Job(**r.json())
|
|
166
|
-
|
|
167
|
-
def run_status(
|
|
168
|
-
self, type: str, params: dict[str, Any],
|
|
169
|
-
name: str | None = None,
|
|
170
|
-
project_id: str | None = None,
|
|
171
|
-
) -> str:
|
|
172
|
-
"""Dispatch a new job and return immediately without waiting for result.
|
|
173
|
-
|
|
174
|
-
See Metafold API docs for the full list of jobs.
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
type: Job type.
|
|
178
|
-
params: Job parameters.
|
|
179
|
-
name: Optional job name.
|
|
180
|
-
project_id: Job project ID.
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
Job status url.
|
|
184
|
-
"""
|
|
185
|
-
project_id = self._client.project_id(project_id)
|
|
186
|
-
payload = asdict(type=type, parameters=params, name=name)
|
|
187
|
-
r: Response = self._client.post(f"/projects/{project_id}/jobs", json=payload)
|
|
188
|
-
return r.json()["link"]
|
|
189
|
-
|
|
190
|
-
def poll(self, *args, **kwargs):
|
|
191
|
-
return self._client.poll(*args, **kwargs)
|
|
192
|
-
|
|
193
|
-
def update(
|
|
194
|
-
self, job_id: str,
|
|
195
|
-
name: str | None = None,
|
|
196
|
-
project_id: str | None = None,
|
|
197
|
-
) -> Job:
|
|
198
|
-
"""Update a job.
|
|
199
|
-
|
|
200
|
-
Args:
|
|
201
|
-
job_id: ID of job to update.
|
|
202
|
-
name: New job name. The existing name remains unchanged if None.
|
|
203
|
-
project_id: Job project ID.
|
|
204
|
-
|
|
205
|
-
Returns:
|
|
206
|
-
Updated job resource.
|
|
207
|
-
"""
|
|
208
|
-
project_id = self._client.project_id(project_id)
|
|
209
|
-
url = f"/projects/{project_id}/jobs/{job_id}"
|
|
210
|
-
payload = asdict(name=name)
|
|
211
|
-
r: Response = self._client.patch(url, data=payload)
|
|
212
|
-
return Job(**r.json())
|
|
213
|
-
|
|
214
138
|
def delete(self, job_id: str, project_id: str | None = None):
|
|
215
139
|
"""Delete a job.
|
|
216
140
|
|
|
@@ -72,6 +72,14 @@ ZERO_VELOCITY = [
|
|
|
72
72
|
]
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
def _has_motion(velocity: Optional[list]) -> bool:
|
|
76
|
+
"""True if a velocity profile has any non-zero component.
|
|
77
|
+
[time, vx, vy, vz]."""
|
|
78
|
+
if not velocity:
|
|
79
|
+
return False
|
|
80
|
+
return any(any(c for c in row[1:4]) for row in velocity)
|
|
81
|
+
|
|
82
|
+
|
|
75
83
|
class BoundaryCondition(Enum):
|
|
76
84
|
SYMMETRIC = "symmetric" # symmetry plane (acts as frictionless wall)
|
|
77
85
|
VELOCITY_DIRICHLET = "velocity_dirichlet" # velocity fixed to zero at the face
|
|
@@ -291,6 +299,39 @@ class ExperimentSupportParallelepiped(ExperimentParallelepiped, ExperimentSuppor
|
|
|
291
299
|
material: Material = field(default_factory=lambda: DEFAULT_SUPPORT_MATERIAL)
|
|
292
300
|
|
|
293
301
|
|
|
302
|
+
def validate_simulation_parts(parts: list) -> None:
|
|
303
|
+
"""Raise ValueError if the parts can't produce a compression simulation.
|
|
304
|
+
|
|
305
|
+
Callers that dispatch work asynchronously should call this up front so
|
|
306
|
+
invalid input fails before any project or job is created.
|
|
307
|
+
"""
|
|
308
|
+
if not parts:
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
has_piston = False
|
|
312
|
+
has_support = False
|
|
313
|
+
piston_moves = False
|
|
314
|
+
for p in parts:
|
|
315
|
+
if isinstance(p, ExperimentPistonBase):
|
|
316
|
+
has_piston = True
|
|
317
|
+
piston_moves = piston_moves or _has_motion(p.velocity)
|
|
318
|
+
elif isinstance(p, ExperimentSupportBase):
|
|
319
|
+
has_support = True
|
|
320
|
+
|
|
321
|
+
if not has_piston and not has_support:
|
|
322
|
+
raise ValueError(
|
|
323
|
+
"No piston part: a compression simulation needs "
|
|
324
|
+
"a rigid part to apply load. Add a part of type piston_mesh, "
|
|
325
|
+
"piston_cylinder, or piston_box."
|
|
326
|
+
)
|
|
327
|
+
# Supports are stationary by design, so only pistons must move.
|
|
328
|
+
if has_piston and not piston_moves:
|
|
329
|
+
raise ValueError(
|
|
330
|
+
"Piston velocity is zero. Set a velocity "
|
|
331
|
+
"profile of [time, vx, vy, vz] rows with a non-zero component."
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
294
335
|
@dataclass
|
|
295
336
|
class ReferenceData:
|
|
296
337
|
csv_path: Path = Path("")
|
|
@@ -482,6 +523,9 @@ class CompressionSimulation:
|
|
|
482
523
|
else:
|
|
483
524
|
self.part_infos.append(inner_part)
|
|
484
525
|
|
|
526
|
+
# A rigid part drives the compression.
|
|
527
|
+
validate_simulation_parts(parts)
|
|
528
|
+
|
|
485
529
|
self.setup_ply_files(stl_folder_path)
|
|
486
530
|
self.setup_results(output_path)
|
|
487
531
|
if client is None:
|
|
@@ -160,6 +160,7 @@ from metafold.simulation.compression_simulation import (
|
|
|
160
160
|
ExperimentPistonMesh,
|
|
161
161
|
SimulationParameters,
|
|
162
162
|
WorkflowStep,
|
|
163
|
+
validate_simulation_parts,
|
|
163
164
|
)
|
|
164
165
|
|
|
165
166
|
# Build preset lookup from all Material constants in the materials module.
|
|
@@ -243,6 +244,16 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
243
244
|
return parts
|
|
244
245
|
|
|
245
246
|
|
|
247
|
+
def validate_experiment_config(config: dict) -> None:
|
|
248
|
+
"""Raise ValueError if a manifest can't produce a runnable experiment.
|
|
249
|
+
Mirrors the checks run_experiment performs so an API can reject bad
|
|
250
|
+
input before creating a project.
|
|
251
|
+
"""
|
|
252
|
+
if not config.get("project_name", ""):
|
|
253
|
+
raise ValueError("project_name is required")
|
|
254
|
+
validate_simulation_parts(_build_parts(config.get("parts", [])))
|
|
255
|
+
|
|
256
|
+
|
|
246
257
|
def _build_varying(varying_config: list[dict]) -> list[ExperimentVarying]:
|
|
247
258
|
varying: list[ExperimentVarying] = []
|
|
248
259
|
for entry in varying_config:
|
|
@@ -15,6 +15,7 @@ from metafold.simulation.compression_simulation import (
|
|
|
15
15
|
ExperimentPistonBase,
|
|
16
16
|
ExperimentPistonCylinder,
|
|
17
17
|
ExperimentPistonMesh,
|
|
18
|
+
ExperimentSupportMesh,
|
|
18
19
|
SimulationParameters,
|
|
19
20
|
WorkflowStep,
|
|
20
21
|
WorkflowStepType,
|
|
@@ -84,6 +85,7 @@ class TestConstruction:
|
|
|
84
85
|
|
|
85
86
|
def test_missing_ply_raises(self, ply_folder, tmp_path):
|
|
86
87
|
parts = [
|
|
88
|
+
ExperimentPistonCylinder(),
|
|
87
89
|
ExperimentMesh("midsole", DEFAULT_MIDSOLE_NOMINAL, "nonexistent.ply"),
|
|
88
90
|
]
|
|
89
91
|
with pytest.raises(ValueError, match="not found"):
|
|
@@ -95,6 +97,82 @@ class TestConstruction:
|
|
|
95
97
|
client=MagicMock(),
|
|
96
98
|
)
|
|
97
99
|
|
|
100
|
+
def test_no_piston_raises(self, ply_folder, tmp_path):
|
|
101
|
+
# Without a rigid part nothing drives the compression
|
|
102
|
+
parts = [
|
|
103
|
+
ExperimentMesh("midsole", DEFAULT_MIDSOLE_NOMINAL, "mid.ply"),
|
|
104
|
+
ExperimentMesh("outsole", DEFAULT_OUTSOLE, "out.ply"),
|
|
105
|
+
]
|
|
106
|
+
with pytest.raises(ValueError, match="piston"):
|
|
107
|
+
CompressionSimulation(
|
|
108
|
+
parts=parts,
|
|
109
|
+
simulation_name="t",
|
|
110
|
+
stl_folder_path=str(ply_folder),
|
|
111
|
+
output_path=str(tmp_path / "out"),
|
|
112
|
+
client=MagicMock(),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def test_zero_piston_velocity_raises(self, ply_folder, tmp_path):
|
|
116
|
+
# A piston that never moves applies no load
|
|
117
|
+
parts = [
|
|
118
|
+
ExperimentPistonCylinder(velocity=[[0.0, 0, 0, 0.0], [0.04, 0, 0, 0.0]]),
|
|
119
|
+
ExperimentMesh("midsole", DEFAULT_MIDSOLE_NOMINAL, "mid.ply"),
|
|
120
|
+
]
|
|
121
|
+
with pytest.raises(ValueError, match="velocity is zero"):
|
|
122
|
+
CompressionSimulation(
|
|
123
|
+
parts=parts,
|
|
124
|
+
simulation_name="t",
|
|
125
|
+
stl_folder_path=str(ply_folder),
|
|
126
|
+
output_path=str(tmp_path / "out"),
|
|
127
|
+
client=MagicMock(),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def test_empty_piston_velocity_raises(self, ply_folder, tmp_path):
|
|
131
|
+
parts = [
|
|
132
|
+
ExperimentPistonCylinder(velocity=[]),
|
|
133
|
+
ExperimentMesh("midsole", DEFAULT_MIDSOLE_NOMINAL, "mid.ply"),
|
|
134
|
+
]
|
|
135
|
+
with pytest.raises(ValueError, match="velocity is zero"):
|
|
136
|
+
CompressionSimulation(
|
|
137
|
+
parts=parts,
|
|
138
|
+
simulation_name="t",
|
|
139
|
+
stl_folder_path=str(ply_folder),
|
|
140
|
+
output_path=str(tmp_path / "out"),
|
|
141
|
+
client=MagicMock(),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def test_stationary_support_with_moving_piston_ok(self, ply_folder, tmp_path):
|
|
145
|
+
# Supports are stationary by design; only the piston must move.
|
|
146
|
+
parts = [
|
|
147
|
+
ExperimentPistonCylinder(), # default velocity moves
|
|
148
|
+
ExperimentSupportMesh(name="base", filename="top.ply"),
|
|
149
|
+
ExperimentMesh("midsole", DEFAULT_MIDSOLE_NOMINAL, "mid.ply"),
|
|
150
|
+
]
|
|
151
|
+
sim = CompressionSimulation(
|
|
152
|
+
parts=parts,
|
|
153
|
+
simulation_name="t",
|
|
154
|
+
stl_folder_path=str(ply_folder),
|
|
155
|
+
output_path=str(tmp_path / "out"),
|
|
156
|
+
client=MagicMock(),
|
|
157
|
+
)
|
|
158
|
+
assert len(sim.part_infos) == 3
|
|
159
|
+
|
|
160
|
+
def test_support_part_satisfies_rigid_requirement(self, ply_folder, tmp_path):
|
|
161
|
+
# A support is also a rigid driver, so it's a valid alternative to a piston.
|
|
162
|
+
parts = [
|
|
163
|
+
ExperimentSupportMesh(name="base", filename="top.ply"),
|
|
164
|
+
ExperimentMesh("midsole", DEFAULT_MIDSOLE_NOMINAL, "mid.ply"),
|
|
165
|
+
]
|
|
166
|
+
sim = CompressionSimulation(
|
|
167
|
+
parts=parts,
|
|
168
|
+
simulation_name="t",
|
|
169
|
+
stl_folder_path=str(ply_folder),
|
|
170
|
+
output_path=str(tmp_path / "out"),
|
|
171
|
+
client=MagicMock(),
|
|
172
|
+
)
|
|
173
|
+
assert len(sim.part_infos) == 2
|
|
174
|
+
|
|
175
|
+
|
|
98
176
|
class TestWorkflowStepsInput:
|
|
99
177
|
def test_accepts_workflow_step_instance(self):
|
|
100
178
|
steps = CompressionSimulation._clean_workflow_steps_input([
|
|
@@ -102,38 +102,6 @@ job_list = [
|
|
|
102
102
|
},
|
|
103
103
|
]
|
|
104
104
|
|
|
105
|
-
new_job = {
|
|
106
|
-
"id": "1",
|
|
107
|
-
"name": "My Job",
|
|
108
|
-
"type": "test_job",
|
|
109
|
-
"created": "Mon, 01 Jan 2024 00:00:00 GMT",
|
|
110
|
-
"started": "Mon, 01 Jan 2024 00:00:00 GMT",
|
|
111
|
-
"finished": "Mon, 01 Jan 2024 00:00:00 GMT",
|
|
112
|
-
"error": None,
|
|
113
|
-
"inputs": {
|
|
114
|
-
"params": {
|
|
115
|
-
"foo": "1",
|
|
116
|
-
"bar": "a",
|
|
117
|
-
"baz": "[2, \"b\"]",
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
"outputs": {
|
|
121
|
-
"params": None,
|
|
122
|
-
},
|
|
123
|
-
"needs": [],
|
|
124
|
-
"project_id": "1",
|
|
125
|
-
"workflow_id": None,
|
|
126
|
-
"assets": [],
|
|
127
|
-
"parameters": {
|
|
128
|
-
"foo": "1",
|
|
129
|
-
"bar": "a",
|
|
130
|
-
"baz": "[2, \"b\"]",
|
|
131
|
-
},
|
|
132
|
-
"meta": None,
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
poll_count: int = 0
|
|
136
|
-
|
|
137
105
|
|
|
138
106
|
class MockRequestHandler(BaseHTTPRequestHandler):
|
|
139
107
|
def do_GET(self):
|
|
@@ -156,51 +124,6 @@ class MockRequestHandler(BaseHTTPRequestHandler):
|
|
|
156
124
|
payload = deepcopy(job_list[-1])
|
|
157
125
|
payload["assets"] = [asset_json]
|
|
158
126
|
self.wfile.write(json.dumps(payload).encode())
|
|
159
|
-
elif u.path == "/projects/1/jobs/1/status":
|
|
160
|
-
global poll_count
|
|
161
|
-
poll_count += 1
|
|
162
|
-
if poll_count < 3:
|
|
163
|
-
self.send_response(HTTPStatus.ACCEPTED)
|
|
164
|
-
payload = deepcopy(new_job)
|
|
165
|
-
payload["state"] = "started"
|
|
166
|
-
else:
|
|
167
|
-
self.send_response(HTTPStatus.CREATED)
|
|
168
|
-
payload = deepcopy(new_job)
|
|
169
|
-
payload.update({
|
|
170
|
-
"state": "success",
|
|
171
|
-
"assets": [asset_json],
|
|
172
|
-
})
|
|
173
|
-
self.send_header("Content-Type", "application/json")
|
|
174
|
-
self.end_headers()
|
|
175
|
-
self.wfile.write(json.dumps(payload).encode())
|
|
176
|
-
else:
|
|
177
|
-
self.send_error(HTTPStatus.NOT_FOUND)
|
|
178
|
-
|
|
179
|
-
def do_POST(self):
|
|
180
|
-
if self.path == "/projects/1/jobs":
|
|
181
|
-
self.send_response(HTTPStatus.ACCEPTED)
|
|
182
|
-
self.send_header("Content-Type", "application/json")
|
|
183
|
-
self.end_headers()
|
|
184
|
-
payload = deepcopy(new_job)
|
|
185
|
-
payload.update({
|
|
186
|
-
"state": "pending",
|
|
187
|
-
"link": "http://localhost:8000/projects/1/jobs/1/status",
|
|
188
|
-
})
|
|
189
|
-
self.wfile.write(json.dumps(payload).encode())
|
|
190
|
-
else:
|
|
191
|
-
self.send_error(HTTPStatus.NOT_FOUND)
|
|
192
|
-
|
|
193
|
-
def do_PATCH(self):
|
|
194
|
-
if self.path == "/projects/1/jobs/1":
|
|
195
|
-
self.send_response(HTTPStatus.OK)
|
|
196
|
-
self.send_header("Content-Type", "application/json")
|
|
197
|
-
self.end_headers()
|
|
198
|
-
payload = deepcopy(job_list[-1])
|
|
199
|
-
payload.update({
|
|
200
|
-
"name": "baz",
|
|
201
|
-
"assets": [asset_json],
|
|
202
|
-
})
|
|
203
|
-
self.wfile.write(json.dumps(payload).encode())
|
|
204
127
|
else:
|
|
205
128
|
self.send_error(HTTPStatus.NOT_FOUND)
|
|
206
129
|
|
|
@@ -245,63 +168,3 @@ def test_get_job(client):
|
|
|
245
168
|
parameters=default_params,
|
|
246
169
|
meta=None,
|
|
247
170
|
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def test_run_job(client):
|
|
251
|
-
params = {
|
|
252
|
-
"foo": "1",
|
|
253
|
-
"bar": "a",
|
|
254
|
-
"baz": "[2, \"b\"]",
|
|
255
|
-
}
|
|
256
|
-
j = client.jobs.run("test_job", params, name="My Job")
|
|
257
|
-
assert j == Job(
|
|
258
|
-
id="1",
|
|
259
|
-
name="My Job",
|
|
260
|
-
type="test_job",
|
|
261
|
-
state="success",
|
|
262
|
-
created=default_dt,
|
|
263
|
-
started=default_dt,
|
|
264
|
-
finished=default_dt,
|
|
265
|
-
inputs=IO(params=params),
|
|
266
|
-
outputs=IO(),
|
|
267
|
-
needs=[],
|
|
268
|
-
project_id="1",
|
|
269
|
-
workflow_id=None,
|
|
270
|
-
assets=[asset_obj],
|
|
271
|
-
parameters=params,
|
|
272
|
-
meta=None,
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def test_poll_job(client):
|
|
277
|
-
params = {
|
|
278
|
-
"foo": "1",
|
|
279
|
-
"bar": "a",
|
|
280
|
-
"baz": "[2, \"b\"]",
|
|
281
|
-
}
|
|
282
|
-
url = client.jobs.run_status("test_job", params, name="My Job")
|
|
283
|
-
assert url == "http://localhost:8000/projects/1/jobs/1/status"
|
|
284
|
-
|
|
285
|
-
r = client.jobs.poll(url)
|
|
286
|
-
assert Job(**r.json()) == Job(
|
|
287
|
-
id="1",
|
|
288
|
-
name="My Job",
|
|
289
|
-
type="test_job",
|
|
290
|
-
state="success",
|
|
291
|
-
created=default_dt,
|
|
292
|
-
started=default_dt,
|
|
293
|
-
finished=default_dt,
|
|
294
|
-
inputs=IO(params=params),
|
|
295
|
-
outputs=IO(),
|
|
296
|
-
needs=[],
|
|
297
|
-
project_id="1",
|
|
298
|
-
workflow_id=None,
|
|
299
|
-
assets=[asset_obj],
|
|
300
|
-
parameters=params,
|
|
301
|
-
meta=None,
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
def test_update_job(client):
|
|
306
|
-
j = client.jobs.update("1", name="baz")
|
|
307
|
-
assert j.name == "baz"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|