Skip to content

Commit 281335a

Browse files
authored
Feature/envvars (#5)
* feat(src/codesphere/resources/workspace/env-vars): support env-vars endpoints of public api * bump: version 0.3.0 → 0.4.0 --------- Co-authored-by: JD <>
1 parent 2eecc8b commit 281335a

File tree

11 files changed

+259
-5
lines changed

11 files changed

+259
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v0.4.0 (2025-07-22)
2+
3+
### Feat
4+
5+
- **src/codesphere/resources/workspace/env-vars**: support env-vars endpoints of public api
6+
17
## v0.3.0 (2025-07-22)
28

39
### Feat
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import asyncio
2+
import pprint
3+
from codesphere import CodesphereSDK
4+
5+
6+
async def main():
7+
"""Fetches a team and lists all workspaces within it."""
8+
async with CodesphereSDK() as sdk:
9+
teams = await sdk.teams.list()
10+
workspaces = await sdk.workspaces.list_by_team(team_id=teams[0].id)
11+
12+
workspace = workspaces[0]
13+
14+
envs = await workspace.get_env_vars()
15+
print("Current Environment Variables:")
16+
pprint.pprint(envs[0].name)
17+
18+
await workspace.delete_env_vars([envs[0].name]) # you can pass a list of strings to delete multiple env vars
19+
20+
print("Environment Variables after deletion:")
21+
updated_envs = await workspace.get_env_vars()
22+
pprint.pprint(updated_envs)
23+
24+
if __name__ == "__main__":
25+
asyncio.run(main())
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import asyncio
2+
import pprint
3+
from codesphere import CodesphereSDK
4+
5+
6+
async def main():
7+
"""Fetches a team and lists all workspaces within it."""
8+
async with CodesphereSDK() as sdk:
9+
teams = await sdk.teams.list()
10+
workspaces = await sdk.workspaces.list_by_team(team_id=teams[0].id)
11+
12+
workspace = workspaces[0]
13+
14+
envs = await workspace.get_env_vars()
15+
print("Current Environment Variables:")
16+
pprint.pprint(envs)
17+
18+
19+
if __name__ == "__main__":
20+
asyncio.run(main())
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import asyncio
2+
import pprint
3+
from codesphere import CodesphereSDK
4+
5+
6+
async def main():
7+
"""Fetches a team and lists all workspaces within it."""
8+
async with CodesphereSDK() as sdk:
9+
teams = await sdk.teams.list()
10+
workspaces = await sdk.workspaces.list_by_team(team_id=teams[0].id)
11+
12+
workspace = workspaces[0]
13+
14+
envs = await workspace.get_env_vars()
15+
print("Current Environment Variables:")
16+
pprint.pprint(envs)
17+
18+
envs[0].value = "new_value" # Modify an environment variable
19+
await workspace.set_env_vars(envs) # Update the environment variables
20+
21+
print("Updated Environment Variables:")
22+
updated_envs = await workspace.get_env_vars()
23+
pprint.pprint(updated_envs)
24+
25+
26+
if __name__ == "__main__":
27+
asyncio.run(main())

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "codesphere"
33

4-
version = "0.3.0"
4+
version = "0.4.0"
55
description = "Use Codesphere within python scripts."
66
readme = "README.md"
77
license = { file="LICENSE" }

src/codesphere/client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ def __init__(self, base_url: str = "https://codesphere.com/api"):
2323
setattr(self, method, partial(self.request, method.upper()))
2424

2525
async def __aenter__(self):
26+
timeout_config = httpx.Timeout(10.0, read=30.0)
2627
self.client = httpx.AsyncClient(
27-
base_url=self._base_url, headers={"Authorization": f"Bearer {self._token}"}
28+
base_url=self._base_url,
29+
headers={"Authorization": f"Bearer {self._token}"},
30+
timeout=timeout_config,
2831
)
2932
return self
3033

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from __future__ import annotations
2+
from pydantic import BaseModel, PrivateAttr
3+
from typing import Optional, List, TYPE_CHECKING
4+
5+
if TYPE_CHECKING:
6+
from ...client import APIHttpClient
7+
8+
9+
class EnvVarPair(BaseModel):
10+
name: str
11+
value: str
12+
13+
14+
class WorkspaceCreate(BaseModel):
15+
teamId: int
16+
name: str
17+
planId: int
18+
baseImage: Optional[str] = None
19+
isPrivateRepo: bool = True
20+
replicas: int = 1
21+
gitUrl: Optional[str] = None
22+
initialBranch: Optional[str] = None
23+
cloneDepth: Optional[int] = None
24+
sourceWorkspaceId: Optional[int] = None
25+
welcomeMessage: Optional[str] = None
26+
vpnConfig: Optional[str] = None
27+
restricted: Optional[bool] = None
28+
env: Optional[List[EnvVarPair]] = None
29+
30+
31+
# Defines the request body for PATCH /workspaces/{workspaceId}
32+
class WorkspaceUpdate(BaseModel):
33+
planId: Optional[int] = None
34+
baseImage: Optional[str] = None
35+
name: Optional[str] = None
36+
replicas: Optional[int] = None
37+
vpnConfig: Optional[str] = None
38+
restricted: Optional[bool] = None
39+
40+
41+
# Defines the response from GET /workspaces/{workspaceId}/status
42+
class WorkspaceStatus(BaseModel):
43+
isRunning: bool
44+
45+
46+
# This is the main model for a workspace, returned by GET, POST, and LIST
47+
class Workspace(BaseModel):
48+
_http_client: Optional[APIHttpClient] = PrivateAttr(default=None)
49+
50+
id: int
51+
teamId: int
52+
name: str
53+
planId: int
54+
isPrivateRepo: bool
55+
replicas: int
56+
baseImage: Optional[str] = None
57+
dataCenterId: int
58+
userId: int
59+
gitUrl: Optional[str] = None
60+
initialBranch: Optional[str] = None
61+
sourceWorkspaceId: Optional[int] = None
62+
welcomeMessage: Optional[str] = None
63+
vpnConfig: Optional[str] = None
64+
restricted: bool
65+
66+
async def update(self, data: WorkspaceUpdate) -> None:
67+
"""Updates this workspace with new data."""
68+
if not self._http_client:
69+
raise RuntimeError("Cannot make API calls on a detached model.")
70+
71+
await self._http_client.patch(
72+
f"/workspaces/{self.id}", json=data.model_dump(exclude_unset=True)
73+
)
74+
# Optionally, update the local object's state
75+
for key, value in data.model_dump(exclude_unset=True).items():
76+
setattr(self, key, value)
77+
78+
async def delete(self) -> None:
79+
"""Deletes this workspace."""
80+
if not self._http_client:
81+
raise RuntimeError("Cannot make API calls on a detached model.")
82+
await self._http_client.delete(f"/workspaces/{self.id}")
83+
84+
async def get_status(self) -> WorkspaceStatus:
85+
"""Gets the running status of this workspace."""
86+
if not self._http_client:
87+
raise RuntimeError("Cannot make API calls on a detached model.")
88+
89+
response = await self._http_client.get(f"/workspaces/{self.id}/status")
90+
return WorkspaceStatus.model_validate(response.json())

src/codesphere/resources/workspace/env-vars/modely.py

Whitespace-only changes.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from typing import List
2+
from ..base import ResourceBase, APIOperation
3+
from .models import Workspace, WorkspaceCreate, WorkspaceUpdate
4+
5+
6+
class WorkspacesResource(ResourceBase):
7+
"""Manages all API operations for the Workspace resource."""
8+
9+
list_by_team = APIOperation(
10+
method="GET",
11+
endpoint_template="/workspaces/team/{team_id}",
12+
response_model=List[Workspace],
13+
)
14+
15+
get = APIOperation(
16+
method="GET",
17+
endpoint_template="/workspaces/{workspace_id}",
18+
response_model=Workspace,
19+
)
20+
21+
create = APIOperation(
22+
method="POST",
23+
endpoint_template="/workspaces",
24+
input_model=WorkspaceCreate,
25+
response_model=Workspace,
26+
)
27+
28+
update = APIOperation(
29+
method="PATCH",
30+
endpoint_template="/workspaces/{workspace_id}",
31+
input_model=WorkspaceUpdate,
32+
response_model=None,
33+
)
34+
35+
delete = APIOperation(
36+
method="DELETE",
37+
endpoint_template="/workspaces/{workspace_id}",
38+
response_model=None,
39+
)

src/codesphere/resources/workspace/models.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
2-
from pydantic import BaseModel, PrivateAttr
3-
from typing import Optional, List, TYPE_CHECKING
2+
from pydantic import BaseModel, PrivateAttr, parse_obj_as
3+
from typing import Optional, List, TYPE_CHECKING, Union, Dict
44

55
if TYPE_CHECKING:
66
from ...client import APIHttpClient
@@ -88,3 +88,47 @@ async def get_status(self) -> WorkspaceStatus:
8888

8989
response = await self._http_client.get(f"/workspaces/{self.id}/status")
9090
return WorkspaceStatus.model_validate(response.json())
91+
92+
async def get_env_vars(self) -> list[EnvVarPair]:
93+
"""Fetches all environment variables for this workspace."""
94+
if not self._http_client:
95+
raise RuntimeError("Cannot make API calls on a detached model.")
96+
97+
response = await self._http_client.get(f"/workspaces/{self.id}/env-vars")
98+
return parse_obj_as(list[EnvVarPair], response.json())
99+
100+
async def set_env_vars(
101+
self, env_vars: Union[List[EnvVarPair], List[Dict[str, str]]]
102+
) -> None:
103+
"""
104+
Sets or updates environment variables for this workspace.
105+
This operation replaces all existing variables with the provided list.
106+
Accepts either a list of EnvVarPair models or a list of dictionaries.
107+
"""
108+
if not self._http_client:
109+
raise RuntimeError("Cannot make API calls on a detached model.")
110+
111+
json_payload = []
112+
if env_vars and isinstance(env_vars[0], EnvVarPair):
113+
json_payload = [var.model_dump() for var in env_vars]
114+
else:
115+
json_payload = env_vars
116+
117+
await self._http_client.put(
118+
f"/workspaces/{self.id}/env-vars", json=json_payload
119+
)
120+
121+
async def delete_env_vars(
122+
self, var_names: Union[List[str], List[EnvVarPair]]
123+
) -> None:
124+
"""Deletes specific environment variables from this workspace."""
125+
if not self._http_client:
126+
raise RuntimeError("Cannot make API calls on a detached model.")
127+
128+
payload = []
129+
if var_names and isinstance(var_names[0], EnvVarPair):
130+
payload = [var.name for var in var_names]
131+
else:
132+
payload = var_names
133+
134+
await self._http_client.delete(f"/workspaces/{self.id}/env-vars", json=payload)

0 commit comments

Comments
 (0)