|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import json |
| 4 | +from collections.abc import Mapping |
3 | 5 | from typing import Any |
4 | 6 |
|
5 | 7 | from ...models.query_params import RetrieveQueryParams, WorkItemQueryParams |
|
23 | 25 | from .work_logs import WorkLogs |
24 | 26 |
|
25 | 27 |
|
| 28 | +def prepare_work_item_params( |
| 29 | + params: WorkItemQueryParams | Mapping[str, Any] | None, |
| 30 | +) -> dict[str, Any] | None: |
| 31 | + """Serialize work-item query params for use as HTTP query params. |
| 32 | +
|
| 33 | + Accepts either a :class:`WorkItemQueryParams` DTO or a plain mapping, |
| 34 | + and normalises the ``filters`` field: the API expects it as a JSON |
| 35 | + string in a single ``filters=`` query parameter, but callers are free |
| 36 | + to pass it as a dict for ergonomics. Everything else is passed through |
| 37 | + as-is by ``requests``' query-string encoder. |
| 38 | + """ |
| 39 | + if params is None: |
| 40 | + return None |
| 41 | + if isinstance(params, WorkItemQueryParams): |
| 42 | + payload: dict[str, Any] = params.model_dump(exclude_none=True) |
| 43 | + else: |
| 44 | + payload = {k: v for k, v in params.items() if v is not None} |
| 45 | + if "filters" in payload and isinstance(payload["filters"], dict): |
| 46 | + payload["filters"] = json.dumps(payload["filters"], separators=(",", ":")) |
| 47 | + return payload |
| 48 | + |
| 49 | + |
26 | 50 | class WorkItems(BaseResource): |
27 | 51 | def __init__(self, config: Any) -> None: |
28 | 52 | super().__init__(config, "/workspaces/") |
@@ -157,23 +181,67 @@ def list( |
157 | 181 | project_id: UUID of the project |
158 | 182 | params: Optional query parameters for filtering, ordering, and pagination |
159 | 183 |
|
160 | | - Example: |
161 | | - from plane.models.schemas import WorkItemQueryParams |
| 184 | + Example:: |
| 185 | +
|
| 186 | + from plane.models.query_params import WorkItemQueryParams |
| 187 | +
|
| 188 | + # PQL filter (human-readable) |
| 189 | + work_items = client.work_items.list( |
| 190 | + "my-workspace", |
| 191 | + "project-id", |
| 192 | + params=WorkItemQueryParams(pql='priority = "urgent"'), |
| 193 | + ) |
162 | 194 |
|
163 | | - # List work items with filters |
| 195 | + # Structured `filters` (JSON-encoded into the query string) |
164 | 196 | work_items = client.work_items.list( |
165 | 197 | "my-workspace", |
166 | 198 | "project-id", |
167 | 199 | params=WorkItemQueryParams( |
168 | | - priority="high", |
169 | | - state="state-id", |
170 | | - expand="assignees,labels" |
171 | | - ) |
| 200 | + filters={"and": [ |
| 201 | + {"priority": "urgent"}, |
| 202 | + {"state_group__in": ["unstarted", "started"]}, |
| 203 | + ]}, |
| 204 | + ), |
| 205 | + ) |
| 206 | + """ |
| 207 | + response = self._get( |
| 208 | + f"{workspace_slug}/projects/{project_id}/work-items", |
| 209 | + params=prepare_work_item_params(params), |
| 210 | + ) |
| 211 | + return PaginatedWorkItemResponse.model_validate(response) |
| 212 | + |
| 213 | + def list_workspace( |
| 214 | + self, |
| 215 | + workspace_slug: str, |
| 216 | + params: WorkItemQueryParams | None = None, |
| 217 | + ) -> PaginatedWorkItemResponse: |
| 218 | + """List work items across an entire workspace. |
| 219 | +
|
| 220 | + Returns a paginated envelope of work items the caller can view, |
| 221 | + spanning every project in the workspace (per-project authorization |
| 222 | + and conditional grants are honored server-side). |
| 223 | +
|
| 224 | + Args: |
| 225 | + workspace_slug: The workspace slug identifier |
| 226 | + params: Optional query parameters — supports ``filters``, ``pql``, |
| 227 | + ``order_by``, ``cursor``, ``per_page``, ``fields``, ``expand``. |
| 228 | +
|
| 229 | + Example:: |
| 230 | +
|
| 231 | + from plane.models.query_params import WorkItemQueryParams |
| 232 | +
|
| 233 | + results = client.work_items.list_workspace( |
| 234 | + "my-workspace", |
| 235 | + params=WorkItemQueryParams( |
| 236 | + filters={"priority": "urgent"}, |
| 237 | + order_by="-created_at", |
| 238 | + per_page=50, |
| 239 | + ), |
172 | 240 | ) |
173 | 241 | """ |
174 | | - query_params = params.model_dump(exclude_none=True) if params else None |
175 | 242 | response = self._get( |
176 | | - f"{workspace_slug}/projects/{project_id}/work-items", params=query_params |
| 243 | + f"{workspace_slug}/work-items", |
| 244 | + params=prepare_work_item_params(params), |
177 | 245 | ) |
178 | 246 | return PaginatedWorkItemResponse.model_validate(response) |
179 | 247 |
|
@@ -264,14 +332,17 @@ def list_archived( |
264 | 332 | ) -> PaginatedWorkItemResponse: |
265 | 333 | """List archived work items in a project. |
266 | 334 |
|
| 335 | + Supports the same ``filters`` and ``pql`` query parameters as |
| 336 | + :meth:`list`. |
| 337 | +
|
267 | 338 | Args: |
268 | 339 | workspace_slug: The workspace slug identifier |
269 | 340 | project_id: UUID of the project |
270 | 341 | params: Optional query parameters for filtering, ordering, and pagination |
271 | 342 | """ |
272 | | - query_params = params.model_dump(exclude_none=True) if params else None |
273 | 343 | response = self._get( |
274 | | - f"{workspace_slug}/projects/{project_id}/archived-work-items", params=query_params |
| 344 | + f"{workspace_slug}/projects/{project_id}/archived-work-items", |
| 345 | + params=prepare_work_item_params(params), |
275 | 346 | ) |
276 | 347 | return PaginatedWorkItemResponse.model_validate(response) |
277 | 348 |
|
@@ -303,6 +374,4 @@ def unarchive(self, workspace_slug: str, project_id: str, work_item_id: str) -> |
303 | 374 | Returns: |
304 | 375 | None (HTTP 204 No Content) |
305 | 376 | """ |
306 | | - self._delete( |
307 | | - f"{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/unarchive" |
308 | | - ) |
| 377 | + self._delete(f"{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/unarchive") |
0 commit comments