Skip to content

Commit 28a3287

Browse files
appleboyclaude
andcommitted
feat: add AuthGate Python SDK
Complete Python SDK mirroring the Go SDK architecture with idiomatic Python patterns. Includes OAuth 2.0 client (sync + async), OIDC auto-discovery with caching, credential storage (file + keyring + composite secure store), authentication flows (Device Code, Auth Code + PKCE, auto-refresh TokenSource), framework middleware (FastAPI, Flask, Django), and M2M client credentials with httpx.Auth transport. All 65 tests passing, ruff clean, mypy strict clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 parents  commit 28a3287

50 files changed

Lines changed: 4047 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: ["v*"]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
id-token: write
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Install build tools
22+
run: pip install build
23+
24+
- name: Build
25+
run: python -m build
26+
27+
- name: Publish to PyPI
28+
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/testing.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Testing
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.10", "3.11", "3.12", "3.13"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Install dependencies
25+
run: pip install -e ".[dev]"
26+
27+
- name: Lint
28+
run: make lint
29+
30+
- name: Type check
31+
run: make typecheck
32+
33+
- name: Test
34+
run: make test

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
__pycache__/
2+
*.py[cod]
3+
*$py.class
4+
*.egg-info/
5+
dist/
6+
build/
7+
*.egg
8+
.mypy_cache/
9+
.pytest_cache/
10+
.ruff_cache/
11+
.coverage
12+
htmlcov/
13+
*.so
14+
.venv/
15+
venv/

CLAUDE.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code when working with the Python SDK.
4+
5+
## Project Overview
6+
7+
Python SDK for AuthGate — mirrors the Go SDK's architecture using idiomatic Python patterns.
8+
9+
Package: `authgate` (Python 3.10+, src layout with hatchling build)
10+
11+
## Common Commands
12+
13+
```bash
14+
make install # Install in editable mode with dev deps
15+
make test # Run all tests with pytest
16+
make lint # Run ruff linter
17+
make fmt # Format code with ruff
18+
make typecheck # Run mypy strict
19+
```
20+
21+
## Code Style
22+
23+
- ruff for linting and formatting (line length 100)
24+
- mypy strict mode
25+
- Dataclasses over Pydantic (zero extra deps)
26+
- Sync + Async dual API in separate client classes
27+
- Framework-specific middleware: users only import what they need
28+
- `from __future__ import annotations` in all modules
29+
30+
## Architecture
31+
32+
- `oauth/` — Pure HTTP client layer (sync: `OAuthClient`, async: `AsyncOAuthClient`)
33+
- `discovery/` — OIDC auto-discovery with caching
34+
- `credstore/` — Generic credential storage (file, keyring, composite secure store)
35+
- `authflow/` — Device Code flow, Auth Code + PKCE, auto-refresh TokenSource
36+
- `middleware/` — Framework adapters (FastAPI, Flask, Django)
37+
- `clientcreds/` — M2M Client Credentials with auto-caching
38+
- `__init__.py``authenticate()` / `async_authenticate()` entry points
39+
40+
## Before Committing
41+
42+
All code must pass `make lint`, `make fmt`, and `make typecheck` before committing.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 AuthGate Contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.PHONY: test lint fmt typecheck install clean
2+
3+
install:
4+
pip install -e ".[dev]"
5+
6+
test:
7+
python -m pytest tests/ -v --tb=short
8+
9+
coverage:
10+
python -m coverage run -m pytest tests/ -v --tb=short
11+
python -m coverage report -m
12+
13+
lint:
14+
python -m ruff check src/ tests/
15+
16+
fmt:
17+
python -m ruff format src/ tests/
18+
python -m ruff check --fix src/ tests/
19+
20+
typecheck:
21+
python -m mypy src/authgate/
22+
23+
clean:
24+
rm -rf build/ dist/ *.egg-info src/*.egg-info .mypy_cache .pytest_cache .ruff_cache .coverage htmlcov/

README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# AuthGate Python SDK
2+
3+
Python SDK for [AuthGate](https://github.com/go-authgate) — OAuth 2.0 authentication and token management.
4+
5+
## Installation
6+
7+
```bash
8+
pip install authgate
9+
```
10+
11+
With framework support:
12+
13+
```bash
14+
pip install authgate[fastapi]
15+
pip install authgate[flask]
16+
pip install authgate[django]
17+
```
18+
19+
## Quick Start
20+
21+
```python
22+
from authgate import authenticate
23+
24+
client, token = authenticate(
25+
"https://auth.example.com",
26+
"my-client-id",
27+
scopes=["profile", "email"],
28+
)
29+
30+
print(f"Access token: {token.access_token}")
31+
```
32+
33+
## Async Usage
34+
35+
```python
36+
from authgate import async_authenticate
37+
38+
client, token = await async_authenticate(
39+
"https://auth.example.com",
40+
"my-client-id",
41+
scopes=["profile", "email"],
42+
)
43+
```
44+
45+
## Client Credentials (M2M)
46+
47+
```python
48+
from authgate.oauth import OAuthClient, Endpoints
49+
from authgate.clientcreds import TokenSource, BearerAuth
50+
import httpx
51+
52+
client = OAuthClient("my-service", endpoints, client_secret="secret")
53+
ts = TokenSource(client, scopes=["api"])
54+
55+
# Auto-attaches Bearer token to every request
56+
with httpx.Client(auth=BearerAuth(ts)) as http:
57+
resp = http.get("https://api.example.com/data")
58+
```
59+
60+
## Middleware
61+
62+
### FastAPI
63+
64+
```python
65+
from fastapi import FastAPI, Depends
66+
from authgate.middleware.fastapi import BearerAuth
67+
from authgate.middleware.models import TokenInfo
68+
69+
app = FastAPI()
70+
auth = BearerAuth(oauth_client)
71+
72+
@app.get("/protected")
73+
async def protected(info: TokenInfo = Depends(auth)):
74+
return {"user": info.user_id}
75+
```
76+
77+
### Flask
78+
79+
```python
80+
from flask import Flask
81+
from authgate.middleware.flask import bearer_auth, get_token_info
82+
83+
app = Flask(__name__)
84+
85+
@app.route("/protected")
86+
@bearer_auth(oauth_client)
87+
def protected():
88+
info = get_token_info()
89+
return {"user": info.user_id}
90+
```
91+
92+
## Development
93+
94+
```bash
95+
pip install -e ".[dev]"
96+
make test
97+
make lint
98+
make typecheck
99+
```
100+
101+
## License
102+
103+
MIT

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 = "authgate"
7+
dynamic = ["version"]
8+
description = "Python SDK for AuthGate — OAuth 2.0 authentication and token management"
9+
readme = "README.md"
10+
license = "MIT"
11+
requires-python = ">=3.10"
12+
authors = [{ name = "AuthGate Contributors" }]
13+
classifiers = [
14+
"Development Status :: 4 - Beta",
15+
"Intended Audience :: Developers",
16+
"License :: OSI Approved :: MIT License",
17+
"Programming Language :: Python :: 3",
18+
"Programming Language :: Python :: 3.10",
19+
"Programming Language :: Python :: 3.11",
20+
"Programming Language :: Python :: 3.12",
21+
"Programming Language :: Python :: 3.13",
22+
"Typing :: Typed",
23+
]
24+
dependencies = ["httpx>=0.27,<1", "keyring>=25,<27"]
25+
26+
[project.optional-dependencies]
27+
fastapi = ["fastapi>=0.100"]
28+
flask = ["flask>=2.3"]
29+
django = ["django>=4.2"]
30+
dev = [
31+
"pytest>=8",
32+
"pytest-asyncio>=0.23",
33+
"pytest-httpx>=0.30",
34+
"coverage>=7",
35+
"mypy>=1.10",
36+
"ruff>=0.5",
37+
]
38+
39+
[tool.hatch.version]
40+
path = "src/authgate/_version.py"
41+
42+
[tool.hatch.build.targets.wheel]
43+
packages = ["src/authgate"]
44+
45+
[tool.ruff]
46+
target-version = "py310"
47+
line-length = 100
48+
49+
[tool.ruff.lint]
50+
select = [
51+
"E",
52+
"F",
53+
"W",
54+
"I",
55+
"N",
56+
"UP",
57+
"B",
58+
"A",
59+
"SIM",
60+
"RUF",
61+
]
62+
63+
[tool.ruff.lint.isort]
64+
known-first-party = ["authgate"]
65+
66+
[tool.mypy]
67+
python_version = "3.10"
68+
strict = true
69+
warn_return_any = true
70+
warn_unused_configs = true
71+
72+
[[tool.mypy.overrides]]
73+
module = ["keyring", "keyring.errors"]
74+
ignore_missing_imports = true
75+
76+
[[tool.mypy.overrides]]
77+
module = ["fastapi", "fastapi.*"]
78+
ignore_missing_imports = true
79+
80+
[[tool.mypy.overrides]]
81+
module = ["flask", "flask.*"]
82+
ignore_missing_imports = true
83+
84+
[[tool.mypy.overrides]]
85+
module = ["django", "django.*"]
86+
ignore_missing_imports = true
87+
88+
[tool.pytest.ini_options]
89+
testpaths = ["tests"]
90+
asyncio_mode = "auto"

0 commit comments

Comments
 (0)