You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+12-2Lines changed: 12 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,14 +5,24 @@
5
5
### Relationships
6
6
* Added a lossless JSON:API escape hatch. **Every resource model** now derives from the new `pytfe.models.TFEModel` base and exposes `model.relationships`, `model.included`, `model.included_by(type, id)`, `model.related(name)`, and the `model.has_relationships` / `model.has_included` presence flags (distinguishing "absent on the wire" from "present but empty"). The raw blocks are private attributes — excluded from `model_dump()`**and from equality** — so this is additive and non-breaking; they complement `extra="allow"`, which only retains unknown *attributes*.
7
7
***`relationships` capture** is wired broadly across the resources whose models are built through a dedicated parser (workspaces, runs, projects, teams, policies, policy sets, stacks, registry, no-code modules, comments, state versions, variable sets, oauth clients, notification configs, org memberships, query runs, admin orgs/runs/users/workspaces, and more), so the raw relationship references are always reachable.
8
-
***`included` hydration** (typed relations filled from the document's top-level `included`, and a populated `model.included`) currently applies to the single-resource reads that thread it — `workspaces.read*`, `runs.read*`, `no_code_modules.read_variables`. Other single reads and **all list endpoints** capture `relationships` but not yet `included`; threading `included` through the remaining reads and list pagination is an in-progress follow-up.
8
+
***`included` hydration** now comes in two forms, both purely additive:
9
+
***Typed hydration** — declared relationship fields are filled from the document's top-level `included` array (and `model.included` is populated). Applies to the single-resource reads of `workspaces`, `runs`, `agent_pools`, `stack_configuration`, `teams`, `task_stages`, `policy_set`, `organization_membership`, `variable_set`, `run_event`, and `no_code_modules.read_variables`. The rule is uniform: **wherever a resource models a relation as a typed field, `?include=<relation>` fills that field** (e.g. `policy_set.current_version`, `organization_membership.user`, `run_event.actor`).
10
+
***Raw capture** — `model.included` is populated and `model.related(name)` / `model.included_by(type, id)` resolve to the full related bodies. Applies to the single-resource reads whose includable relations are **not** modelled as typed fields, so there is no typed field to fill: `state_versions`, `agents`, `configuration_version`, `oauth_client`, `organizations`, `projects`, `query_run`, `registry_provider`, and `run_task` reads. (Capturing the raw blocks only populates the private escape hatch — no typed field changes, so this is non-breaking.)
11
+
***Not yet wired** — `registry_module`, `run_trigger`, and `policy_check` accept `?include=` only on their *list* endpoints, and **all list endpoints** across the SDK still capture `relationships` but not `included` (the shared top-level `included` array is not yet threaded through list pagination — an in-progress follow-up).
9
12
10
-
See [docs/related-resources.md](docs/related-resources.md).
13
+
See [docs/related-resources.md](docs/related-resources.md) for the per-resource coverage table and a "typed field vs raw accessor" guide.
14
+
15
+
* Added `?include=` support to three single-resource reads that previously exposed no include option, matching the HCP Terraform API (verified against go-tfe's OpenAPI spec and the live API):
*`organizations.read(name, OrganizationReadOptions(include=[...]))` — `subscription` (raw capture). The new `options` argument is optional, so existing positional calls are unchanged.
11
19
12
20
## Bug Fixes
13
21
14
22
### Relationships
15
23
* Fixed `workspaces.read*(..., include=[WorkspaceIncludeOpt.OUTPUTS])` returning outputs with `None` name/value/type. Workspace `outputs` is now hydrated from the JSON:API`included` array through the shared relationship parser (matching go-tfe's `relation,outputs`), instead of a broken special case that read attributes off the id-only relationship references. [#134](https://github.com/hashicorp/python-tfe/issues/134) (the related project-include case, [#74](https://github.com/hashicorp/python-tfe/issues/74), was already resolved by the relationship refactor and is verified covered.)
24
+
*`PolicySetVersion` is now exported from `pytfe.models` and its forward reference to `PolicySet` is resolved via `model_rebuild()`. Previously it was never fully defined, so `policy_set.read*(include=[current_version|newest_version])` silently fell back to an id-only stub instead of hydrating the version's `source`/`created_at`/`status`.
25
+
*`variable_set.read` no longer fabricates placeholder relation values (e.g. `name="workspace-<id>"`, `key="var-<id>"`, `category="terraform"`) for `workspaces`/`projects`/`vars`. Those relations are now id-only stubs by default and hydrate from `included` when requested via `?include=`, like every other typed relation.
ps.current_version.source # now hydrated from `included`
80
+
```
81
+
82
+
***Prefer the typed field** (`ps.current_version`, `ws.outputs`, `team.users`,
83
+
`org_membership.user`, `run_event.actor`) whenever the relation is modelled — it's
84
+
type-checked and stable, and `?include=<relation>` fills it. This works the *same
85
+
way for every resource that models the relation*: there are no resources where a
86
+
typed field silently stays a stub after you `?include=` it.
87
+
***Use the raw accessors** (`model.related(name)`, `model.included_by(type, id)`)
88
+
only for relations the SDK does **not** model as a typed field — e.g. an
89
+
organization's `subscription`, or a workspace `readme`. The data is still returned
90
+
by `?include=`, just untyped.
91
+
92
+
You never need both for the same relation: if a typed field exists, `?include=` fills
93
+
it; if it doesn't, the raw accessors are the way in.
94
+
95
+
## Per-resource coverage
96
+
97
+
`?include=` support by single-resource `read*` (see each resource's `*IncludeOpt`):
98
+
99
+
| Behaviour | Resources |
100
+
|---|---|
101
+
|**Typed hydration** — `include` fills the typed field |`workspaces`, `runs`, `agent_pools`, `stack_configuration`, `teams`, `task_stages`, `policy_set`, `organization_membership`, `variable_set`, `run_event`, `no_code_modules.read_variables`|
102
+
|**Raw capture** — relation not modelled as a typed field; reach it via `related()` / `included_by()`|`organizations` (`subscription`), `state_versions`, `agents`, `configuration_version`, `oauth_client`, `projects`, `query_run`, `registry_provider`, `run_task`|
103
+
|**List-only** — `?include=` exists only on the `list` endpoint |`registry_module`, `run_trigger`, `policy_check`|
104
+
105
+
In every case the **`relationships`** block and the four raw accessors are populated,
106
+
so unmodelled relations are never lost. **List endpoints** currently capture
107
+
`relationships` but not `included` (the page-level `included` array is not yet threaded
108
+
through pagination — in progress).
109
+
63
110
## Notes
64
111
65
112
- The raw blocks are **private attributes**, so they never appear in
@@ -72,6 +119,8 @@ pyTFE handles this on two levels:
72
119
- Accessors are provided by `pytfe.models.TFEModel`, which **every
73
120
resource model** now derives from — so `.relationships` / `.included` /
74
121
`.included_by` / `.related` are available everywhere. They're *populated* on
75
-
resources parsed through a dedicated parser; other resources expose the
76
-
accessors but return them empty until their parser is wired to capture the
77
-
raw blocks.
122
+
single-resource `read*` calls: the `relationships` block on reads that go
123
+
through a relationship-capturing parser, and the `included` array whenever you
124
+
pass `?include=`. **List endpoints** currently populate `relationships` but not
125
+
`included` — the shared top-level `included` array is not yet threaded through
0 commit comments