Skip to content

Commit 6da3ad7

Browse files
feat: workspace and project resources added
1 parent 34e5bc1 commit 6da3ad7

File tree

15 files changed

+598
-310
lines changed

15 files changed

+598
-310
lines changed

examples/basic_usage.py

Lines changed: 50 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,86 @@
1-
"""Basic usage examples for the API SDK."""
1+
"""Basic usage examples for the API SDK (Project info)."""
22

33
import asyncio
44
import os
55
from plane import Client, Config, SyncClient
6-
from plane.models.user import UserCreate, UserUpdate
76

87

98
async def async_example():
10-
"""Example using async client."""
11-
# Initialize client with config
9+
"""Example using async client for projects."""
1210
config = Config(
1311
api_key=os.getenv("MY_API_KEY", "your-api-key"),
1412
base_url="https://api.plane.so/api/v1",
1513
)
1614

17-
# Or initialize from environment variables
18-
# client = Client() # Uses MY_API_KEY env var
19-
2015
async with Client(config) as client:
2116
# Check API health
2217
health = await client.health_check()
2318
print(f"API Health: {health}")
2419

25-
# List users
26-
result = await client.users.list(page=1, per_page=10)
27-
users = result["users"]
28-
pagination = result["pagination"]
29-
20+
# List workspaces
21+
ws_result = await client.workspaces.list(page=1, per_page=5)
22+
workspaces = ws_result["workspaces"]
23+
if not workspaces:
24+
print("No workspaces found.")
25+
return
26+
workspace = workspaces[0]
27+
print(f"Using workspace: {workspace.name} (slug: {workspace.slug})")
28+
29+
# List projects in the workspace
30+
proj_result = await client.projects.list(workspace.slug, page=1, per_page=10)
31+
projects = proj_result["projects"]
32+
pagination = proj_result["pagination"]
3033
print(
31-
f"Found {len(users)} users (page {pagination.page} of {pagination.total_pages})"
32-
)
33-
34-
# Get a specific user
35-
if users:
36-
user = await client.users.get(users[0].id)
37-
print(f"User: {user.full_name} ({user.email})")
38-
39-
# Create a new user
40-
new_user_data = UserCreate(
41-
42-
firstName="New",
43-
lastName="User",
44-
password="securepassword123",
34+
f"Found {len(projects)} projects (page {pagination.page} of {pagination.total_pages})"
4535
)
36+
for project in projects:
37+
print(f"- {project.name} (id: {project.id}, desc: {project.description})")
4638

47-
try:
48-
new_user = await client.users.create(new_user_data)
49-
print(f"Created user: {new_user.id}")
50-
51-
# Update the user
52-
update_data = UserUpdate(firstName="Updated")
53-
updated_user = await client.users.update(new_user.id, update_data)
54-
print(f"Updated user: {updated_user.full_name}")
55-
56-
# Search users
57-
search_results = await client.users.search("Updated", limit=5)
58-
print(f"Search found {len(search_results)} users")
59-
60-
# Delete the user
61-
await client.users.delete(new_user.id)
62-
print("User deleted")
63-
64-
except Exception as e:
65-
print(f"Error: {e}")
39+
# Get a specific project (first one)
40+
if projects:
41+
project = await client.projects.get(workspace.slug, projects[0].id)
42+
print(
43+
f"Project details: {project.name} (id: {project.id}) - {project.description}"
44+
)
6645

6746

6847
def sync_example():
69-
"""Example using synchronous client."""
48+
"""Example using synchronous client for projects."""
7049
config = Config(
7150
api_key=os.getenv("MY_API_KEY", "your-api-key"),
72-
base_url="https://api.example.com/v1",
51+
base_url="https://api.plane.so/api/v1",
7352
)
7453

7554
with SyncClient(config) as client:
7655
# Check API health
7756
health = client.health_check()
7857
print(f"API Health: {health}")
7958

80-
# List users
81-
result = client.users.list(page=1, per_page=5)
82-
users = result["users"]
83-
84-
print(f"Found {len(users)} users")
85-
86-
# Create and manage a user
87-
new_user_data = UserCreate(
88-
89-
firstName="Sync",
90-
lastName="User",
91-
password="securepassword123",
59+
# List workspaces
60+
ws_result = client.workspaces.list(page=1, per_page=5)
61+
workspaces = ws_result["workspaces"]
62+
if not workspaces:
63+
print("No workspaces found.")
64+
return
65+
workspace = workspaces[0]
66+
print(f"Using workspace: {workspace.name} (slug: {workspace.slug})")
67+
68+
# List projects in the workspace
69+
proj_result = client.projects.list(workspace.slug, page=1, per_page=10)
70+
projects = proj_result["projects"]
71+
pagination = proj_result["pagination"]
72+
print(
73+
f"Found {len(projects)} projects (page {pagination.page} of {pagination.total_pages})"
9274
)
93-
94-
try:
95-
new_user = client.users.create(new_user_data)
96-
print(f"Created user: {new_user.full_name}")
97-
98-
# Clean up
99-
client.users.delete(new_user.id)
100-
print("User deleted")
101-
102-
except Exception as e:
103-
print(f"Error: {e}")
75+
for project in projects:
76+
print(f"- {project.name} (id: {project.id}, desc: {project.description})")
77+
78+
# Get a specific project (first one)
79+
if projects:
80+
project = client.projects.get(workspace.slug, projects[0].id)
81+
print(
82+
f"Project details: {project.name} (id: {project.id}) - {project.description}"
83+
)
10484

10585

10686
if __name__ == "__main__":

pyproject.toml

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ dev = [
2828
"pytest>=7.0.0",
2929
"pytest-asyncio>=0.21.0",
3030
"pytest-cov>=4.0.0",
31-
"black>=23.0.0",
31+
"ruff>=0.0.290",
3232
"isort>=5.12.0",
3333
"flake8>=6.0.0",
3434
"mypy>=1.0.0",
@@ -40,9 +40,71 @@ Documentation = "https://github.com/makeplane/sdk-python"
4040
Repository = "https://github.com/makeplane/sdk-python"
4141
Issues = "https://github.com/makeplane/sdk-python/issues"
4242

43-
[tool.black]
43+
[tool.ruff]
4444
line-length = 88
45-
target-version = ['py38']
45+
target-version = "py38"
46+
47+
[tool.ruff.format]
48+
# Use double quotes for strings.
49+
quote-style = "double"
50+
51+
# Indent with spaces, rather than tabs.
52+
indent-style = "space"
53+
54+
# Respect magic trailing commas.
55+
# skip-magic-trailing-comma = true
56+
57+
# Automatically detect the appropriate line ending.
58+
line-ending = "auto"
59+
60+
[tool.ruff.lint]
61+
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
62+
select = ["E", "F"]
63+
ignore = []
64+
65+
# Allow autofix for all enabled rules (when `--fix`) is provided.
66+
fixable = ["ALL"]
67+
unfixable = []
68+
69+
# Allow unused variables when underscore-prefixed.
70+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
71+
72+
[tool.ruff.lint.pep8-naming]
73+
# Allow lowercase variables like "id"
74+
classmethod-decorators = ["classmethod", "validator", "root_validator"]
75+
76+
[tool.ruff.lint.per-file-ignores]
77+
# Ignore specific rules for tests
78+
"tests/*" = ["E402", "F401", "F811"]
79+
# Ignore imported but unused in __init__.py files
80+
"__init__.py" = ["F401"]
81+
82+
[tool.ruff.lint.mccabe]
83+
# Unlike Flake8, default to a complexity level of 10.
84+
max-complexity = 10
85+
86+
[tool.ruff.lint.isort]
87+
combine-as-imports = true
88+
detect-same-package = true
89+
force-wrap-aliases = true
90+
known-first-party = ["plane"]
91+
known-third-party = ["rest_framework"]
92+
relative-imports-order = "closest-to-furthest"
93+
94+
[tool.ruff.lint.flake8-tidy-imports]
95+
ban-relative-imports = "parents"
96+
97+
[tool.ruff.lint.pycodestyle]
98+
ignore-overlong-task-comments = true
99+
max-doc-length = 88
100+
101+
[tool.ruff.lint.pydocstyle]
102+
convention = "google"
103+
104+
[tool.ruff.lint.pylint]
105+
max-args = 8
106+
max-statements = 50
107+
46108

47109
[tool.isort]
48110
profile = "black"

src/plane/client.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from typing import Optional
55
import httpx
66
from .config import Config
7-
from .resources.users import UsersResource
7+
from .resources.workspaces import UsersResource
8+
from .resources.projects import ProjectsResource
89

910

1011
class Client:
@@ -20,6 +21,7 @@ def __init__(self, config: Optional[Config] = None, **kwargs):
2021

2122
# Initialize resources
2223
self.users = UsersResource(self)
24+
self.projects = ProjectsResource(self)
2325

2426
def _create_http_client(self) -> httpx.AsyncClient:
2527
"""Create HTTP client with proper configuration."""
@@ -85,6 +87,11 @@ def users(self):
8587
"""Access users resource synchronously."""
8688
return SyncUsersResource(self._async_client.users)
8789

90+
@property
91+
def projects(self):
92+
"""Access projects resource synchronously."""
93+
return SyncProjectsResource(self._async_client.projects)
94+
8895
def health_check(self) -> dict:
8996
"""Check API health synchronously."""
9097
return self._run_async(self._async_client.health_check())
@@ -141,3 +148,46 @@ def delete(self, user_id: str):
141148
def search(self, query: str, limit: int = 10):
142149
"""Search users synchronously."""
143150
return self._run_async(self._async_resource.search(query, limit))
151+
152+
153+
class SyncProjectsResource:
154+
"""Synchronous wrapper for projects resource."""
155+
156+
def __init__(self, async_resource: ProjectsResource):
157+
self._async_resource = async_resource
158+
self._client = async_resource._client
159+
160+
def _run_async(self, coro):
161+
try:
162+
loop = asyncio.get_event_loop()
163+
except RuntimeError:
164+
loop = asyncio.new_event_loop()
165+
asyncio.set_event_loop(loop)
166+
return loop.run_until_complete(coro)
167+
168+
def list(
169+
self,
170+
workspace_slug: str,
171+
page: int = 1,
172+
per_page: int = 20,
173+
search: Optional[str] = None,
174+
):
175+
return self._run_async(
176+
self._async_resource.list(workspace_slug, page, per_page, search)
177+
)
178+
179+
def get(self, workspace_slug: str, project_id: str):
180+
return self._run_async(self._async_resource.get(workspace_slug, project_id))
181+
182+
def create(self, workspace_slug: str, project_data):
183+
return self._run_async(
184+
self._async_resource.create(workspace_slug, project_data)
185+
)
186+
187+
def update(self, workspace_slug: str, project_id: str, project_data):
188+
return self._run_async(
189+
self._async_resource.update(workspace_slug, project_id, project_data)
190+
)
191+
192+
def delete(self, workspace_slug: str, project_id: str):
193+
return self._run_async(self._async_resource.delete(workspace_slug, project_id))

src/plane/models/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
"""Models package."""
22

33
from .base import BaseAPIModel, TimestampMixin, PaginatedResponse
4-
from .user import User, UserCreate, UserUpdate
4+
from .workspace import Workspace, WorkspaceUpdate
55

66
__all__ = [
77
"BaseAPIModel",
88
"TimestampMixin",
99
"PaginatedResponse",
10-
"User",
11-
"UserCreate",
12-
"UserUpdate",
10+
"Workspace",
11+
"WorkspaceUpdate",
1312
]

0 commit comments

Comments
 (0)