This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Plane Python SDK (plane-sdk on PyPI, v0.2.4) — a synchronous, type-annotated Python client for the Plane API. Built on requests + pydantic v2, targeting Python 3.10+.
# Install for development
pip install -e .
pip install -r requirements.txt
# Run all unit tests (requires env vars, see below)
pytest tests/unit/
# Run a specific test file or test
pytest tests/unit/test_projects.py
pytest tests/unit/test_projects.py::TestProjectsAPICRUD::test_create_project
# Integration/script tests (excluded by default via addopts)
pytest tests/scripts/ --override-ini="addopts="
# Formatting & linting
black plane tests
ruff check plane tests
ruff check --fix plane tests
# Type checking
mypy planeTests make real HTTP requests (no mocking). Set these before running:
PLANE_BASE_URL— API base URLPLANE_API_KEYorPLANE_ACCESS_TOKEN— authentication (exactly one)WORKSPACE_SLUG— test workspaceAGENT_SLUG— (optional) needed only for agent run tests
PlaneClient is the single entry point. It holds a Configuration and exposes resource objects as attributes:
PlaneClient
├── .projects → Projects(BaseResource)
├── .work_items → WorkItems(BaseResource)
│ ├── .comments
│ ├── .attachments
│ ├── .links
│ └── ...sub-resources
├── .cycles → Cycles(BaseResource)
└── ...15+ resources
plane/api/— Resource classes. Every resource extendsBaseResourcewhich handles HTTP methods, auth headers, URL building (/api/v1/...), retry viaurllib3.Retry, and response parsing.plane/models/— Pydantic v2 models. Three kinds per resource:- Response models (e.g.
Project):extra="allow"for forward compatibility with new API fields. - Request DTOs (e.g.
CreateProject,UpdateProject):extra="ignore"to be strict about inputs. - Query param models (e.g.
PaginatedQueryParams):extra="ignore".
- Response models (e.g.
plane/client/—PlaneClient(API key / access token auth) andOAuthClient(OAuth 2.0 flows).plane/errors/—PlaneError→HttpError,ConfigurationError.plane/config.py—ConfigurationandRetryConfigdataclasses.
Resources with children (work_items, customers, initiatives, teamspaces) instantiate sub-resource objects in __init__:
class WorkItems(BaseResource):
def __init__(self, config):
super().__init__(config, "/workspaces/")
self.comments = WorkItemComments(config)
self.attachments = WorkItemAttachments(config)All API endpoints end with a trailing /. URLs are built as {base_path}/api/v1{resource_base_path}/{endpoint}/.
- Line length: 100 (Black + Ruff)
- Use
X | NonenotOptional[X]; uselist[str]notList[str](Python 3.10+ builtins) - Import abstract types from
collections.abc(e.g.Mapping,Iterable) - Ruff rules: E, F, I (isort), UP (pyupgrade), B (bugbear)
- Never use "Issue" in endpoint or parameter names — always use "Work Item"
- Auth is mutually exclusive:
api_keyXORaccess_token(raisesConfigurationErrorif both/neither) - Resource methods accept Pydantic DTOs, serialize with
model_dump(exclude_none=True), and validate responses withModel.model_validate() - All resources follow CRUD verbs:
create,retrieve,update,delete,list