diff --git a/python/CONTRIBUTING.md b/python/CONTRIBUTING.md index fefded45d..c0431731c 100644 --- a/python/CONTRIBUTING.md +++ b/python/CONTRIBUTING.md @@ -64,7 +64,7 @@ uv sync --all-extras --dev ```bash cd python/x402 -pip install -e ".[dev]" +pip install -e . --group dev ``` ## Development Workflow @@ -75,7 +75,7 @@ From the `python/x402/` directory: | Command | Description | |---------|-------------| -| `uv sync --dev` | Install/update dependencies | +| `uv sync` | Install/update dependencies | | `uv run pytest` | Run tests | | `uvx ruff check` | Lint code | | `uvx ruff check --fix` | Lint and fix | @@ -113,7 +113,7 @@ class x402YourClient(BaseX402Client): # Implement payment handling hooks ``` -2. Export from `src/x402/clients/__init__.py` +2. Add friendly `ImportError` wrapper at the top of your module (see `src/x402/clients/httpx.py` for example) 3. Add tests in `tests/clients/test_your_client.py` @@ -137,13 +137,11 @@ src/x402/your_framework/ - Call facilitator to verify and settle payments - Add `X-PAYMENT-RESPONSE` header on success -4. Add the dependency to `pyproject.toml`: +4. Add the dependency to `pyproject.toml` as an optional extra: ```toml -dependencies = [ - # ... existing deps - "your-framework>=1.0.0", -] +[project.optional-dependencies] +your-framework = ["your-framework>=1.0.0"] ``` 5. Add tests in `tests/your_framework_tests/` diff --git a/python/x402/README.md b/python/x402/README.md index f6441e9f2..225a8c96a 100644 --- a/python/x402/README.md +++ b/python/x402/README.md @@ -4,8 +4,22 @@ Python package for the x402 payments protocol. ## Installation +Install the core package with your preferred framework/client: + ```bash -pip install x402 +# Server frameworks (pick one based on your stack) +pip install x402[fastapi] # FastAPI middleware +pip install x402[flask] # Flask middleware + +# HTTP clients (pick one based on your preference) +pip install x402[httpx] # Async httpx client +pip install x402[requests] # Sync requests client + +# Or install multiple extras +pip install x402[fastapi,httpx] + +# Install everything (for development) +pip install x402[all] ``` ## Overview @@ -17,6 +31,8 @@ The x402 package provides the core building blocks for implementing the x402 Pay - httpx client for paying resources - requests client for paying resources +Each integration is an optional dependency - install only what you need to keep your environment lean. + ## FastAPI Integration The simplest way to add x402 payment protection to your FastAPI application: diff --git a/python/x402/pyproject.toml b/python/x402/pyproject.toml index a34e26bab..edd50f051 100644 --- a/python/x402/pyproject.toml +++ b/python/x402/pyproject.toml @@ -14,14 +14,26 @@ dependencies = [ "eth-account>=0.13.7", "eth-typing>=4.0.0", "eth-utils>=3.0.0", - "fastapi[standard]>=0.115.12", - "flask>=3.0.0", "pydantic>=2.10.3", "pydantic-settings>=2.2.1", "python-dotenv>=1.0.1", "web3>=6.0.0", ] +[project.optional-dependencies] +# Server frameworks - install one based on your stack +flask = ["flask>=3.0.0"] +fastapi = ["fastapi[standard]>=0.115.12"] + +# HTTP clients - install one based on your preference +httpx = ["httpx>=0.28.0"] +requests = ["requests>=2.31.0"] + +# Convenience bundles +servers = ["x402[flask,fastapi]"] +clients = ["x402[httpx,requests]"] +all = ["x402[flask,fastapi,httpx,requests]"] + [project.scripts] @@ -34,6 +46,11 @@ dev = [ "pytest>=8.3.5", "pytest-asyncio>=1.0.0", "ruff>=0.11.9", + # Include all optional deps for testing + "flask>=3.0.0", + "fastapi[standard]>=0.115.12", + "httpx>=0.28.0", + "requests>=2.31.0", ] [tool.pytest.ini_options] diff --git a/python/x402/src/x402/clients/__init__.py b/python/x402/src/x402/clients/__init__.py index feffaa396..eda4b72b6 100644 --- a/python/x402/src/x402/clients/__init__.py +++ b/python/x402/src/x402/clients/__init__.py @@ -1,20 +1,18 @@ +""" +HTTP client integrations for x402 payment handling. + +Core exports (always available): + - x402Client: Base client for payment handling + - decode_x_payment_response: Decode X-Payment-Response header + +Optional clients (install separately): + pip install x402[httpx] → from x402.clients.httpx import x402HttpxClient + pip install x402[requests] → from x402.clients.requests import x402_requests +""" + from x402.clients.base import x402Client, decode_x_payment_response -from x402.clients.httpx import ( - x402_payment_hooks, - x402HttpxClient, -) -from x402.clients.requests import ( - x402HTTPAdapter, - x402_http_adapter, - x402_requests, -) __all__ = [ "x402Client", "decode_x_payment_response", - "x402_payment_hooks", - "x402HttpxClient", - "x402HTTPAdapter", - "x402_http_adapter", - "x402_requests", ] diff --git a/python/x402/src/x402/clients/httpx.py b/python/x402/src/x402/clients/httpx.py index 430cfb8ee..09509a280 100644 --- a/python/x402/src/x402/clients/httpx.py +++ b/python/x402/src/x402/clients/httpx.py @@ -1,5 +1,12 @@ from typing import Optional, Dict, List -from httpx import Request, Response, AsyncClient + +try: + from httpx import Request, Response, AsyncClient +except ImportError as e: + raise ImportError( + "httpx client requires the httpx package. Install with: pip install x402[httpx]" + ) from e + from eth_account import Account from x402.clients.base import ( x402Client, diff --git a/python/x402/src/x402/clients/requests.py b/python/x402/src/x402/clients/requests.py index fee534694..50bf100c6 100644 --- a/python/x402/src/x402/clients/requests.py +++ b/python/x402/src/x402/clients/requests.py @@ -1,7 +1,14 @@ from typing import Optional -import requests import json -from requests.adapters import HTTPAdapter + +try: + import requests + from requests.adapters import HTTPAdapter +except ImportError as e: + raise ImportError( + "requests client requires the requests package. Install with: pip install x402[requests]" + ) from e + from eth_account import Account from x402.clients.base import ( x402Client, diff --git a/python/x402/src/x402/fastapi/__init__.py b/python/x402/src/x402/fastapi/__init__.py index e69de29bb..9eb2bb6b3 100644 --- a/python/x402/src/x402/fastapi/__init__.py +++ b/python/x402/src/x402/fastapi/__init__.py @@ -0,0 +1,14 @@ +""" +FastAPI middleware for x402 payment requirements. + +Install: pip install x402[fastapi] +Usage: from x402.fastapi.middleware import require_payment + +Example: + from fastapi import FastAPI + from x402.fastapi.middleware import require_payment + + app = FastAPI() + app.middleware("http")(require_payment(price="$0.001", pay_to_address="0x...")) +""" + diff --git a/python/x402/src/x402/fastapi/middleware.py b/python/x402/src/x402/fastapi/middleware.py index ebb983231..4048392d1 100644 --- a/python/x402/src/x402/fastapi/middleware.py +++ b/python/x402/src/x402/fastapi/middleware.py @@ -3,8 +3,14 @@ import logging from typing import Any, Callable, Optional, get_args, cast -from fastapi import Request -from fastapi.responses import JSONResponse, HTMLResponse +try: + from fastapi import Request + from fastapi.responses import JSONResponse, HTMLResponse +except ImportError as e: + raise ImportError( + "FastAPI middleware requires the fastapi package. Install with: pip install x402[fastapi]" + ) from e + from pydantic import validate_call from x402.common import ( diff --git a/python/x402/src/x402/flask/__init__.py b/python/x402/src/x402/flask/__init__.py index e69de29bb..db614ccec 100644 --- a/python/x402/src/x402/flask/__init__.py +++ b/python/x402/src/x402/flask/__init__.py @@ -0,0 +1,15 @@ +""" +Flask middleware for x402 payment requirements. + +Install: pip install x402[flask] +Usage: from x402.flask.middleware import PaymentMiddleware + +Example: + from flask import Flask + from x402.flask.middleware import PaymentMiddleware + + app = Flask(__name__) + middleware = PaymentMiddleware(app) + middleware.add(path="/weather", price="$0.001", pay_to_address="0x...") +""" + diff --git a/python/x402/src/x402/flask/middleware.py b/python/x402/src/x402/flask/middleware.py index 8f99c8d66..2d969a8fe 100644 --- a/python/x402/src/x402/flask/middleware.py +++ b/python/x402/src/x402/flask/middleware.py @@ -1,7 +1,13 @@ import base64 import json from typing import Any, Dict, Optional, Union, get_args, cast -from flask import Flask, request, g + +try: + from flask import Flask, request, g +except ImportError as e: + raise ImportError( + "Flask middleware requires the flask package. Install with: pip install x402[flask]" + ) from e from x402.path import path_is_match from x402.types import ( Price,