Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ This is the workflow that produces low-friction reviews. Follow it.

## Style

The codebase uses [ruff](https://docs.astral.sh/ruff/) for both formatting and linting and [mypy](https://mypy.readthedocs.io/) for type checking. Type hints are required on every public method's signature. Docstrings are required on every public method — keep them to one or two lines unless the behavior is genuinely non-obvious.
The codebase uses [ruff](https://docs.astral.sh/ruff/) for both formatting and linting and [mypy](https://mypy.readthedocs.io/) for type checking. Type hints are required on every public method's signature. **Public resource methods carry a Google-style docstring** — a one-line summary plus `Args:`, `Returns:`, `Raises:`, and `Example:` sections *as applicable* (omit `Args:` for a no-argument method; omit `Raises:` when the method raises nothing, e.g. a pure local computation) — written for *consumers* and the AI coding assistants that read them via the language server. Accuracy comes first: under `Raises:` list only the exceptions the method body itself raises (not those raised by an options model's validators); encode return gotchas in `Returns:` (single-use `Iterator`, raw `bytes` for blob downloads that follow a redirect, `None` for `204`s); and make every `Example:` a real, runnable call using the correct `client.<attr>` name and a real `*Options` class. Internal/private helpers stay terse (one line, or none when obvious).

Comments are minimal by design. A comment should explain *why* something non-obvious is true, not *what* the code does. The names and types should be enough to convey "what".

Expand Down
59 changes: 58 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,81 @@
# Unreleased

# Released
# v1.2.0

## Enhancements

### Relationships
Related data is now a complete, first-class part of every response. Before, `?include=` often did not actually fill the related fields it returned, and anything the SDK did not model as a typed field was dropped on the floor. Now the full set of related resources the API hands back is always available to you: typed where pytfe models it, raw where it does not. The practical win is that **you are no longer limited to the relationships pytfe has added typed support for.** You can read any related resource in a response without dropping to manual HTTP or waiting for a new SDK release.

* `?include=` now fills in related data. When the SDK models a relation as a typed field (for example `workspace.outputs`, `policy_set.current_version`, `organization_membership.user`, `run_event.actor`), passing `?include=<relation>` fills that field with the real record instead of an id-only stub.
* Relations the SDK does **not** model are no longer lost. Every top-level resource model now derives from a new `pytfe.models.TFEModel` base and gains read-only accessors for the raw JSON:API data the API returned: `model.relationships`, `model.included`, `model.related(name)`, `model.included_by(type, id)`, and the `model.has_relationships` and `model.has_included` flags. So when a relation has no typed field of its own (for example an organization's `subscription`, or a workspace `readme`), `?include=` still returns it and you reach it with `model.related("subscription")` or `model.included_by(type, id)`. These accessors are read-only extras that never appear in `model_dump()` or affect equality, so this is additive and non-breaking. List endpoints expose the relationship refs but do not yet fill `included`. See [docs/related-resources.md](docs/related-resources.md) for the per-resource table and a "typed field vs raw accessor" guide.
* Added `?include=` support to three reads that previously had no include option, matching the HCP Terraform API:
* `teams.read(team_id, TeamReadOptions(include=[...]))`: `users`, `organization-memberships`.
* `task_stages.read(task_stage_id, TaskStageReadOptions(include=[...]))`: `run`, `run.workspace`, `task-results`, `policy-evaluations`.
* `organizations.read(name, OrganizationReadOptions(include=[...]))`: `subscription`. The new `options` argument is optional, so existing calls are unchanged.

### New resources
* Added `client.subscriptions` — read an organization's subscription (HCP Terraform only). `read_for_organization(org)` (`GET /organizations/{org}/subscription`) and `read(id)` (`GET /subscriptions/{id}`). The linked feature set is hydrated into `.included` and reachable via `subscription.related("feature-set")`. New model: `Subscription`. New error: `InvalidSubscriptionIDError`.
* Added `client.invoices` — read an organization's billing invoices (HCP Terraform only). `list(org)` (cursor-paginated via `meta.continuation`, fixed page size 10) and `read_next(org)` (the upcoming invoice, or `None` when there is no upcoming invoice). New model: `Invoice`.
* Added `client.ip_ranges` — read HCP Terraform / Terraform Enterprise outbound IP ranges via `GET /api/meta/ip-ranges`. `read(modified_since=None)` returns an `IPRange` (CIDR lists for `api`, `notifications`, `sentinel`, `vcs`), or `None` when an `If-Modified-Since` date is supplied and the ranges are unchanged (HTTP 304). New model: `IPRange`.
* Added `client.plan_exports` — export Terraform plan data (Sentinel mock bundles). `create(options)`, `read(id)`, `delete(id)`, and `download(id)` (returns the `.tar.gz` archive bytes, following the temporary presigned-URL redirect). New models: `PlanExport`, `PlanExportCreateOptions`, `PlanExportStatus`, `PlanExportDataType`, `PlanExportStatusTimestamps`. New errors: `InvalidPlanExportIDError`, `RequiredPlanError`.
* Added `client.cost_estimates` — read run cost estimates. `read(id)` returns a `CostEstimate`; `logs(id)` returns the estimate's log output text. `CostEstimate`, `CostEstimateStatus`, and `CostEstimateStatusTimestamps` are now exported from `pytfe.models`. New error: `InvalidCostEstimateIDError`.
* Added IP allowlists (the JSON:API `cidr-range-lists` / `cidr-ranges` resources) as `client.cidr_range_lists` and `client.cidr_ranges`. `cidr_range_lists` supports `list`, `create`, `read`, `update`, `delete`, plus `list_cidr_ranges`, `add_cidr_range`, and `add_agent_pools` / `remove_agent_pools`; `cidr_ranges` supports `read`, `update`, `delete`. New models: `CIDRRangeList`, `CIDRRange`, `EnforcementScope`, and their create/update/list options. New errors: `InvalidCIDRRangeListIDError`, `InvalidCIDRRangeIDError`, `RequiredCIDRBlockError`.
* Added `client.registry` — a client for the **public Terraform Registry** module API (`registry.terraform.io`). This is a new, unauthenticated surface on a different host (the SDK never sends the bearer token to the registry); `base_url` is configurable for other registries implementing the module registry protocol. Methods: `list_modules`, `search_modules`, `list_latest_for_all_providers`, `latest_for_provider`, `get_module`, `list_versions`, `download_url`, `latest_download_url`, and `downloads_summary`. New models are exported under the `PublicRegistry*` prefix (e.g. `PublicRegistryModule`, `PublicRegistryModuleVersions`, `PublicRegistryModuleDownloadsSummary`). New errors: `InvalidModuleNamespaceError`, `InvalidModuleNameError`, `InvalidModuleProviderError`, `InvalidModuleVersionError`.
* Added `client.assessment_results` — read workspace health assessment (drift detection / continuous validation) results. `read(id)` returns an `AssessmentResult`; `json_output(id)` and `json_schema(id)` return the underlying JSON plan / provider schema (following the blob redirect, `None` on 204); `log_output(id)` returns the Terraform JSON log as text. `AssessmentResult` is now a `TFEModel`, so its `workspace`/`source` relationships are reachable via `.relationships` / `.related(...)`. New error: `InvalidAssessmentResultIDError`.
* Added `client.hyok_configurations` — manage HYOK (Hold Your Own Key) configurations. `list(org)`, `create(org, options)`, `read(id)`, `delete(id)`, `test(id)`, and `revoke(id)`. A HYOK configuration ties an OIDC configuration (`client.*_oidc_configurations`) and an agent pool to a customer-controlled KMS key; this is the parent resource for the per-cloud OIDC configs. A configuration must be revoked before it can be deleted. New models: `HYOKConfiguration`, `HYOKConfigurationCreateOptions`, `HYOKConfigurationStatus`, `HYOKKMSOptions`, `OIDCConfigurationType`. New errors: `InvalidHYOKConfigurationIDError`, `RequiredKEKIDError`.

### Discovery for AI agents and tooling
The installed package is now self-describing, so a consumer (including an AI
agent or other tooling working only from `site-packages/pytfe`) can enumerate and
drive the SDK without hardcoding resource names or browsing the GitHub repo.
* `pytfe.describe()` returns a machine-readable manifest of the API surface —
every resource namespace on `TFEClient`, its public methods, signatures, and
one-line summaries, with the `admin` namespace nested. It makes no network
calls (a throwaway client with an empty config is used purely to introspect
the wiring) and the result is JSON-serializable. Each method's `*Options`
model still exposes JSON Schema via `model_json_schema()`.
* `pytfe.llms_txt()` returns a concise, agent-oriented orientation guide that
now ships inside the wheel at `pytfe/llms.txt` (alongside `py.typed`).
* `TFEClient` gained a comprehensive class docstring (resource namespaces,
quickstart, conventions) so `help(TFEClient)` and IDE hover are useful, and
it is now a context manager: `with TFEClient(...) as tfe: ...` closes the
pooled HTTP connection automatically. `close()` is documented and idempotent.
* Every public resource method now ships a Google-style docstring — a one-line
summary plus `Args`, `Returns`, `Raises`, and a runnable `Example:` block
(sections included as applicable). These are written for consumers and the AI
coding assistants (Copilot/Claude/Cursor) that read them via the language
server from `site-packages`, so completions for pytfe calls are more accurate
out of the box. Return-shape gotchas (single-use `Iterator`, raw `bytes` for
blob downloads, `None` for `204`s) and the exact exceptions each method raises
are now documented in place. Linked from the README's new *AI coding
assistants* row alongside `llms.txt`.
* `StateVersionIncludeOpt` and `PolicySetOutcomeListOptions` are now exported
from `pytfe.models`, matching the rest of their model families.

### Packaging
* The source distribution (sdist) now includes `examples/`, `CHANGELOG.md`, and
`AGENTS.md` so source consumers get the full example and changelog context.
The wheel is unchanged (examples remain non-importable).


## Bug Fixes

### Relationships
* Fixed `workspaces.read*(include=[WorkspaceIncludeOpt.OUTPUTS])` returning outputs with `None` name, value, and type. Workspace `outputs` is now filled from the `included` data. [#134](https://github.com/hashicorp/python-tfe/issues/134)
* Fixed `policy_set.read*(include=[current_version | newest_version])` returning an id-only stub. `PolicySetVersion` is now exported from `pytfe.models` and fully resolved, so the version's `source`, `created_at`, and `status` are populated.
* Fixed `variable_set.read` inventing placeholder values (such as `name="workspace-<id>"` or `key="var-<id>"`) for `workspaces`, `projects`, and `vars`. These are now id-only stubs by default and fill from `included` when requested.

### Cost estimates
* Fixed `CostEstimate` failing to parse real API responses: `status-timestamps` now treats every timestamp as optional (the API only returns the ones that have occurred) and adds the missing `pending-at`, and `error-message` now accepts `null`. Previously an included `cost-estimate` with a null error or partial timestamps would silently collapse to an id-only stub.

### Transport
* Fixed the shared HTTP client retaining `Set-Cookie` session cookies across requests. The `/api/meta/ip-ranges` endpoint returns an `_atlas_session_data` cookie; once stored, that browser session silently overrode bearer-token auth on every subsequent request, causing spurious `401`/`404` errors. The transport now never persists cookies (this SDK authenticates only with the bearer token). Without this fix, any call to `client.ip_ranges.read()` broke all later authenticated calls on the same client.

### Organizations
* Fixed `organizations.read_entitlements` silently dropping most entitlement flags. The parser surfaced only 15 of the ~47 flags the API returns, so flags such as `hyok`, `assessments`, `stacks`, `terraform-actions`, and `change-requests` were discarded. `Entitlements` now exposes those as typed fields and retains every remaining flag (including the integer `*-limit` flags) under `model_extra` via `extra="allow"`. The change is additive — existing typed fields are unchanged.


# Released
# v1.1.0

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ and upstream HCP Terraform API docs.
| Need | Start here |
|---|---|
| Configure the SDK | [Authentication](./docs/authentication.md), [Pagination](./docs/pagination.md), [Logging](./docs/LOGGING.md) |
| API guides | [API index](./docs/api/index.md), [API coverage](./docs/api-coverage.md), [Related resources (`include`)](./docs/related-resources.md), [Workspaces](./docs/api/workspaces.md), [Runs/plans/applies](./docs/api/runs-plans-applies.md), [State versions](./docs/api/state-versions.md) |
| AI coding assistants | [`llms.txt`](./src/pytfe/llms.txt) — concise pytfe orientation (quickstart, conventions, error handling) for LLMs writing pytfe code |
| API guides | [API index](./docs/api/index.md), [API coverage](./docs/api-coverage.md), [Related resources (`include`)](./docs/related-resources.md), [Workspaces](./docs/api/workspaces.md), [Runs/plans/applies](./docs/api/runs-plans-applies.md), [State versions](./docs/api/state-versions.md), [Public Registry](./docs/api/registry.md) |
| Scenario guides | [API-driven run](./docs/scenarios/api-driven-run.md), [State management](./docs/scenarios/state-management.md), [Migrate workspaces and state](./docs/scenarios/migrate-workspaces-and-state.md), [Team access onboarding](./docs/scenarios/team-access-onboarding.md), [No-code provisioning](./docs/scenarios/no-code-provisioning.md), [TFE identity bootstrap](./docs/scenarios/tfe-identity-bootstrap.md), [TFE admin bootstrap](./docs/scenarios/tfe-admin-bootstrap.md), [OIDC dynamic credentials](./docs/scenarios/oidc-dynamic-credentials.md) |
| Operations guides | [Troubleshooting](./docs/troubleshooting.md), [Errors](./docs/errors.md), [Terraform Enterprise](./docs/terraform-enterprise.md) |
| Contribute to the SDK | [CONTRIBUTING](./docs/CONTRIBUTING.md), [ITERATORS](./docs/ITERATORS.md), [MODELS](./docs/MODELS.md), [RESOURCE](./docs/RESOURCE.md) |
Expand Down
26 changes: 16 additions & 10 deletions docs/api-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ resource list is reconciled against the public

**Legend:** ✅ Covered &nbsp;·&nbsp; 🟡 Partial &nbsp;·&nbsp; ❌ Not yet implemented

pytfe implements **61 resource namespaces**. The resources still missing or
pytfe implements **71 resource namespaces**. The resources still missing or
partially covered are listed at the bottom of this page.

## Covered resources
Expand All @@ -19,6 +19,8 @@ partially covered are listed at the bottom of this page.
| | Organization memberships | `client.organization_memberships` | ✅ |
| | Organization tags | `client.organization_tags` | ✅ |
| | Organization tokens | `client.organization_tokens` | ✅ |
| | Subscriptions | `client.subscriptions` | ✅ |
| | Invoices | `client.invoices` | ✅ |
| | Organization token TTL policies | `client.organization_token_ttl_policies` | ✅ |
| | Organization audit configuration | `client.organization_audit_configurations` | ✅ |
| | Teams | `client.teams` | ✅ |
Expand All @@ -27,6 +29,8 @@ partially covered are listed at the bottom of this page.
| | Team workspace access | `client.team_workspace_accesses` | ✅ |
| | Users | `client.users` | ✅ |
| | SSH keys | `client.ssh_keys` | ✅ |
| | IP allowlists (CIDR range lists) | `client.cidr_range_lists` | ✅ |
| | CIDR ranges | `client.cidr_ranges` | ✅ |
| Workspaces & config | Workspaces | `client.workspaces` | ✅ |
| | Workspace resources | `client.workspace_resources` | ✅ |
| | Projects | `client.projects` | ✅ |
Expand All @@ -39,7 +43,10 @@ partially covered are listed at the bottom of this page.
| | Run events | `client.run_events` | ✅ |
| | Run triggers | `client.run_triggers` | ✅ |
| | Plans | `client.plans` | ✅ |
| | Plan exports | `client.plan_exports` | ✅ |
| | Applies | `client.applies` | ✅ |
| | Cost estimates | `client.cost_estimates` | ✅ |
| | Assessment results | `client.assessment_results` | ✅ |
| | Comments | `client.comments` | ✅ |
| | Query runs | `client.query_runs` | ✅ |
| | State versions | `client.state_versions` | ✅ |
Expand All @@ -61,6 +68,7 @@ partially covered are listed at the bottom of this page.
| | Registry provider platforms | `client.registry_provider_platforms` | ✅ |
| | Registry provider versions | `client.registry_provider_versions` | ✅ |
| | No-code modules | `client.no_code_modules` | ✅ |
| | Public Registry module API (registry.terraform.io) | `client.registry` | ✅ |
| Agents | Agent pools | `client.agent_pools` | ✅ |
| | Agents | `client.agents` | ✅ |
| | Agent tokens | `client.agent_tokens` | ✅ |
Expand All @@ -75,6 +83,8 @@ partially covered are listed at the bottom of this page.
| | Azure OIDC configurations | `client.azure_oidc_configurations` | ✅ |
| | GCP OIDC configurations | `client.gcp_oidc_configurations` | ✅ |
| | Vault OIDC configurations | `client.vault_oidc_configurations` | ✅ |
| | HYOK configurations | `client.hyok_configurations` | ✅ |
| Meta | IP ranges | `client.ip_ranges` | ✅ |
| Admin (TFE site-admin) | Organizations, users, runs, workspaces | `client.admin.organizations` / `.users` / `.runs` / `.workspaces` | ✅ |
| | Terraform / OPA / Sentinel versions | `client.admin.terraform_versions` / `.opa_versions` / `.sentinel_versions` | ✅ |
| | SAML / SCIM / SMTP settings + SCIM tokens | `client.admin.saml_settings` / `.scim_settings` / `.scim_tokens` / `.smtp_settings` | ✅ |
Expand All @@ -93,18 +103,11 @@ Public HCP Terraform API resources that do not yet have a pytfe client namespace

| Resource | Notes |
|---|---|
| Assessment results | Health-assessment reads. Model exists (`models/assessment_result.py`); surfaced indirectly via `workspace.current_assessment_result`. |
| Audit trails tokens | Auth tokens for the audit-trail streaming API. |
| Change requests | — |
| Cost estimates | Run cost-estimation reads. Model exists (`models/cost_estimate.py`). |
| Feature sets | Organization feature sets. |
| GPG keys | Private Registry provider signing keys. |
| Group member roles | Team member role assignments. |
| Invoices | Organization billing invoices. |
| IP allowlists | Organization IP allowlist. |
| IP ranges | `/api/meta/ip-ranges`. |
| Metrics service tokens | Metrics endpoint service tokens. |
| Plan exports | Sentinel mock / plan-export download. Model exists (`models/plan_export.py`). |
| Stack configuration summary | Builds on the existing stack_configuration resource |
| Stack deployment | Core Stacks deployment lifecycle |
| Stack deployment groups | Extends stack_deployment |
Expand All @@ -113,12 +116,15 @@ Public HCP Terraform API resources that do not yet have a pytfe client namespace
| Stack deployment steps | Granular deployment step tracking |
| Stack diagnostic | Diagnostics companion to stack_deployment |
| Stack state | State surface for deployed stacks |
| Subscriptions | Organization subscription management. |
| Team member | - |
| Terraform actions | Only the Run `invoke_action_addrs` field today; no dedicated resource. |
| User tokens | Personal (user) API tokens. |
| VCS events | — |

> Note: the TFE site-admin API (`/api/v2/admin/*`, TFE-only — not part of the
> public HCP Terraform API) **is** implemented under `client.admin` (see the
> Admin rows above).
>
> Note: **team membership** is covered by `client.teams`
> (`add_users` / `remove_users` / `list_users` and the `*_organization_memberships`
> variants), and **audit-trail tokens** are covered by `client.organization_tokens`
> via `token_type=TokenType.AUDIT_TRAILS` — neither is a separate namespace.
Loading
Loading