Skip to content

Commit 92eb69a

Browse files
authored
refactor(team): streamline api operations pattern (#29)
Co-authored-by: Datata1 <>
1 parent 17b8372 commit 92eb69a

26 files changed

+467
-200
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,7 @@ pypi: ## publishes to PyPI
6565
uv build
6666
@echo "\n>>> Publishing to PyPI..."
6767
uv publish
68-
@echo "\n\033[0;32mPyPI release complete! The GitHub Action will now create the GitHub Release.\033[0m"
68+
@echo "\n\033[0;32mPyPI release complete! The GitHub Action will now create the GitHub Release.\033[0m"
69+
70+
tree: ## shows filetree in terminal without uninteresting files
71+
tree -I "*.pyc|*.lock"

examples/metadata/get_datacenters.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import asyncio
2+
import logging
23
from codesphere import CodesphereSDK
34

5+
logging.basicConfig(level=logging.INFO)
6+
47

58
async def main():
69
"""Fetches datacenters."""
710
async with CodesphereSDK() as sdk:
8-
datacenters = await sdk.metadata.datacenters()
11+
datacenters = await sdk.metadata.list_datacenters()
912
for datacenter in datacenters:
1013
print(datacenter.model_dump_json(indent=2))
1114

examples/metadata/get_workspace_base_image.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import asyncio
2+
import logging
23
from codesphere import CodesphereSDK
34

5+
logging.basicConfig(level=logging.INFO)
6+
47

58
async def main():
69
"""Fetches base images."""

examples/metadata/get_workspace_plans.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import asyncio
2+
import logging
23
from codesphere import CodesphereSDK
34

5+
logging.basicConfig(level=logging.INFO)
6+
47

58
async def main():
69
"""Fetches workspace plans."""

examples/teams/create_team.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import asyncio
2+
import logging
23
from codesphere import CodesphereSDK, TeamCreate
34

5+
logging.basicConfig(level=logging.INFO)
6+
47

58
async def main():
69
try:
710
async with CodesphereSDK() as sdk:
811
newTeam = TeamCreate(name="test", dc=2)
912
created_team = await sdk.teams.create(data=newTeam)
10-
print("\n--- Details for the created team ---")
1113
print(created_team.model_dump_json(indent=2))
1214
except Exception as e:
1315
print(f"An error occurred: {e}")

examples/teams/delete_team.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import asyncio
2+
import logging
23
from codesphere import CodesphereSDK
34

5+
logging.basicConfig(level=logging.INFO)
6+
47

58
async def main():
69
try:
710
async with CodesphereSDK() as sdk:
811
team_to_delete = await sdk.teams.get(team_id="<id>")
9-
print("\n--- Details of the team to be deleted ---")
1012
print(team_to_delete.model_dump_json(indent=2))
1113
await team_to_delete.delete()
1214
print(f"Team with ID {team_to_delete.id} was successfully deleted.")

examples/teams/get_team.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import asyncio
2-
2+
import logging
33
from codesphere import CodesphereSDK
44

5+
logging.basicConfig(level=logging.INFO)
6+
57

68
async def main():
79
try:
810
async with CodesphereSDK() as sdk:
9-
teams = await sdk.teams.list()
10-
print(teams[0].model_dump_json(indent=2))
11-
first_team = await sdk.teams.get(team_id=teams[0].id)
12-
print("\n--- Details for the first team ---")
13-
print(first_team.model_dump_json(indent=2))
11+
team = await sdk.teams.get(team_id="<id>")
12+
print(team.model_dump_json(indent=2))
1413

1514
except Exception as e:
1615
print(f"An error occurred: {e}")

examples/teams/list_teams.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import asyncio
2-
2+
import logging
33
from codesphere import CodesphereSDK
44

5+
logging.basicConfig(level=logging.INFO)
6+
57

68
async def main():
79
try:

src/codesphere/__init__.py

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,53 @@
1-
from .client import APIHttpClient
2-
from .resources.team.resources import TeamsResource, TeamCreate
3-
from .resources.workspace.resources import WorkspacesResource
4-
from .resources.metadata.resources import MetadataResource
5-
from .resources.workspace.models import (
6-
Workspace,
7-
WorkspaceCreate,
8-
WorkspaceUpdate,
9-
WorkspaceStatus,
10-
)
1+
"""
2+
Codesphere SDK - Python client for the Codesphere API.
3+
4+
This package provides a high-level asynchronous client for interacting
5+
with the `Codesphere Public API <https://codesphere.com/api/swagger-ui/?ref=codesphere.ghost.io#/>`_.
116
7+
Main Entrypoint:
8+
`from codesphere import CodesphereSDK`
129
13-
class CodesphereSDK:
14-
def __init__(self, token: str = None):
15-
self._http_client = APIHttpClient()
16-
self.teams: TeamsResource | None = None
17-
self.workspaces: WorkspacesResource | None = None
10+
Basic Usage:
11+
>>> import asyncio
12+
>>> from codesphere import CodesphereSDK
13+
>>>
14+
>>> async def main():
15+
>>> async with CodesphereSDK() as sdk:
16+
>>> teams = await sdk.teams.list()
17+
>>> print(teams)
18+
>>>
19+
>>> asyncio.run(main())
20+
"""
1821

19-
async def __aenter__(self):
20-
"""Wird beim Eintritt in den 'async with'-Block aufgerufen."""
21-
await self._http_client.__aenter__()
22+
import logging
23+
from .client import CodesphereSDK
2224

23-
self.teams = TeamsResource(self._http_client)
24-
self.workspaces = WorkspacesResource(self._http_client)
25-
self.metadata = MetadataResource(self._http_client)
26-
return self
25+
from .cs_types.exceptions.exceptions import CodesphereError, AuthenticationError
2726

28-
async def __aexit__(self, exc_type, exc_val, exc_tb):
29-
"""Wird beim Verlassen des 'async with'-Blocks aufgerufen."""
30-
await self._http_client.__aexit__(exc_type, exc_val, exc_tb)
27+
from .resources.team import Team, TeamCreate, TeamBase
28+
from .resources.workspace import (
29+
Workspace,
30+
WorkspaceCreate,
31+
WorkspaceUpdate,
32+
WorkspaceStatus,
33+
)
34+
from .resources.metadata import Datacenter, Characteristic, WsPlan, Image
3135

36+
logging.getLogger("codesphere").addHandler(logging.NullHandler())
3237

3338
__all__ = [
3439
"CodesphereSDK",
3540
"CodesphereError",
3641
"AuthenticationError",
3742
"Team",
3843
"TeamCreate",
39-
"TeamInList",
44+
"TeamBase",
4045
"Workspace",
4146
"WorkspaceCreate",
4247
"WorkspaceUpdate",
4348
"WorkspaceStatus",
49+
"Datacenter",
50+
"Characteristic",
51+
"WsPlan",
52+
"Image",
4453
]

src/codesphere/client.py

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,75 @@
1-
import httpx
2-
from pydantic import BaseModel
3-
from typing import Optional, Any
4-
from functools import partial
1+
"""
2+
Codesphere SDK Client
53
6-
from .config import settings
4+
This module provides the main client class, CodesphereSDK.
5+
"""
76

7+
from .cs_types.rest.http_client import APIHttpClient
8+
from .resources.metadata.resources import MetadataResource
9+
from .resources.team.resources import TeamsResource
10+
from .resources.workspace.resources import WorkspacesResource
811

9-
class APIHttpClient:
10-
def __init__(self, base_url: str = "https://codesphere.com/api"):
11-
self._token = settings.token.get_secret_value()
12-
self._base_url = base_url or str(settings.base_url)
13-
self.client: Optional[httpx.AsyncClient] = None
1412

15-
# Dynamically create get, post, put, patch, delete methods
16-
for method in ["get", "post", "put", "patch", "delete"]:
17-
setattr(self, method, partial(self.request, method.upper()))
13+
class CodesphereSDK:
14+
"""The main entrypoint for interacting with the `Codesphere Public API <https://codesphere.com/api/swagger-ui/?ref=codesphere.ghost.io#/>`_.
1815
19-
async def __aenter__(self):
20-
timeout_config = httpx.Timeout(
21-
settings.client_timeout_connect, read=settings.client_timeout_read
22-
)
23-
self.client = httpx.AsyncClient(
24-
base_url=self._base_url,
25-
headers={"Authorization": f"Bearer {self._token}"},
26-
timeout=timeout_config,
27-
)
28-
return self
16+
This class manages the HTTP client, its lifecycle,
17+
and provides access to the various API resources.
18+
19+
Primary usage is via an asynchronous context manager:
20+
21+
Usage:
22+
>>> import asyncio
23+
>>> from codesphere import CodesphereSDK
24+
>>>
25+
>>> async def main():
26+
>>> async with CodesphereSDK() as sdk:
27+
>>> teams = await sdk.teams.list()
28+
>>> print(teams)
29+
>>>
30+
>>> asyncio.run(main())
31+
32+
Attributes:
33+
teams (TeamsResource): Access to Team API operations.
34+
workspaces (WorkspacesResource): Access to Workspace API operations.
35+
metadata (MetadataResource): Access to Metadata API operations.
36+
"""
37+
38+
teams: TeamsResource
39+
"""Access to the Team API. (e.g., `sdk.teams.list()`)"""
2940

30-
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any):
31-
if self.client:
32-
await self.client.aclose()
41+
workspaces: WorkspacesResource
42+
"""Access to the Workspace API. (e.g., `sdk.workspaces.list()`)"""
3343

34-
async def request(
35-
self, method: str, endpoint: str, **kwargs: Any
36-
) -> httpx.Response:
37-
if not self.client:
38-
raise RuntimeError(
39-
"APIHttpClient must be used within an 'async with' statement."
40-
)
44+
metadata: MetadataResource
45+
"""Access to the Metadata API. (e.g., `sdk.metadata.list_plans()`)"""
4146

42-
# If a 'json' payload is a Pydantic model, automatically convert it.
43-
if "json" in kwargs and isinstance(kwargs["json"], BaseModel):
44-
kwargs["json"] = kwargs["json"].model_dump(exclude_none=True)
47+
def __init__(self):
48+
self._http_client = APIHttpClient()
49+
self.teams = TeamsResource(self._http_client)
50+
self.workspaces = WorkspacesResource(self._http_client)
51+
self.metadata = MetadataResource(self._http_client)
4552

46-
print(f"{method} {endpoint} {kwargs}")
53+
async def open(self):
54+
"""Manually opens the underlying HTTP client session.
55+
56+
Required for manual lifecycle control when not using `async with`.
57+
58+
Usage:
59+
>>> sdk = CodesphereSDK()
60+
>>> await sdk.open()
61+
>>> # ... API calls ...
62+
>>> await sdk.close()
63+
"""
64+
await self._http_client.open()
65+
66+
async def close(self):
67+
"""Manually closes the underlying HTTP client session."""
68+
await self._http_client.close()
69+
70+
async def __aenter__(self):
71+
await self.open()
72+
return self
4773

48-
response = await self.client.request(method, endpoint, **kwargs)
49-
response.raise_for_status()
50-
return response
74+
async def __aexit__(self, exc_type, exc_val, exc_tb):
75+
await self._http_client.close(exc_type, exc_val, exc_tb)

0 commit comments

Comments
 (0)