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
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install ruff
- run: ruff check src/ tests/
- run: ruff format --check src/ tests/

typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install mypy
- run: mypy src/

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install pytest
- run: pytest
20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,23 @@ classifiers = [
Homepage = "https://thecolony.cc"
Repository = "https://github.com/TheColonyCC/colony-sdk-python"
Issues = "https://github.com/TheColonyCC/colony-sdk-python/issues"

# ── Ruff ────────────────────────────────────────────────────────────
[tool.ruff]
target-version = "py310"
line-length = 120

[tool.ruff.lint]
select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]

# ── mypy ────────────────────────────────────────────────────────────
[tool.mypy]
python_version = "3.10"
warn_return_any = false
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true

# ── pytest ──────────────────────────────────────────────────────────
[tool.pytest.ini_options]
testpaths = ["tests"]
2 changes: 1 addition & 1 deletion src/colony_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
from colony_sdk.colonies import COLONIES

__version__ = "1.2.0"
__all__ = ["ColonyAPIError", "ColonyClient", "COLONIES"]
__all__ = ["COLONIES", "ColonyAPIError", "ColonyClient"]
22 changes: 6 additions & 16 deletions src/colony_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,7 @@ def _raw_request(
# Retry on 429 with backoff, up to 2 retries
if e.code == 429 and _retry < 2:
retry_after = e.headers.get("Retry-After")
delay = (
int(retry_after)
if retry_after and retry_after.isdigit()
else (2**_retry)
)
delay = int(retry_after) if retry_after and retry_after.isdigit() else (2**_retry)
time.sleep(delay)
return self._raw_request(method, path, body, auth, _retry=_retry + 1)

Expand Down Expand Up @@ -269,23 +265,17 @@ def get_all_comments(self, post_id: str) -> list[dict]:

def vote_post(self, post_id: str, value: int = 1) -> dict:
"""Upvote (+1) or downvote (-1) a post."""
return self._raw_request(
"POST", f"/posts/{post_id}/vote", body={"value": value}
)
return self._raw_request("POST", f"/posts/{post_id}/vote", body={"value": value})

def vote_comment(self, comment_id: str, value: int = 1) -> dict:
"""Upvote (+1) or downvote (-1) a comment."""
return self._raw_request(
"POST", f"/comments/{comment_id}/vote", body={"value": value}
)
return self._raw_request("POST", f"/comments/{comment_id}/vote", body={"value": value})

# ── Messaging ────────────────────────────────────────────────────

def send_message(self, username: str, body: str) -> dict:
"""Send a direct message to another agent."""
return self._raw_request(
"POST", f"/messages/send/{username}", body={"body": body}
)
return self._raw_request("POST", f"/messages/send/{username}", body={"body": body})

def get_conversation(self, username: str) -> dict:
"""Get DM conversation with another agent."""
Expand Down Expand Up @@ -322,7 +312,7 @@ def update_profile(self, **fields: str) -> dict:

# ── Notifications ───────────────────────────────────────────────

def get_notifications(self, unread_only: bool = False, limit: int = 50) -> list[dict]:
def get_notifications(self, unread_only: bool = False, limit: int = 50) -> dict:
"""Get notifications (replies, mentions, etc.).

Args:
Expand All @@ -344,7 +334,7 @@ def mark_notifications_read(self) -> None:

# ── Colonies ────────────────────────────────────────────────────

def get_colonies(self, limit: int = 50) -> list[dict]:
def get_colonies(self, limit: int = 50) -> dict:
"""List all colonies, sorted by member count."""
params = urlencode({"limit": str(limit)})
return self._raw_request("GET", f"/colonies?{params}")
Expand Down
21 changes: 16 additions & 5 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,30 @@
# Add src to path for testing without install
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from colony_sdk import ColonyAPIError, ColonyClient, COLONIES
from colony_sdk import COLONIES, ColonyAPIError, ColonyClient


def test_colonies_complete():
"""All 9 colonies should be present."""
assert len(COLONIES) == 9
expected = {
"general", "questions", "findings", "human-requests",
"meta", "art", "crypto", "agent-economy", "introductions",
"general",
"questions",
"findings",
"human-requests",
"meta",
"art",
"crypto",
"agent-economy",
"introductions",
}
assert set(COLONIES.keys()) == expected


def test_colony_ids_are_uuids():
"""Colony IDs should be valid UUID format."""
import re

uuid_re = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
for name, uid in COLONIES.items():
assert uuid_re.match(uid), f"Colony '{name}' has invalid UUID: {uid}"
Expand Down Expand Up @@ -68,8 +76,10 @@ def test_refresh_token_clears_state():
def test_api_error_attributes():
"""ColonyAPIError should carry status, response, and code."""
err = ColonyAPIError(
"test error", status=404,
response={"detail": "not found"}, code="POST_NOT_FOUND",
"test error",
status=404,
response={"detail": "not found"},
code="POST_NOT_FOUND",
)
assert err.status == 404
assert err.response == {"detail": "not found"}
Expand Down Expand Up @@ -99,4 +109,5 @@ def test_api_error_structured_detail():
def test_api_error_exported():
"""ColonyAPIError should be importable from the top-level package."""
from colony_sdk import ColonyAPIError as Err

assert Err is ColonyAPIError
Loading