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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "colony-sdk"
version = "1.6.0"
version = "1.7.0"
description = "Python SDK for The Colony (thecolony.cc) — the official Python client for the AI agent internet"
readme = "README.md"
license = {text = "MIT"}
Expand Down
28 changes: 27 additions & 1 deletion src/colony_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,27 @@ async def main():
verify_webhook,
)
from colony_sdk.colonies import COLONIES
from colony_sdk.models import (
Colony,
Comment,
Message,
Notification,
PollResults,
Post,
RateLimitInfo,
User,
Webhook,
)

if TYPE_CHECKING: # pragma: no cover
from colony_sdk.async_client import AsyncColonyClient
from colony_sdk.testing import MockColonyClient

__version__ = "1.6.0"
__version__ = "1.7.0"
__all__ = [
"COLONIES",
"AsyncColonyClient",
"Colony",
"ColonyAPIError",
"ColonyAuthError",
"ColonyClient",
Expand All @@ -54,7 +67,16 @@ async def main():
"ColonyRateLimitError",
"ColonyServerError",
"ColonyValidationError",
"Comment",
"Message",
"MockColonyClient",
"Notification",
"PollResults",
"Post",
"RateLimitInfo",
"RetryConfig",
"User",
"Webhook",
"verify_webhook",
]

Expand All @@ -70,4 +92,8 @@ def __getattr__(name: str) -> Any:
from colony_sdk.async_client import AsyncColonyClient

return AsyncColonyClient
if name == "MockColonyClient":
from colony_sdk.testing import MockColonyClient

return MockColonyClient
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
17 changes: 16 additions & 1 deletion src/colony_sdk/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ async def main():
_should_retry,
)
from colony_sdk.colonies import COLONIES
from colony_sdk.models import RateLimitInfo

try:
import httpx
Expand Down Expand Up @@ -84,6 +85,7 @@ def __init__(
self._token_expiry: float = 0
self._client = client
self._owns_client = client is None
self.last_rate_limit: RateLimitInfo | None = None

def __repr__(self) -> str:
return f"AsyncColonyClient(base_url={self.base_url!r})"
Expand Down Expand Up @@ -159,8 +161,14 @@ async def _raw_request(
if auth:
await self._ensure_token()

import logging

_logger = logging.getLogger("colony_sdk")

from colony_sdk import __version__

url = f"{self.base_url}{path}"
headers: dict[str, str] = {}
headers: dict[str, str] = {"User-Agent": f"colony-sdk-python/{__version__}"}
if body is not None:
headers["Content-Type"] = "application/json"
if auth and self._token:
Expand All @@ -169,6 +177,8 @@ async def _raw_request(
client = self._get_client()
payload = json.dumps(body).encode() if body is not None else None

_logger.debug("→ %s %s", method, url)

try:
resp = await client.request(method, url, content=payload, headers=headers)
except httpx.HTTPError as e:
Expand All @@ -178,8 +188,13 @@ async def _raw_request(
response={},
) from e

# Parse rate-limit headers when available.
resp_headers = dict(resp.headers)
self.last_rate_limit = RateLimitInfo.from_headers(resp_headers)

if 200 <= resp.status_code < 300:
text = resp.text
_logger.debug("← %s %s (%d bytes)", method, url, len(text))
if not text:
return {}
try:
Expand Down
17 changes: 16 additions & 1 deletion src/colony_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import hashlib
import hmac
import json
import logging
import time
from collections.abc import Iterator
from dataclasses import dataclass, field
Expand All @@ -21,6 +22,9 @@
from urllib.request import Request, urlopen

from colony_sdk.colonies import COLONIES
from colony_sdk.models import RateLimitInfo

logger = logging.getLogger("colony_sdk")

DEFAULT_BASE_URL = "https://thecolony.cc/api/v1"

Expand Down Expand Up @@ -358,6 +362,7 @@ def __init__(
self.retry = retry if retry is not None else _DEFAULT_RETRY
self._token: str | None = None
self._token_expiry: float = 0
self.last_rate_limit: RateLimitInfo | None = None

def __repr__(self) -> str:
return f"ColonyClient(base_url={self.base_url!r})"
Expand Down Expand Up @@ -413,8 +418,10 @@ def _raw_request(
if auth:
self._ensure_token()

from colony_sdk import __version__

url = f"{self.base_url}{path}"
headers: dict[str, str] = {}
headers: dict[str, str] = {"User-Agent": f"colony-sdk-python/{__version__}"}
if body is not None:
headers["Content-Type"] = "application/json"
if auth and self._token:
Expand All @@ -423,9 +430,15 @@ def _raw_request(
payload = json.dumps(body).encode() if body is not None else None
req = Request(url, data=payload, headers=headers, method=method)

logger.debug("→ %s %s", method, url)

try:
with urlopen(req, timeout=self.timeout) as resp:
raw = resp.read().decode()
# Parse rate-limit headers when available.
resp_headers = {k: v for k, v in resp.getheaders()}
self.last_rate_limit = RateLimitInfo.from_headers(resp_headers)
logger.debug("← %s %s (%d bytes)", method, url, len(raw))
return json.loads(raw) if raw else {}
except HTTPError as e:
resp_body = e.read().decode()
Expand All @@ -444,6 +457,7 @@ def _raw_request(
time.sleep(delay)
return self._raw_request(method, path, body, auth, _retry=_retry + 1, _token_refreshed=_token_refreshed)

logger.warning("← %s %s → HTTP %d", method, url, e.code)
raise _build_api_error(
e.code,
resp_body,
Expand All @@ -453,6 +467,7 @@ def _raw_request(
) from e
except URLError as e:
# DNS failure, connection refused, timeout — never reached the server.
logger.warning("← %s %s → network error: %s", method, url, e.reason)
raise ColonyNetworkError(
f"Colony API network error ({method} {path}): {e.reason}",
status=0,
Expand Down
Loading