Skip to content
Draft
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
18 changes: 12 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

jobs:
# Check version synchronization
version-check:
Expand All @@ -17,7 +20,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.14"
python-version: "3.12"

- name: Check version sync
run: python scripts/sync-versions.py --check
Expand All @@ -32,15 +35,18 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.14"
python-version: "3.12"

- name: Check feature parity
run: python scripts/check-feature-parity.py

# TypeScript CI
typescript:
name: TypeScript
name: TypeScript (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ["18", "20", "22"]
defaults:
run:
working-directory: ts
Expand All @@ -50,7 +56,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: ${{ matrix.node-version }}
cache: "npm"
cache-dependency-path: ts/package-lock.json

Expand All @@ -72,7 +78,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.14"]
python-version: ["3.11", "3.12", "3.13"]
defaults:
run:
working-directory: python
Expand All @@ -96,7 +102,7 @@ jobs:
run: mypy numbersprotocol_capture --ignore-missing-imports

- name: Run tests
run: pytest -v
run: pytest -v --cov-fail-under=75

# All checks passed
ci-success:
Expand Down
121 changes: 14 additions & 107 deletions python/numbersprotocol_capture/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def _request(
*,
data: dict[str, Any] | None = None,
files: dict[str, Any] | None = None,
json_body: dict[str, Any] | None = None,
json_body: dict[str, Any] | list[Any] | None = None,
nid: str | None = None,
) -> dict[str, Any]:
"""Makes an authenticated API request."""
Expand Down Expand Up @@ -445,25 +445,7 @@ def get_history(self, nid: str) -> list[Commit]:
params["testnet"] = "true"

url = f"{HISTORY_API_URL}?{urlencode(params)}"

headers = {
"Content-Type": "application/json",
"Authorization": f"token {self._token}",
}

try:
response = self._client.get(url, headers=headers)
except httpx.RequestError as e:
raise create_api_error(0, f"Network error: {e}", nid) from e

if not response.is_success:
raise create_api_error(
response.status_code,
"Failed to fetch asset history",
nid,
)

data = response.json()
data = self._request("GET", url, nid=nid)

return [
Commit(
Expand Down Expand Up @@ -511,28 +493,7 @@ def get_asset_tree(self, nid: str) -> AssetTree:
for c in commits
]

headers = {
"Content-Type": "application/json",
"Authorization": f"token {self._token}",
}

try:
response = self._client.post(
MERGE_TREE_API_URL,
headers=headers,
json=commit_data,
)
except httpx.RequestError as e:
raise create_api_error(0, f"Network error: {e}", nid) from e

if not response.is_success:
raise create_api_error(
response.status_code,
"Failed to merge asset trees",
nid,
)

data = response.json()
data = self._request("POST", MERGE_TREE_API_URL, json_body=commit_data, nid=nid)
merged = data.get("mergedAssetTree", data)

# Map known fields and put the rest in extra
Expand Down Expand Up @@ -675,51 +636,24 @@ def search_asset(
form_data["sample_count"] = str(options.sample_count)

# Verify Engine API requires token in Authorization header, not form data
headers = {"Authorization": f"token {self._token}"}

try:
if files_data:
response = self._client.post(
ASSET_SEARCH_API_URL,
headers=headers,
data=form_data,
files=files_data,
)
else:
response = self._client.post(
ASSET_SEARCH_API_URL,
headers=headers,
data=form_data,
)
except httpx.RequestError as e:
raise create_api_error(0, f"Network error: {e}") from e

if not response.is_success:
message = f"Asset search failed with status {response.status_code}"
try:
error_data = response.json()
message = (
error_data.get("message")
or error_data.get("error")
or message
)
except Exception:
pass
raise create_api_error(response.status_code, message)

data = response.json()
response_data = self._request(
"POST",
ASSET_SEARCH_API_URL,
data=form_data,
files=files_data,
)

# Map response to our type
similar_matches = [
SimilarMatch(nid=m["nid"], distance=m["distance"])
for m in data.get("similar_matches", [])
for m in response_data.get("similar_matches", [])
]

return AssetSearchResult(
precise_match=data.get("precise_match", ""),
input_file_mime_type=data.get("input_file_mime_type", ""),
precise_match=response_data.get("precise_match", ""),
input_file_mime_type=response_data.get("input_file_mime_type", ""),
similar_matches=similar_matches,
order_id=data.get("order_id", ""),
order_id=response_data.get("order_id", ""),
)

def search_nft(self, nid: str) -> NftSearchResult:
Expand All @@ -740,34 +674,7 @@ def search_nft(self, nid: str) -> NftSearchResult:
if not nid:
raise ValidationError("nid is required for NFT search")

headers = {
"Content-Type": "application/json",
"Authorization": f"token {self._token}",
}

try:
response = self._client.post(
NFT_SEARCH_API_URL,
headers=headers,
json={"nid": nid},
)
except httpx.RequestError as e:
raise create_api_error(0, f"Network error: {e}", nid) from e

if not response.is_success:
message = f"NFT search failed with status {response.status_code}"
try:
error_data = response.json()
message = (
error_data.get("message")
or error_data.get("error")
or message
)
except Exception:
pass
raise create_api_error(response.status_code, message, nid)

data = response.json()
data = self._request("POST", NFT_SEARCH_API_URL, json_body={"nid": nid}, nid=nid)

# Map response to our type
records = [
Expand Down
10 changes: 6 additions & 4 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ version = "0.2.1"
description = "Python SDK for Numbers Protocol Capture API"
readme = "README.md"
license = "MIT"
requires-python = ">=3.14"
requires-python = ">=3.11"
authors = [
{ name = "Numbers Protocol" }
]
Expand All @@ -26,7 +26,9 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
]
Expand Down Expand Up @@ -61,7 +63,7 @@ packages = ["numbersprotocol_capture"]

[tool.ruff]
line-length = 100
target-version = "py313" # Use py313 until ruff supports py314
target-version = "py311"

[tool.ruff.lint]
select = [
Expand All @@ -81,7 +83,7 @@ ignore = [
known-first-party = ["numbersprotocol_capture"]

[tool.mypy]
python_version = "3.14"
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_ignores = true
Expand Down
11 changes: 8 additions & 3 deletions scripts/check-feature-parity.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"""

import re
import sys
from dataclasses import dataclass
from pathlib import Path

Expand Down Expand Up @@ -140,8 +141,8 @@ def check_py_features() -> None:
feature.py_implemented = True


def print_report() -> None:
"""Print feature parity report."""
def print_report() -> bool:
"""Print feature parity report. Returns True if full parity achieved."""
print("=" * 60)
print("Feature Parity Report")
print("=" * 60)
Expand Down Expand Up @@ -204,15 +205,19 @@ def print_report() -> None:

if parity_count == total_features:
print("\n✓ Full feature parity achieved!")
return True
else:
missing = total_features - parity_count
print(f"\n✗ {missing} feature(s) missing parity")
return False


def main() -> None:
check_ts_features()
check_py_features()
print_report()
all_parity = print_report()
if not all_parity:
sys.exit(1)


if __name__ == "__main__":
Expand Down
Loading