Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f6a8f4b
iterator-pattern logic is added, examples file updated and tests are …
NimishaShrivastava-dev Feb 18, 2026
775f72f
Refactor policies listing to iterator pattern
TanyaSingh369-svg Feb 23, 2026
825fd86
RunList models are added
NimishaShrivastava-dev Feb 23, 2026
06b0683
Remove quote from Policies.list and keep iterator refactor
TanyaSingh369-svg Feb 23, 2026
2fea9e7
tests done
NimishaShrivastava-dev Feb 23, 2026
3f52a31
refactor(policy): convert list to Iterator, remove quote usage
TanyaSingh369-svg Feb 23, 2026
a37463b
Refactored Run_event.py to iterator pattern
NimishaShrivastava-dev Feb 25, 2026
763d8a5
refactor(policy-set): convert list to Iterator pattern
TanyaSingh369-svg Feb 25, 2026
65ebe3e
Merge pull request #91 from NimishaShrivastava-dev/Refactoring/iterat…
isivaselvan Feb 27, 2026
5a8ce85
Merge pull request #92 from TanyaSingh369-svg/refactoring/iterator-pa…
isivaselvan Feb 27, 2026
b3a8acb
Merge pull request #97 from NimishaShrivastava-dev/refactoring-iterat…
isivaselvan Mar 5, 2026
c88c85d
Update policy_set example for iterator output
TanyaSingh369-svg Mar 5, 2026
4f1e276
Add unit tests for policy_set
TanyaSingh369-svg Mar 5, 2026
a088311
Merge pull request #95 from TanyaSingh369-svg/refactor/iterator-patte…
isivaselvan Mar 5, 2026
64b884a
Added User resource and integrate with client
TanyaSingh369-svg Mar 12, 2026
a756e84
Add Users.read endpoint implementation and unit tests
TanyaSingh369-svg Mar 16, 2026
1c61edc
Apply ruff formatting fixes
TanyaSingh369-svg Mar 16, 2026
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
9 changes: 2 additions & 7 deletions examples/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ def main():
_print_header(f"Listing policies in organization: {args.org}")

list_options = PolicyListOptions(
page_number=args.page,
page_size=args.page_size,
)

Expand All @@ -93,14 +92,10 @@ def main():
PolicyKind.SENTINEL if args.kind == "sentinel" else PolicyKind.OPA
)

policy_list = client.policies.list(args.org, list_options)

print(f"Total policies: {policy_list.total_count}")
print(f"Page {policy_list.current_page} of {policy_list.total_pages}")
print()
policy_iter = client.policies.list(args.org, list_options)

existing_policy = None
for policy in policy_list.items:
for policy in policy_iter:
print(
f"- {policy.id} | {policy.name} | kind={policy.kind} | enforcement={policy.enforcement_level}"
)
Expand Down
9 changes: 4 additions & 5 deletions examples/policy_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,15 @@ def main():
)

try:
ps_list = client.policy_sets.list(args.org, list_options)
ps_list = list(client.policy_sets.list(args.org, list_options))

print(f"Total policy sets: {ps_list.total_count}")
print(f"Page {ps_list.current_page} of {ps_list.total_pages}")
print(f"Total policy sets: {len(ps_list)}")
print()

if not ps_list.items:
if not ps_list:
print("No policy sets found for this organization.")
else:
for ps in ps_list.items:
for ps in ps_list:
print(
f"- ID: {ps.id} | Name: {ps.name} | Kind: {ps.kind} | Global: {ps.Global}"
)
Expand Down
28 changes: 15 additions & 13 deletions examples/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,31 +67,31 @@ def main():
)

try:
run_list = client.runs.list(args.workspace_id, options)
print("running inside run list")
run_list = list(client.runs.list(args.workspace_id, options))
except Exception as e:
print(f"Error listing runs: {e}")
if args.organization:
print("Trying organization-level listing instead...")
else:
return

if "run_list" in locals():
print(f"Total runs: {run_list.total_count}")
print(f"Page {run_list.current_page} of {run_list.total_pages}")
if "run_list" in locals() and run_list:
print(f"Total runs fetched: {len(run_list)}")
print()

for run in run_list.items:
for run in run_list:
print(f"- {run.id} | status={run.status} | created={run.created_at}")
print(f"message: {run.message}")
print(f"has_changes: {run.has_changes} | is_destroy: {run.is_destroy}")

if not run_list.items:
if not run_list:
print("No runs found.")
else:
# 2) Read the most recent run with details
_print_header("Reading most recent run details")

latest_run = run_list.items[0]
latest_run = run_list[0]
read_options = RunReadOptions(
include=[
RunIncludeOpt.RUN_PLAN,
Expand Down Expand Up @@ -188,10 +188,12 @@ def main():
status="applied,planned,errored",
)

org_runs = client.runs.list_for_organization(args.organization, org_options)
print(f"Found {len(org_runs.items)} runs across organization")
org_runs = list(
client.runs.list_for_organization(args.organization, org_options)
)
print(f"Found {len(org_runs)} runs across organization")

for run in org_runs.items[:3]: # Show first 3
for run in org_runs[:3]: # Show first 3
print(f"- {run.id} | status={run.status}")
if run.workspace:
print(f"workspace: {run.workspace.name}")
Expand All @@ -204,15 +206,15 @@ def main():
_print_header("Run Actions Demo (Safe Mode)")

# Get runs first if not already available
if "run_list" not in locals() or not run_list.items:
if "run_list" not in locals() or not run_list:
try:
options = RunListOptions(page_size=1)
run_list = client.runs.list(args.workspace_id, options)
run_list = list(client.runs.list(args.workspace_id, options))
except Exception as e:
print(f"Error getting runs for actions demo: {e}")
return

if not run_list.items:
if not run_list:
print("No runs available for actions demo")
return

Expand Down
25 changes: 10 additions & 15 deletions examples/run_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,23 +94,19 @@ def main():
options = RunEventListOptions(include=include_opts if include_opts else None)

try:
event_list = client.run_events.list(args.run_id, options)

print(f"Total run events: {event_list.total_count or 'N/A'}")
if event_list.current_page and event_list.total_pages:
print(f"Page {event_list.current_page} of {event_list.total_pages}")
print()
event_count = 0
for event in client.run_events.list(args.run_id, options):
print(f"Event ID: {event.id}")
print(f"Action: {event.action or 'N/A'}")
print(f"Description: {event.description or 'N/A'}")
print(f"Created At: {event.created_at or 'N/A'}")
print()
event_count += 1

if not event_list.items:
if event_count == 0:
print("No run events found for this run.")
else:
for event in event_list.items:
print(f"Event ID: {event.id}")
print(f"Action: {event.action or 'N/A'}")
print(f"Description: {event.description or 'N/A'}")
print(f"Created At: {event.created_at or 'N/A'}")

print()
print(f"Total run events listed: {event_count}")

except Exception as e:
print(f"Error listing run events: {e}")
Expand Down Expand Up @@ -139,7 +135,6 @@ def main():
# 3) Summary
_print_header("Summary")
print(f"Successfully demonstrated run events for run: {args.run_id}")
print(f"Total events found: {event_list.total_count or 'N/A'}")
if args.event_id:
print(f"Successfully read specific event: {args.event_id}")
return 0
Expand Down
2 changes: 2 additions & 0 deletions src/pytfe/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from .resources.ssh_keys import SSHKeys
from .resources.state_version_outputs import StateVersionOutputs
from .resources.state_versions import StateVersions
from .resources.user import Users
from .resources.variable import Variables
from .resources.variable_sets import VariableSets, VariableSetVariables
from .resources.workspace_resources import WorkspaceResourcesService
Expand Down Expand Up @@ -69,6 +70,7 @@ def __init__(self, config: TFEConfig | None = None):
self.plans = Plans(self._transport)
self.organizations = Organizations(self._transport)
self.organization_memberships = OrganizationMemberships(self._transport)
self.users = Users(self._transport)
self.projects = Projects(self._transport)
self.variables = Variables(self._transport)
self.variable_sets = VariableSets(self._transport)
Expand Down
1 change: 0 additions & 1 deletion src/pytfe/models/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class PolicyListOptions(BaseModel):

search: str | None = Field(None, alias="search[name]")
kind: PolicyKind | None = Field(None, alias="filter[kind]")
page_number: int | None = Field(None, alias="page[number]")
page_size: int | None = Field(None, alias="page[size]")


Expand Down
1 change: 1 addition & 0 deletions src/pytfe/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class User(BaseModel):
model_config = ConfigDict(populate_by_name=True, validate_by_name=True)

id: str = Field(..., alias="id")
auth_method: str = Field(default="", alias="auth-method")
avatar_url: str = Field(default="", alias="avatar-url")
email: str = Field(default="", alias="email")
is_service_account: bool = Field(default=False, alias="is-service-account")
Expand Down
51 changes: 23 additions & 28 deletions src/pytfe/resources/policy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

from collections.abc import Iterator
from typing import Any

from ..errors import (
InvalidNameError,
InvalidOrgError,
Expand All @@ -11,7 +14,6 @@
from ..models.policy import (
Policy,
PolicyCreateOptions,
PolicyList,
PolicyListOptions,
PolicyUpdateOptions,
)
Expand All @@ -22,35 +24,28 @@
class Policies(_Service):
def list(
self, organization: str, options: PolicyListOptions | None = None
) -> PolicyList:
"""List all the policies of the given organization."""
) -> Iterator[Policy]:
"""Iterate all the policies of the given organization."""
if not valid_string_id(organization):
raise InvalidOrgError()
params = (
options.model_dump(by_alias=True, exclude_none=True) if options else None
)
r = self.t.request(
"GET",
f"/api/v2/organizations/{organization}/policies",
params=params,
)
jd = r.json()
items = []
meta = jd.get("meta", {})
pagination = meta.get("pagination", {})
for d in jd.get("data", []):
attrs = d.get("attributes", {})
attrs["id"] = d.get("id")
attrs["organization"] = d.get("relationships", {}).get("organization", {})
items.append(Policy.model_validate(attrs))
return PolicyList(
items=items,
current_page=pagination.get("current-page"),
total_pages=pagination.get("total-pages"),
prev_page=pagination.get("prev-page"),
next_page=pagination.get("next-page"),
total_count=pagination.get("total-count"),
)

path = f"/api/v2/organizations/{organization}/policies"
params: dict[str, Any] = {}

if options:
if getattr(options, "page_size", None):
params["page[size]"] = str(options.page_size)

def _gen() -> Iterator[Policy]:
for item in self._list(path, params=params):
attrs = item.get("attributes", {})
attrs["id"] = item.get("id")
attrs["organization"] = item.get("relationships", {}).get(
"organization", {}
)
yield Policy.model_validate(attrs)

return _gen()

def create(self, organization: str, options: PolicyCreateOptions) -> Policy:
"""Create a new policy in the given organization."""
Expand Down
73 changes: 34 additions & 39 deletions src/pytfe/resources/policy_set.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from collections.abc import Iterator

from ..errors import (
InvalidNameError,
InvalidOrgError,
Expand All @@ -17,7 +19,6 @@
PolicySetAddWorkspaceExclusionsOptions,
PolicySetAddWorkspacesOptions,
PolicySetCreateOptions,
PolicySetList,
PolicySetListOptions,
PolicySetReadOptions,
PolicySetRemovePoliciesOptions,
Expand All @@ -38,47 +39,41 @@ class PolicySets(_Service):

def list(
self, organization: str, options: PolicySetListOptions | None = None
) -> PolicySetList:
"""List all the policy sets of the given organization."""
) -> Iterator[PolicySet]:
"""Iterate all the policy sets of the given organization."""
if not valid_string_id(organization):
raise InvalidOrgError()

# Build params from options but do not pass page[number] — let _list handle pagination.
params = options.model_dump(by_alias=True, exclude_none=True) if options else {}
r = self.t.request(
"GET",
f"/api/v2/organizations/{organization}/policy-sets",
params=params,
)
jd = r.json()
items = []
meta = jd.get("meta", {})
pagination = meta.get("pagination", {})
for d in jd.get("data", []):
attrs = d.get("attributes", {})
attrs["id"] = d.get("id")
attrs["organization"] = d.get("relationships", {}).get("organization", {})
attrs["workspace_exclusions"] = (
d.get("relationships", {})
.get("workspace-exclusions", {})
.get("data", [])
)
attrs["workspaces"] = (
d.get("relationships", {}).get("workspaces", {}).get("data", [])
)
attrs["projects"] = (
d.get("relationships", {}).get("projects", {}).get("data", [])
)
attrs["policies"] = (
d.get("relationships", {}).get("policies", {}).get("data", [])
)
items.append(PolicySet.model_validate(attrs))
return PolicySetList(
items=items,
current_page=pagination.get("current-page"),
total_pages=pagination.get("total-pages"),
prev_page=pagination.get("prev-page"),
next_page=pagination.get("next-page"),
total_count=pagination.get("total-count"),
)
params.pop("page[number]", None)

path = f"/api/v2/organizations/{organization}/policy-sets"

def _gen() -> Iterator[PolicySet]:
for d in self._list(path, params=params):
attrs = d.get("attributes", {})
attrs["id"] = d.get("id")
attrs["organization"] = d.get("relationships", {}).get(
"organization", {}
)
attrs["workspace_exclusions"] = (
d.get("relationships", {})
.get("workspace-exclusions", {})
.get("data", [])
)
attrs["workspaces"] = (
d.get("relationships", {}).get("workspaces", {}).get("data", [])
)
attrs["projects"] = (
d.get("relationships", {}).get("projects", {}).get("data", [])
)
attrs["policies"] = (
d.get("relationships", {}).get("policies", {}).get("data", [])
)
yield PolicySet.model_validate(attrs)

return _gen()

def create(self, organization: str, options: PolicySetCreateOptions) -> PolicySet:
"""Create a new policy set in the given organization."""
Expand Down
Loading
Loading