Skip to content

Commit d08eb41

Browse files
committed
feat: add complete Python SDK implementation
- Implement core modules: types, parser, matcher, headers, enforcer, discovery - Add middleware adapters for FastAPI, Flask, and Django frameworks - Include comprehensive test suite with 107 passing tests - Add detailed README with installation, quick start, and API reference - Create launch plan document outlining project roadmap
1 parent d9829eb commit d08eb41

22 files changed

Lines changed: 3912 additions & 0 deletions

.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,42 @@ jspm_packages/
6565
.env.local
6666
.env.production
6767

68+
# Python
69+
__pycache__/
70+
*.py[cod]
71+
*$py.class
72+
*.so
73+
.Python
74+
build/
75+
develop-eggs/
76+
dist/
77+
downloads/
78+
eggs/
79+
.eggs/
80+
lib/
81+
lib64/
82+
parts/
83+
sdist/
84+
var/
85+
wheels/
86+
*.egg-info/
87+
.installed.cfg
88+
*.egg
89+
MANIFEST
90+
.env
91+
venv/
92+
.venv/
93+
ENV/
94+
env/
95+
.pytest_cache/
96+
.coverage
97+
htmlcov/
98+
.tox/
99+
.nox/
100+
.mypy_cache/
101+
.dmypy.json
102+
dmypy.json
103+
68104
# parcel-bundler cache (https://parceljs.org/)
69105
.cache
70106
.parcel-cache

sdk/python/README.md

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# APoP Python SDK
2+
3+
**Agent Policy Protocol (APoP) SDK for Python** — the authorization & consent layer for the agentic web.
4+
5+
> Like `robots.txt`, but for AI agents — with fine-grained access control, identity verification, rate limiting, and cross-protocol interoperability.
6+
7+
[![PyPI version](https://img.shields.io/pypi/v/apop.svg)](https://pypi.org/project/apop/)
8+
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
9+
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
10+
11+
## Installation
12+
13+
```bash
14+
pip install apop
15+
```
16+
17+
With framework extras:
18+
19+
```bash
20+
pip install apop[fastapi] # FastAPI / Starlette
21+
pip install apop[flask] # Flask
22+
pip install apop[django] # Django
23+
pip install apop[all] # All frameworks
24+
```
25+
26+
## Quick Start
27+
28+
### 1. Create your policy (`agent-policy.json`)
29+
30+
```json
31+
{
32+
"version": "1.0",
33+
"defaultPolicy": {
34+
"allow": true,
35+
"disallow": ["extract", "automated_purchase"],
36+
"rateLimit": { "requests": 100, "window": "hour" }
37+
},
38+
"pathPolicies": [
39+
{ "path": "/admin/*", "allow": false },
40+
{ "path": "/api/**", "allow": true, "requireVerification": true }
41+
]
42+
}
43+
```
44+
45+
### 2. Use with FastAPI
46+
47+
```python
48+
from fastapi import FastAPI
49+
from apop.parser import parse_policy_file
50+
from apop.middleware.fastapi import create_fastapi_middleware, create_discovery_route
51+
from apop.types import MiddlewareOptions
52+
53+
app = FastAPI()
54+
55+
# Load and validate policy
56+
result = parse_policy_file("agent-policy.json")
57+
policy = result.policy
58+
59+
# Add enforcement middleware
60+
app.add_middleware(
61+
create_fastapi_middleware(MiddlewareOptions(policy=policy))
62+
)
63+
64+
# Serve policy at well-known URL
65+
app.get("/.well-known/agent-policy.json")(create_discovery_route(policy))
66+
```
67+
68+
### 3. Use with Flask
69+
70+
```python
71+
from flask import Flask
72+
from apop.parser import parse_policy_file
73+
from apop.middleware.flask import create_flask_middleware, create_flask_discovery
74+
from apop.types import MiddlewareOptions
75+
76+
app = Flask(__name__)
77+
78+
result = parse_policy_file("agent-policy.json")
79+
policy = result.policy
80+
81+
create_flask_middleware(app, MiddlewareOptions(policy=policy))
82+
create_flask_discovery(app, policy)
83+
```
84+
85+
### 4. Use with Django
86+
87+
```python
88+
# settings.py
89+
MIDDLEWARE = [
90+
# ... other middleware ...
91+
'apop.middleware.django.APoPMiddleware',
92+
]
93+
94+
APOP_POLICY_FILE = BASE_DIR / "agent-policy.json"
95+
```
96+
97+
### 5. Programmatic Enforcement
98+
99+
```python
100+
from apop.parser import parse_policy
101+
from apop.enforcer import enforce
102+
from apop.types import RequestContext
103+
104+
result = parse_policy('{"version":"1.0","defaultPolicy":{"allow":true}}')
105+
policy = result.policy
106+
107+
decision = enforce(policy, RequestContext(
108+
path="/api/data",
109+
agent_name="TestBot/1.0",
110+
agent_intent="read",
111+
agent_id="did:web:testbot.com",
112+
agent_signature="sig123",
113+
))
114+
115+
print(decision.status) # "allowed"
116+
print(decision.http_status) # 200
117+
print(decision.headers) # {"Agent-Policy-Status": "allowed", ...}
118+
```
119+
120+
### 6. Policy Discovery
121+
122+
```python
123+
import asyncio
124+
from apop.discovery import discover_policy
125+
126+
async def main():
127+
result = await discover_policy("example.com")
128+
if result.policy:
129+
print(f"Found policy via {result.method}")
130+
print(f"Version: {result.policy.version}")
131+
else:
132+
print(f"No policy found: {result.error}")
133+
134+
asyncio.run(main())
135+
```
136+
137+
## API Reference
138+
139+
### Core Modules
140+
141+
| Module | Description |
142+
| ---------------- | -------------------------------------------------------- |
143+
| `apop.parser` | Parse & validate `agent-policy.json` against JSON Schema |
144+
| `apop.enforcer` | Evaluate policy against request context |
145+
| `apop.matcher` | Glob-style path matching (`/*`, `/**`) |
146+
| `apop.headers` | Parse agent request headers, build response headers |
147+
| `apop.discovery` | 4-method discovery chain (well-known, header, meta, DNS) |
148+
| `apop.types` | Dataclass types for all APoP entities |
149+
150+
### Middleware Adapters
151+
152+
| Module | Framework |
153+
| ------------------------- | ------------------- |
154+
| `apop.middleware.fastapi` | FastAPI / Starlette |
155+
| `apop.middleware.flask` | Flask |
156+
| `apop.middleware.django` | Django |
157+
158+
### Key Functions
159+
160+
```python
161+
# Parser
162+
parse_policy(json_str: str) -> ParseResult
163+
validate_policy(data: Any) -> ParseResult
164+
parse_policy_file(path: str) -> ParseResult
165+
get_schema() -> dict
166+
167+
# Enforcer
168+
enforce(policy: AgentPolicy, ctx: RequestContext) -> EnforcementResult
169+
170+
# Matcher
171+
path_matches(url_path: str, pattern: str) -> bool
172+
match_path_policy(policy: AgentPolicy, url_path: str) -> PathPolicy | None
173+
merge_policy(default: PolicyRule, path_rule: PathPolicy | None) -> MergedPolicy
174+
175+
# Headers
176+
parse_request_headers(headers: dict) -> AgentRequestHeaders
177+
is_agent(headers: AgentRequestHeaders) -> bool
178+
parse_intents(header: str | None) -> list[str]
179+
build_allowed_headers(**kwargs) -> dict[str, str]
180+
build_denied_headers(**kwargs) -> dict[str, str]
181+
build_verification_headers(**kwargs) -> dict[str, str]
182+
build_rate_limited_headers(**kwargs) -> dict[str, str]
183+
184+
# Discovery (async)
185+
discover_policy(domain: str, options?: DiscoveryOptions) -> DiscoveryResult
186+
```
187+
188+
### Types
189+
190+
| Type | Description |
191+
| --------------------- | -------------------------------------------------------- |
192+
| `AgentPolicy` | Top-level policy manifest |
193+
| `PolicyRule` | Default or path-specific rules |
194+
| `PathPolicy` | Path-specific policy with allow/deny lists |
195+
| `RateLimit` | Rate limiting config (requests + window) |
196+
| `Verification` | Identity verification config |
197+
| `EnforcementResult` | Result of `enforce()` — status, HTTP code, headers, body |
198+
| `RequestContext` | Incoming request info for enforcement |
199+
| `AgentRequestHeaders` | Parsed agent headers |
200+
| `DiscoveryResult` | Result of `discover_policy()` |
201+
202+
### HTTP Status Codes
203+
204+
| Code | Constant | Meaning |
205+
| ---- | -------------------------------------------- | ---------------------------- |
206+
| 430 | `APOP_STATUS_CODES["ACTION_NOT_ALLOWED"]` | Action not allowed by policy |
207+
| 438 | `APOP_STATUS_CODES["RATE_LIMITED"]` | Rate limit exceeded |
208+
| 439 | `APOP_STATUS_CODES["VERIFICATION_REQUIRED"]` | Agent must verify identity |
209+
210+
### Action Types
211+
212+
```python
213+
from apop.types import ACTION_TYPES
214+
# ('read', 'index', 'extract', 'summarize', 'render',
215+
# 'api_call', 'form_submit', 'automated_purchase', 'tool_invoke', 'all')
216+
```
217+
218+
## Known Limitations
219+
220+
> These match the Node.js SDK and are documented for transparency:
221+
222+
- **Rate limiting is advisory-only**: Headers are set from config, but actual request counting is not implemented. Use a dedicated rate limiter (e.g., `slowapi`, Redis) for production enforcement.
223+
- **Signature verification is presence-check only**: `require_verification=True` checks that an `Agent-Signature` or `Agent-VC` header exists, but does not perform cryptographic validation.
224+
225+
## Development
226+
227+
```bash
228+
# Clone the repo
229+
git clone https://github.com/ArunSuperdom/agent-policy-protocol.git
230+
cd agent-policy-protocol/sdk/python
231+
232+
# Install dev dependencies
233+
pip install -e ".[dev]"
234+
235+
# Run tests
236+
pytest
237+
238+
# Type check
239+
mypy src/apop
240+
241+
# Lint
242+
ruff check src/ tests/
243+
```
244+
245+
## License
246+
247+
Apache 2.0 — see [LICENSE](../../LICENSE).
248+
249+
## Links
250+
251+
- [APoP Specification](../../spec/README.md)
252+
- [JSON Schema](../../spec/schema/agent-policy.schema.json)
253+
- [Node.js SDK](../node/README.md)
254+
- [Example Policies](../../examples/)
255+
- [GitHub Repository](https://github.com/ArunSuperdom/agent-policy-protocol)

sdk/python/pyproject.toml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "apop"
7+
version = "1.0.0"
8+
description = "Agent Policy Protocol (APoP) SDK for Python — authorization & consent layer for the agentic web"
9+
readme = "README.md"
10+
license = "Apache-2.0"
11+
requires-python = ">=3.10"
12+
authors = [
13+
{ name = "Arun Vijayarengan", email = "arun@superdom.ai" },
14+
]
15+
keywords = [
16+
"agent-policy",
17+
"apop",
18+
"ai-agents",
19+
"agentic-web",
20+
"authorization",
21+
"consent",
22+
"robotstxt",
23+
"mcp",
24+
"a2a",
25+
]
26+
classifiers = [
27+
"Development Status :: 4 - Beta",
28+
"Intended Audience :: Developers",
29+
"License :: OSI Approved :: Apache Software License",
30+
"Programming Language :: Python :: 3",
31+
"Programming Language :: Python :: 3.10",
32+
"Programming Language :: Python :: 3.11",
33+
"Programming Language :: Python :: 3.12",
34+
"Programming Language :: Python :: 3.13",
35+
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
36+
"Topic :: Security",
37+
"Topic :: Software Development :: Libraries :: Python Modules",
38+
"Typing :: Typed",
39+
]
40+
dependencies = [
41+
"jsonschema>=4.20.0",
42+
"httpx>=0.25.0",
43+
]
44+
45+
[project.optional-dependencies]
46+
fastapi = ["fastapi>=0.100.0", "starlette>=0.27.0"]
47+
flask = ["flask>=2.3.0"]
48+
django = ["django>=4.2"]
49+
all = [
50+
"fastapi>=0.100.0",
51+
"starlette>=0.27.0",
52+
"flask>=2.3.0",
53+
"django>=4.2",
54+
]
55+
dev = [
56+
"pytest>=7.4.0",
57+
"pytest-asyncio>=0.21.0",
58+
"pytest-httpx>=0.30.0",
59+
"ruff>=0.1.0",
60+
"mypy>=1.7.0",
61+
"fastapi>=0.100.0",
62+
"starlette>=0.27.0",
63+
"flask>=2.3.0",
64+
"django>=4.2",
65+
"httpx>=0.25.0",
66+
]
67+
68+
[project.urls]
69+
Homepage = "https://github.com/ArunSuperdom/agent-policy-protocol"
70+
Documentation = "https://agentpolicy.org"
71+
Repository = "https://github.com/ArunSuperdom/agent-policy-protocol"
72+
Issues = "https://github.com/ArunSuperdom/agent-policy-protocol/issues"
73+
74+
[tool.hatch.build.targets.wheel]
75+
packages = ["src/apop"]
76+
77+
[tool.pytest.ini_options]
78+
testpaths = ["tests"]
79+
asyncio_mode = "auto"
80+
81+
[tool.ruff]
82+
target-version = "py310"
83+
line-length = 100
84+
85+
[tool.ruff.lint]
86+
select = ["E", "F", "I", "N", "W", "UP"]
87+
88+
[tool.mypy]
89+
python_version = "3.10"
90+
strict = true

0 commit comments

Comments
 (0)