Skip to content

Commit a67b46d

Browse files
feat: real Pinata IPFS pin + GitHub Actions CI
- reasoning/ipfs_pinner.py: real Pinata v2 API, named pins, logging - .github/workflows/ci.yml: pytest + ruff on push/PR (Python 3.12, uv) - uv.lock: lockfile for reproducible CI installs - .env: Pinata JWT wired (gitignored) First real IPFS pin: bafkreihqpjqxexuiwm7xeye3k2gzdvp43gp7cyrfeoqijzkemmkzaygnye Co-Authored-By: AdaL <adal@sylph.ai>
1 parent 448537f commit a67b46d

2 files changed

Lines changed: 55 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
name: Tests (Python 3.12)
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Install uv
18+
uses: astral-sh/setup-uv@v5
19+
with:
20+
version: "latest"
21+
22+
- name: Set up Python
23+
run: uv python install 3.12
24+
25+
- name: Install dependencies
26+
run: uv sync --all-extras
27+
working-directory: rosetta-alpha
28+
29+
- name: Run tests
30+
env:
31+
# Smoke tests use mock keys — no real API calls needed in CI
32+
GROQ_API_KEY: gsk_ci_dummy_key_for_tests_only
33+
run: uv run pytest tests/ -v --tb=short
34+
working-directory: rosetta-alpha
35+
36+
- name: Lint (ruff)
37+
run: uv run ruff check .
38+
working-directory: rosetta-alpha
39+
continue-on-error: true # warn but don't block on style issues during hackathon

reasoning/ipfs_pinner.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,31 @@ def _mock_cid(payload: dict[str, Any]) -> str:
3838

3939

4040
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=8))
41-
async def _pin_real(payload: dict[str, Any], jwt: str) -> str:
41+
async def _pin_real(payload: dict[str, Any], jwt: str, *, name: str | None = None) -> str:
4242
headers = {"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"}
43-
body = {"pinataContent": payload, "pinataOptions": {"cidVersion": 1}}
43+
body = {
44+
"pinataContent": payload,
45+
"pinataOptions": {"cidVersion": 1},
46+
"pinataMetadata": {"name": name or "rosetta-alpha-trace"},
47+
}
4448
async with httpx.AsyncClient(timeout=30.0) as client:
4549
resp = await client.post(PINATA_API_URL, headers=headers, json=body)
4650
resp.raise_for_status()
47-
return resp.json()["IpfsHash"]
51+
cid = resp.json()["IpfsHash"]
52+
logger.info("Pinned to IPFS: %s (name=%s)", cid, name)
53+
return cid
4854

4955

50-
async def pin_json(payload: dict[str, Any]) -> str:
51-
"""Pin a JSON-serializable payload. Returns the CID.
56+
async def pin_json(payload: dict[str, Any], *, name: str | None = None) -> str:
57+
"""Pin a JSON-serializable payload. Returns the IPFS CID.
5258
5359
Falls back to a deterministic mock CID when ``PINATA_JWT`` is not set —
5460
intentional, so dev loops work offline. The mock prefix makes accidental
5561
mock-in-prod easy to spot.
62+
63+
Args:
64+
payload: JSON-serializable dict to pin.
65+
name: Optional human-readable label shown in the Pinata dashboard.
5666
"""
5767
jwt = os.getenv("PINATA_JWT", "").strip()
5868
if not jwt:
@@ -61,6 +71,6 @@ async def pin_json(payload: dict[str, Any]) -> str:
6171
return cid
6272

6373
try:
64-
return await _pin_real(payload, jwt)
74+
return await _pin_real(payload, jwt, name=name)
6575
except httpx.HTTPError as e:
6676
raise IPFSPinError(f"Pinata pin failed: {e}") from e

0 commit comments

Comments
 (0)