From 775f72fe2c8852fbf8a2f22a5714048ee1686e9c Mon Sep 17 00:00:00 2001 From: Tanya Singh Date: Mon, 23 Feb 2026 09:22:11 +0530 Subject: [PATCH 1/3] Refactor policies listing to iterator pattern --- examples/policy.py | 8 ++--- src/pytfe/models/__init__.py | 4 +-- src/pytfe/models/policy.py | 12 -------- src/pytfe/resources/policy.py | 50 ++++++++++++++----------------- src/pytfe/resources/policy_set.py | 1 + tests/units/test_policy.py | 25 ++++++---------- 6 files changed, 35 insertions(+), 65 deletions(-) diff --git a/examples/policy.py b/examples/policy.py index 74352f4..a0cdfb8 100644 --- a/examples/policy.py +++ b/examples/policy.py @@ -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, ) @@ -93,14 +92,11 @@ def main(): PolicyKind.SENTINEL if args.kind == "sentinel" else PolicyKind.OPA ) - policy_list = client.policies.list(args.org, list_options) + policy_iter = 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() 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}" ) diff --git a/src/pytfe/models/__init__.py b/src/pytfe/models/__init__.py index 8524e6b..4adcad3 100644 --- a/src/pytfe/models/__init__.py +++ b/src/pytfe/models/__init__.py @@ -96,7 +96,6 @@ from .policy import ( Policy, PolicyCreateOptions, - PolicyList, PolicyListOptions, PolicyUpdateOptions, ) @@ -609,7 +608,6 @@ # Policy "Policy", "PolicyCreateOptions", - "PolicyList", "PolicyListOptions", "PolicyUpdateOptions", # Policy Sets @@ -656,4 +654,4 @@ # Rebuild models with forward references after all models are loaded PolicyCheck.model_rebuild() -PolicyCheckList.model_rebuild() +PolicyCheckList.model_rebuild() \ No newline at end of file diff --git a/src/pytfe/models/policy.py b/src/pytfe/models/policy.py index fb182d9..079a302 100644 --- a/src/pytfe/models/policy.py +++ b/src/pytfe/models/policy.py @@ -22,23 +22,11 @@ class Policy(BaseModel): organization: Organization | None = Field(None, alias="organization") -class PolicyList(BaseModel): - model_config = ConfigDict(populate_by_name=True, validate_by_name=True) - - items: list[Policy] = Field(default_factory=list) - current_page: int | None = None - total_pages: int | None = None - prev_page: int | None = None - next_page: int | None = None - total_count: int | None = None - - class PolicyListOptions(BaseModel): model_config = ConfigDict(populate_by_name=True, validate_by_name=True) 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]") diff --git a/src/pytfe/resources/policy.py b/src/pytfe/resources/policy.py index fb30ca0..43635df 100644 --- a/src/pytfe/resources/policy.py +++ b/src/pytfe/resources/policy.py @@ -1,5 +1,8 @@ from __future__ import annotations +from typing import Any, Iterator +from urllib.parse import quote + from ..errors import ( InvalidNameError, InvalidOrgError, @@ -11,7 +14,6 @@ from ..models.policy import ( Policy, PolicyCreateOptions, - PolicyList, PolicyListOptions, PolicyUpdateOptions, ) @@ -22,35 +24,27 @@ 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/{quote(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.""" diff --git a/src/pytfe/resources/policy_set.py b/src/pytfe/resources/policy_set.py index f25e986..6526514 100644 --- a/src/pytfe/resources/policy_set.py +++ b/src/pytfe/resources/policy_set.py @@ -1,3 +1,4 @@ + from __future__ import annotations from ..errors import ( diff --git a/tests/units/test_policy.py b/tests/units/test_policy.py index af4790c..8615b9f 100644 --- a/tests/units/test_policy.py +++ b/tests/units/test_policy.py @@ -14,7 +14,6 @@ EnforcementLevel, Policy, PolicyCreateOptions, - PolicyList, PolicyUpdateOptions, ) from pytfe.models.policy_set import PolicyKind @@ -84,22 +83,16 @@ def test_list_policies_success_without_options( mock_response.json.return_value = mock_response_data mock_transport.request.return_value = mock_response - result = policies_service.list("org-123") + result_iter = policies_service.list("org-123") + items = list(result_iter) - mock_transport.request.assert_called_once_with( - "GET", "/api/v2/organizations/org-123/policies", params=None - ) + assert mock_transport.request.called - assert isinstance(result, PolicyList) - assert len(result.items) == 1 - assert result.items[0].id == "pol-123" - assert result.items[0].name == "test-policy" - assert result.items[0].kind == PolicyKind.SENTINEL - assert ( - result.items[0].enforcement_level == EnforcementLevel.ENFORCEMENT_ADVISORY - ) - assert result.current_page == 1 - assert result.total_count == 1 + assert len(items) == 1 + assert items[0].id == "pol-123" + assert items[0].name == "test-policy" + assert items[0].kind == PolicyKind.SENTINEL + assert items[0].enforcement_level == EnforcementLevel.ENFORCEMENT_ADVISORY def test_create_policy_validations(self, policies_service): """Test create method validations.""" @@ -304,4 +297,4 @@ def test_valid_create_options_success(self, policies_service): enforcement_level=EnforcementLevel.ENFORCEMENT_MANDATORY, ) result = policies_service._valid_create_options(options) - assert result is None + assert result is None \ No newline at end of file From 06b06835e6604b1c575c97fc6a5824c462670116 Mon Sep 17 00:00:00 2001 From: Tanya Singh Date: Mon, 23 Feb 2026 12:39:30 +0530 Subject: [PATCH 2/3] Remove quote from Policies.list and keep iterator refactor --- examples/policy.py | 1 - src/pytfe/models/__init__.py | 2 +- src/pytfe/models/policy.py | 11 +++++++++++ src/pytfe/resources/policy.py | 7 ++++--- src/pytfe/resources/policy_set.py | 1 - tests/units/test_policy.py | 4 ++-- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/examples/policy.py b/examples/policy.py index a0cdfb8..4629cca 100644 --- a/examples/policy.py +++ b/examples/policy.py @@ -94,7 +94,6 @@ def main(): policy_iter = client.policies.list(args.org, list_options) - existing_policy = None for policy in policy_iter: print( diff --git a/src/pytfe/models/__init__.py b/src/pytfe/models/__init__.py index 4adcad3..b2f5ae4 100644 --- a/src/pytfe/models/__init__.py +++ b/src/pytfe/models/__init__.py @@ -654,4 +654,4 @@ # Rebuild models with forward references after all models are loaded PolicyCheck.model_rebuild() -PolicyCheckList.model_rebuild() \ No newline at end of file +PolicyCheckList.model_rebuild() diff --git a/src/pytfe/models/policy.py b/src/pytfe/models/policy.py index 079a302..daf614f 100644 --- a/src/pytfe/models/policy.py +++ b/src/pytfe/models/policy.py @@ -22,6 +22,17 @@ class Policy(BaseModel): organization: Organization | None = Field(None, alias="organization") +class PolicyList(BaseModel): + model_config = ConfigDict(populate_by_name=True, validate_by_name=True) + + items: list[Policy] = Field(default_factory=list) + current_page: int | None = None + total_pages: int | None = None + prev_page: int | None = None + next_page: int | None = None + total_count: int | None = None + + class PolicyListOptions(BaseModel): model_config = ConfigDict(populate_by_name=True, validate_by_name=True) diff --git a/src/pytfe/resources/policy.py b/src/pytfe/resources/policy.py index 43635df..25153c0 100644 --- a/src/pytfe/resources/policy.py +++ b/src/pytfe/resources/policy.py @@ -1,7 +1,7 @@ from __future__ import annotations -from typing import Any, Iterator -from urllib.parse import quote +from collections.abc import Iterator +from typing import Any from ..errors import ( InvalidNameError, @@ -29,12 +29,13 @@ def list( if not valid_string_id(organization): raise InvalidOrgError() - path = f"/api/v2/organizations/{quote(organization)}/policies" + 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", {}) diff --git a/src/pytfe/resources/policy_set.py b/src/pytfe/resources/policy_set.py index 6526514..f25e986 100644 --- a/src/pytfe/resources/policy_set.py +++ b/src/pytfe/resources/policy_set.py @@ -1,4 +1,3 @@ - from __future__ import annotations from ..errors import ( diff --git a/tests/units/test_policy.py b/tests/units/test_policy.py index 8615b9f..ea335f9 100644 --- a/tests/units/test_policy.py +++ b/tests/units/test_policy.py @@ -84,7 +84,7 @@ def test_list_policies_success_without_options( mock_transport.request.return_value = mock_response result_iter = policies_service.list("org-123") - items = list(result_iter) + items = list(result_iter) assert mock_transport.request.called @@ -297,4 +297,4 @@ def test_valid_create_options_success(self, policies_service): enforcement_level=EnforcementLevel.ENFORCEMENT_MANDATORY, ) result = policies_service._valid_create_options(options) - assert result is None \ No newline at end of file + assert result is None From 3f52a317f17a51b130d610212232caffc533ee51 Mon Sep 17 00:00:00 2001 From: Tanya Singh Date: Mon, 23 Feb 2026 14:31:49 +0530 Subject: [PATCH 3/3] refactor(policy): convert list to Iterator, remove quote usage --- src/pytfe/models/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pytfe/models/__init__.py b/src/pytfe/models/__init__.py index b2f5ae4..8524e6b 100644 --- a/src/pytfe/models/__init__.py +++ b/src/pytfe/models/__init__.py @@ -96,6 +96,7 @@ from .policy import ( Policy, PolicyCreateOptions, + PolicyList, PolicyListOptions, PolicyUpdateOptions, ) @@ -608,6 +609,7 @@ # Policy "Policy", "PolicyCreateOptions", + "PolicyList", "PolicyListOptions", "PolicyUpdateOptions", # Policy Sets