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
927 changes: 927 additions & 0 deletions examples/configuration_version_complete_test.py

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions src/tfe/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,17 @@ def _raise_if_error(self, resp: httpx.Response) -> None:
errors = parse_error_payload(payload)
msg: str = f"HTTP {status}"
if errors:
maybe_detail = errors[0].get("detail")
maybe_title = errors[0].get("title")
if isinstance(maybe_detail, str) and maybe_detail:
msg = maybe_detail
elif isinstance(maybe_title, str) and maybe_title:
msg = maybe_title
# Handle case where errors might contain strings instead of dicts
first_error = errors[0]
if isinstance(first_error, dict):
maybe_detail = first_error.get("detail")
maybe_title = first_error.get("title")
if isinstance(maybe_detail, str) and maybe_detail:
msg = maybe_detail
elif isinstance(maybe_title, str) and maybe_title:
msg = maybe_title
elif isinstance(first_error, str):
msg = first_error

if status in (401, 403):
raise AuthError(msg, status=status, errors=errors)
Expand Down
2 changes: 1 addition & 1 deletion src/tfe/_jsonapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def build_headers(user_agent_suffix: str | None = None) -> dict[str, str]:
}


def parse_error_payload(payload: dict[str, Any]) -> list[dict]:
def parse_error_payload(payload: dict[str, Any]) -> list[dict | str]:
errs = payload.get("errors")
if isinstance(errs, list):
return errs
Expand Down
2 changes: 2 additions & 0 deletions src/tfe/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ._http import HTTPTransport
from .config import TFEConfig
from .resources.apply import Applies
from .resources.configuration_version import ConfigurationVersions
from .resources.organizations import Organizations
from .resources.plan import Plans
from .resources.projects import Projects
Expand Down Expand Up @@ -35,6 +36,7 @@ def __init__(self, config: TFEConfig | None = None):
proxies=cfg.proxies,
ca_bundle=cfg.ca_bundle,
)
self.configuration_versions = ConfigurationVersions(self._transport)
self.applies = Applies(self._transport)
self.plans = Plans(self._transport)
self.organizations = Organizations(self._transport)
Expand Down
4 changes: 3 additions & 1 deletion src/tfe/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def __init__(
message: str,
*,
status: int | None = None,
errors: list[dict] | None = None,
errors: list[dict | str] | None = None,
):
super().__init__(message)
self.status = status
Expand Down Expand Up @@ -84,6 +84,8 @@ class ErrStateVersionUploadNotSupported(TFEError): ...
# Workspaces
ERR_INVALID_WORKSPACE_ID = "invalid workspace ID"
ERR_INVALID_VARIABLE_ID = "invalid variable ID"
# Configuration Versions
ERR_INVALID_CONFIG_VERSION_ID = "invalid configuration version ID"
ERR_REQUIRED_KEY = "key is required"
ERR_REQUIRED_CATEGORY = "category is required"

Expand Down
25 changes: 25 additions & 0 deletions src/tfe/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@
import importlib.util
import os

# Re-export all configuration version types
from .configuration_version_types import (
ConfigurationSource,
ConfigurationStatus,
ConfigurationVersion,
ConfigurationVersionCreateOptions,
ConfigurationVersionList,
ConfigurationVersionListOptions,
ConfigurationVersionReadOptions,
ConfigurationVersionUpload,
ConfigVerIncludeOpt,
IngressAttributes,
)

# Re-export all registry module types
from .registry_module_types import (
AgentExecutionMode,
Expand Down Expand Up @@ -51,6 +65,17 @@

# Define what should be available when importing with *
__all__ = [
# Configuration version types
"ConfigurationSource",
"ConfigurationStatus",
"ConfigurationVersion",
"ConfigurationVersionCreateOptions",
"ConfigurationVersionList",
"ConfigurationVersionListOptions",
"ConfigurationVersionReadOptions",
"ConfigurationVersionUpload",
"ConfigVerIncludeOpt",
"IngressAttributes",
# Registry module types
"AgentExecutionMode",
"Commit",
Expand Down
135 changes: 135 additions & 0 deletions src/tfe/models/configuration_version_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from __future__ import annotations

from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class ConfigurationStatus(str, Enum):
"""Configuration version status enumeration."""

ARCHIVED = "archived"
ERRORED = "errored"
FETCHING = "fetching"
PENDING = "pending"
UPLOADED = "uploaded"


class ConfigurationSource(str, Enum):
"""Configuration version source enumeration."""

API = "tfe-api"
BITBUCKET = "bitbucket"
GITHUB = "github"
GITLAB = "gitlab"
ADO = "ado"
TERRAFORM = "terraform"


class ConfigVerIncludeOpt(str, Enum):
"""Configuration version include options."""

INGRESS_ATTRIBUTES = "ingress_attributes"


class IngressAttributes(BaseModel):
"""Ingress attributes model."""

branch: str | None = None
clone_url: str | None = Field(alias="clone-url", default=None)
commit_message: str | None = Field(alias="commit-message", default=None)
commit_sha: str | None = Field(alias="commit-sha", default=None)
commit_url: str | None = Field(alias="commit-url", default=None)
compare_url: str | None = Field(alias="compare-url", default=None)
identifier: str | None = None
is_pull_request: bool | None = Field(alias="is-pull-request", default=None)
on_default_branch: bool | None = Field(alias="on-default-branch", default=None)
pull_request_number: int | None = Field(alias="pull-request-number", default=None)
pull_request_url: str | None = Field(alias="pull-request-url", default=None)
pull_request_title: str | None = Field(alias="pull-request-title", default=None)
pull_request_body: str | None = Field(alias="pull-request-body", default=None)
tag: str | None = None
sender_username: str | None = Field(alias="sender-username", default=None)
sender_avatar_url: str | None = Field(alias="sender-avatar-url", default=None)
sender_html_url: str | None = Field(alias="sender-html-url", default=None)

model_config = {"populate_by_name": True}


class ConfigurationVersion(BaseModel):
"""Configuration version model."""

id: str
auto_queue_runs: bool = Field(alias="auto-queue-runs")
error: str | None = None
error_message: str | None = Field(alias="error-message", default=None)
source: ConfigurationSource
speculative: bool = False
status: ConfigurationStatus
status_timestamps: dict[str, str] | None = Field(
alias="status-timestamps", default=None
)
provisional: bool = False
upload_url: str | None = Field(alias="upload-url", default=None)

# Relations
ingress_attributes: IngressAttributes | None = Field(
alias="ingress-attributes", default=None
)

# Links
links: dict[str, Any] | None = None

model_config = {"populate_by_name": True}


class ConfigurationVersionList(BaseModel):
"""Configuration version list response."""

items: list[ConfigurationVersion]
pagination: dict[str, Any] | None = None


class ConfigurationVersionListOptions(BaseModel):
"""Options for listing configuration versions."""

# Pagination options
page_number: int | None = Field(alias="page[number]", default=None)
page_size: int | None = Field(alias="page[size]", default=None)

# Include related resources
include: list[ConfigVerIncludeOpt] | None = None

model_config = {"populate_by_name": True}


class ConfigurationVersionCreateOptions(BaseModel):
"""Options for creating a configuration version."""

# Optional: When true, runs are queued automatically when the configuration version is uploaded
auto_queue_runs: bool | None = Field(alias="auto-queue-runs", default=None)

# Optional: When true, this configuration version can only be used for planning
speculative: bool | None = None

# Optional: When true, this configuration version is provisional
provisional: bool | None = None

model_config = {"populate_by_name": True}


class ConfigurationVersionReadOptions(BaseModel):
"""Options for reading a configuration version."""

# Include related resources
include: list[ConfigVerIncludeOpt] | None = None


# Upload-related classes
class ConfigurationVersionUpload(BaseModel):
"""Configuration version upload response."""

upload_url: str = Field(alias="upload-url")

model_config = {"populate_by_name": True}
Loading
Loading