From cd8d28e1b0765231792672045869d9a9424d9550 Mon Sep 17 00:00:00 2001 From: Datata1 Date: Wed, 16 Jul 2025 19:24:24 +0200 Subject: [PATCH 1/2] feat(codesphere/workspace): add workspace resources --- CONTRIBUTING => CONTRIBUTING.md | 0 Makefile | 2 +- examples/workspaces/create_workspace.py | 28 ++++++ examples/workspaces/delete_workspace.py | 25 ++++++ .../{ => env-vars}/delete_envvars.py | 0 .../workspaces/{ => env-vars}/list_envvars.py | 0 .../workspaces/{ => env-vars}/set_envvars.py | 0 examples/workspaces/{ => git}/get_git_head.py | 0 examples/workspaces/{ => git}/git_pull.py | 0 examples/workspaces/{ => git}/git_push.py | 0 .../{ => landscape}/deploy_landscape.py | 0 .../{ => landscape}/teardown_landscape.py | 0 examples/workspaces/list_workspaces.py | 20 +++++ .../workspaces/{ => pipeline}/get_logs.py | 0 .../{ => pipeline}/get_pipeline_status.py | 0 .../{ => pipeline}/get_replica_logs.py | 0 .../{ => pipeline}/get_server_logs.py | 0 .../{ => pipeline}/start_pipeline.py | 0 .../{ => pipeline}/stop_pipeline.py | 0 examples/workspaces/update_workspace.py | 28 ++++++ src/codesphere/__init__.py | 29 ++++-- src/codesphere/client.py | 35 +++----- src/codesphere/resources/base.py | 11 ++- src/codesphere/resources/domain/models.py | 37 -------- src/codesphere/resources/domain/resource.py | 21 ----- .../codesphere/resources/domain/resources.py | 0 .../codesphere/resources/metadata/models.py | 0 .../resources/metadata/resources.py | 0 src/codesphere/resources/team/models.py | 14 +-- .../team/{resource.py => resources.py} | 0 .../resources/workspace/env-vars/modely.py | 0 .../resources/workspace/env-vars/resources.py | 0 .../resources/workspace/landscape/models.py | 0 .../workspace/landscape/resources.py | 0 src/codesphere/resources/workspace/models.py | 90 +++++++++++++++++++ .../resources/workspace/pipeline/models.py | 0 .../resources/workspace/pipeline/resources.py | 0 .../resources/workspace/resources.py | 39 ++++++++ 38 files changed, 279 insertions(+), 100 deletions(-) rename CONTRIBUTING => CONTRIBUTING.md (100%) rename examples/workspaces/{ => env-vars}/delete_envvars.py (100%) rename examples/workspaces/{ => env-vars}/list_envvars.py (100%) rename examples/workspaces/{ => env-vars}/set_envvars.py (100%) rename examples/workspaces/{ => git}/get_git_head.py (100%) rename examples/workspaces/{ => git}/git_pull.py (100%) rename examples/workspaces/{ => git}/git_push.py (100%) rename examples/workspaces/{ => landscape}/deploy_landscape.py (100%) rename examples/workspaces/{ => landscape}/teardown_landscape.py (100%) rename examples/workspaces/{ => pipeline}/get_logs.py (100%) rename examples/workspaces/{ => pipeline}/get_pipeline_status.py (100%) rename examples/workspaces/{ => pipeline}/get_replica_logs.py (100%) rename examples/workspaces/{ => pipeline}/get_server_logs.py (100%) rename examples/workspaces/{ => pipeline}/start_pipeline.py (100%) rename examples/workspaces/{ => pipeline}/stop_pipeline.py (100%) delete mode 100644 src/codesphere/resources/domain/resource.py rename examples/workspaces/get_workspace_status.py => src/codesphere/resources/domain/resources.py (100%) rename examples/workspaces/get_workspaces.py => src/codesphere/resources/metadata/models.py (100%) create mode 100644 src/codesphere/resources/metadata/resources.py rename src/codesphere/resources/team/{resource.py => resources.py} (100%) create mode 100644 src/codesphere/resources/workspace/env-vars/modely.py create mode 100644 src/codesphere/resources/workspace/env-vars/resources.py create mode 100644 src/codesphere/resources/workspace/landscape/models.py create mode 100644 src/codesphere/resources/workspace/landscape/resources.py create mode 100644 src/codesphere/resources/workspace/models.py create mode 100644 src/codesphere/resources/workspace/pipeline/models.py create mode 100644 src/codesphere/resources/workspace/pipeline/resources.py create mode 100644 src/codesphere/resources/workspace/resources.py diff --git a/CONTRIBUTING b/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING rename to CONTRIBUTING.md diff --git a/Makefile b/Makefile index 0f20c73..475b532 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ commit: ## Starts Commitizen for a guided commit message lint: ## Checks code quality with ruff @echo ">>> Checking code quality with ruff..." - uv run ruff check src + uv run ruff check src --fix format: ## Formats code with ruff @echo ">>> Formatting code with ruff..." diff --git a/examples/workspaces/create_workspace.py b/examples/workspaces/create_workspace.py index e69de29..c8b19d6 100644 --- a/examples/workspaces/create_workspace.py +++ b/examples/workspaces/create_workspace.py @@ -0,0 +1,28 @@ +import asyncio +import pprint +from codesphere import CodesphereSDK, WorkspaceCreate + + +async def main(): + """Creates a new workspace in a specific team.""" + team_id = 12345 + + async with CodesphereSDK() as sdk: + print(f"--- Creating a new workspace in team {team_id} ---") + + workspace_data = WorkspaceCreate( + name="my-new-sdk-workspace-3", + planId=8, + teamId=int(team_id), + isPrivateRepo=True, + replicas=1, + ) + + created_workspace = await sdk.workspaces.create(data=workspace_data) + + print("\n--- Details of successfully created workspace ---") + pprint.pprint(created_workspace.model_dump()) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/workspaces/delete_workspace.py b/examples/workspaces/delete_workspace.py index e69de29..6a4b6c7 100644 --- a/examples/workspaces/delete_workspace.py +++ b/examples/workspaces/delete_workspace.py @@ -0,0 +1,25 @@ +import asyncio +from codesphere import CodesphereSDK + + +async def main(): + """Deletes a specific workspace.""" + + workspace_id_to_delete = 12345 + + async with CodesphereSDK() as sdk: + print(f"--- Fetching workspace with ID: {workspace_id_to_delete} ---") + workspace_to_delete = await sdk.workspaces.get( + workspace_id=workspace_id_to_delete + ) + + print(f"\n--- Deleting workspace: '{workspace_to_delete.name}' ---") + + # This is a destructive action! + await workspace_to_delete.delete() + + print(f"Workspace '{workspace_to_delete.name}' has been successfully deleted.") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/workspaces/delete_envvars.py b/examples/workspaces/env-vars/delete_envvars.py similarity index 100% rename from examples/workspaces/delete_envvars.py rename to examples/workspaces/env-vars/delete_envvars.py diff --git a/examples/workspaces/list_envvars.py b/examples/workspaces/env-vars/list_envvars.py similarity index 100% rename from examples/workspaces/list_envvars.py rename to examples/workspaces/env-vars/list_envvars.py diff --git a/examples/workspaces/set_envvars.py b/examples/workspaces/env-vars/set_envvars.py similarity index 100% rename from examples/workspaces/set_envvars.py rename to examples/workspaces/env-vars/set_envvars.py diff --git a/examples/workspaces/get_git_head.py b/examples/workspaces/git/get_git_head.py similarity index 100% rename from examples/workspaces/get_git_head.py rename to examples/workspaces/git/get_git_head.py diff --git a/examples/workspaces/git_pull.py b/examples/workspaces/git/git_pull.py similarity index 100% rename from examples/workspaces/git_pull.py rename to examples/workspaces/git/git_pull.py diff --git a/examples/workspaces/git_push.py b/examples/workspaces/git/git_push.py similarity index 100% rename from examples/workspaces/git_push.py rename to examples/workspaces/git/git_push.py diff --git a/examples/workspaces/deploy_landscape.py b/examples/workspaces/landscape/deploy_landscape.py similarity index 100% rename from examples/workspaces/deploy_landscape.py rename to examples/workspaces/landscape/deploy_landscape.py diff --git a/examples/workspaces/teardown_landscape.py b/examples/workspaces/landscape/teardown_landscape.py similarity index 100% rename from examples/workspaces/teardown_landscape.py rename to examples/workspaces/landscape/teardown_landscape.py diff --git a/examples/workspaces/list_workspaces.py b/examples/workspaces/list_workspaces.py index e69de29..8e1e6cb 100644 --- a/examples/workspaces/list_workspaces.py +++ b/examples/workspaces/list_workspaces.py @@ -0,0 +1,20 @@ +import asyncio +import pprint +from codesphere import CodesphereSDK + + +async def main(): + """Fetches a team and lists all workspaces within it.""" + async with CodesphereSDK() as sdk: + teams = await sdk.teams.list() + workspaces = await sdk.workspaces.list_by_team(team_id=teams[0].id) + + for workspace in workspaces: + pprint.pprint(workspace.model_dump()) + print(f"Found {len(workspaces)} workspace(s):") + for ws in workspaces: + print(f" - ID: {ws.id}, Name: {ws.name}, Status: {await ws.get_status()}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/workspaces/get_logs.py b/examples/workspaces/pipeline/get_logs.py similarity index 100% rename from examples/workspaces/get_logs.py rename to examples/workspaces/pipeline/get_logs.py diff --git a/examples/workspaces/get_pipeline_status.py b/examples/workspaces/pipeline/get_pipeline_status.py similarity index 100% rename from examples/workspaces/get_pipeline_status.py rename to examples/workspaces/pipeline/get_pipeline_status.py diff --git a/examples/workspaces/get_replica_logs.py b/examples/workspaces/pipeline/get_replica_logs.py similarity index 100% rename from examples/workspaces/get_replica_logs.py rename to examples/workspaces/pipeline/get_replica_logs.py diff --git a/examples/workspaces/get_server_logs.py b/examples/workspaces/pipeline/get_server_logs.py similarity index 100% rename from examples/workspaces/get_server_logs.py rename to examples/workspaces/pipeline/get_server_logs.py diff --git a/examples/workspaces/start_pipeline.py b/examples/workspaces/pipeline/start_pipeline.py similarity index 100% rename from examples/workspaces/start_pipeline.py rename to examples/workspaces/pipeline/start_pipeline.py diff --git a/examples/workspaces/stop_pipeline.py b/examples/workspaces/pipeline/stop_pipeline.py similarity index 100% rename from examples/workspaces/stop_pipeline.py rename to examples/workspaces/pipeline/stop_pipeline.py diff --git a/examples/workspaces/update_workspace.py b/examples/workspaces/update_workspace.py index e69de29..19dd56f 100644 --- a/examples/workspaces/update_workspace.py +++ b/examples/workspaces/update_workspace.py @@ -0,0 +1,28 @@ +import asyncio +import pprint +from codesphere import CodesphereSDK, WorkspaceUpdate + + +async def main(): + """Fetches a workspace and updates its name.""" + workspace_id_to_update = 12245 + + async with CodesphereSDK() as sdk: + print(f"--- Fetching workspace with ID: {workspace_id_to_update} ---") + workspace = await sdk.workspaces.get(workspace_id=workspace_id_to_update) + + print("Original workspace details:") + pprint.pprint(workspace.model_dump()) + + update_data = WorkspaceUpdate(name="updated workspace", planId=8) + + print(f"\n--- Updating workspace name to '{update_data.name}' ---") + + await workspace.update(data=update_data) + + print("\n--- Workspace successfully updated. New details: ---") + pprint.pprint(workspace.model_dump()) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/codesphere/__init__.py b/src/codesphere/__init__.py index c3ef8b6..0b3b9ab 100644 --- a/src/codesphere/__init__.py +++ b/src/codesphere/__init__.py @@ -1,24 +1,43 @@ # z.B. in src/codesphere/__init__.py oder einer eigenen client.py from .client import APIHttpClient -from .resources.team.resource import TeamsResource +from .resources.team.resources import TeamsResource +from .resources.workspace.resources import WorkspacesResource +from .resources.workspace.models import ( + Workspace, + WorkspaceCreate, + WorkspaceUpdate, + WorkspaceStatus, +) class CodesphereSDK: def __init__(self, token: str = None): self._http_client = APIHttpClient() - # Die Ressourcen werden erst im __aenter__ initialisiert self.teams: TeamsResource | None = None + self.workspaces: WorkspacesResource | None = None async def __aenter__(self): """Wird beim Eintritt in den 'async with'-Block aufgerufen.""" - # Startet den internen HTTP-Client await self._http_client.__aenter__() - # Initialisiert die Ressourcen-Handler mit dem aktiven Client self.teams = TeamsResource(self._http_client) + self.workspaces = WorkspacesResource(self._http_client) return self async def __aexit__(self, exc_type, exc_val, exc_tb): """Wird beim Verlassen des 'async with'-Blocks aufgerufen.""" - # Schließt den internen HTTP-Client sicher await self._http_client.__aexit__(exc_type, exc_val, exc_tb) + + +__all__ = [ + "CodesphereSDK", + "CodesphereError", + "AuthenticationError", + "Team", + "TeamCreate", + "TeamInList", + "Workspace", + "WorkspaceCreate", + "WorkspaceUpdate", + "WorkspaceStatus", +] diff --git a/src/codesphere/client.py b/src/codesphere/client.py index 3b0aa34..df8139e 100644 --- a/src/codesphere/client.py +++ b/src/codesphere/client.py @@ -2,6 +2,7 @@ import httpx from pydantic import BaseModel from typing import Optional, Any +from functools import partial from .resources.exceptions.exceptions import AuthenticationError @@ -15,7 +16,11 @@ def __init__(self, base_url: str = "https://codesphere.com/api"): self._token = auth_token self._base_url = base_url - self.client: Optional[httpx.Client] = None + self.client: Optional[httpx.AsyncClient] = None + + # Dynamically create get, post, put, patch, delete methods + for method in ["get", "post", "put", "patch", "delete"]: + setattr(self, method, partial(self.request, method.upper())) async def __aenter__(self): self.client = httpx.AsyncClient( @@ -31,28 +36,16 @@ async def request( self, method: str, endpoint: str, **kwargs: Any ) -> httpx.Response: if not self.client: - raise RuntimeError("APIHttpClient must be used within a 'with' statement.") + raise RuntimeError( + "APIHttpClient must be used within an 'async with' statement." + ) + + # If a 'json' payload is a Pydantic model, automatically convert it. + if "json" in kwargs and isinstance(kwargs["json"], BaseModel): + kwargs["json"] = kwargs["json"].model_dump(exclude_none=True) + print(f"{method} {endpoint} {kwargs}") response = await self.client.request(method, endpoint, **kwargs) response.raise_for_status() return response - - async def get(self, endpoint: str, json: Optional[dict] = None) -> httpx.Response: - json_data = json.model_dump() if json else None - return await self.request("GET", endpoint, json=json_data) - - async def post( - self, endpoint: str, json: Optional[BaseModel] = None - ) -> httpx.Response: - json_data = json.model_dump() if json else None - return await self.request("POST", endpoint, json=json_data) - - async def put( - self, endpoint: str, json: Optional[BaseModel] = None - ) -> httpx.Response: - json_data = json.model_dump() if json else None - return await self.request("PUT", endpoint, json=json_data) - - async def delete(self, endpoint: str) -> httpx.Response: - return await self.request("DELETE", endpoint) diff --git a/src/codesphere/resources/base.py b/src/codesphere/resources/base.py index ccd6ea6..a0b4376 100644 --- a/src/codesphere/resources/base.py +++ b/src/codesphere/resources/base.py @@ -39,11 +39,14 @@ async def _execute_operation(self, operation: APIOperation, **kwargs: Any) -> An endpoint = operation.endpoint_template.format(**format_args) params = kwargs.get("params") + json_data_obj = kwargs.get("data") payload = None - if operation.input_model: + if json_data_obj and isinstance(json_data_obj, BaseModel): + payload = json_data_obj.model_dump(exclude_none=True) + elif operation.input_model: input_data = operation.input_model(**kwargs) - payload = input_data.model_dump() + payload = input_data.model_dump(exclude_none=True) response = await self._http_client.request( method=operation.method, endpoint=endpoint, json=payload, params=params @@ -54,6 +57,10 @@ async def _execute_operation(self, operation: APIOperation, **kwargs: Any) -> An json_response = response.json() + # print("--- RAW API RESPONSE ---") + # pprint.pprint(json_response) + # print("------------------------") + origin = get_origin(operation.response_model) if origin is list or origin is List: item_model = get_args(operation.response_model)[0] diff --git a/src/codesphere/resources/domain/models.py b/src/codesphere/resources/domain/models.py index 4f4ed6f..e69de29 100644 --- a/src/codesphere/resources/domain/models.py +++ b/src/codesphere/resources/domain/models.py @@ -1,37 +0,0 @@ -from __future__ import annotations -from pydantic import BaseModel, PrivateAttr -from typing import Optional, TYPE_CHECKING -from datetime import datetime - -if TYPE_CHECKING: - from ...client import APIHttpClient - - -class Domain(BaseModel): - _http_client: Optional[APIHttpClient] = PrivateAttr(default=None) - - id: str - name: str - verified_at: Optional[datetime] = None - - async def delete(self) -> None: - """Deletes this specific domain instance via the API.""" - if not self._http_client: - raise RuntimeError("Cannot make API calls on a detached model.") - if not self.id: - raise ValueError("Cannot delete a domain without an ID.") - - await self._http_client.delete(f"/domains/{self.id}") - print(f"Domain '{self.name}' has been deleted.") - - async def save(self) -> None: - """Updates this domain with its current data.""" - if not self._http_client: - raise RuntimeError("Cannot make API calls on a detached model.") - if not self.id: - raise ValueError("Cannot update a domain without an ID.") - - update_data = self.model_dump(exclude={"id"}) - - await self._http_client.put(f"/domains/{self.id}", json=update_data) - print(f"Domain '{self.name}' has been updated.") diff --git a/src/codesphere/resources/domain/resource.py b/src/codesphere/resources/domain/resource.py deleted file mode 100644 index 9f841e1..0000000 --- a/src/codesphere/resources/domain/resource.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import List -from ..base import ResourceBase, APIOperation -from .models import Domain - - -class DomainsResource(ResourceBase): - def __init__(self, http_client, team_id: str): - super().__init__(http_client) - self.team_id = team_id - - list = APIOperation( - method="GET", - endpoint_template="/domains/team/{team_id}", - response_model=List[Domain], - ) - - get = APIOperation( - method="GET", - endpoint_template="/domains/team/{team_id}/{domain_name}", - response_model=Domain, - ) diff --git a/examples/workspaces/get_workspace_status.py b/src/codesphere/resources/domain/resources.py similarity index 100% rename from examples/workspaces/get_workspace_status.py rename to src/codesphere/resources/domain/resources.py diff --git a/examples/workspaces/get_workspaces.py b/src/codesphere/resources/metadata/models.py similarity index 100% rename from examples/workspaces/get_workspaces.py rename to src/codesphere/resources/metadata/models.py diff --git a/src/codesphere/resources/metadata/resources.py b/src/codesphere/resources/metadata/resources.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codesphere/resources/team/models.py b/src/codesphere/resources/team/models.py index f21afa1..9c7489a 100644 --- a/src/codesphere/resources/team/models.py +++ b/src/codesphere/resources/team/models.py @@ -1,9 +1,7 @@ from __future__ import annotations -from pydantic import BaseModel, PrivateAttr, model_validator +from pydantic import BaseModel, PrivateAttr from typing import Optional, TYPE_CHECKING -from ..domain.resource import DomainsResource - if TYPE_CHECKING: from ...client import APIHttpClient @@ -41,16 +39,6 @@ class Team(TeamBase): """ _http_client: Optional[APIHttpClient] = PrivateAttr(default=None) - _domains: Optional[DomainsResource] = PrivateAttr(default=None) - - @model_validator(mode="after") - def setup_sub_resources(self) -> "Team": - """Creates the sub-resources after initialization.""" - if self._http_client: - self._domains = DomainsResource( - http_client=self._http_client, team_id=str(self.id) - ) - return self async def delete(self) -> None: """Deletes this team via the API.""" diff --git a/src/codesphere/resources/team/resource.py b/src/codesphere/resources/team/resources.py similarity index 100% rename from src/codesphere/resources/team/resource.py rename to src/codesphere/resources/team/resources.py diff --git a/src/codesphere/resources/workspace/env-vars/modely.py b/src/codesphere/resources/workspace/env-vars/modely.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codesphere/resources/workspace/env-vars/resources.py b/src/codesphere/resources/workspace/env-vars/resources.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codesphere/resources/workspace/landscape/models.py b/src/codesphere/resources/workspace/landscape/models.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codesphere/resources/workspace/landscape/resources.py b/src/codesphere/resources/workspace/landscape/resources.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codesphere/resources/workspace/models.py b/src/codesphere/resources/workspace/models.py new file mode 100644 index 0000000..105506b --- /dev/null +++ b/src/codesphere/resources/workspace/models.py @@ -0,0 +1,90 @@ +from __future__ import annotations +from pydantic import BaseModel, PrivateAttr +from typing import Optional, List, TYPE_CHECKING + +if TYPE_CHECKING: + from ...client import APIHttpClient + + +class EnvVarPair(BaseModel): + name: str + value: str + + +class WorkspaceCreate(BaseModel): + teamId: int + name: str + planId: int + baseImage: Optional[str] = None + isPrivateRepo: bool = True + replicas: int = 1 + gitUrl: Optional[str] = None + initialBranch: Optional[str] = None + cloneDepth: Optional[int] = None + sourceWorkspaceId: Optional[int] = None + welcomeMessage: Optional[str] = None + vpnConfig: Optional[str] = None + restricted: Optional[bool] = None + env: Optional[List[EnvVarPair]] = None + + +# Defines the request body for PATCH /workspaces/{workspaceId} +class WorkspaceUpdate(BaseModel): + planId: Optional[int] = None + baseImage: Optional[str] = None + name: Optional[str] = None + replicas: Optional[int] = None + vpnConfig: Optional[str] = None + restricted: Optional[bool] = None + + +# Defines the response from GET /workspaces/{workspaceId}/status +class WorkspaceStatus(BaseModel): + isRunning: bool + + +# This is the main model for a workspace, returned by GET, POST, and LIST +class Workspace(BaseModel): + _http_client: Optional[APIHttpClient] = PrivateAttr(default=None) + + id: int + teamId: int + name: str + planId: int + isPrivateRepo: bool + replicas: int + baseImage: Optional[str] = None + dataCenterId: int + userId: int + gitUrl: Optional[str] = None + initialBranch: Optional[str] = None + sourceWorkspaceId: Optional[int] = None + welcomeMessage: Optional[str] = None + vpnConfig: Optional[str] = None + restricted: bool + + async def update(self, data: WorkspaceUpdate) -> None: + """Updates this workspace with new data.""" + if not self._http_client: + raise RuntimeError("Cannot make API calls on a detached model.") + + await self._http_client.patch( + f"/workspaces/{self.id}", json=data.model_dump(exclude_unset=True) + ) + # Optionally, update the local object's state + for key, value in data.model_dump(exclude_unset=True).items(): + setattr(self, key, value) + + async def delete(self) -> None: + """Deletes this workspace.""" + if not self._http_client: + raise RuntimeError("Cannot make API calls on a detached model.") + await self._http_client.delete(f"/workspaces/{self.id}") + + async def get_status(self) -> WorkspaceStatus: + """Gets the running status of this workspace.""" + if not self._http_client: + raise RuntimeError("Cannot make API calls on a detached model.") + + response = await self._http_client.get(f"/workspaces/{self.id}/status") + return WorkspaceStatus.model_validate(response.json()) diff --git a/src/codesphere/resources/workspace/pipeline/models.py b/src/codesphere/resources/workspace/pipeline/models.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codesphere/resources/workspace/pipeline/resources.py b/src/codesphere/resources/workspace/pipeline/resources.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codesphere/resources/workspace/resources.py b/src/codesphere/resources/workspace/resources.py new file mode 100644 index 0000000..58f640a --- /dev/null +++ b/src/codesphere/resources/workspace/resources.py @@ -0,0 +1,39 @@ +from typing import List +from ..base import ResourceBase, APIOperation +from .models import Workspace, WorkspaceCreate, WorkspaceUpdate + + +class WorkspacesResource(ResourceBase): + """Manages all API operations for the Workspace resource.""" + + list_by_team = APIOperation( + method="GET", + endpoint_template="/workspaces/team/{team_id}", + response_model=List[Workspace], + ) + + get = APIOperation( + method="GET", + endpoint_template="/workspaces/{workspace_id}", + response_model=Workspace, + ) + + create = APIOperation( + method="POST", + endpoint_template="/workspaces", + input_model=WorkspaceCreate, + response_model=Workspace, + ) + + update = APIOperation( + method="PATCH", + endpoint_template="/workspaces/{workspace_id}", + input_model=WorkspaceUpdate, + response_model=None, + ) + + delete = APIOperation( + method="DELETE", + endpoint_template="/workspaces/{workspace_id}", + response_model=None, + ) From e11c91b7c91cf70b37f8dd2a62b93dd51c319f8e Mon Sep 17 00:00:00 2001 From: Datata1 Date: Wed, 16 Jul 2025 19:24:25 +0200 Subject: [PATCH 2/2] =?UTF-8?q?bump:=20version=200.1.1=20=E2=86=92=200.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5691d47..bc10b43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.2.0 (2025-07-16) + +### Feat + +- **codesphere/workspace**: add workspace resources + ## v0.1.1 (2025-07-16) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 7af67a0..303651a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "codesphere" -version = "0.1.1" +version = "0.2.0" description = "Use Codesphere within python scripts." readme = "README.md" license = { file="LICENSE" } diff --git a/uv.lock b/uv.lock index 5aa1dfd..1e09c88 100644 --- a/uv.lock +++ b/uv.lock @@ -182,7 +182,7 @@ wheels = [ [[package]] name = "codesphere" -version = "0.1.1" +version = "0.2.0" source = { editable = "." } dependencies = [ { name = "aiohttp" },