xident 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xident-1.0.0/.github/workflows/code-review.yml +45 -0
- xident-1.0.0/.github/workflows/pr-review.yml +181 -0
- xident-1.0.0/.github/workflows/test.yml +26 -0
- xident-1.0.0/PKG-INFO +218 -0
- xident-1.0.0/README.md +185 -0
- xident-1.0.0/examples/basic.py +62 -0
- xident-1.0.0/examples/django_view.py +98 -0
- xident-1.0.0/examples/fastapi_app.py +87 -0
- xident-1.0.0/examples/flask_app.py +70 -0
- xident-1.0.0/pyproject.toml +67 -0
- xident-1.0.0/src/xident/__init__.py +57 -0
- xident-1.0.0/src/xident/_client.py +185 -0
- xident-1.0.0/src/xident/_config.py +58 -0
- xident-1.0.0/src/xident/_http_client.py +290 -0
- xident-1.0.0/src/xident/_types.py +69 -0
- xident-1.0.0/src/xident/errors.py +104 -0
- xident-1.0.0/src/xident/py.typed +0 -0
- xident-1.0.0/src/xident/resources/__init__.py +6 -0
- xident-1.0.0/src/xident/resources/verification.py +185 -0
- xident-1.0.0/src/xident/resources/webhooks.py +158 -0
- xident-1.0.0/src/xident/responses/__init__.py +6 -0
- xident-1.0.0/src/xident/responses/init_result.py +31 -0
- xident-1.0.0/src/xident/responses/session_result.py +130 -0
- xident-1.0.0/tests/__init__.py +0 -0
- xident-1.0.0/tests/conftest.py +155 -0
- xident-1.0.0/tests/test_client.py +134 -0
- xident-1.0.0/tests/test_config.py +77 -0
- xident-1.0.0/tests/test_errors.py +129 -0
- xident-1.0.0/tests/test_http_client.py +323 -0
- xident-1.0.0/tests/test_session_result.py +207 -0
- xident-1.0.0/tests/test_verification.py +150 -0
- xident-1.0.0/tests/test_webhooks.py +179 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Code Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pull-requests: read
|
|
10
|
+
issues: read
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
review:
|
|
15
|
+
name: Claude Code Review
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 1
|
|
21
|
+
|
|
22
|
+
- name: Run Claude Code Review
|
|
23
|
+
uses: anthropics/claude-code-action@v1
|
|
24
|
+
with:
|
|
25
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
26
|
+
plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
|
|
27
|
+
plugins: "code-review@claude-code-plugins"
|
|
28
|
+
prompt: "/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
|
|
29
|
+
|
|
30
|
+
security:
|
|
31
|
+
name: Security Review
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
with:
|
|
36
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
37
|
+
fetch-depth: 2
|
|
38
|
+
|
|
39
|
+
- name: Claude Security Review
|
|
40
|
+
uses: anthropics/claude-code-security-review@main
|
|
41
|
+
with:
|
|
42
|
+
comment-pr: true
|
|
43
|
+
upload-results: true
|
|
44
|
+
env:
|
|
45
|
+
CLAUDE_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# PR Review — Python SDK
|
|
3
|
+
#
|
|
4
|
+
# Runs on every pull request to main.
|
|
5
|
+
# Jobs: test, code-review, security-review, pr-summary
|
|
6
|
+
# =============================================================================
|
|
7
|
+
|
|
8
|
+
name: PR Review
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
pull_request:
|
|
12
|
+
types: [opened, synchronize, reopened]
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
pull-requests: write
|
|
17
|
+
issues: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
name: Test
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
outputs:
|
|
25
|
+
test-result: ${{ steps.test.outcome }}
|
|
26
|
+
test-output: ${{ steps.test.outputs.output }}
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
|
|
35
|
+
- name: Run tests
|
|
36
|
+
id: test
|
|
37
|
+
continue-on-error: true
|
|
38
|
+
run: |
|
|
39
|
+
OUTPUT=$(python -m pytest tests/ 2>&1 || echo "No tests yet")
|
|
40
|
+
echo "output<<EOF" >> $GITHUB_OUTPUT
|
|
41
|
+
echo "$OUTPUT" | tail -50 >> $GITHUB_OUTPUT
|
|
42
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
43
|
+
|
|
44
|
+
code-review:
|
|
45
|
+
name: Claude Code Review
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
with:
|
|
50
|
+
fetch-depth: 0
|
|
51
|
+
|
|
52
|
+
- name: Claude Code Review
|
|
53
|
+
uses: anthropics/claude-code-action@v1
|
|
54
|
+
with:
|
|
55
|
+
prompt: |
|
|
56
|
+
Review this PR thoroughly. Focus on:
|
|
57
|
+
1. Code quality, readability, and adherence to project conventions
|
|
58
|
+
2. Potential bugs, edge cases, and error handling
|
|
59
|
+
3. Performance implications
|
|
60
|
+
4. Test coverage adequacy
|
|
61
|
+
5. Documentation completeness
|
|
62
|
+
|
|
63
|
+
Be specific with line references. Suggest improvements, not just problems.
|
|
64
|
+
env:
|
|
65
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
66
|
+
|
|
67
|
+
security-review:
|
|
68
|
+
name: Security Review
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
outputs:
|
|
71
|
+
findings-count: ${{ steps.security.outputs.findings-count }}
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
with:
|
|
75
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
76
|
+
fetch-depth: 2
|
|
77
|
+
|
|
78
|
+
- name: Claude Security Review
|
|
79
|
+
id: security
|
|
80
|
+
uses: anthropics/claude-code-security-review@main
|
|
81
|
+
with:
|
|
82
|
+
comment-pr: false
|
|
83
|
+
upload-results: true
|
|
84
|
+
env:
|
|
85
|
+
CLAUDE_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
86
|
+
|
|
87
|
+
pr-summary:
|
|
88
|
+
name: PR Summary
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
needs: [test, security-review]
|
|
91
|
+
if: always()
|
|
92
|
+
steps:
|
|
93
|
+
- uses: actions/checkout@v4
|
|
94
|
+
with:
|
|
95
|
+
fetch-depth: 0
|
|
96
|
+
|
|
97
|
+
- name: Generate PR Summary
|
|
98
|
+
uses: actions/github-script@v7
|
|
99
|
+
with:
|
|
100
|
+
script: |
|
|
101
|
+
const testResult = '${{ needs.test.outputs.test-result }}';
|
|
102
|
+
const testOutput = `${{ needs.test.outputs.test-output }}`;
|
|
103
|
+
const securityFindings = '${{ needs.security-review.outputs.findings-count }}' || '0';
|
|
104
|
+
|
|
105
|
+
const { data: pr } = await github.rest.pulls.get({
|
|
106
|
+
owner: context.repo.owner,
|
|
107
|
+
repo: context.repo.repo,
|
|
108
|
+
pull_number: context.issue.number
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const { data: files } = await github.rest.pulls.listFiles({
|
|
112
|
+
owner: context.repo.owner,
|
|
113
|
+
repo: context.repo.repo,
|
|
114
|
+
pull_number: context.issue.number
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const filesSummary = files.map(f =>
|
|
118
|
+
`- \`${f.filename}\` (+${f.additions}/-${f.deletions})`
|
|
119
|
+
).join('\n');
|
|
120
|
+
|
|
121
|
+
const testEmoji = testResult === 'success' ? '✅' : testResult === 'failure' ? '❌' : '⚠️';
|
|
122
|
+
const securityEmoji = parseInt(securityFindings) === 0 ? '✅' : '🔴';
|
|
123
|
+
|
|
124
|
+
const body = `## 📋 PR Review Summary
|
|
125
|
+
|
|
126
|
+
### 🎯 Changes Overview
|
|
127
|
+
**${files.length} files changed** | +${pr.additions} additions | -${pr.deletions} deletions
|
|
128
|
+
|
|
129
|
+
<details>
|
|
130
|
+
<summary>Files changed</summary>
|
|
131
|
+
|
|
132
|
+
${filesSummary}
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
### ${testEmoji} Test Results
|
|
136
|
+
**Status**: ${testResult || 'unknown'}
|
|
137
|
+
|
|
138
|
+
<details>
|
|
139
|
+
<summary>Test output</summary>
|
|
140
|
+
|
|
141
|
+
\`\`\`
|
|
142
|
+
${testOutput || 'No test output captured'}
|
|
143
|
+
\`\`\`
|
|
144
|
+
</details>
|
|
145
|
+
|
|
146
|
+
### ${securityEmoji} Security Review
|
|
147
|
+
**Findings**: ${securityFindings} issue(s) detected
|
|
148
|
+
|
|
149
|
+
> Full security review details available in the workflow artifacts.
|
|
150
|
+
|
|
151
|
+
### 🤖 Code Review
|
|
152
|
+
Claude Code Review runs as a separate job and posts inline comments directly on the PR.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
*Generated by PR Review workflow*`;
|
|
156
|
+
|
|
157
|
+
const comments = await github.rest.issues.listComments({
|
|
158
|
+
owner: context.repo.owner,
|
|
159
|
+
repo: context.repo.repo,
|
|
160
|
+
issue_number: context.issue.number
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const botComment = comments.data.find(c =>
|
|
164
|
+
c.body.includes('📋 PR Review Summary')
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (botComment) {
|
|
168
|
+
await github.rest.issues.updateComment({
|
|
169
|
+
owner: context.repo.owner,
|
|
170
|
+
repo: context.repo.repo,
|
|
171
|
+
comment_id: botComment.id,
|
|
172
|
+
body: body
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
await github.rest.issues.createComment({
|
|
176
|
+
owner: context.repo.owner,
|
|
177
|
+
repo: context.repo.repo,
|
|
178
|
+
issue_number: context.issue.number,
|
|
179
|
+
body: body
|
|
180
|
+
});
|
|
181
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
name: Test
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: pip install -e ".[dev]" 2>/dev/null || pip install -e . 2>/dev/null || pip install -r requirements.txt
|
|
24
|
+
|
|
25
|
+
- name: Run tests
|
|
26
|
+
run: python -m pytest tests/ -v --tb=short
|
xident-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xident
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for Xident age & identity verification
|
|
5
|
+
Project-URL: Homepage, https://xident.io
|
|
6
|
+
Project-URL: Documentation, https://docs.xident.io
|
|
7
|
+
Project-URL: Repository, https://github.com/xident-io/python-sdk
|
|
8
|
+
Project-URL: Changelog, https://github.com/xident-io/python-sdk/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Xident <support@xident.io>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: age-verification,identity,kyc,sdk,xident
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: httpx>=0.25
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Xident Python SDK
|
|
35
|
+
|
|
36
|
+
Official Python SDK for [Xident](https://xident.io) age and identity verification. Try it live at [demo.xident.io](https://demo.xident.io).
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/xident/)
|
|
39
|
+
[](https://pypi.org/project/xident/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install xident
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Requires Python 3.9+.
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from xident import Xident
|
|
54
|
+
|
|
55
|
+
client = Xident(api_key="sk_live_...")
|
|
56
|
+
|
|
57
|
+
# Create an init token
|
|
58
|
+
result = client.verification.init(
|
|
59
|
+
callback_url="https://example.com/callback",
|
|
60
|
+
min_age=18,
|
|
61
|
+
)
|
|
62
|
+
print(result.verify_url) # Redirect user here
|
|
63
|
+
|
|
64
|
+
# After callback, verify result server-side
|
|
65
|
+
session = client.verification.get_result("xtk_abc123")
|
|
66
|
+
if session.is_verified():
|
|
67
|
+
print(f"Verified! Age: {session.age_bracket()}+")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Async Support
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from xident import AsyncXident
|
|
74
|
+
|
|
75
|
+
client = AsyncXident(api_key="sk_live_...")
|
|
76
|
+
|
|
77
|
+
result = await client.verification.init(
|
|
78
|
+
callback_url="https://example.com/callback",
|
|
79
|
+
min_age=18,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
session = await client.verification.get_result("xtk_abc123")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
client = Xident(
|
|
89
|
+
api_key="sk_live_...", # Required: secret API key
|
|
90
|
+
base_url="https://...", # Override API URL
|
|
91
|
+
timeout=30, # Request timeout (seconds)
|
|
92
|
+
max_retries=3, # Retry on 5xx errors
|
|
93
|
+
headers={"X-Custom": "..."}, # Extra headers
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Verification
|
|
98
|
+
|
|
99
|
+
### Create Init Token
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
result = client.verification.init(
|
|
103
|
+
callback_url="https://example.com/callback", # Required
|
|
104
|
+
min_age=18, # Age threshold (12, 15, 18, 21, 25)
|
|
105
|
+
success_url="...", # Override redirect on success
|
|
106
|
+
failed_url="...", # Override redirect on failure
|
|
107
|
+
user_id="user_42", # Your user identifier
|
|
108
|
+
theme="dark", # Widget theme (light, dark, auto)
|
|
109
|
+
locale="de", # Widget locale
|
|
110
|
+
metadata="custom_data", # Opaque metadata string
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
print(result.token) # "xit_abc123"
|
|
114
|
+
print(result.verify_url) # Full URL to redirect user to
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Get Verification Result
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
session = client.verification.get_result("xtk_abc123")
|
|
121
|
+
|
|
122
|
+
session.is_verified() # True if completed successfully
|
|
123
|
+
session.is_failed() # True if verification failed
|
|
124
|
+
session.is_pending() # True if still in progress
|
|
125
|
+
session.is_terminal() # True if no more changes possible
|
|
126
|
+
|
|
127
|
+
session.age_bracket() # 18 (verified age threshold)
|
|
128
|
+
session.method() # "ml_fast", "ocr", etc.
|
|
129
|
+
session.country_code # "US", "DE", etc.
|
|
130
|
+
session.status # SessionStatus.COMPLETED
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Webhooks
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# Verify and parse a webhook event
|
|
137
|
+
event = client.webhooks.construct_event(
|
|
138
|
+
payload=request_body, # Raw JSON string or bytes
|
|
139
|
+
signature=x_xident_signature, # X-Xident-Signature header
|
|
140
|
+
secret="whsec_...", # Webhook secret from dashboard
|
|
141
|
+
tolerance=300, # Max age in seconds (default: 5 min)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
print(event["type"]) # "session.completed"
|
|
145
|
+
print(event["data"]) # Event payload dict
|
|
146
|
+
|
|
147
|
+
# Or verify signature only
|
|
148
|
+
client.webhooks.verify_signature(payload, signature, secret)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Error Handling
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from xident import (
|
|
155
|
+
XidentError, # Base for all errors
|
|
156
|
+
AuthenticationError, # 401/403
|
|
157
|
+
ValidationError, # 400
|
|
158
|
+
NotFoundError, # 404
|
|
159
|
+
RateLimitError, # 429 (has retry_after)
|
|
160
|
+
ServerError, # 5xx
|
|
161
|
+
NetworkError, # Connection failed
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
result = client.verification.init(callback_url="...")
|
|
166
|
+
except AuthenticationError as e:
|
|
167
|
+
print(f"Bad API key: {e.error_code}")
|
|
168
|
+
except RateLimitError as e:
|
|
169
|
+
print(f"Rate limited, retry in {e.retry_after}s")
|
|
170
|
+
except NetworkError as e:
|
|
171
|
+
print(f"Connection failed: {e}")
|
|
172
|
+
except XidentError as e:
|
|
173
|
+
print(f"SDK error: {e}")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Context Manager
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
# Auto-close HTTP client
|
|
180
|
+
with Xident(api_key="sk_live_...") as client:
|
|
181
|
+
result = client.verification.init(callback_url="...")
|
|
182
|
+
|
|
183
|
+
# Async
|
|
184
|
+
async with AsyncXident(api_key="sk_live_...") as client:
|
|
185
|
+
result = await client.verification.init(callback_url="...")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Framework Examples
|
|
189
|
+
|
|
190
|
+
See the `examples/` directory for complete integrations:
|
|
191
|
+
|
|
192
|
+
- **[basic.py](examples/basic.py)** -- Pure Python
|
|
193
|
+
- **[flask_app.py](examples/flask_app.py)** -- Flask
|
|
194
|
+
- **[django_view.py](examples/django_view.py)** -- Django
|
|
195
|
+
- **[fastapi_app.py](examples/fastapi_app.py)** -- FastAPI (async)
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Install dev dependencies
|
|
201
|
+
pip install -e ".[dev]"
|
|
202
|
+
|
|
203
|
+
# Run tests
|
|
204
|
+
pytest
|
|
205
|
+
|
|
206
|
+
# Run tests with coverage
|
|
207
|
+
pytest --cov=xident
|
|
208
|
+
|
|
209
|
+
# Type checking
|
|
210
|
+
mypy src/xident
|
|
211
|
+
|
|
212
|
+
# Linting
|
|
213
|
+
ruff check src/ tests/
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|
xident-1.0.0/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Xident Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [Xident](https://xident.io) age and identity verification. Try it live at [demo.xident.io](https://demo.xident.io).
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/xident/)
|
|
6
|
+
[](https://pypi.org/project/xident/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install xident
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.9+.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from xident import Xident
|
|
21
|
+
|
|
22
|
+
client = Xident(api_key="sk_live_...")
|
|
23
|
+
|
|
24
|
+
# Create an init token
|
|
25
|
+
result = client.verification.init(
|
|
26
|
+
callback_url="https://example.com/callback",
|
|
27
|
+
min_age=18,
|
|
28
|
+
)
|
|
29
|
+
print(result.verify_url) # Redirect user here
|
|
30
|
+
|
|
31
|
+
# After callback, verify result server-side
|
|
32
|
+
session = client.verification.get_result("xtk_abc123")
|
|
33
|
+
if session.is_verified():
|
|
34
|
+
print(f"Verified! Age: {session.age_bracket()}+")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Async Support
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from xident import AsyncXident
|
|
41
|
+
|
|
42
|
+
client = AsyncXident(api_key="sk_live_...")
|
|
43
|
+
|
|
44
|
+
result = await client.verification.init(
|
|
45
|
+
callback_url="https://example.com/callback",
|
|
46
|
+
min_age=18,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
session = await client.verification.get_result("xtk_abc123")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
client = Xident(
|
|
56
|
+
api_key="sk_live_...", # Required: secret API key
|
|
57
|
+
base_url="https://...", # Override API URL
|
|
58
|
+
timeout=30, # Request timeout (seconds)
|
|
59
|
+
max_retries=3, # Retry on 5xx errors
|
|
60
|
+
headers={"X-Custom": "..."}, # Extra headers
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Verification
|
|
65
|
+
|
|
66
|
+
### Create Init Token
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
result = client.verification.init(
|
|
70
|
+
callback_url="https://example.com/callback", # Required
|
|
71
|
+
min_age=18, # Age threshold (12, 15, 18, 21, 25)
|
|
72
|
+
success_url="...", # Override redirect on success
|
|
73
|
+
failed_url="...", # Override redirect on failure
|
|
74
|
+
user_id="user_42", # Your user identifier
|
|
75
|
+
theme="dark", # Widget theme (light, dark, auto)
|
|
76
|
+
locale="de", # Widget locale
|
|
77
|
+
metadata="custom_data", # Opaque metadata string
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
print(result.token) # "xit_abc123"
|
|
81
|
+
print(result.verify_url) # Full URL to redirect user to
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Get Verification Result
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
session = client.verification.get_result("xtk_abc123")
|
|
88
|
+
|
|
89
|
+
session.is_verified() # True if completed successfully
|
|
90
|
+
session.is_failed() # True if verification failed
|
|
91
|
+
session.is_pending() # True if still in progress
|
|
92
|
+
session.is_terminal() # True if no more changes possible
|
|
93
|
+
|
|
94
|
+
session.age_bracket() # 18 (verified age threshold)
|
|
95
|
+
session.method() # "ml_fast", "ocr", etc.
|
|
96
|
+
session.country_code # "US", "DE", etc.
|
|
97
|
+
session.status # SessionStatus.COMPLETED
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Webhooks
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# Verify and parse a webhook event
|
|
104
|
+
event = client.webhooks.construct_event(
|
|
105
|
+
payload=request_body, # Raw JSON string or bytes
|
|
106
|
+
signature=x_xident_signature, # X-Xident-Signature header
|
|
107
|
+
secret="whsec_...", # Webhook secret from dashboard
|
|
108
|
+
tolerance=300, # Max age in seconds (default: 5 min)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
print(event["type"]) # "session.completed"
|
|
112
|
+
print(event["data"]) # Event payload dict
|
|
113
|
+
|
|
114
|
+
# Or verify signature only
|
|
115
|
+
client.webhooks.verify_signature(payload, signature, secret)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Error Handling
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from xident import (
|
|
122
|
+
XidentError, # Base for all errors
|
|
123
|
+
AuthenticationError, # 401/403
|
|
124
|
+
ValidationError, # 400
|
|
125
|
+
NotFoundError, # 404
|
|
126
|
+
RateLimitError, # 429 (has retry_after)
|
|
127
|
+
ServerError, # 5xx
|
|
128
|
+
NetworkError, # Connection failed
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
result = client.verification.init(callback_url="...")
|
|
133
|
+
except AuthenticationError as e:
|
|
134
|
+
print(f"Bad API key: {e.error_code}")
|
|
135
|
+
except RateLimitError as e:
|
|
136
|
+
print(f"Rate limited, retry in {e.retry_after}s")
|
|
137
|
+
except NetworkError as e:
|
|
138
|
+
print(f"Connection failed: {e}")
|
|
139
|
+
except XidentError as e:
|
|
140
|
+
print(f"SDK error: {e}")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Context Manager
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# Auto-close HTTP client
|
|
147
|
+
with Xident(api_key="sk_live_...") as client:
|
|
148
|
+
result = client.verification.init(callback_url="...")
|
|
149
|
+
|
|
150
|
+
# Async
|
|
151
|
+
async with AsyncXident(api_key="sk_live_...") as client:
|
|
152
|
+
result = await client.verification.init(callback_url="...")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Framework Examples
|
|
156
|
+
|
|
157
|
+
See the `examples/` directory for complete integrations:
|
|
158
|
+
|
|
159
|
+
- **[basic.py](examples/basic.py)** -- Pure Python
|
|
160
|
+
- **[flask_app.py](examples/flask_app.py)** -- Flask
|
|
161
|
+
- **[django_view.py](examples/django_view.py)** -- Django
|
|
162
|
+
- **[fastapi_app.py](examples/fastapi_app.py)** -- FastAPI (async)
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Install dev dependencies
|
|
168
|
+
pip install -e ".[dev]"
|
|
169
|
+
|
|
170
|
+
# Run tests
|
|
171
|
+
pytest
|
|
172
|
+
|
|
173
|
+
# Run tests with coverage
|
|
174
|
+
pytest --cov=xident
|
|
175
|
+
|
|
176
|
+
# Type checking
|
|
177
|
+
mypy src/xident
|
|
178
|
+
|
|
179
|
+
# Linting
|
|
180
|
+
ruff check src/ tests/
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT
|