diff --git a/plane/api/cycles.py b/plane/api/cycles.py index 4ffb998..4a6d857 100644 --- a/plane/api/cycles.py +++ b/plane/api/cycles.py @@ -5,12 +5,13 @@ CreateCycle, Cycle, PaginatedArchivedCycleResponse, + PaginatedCycleLiteResponse, PaginatedCycleResponse, PaginatedCycleWorkItemResponse, TransferCycleWorkItemsRequest, UpdateCycle, ) -from ..models.query_params import WorkItemQueryParams +from ..models.query_params import LiteListQueryParams, WorkItemQueryParams from .base_resource import BaseResource from .work_items.base import prepare_work_item_params @@ -84,6 +85,31 @@ def list( response = self._get(f"{workspace_slug}/projects/{project_id}/cycles", params=params) return PaginatedCycleResponse.model_validate(response) + def list_lite( + self, + workspace_slug: str, + project_id: str, + params: LiteListQueryParams | None = None, + ) -> PaginatedCycleLiteResponse: + """List cycles as a paginated "lite" response. + + Calls the read-only ``/cycles-lite/`` endpoint, which returns the full + cycle field set minus the issue-count metric annotations (total_issues, + completed_issues, etc.), suitable for pickers and reference lookups. + Only ordering and cursor pagination are supported -- there are no field + filters. ``per_page`` defaults to and caps at 1000. + + Args: + workspace_slug: The workspace slug identifier + project_id: UUID of the project + params: Optional ordering + cursor pagination query parameters + """ + query_params = params.model_dump(exclude_none=True) if params else None + response = self._get( + f"{workspace_slug}/projects/{project_id}/cycles-lite", params=query_params + ) + return PaginatedCycleLiteResponse.model_validate(response) + def list_archived( self, workspace_slug: str, project_id: str, params: Mapping[str, Any] | None = None ) -> PaginatedArchivedCycleResponse: diff --git a/plane/api/modules.py b/plane/api/modules.py index 19751a8..463f6e0 100644 --- a/plane/api/modules.py +++ b/plane/api/modules.py @@ -5,11 +5,12 @@ CreateModule, Module, PaginatedArchivedModuleResponse, + PaginatedModuleLiteResponse, PaginatedModuleResponse, PaginatedModuleWorkItemResponse, UpdateModule, ) -from ..models.query_params import WorkItemQueryParams +from ..models.query_params import LiteListQueryParams, WorkItemQueryParams from .base_resource import BaseResource from .work_items.base import prepare_work_item_params @@ -83,6 +84,31 @@ def list( response = self._get(f"{workspace_slug}/projects/{project_id}/modules", params=params) return PaginatedModuleResponse.model_validate(response) + def list_lite( + self, + workspace_slug: str, + project_id: str, + params: LiteListQueryParams | None = None, + ) -> PaginatedModuleLiteResponse: + """List modules as a paginated "lite" response. + + Calls the read-only ``/modules-lite/`` endpoint, which returns the full + module field set minus the issue-count metric annotations (total_issues, + completed_issues, etc.), suitable for pickers and reference lookups. + Only ordering and cursor pagination are supported -- there are no field + filters. ``per_page`` defaults to and caps at 1000. + + Args: + workspace_slug: The workspace slug identifier + project_id: UUID of the project + params: Optional ordering + cursor pagination query parameters + """ + query_params = params.model_dump(exclude_none=True) if params else None + response = self._get( + f"{workspace_slug}/projects/{project_id}/modules-lite", params=query_params + ) + return PaginatedModuleLiteResponse.model_validate(response) + def list_archived( self, workspace_slug: str, project_id: str, params: Mapping[str, Any] | None = None ) -> PaginatedArchivedModuleResponse: diff --git a/plane/api/projects.py b/plane/api/projects.py index 252fb64..9dff63f 100644 --- a/plane/api/projects.py +++ b/plane/api/projects.py @@ -5,6 +5,7 @@ from ..models.projects import ( CreateProject, + PaginatedProjectLiteResponse, PaginatedProjectMemberResponse, PaginatedProjectResponse, Project, @@ -14,6 +15,7 @@ UpdateProject, ) from ..models.query_params import ( + LiteListQueryParams, MemberListQueryParams, MemberQueryParams, PaginatedQueryParams, @@ -80,6 +82,25 @@ def list( response = self._get(f"{workspace_slug}/projects", params=query_params) return PaginatedProjectResponse.model_validate(response) + def list_lite( + self, workspace_slug: str, params: LiteListQueryParams | None = None + ) -> PaginatedProjectLiteResponse: + """List projects as a paginated "lite" response. + + Calls the read-only ``/projects-lite/`` endpoint, which returns a + field-trimmed shape (id, identifier, name, cover_image, icon_prop, + emoji, description, cover_image_url) suitable for pickers and reference + lookups. Only ordering and cursor pagination are supported -- there are + no field filters. ``per_page`` defaults to and caps at 1000. + + Args: + workspace_slug: The workspace slug identifier + params: Optional ordering + cursor pagination query parameters + """ + query_params = params.model_dump(exclude_none=True) if params else None + response = self._get(f"{workspace_slug}/projects-lite", params=query_params) + return PaginatedProjectLiteResponse.model_validate(response) + def get_worklog_summary(self, workspace_slug: str, project_id: str) -> [ProjectWorklogSummary]: """Get work log summary for a project. diff --git a/plane/models/__init__.py b/plane/models/__init__.py index 5121534..c53e77a 100644 --- a/plane/models/__init__.py +++ b/plane/models/__init__.py @@ -15,6 +15,7 @@ ) from .query_params import ( BaseQueryParams, + LiteListQueryParams, MemberListQueryParams, MemberQueryParams, PaginatedQueryParams, @@ -39,6 +40,7 @@ "IntakeWorkItemStatusEnum", # query params "BaseQueryParams", + "LiteListQueryParams", "MemberListQueryParams", "MemberQueryParams", "PaginatedQueryParams", diff --git a/plane/models/cycles.py b/plane/models/cycles.py index 70ad4ac..56d71b5 100644 --- a/plane/models/cycles.py +++ b/plane/models/cycles.py @@ -141,6 +141,14 @@ class PaginatedCycleResponse(PaginatedResponse): results: list[Cycle] +class PaginatedCycleLiteResponse(PaginatedResponse): + """Paginated response for the cycles-lite endpoint.""" + + model_config = ConfigDict(extra="allow", populate_by_name=True) + + results: list[CycleLite] + + class PaginatedArchivedCycleResponse(PaginatedResponse): """Paginated response for archived cycles.""" diff --git a/plane/models/modules.py b/plane/models/modules.py index 6b4d149..d3f3481 100644 --- a/plane/models/modules.py +++ b/plane/models/modules.py @@ -132,6 +132,14 @@ class PaginatedModuleResponse(PaginatedResponse): results: list[Module] +class PaginatedModuleLiteResponse(PaginatedResponse): + """Paginated response for the modules-lite endpoint.""" + + model_config = ConfigDict(extra="allow", populate_by_name=True) + + results: list[ModuleLite] + + class PaginatedArchivedModuleResponse(PaginatedResponse): """Paginated response for archived modules.""" diff --git a/plane/models/projects.py b/plane/models/projects.py index 48ab1c1..e399fe6 100644 --- a/plane/models/projects.py +++ b/plane/models/projects.py @@ -58,6 +58,25 @@ class Project(BaseModel): default_state: str | None = None +class ProjectLite(BaseModel): + """Lite project information. + + Trimmed shape returned by the read-only ``projects-lite`` list endpoint + (``ProjectLiteSerializer``), intended for pickers and reference lookups. + """ + + model_config = ConfigDict(extra="allow", populate_by_name=True) + + id: str | None = None + identifier: str + name: str + cover_image: str | None = None + icon_prop: Any | None = None + emoji: str | None = None + description: str | None = None + cover_image_url: str | None = None + + class CreateProject(BaseModel): """Request model for creating a project.""" @@ -138,6 +157,14 @@ class PaginatedProjectResponse(PaginatedResponse): results: list[Project] +class PaginatedProjectLiteResponse(PaginatedResponse): + """Paginated response for the projects-lite endpoint.""" + + model_config = ConfigDict(extra="allow", populate_by_name=True) + + results: list[ProjectLite] + + class ProjectMember(UserLite): """Project member model. diff --git a/plane/models/query_params.py b/plane/models/query_params.py index 6ce0c24..4e5b02a 100644 --- a/plane/models/query_params.py +++ b/plane/models/query_params.py @@ -154,6 +154,33 @@ class MemberListQueryParams(MemberQueryParams): ) +class LiteListQueryParams(BaseModel): + """Query parameters for the read-only "lite" list endpoints. + + The lite list routes (``projects-lite``, ``cycles-lite``, ``modules-lite``) + support only ordering and cursor pagination -- they expose no field filters. + ``per_page`` defaults to and caps at 1000 on the server. + """ + + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + cursor: str | None = Field( + None, + description='Pagination cursor of the form "{per_page}:{page}:{offset}", ' + "e.g. 1000:0:0. Use the response's next_cursor to fetch the next page.", + ) + per_page: int | None = Field( + None, + description="Number of results per page (default and max 1000)", + ge=1, + le=1000, + ) + order_by: str | None = Field( + None, + description="Field to order results by. Prefix with '-' for descending order", + ) + + WorkItemCountGroupBy = Literal[ "state_id", "state__group", @@ -220,6 +247,7 @@ class WorkItemCountQueryParams(BaseModel): __all__ = [ "BaseQueryParams", + "LiteListQueryParams", "MemberListQueryParams", "MemberQueryParams", "PaginatedQueryParams", diff --git a/tests/unit/test_cycles.py b/tests/unit/test_cycles.py index 7f4b6bf..a02ee0e 100644 --- a/tests/unit/test_cycles.py +++ b/tests/unit/test_cycles.py @@ -5,8 +5,9 @@ import pytest from plane.client import PlaneClient -from plane.models.cycles import CreateCycle, UpdateCycle +from plane.models.cycles import CreateCycle, CycleLite, UpdateCycle from plane.models.projects import Project, ProjectFeature +from plane.models.query_params import LiteListQueryParams class TestCyclesAPI: @@ -22,6 +23,19 @@ def test_list_cycles( assert hasattr(response, "count") assert isinstance(response.results, list) + def test_list_lite( + self, client: PlaneClient, workspace_slug: str, project: Project + ) -> None: + """list_lite returns a paginated envelope of CycleLite items.""" + params = LiteListQueryParams(per_page=5, order_by="-created_at") + response = client.cycles.list_lite(workspace_slug, project.id, params=params) + assert isinstance(response.results, list) + assert isinstance(response.total_count, int) + assert isinstance(response.next_page_results, bool) + assert len(response.results) <= 5 + for cycle in response.results: + assert isinstance(cycle, CycleLite) + def test_list_archived_cycles( self, client: PlaneClient, workspace_slug: str, project: Project ) -> None: diff --git a/tests/unit/test_modules.py b/tests/unit/test_modules.py index cfeb0df..f174ce7 100644 --- a/tests/unit/test_modules.py +++ b/tests/unit/test_modules.py @@ -6,8 +6,9 @@ from plane.client import PlaneClient from plane.models.enums import ModuleStatus -from plane.models.modules import CreateModule, UpdateModule +from plane.models.modules import CreateModule, ModuleLite, UpdateModule from plane.models.projects import Project, ProjectFeature +from plane.models.query_params import LiteListQueryParams class TestModulesAPI: @@ -23,6 +24,19 @@ def test_list_modules( assert hasattr(response, "count") assert isinstance(response.results, list) + def test_list_lite( + self, client: PlaneClient, workspace_slug: str, project: Project + ) -> None: + """list_lite returns a paginated envelope of ModuleLite items.""" + params = LiteListQueryParams(per_page=5, order_by="-created_at") + response = client.modules.list_lite(workspace_slug, project.id, params=params) + assert isinstance(response.results, list) + assert isinstance(response.total_count, int) + assert isinstance(response.next_page_results, bool) + assert len(response.results) <= 5 + for module in response.results: + assert isinstance(module, ModuleLite) + def test_list_archived_modules( self, client: PlaneClient, workspace_slug: str, project: Project ) -> None: diff --git a/tests/unit/test_projects.py b/tests/unit/test_projects.py index e4d05b7..c21ce9c 100644 --- a/tests/unit/test_projects.py +++ b/tests/unit/test_projects.py @@ -5,8 +5,15 @@ import pytest from plane.client import PlaneClient -from plane.models.projects import CreateProject, Project, ProjectMember, UpdateProject +from plane.models.projects import ( + CreateProject, + Project, + ProjectLite, + ProjectMember, + UpdateProject, +) from plane.models.query_params import ( + LiteListQueryParams, MemberListQueryParams, MemberQueryParams, PaginatedQueryParams, @@ -32,6 +39,24 @@ def test_list_projects_with_params(self, client: PlaneClient, workspace_slug: st assert hasattr(response, "results") assert len(response.results) <= 5 + def test_list_lite(self, client: PlaneClient, workspace_slug: str) -> None: + """list_lite returns a paginated envelope of ProjectLite items.""" + response = client.projects.list_lite(workspace_slug) + assert isinstance(response.results, list) + assert isinstance(response.total_count, int) + assert isinstance(response.next_page_results, bool) + for project in response.results: + assert isinstance(project, ProjectLite) + assert project.name is not None + assert project.identifier is not None + + def test_list_lite_with_params(self, client: PlaneClient, workspace_slug: str) -> None: + """list_lite honors ordering + cursor pagination params.""" + params = LiteListQueryParams(per_page=5, order_by="-created_at") + response = client.projects.list_lite(workspace_slug, params=params) + assert isinstance(response.results, list) + assert len(response.results) <= 5 + class TestProjectsAPICRUD: """Test Projects API CRUD operations."""