Skip to content

Commit ac21906

Browse files
authored
Merge pull request #201 from ellieayla/filter-projects-by-tag
List projects filtered by key=value tags.
2 parents f93207e + aeb25f8 commit ac21906

3 files changed

Lines changed: 52 additions & 0 deletions

File tree

‎src/pytfe/models/project.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class ProjectListOptions(BaseModel):
4848

4949
# Optional: String used to filter results by complete project name
5050
name: str | None = None
51+
# Optional: Tags used to filter results by key=value pairs
52+
tags: dict[str, str] | None = None
5153
# Optional: Query string to search projects by names
5254
query: str | None = None
5355
# Optional: Include related resources

‎src/pytfe/resources/projects.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ def list(
160160
params["q"] = options.query
161161
if options.name:
162162
params["filter[names]"] = options.name
163+
if options.tags:
164+
for i, (tag_name, tag_value) in enumerate(options.tags.items()):
165+
params[f"filter[tagged][{i}][key]"] = _safe_str(tag_name)
166+
params[f"filter[tagged][{i}][value]"] = _safe_str(tag_value)
163167
if options.page_size:
164168
params["page[size]"] = options.page_size
165169

‎tests/units/test_project.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
Project,
1111
ProjectAddTagBindingsOptions,
1212
ProjectCreateOptions,
13+
ProjectListOptions,
1314
ProjectUpdateOptions,
1415
TagBinding,
1516
)
@@ -109,6 +110,51 @@ def test_list_projects_success(self):
109110
expected_path = f"/api/v2/organizations/{organization}/projects"
110111
self.projects_service._list.assert_called_once_with(expected_path)
111112

113+
def test_list_projects_by_tags_query_params_serialized_success(self):
114+
"""Test successful serialization of project list tag filter into query parameters."""
115+
organization = "test-org"
116+
expected_path = f"/api/v2/organizations/{organization}/projects"
117+
118+
# Mock the _list method so we can introspect the query parameters
119+
self.projects_service._list = Mock(return_value=[])
120+
121+
# Call the method under test
122+
list(
123+
self.projects_service.list(
124+
organization, options=ProjectListOptions(tags={"department": "five"})
125+
)
126+
)
127+
128+
self.projects_service._list.assert_called_once_with(
129+
expected_path,
130+
params={
131+
"filter[tagged][0][key]": "department",
132+
"filter[tagged][0][value]": "five",
133+
},
134+
)
135+
136+
def test_list_projects_by_name_query_params_serialized_success(self):
137+
"""Test successful serialization of project list name filter into query parameters."""
138+
organization = "test-org"
139+
expected_path = f"/api/v2/organizations/{organization}/projects"
140+
141+
# Mock the _list method so we can introspect the query parameters
142+
self.projects_service._list = Mock(return_value=[])
143+
144+
# Call the method under test
145+
list(
146+
self.projects_service.list(
147+
organization, options=ProjectListOptions(name="prj-456")
148+
)
149+
)
150+
151+
self.projects_service._list.assert_called_once_with(
152+
expected_path,
153+
params={
154+
"filter[names]": "prj-456",
155+
},
156+
)
157+
112158
def test_create_project_success(self):
113159
"""Test successful project creation"""
114160
organization = "test-org"

0 commit comments

Comments
 (0)