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
22 changes: 10 additions & 12 deletions examples/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@
│ create_saved_view │ Create saved Explorer view │ organization: str; options: ExplorerSavedViewCreateOptions │ ExplorerSavedView │
│ read_saved_view │ Fetch one saved view by id │ organization: str; view_id: str │ ExplorerSavedView │
│ update_saved_view │ Update saved view definition │ organization: str; view_id: str; options: ExplorerSavedViewUpdateOptions │ ExplorerSavedView │
│ delete_saved_view │ Remove saved view by id │ organization: str; view_id: str │ ExplorerSavedView
│ delete_saved_view │ Remove saved view by id │ organization: str; view_id: str │ None
│ saved_view_results │ Execute saved view, stream rows │ organization: str; view_id: str │ Iterator[ExplorerRow] │
│ saved_view_results_csv │ Saved view results as CSV │ organization: str; view_id: str │ str (CSV; fallbacks)
│ saved_view_results_csv │ Saved view results as CSV │ organization: str; view_id: str │ str (CSV)
└────────────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────┴──────────────────────────────┘
delete_saved_view: if the DELETE response has no JSON body, the client returns a
minimal ExplorerSavedView with the same id.
saved_view_results_csv: tries the saved-view CSV endpoint first; on failure it may
call export_csv after read_saved_view, or build CSV from saved_view_results.
saved_view_results_csv: hits the dedicated saved-view CSV endpoint
(``/explorer/views/{id}/csv``) and returns the response text verbatim.

INPUT AND OUTPUT MODELS (how to pass; allowed values)
───────────────────────────────────────────────────────
Expand Down Expand Up @@ -95,9 +93,9 @@
list of strings (even for a single operand).

Output models (return values only; you do not instantiate these for requests)
ExplorerRow — from query(), saved_view_results(): read .id, .row_type, .attributes.
.attributes is a dict of column values; keys may be hyphenated or snake_case depending
on the API field name.
ExplorerRow — from query(), saved_view_results(): read .id, .type, .attributes.
.attributes is a dict of column values; keys are normalised to snake_case at
parse time so callers can index ``row.attributes["workspace_name"]`` directly.
ExplorerSavedView — from create_saved_view, read_saved_view, update_saved_view,
delete_saved_view, list_saved_views: .id, .name, .created_at, .query_type, .query.
str — from export_csv, saved_view_results_csv: raw CSV document body.
Expand Down Expand Up @@ -208,7 +206,7 @@ def main() -> None:
# from ExplorerQueryOptions. Here we request the workspaces view, sort by
# workspace_name descending (leading hyphen in sort), and add a single URL-style
# filter (workspace_name contains "42"). The iterator yields ExplorerRow objects
# (id, row_type, attributes dict); we only print the first five rows.
# (id, type, attributes dict); we only print the first five rows.
_banner(
"Step 1 of 7: query()",
"Workspaces view, sorted by -workspace_name, filter workspace_name contains '42'.",
Expand Down Expand Up @@ -236,7 +234,7 @@ def main() -> None:
)
print(f" Row {count}:")
print(f" id: {row.id}")
print(f" row_type: {row.row_type!r}")
print(f" type: {row.type!r}")
print(f" workspace_name: {name!r}")
print(" ---")
print(f"Summary: printed {count} row(s) (limit 5).")
Expand Down Expand Up @@ -333,7 +331,7 @@ def main() -> None:
break
print(f" Result row {i + 1}:")
print(f" id: {row.id}")
print(f" row_type: {row.row_type!r}")
print(f" type: {row.type!r}")
print(" ---")
print("Summary: saved_view_results completed (limit 3 rows printed).")
except TFEError as e:
Expand Down
33 changes: 24 additions & 9 deletions src/pytfe/models/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class ExplorerViewType(str, Enum):
class ExplorerUrlFilter(BaseModel):
"""One slot in ExplorerQueryOptions.filters → filter[i][field][op][idx] query keys."""

model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

index: int = Field(..., ge=0, description="Filter index in the query string")
field: str = Field(
..., min_length=1, description="Explorer field name in snake_case"
Expand All @@ -43,7 +45,7 @@ class ExplorerUrlFilter(BaseModel):
class ExplorerQueryOptions(BaseModel):
"""GET /organizations/{org}/explorer (and export/csv) query string as structured fields."""

model_config = ConfigDict(populate_by_name=True)
model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

view_type: ExplorerViewType = Field(..., alias="type")
sort: str | None = Field(
Expand All @@ -63,18 +65,25 @@ class ExplorerQueryOptions(BaseModel):


class ExplorerRow(BaseModel):
"""One Explorer result row: json:api id/type plus flat attributes for the view."""
"""One Explorer result row: JSON:API id/type plus flat attributes for the view.

Attribute keys are normalised to snake_case at parse time so callers can
index ``row.attributes["workspace_name"]`` rather than juggling hyphen vs
snake variants.
"""

model_config = ConfigDict(populate_by_name=True)
model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

id: str
row_type: str = Field(..., alias="type")
type: str
Comment thread
iam404 marked this conversation as resolved.
attributes: dict[str, Any] = Field(default_factory=dict)


class ExplorerSavedQueryFilter(BaseModel):
"""One saved-view filter row (list-valued `value` matches create/update JSON)."""

model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

field: str = Field(..., min_length=1)
operator: str = Field(..., min_length=1)
value: list[str] = Field(default_factory=list)
Expand All @@ -83,7 +92,7 @@ class ExplorerSavedQueryFilter(BaseModel):
class ExplorerSavedQuery(BaseModel):
"""Nested query on a saved view: view type, filters, optional fields and sort lists."""

model_config = ConfigDict(populate_by_name=True)
model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

query_type: ExplorerViewType = Field(..., alias="type")
filter: list[ExplorerSavedQueryFilter] | None = None
Expand All @@ -92,9 +101,15 @@ class ExplorerSavedQuery(BaseModel):


class ExplorerSavedView(BaseModel):
"""Saved view resource: metadata plus embedded query (response and some request paths)."""
"""Saved view resource: metadata plus embedded query.

The HCP Terraform API returns ``query-type`` at the view level *and*
``type`` nested inside ``query``. They are always equal in practice; the
SDK surfaces both because they appear in different positions in the
request/response payload.
"""

model_config = ConfigDict(populate_by_name=True)
model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

id: str
name: str
Expand All @@ -106,7 +121,7 @@ class ExplorerSavedView(BaseModel):
class ExplorerSavedViewCreateOptions(BaseModel):
"""POST .../explorer/views attributes: display name, top-level query-type, nested query."""

model_config = ConfigDict(populate_by_name=True)
model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

name: str = Field(..., min_length=1)
query_type: ExplorerViewType = Field(..., alias="query-type")
Expand All @@ -116,7 +131,7 @@ class ExplorerSavedViewCreateOptions(BaseModel):
class ExplorerSavedViewUpdateOptions(BaseModel):
"""PATCH .../explorer/views/{id} attributes: name and full replacement query."""

model_config = ConfigDict(populate_by_name=True)
model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

name: str = Field(..., min_length=1)
query: ExplorerSavedQuery
Loading
Loading