Skip to content

Commit 4c84386

Browse files
committed
move to iterrator
1 parent 4724532 commit 4c84386

5 files changed

Lines changed: 47 additions & 20 deletions

File tree

README.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ config = TFEConfig(
3636

3737
client = TFEClient(config)
3838

39-
orgs = client.organizations.list()
40-
for org in orgs.items:
39+
for org in client.organizations.list():
4140
print(org.name)
4241
```
4342

@@ -57,8 +56,7 @@ from pytfe import TFEClient, TFEConfig
5756

5857
# Equivalent to providing no values; falls back to env vars if set.
5958
client = TFEClient(TFEConfig())
60-
orgs = client.organizations.list()
61-
for org in orgs.items:
59+
for org in client.organizations.list():
6260
print(org.name)
6361
```
6462

@@ -69,11 +67,32 @@ from pytfe import TFEClient, TFEConfig
6967
config = TFEConfig(address="", token="")
7068
client = TFEClient(config)
7169

72-
orgs = client.organizations.list()
73-
for org in orgs.items:
70+
for org in client.organizations.list():
7471
print(org.name)
7572
```
7673

74+
## Listing resources
75+
76+
Anything named `list` or `list_*` on a resource service returns an **iterator**, not a Python `list`. Pagination is handled for you under the hood — the iterator keeps fetching pages from the API until there are no more. This mirrors the underlying HCP Terraform API, where every list endpoint is paginated (`page[number]` / `page[size]`), and keeps memory flat even when an organization has thousands of workspaces or runs.
77+
78+
You'll use it one of two ways:
79+
80+
```python
81+
# Stream — handy when you might break early or when results are large
82+
for ws in client.workspaces.list("my-org"):
83+
if ws.name.startswith("prod-"):
84+
print(ws.id, ws.name)
85+
86+
# Materialize — when you actually want a list to index, len(), or pass around
87+
workspaces = list(client.workspaces.list("my-org"))
88+
print(f"found {len(workspaces)} workspaces")
89+
```
90+
91+
A couple of things worth knowing:
92+
93+
- The iterator is **single-use**. Once you've walked it, iterating again gives you nothing. Capture it with `list(...)` first if you need to reuse the result.
94+
- Filters and page size live on the `*ListOptions` model for each resource — e.g. `WorkspaceListOptions(search="prod", page_size=50)`. Pagination still happens transparently; `page_size` only controls how big each underlying API page is.
95+
7796
## Documentation
7897

7998
- API reference and guides (SDK): **coming soon**

examples/registry_module.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,7 @@ def main():
304304
registry_name=RegistryName.PRIVATE,
305305
)
306306

307-
versions = client.registry_modules.list_versions(module_id)
308-
versions_list = list(versions) if hasattr(versions, "__iter__") else []
307+
versions_list = list(client.registry_modules.list_versions(module_id))
309308
print(f"Found {len(versions_list)} versions")
310309

311310
for i, version in enumerate(versions_list[:3], 1):

examples/team.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def main():
240240
)
241241
if args.list_members:
242242
_print_header(f"Listing members of team {args.team_id}")
243-
users = client.teams.list_users(args.team_id)
243+
users = list(client.teams.list_users(args.team_id))
244244
print(f"users ({len(users)}):")
245245
for u in users:
246246
print(f" - {u.id} {getattr(u, 'username', '')}")

src/pytfe/resources/registry_module.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,15 @@ def read_version(
217217

218218
return self._parse_registry_module_version(data)
219219

220-
def list_versions(self, module_id: RegistryModuleID) -> list[RegistryModuleVersion]: # type: ignore[valid-type]
221-
"""List all versions of a registry module."""
220+
def list_versions(
221+
self, module_id: RegistryModuleID
222+
) -> Iterator[RegistryModuleVersion]:
223+
"""List all versions of a registry module.
224+
225+
The endpoint returns all versions in a single response (no pagination),
226+
but the signature matches the rest of the SDK — wrap in ``list(...)``
227+
if you need a materialized list.
228+
"""
222229
if not self._validate_module_id(module_id):
223230
raise ValueError("Invalid module ID")
224231

@@ -241,12 +248,12 @@ def list_versions(self, module_id: RegistryModuleID) -> list[RegistryModuleVersi
241248
# Handle the case where data might be None or empty
242249
data = response_data.get("data", []) if response_data else []
243250

244-
versions = []
251+
versions: list[RegistryModuleVersion] = []
245252
for item in data:
246253
if item: # Skip None items
247254
versions.append(self._parse_registry_module_version(item))
248255

249-
return versions
256+
return iter(versions)
250257

251258
except Exception:
252259
# Fallback: If the API endpoint doesn't exist, try to get versions from the module itself
@@ -270,9 +277,9 @@ def list_versions(self, module_id: RegistryModuleID) -> list[RegistryModuleVersi
270277
}
271278
versions.append(self._parse_registry_module_version(version_data))
272279

273-
return versions
280+
return iter(versions)
274281
except Exception:
275-
return [] # Return empty list if all methods fail
282+
return iter([]) # Return empty iterator if all methods fail
276283

277284
def read_terraform_registry_module(
278285
self, module_id: RegistryModuleID, version: str

src/pytfe/resources/team.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,14 @@ def remove_organization_memberships(
190190
)
191191
return None
192192

193-
def list_users(self, team_id: str) -> list[User]:
193+
def list_users(self, team_id: str) -> Iterator[User]:
194194
"""List the users that belong to a team.
195195
196-
Implemented via ``GET /teams/{id}?include=users``.
196+
Implemented via ``GET /teams/{id}?include=users`` — the API has no
197+
dedicated paginated endpoint for team users, so all results arrive
198+
in a single response. The signature still returns an iterator to
199+
stay consistent with the other ``list_*`` methods in the SDK; wrap
200+
the result in ``list(...)`` if you need a materialized list.
197201
"""
198202
if not valid_string_id(team_id):
199203
raise InvalidTeamIDError()
@@ -204,14 +208,12 @@ def list_users(self, team_id: str) -> list[User]:
204208
)
205209
payload = r.json() or {}
206210
included = payload.get("included") or []
207-
users: list[User] = []
208211
for inc in included:
209212
if inc.get("type") != "users":
210213
continue
211214
attrs = dict(inc.get("attributes") or {})
212215
attrs["id"] = inc.get("id")
213-
users.append(User.model_validate(attrs))
214-
return users
216+
yield User.model_validate(attrs)
215217

216218
def list_organization_memberships(
217219
self,

0 commit comments

Comments
 (0)