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
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ client.labels # Label management
client.states # State/workflow management
client.work_item_types # Work item type management
client.work_item_properties # Custom properties
client.workspace_work_item_properties # Workspace-level custom properties
client.epics # Epic management
client.intake # Intake management
client.pages # Page management
Expand Down Expand Up @@ -647,6 +648,74 @@ prop = client.work_item_properties.update(
client.work_item_properties.delete(workspace_slug, project_id, work_item_type_id, property_id)
```

#### Workspace Work Item Properties

```python
# Create a workspace-level property
from plane.models.work_item_properties import CreateWorkItemProperty

prop = client.workspace_work_item_properties.create(
workspace_slug="my-workspace",
data=CreateWorkItemProperty(
display_name="Severity",
property_type="text",
is_active=True
)
)

# List workspace-level properties
properties = client.workspace_work_item_properties.list(workspace_slug="my-workspace")

# Retrieve a workspace-level property
prop = client.workspace_work_item_properties.retrieve(workspace_slug, property_id)

# Update a workspace-level property
from plane.models.work_item_properties import UpdateWorkItemProperty

prop = client.workspace_work_item_properties.update(
workspace_slug, property_id,
data=UpdateWorkItemProperty(description="Updated description")
)

# Delete a workspace-level property
client.workspace_work_item_properties.delete(workspace_slug, property_id)

# Options sub-resource (for option property type)
from plane.models.work_item_properties import (
CreateWorkItemPropertyOption,
UpdateWorkItemPropertyOption
)

option = client.workspace_work_item_properties.options.create(
workspace_slug, property_id,
data=CreateWorkItemPropertyOption(name="Critical")
)
options = client.workspace_work_item_properties.options.list(workspace_slug, property_id)
option = client.workspace_work_item_properties.options.retrieve(workspace_slug, property_id, option_id)
option = client.workspace_work_item_properties.options.update(workspace_slug, property_id, option_id, data)
client.workspace_work_item_properties.options.delete(workspace_slug, property_id, option_id)

# Contexts sub-resource (for configuring visibility, required status per project/type)
from plane.models.work_item_property_context import (
CreateWorkItemPropertyContext,
UpdateWorkItemPropertyContext
)

context = client.workspace_work_item_properties.contexts.create(
workspace_slug, property_id,
data=CreateWorkItemPropertyContext(
name="Bug Context",
applies_to_all_projects=True,
applies_to_all_work_item_types=False,
issue_type_ids=["type-uuid"]
)
)
contexts = client.workspace_work_item_properties.contexts.list(workspace_slug, property_id)
context = client.workspace_work_item_properties.contexts.retrieve(workspace_slug, property_id, context_id)
context = client.workspace_work_item_properties.contexts.update(workspace_slug, property_id, context_id, data)
client.workspace_work_item_properties.contexts.delete(workspace_slug, property_id, context_id)
```

### Additional Resources

#### Epics
Expand Down
9 changes: 9 additions & 0 deletions plane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
WorkItemTemplate,
)
from .models.projects import ProjectFeature
from .models.work_item_property_context import (
CreateWorkItemPropertyContext,
UpdateWorkItemPropertyContext,
WorkItemPropertyContext,
)
from .models.workflows import (
AttachWorkflowStates,
CreateWorkflow,
Expand Down Expand Up @@ -112,4 +117,8 @@
"PageTemplate",
"CreatePageTemplate",
"UpdatePageTemplate",
# Work item property context models
"WorkItemPropertyContext",
"CreateWorkItemPropertyContext",
"UpdateWorkItemPropertyContext",
]
1 change: 0 additions & 1 deletion plane/api/milestones.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from ..models.milestones import (
CreateMilestone,
Milestone,
MilestoneWorkItem,
PaginatedMilestoneResponse,
PaginatedMilestoneWorkItemResponse,
UpdateMilestone,
Expand Down
16 changes: 0 additions & 16 deletions plane/api/work_items/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,22 +245,6 @@ def list_workspace(
)
return PaginatedWorkItemResponse.model_validate(response)

def list_workspace(
self,
workspace_slug: str,
params: WorkItemQueryParams | None = None,
) -> PaginatedWorkItemResponse:
"""List work items across the entire workspace.

Args:
workspace_slug: The workspace slug identifier
params: Optional query parameters for filtering, ordering, and pagination
"""
query_params = params.model_dump(exclude_none=True) if params else None
response = self._get(
f"{workspace_slug}/work-items", params=query_params
)
return PaginatedWorkItemResponse.model_validate(response)

def search(
self,
Expand Down
2 changes: 2 additions & 0 deletions plane/api/workspace_work_item_properties/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
WorkItemProperty,
)
from ..base_resource import BaseResource
from .contexts import WorkspaceWorkItemPropertyContexts
from .options import WorkspaceWorkItemPropertyOptions


Expand All @@ -17,6 +18,7 @@ def __init__(self, config: Any) -> None:

# Initialize sub-resources
self.options = WorkspaceWorkItemPropertyOptions(config)
self.contexts = WorkspaceWorkItemPropertyContexts(config)

def list(self, workspace_slug: str) -> list[WorkItemProperty]:
"""List all work item properties in the workspace.
Expand Down
101 changes: 101 additions & 0 deletions plane/api/workspace_work_item_properties/contexts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from typing import Any

from ...models.work_item_property_context import (
CreateWorkItemPropertyContext,
UpdateWorkItemPropertyContext,
WorkItemPropertyContext,
)
from ..base_resource import BaseResource


class WorkspaceWorkItemPropertyContexts(BaseResource):
"""API client for managing contexts on workspace-level work item properties."""

def __init__(self, config: Any) -> None:
super().__init__(config, "/workspaces/")

def list(
self, workspace_slug: str, property_id: str
) -> list[WorkItemPropertyContext]:
"""List all contexts for a workspace work item property.

Args:
workspace_slug: The workspace slug identifier
property_id: UUID of the work item property
"""
response = self._get(
f"{workspace_slug}/work-item-properties/{property_id}/contexts/"
)
return [WorkItemPropertyContext.model_validate(item) for item in response]

def create(
self,
workspace_slug: str,
property_id: str,
data: CreateWorkItemPropertyContext,
) -> WorkItemPropertyContext:
"""Create a new context for a workspace work item property.

Non-default contexts must specify at least one project and one issue type
(unless the corresponding applies_to_all_* flag is set).

Args:
workspace_slug: The workspace slug identifier
property_id: UUID of the work item property
data: Context data
"""
response = self._post(
f"{workspace_slug}/work-item-properties/{property_id}/contexts/",
data.model_dump(exclude_none=True),
)
return WorkItemPropertyContext.model_validate(response)

def update(
self,
workspace_slug: str,
property_id: str,
context_id: str,
data: UpdateWorkItemPropertyContext,
) -> WorkItemPropertyContext:
"""Update a context for a workspace work item property.

Args:
workspace_slug: The workspace slug identifier
property_id: UUID of the work item property
context_id: UUID of the context
data: Updated context data
"""
response = self._patch(
f"{workspace_slug}/work-item-properties/{property_id}/contexts/{context_id}/",
data.model_dump(exclude_none=True),
)
return WorkItemPropertyContext.model_validate(response)

def retrieve(
self, workspace_slug: str, property_id: str, context_id: str
) -> WorkItemPropertyContext:
"""Retrieve a single context for a workspace work item property.

Args:
workspace_slug: The workspace slug identifier
property_id: UUID of the work item property
context_id: UUID of the context
"""
response = self._get(
f"{workspace_slug}/work-item-properties/{property_id}/contexts/{context_id}/"
)
return WorkItemPropertyContext.model_validate(response)

def delete(
self, workspace_slug: str, property_id: str, context_id: str
) -> None:
"""Delete a context from a workspace work item property.

Args:
workspace_slug: The workspace slug identifier
property_id: UUID of the work item property
context_id: UUID of the context
"""
return self._delete(
f"{workspace_slug}/work-item-properties/{property_id}/contexts/{context_id}/"
)
6 changes: 4 additions & 2 deletions plane/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ def _rebuild_forward_references() -> None:
"""Rebuild Pydantic models to resolve forward references after circular imports."""
# Import both modules - now they won't have circular import issues
# because we're using TYPE_CHECKING and string forward references
from .modules import ModuleLite, ModuleWorkItem, PaginatedModuleWorkItemResponse
from .work_items import WorkItemExpand, WorkItem
from .modules import ModuleLite as ModuleLite
from .modules import ModuleWorkItem, PaginatedModuleWorkItemResponse
from .work_items import WorkItem as WorkItem
from .work_items import WorkItemExpand

# Rebuild models that have forward references to each other
WorkItemExpand.model_rebuild() # Has forward ref to ModuleLite
Expand Down
4 changes: 2 additions & 2 deletions plane/models/milestones.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

from pydantic import BaseModel, ConfigDict

from .pagination import PaginatedResponse

if TYPE_CHECKING:
from .work_items import WorkItem
pass


class Milestone(BaseModel):
Expand Down
1 change: 0 additions & 1 deletion plane/models/work_item_property_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from pydantic import BaseModel, ConfigDict


# -------------------------- LITERALS --------------------------

TextDisplayFormat = Literal["single-line", "multi-line", "readonly"]
Expand Down
85 changes: 85 additions & 0 deletions plane/models/work_item_property_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from typing import Any

from pydantic import BaseModel, ConfigDict


class WorkItemPropertyContextOption(BaseModel):
"""Option entry within a context response."""

model_config = ConfigDict(extra="allow", populate_by_name=True)

id: str
is_default: bool | None = None
sort_order: float | None = None


class WorkItemPropertyContext(BaseModel):
"""Work item property context model.

Represents a (project, work-item-type) scope override for a custom property.
See docs/contexts.md for full semantics.
"""

model_config = ConfigDict(extra="allow", populate_by_name=True)

id: str | None = None
property: str | None = None
name: str | None = None
is_default: bool | None = None
applies_to_all_projects: bool | None = None
applies_to_all_work_item_types: bool | None = None
is_required: bool | None = None
is_multi: bool | None = None
default_value: list[str] | None = None
settings: dict[str, Any] | None = None
sort_order: float | None = None
external_source: str | None = None
external_id: str | None = None
project_ids: list[str] | None = None
issue_type_ids: list[str] | None = None
options: list[WorkItemPropertyContextOption] | None = None
created_at: str | None = None
updated_at: str | None = None
deleted_at: str | None = None
created_by: str | None = None
updated_by: str | None = None
workspace: str | None = None


class CreateWorkItemPropertyContext(BaseModel):
"""Request model for creating a work item property context."""

model_config = ConfigDict(extra="ignore", populate_by_name=True)

name: str
applies_to_all_projects: bool | None = None
applies_to_all_work_item_types: bool | None = None
is_required: bool | None = None
is_multi: bool | None = None
default_value: list[str] | None = None
settings: dict[str, Any] | None = None
external_source: str | None = None
external_id: str | None = None
project_ids: list[str] | None = None
issue_type_ids: list[str] | None = None
options: list[dict[str, Any]] | None = None


class UpdateWorkItemPropertyContext(BaseModel):
"""Request model for updating a work item property context."""

model_config = ConfigDict(extra="ignore", populate_by_name=True)

name: str | None = None
applies_to_all_projects: bool | None = None
applies_to_all_work_item_types: bool | None = None
is_required: bool | None = None
is_multi: bool | None = None
default_value: list[str] | None = None
settings: dict[str, Any] | None = None
sort_order: float | None = None
external_source: str | None = None
external_id: str | None = None
project_ids: list[str] | None = None
issue_type_ids: list[str] | None = None
options: list[dict[str, Any]] | None = None
1 change: 1 addition & 0 deletions plane/models/workspaces.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pydantic import BaseModel, ConfigDict


class WorkspaceFeature(BaseModel):
"""Workspace feature model."""

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "plane-sdk"
version = "0.2.13"
version = "0.2.14"
description = "Python SDK for Plane API"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion tests/scripts/test_archive.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Test script for verifying Project and WorkItem archive methods."""
# ruff: noqa: E402
import os
import sys
import time
from datetime import datetime
from pathlib import Path

Expand Down
Loading