Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,41 @@ on:
- 'src/codesphere/**'
- '.github/workflows/ci.yml'
- 'tests/**'
- 'pyproject.toml'
- 'ruff.toml'
- 'uv.lock'

permissions:
contents: write
pull-requests: write

jobs:
typecheck:
name: Type Check (ty)
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install uv package manager
uses: astral-sh/setup-uv@v6
with:
activate-environment: true

- name: Install dependencies
run: uv sync --extra dev
shell: bash

- name: Run ty type check
run: uv run ty check
shell: bash

- name: Minimize uv cache
run: uv cache prune --ci

security_check:
name: Security Check (Bandit)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -123,7 +152,7 @@ jobs:

- name: Run tests with pytest
run: |
uv run pytest --junitxml=junit/test-results.xml --cov-report=xml --cov-report=html --cov=. --ignore=tests/integration | tee pytest-coverage.txt
uv run pytest --junitxml=junit/test-results.xml --cov-report=xml --cov-report=html --cov --ignore=tests/integration | tee pytest-coverage.txt
shell: bash

- name: Pytest coverage comment
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ env/

.pytest_cache

__marimo__
__marimo__
# Coverage artifacts
.coverage
coverage.xml
test-results/
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@ repos:
- id: ruff-check
args: [ --fix ]
- id: ruff-format

- repo: local
hooks:
- id: ty-check
name: ty check
entry: uv run ty check
language: system
types: [python]
pass_filenames: false
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install lint format test test-integration test-unit bump release pypi tree version changelog
.PHONY: help install lint format typecheck test test-integration test-unit bump release pypi tree version changelog

.DEFAULT_GOAL := help

Expand Down Expand Up @@ -27,6 +27,10 @@ format: ## Formats code with ruff
@echo ">>> Formatting code with ruff..."
uv run ruff format src

typecheck: ## Checks types with ty
@echo ">>> Checking types with ty..."
uv run ty check

test: ## Runs all tests with pytest
@echo ">>> Running all tests with pytest..."
uv run pytest
Expand Down
25 changes: 12 additions & 13 deletions examples/dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@

import asyncio
from contextlib import asynccontextmanager
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Optional

from dotenv import load_dotenv
from fastapi import FastAPI, Form, HTTPException, Query, Request
Expand All @@ -44,7 +43,7 @@
from codesphere.resources.workspace.logs import LogStage # noqa: E402

# Global SDK instance (managed via lifespan)
cs: Optional[CodesphereSDK] = None
cs: CodesphereSDK | None = None


@asynccontextmanager
Expand All @@ -71,7 +70,7 @@ async def lifespan(app: FastAPI):
# =============================================================================


def format_datetime(value: Optional[datetime]) -> str:
def format_datetime(value: datetime | None) -> str:
"""Format datetime for display."""
if value is None:
return "N/A"
Expand Down Expand Up @@ -156,7 +155,7 @@ async def team_detail(request: Request, team_id: int):

# Get usage summary (last 7 days)
try:
end_date = datetime.now(timezone.utc)
end_date = datetime.now(UTC)
begin_date = end_date - timedelta(days=7)
usage = await team.usage.get_landscape_summary(
begin_date=begin_date, end_date=end_date, limit=10
Expand Down Expand Up @@ -243,8 +242,8 @@ async def create_workspace(
team_id: int = Form(...),
name: str = Form(...),
plan_id: int = Form(...),
base_image: Optional[str] = Form(None),
git_url: Optional[str] = Form(None),
base_image: str | None = Form(None),
git_url: str | None = Form(None),
):
"""Create a new workspace."""
try:
Expand All @@ -265,16 +264,16 @@ async def create_workspace(
"partials/error.html",
{"request": request, "error": str(e)},
)
raise HTTPException(status_code=400, detail=str(e))
raise HTTPException(status_code=400, detail=str(e)) from e


@app.post("/workspaces/{workspace_id}/update", response_class=HTMLResponse)
async def update_workspace(
request: Request,
workspace_id: int,
name: Optional[str] = Form(None),
plan_id: Optional[int] = Form(None),
replicas: Optional[int] = Form(None),
name: str | None = Form(None),
plan_id: int | None = Form(None),
replicas: int | None = Form(None),
):
"""Update workspace settings."""
workspace = await cs.workspaces.get(workspace_id=workspace_id)
Expand Down Expand Up @@ -630,8 +629,8 @@ async def logs_partial(
workspace_id: int,
stage: str = Query(default="prepare"),
step: int = Query(default=0),
server: Optional[str] = Query(default=None),
replica: Optional[int] = Query(default=None),
server: str | None = Query(default=None),
replica: int | None = Query(default=None),
):
"""Get logs for a pipeline stage or server."""
workspace = await cs.workspaces.get(workspace_id=workspace_id)
Expand Down
13 changes: 8 additions & 5 deletions examples/scripts/create_workspace_with_landscape.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import time
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta

from codesphere import CodesphereSDK
from codesphere.resources.workspace import WorkspaceCreate
Expand Down Expand Up @@ -44,7 +44,8 @@ async def main():
.add_reactive_service("web")
.plan(plan.id)
.add_step(
'for i in $(seq 1 20); do echo "[$i] Processing request..."; sleep 1; done'
"for i in $(seq 1 20); do "
'echo "[$i] Processing request..."; sleep 1; done'
)
.add_port(3000, public=True)
.add_path("/", port=3000)
Expand Down Expand Up @@ -95,11 +96,12 @@ async def main():

print("\n--- Usage History ---")

end_date = datetime.now(timezone.utc)
end_date = datetime.now(UTC)
begin_date = end_date - timedelta(days=1)

print(
f"Fetching usage summary from {begin_date.isoformat()} to {end_date.isoformat()}..."
f"Fetching usage summary from {begin_date.isoformat()} "
f"to {end_date.isoformat()}..."
)
usage_summary = await team.usage.get_landscape_summary(
begin_date=begin_date,
Expand Down Expand Up @@ -135,7 +137,8 @@ async def main():
print(f"Total events: {events.total_items}")
for event in events.items:
print(
f" [{event.date.isoformat()}] {event.action.value.upper()} by {event.initiator_email}"
f" [{event.date.isoformat()}] {event.action.value.upper()} "
f"by {event.initiator_email}"
)

print("\nRefreshing usage summary...")
Expand Down
1 change: 1 addition & 0 deletions examples/sdk_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
with app.setup:
"""Import dependencies and initialize the SDK."""
import os

from codesphere import CodesphereSDK

# Display token status
Expand Down
80 changes: 80 additions & 0 deletions plans/refactor/01-typing-gate-and-tooling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 01 — Add a blocking `ty` type-check gate (ratcheted) and clean up tooling config

**Priority:** P1
**Depends on:** —
**Unblocks:** 02–07 (the ratchet is how later tickets prove their typing work)

## Problem

The SDK ships `src/codesphere/py.typed` (so downstream type checkers trust our annotations) and
`.github/copilot-instructions.md` states "Strict type hints required" — but **no type checker
exists anywhere**: not in dev dependencies, not in `.pre-commit-config.yaml`, not in
`.github/workflows/ci.yml`. The claim is unenforced, and several annotations in `core/` are
actively wrong (see ticket 02).

Surrounding tooling config has drifted:

- `ruff.toml` selects only `["F", "E4", "E7", "E9"]` and ignores `E721`/`F841` (unused locals
pass lint). Its `exclude` list references `src/codesphere_sdk/...` paths that don't exist —
leftovers from a generated-client era.
- `pyproject.toml` `[tool.coverage.run]` has `source = ["api", "handler", "tasks"]`
(pyproject.toml:64) — none of those packages exist, so coverage numbers are unanchored.
`omit` also lists nonexistent `docs/*` / `scripts/**` roots.
- `pyproject.toml:13-14,22` declare `aiohttp`, `aiohttp-retry`, and `urllib3` as runtime
dependencies; `grep -r` over `src/` shows zero imports of any of them (the SDK is pure httpx).
They bloat installs and imply a retry feature that doesn't exist (see ticket 07).
- `requires-python = ">=3.12.9"` (pyproject.toml:11) pins an oddly specific patch release,
excluding 3.12.0–3.12.8 users for no identified reason.

## Approach

Toolchain choice: **`ty`** (Astral) rather than mypy/pyright, to keep type checking and linting
in the same toolchain family as ruff. ty is pre-1.0: **pin its version** in the dev group and
expect occasional rule renames on upgrades (note this in a comment next to the pin).

1. **Add ty as a blocking gate with a ratchet.**
- `uv add --dev ty` (pinned, e.g. `ty==0.0.x`).
- Configure in `pyproject.toml` under `[tool.ty]`: `src.root = "src"`, error-level rules for
the strictness-relevant checks (unresolved attributes, invalid assignments, invalid
argument types, missing/implicit `Any` where ty supports it).
- **Ratchet:** exclude the paths that cannot pass until later tickets land, via
`[[tool.ty.overrides]]` (or `src.exclude` if the pinned ty version's override granularity
is insufficient):
- `src/codesphere/core/**` and `src/codesphere/resources/**` → removed by tickets 02/04
- `src/codesphere/http_client.py`, `src/codesphere/client.py`, `src/codesphere/config.py` → removed by ticket 03
Annotate each exclusion with the ticket number that deletes it.
- CI: add a `typecheck` job to `.github/workflows/ci.yml` running `uv run ty check`
(blocking). Add the same to `.pre-commit-config.yaml` and a `make typecheck` target.

2. **Broaden ruff.**
- `select = ["F", "E", "W", "I", "UP", "B", "SIM", "RUF"]`; drop the `F841` and `E721`
ignores; delete the entire stale `src/codesphere_sdk/*` exclude block.
- Run `ruff check --fix` + `ruff format`; fix the residue by hand (expect mostly import
sorting and pyupgrade rewrites like `Optional[X]` → `X | None`).

3. **Fix coverage config.** `[tool.coverage.run] source = ["src/codesphere"]`; prune the
nonexistent `omit` entries; change the CI pytest invocation from `--cov=.` to rely on the
config (`--cov`).

4. **Prune dependencies.** Remove `aiohttp`, `aiohttp-retry`, `urllib3` from
`[project.dependencies]`. While there, grep-verify each remaining runtime dep is actually
imported (`python-dateutil` is suspect) and remove any that aren't. `uv lock` after.

5. **Relax the Python floor** to `requires-python = ">=3.12"` (align `.python-version` and
`ruff.toml target-version = "py312"`, which already agree).

## Breaking changes

None. Dependency removals only shrink the install footprint; relaxing `requires-python` widens
compatibility.

## Acceptance criteria

- [ ] CI fails on any ty error outside the documented ratchet exclusions; `make typecheck` and
the pre-commit hook run the same command.
- [ ] Every ratchet exclusion carries a comment naming the ticket that removes it.
- [ ] `ruff check` passes with the broadened rule set; no `codesphere_sdk` references remain in
`ruff.toml`.
- [ ] Coverage output reports lines in `src/codesphere/**` (spot-check the CI coverage comment).
- [ ] Fresh `uv pip install .` pulls neither aiohttp nor urllib3; `uv.lock` regenerated.
- [ ] `requires-python = ">=3.12"`.
Loading
Loading