Skip to content

Commit 3ff9aff

Browse files
authored
Merge pull request #101 from NimishaShrivastava-dev/refactor-sshkey-list-iterator
Ssh-iterator pattern added
2 parents d96638c + 70a8ebc commit 3ff9aff

6 files changed

Lines changed: 86 additions & 43 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,4 @@ protos.bin
137137

138138
# Local RSA keys
139139
.local/*
140+

examples/ssh_keys.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ def main():
9999
try:
100100
# 1. List existing SSH keys
101101
print("\n1. Listing SSH keys...")
102-
ssh_keys = client.ssh_keys.list(TFE_ORG)
103-
print(f"Found {len(ssh_keys.items)} SSH keys:")
104-
for key in ssh_keys.items:
102+
ssh_keys_count = 0
103+
for key in client.ssh_keys.list(TFE_ORG):
105104
print(f"- ID: {key.id}, Name: {key.name}")
105+
ssh_keys_count += 1
106+
print(f"Found {ssh_keys_count} SSH keys")
106107

107108
# 2. Create a new SSH key
108109
print("\n2. Creating a new SSH key...")
@@ -132,16 +133,17 @@ def main():
132133

133134
# 6. Verify deletion by listing again
134135
print("\n6. Verifying deletion...")
135-
ssh_keys_after = client.ssh_keys.list(TFE_ORG)
136-
print(f"SSH keys after deletion: {len(ssh_keys_after.items)}")
136+
ssh_keys_after_count = sum(1 for _ in client.ssh_keys.list(TFE_ORG))
137+
print(f"SSH keys after deletion: {ssh_keys_after_count}")
137138

138-
# 7. Demonstrate pagination with options
139-
print("\n7. Demonstrating pagination options...")
139+
# 7. Demonstrate iterator with pagination options
140+
print("\n7. Demonstrating iterator with pagination options...")
140141
list_options = SSHKeyListOptions(page_size=5, page_number=1)
141-
paginated_keys = client.ssh_keys.list(TFE_ORG, list_options)
142-
print(f"Page 1 with page size 5: {len(paginated_keys.items)} keys")
143-
print(f"Total pages: {paginated_keys.total_pages}")
144-
print(f"Total count: {paginated_keys.total_count}")
142+
paginated_count = 0
143+
for key in client.ssh_keys.list(TFE_ORG, list_options):
144+
paginated_count += 1
145+
print(f" - {key.name}")
146+
print(f"Listed {paginated_count} keys with pagination options")
145147

146148
print("\n SSH Keys API example completed successfully!")
147149

src/pytfe/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def __init__(self, config: TFEConfig | None = None):
6969
self.plans = Plans(self._transport)
7070
self.organizations = Organizations(self._transport)
7171
self.organization_memberships = OrganizationMemberships(self._transport)
72+
7273
self.projects = Projects(self._transport)
7374
self.variables = Variables(self._transport)
7475
self.variable_sets = VariableSets(self._transport)

src/pytfe/models/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,6 @@
287287
from .ssh_key import (
288288
SSHKey,
289289
SSHKeyCreateOptions,
290-
SSHKeyList,
291290
SSHKeyListOptions,
292291
SSHKeyUpdateOptions,
293292
)
@@ -395,7 +394,6 @@
395394
# SSH keys
396395
"SSHKey",
397396
"SSHKeyCreateOptions",
398-
"SSHKeyList",
399397
"SSHKeyListOptions",
400398
"SSHKeyUpdateOptions",
401399
# Reserved tag keys

src/pytfe/resources/ssh_keys.py

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from collections.abc import Iterator
34
from typing import Any
45

56
from ..errors import (
@@ -9,7 +10,6 @@
910
from ..models.ssh_key import (
1011
SSHKey,
1112
SSHKeyCreateOptions,
12-
SSHKeyList,
1313
SSHKeyListOptions,
1414
SSHKeyUpdateOptions,
1515
)
@@ -22,37 +22,17 @@ class SSHKeys(_Service):
2222

2323
def list(
2424
self, organization: str, options: SSHKeyListOptions | None = None
25-
) -> SSHKeyList:
25+
) -> Iterator[SSHKey]:
2626
"""List SSH keys for the given organization."""
2727
if not valid_string_id(organization):
2828
raise InvalidOrgError()
2929

30-
params = (
31-
options.model_dump(by_alias=True, exclude_none=True) if options else None
32-
)
33-
34-
r = self.t.request(
35-
"GET",
36-
f"/api/v2/organizations/{organization}/ssh-keys",
37-
params=params,
38-
)
39-
40-
jd = r.json()
41-
items = []
42-
meta = jd.get("meta", {})
43-
pagination = meta.get("pagination", {})
44-
45-
for d in jd.get("data", []):
46-
items.append(self._parse_ssh_key(d))
47-
48-
return SSHKeyList(
49-
items=items,
50-
current_page=pagination.get("current-page"),
51-
total_pages=pagination.get("total-pages"),
52-
prev_page=pagination.get("prev-page"),
53-
next_page=pagination.get("next-page"),
54-
total_count=pagination.get("total-count"),
55-
)
30+
params = options.model_dump(by_alias=True, exclude_none=True) if options else {}
31+
path = f"/api/v2/organizations/{organization}/ssh-keys"
32+
for item in self._list(path, params=params):
33+
attrs = item.get("attributes", {})
34+
attrs["id"] = item.get("id")
35+
yield SSHKey.model_validate(attrs)
5636

5737
def create(self, organization: str, options: SSHKeyCreateOptions) -> SSHKey:
5838
"""Create a new SSH key for the given organization."""

tests/units/test_ssh_keys.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Test the SSH Keys functionality."""
22

3-
from unittest.mock import Mock
3+
from unittest.mock import Mock, patch
44

55
import pytest
66

@@ -10,7 +10,9 @@
1010
InvalidSSHKeyIDError,
1111
)
1212
from pytfe.models.ssh_key import (
13+
SSHKey,
1314
SSHKeyCreateOptions,
15+
SSHKeyListOptions,
1416
SSHKeyUpdateOptions,
1517
)
1618
from pytfe.resources.ssh_keys import SSHKeys
@@ -51,7 +53,8 @@ def ssh_keys_service(self):
5153
def test_list_ssh_keys_invalid_org(self, ssh_keys_service):
5254
"""Test listing SSH keys with invalid organization."""
5355
with pytest.raises(InvalidOrgError):
54-
ssh_keys_service.list("")
56+
# Need to consume the iterator to trigger the error
57+
list(ssh_keys_service.list(""))
5558

5659
def test_create_ssh_key_invalid_org(self, ssh_keys_service):
5760
"""Test creating SSH key with invalid organization."""
@@ -74,3 +77,61 @@ def test_delete_ssh_key_invalid_id(self, ssh_keys_service):
7477
"""Test deleting SSH key with invalid ID."""
7578
with pytest.raises(InvalidSSHKeyIDError):
7679
ssh_keys_service.delete("")
80+
81+
def test_list_ssh_keys_success(self, ssh_keys_service):
82+
"""Test successful list operation with iterator."""
83+
mock_list_data = [
84+
{
85+
"id": "sshkey-123",
86+
"attributes": {
87+
"name": "Test SSH Key 1",
88+
},
89+
},
90+
{
91+
"id": "sshkey-456",
92+
"attributes": {
93+
"name": "Test SSH Key 2",
94+
},
95+
},
96+
]
97+
98+
with patch.object(ssh_keys_service, "_list") as mock_list:
99+
mock_list.return_value = mock_list_data
100+
101+
# Test with options
102+
options = SSHKeyListOptions(page_number=1, page_size=5)
103+
result = list(ssh_keys_service.list("test-org", options))
104+
105+
# Verify _list was called with correct path
106+
assert mock_list.call_count == 1
107+
call_args = mock_list.call_args
108+
assert call_args[0][0] == "/api/v2/organizations/test-org/ssh-keys"
109+
110+
# Verify params structure includes pagination and options
111+
params = call_args[1]["params"]
112+
assert "page[number]" in params
113+
assert "page[size]" in params
114+
115+
# Verify result structure - iterator yields SSHKey objects
116+
assert len(result) == 2
117+
118+
# Verify SSH key objects were created correctly from response data
119+
key1 = result[0]
120+
assert isinstance(key1, SSHKey)
121+
assert key1.id == "sshkey-123"
122+
assert key1.name == "Test SSH Key 1"
123+
124+
key2 = result[1]
125+
assert isinstance(key2, SSHKey)
126+
assert key2.id == "sshkey-456"
127+
assert key2.name == "Test SSH Key 2"
128+
129+
def test_list_ssh_keys_empty(self, ssh_keys_service):
130+
"""Test list operation returning empty results."""
131+
with patch.object(ssh_keys_service, "_list") as mock_list:
132+
mock_list.return_value = []
133+
134+
result = list(ssh_keys_service.list("test-org"))
135+
136+
assert len(result) == 0
137+
mock_list.assert_called_once()

0 commit comments

Comments
 (0)