Skip to content

Commit 511e941

Browse files
author
Datata1
committed
test: review
1 parent 2dd3b2f commit 511e941

39 files changed

+167
-823
lines changed

.env.example

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
# Required: Your Codesphere API token
2-
# Get this from your Codesphere account settings
31
CS_TOKEN=your-api-token-here
42
# CS_BASE_URL=https://codesphere.com/api
53
# CS_TEST_TEAM_ID=12345
6-
# CS_TEST_DC_ID=1
4+
# CS_TEST_DC_ID=2

.github/copilot-instructions.md

Lines changed: 49 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -1,242 +1,53 @@
1-
# Copilot Instructions for Codesphere Python SDK
1+
# Codesphere SDK - AI Instructions
22

3-
## Project Context
4-
5-
This repository contains the **Codesphere Python SDK**, an official asynchronous Python client for the [Codesphere Public API](https://codesphere.com/api/swagger-ui/). It provides a high-level, type-safe interface for managing Codesphere resources including Teams, Workspaces, Domains, Environment Variables, and Metadata.
6-
7-
The SDK follows a resource-based architecture where API operations are defined declaratively and executed through a centralized handler system.
3+
## Architecture & Core
4+
- **Async-First:** All I/O operations MUST be `async/await`.
5+
- **Base Classes:**
6+
- Models: Use `core.base.CamelModel` (handles camelCase API conversion).
7+
- Resources: Inherit from `ResourceBase` + `_APIOperationExecutor`.
8+
- **HTTP:** Use `APIHttpClient` (wraps httpx). Raise `httpx.HTTPStatusError` for API errors.
89

910
## Project Structure
10-
11-
```
12-
src/codesphere/
13-
├── __init__.py # Public API exports
14-
├── client.py # Main SDK entry point (CodesphereSDK)
15-
├── config.py # Settings via pydantic-settings
16-
├── exceptions.py # Custom exception classes
17-
├── http_client.py # Async HTTP client wrapper (APIHttpClient)
18-
├── utils.py # Utility functions
19-
├── core/ # Core SDK infrastructure
20-
│ ├── base.py # Base classes (ResourceBase, CamelModel, ResourceList)
21-
│ ├── handler.py # API operation executor (_APIOperationExecutor, APIRequestHandler)
22-
│ └── operations.py # APIOperation definition and AsyncCallable type
23-
└── resources/ # API resource implementations
24-
├── metadata/ # Datacenters, Plans, Images
25-
├── team/ # Teams and nested Domains
26-
│ └── domain/ # Domain management (schemas, operations, manager)
27-
└── workspace/ # Workspaces and nested resources
28-
├── envVars/ # Environment variables management
29-
├── landscape/ # (Placeholder)
30-
└── pipeline/ # (Placeholder)
31-
32-
tests/ # Test files mirroring src structure
33-
├── conftest.py # Shared unit test fixtures
34-
├── core/ # Core infrastructure tests
35-
├── resources/ # Resource unit tests
36-
└── integration/ # Integration tests (real API)
37-
├── conftest.py # Integration fixtures & workspace setup
38-
├── test_domains.py
39-
├── test_env_vars.py
40-
├── test_metadata.py
41-
├── test_teams.py
42-
└── test_workspaces.py
43-
44-
examples/ # Usage examples organized by resource type
45-
```
46-
47-
## Coding Guidelines
48-
49-
### General Principles
50-
51-
- **Async-First**: All API operations MUST be async. Use `async/await` syntax consistently.
52-
- **Type Hints**: Always provide complete type annotations for function parameters, return values, and class attributes.
53-
- **Pydantic Models**: Use Pydantic `BaseModel` for all data structures. Prefer `CamelModel` for API payloads to handle camelCase conversion.
54-
55-
### Naming Conventions
56-
57-
- **Variables/Functions**: Use `snake_case` (e.g., `workspace_id`, `list_datacenters`)
58-
- **Classes**: Use `PascalCase` (e.g., `WorkspaceCreate`, `APIHttpClient`)
59-
- **Constants/Operations**: Use `UPPER_SNAKE_CASE` with leading underscore for internal operations (e.g., `_LIST_BY_TEAM_OP`)
60-
- **Private Attributes**: Prefix with underscore (e.g., `_http_client`, `_token`)
61-
62-
### Resource Pattern
63-
64-
When adding new API resources, follow this structure:
65-
66-
1. **schemas.py**: Define Pydantic models for Create, Base, Update, and the main resource class
67-
2. **operations.py**: Define `APIOperation` instances for each endpoint
68-
3. **resources.py**: Create the resource class extending `ResourceBase` with operation fields
69-
4. **__init__.py**: Export public classes
70-
71-
```python
72-
# Example operation definition
73-
_GET_OP = APIOperation(
74-
method="GET",
75-
endpoint_template="/resources/{resource_id}",
76-
response_model=ResourceModel,
77-
)
78-
79-
# Example resource method
80-
async def get(self, resource_id: int) -> ResourceModel:
81-
return await self.get_op(resource_id=resource_id)
82-
```
83-
84-
### Model Guidelines
85-
86-
- Extend `CamelModel` for API request/response models (automatic camelCase aliasing)
87-
- Extend `_APIOperationExecutor` for models that can perform operations on themselves
88-
- Use `Field(default=..., exclude=True)` for operation callables
89-
- Use `@cached_property` for lazy-loaded sub-managers (e.g., `workspace.env_vars`)
90-
91-
```python
92-
class Workspace(WorkspaceBase, _APIOperationExecutor):
93-
delete_op: AsyncCallable[None] = Field(default=_DELETE_OP, exclude=True)
94-
95-
async def delete(self) -> None:
96-
await self.delete_op()
97-
```
98-
99-
### Error Handling
100-
101-
- Raise `httpx.HTTPStatusError` for HTTP errors (handled by `APIHttpClient`)
102-
- Raise `RuntimeError` for SDK misuse (e.g., accessing resources without context manager)
103-
- Use custom exceptions from `exceptions.py` for SDK-specific errors
104-
105-
### Code Style
106-
107-
- Line length: 88 characters (Ruff/Black standard)
108-
- Indentation: 4 spaces
109-
- Quotes: Double quotes for strings
110-
- Imports: Group stdlib, third-party, and local imports
111-
112-
---
113-
114-
## Testing Guidelines
115-
116-
When adding features or making changes, appropriate tests are **required**. The SDK uses two types of tests:
117-
118-
### Unit Tests
119-
120-
Located in `tests/` (excluding `tests/integration/`). These mock HTTP responses and test SDK logic in isolation.
121-
122-
**When to write unit tests:**
123-
- New Pydantic models or schemas
124-
- New API operations
125-
- Core handler or utility logic changes
126-
127-
**Unit test patterns:**
128-
129-
```python
130-
import pytest
131-
from dataclasses import dataclass
132-
from unittest.mock import AsyncMock, MagicMock
133-
134-
# Use @dataclass for parameterized test cases
135-
@dataclass
136-
class WorkspaceTestCase:
137-
name: str
138-
workspace_id: int
139-
expected_name: str
140-
141-
@pytest.mark.asyncio
142-
@pytest.mark.parametrize("case", [
143-
WorkspaceTestCase(name="basic", workspace_id=123, expected_name="test-ws"),
144-
])
145-
async def test_workspace_get(case: WorkspaceTestCase):
146-
"""Should fetch a workspace by ID."""
147-
mock_response = MagicMock()
148-
mock_response.json.return_value = {"id": case.workspace_id, "name": case.expected_name}
149-
150-
# Test implementation...
151-
```
152-
153-
### Integration Tests
154-
155-
Located in `tests/integration/`. These run against the real Codesphere API.
156-
157-
**When to write integration tests:**
158-
- New API endpoints (CRUD operations)
159-
- Changes to request/response serialization
160-
- Schema field changes (detect API contract changes early)
161-
162-
**Integration test patterns:**
163-
164-
```python
165-
import pytest
166-
from codesphere import CodesphereSDK
167-
168-
pytestmark = [pytest.mark.integration, pytest.mark.asyncio]
169-
170-
class TestMyResourceIntegration:
171-
"""Integration tests for MyResource endpoints."""
172-
173-
async def test_list_resources(self, sdk_client: CodesphereSDK):
174-
"""Should retrieve a list of resources."""
175-
resources = await sdk_client.my_resource.list()
176-
177-
assert isinstance(resources, list)
178-
179-
async def test_create_and_delete(
180-
self,
181-
sdk_client: CodesphereSDK,
182-
test_team_id: int,
183-
):
184-
"""Should create and delete a resource."""
185-
resource = await sdk_client.my_resource.create(name="test")
186-
187-
try:
188-
assert resource.name == "test"
189-
finally:
190-
# Always cleanup created resources
191-
await resource.delete()
192-
```
193-
194-
**Available integration test fixtures** (from `tests/integration/conftest.py`):
195-
11+
- `src/codesphere/core/`: Base classes & handlers.
12+
- `src/codesphere/resources/`: Resource implementations (follow `schemas.py`, `operations.py`, `resources.py` pattern).
13+
- `tests/integration/`: Real API tests (require `CS_TOKEN`).
14+
- `tests/unit/`: Mocked logic tests.
15+
16+
## Resource Implementation Pattern
17+
When adding resources, strictly follow this pattern:
18+
19+
1. **`schemas.py`**: Define Pydantic models (inherit `CamelModel`).
20+
2. **`operations.py`**: Define `APIOperation` constants.
21+
```python
22+
_GET_OP = APIOperation(method="GET", endpoint_template="/res/{id}", response_model=ResModel)
23+
```
24+
3. **`resources.py`**: Implementation logic.
25+
```python
26+
class MyResource(ResourceBase, _APIOperationExecutor):
27+
# Operation callable (exclude from model dump)
28+
delete_op: AsyncCallable[None] = Field(default=_DELETE_OP, exclude=True)
29+
30+
async def delete(self) -> None:
31+
await self.delete_op()
32+
```
33+
34+
## Testing Rules
35+
- **Unit Tests (`tests/`):** MUST mock all HTTP calls (`unittest.mock.AsyncMock`).
36+
- **Integration Tests (`tests/integration/`):**
37+
- Use `pytest.mark.integration`.
38+
- Use provided fixtures: `sdk_client` (fresh client), `test_team_id`, `test_workspace`.
39+
- **Cleanup:** Always delete created resources in a `try...finally` block.
40+
41+
## Key Fixtures & Env Vars
19642
| Fixture | Scope | Description |
197-
|---------|-------|-------------|
198-
| `sdk_client` | function | Fresh SDK client for each test |
199-
| `session_sdk_client` | session | Shared SDK client for setup/teardown |
200-
| `test_team_id` | session | Team ID for testing |
201-
| `test_workspace` | session | Single pre-created workspace |
202-
| `test_workspaces` | session | List of 2 test workspaces |
203-
| `integration_token` | session | The API token (from `CS_TOKEN`) |
204-
205-
**Environment variables:**
206-
207-
| Variable | Required | Description |
208-
|----------|----------|-------------|
209-
| `CS_TOKEN` | Yes | Codesphere API token |
210-
| `CS_TEST_TEAM_ID` | No | Specific team ID (defaults to first team) |
211-
| `CS_TEST_DC_ID` | No | Datacenter ID (defaults to 1) |
212-
213-
### Running Tests
214-
215-
```bash
216-
make test # Run unit tests only
217-
make test-unit # Run unit tests only (explicit)
218-
make test-integration # Run integration tests (requires CS_TOKEN)
219-
```
220-
221-
### Test Requirements Checklist
222-
223-
When submitting a PR, ensure:
224-
225-
- [ ] **New endpoints** have integration tests covering all operations
226-
- [ ] **New models** have unit tests for serialization/deserialization
227-
- [ ] **Bug fixes** include a test that reproduces the issue
228-
- [ ] **All tests pass** locally before pushing
229-
230-
---
231-
232-
## Development Commands
233-
234-
```bash
235-
make install # Set up development environment
236-
make lint # Run Ruff linter
237-
make format # Format code with Ruff
238-
make test # Run unit tests
239-
make test-unit # Run unit tests (excludes integration)
240-
make test-integration # Run integration tests
241-
make commit # Guided commit with Commitizen
242-
```
43+
|---|---|---|
44+
| `sdk_client` | func | Fresh `CodesphereSDK` instance. |
45+
| `test_team_id` | session | ID from `CS_TEST_TEAM_ID` (or default). |
46+
| `integration_token` | session | Token from `CS_TOKEN`. |
47+
48+
## Style & Naming
49+
- **Classes:** PascalCase (`WorkspaceCreate`).
50+
- **Vars:** snake_case.
51+
- **Internal Ops:** UPPER_SNAKE (`_LIST_OP`).
52+
- **Private:** Leading underscore (`_http_client`).
53+
- **Typing:** Strict type hints required.

.github/workflows/integration.yml

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,7 @@
11
name: Integration Tests
22

33
on:
4-
# Manual trigger with optional inputs
54
workflow_dispatch:
6-
inputs:
7-
test_team_id:
8-
description: "Team ID to use for testing (optional)"
9-
required: false
10-
type: string
11-
test_dc_id:
12-
description: "Datacenter ID for test resources"
13-
required: false
14-
default: "1"
15-
type: string
16-
17-
# Run on pull requests
185
pull_request:
196
types: [opened, synchronize, reopened, ready_for_review]
207
paths:
@@ -24,6 +11,8 @@ on:
2411

2512
permissions:
2613
contents: read
14+
checks: write
15+
pull-requests: write
2716

2817
env:
2918
PYTHON_VERSION: "3.12"
@@ -32,8 +21,7 @@ jobs:
3221
integration-tests:
3322
name: Run Integration Tests
3423
runs-on: ubuntu-latest
35-
# Only run if the secret is available (prevents failures on forks)
36-
if: ${{ github.repository == 'Datata1/codesphere-python' || github.event_name == 'workflow_dispatch' }}
24+
if: ${{ github.repository == 'Datata1/codesphere-python' }}
3725

3826
steps:
3927
- name: Checkout repository
@@ -43,15 +31,16 @@ jobs:
4331
uses: astral-sh/setup-uv@v6
4432
with:
4533
activate-environment: true
34+
enable-cache: true
4635

4736
- name: Install dependencies
4837
run: uv sync --extra dev
4938

5039
- name: Run integration tests
5140
env:
5241
CS_TOKEN: ${{ secrets.CS_TOKEN }}
53-
CS_TEST_TEAM_ID: ${{ inputs.test_team_id || secrets.CS_TEST_TEAM_ID }}
54-
CS_TEST_DC_ID: ${{ inputs.test_dc_id || '1' }}
42+
CS_TEST_TEAM_ID: ${{ secrets.CS_TEST_TEAM_ID }}
43+
CS_TEST_DC_ID: ${{ secrets.CS_TEST_DC_ID }}
5544
run: |
5645
echo "Running integration tests..."
5746
uv run pytest tests/integration -v --run-integration \
@@ -88,4 +77,4 @@ jobs:
8877
with:
8978
files: junit/integration-results.xml
9079
check_name: Integration Test Results
91-
comment_mode: off
80+
comment_mode: on

examples/.gitkeep

Whitespace-only changes.

src/codesphere/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Settings(BaseSettings):
77
env_file=".env",
88
env_file_encoding="utf-8",
99
env_prefix="CS_",
10-
extra="ignore", # Allow extra CS_* env vars (e.g., CS_TEST_TEAM_ID)
10+
extra="ignore",
1111
)
1212

1313
token: SecretStr

src/codesphere/core/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CamelModel(BaseModel):
1717
model_config = ConfigDict(
1818
alias_generator=to_camel,
1919
populate_by_name=True,
20-
serialize_by_alias=True, # Serialize using camelCase aliases
20+
serialize_by_alias=True,
2121
)
2222

2323

0 commit comments

Comments
 (0)