Skip to content

makeplane/plane-python-sdk

Repository files navigation

Plane Python SDK

A comprehensive, type-annotated Python SDK for interacting with the Plane API. This SDK provides a clean, modern interface for all Plane API operations, following Python best practices with full type safety and Pydantic v2 integration.

Features

  • 🚀 Type-Safe: Full type annotations with Pydantic v2 models
  • 🔧 Modern Python: Built for Python 3.10+ with modern typing idioms
  • 🛡️ Error Handling: Comprehensive error types and exception handling
  • 🔄 Retry Logic: Built-in retry mechanism with configurable backoff
  • 📦 Resource-Based: Clean resource-based API organization
  • 🎯 Comprehensive: Support for all major Plane API endpoints
  • Synchronous: Uses requests with connection pooling

Breaking Changes (v0.2.0 vs v0.1.x)

This SDK (v0.2.0) replaces the v0.1.x OpenAPI-generated client and introduces intentional breaking changes for a cleaner, type-safe developer experience.

  • Authentication and client

    • New PlaneClient(base_url, api_key | access_token) replaces OpenAPI Configuration/ApiClient usage
    • Exactly one of api_key or access_token is required; providing both raises a ConfigurationError
    • base_url should NOT include /api/v1; the SDK appends /api/v1 automatically
  • HTTP headers

    • API key header standardized to X-Api-Key; access tokens use Authorization: Bearer <token>
  • Resource paths and naming

    • All paths use work-items instead of v0.1.x issues
    • Sub-resources are grouped under client.work_items.<subresource>
  • Method names

    • Methods are standardized across resources: list, create, retrieve, update, delete
    • Replaces verbose, OpenAPI-generated method names
  • Models and DTOs

    • Uses Pydantic v2 with: response models extra="allow"; Create*/Update* DTOs extra="ignore"
    • Separate DTOs for create/update: Create* and Update*
    • Field naming is normalized
  • Pagination shape

    • Paginated responses now expose: results, total_count, next_page_number, prev_page_number
    • This replaces v0.1.x shapes that included different field names
  • Query parameters

    • Typed query params via models like WorkItemQueryParams and RetrieveQueryParams
    • Common fields include per_page, page, order_by, expand
  • Errors

    • Raises HttpError(message, status_code, response) on non-2xx responses
    • Configuration validation errors raise ConfigurationError
  • Imports and organization

    • Import models from plane.models.<resource>
    • No OpenAPI *Api classes; use resource objects from PlaneClient
  • Trailing slashes

    • All endpoints include trailing / by design; the SDK enforces this consistently

Migration example (v0.1.x → v0.2.0):

# v0.1.x (OpenAPI-generated)
from plane import Configuration, ApiClient
from plane.apis import WorkItemsApi

cfg = Configuration(host="https://api.plane.so")
cfg.api_key['X-API-Key'] = "<api-key>"
api = WorkItemsApi(ApiClient(cfg))
api.list_work_items(slug, project_id=project_id)

# v0.2.0 (this SDK)
from plane.client import PlaneClient
from plane.models.query_params import WorkItemQueryParams

client = PlaneClient(base_url="https://api.plane.so", api_key="<api-key>")
client.work_items.list(
    workspace_slug=slug,
    project_id=project_id,
    params=WorkItemQueryParams(per_page=20, order_by="-created_at")
)

Installation

pip install plane-sdk

Quick Start

Authentication

⚠️ Required: You must provide exactly one of api_key or access_token for authentication.

import os
from plane.client import PlaneClient
from plane.errors import ConfigurationError

# Using API key
client = PlaneClient(
    base_url="https://api.plane.so",
    api_key=os.environ["PLANE_API_KEY"]
)

# OR using access token (not both)
client = PlaneClient(
    base_url="https://api.plane.so",
    access_token=os.environ["PLANE_ACCESS_TOKEN"]
)

# Raises ConfigurationError if neither or both are provided

OAuth Authentication

The SDK also supports OAuth 2.0 authentication for more advanced use cases:

from plane import OAuthClient

# Initialize OAuth client
oauth_client = OAuthClient(
    base_url="https://api.plane.so",
    client_id="your_client_id",
    client_secret="your_client_secret"
)

# Authorization Code Flow (for web applications)
# Step 1: Get authorization URL
auth_url = oauth_client.get_authorization_url(
    redirect_uri="https://your-app.com/callback",
    scope="read write",
    state="random_state_string"
)

# Step 2: Exchange authorization code for token
token = oauth_client.exchange_code(
    code="authorization_code_from_callback",
    redirect_uri="https://your-app.com/callback"
)

# Step 3: Use the access token
client = PlaneClient(
    base_url="https://api.plane.so",
    access_token=token.access_token
)

# Client Credentials Flow (for server-to-server)
token = oauth_client.get_client_credentials_token(
    scope="read write",
    app_installation_id="optional_workspace_app_installation_id"
)

# Refresh expired tokens
new_token = oauth_client.refresh_token(token.refresh_token)

# Revoke tokens
oauth_client.revoke_token(token.access_token)

For detailed OAuth examples, see examples/oauth_example.py.

Basic Usage

# List projects in a workspace
projects = client.projects.list("my-workspace")

# Create a work item
from plane.models.work_items import CreateWorkItem

work_item = client.work_items.create(
    workspace_slug="my-workspace",
    project_id="project-id",
    data=CreateWorkItem(name="New task", state_id="state-id")
)

# Retrieve a work item with parameters
from plane.models.query_params import RetrieveQueryParams

work_item = client.work_items.retrieve(
    workspace_slug="my-workspace",
    project_id="project-id",
    work_item_id="work-item-id",
    params=RetrieveQueryParams(expand="assignees,labels,state")
)

# List work items with pagination and filtering
from plane.models.query_params import WorkItemQueryParams

work_items = client.work_items.list(
    workspace_slug="my-workspace",
    project_id="project-id",
    params=WorkItemQueryParams(per_page=50, order_by="-created_at")
)

Architecture

Client Structure

The SDK is organized around a central PlaneClient that provides access to various resource classes:

from plane.client import PlaneClient

client = PlaneClient(
    base_url="https://api.plane.so",
    api_key="your-api-key"
)

# Access different resources
client.users              # User management
client.workspaces        # Workspace operations
client.projects          # Project management
client.work_items        # Work item operations
client.cycles            # Cycle management
client.modules           # Module management
client.labels            # Label management
client.states            # State/workflow management
client.work_item_types   # Work item type management
client.work_item_properties  # Custom properties
client.epics             # Epic management
client.intake            # Intake management
client.pages             # Page management
client.customers         # Customer management

Resource Organization

All API resources extend a shared BaseResource class that handles:

  • HTTP request/response logic
  • Authentication headers
  • Error handling and retry logic
  • URL building with proper path formatting

Type Safety

The SDK uses Pydantic v2 models for all data structures:

  • Request models
  • Response models
  • Query parameter models

Note: Response models are configured with extra="allow" to be forward-compatible with new fields. Create*/Update* DTOs and query parameter models use extra="ignore".

Available Resources

Core Resources

Users

# Get current user
me = client.users.get_me()

# Retrieve a specific user
user = client.users.retrieve(user_id)

# List all users
users = client.users.list()

Workspaces

# Get workspace members
members = client.workspaces.get_members(workspace_slug)

Project Management

Projects

# Create a project
from plane.models.projects import CreateProject

project = client.projects.create(
    workspace_slug="my-workspace",
    data=CreateProject(
        name="My Project",
        identifier="MP",
        description="Project description"
    )
)

# List projects
projects = client.projects.list(workspace_slug="my-workspace")

# Retrieve a project
project = client.projects.retrieve(workspace_slug, project_id)

# Update a project
from plane.models.projects import UpdateProject

project = client.projects.update(
    workspace_slug, project_id,
    data=UpdateProject(name="Updated Name")
)

# Delete a project
client.projects.delete(workspace_slug, project_id)

# Get worklog summary
worklog_summary = client.projects.get_worklog_summary(workspace_slug, project_id)

# Get project members
members = client.projects.get_members(workspace_slug, project_id)

Work Items

# Create a work item
from plane.models.work_items import CreateWorkItem

work_item = client.work_items.create(
    workspace_slug="my-workspace",
    project_id="project-id",
    data=CreateWorkItem(
        name="Fix login bug",
        description_html="<p>Fix the login issue</p>",
        state_id="state-id",
        priority="high"
    )
)

# Retrieve a work item
from plane.models.query_params import RetrieveQueryParams

work_item = client.work_items.retrieve(
    workspace_slug, project_id, work_item_id,
    params=RetrieveQueryParams(expand="assignees,labels,state")
)

# List work items
from plane.models.query_params import WorkItemQueryParams

work_items = client.work_items.list(
    workspace_slug, project_id,
    params=WorkItemQueryParams(per_page=50, order_by="-created_at")
)

# Update a work item
from plane.models.work_items import UpdateWorkItem

work_item = client.work_items.update(
    workspace_slug, project_id, work_item_id,
    data=UpdateWorkItem(priority="low", state_id="new-state-id")
)

# Delete a work item
client.work_items.delete(workspace_slug, project_id, work_item_id)

# Search work items
results = client.work_items.search(
    workspace_slug, project_id,
    query="bug fix"
)

Work Item Sub-Resources

# Comments
comments = client.work_items.comments.list(workspace_slug, project_id, work_item_id)
comment = client.work_items.comments.create(workspace_slug, project_id, work_item_id, data)
comment = client.work_items.comments.retrieve(workspace_slug, project_id, work_item_id, comment_id)
comment = client.work_items.comments.update(workspace_slug, project_id, work_item_id, comment_id, data)
client.work_items.comments.delete(workspace_slug, project_id, work_item_id, comment_id)

# Attachments
attachments = client.work_items.attachments.list(workspace_slug, project_id, work_item_id)
attachment = client.work_items.attachments.create(workspace_slug, project_id, work_item_id, data)
attachment = client.work_items.attachments.retrieve(workspace_slug, project_id, work_item_id, attachment_id)
client.work_items.attachments.delete(workspace_slug, project_id, work_item_id, attachment_id)

# Links
links = client.work_items.links.list(workspace_slug, project_id, work_item_id)
link = client.work_items.links.create(workspace_slug, project_id, work_item_id, data)
link = client.work_items.links.retrieve(workspace_slug, project_id, work_item_id, link_id)
link = client.work_items.links.update(workspace_slug, project_id, work_item_id, link_id, data)
client.work_items.links.delete(workspace_slug, project_id, work_item_id, link_id)

# Relations
relations = client.work_items.relations.list(workspace_slug, project_id, work_item_id)
relation = client.work_items.relations.create(workspace_slug, project_id, work_item_id, data)

# Activities
activities = client.work_items.activities.list(workspace_slug, project_id, work_item_id)

# Work Logs
work_logs = client.work_items.work_logs.list(workspace_slug, project_id, work_item_id)
work_log = client.work_items.work_logs.create(workspace_slug, project_id, work_item_id, data)
work_log = client.work_items.work_logs.retrieve(workspace_slug, project_id, work_item_id, work_log_id)
work_log = client.work_items.work_logs.update(workspace_slug, project_id, work_item_id, work_log_id, data)
client.work_items.work_logs.delete(workspace_slug, project_id, work_item_id, work_log_id)

Cycles

# Create a cycle
from plane.models.cycles import CreateCycle

cycle = client.cycles.create(
    workspace_slug, project_id,
    data=CreateCycle(
        name="Sprint 1",
        start_date="2024-01-01",
        end_date="2024-01-15",
        owned_by="user-id"
    )
)

# List cycles
cycles = client.cycles.list(workspace_slug, project_id)

# Retrieve a cycle
cycle = client.cycles.retrieve(workspace_slug, project_id, cycle_id)

# Update a cycle
from plane.models.cycles import UpdateCycle

cycle = client.cycles.update(
    workspace_slug, project_id, cycle_id,
    data=UpdateCycle(name="Updated Sprint")
)

# Delete a cycle
client.cycles.delete(workspace_slug, project_id, cycle_id)

# List archived cycles
archived = client.cycles.list_archived(workspace_slug, project_id)

# Add work items to cycle
from plane.models.cycles import AddWorkItemsToCycleRequest

client.cycles.add_work_items(
    workspace_slug, project_id, cycle_id,
    data=AddWorkItemsToCycleRequest(issues=[work_item_id])
)

# Remove work item from cycle
client.cycles.remove_work_item(workspace_slug, project_id, cycle_id, work_item_id)

# List work items in cycle
cycle_items = client.cycles.list_work_items(workspace_slug, project_id, cycle_id)

# Transfer work items between cycles
from plane.models.cycles import TransferCycleWorkItemsRequest

client.cycles.transfer_work_items(
    workspace_slug, project_id, cycle_id,
    data=TransferCycleWorkItemsRequest(new_cycle_id="other-cycle-id")
)

# Archive/unarchive cycles
client.cycles.archive(workspace_slug, project_id, cycle_id)
client.cycles.unarchive(workspace_slug, project_id, cycle_id)

Modules

# Create a module
from plane.models.modules import CreateModule

module = client.modules.create(
    workspace_slug, project_id,
    data=CreateModule(name="Auth Module")
)

# List modules
modules = client.modules.list(workspace_slug, project_id)

# Retrieve a module
module = client.modules.retrieve(workspace_slug, project_id, module_id)

# Update a module
from plane.models.modules import UpdateModule

module = client.modules.update(
    workspace_slug, project_id, module_id,
    data=UpdateModule(name="Updated Module")
)

# Delete a module
client.modules.delete(workspace_slug, project_id, module_id)

# List archived modules
archived = client.modules.list_archived(workspace_slug, project_id)

# Add work items to module
from plane.models.modules import AddWorkItemsToModuleRequest

client.modules.add_work_items(
    workspace_slug, project_id, module_id,
    data=AddWorkItemsToModuleRequest(issues=[work_item_id])
)

# Remove work item from module
client.modules.remove_work_item(workspace_slug, project_id, module_id, work_item_id)

# List work items in module
module_items = client.modules.list_work_items(workspace_slug, project_id, module_id)

# Archive/unarchive modules
client.modules.archive(workspace_slug, project_id, module_id)
client.modules.unarchive(workspace_slug, project_id, module_id)

States

# Create a state
from plane.models.states import CreateState

state = client.states.create(
    workspace_slug, project_id,
    data=CreateState(
        name="In Progress",
        color="#3b82f6",
        group="started"
    )
)

# List states
states = client.states.list(workspace_slug, project_id)

# Retrieve a state
state = client.states.retrieve(workspace_slug, project_id, state_id)

# Update a state
from plane.models.states import UpdateState

state = client.states.update(
    workspace_slug, project_id, state_id,
    data=UpdateState(name="Updated Status")
)

# Delete a state
client.states.delete(workspace_slug, project_id, state_id)

Labels

# Create a label
from plane.models.labels import CreateLabel

label = client.labels.create(
    workspace_slug, project_id,
    data=CreateLabel(name="Bug", color="#ef4444")
)

# List labels
labels = client.labels.list(workspace_slug, project_id)

# Retrieve a label
label = client.labels.retrieve(workspace_slug, project_id, label_id)

# Update a label
from plane.models.labels import UpdateLabel

label = client.labels.update(
    workspace_slug, project_id, label_id,
    data=UpdateLabel(name="Updated Label")
)

# Delete a label
client.labels.delete(workspace_slug, project_id, label_id)

Work Item Configuration

Work Item Types

# Create a work item type
from plane.models.work_item_types import CreateWorkItemType

wit = client.work_item_types.create(
    workspace_slug, project_id,
    data=CreateWorkItemType(name="Story")
)

# List work item types
types = client.work_item_types.list(workspace_slug, project_id)

# Retrieve a work item type
wit = client.work_item_types.retrieve(workspace_slug, project_id, type_id)

# Update a work item type
from plane.models.work_item_types import UpdateWorkItemType

wit = client.work_item_types.update(
    workspace_slug, project_id, type_id,
    data=UpdateWorkItemType(name="Updated Type")
)

# Delete a work item type
client.work_item_types.delete(workspace_slug, project_id, type_id)

Work Item Properties

# Create a property
from plane.models.work_item_properties import CreateWorkItemProperty

prop = client.work_item_properties.create(
    workspace_slug, project_id, work_item_type_id,
    data=CreateWorkItemProperty(name="Severity")
)

# List properties
properties = client.work_item_properties.list(workspace_slug, project_id, work_item_type_id)

# Retrieve a property
prop = client.work_item_properties.retrieve(workspace_slug, project_id, work_item_type_id, property_id)

# Update a property
from plane.models.work_item_properties import UpdateWorkItemProperty

prop = client.work_item_properties.update(
    workspace_slug, project_id, work_item_type_id, property_id,
    data=UpdateWorkItemProperty(name="Updated Property")
)

# Delete a property
client.work_item_properties.delete(workspace_slug, project_id, work_item_type_id, property_id)

Additional Resources

Epics

# List epics
epics = client.epics.list(workspace_slug, project_id)

# Retrieve an epic
epic = client.epics.retrieve(workspace_slug, project_id, epic_id)

Intake

# Create intake issue
from plane.models.intake import CreateIntake

intake = client.intake.create(
    workspace_slug, project_id,
    data=CreateIntake(name="Customer request")
)

# List intake issues
intake_items = client.intake.list(workspace_slug, project_id)

# Retrieve intake issue
intake = client.intake.retrieve(workspace_slug, project_id, intake_id)

# Update intake issue
from plane.models.intake import UpdateIntake

intake = client.intake.update(
    workspace_slug, project_id, intake_id,
    data=UpdateIntake(status="completed")
)

# Delete intake issue
client.intake.delete(workspace_slug, project_id, intake_id)

Pages

# List workspace pages
pages = client.pages.list_workspace_pages(workspace_slug)

# List project pages
pages = client.pages.list_project_pages(workspace_slug, project_id)

# Retrieve a workspace page
page = client.pages.retrieve_workspace_page(workspace_slug, page_id)

# Retrieve a project page
page = client.pages.retrieve_project_page(workspace_slug, project_id, page_id)

Customers

# List customers
customers = client.customers.list(workspace_slug)

# Create a customer
from plane.models.customers import CreateCustomer

customer = client.customers.create(
    workspace_slug,
    data=CreateCustomer(name="Acme Inc")
)

# Retrieve a customer
customer = client.customers.retrieve(workspace_slug, customer_id)

# Update a customer
from plane.models.customers import UpdateCustomer

customer = client.customers.update(
    workspace_slug, customer_id,
    data=UpdateCustomer(name="Updated Name")
)

# Delete a customer
client.customers.delete(workspace_slug, customer_id)

# Customer properties
properties = client.customers.properties.list(workspace_slug, customer_id)
property = client.customers.properties.create(workspace_slug, customer_id, data)

# Customer requests
requests = client.customers.requests.list(workspace_slug, customer_id)

Data Models

The SDK provides comprehensive Pydantic v2 models for all API operations.

Query Parameters

  • BaseQueryParams - Base query parameters
  • PaginatedQueryParams - Pagination support (per_page, page)
  • WorkItemQueryParams - Work item specific queries (expand, order_by, etc.)
  • RetrieveQueryParams - Retrieve operations (expand, fields, etc.)

Response Models

Paginated responses follow the pattern Paginated<Resource>Response and include:

  • results - Array of resource objects
  • total_count - Total number of results
  • next_page_number - Next page number (if applicable)
  • prev_page_number - Previous page number (if applicable)

Error Handling

The SDK provides comprehensive error handling with specific exception types:

from plane.errors import PlaneError, ConfigurationError, HttpError

# Configuration errors
try:
    client = PlaneClient(base_url="https://api.plane.so")
    # Missing both api_key and access_token
except ConfigurationError as e:
    print(f"Configuration error: {e}")

# HTTP errors
try:
    work_item = client.work_items.retrieve("workspace", "project", "invalid-id")
except HttpError as e:
    print(f"HTTP error {e.status_code}: {e}")
    print(f"Response: {e.response}")

Error Types

  • PlaneError - Base exception class with optional status_code
  • ConfigurationError - Invalid client configuration (missing credentials or both auth methods provided)
  • HttpError - HTTP request/response errors with status code and response body

Configuration

Basic Configuration

from plane.client import PlaneClient

client = PlaneClient(
    base_url="https://api.plane.so",
    api_key="your-api-key"
)

Advanced Configuration

from plane.config import Configuration, RetryConfig
from plane.client import PlaneClient

# Custom retry configuration
retry_config = RetryConfig(
    total=5,                                    # Number of retries
    backoff_factor=0.5,                         # Backoff multiplier
    status_forcelist=(429, 500, 502, 503, 504) # Retry on these status codes
)

# Create client with custom config
client = PlaneClient(
    base_url="https://api.plane.so",
    api_key="your-api-key",
    timeout=60.0,                               # Request timeout in seconds
    retry=retry_config                          # Optional retry config
)

Configuration Options

Option Type Default Description
base_url str Required API base URL
api_key str Optional API key for authentication
access_token str Optional Access token for authentication
timeout float | tuple[float, float] 30.0 Request timeout in seconds
retry RetryConfig None Retry configuration

Note: Provide exactly one of api_key or access_token.

Examples

Complete Workflow Example

from plane.client import PlaneClient
from plane.models.projects import CreateProject
from plane.models.work_items import CreateWorkItem
from plane.models.states import CreateState
from plane.models.labels import CreateLabel
from plane.models.query_params import WorkItemQueryParams

client = PlaneClient(
    base_url="https://api.plane.so",
    api_key="your-api-key"
)

# Create a project
project = client.projects.create(
    workspace_slug="my-workspace",
    data=CreateProject(
        name="My New Project",
        identifier="MNP",
        description="A project created with the Python SDK"
    )
)

# Create a state
state = client.states.create(
    workspace_slug="my-workspace",
    project_id=project.id,
    data=CreateState(
        name="In Progress",
        color="#3b82f6",
        group="started"
    )
)

# Create a label
label = client.labels.create(
    workspace_slug="my-workspace",
    project_id=project.id,
    data=CreateLabel(name="Bug", color="#ef4444")
)

# Create a work item
work_item = client.work_items.create(
    workspace_slug="my-workspace",
    project_id=project.id,
    data=CreateWorkItem(
        name="Fix authentication bug",
        description_html="<p>Fix the authentication issue in the login flow</p>",
        priority="high",
        state_id=state.id,
        labels=[label.id]
    )
)

# List work items with filters
work_items = client.work_items.list(
    workspace_slug="my-workspace",
    project_id=project.id,
    params=WorkItemQueryParams(per_page=20, order_by="-created_at")
)

print(f"Created work item: {work_item.name}")
print(f"Total work items: {len(work_items.results)}")

Working with Cycles

from plane.models.cycles import CreateCycle, AddWorkItemsToCycleRequest

# Create a cycle
cycle = client.cycles.create(
    workspace_slug="my-workspace",
    project_id=project.id,
    data=CreateCycle(
        name="Sprint 1",
        description="First sprint of the project",
        start_date="2024-01-01",
        end_date="2024-01-15",
        owned_by="user-id"
    )
)

# Add work items to cycle
client.cycles.add_work_items(
    workspace_slug="my-workspace",
    project_id=project.id,
    cycle_id=cycle.id,
    data=AddWorkItemsToCycleRequest(issues=[work_item.id])
)

# List cycle work items
cycle_work_items = client.cycles.list_work_items(
    workspace_slug="my-workspace",
    project_id=project.id,
    cycle_id=cycle.id
)

print(f"Cycle: {cycle.name}")
print(f"Work items in cycle: {len(cycle_work_items.results)}")

Working with Comments and Attachments

from plane.models.work_items import CreateWorkItemComment

# Add a comment
comment = client.work_items.comments.create(
    workspace_slug="my-workspace",
    project_id=project.id,
    work_item_id=work_item.id,
    data=CreateWorkItemComment(
        comment_html="<p>This is a comment on the work item</p>",
        access="INTERNAL"
    )
)

# List comments
comments = client.work_items.comments.list(
    workspace_slug="my-workspace",
    project_id=project.id,
    work_item_id=work_item.id
)

print(f"Total comments: {len(comments.results)}")

# Upload an attachment
attachment = client.work_items.attachments.create(
    workspace_slug="my-workspace",
    project_id=project.id,
    work_item_id=work_item.id,
    data={
        "asset": "file",  # URL to file or file path
        "attributes": {"name": "screenshot.png"}
    }
)

print(f"Attachment ID: {attachment.id}")

Requirements

  • Python 3.10+
  • requests >= 2.31.0
  • pydantic >= 2.4.0

Development

Setup

git clone <repository-url>
cd plane-python-sdk
pip install -e ".[dev]"

Running Tests

# Run all tests
pytest

# Run specific test file
pytest tests/unit/test_work_items.py

# Run with coverage
pytest --cov=plane tests/

Code Quality

The project uses:

  • Black for code formatting
  • Ruff for linting (rules: E, F, I, UP, B)
  • MyPy for type checking
  • Pytest for testing

Run pre-commit checks:

pre-commit run --all-files

Project Structure

plane-python-sdk/
├── plane/
│   ├── __init__.py
│   ├── client.py              # Main PlaneClient
│   ├── config.py              # Configuration classes
│   ├── api/                   # API resource classes
│   │   ├── base_resource.py   # Base class for all resources
│   │   ├── work_items/        # Work item sub-resources
│   │   ├── work_item_properties/
│   │   ├── customers/
│   │   └── ...
│   ├── models/                # Pydantic models
│   │   ├── work_items.py
│   │   ├── projects.py
│   │   ├── query_params.py
│   │   ├── enums.py
│   │   └── ...
│   └── errors/                # Exception classes
│       └── errors.py
├── tests/
│   ├── unit/                  # Unit tests
│   └── scripts/               # Integration test scripts
├── pyproject.toml
├── README.md
└── requirements.txt

License

MIT License - see LICENSE file for details.

Support

For issues and questions:


Note: This SDK is designed to work with Plane's REST API. Make sure you have the appropriate API credentials and permissions for the operations you're trying to perform.

About

Python SDK for plane.so

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages