Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion plane/api/cycles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
28 changes: 27 additions & 1 deletion plane/api/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
21 changes: 21 additions & 0 deletions plane/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from ..models.projects import (
CreateProject,
PaginatedProjectLiteResponse,
PaginatedProjectMemberResponse,
PaginatedProjectResponse,
Project,
Expand All @@ -14,6 +15,7 @@
UpdateProject,
)
from ..models.query_params import (
LiteListQueryParams,
MemberListQueryParams,
MemberQueryParams,
PaginatedQueryParams,
Expand Down Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions plane/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
)
from .query_params import (
BaseQueryParams,
LiteListQueryParams,
MemberListQueryParams,
MemberQueryParams,
PaginatedQueryParams,
Expand All @@ -39,6 +40,7 @@
"IntakeWorkItemStatusEnum",
# query params
"BaseQueryParams",
"LiteListQueryParams",
"MemberListQueryParams",
"MemberQueryParams",
"PaginatedQueryParams",
Expand Down
8 changes: 8 additions & 0 deletions plane/models/cycles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
8 changes: 8 additions & 0 deletions plane/models/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
27 changes: 27 additions & 0 deletions plane/models/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -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.

Expand Down
28 changes: 28 additions & 0 deletions plane/models/query_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -220,6 +247,7 @@ class WorkItemCountQueryParams(BaseModel):

__all__ = [
"BaseQueryParams",
"LiteListQueryParams",
"MemberListQueryParams",
"MemberQueryParams",
"PaginatedQueryParams",
Expand Down
16 changes: 15 additions & 1 deletion tests/unit/test_cycles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
16 changes: 15 additions & 1 deletion tests/unit/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
27 changes: 26 additions & 1 deletion tests/unit/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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."""
Expand Down