Skip to content
Merged
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
3 changes: 3 additions & 0 deletions plane/api/collections/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .base import Collections

__all__ = ["Collections"]
84 changes: 84 additions & 0 deletions plane/api/collections/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from __future__ import annotations

from typing import Any

from plane.api.base_resource import BaseResource
from plane.api.collections.members import CollectionMembers
from plane.api.collections.pages import CollectionPages
from plane.models.collections import (
Collection,
CreateCollection,
UpdateCollection,
)


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

# Initialize sub-resources
self.pages = CollectionPages(config)
self.members = CollectionMembers(config)

def list(self, workspace_slug: str) -> list[Collection]:
"""List all collections in a workspace.

Args:
workspace_slug: The workspace slug identifier
"""
response = self._get(f"{workspace_slug}/collections")
return [Collection.model_validate(item) for item in response]

def create(self, workspace_slug: str, data: CreateCollection) -> Collection:
"""Create a new collection in a workspace.

Args:
workspace_slug: The workspace slug identifier
data: Collection data
"""
response = self._post(f"{workspace_slug}/collections", data.model_dump(exclude_none=True))
return Collection.model_validate(response)

def retrieve(self, workspace_slug: str, collection_id: str) -> Collection:
"""Retrieve a collection by ID.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
"""
response = self._get(f"{workspace_slug}/collections/{collection_id}")
return Collection.model_validate(response)

def update(self, workspace_slug: str, collection_id: str, data: UpdateCollection) -> Collection:
"""Update a collection's name, logo, or sort order.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
data: Fields to update (access cannot be changed after creation)
"""
response = self._patch(
f"{workspace_slug}/collections/{collection_id}",
data.model_dump(exclude_none=True),
)
return Collection.model_validate(response)

def delete(
self,
workspace_slug: str,
collection_id: str,
archive_pages: bool | None = None,
) -> None:
"""Delete a collection.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
archive_pages: Whether to archive the collection's pages instead of
leaving them unfiled. Omit to use the server's default (True).
Private collections always archive their pages regardless.
"""
params = None
if archive_pages is not None:
params = {"archive_pages": "true" if archive_pages else "false"}
return self._delete(f"{workspace_slug}/collections/{collection_id}", params=params)
Comment thread
sunder-ch marked this conversation as resolved.
72 changes: 72 additions & 0 deletions plane/api/collections/members.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from __future__ import annotations

from typing import Any

from plane.api.base_resource import BaseResource
from plane.models.collections import (
CollectionMember,
CreateCollectionMember,
UpdateCollectionMember,
)


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

def list(self, workspace_slug: str, collection_id: str) -> list[CollectionMember]:
"""List members of a (typically private) collection.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
"""
response = self._get(f"{workspace_slug}/collections/{collection_id}/members")
return [CollectionMember.model_validate(item) for item in response]

def add(
self, workspace_slug: str, collection_id: str, data: CreateCollectionMember
) -> CollectionMember:
"""Add a member to a collection.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
data: Member user id and access level
"""
response = self._post(
f"{workspace_slug}/collections/{collection_id}/members",
data.model_dump(exclude_none=True),
)
return CollectionMember.model_validate(response)

def update(
self,
workspace_slug: str,
collection_id: str,
member_id: str,
data: UpdateCollectionMember,
) -> CollectionMember:
"""Update a collection member's access level.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
member_id: UUID of the CollectionMember row (not the user id)
data: New access level
"""
response = self._patch(
f"{workspace_slug}/collections/{collection_id}/members/{member_id}",
data.model_dump(exclude_none=True),
)
return CollectionMember.model_validate(response)

def remove(self, workspace_slug: str, collection_id: str, member_id: str) -> None:
"""Remove a member from a collection.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
member_id: UUID of the CollectionMember row (not the user id)
"""
return self._delete(f"{workspace_slug}/collections/{collection_id}/members/{member_id}")
Comment thread
sunder-ch marked this conversation as resolved.
104 changes: 104 additions & 0 deletions plane/api/collections/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from __future__ import annotations

from typing import Any

from plane.api.base_resource import BaseResource
from plane.models.collections import (
AddCollectionPages,
CollectionPage,
CollectionPageSearchResult,
PaginatedCollectionPageResponse,
UpdateCollectionPage,
)
from plane.models.query_params import CollectionPageQueryParams


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

def list(
self,
workspace_slug: str,
collection_id: str,
params: CollectionPageQueryParams | None = None,
) -> PaginatedCollectionPageResponse:
"""List pages that belong to a collection.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
params: Optional search/parent_id/pagination filters
"""
query_params = params.model_dump(exclude_none=True) if params else None
response = self._get(
f"{workspace_slug}/collections/{collection_id}/pages", params=query_params
)
return PaginatedCollectionPageResponse.model_validate(response)

def add(
self, workspace_slug: str, collection_id: str, data: AddCollectionPages
) -> list[CollectionPage]:
"""Add existing page(s) to a collection.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
data: Page IDs to add, with optional sort_orders/placement
"""
response = self._post(
f"{workspace_slug}/collections/{collection_id}/pages",
data.model_dump(exclude_none=True),
)
return [CollectionPage.model_validate(item) for item in response]

def search(
self, workspace_slug: str, collection_id: str, search: str | None = None
) -> list[CollectionPageSearchResult]:
"""Search pages that are not yet in a collection, to add them.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
search: Optional case-insensitive substring filter on page name
"""
query_params = {"search": search} if search else None
response = self._get(
f"{workspace_slug}/collections/{collection_id}/pages-search",
params=query_params,
)
return [CollectionPageSearchResult.model_validate(item) for item in response]
Comment thread
sunder-ch marked this conversation as resolved.

def update(
self,
workspace_slug: str,
collection_id: str,
page_collection_id: str,
data: UpdateCollectionPage,
) -> CollectionPage:
"""Move a page to a different collection, or reorder it within the current one.

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the page's current collection
page_collection_id: UUID of the page-collection membership row
data: `collection` to move (omit/leave unset to just reorder),
and/or `sort_order`/`placement` to reorder
"""
response = self._patch(
f"{workspace_slug}/collections/{collection_id}/pages/{page_collection_id}",
data.model_dump(exclude_none=True),
)
return CollectionPage.model_validate(response)

def remove(self, workspace_slug: str, collection_id: str, page_collection_id: str) -> None:
"""Remove a page from a collection (does not delete the page itself).

Args:
workspace_slug: The workspace slug identifier
collection_id: UUID of the collection
page_collection_id: UUID of the page-collection membership row
"""
return self._delete(
f"{workspace_slug}/collections/{collection_id}/pages/{page_collection_id}"
)
2 changes: 2 additions & 0 deletions plane/client/plane_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ..api.agent_runs import AgentRuns
from ..api.collections import Collections
from ..api.customers import Customers
from ..api.cycles import Cycles
from ..api.epics import Epics
Expand Down Expand Up @@ -61,6 +62,7 @@ def __init__(
self.epics = Epics(self.config)
self.work_items = WorkItems(self.config)
self.pages = Pages(self.config)
self.collections = Collections(self.config)
self.labels = Labels(self.config)
self.states = States(self.config)
self.milestones = Milestones(self.config)
Expand Down
Loading