Skip to content

Commit 6e00a0f

Browse files
committed
update Run Relationships
1 parent 316b52a commit 6e00a0f

2 files changed

Lines changed: 172 additions & 12 deletions

File tree

src/pytfe/resources/run.py

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
RequiredWorkspaceError,
1111
TerraformVersionValidForPlanOnlyError,
1212
)
13+
from ..models.apply import Apply
14+
from ..models.comment import Comment
15+
from ..models.configuration_version import ConfigurationVersion
16+
from ..models.cost_estimate import CostEstimate
17+
from ..models.plan import Plan
18+
from ..models.policy_check import PolicyCheck
1319
from ..models.run import (
1420
Run,
1521
RunApplyOptions,
@@ -21,10 +27,57 @@
2127
RunListOptions,
2228
RunReadOptions,
2329
)
30+
from ..models.run_event import RunEvent
31+
from ..models.task_stage import TaskStage
32+
from ..models.user import User
33+
from ..models.workspace import Workspace
2434
from ..utils import _safe_str, valid_string, valid_string_id
2535
from ._base import _Service
2636

2737

38+
def transform_relationships(relationships):
39+
"""
40+
Transform relationships dict to map relationship names to their model objects.
41+
Single IDs become model instances, multiple IDs become lists of model instances.
42+
"""
43+
result = {}
44+
45+
# Map relationship keys to their model constructors
46+
model_map = {
47+
"apply": Apply,
48+
"configuration-version": ConfigurationVersion,
49+
"cost-estimate": CostEstimate,
50+
"created-by": User,
51+
"confirmed-by": User,
52+
"plan": Plan,
53+
"workspace": Workspace,
54+
"policy-checks": PolicyCheck,
55+
"run-events": RunEvent,
56+
"task-stages": TaskStage,
57+
"comments": Comment,
58+
}
59+
60+
for key, value in relationships.items():
61+
data = value.get("data")
62+
63+
if data is None:
64+
continue
65+
66+
model_class = model_map.get(key)
67+
if not model_class:
68+
# Unknown relationship type, skip it
69+
continue
70+
71+
if isinstance(data, list):
72+
# Multiple entries - create list of model instances
73+
result[key] = [model_class(id=item["id"]) for item in data if "id" in item]
74+
elif isinstance(data, dict) and "id" in data:
75+
# Single entry - create model instance
76+
result[key] = model_class(id=data["id"])
77+
78+
return result
79+
80+
2881
class Runs(_Service):
2982
def list(
3083
self, workspace_id: str, options: RunListOptions | None = None
@@ -94,10 +147,11 @@ def create(self, options: RunCreateOptions) -> Run:
94147
)
95148
d = r.json().get("data", {})
96149
attrs = d.get("attributes", {})
97-
return Run(
98-
id=_safe_str(d.get("id")),
99-
**{k.replace("-", "_"): v for k, v in attrs.items()},
100-
)
150+
relationships = transform_relationships(d.get("relationships", {}))
151+
combined = {
152+
k.replace("-", "_"): v for k, v in {**attrs, **relationships}.items()
153+
}
154+
return Run(id=_safe_str(d.get("id")), **combined)
101155

102156
def read(self, run_id: str) -> Run:
103157
"""Read a run by its ID."""
@@ -119,10 +173,11 @@ def read_with_options(
119173
)
120174
d = r.json().get("data", {})
121175
attrs = d.get("attributes", {})
122-
return Run(
123-
id=_safe_str(d.get("id")),
124-
**{k.replace("-", "_"): v for k, v in attrs.items()},
125-
)
176+
relationships = transform_relationships(d.get("relationships", {}))
177+
combined = {
178+
k.replace("-", "_"): v for k, v in {**attrs, **relationships}.items()
179+
}
180+
return Run(id=_safe_str(d.get("id")), **combined)
126181

127182
def apply(self, run_id: str, options: RunApplyOptions | None = None) -> None:
128183
"""Apply a run by its ID."""

tests/units/test_run.py

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,14 +294,115 @@ def test_read_with_options_success(self, runs_service):
294294
mock_response_data = {
295295
"data": {
296296
"id": "run-detailed-123",
297+
"type": "runs",
297298
"attributes": {
298-
"status": "planned",
299-
"source": "tfe-api",
300-
"message": "Detailed read test",
301-
"created-at": "2023-01-01T12:00:00Z",
299+
"actions": {
300+
"is-cancelable": False,
301+
"is-confirmable": False,
302+
"is-discardable": False,
303+
"is-force-cancelable": False,
304+
},
305+
"allow-config-generation": False,
306+
"allow-empty-apply": False,
307+
"auto-apply": False,
308+
"canceled-at": None,
309+
"created-at": "2026-02-19T01:58:46.126Z",
302310
"has-changes": True,
303311
"is-destroy": False,
312+
"message": "Triggered via CLI",
313+
"plan-only": False,
314+
"refresh": True,
315+
"refresh-only": False,
316+
"replace-addrs": None,
317+
"save-plan": False,
318+
"source": "terraform+cloud",
319+
"status-timestamps": {
320+
"errored-at": "2026-02-19T01:59:19+00:00",
321+
"planned-at": "2026-02-19T01:59:16+00:00",
322+
"queuing-at": "2026-02-19T01:58:46+00:00",
323+
"planning-at": "2026-02-19T01:58:48+00:00",
324+
"plan-queued-at": "2026-02-19T01:58:46+00:00",
325+
"plan-queueable-at": "2026-02-19T01:58:46+00:00",
326+
},
327+
"status": "errored",
328+
"target-addrs": None,
329+
"trigger-reason": "manual",
330+
"terraform-version": "1.13.5",
331+
"updated-at": "2026-02-19T01:59:19.891Z",
332+
"permissions": {
333+
"can-apply": True,
334+
"can-cancel": True,
335+
"can-comment": True,
336+
"can-discard": True,
337+
"can-force-execute": True,
338+
"can-force-cancel": True,
339+
"can-override-policy-check": True,
340+
},
341+
"variables": [],
342+
"invoke-action-addrs": None,
343+
},
344+
"relationships": {
345+
"workspace": {
346+
"data": {"id": "ws-a2Kntu53K79hsPRH", "type": "workspaces"}
347+
},
348+
"apply": {
349+
"data": {"id": "apply-Y1rVt6MpiwzdMjbK", "type": "applies"},
350+
"links": {"related": "/api/v2/runs/run-ugBnsFDyDviC876w/apply"},
351+
},
352+
"configuration-version": {
353+
"data": {
354+
"id": "cv-bakH4hn9cPXb2yZq",
355+
"type": "configuration-versions",
356+
},
357+
"links": {
358+
"related": "/api/v2/runs/run-ugBnsFDyDviC876w/configuration-version"
359+
},
360+
},
361+
"created-by": {
362+
"data": {"id": "user-FRJGnNMX6fpe9Cdd", "type": "users"},
363+
"links": {
364+
"related": "/api/v2/runs/run-ugBnsFDyDviC876w/created-by"
365+
},
366+
},
367+
"plan": {
368+
"data": {"id": "plan-WooDdHWZnSE3Zs8j", "type": "plans"},
369+
"links": {"related": "/api/v2/runs/run-ugBnsFDyDviC876w/plan"},
370+
},
371+
"run-events": {
372+
"data": [
373+
{"id": "re-bqJGaaCrt5QZfexJ", "type": "run-events"},
374+
{"id": "re-j8d6eWyfyHSUbX7x", "type": "run-events"},
375+
{"id": "re-UAXd9VyRTXZy3hpx", "type": "run-events"},
376+
{"id": "re-DFFf51Doi8mmHC9G", "type": "run-events"},
377+
{"id": "re-U2m4RMQhEY9voN1K", "type": "run-events"},
378+
{"id": "re-WWfUbu5NTWdYKgBs", "type": "run-events"},
379+
],
380+
"links": {
381+
"related": "/api/v2/runs/run-ugBnsFDyDviC876w/run-events"
382+
},
383+
},
384+
"task-stages": {
385+
"data": [],
386+
"links": {
387+
"related": "/api/v2/runs/run-ugBnsFDyDviC876w/task-stages"
388+
},
389+
},
390+
"policy-checks": {
391+
"data": [
392+
{"id": "polchk-JxgtJ56kFifnngyT", "type": "policy-checks"}
393+
],
394+
"links": {
395+
"related": "/api/v2/runs/run-ugBnsFDyDviC876w/policy-checks"
396+
},
397+
},
398+
"comments": {
399+
"data": [],
400+
"links": {
401+
"related": "/api/v2/runs/run-ugBnsFDyDviC876w/comments"
402+
},
403+
},
304404
},
405+
"links": {"self": "/api/v2/runs/run-ugBnsFDyDviC876w"},
305406
}
306407
}
307408

@@ -330,6 +431,10 @@ def test_read_with_options_success(self, runs_service):
330431
# Verify result
331432
assert isinstance(result, Run)
332433
assert result.id == "run-detailed-123"
434+
assert result.created_by.id == "user-FRJGnNMX6fpe9Cdd"
435+
assert result.plan.id == "plan-WooDdHWZnSE3Zs8j"
436+
assert result.apply.id == "apply-Y1rVt6MpiwzdMjbK"
437+
assert result.workspace.id == "ws-a2Kntu53K79hsPRH"
333438

334439
def test_apply_run_success(self, runs_service):
335440
"""Test successful apply operation."""

0 commit comments

Comments
 (0)