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.
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/xident.svg)](https://pypi.org/project/xident/)
39
+ [![Python versions](https://img.shields.io/pypi/pyversions/xident.svg)](https://pypi.org/project/xident/)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/xident.svg)](https://pypi.org/project/xident/)
6
+ [![Python versions](https://img.shields.io/pypi/pyversions/xident.svg)](https://pypi.org/project/xident/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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