simplex 1.0.8__tar.gz → 1.0.11__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.
Potentially problematic release.
This version of simplex might be problematic. Click here for more details.
- simplex-1.0.11/.DS_Store +0 -0
- simplex-1.0.11/.gitignore +33 -0
- {simplex-1.0.8/simplex.egg-info → simplex-1.0.11}/PKG-INFO +1 -1
- simplex-1.0.11/publish.sh +114 -0
- {simplex-1.0.8 → simplex-1.0.11}/pyproject.toml +2 -2
- simplex-1.0.11/requirements-dev.txt +14 -0
- {simplex-1.0.8 → simplex-1.0.11}/setup.py +1 -1
- {simplex-1.0.8 → simplex-1.0.11}/simplex/__init__.py +9 -1
- simplex-1.0.11/simplex/webhook.py +175 -0
- {simplex-1.0.8 → simplex-1.0.11/simplex.egg-info}/PKG-INFO +1 -1
- {simplex-1.0.8 → simplex-1.0.11}/simplex.egg-info/SOURCES.txt +5 -0
- {simplex-1.0.8 → simplex-1.0.11}/LICENSE +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/MANIFEST.in +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/README.md +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/requirements.txt +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/setup.cfg +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex/client.py +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex/errors.py +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex/http_client.py +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex/resources/__init__.py +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex/resources/workflow.py +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex/resources/workflow_session.py +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex/types.py +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex.egg-info/dependency_links.txt +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex.egg-info/requires.txt +0 -0
- {simplex-1.0.8 → simplex-1.0.11}/simplex.egg-info/top_level.txt +0 -0
simplex-1.0.11/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Environment
|
|
24
|
+
.env
|
|
25
|
+
.venv
|
|
26
|
+
env/
|
|
27
|
+
venv/
|
|
28
|
+
ENV/
|
|
29
|
+
|
|
30
|
+
# IDE
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
*.json
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Exit on error
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
echo "Starting Python SDK publish script..."
|
|
7
|
+
|
|
8
|
+
# Load environment variables from .env
|
|
9
|
+
if [ -f .env ]; then
|
|
10
|
+
echo "Found .env file"
|
|
11
|
+
export $(cat .env | grep -v '^#' | xargs)
|
|
12
|
+
echo "Loaded environment variables"
|
|
13
|
+
else
|
|
14
|
+
echo "Error: .env file not found"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Check if PYPI_API_TOKEN exists
|
|
19
|
+
if [ -z "$PYPI_API_TOKEN" ]; then
|
|
20
|
+
echo "Error: PYPI_API_TOKEN not found in .env file"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
echo "Found PYPI_API_TOKEN"
|
|
24
|
+
|
|
25
|
+
# Extract current version from setup.py and increment patch version
|
|
26
|
+
echo "Attempting to read version from setup.py..."
|
|
27
|
+
if [ ! -f setup.py ]; then
|
|
28
|
+
echo "Error: setup.py file not found"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "Contents of setup.py version line:"
|
|
33
|
+
grep "version=" setup.py || echo "No version line found"
|
|
34
|
+
|
|
35
|
+
# More portable version extraction that works on both Mac and Linux
|
|
36
|
+
current_version=$(grep "version=" setup.py | sed 's/.*version="\([^"]*\)".*/\1/')
|
|
37
|
+
if [ -z "$current_version" ]; then
|
|
38
|
+
echo "Error: Could not extract version from setup.py"
|
|
39
|
+
echo "Make sure setup.py contains a line like: version='1.0.0' or version=\"1.0.0\""
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
echo "Current version: $current_version"
|
|
43
|
+
IFS='.' read -r major minor patch <<< "$current_version"
|
|
44
|
+
new_patch=$((patch + 1))
|
|
45
|
+
new_version="$major.$minor.$new_patch"
|
|
46
|
+
|
|
47
|
+
# Update version in setup.py (Mac-compatible sed syntax)
|
|
48
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
49
|
+
# Mac version - escape the quotes and use a different delimiter
|
|
50
|
+
sed -i '' "s|version=\"${current_version}\"|version=\"${new_version}\"|" setup.py
|
|
51
|
+
else
|
|
52
|
+
# Linux version
|
|
53
|
+
sed -i "s/version=\"${current_version}\"/version=\"${new_version}\"/" setup.py
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "Updated setup.py from $current_version to $new_version"
|
|
57
|
+
|
|
58
|
+
# Update version in pyproject.toml
|
|
59
|
+
if [ -f pyproject.toml ]; then
|
|
60
|
+
echo "Updating version in pyproject.toml..."
|
|
61
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
62
|
+
sed -i '' "s|version = \".*\"|version = \"${new_version}\"|" pyproject.toml
|
|
63
|
+
else
|
|
64
|
+
sed -i "s/version = \".*\"/version = \"${new_version}\"/" pyproject.toml
|
|
65
|
+
fi
|
|
66
|
+
echo "Updated pyproject.toml to $new_version"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Update version in simplex/__init__.py
|
|
70
|
+
if [ -f simplex/__init__.py ]; then
|
|
71
|
+
echo "Updating version in simplex/__init__.py..."
|
|
72
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
73
|
+
sed -i '' "s|__version__ = \".*\"|__version__ = \"${new_version}\"|" simplex/__init__.py
|
|
74
|
+
else
|
|
75
|
+
sed -i "s/__version__ = \".*\"/__version__ = \"${new_version}\"/" simplex/__init__.py
|
|
76
|
+
fi
|
|
77
|
+
echo "Updated simplex/__init__.py to $new_version"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
echo "All version files updated from $current_version to $new_version"
|
|
81
|
+
|
|
82
|
+
# Check BASE_URL in simplex/client.py
|
|
83
|
+
echo "Checking BASE_URL in simplex/client.py..."
|
|
84
|
+
if ! grep -q 'https://api.simplex.sh' simplex/client.py; then
|
|
85
|
+
echo "Warning: BASE_URL in simplex/client.py may not be set to production URL"
|
|
86
|
+
echo "Continuing anyway..."
|
|
87
|
+
fi
|
|
88
|
+
echo "BASE_URL check passed"
|
|
89
|
+
|
|
90
|
+
# Clean previous builds
|
|
91
|
+
echo "Cleaning previous builds..."
|
|
92
|
+
rm -rf dist/ build/ *.egg-info simplex.egg-info
|
|
93
|
+
|
|
94
|
+
# Build the package using modern build tool
|
|
95
|
+
echo "Building package..."
|
|
96
|
+
python -m build
|
|
97
|
+
|
|
98
|
+
# Upload to PyPI using API token
|
|
99
|
+
echo "Uploading to PyPI..."
|
|
100
|
+
TWINE_USERNAME=__token__ TWINE_PASSWORD=$PYPI_API_TOKEN python -m twine upload dist/*
|
|
101
|
+
|
|
102
|
+
echo "Successfully published version $new_version to PyPI"
|
|
103
|
+
|
|
104
|
+
# Create git commit and tag
|
|
105
|
+
echo "Creating git commit and tag..."
|
|
106
|
+
git add setup.py pyproject.toml simplex/__init__.py
|
|
107
|
+
git commit -m "Bump Python SDK version to $new_version"
|
|
108
|
+
git tag "python-v$new_version"
|
|
109
|
+
git push && git push --tags
|
|
110
|
+
|
|
111
|
+
echo "Created and pushed git tag python-v$new_version"
|
|
112
|
+
echo ""
|
|
113
|
+
echo "🎉 Python SDK v$new_version published successfully!"
|
|
114
|
+
echo "Users can now install with: pip install simplex"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simplex"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.11"
|
|
8
8
|
description = "Official Python SDK for the Simplex API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -53,7 +53,7 @@ target-version = ['py38', 'py39', 'py310', 'py311']
|
|
|
53
53
|
include = '\.pyi?$'
|
|
54
54
|
|
|
55
55
|
[tool.mypy]
|
|
56
|
-
python_version = "
|
|
56
|
+
python_version = "1.0.11"
|
|
57
57
|
warn_return_any = true
|
|
58
58
|
warn_unused_configs = true
|
|
59
59
|
disallow_untyped_defs = false
|
|
@@ -20,8 +20,13 @@ from simplex.errors import (
|
|
|
20
20
|
)
|
|
21
21
|
from simplex.resources.workflow import Workflow
|
|
22
22
|
from simplex.resources.workflow_session import WorkflowSession
|
|
23
|
+
from simplex.webhook import (
|
|
24
|
+
verify_simplex_webhook,
|
|
25
|
+
verify_simplex_webhook_dict,
|
|
26
|
+
WebhookVerificationError,
|
|
27
|
+
)
|
|
23
28
|
|
|
24
|
-
__version__ = "1.0.
|
|
29
|
+
__version__ = "1.0.11"
|
|
25
30
|
__all__ = [
|
|
26
31
|
"SimplexClient",
|
|
27
32
|
"SimplexError",
|
|
@@ -32,4 +37,7 @@ __all__ = [
|
|
|
32
37
|
"WorkflowError",
|
|
33
38
|
"Workflow",
|
|
34
39
|
"WorkflowSession",
|
|
40
|
+
"verify_simplex_webhook",
|
|
41
|
+
"verify_simplex_webhook_dict",
|
|
42
|
+
"WebhookVerificationError",
|
|
35
43
|
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Webhook verification utilities for Simplex webhooks.
|
|
3
|
+
|
|
4
|
+
This module provides functions to verify the authenticity of webhook requests
|
|
5
|
+
from Simplex using HMAC-SHA256 signature verification.
|
|
6
|
+
|
|
7
|
+
Example usage:
|
|
8
|
+
>>> from simplex import verify_simplex_webhook, WebhookVerificationError
|
|
9
|
+
>>>
|
|
10
|
+
>>> try:
|
|
11
|
+
>>> verify_simplex_webhook(
|
|
12
|
+
>>> body=request.body,
|
|
13
|
+
>>> signature=request.headers.get('X-Simplex-Signature'),
|
|
14
|
+
>>> webhook_secret='your-webhook-secret'
|
|
15
|
+
>>> )
|
|
16
|
+
>>> # Webhook verified, safe to process
|
|
17
|
+
>>> except WebhookVerificationError as e:
|
|
18
|
+
>>> # Invalid webhook
|
|
19
|
+
>>> print(f"Verification failed: {e}")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import hmac
|
|
23
|
+
import hashlib
|
|
24
|
+
from typing import Optional, Union, Dict
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WebhookVerificationError(Exception):
|
|
28
|
+
"""
|
|
29
|
+
Exception raised when webhook verification fails.
|
|
30
|
+
|
|
31
|
+
This error is raised when:
|
|
32
|
+
- The signature header is missing
|
|
33
|
+
- The signature is invalid
|
|
34
|
+
- The signature doesn't match the expected value
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, message: str):
|
|
38
|
+
"""
|
|
39
|
+
Initialize a WebhookVerificationError.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
message: Description of the verification failure
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(message)
|
|
45
|
+
self.message = message
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def verify_simplex_webhook(
|
|
49
|
+
body: Union[str, bytes],
|
|
50
|
+
signature: Optional[str],
|
|
51
|
+
webhook_secret: str
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Verify a Simplex webhook request using HMAC-SHA256 signature verification.
|
|
55
|
+
|
|
56
|
+
This function ensures that webhook requests are authentic and haven't been
|
|
57
|
+
tampered with in transit. It uses the same pattern as GitHub webhooks.
|
|
58
|
+
|
|
59
|
+
The signature is computed as: HMAC-SHA256(webhook_secret, request_body)
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
body: Raw request body as string or bytes (must be the original unparsed body)
|
|
63
|
+
signature: The X-Simplex-Signature header value from the request
|
|
64
|
+
webhook_secret: Your webhook secret from the Simplex dashboard
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
WebhookVerificationError: If signature is missing, invalid, or verification fails
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> # Flask example
|
|
71
|
+
>>> from flask import Flask, request, jsonify
|
|
72
|
+
>>> from simplex import verify_simplex_webhook, WebhookVerificationError
|
|
73
|
+
>>>
|
|
74
|
+
>>> app = Flask(__name__)
|
|
75
|
+
>>>
|
|
76
|
+
>>> @app.route('/webhook', methods=['POST'])
|
|
77
|
+
>>> def webhook():
|
|
78
|
+
>>> try:
|
|
79
|
+
>>> verify_simplex_webhook(
|
|
80
|
+
>>> body=request.get_data(as_text=True),
|
|
81
|
+
>>> signature=request.headers.get('X-Simplex-Signature'),
|
|
82
|
+
>>> webhook_secret='your-webhook-secret'
|
|
83
|
+
>>> )
|
|
84
|
+
>>>
|
|
85
|
+
>>> # Webhook verified, safe to process
|
|
86
|
+
>>> payload = request.get_json()
|
|
87
|
+
>>> print(f"Received webhook: {payload['session_id']}")
|
|
88
|
+
>>> return jsonify({'received': True})
|
|
89
|
+
>>>
|
|
90
|
+
>>> except WebhookVerificationError as e:
|
|
91
|
+
>>> return jsonify({'error': str(e)}), 401
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> # FastAPI example
|
|
95
|
+
>>> from fastapi import FastAPI, Request, HTTPException
|
|
96
|
+
>>> from simplex import verify_simplex_webhook, WebhookVerificationError
|
|
97
|
+
>>>
|
|
98
|
+
>>> app = FastAPI()
|
|
99
|
+
>>>
|
|
100
|
+
>>> @app.post("/webhook")
|
|
101
|
+
>>> async def webhook(request: Request):
|
|
102
|
+
>>> body = await request.body()
|
|
103
|
+
>>> signature = request.headers.get('X-Simplex-Signature')
|
|
104
|
+
>>>
|
|
105
|
+
>>> try:
|
|
106
|
+
>>> verify_simplex_webhook(
|
|
107
|
+
>>> body=body,
|
|
108
|
+
>>> signature=signature,
|
|
109
|
+
>>> webhook_secret='your-webhook-secret'
|
|
110
|
+
>>> )
|
|
111
|
+
>>>
|
|
112
|
+
>>> payload = await request.json()
|
|
113
|
+
>>> return {'received': True}
|
|
114
|
+
>>>
|
|
115
|
+
>>> except WebhookVerificationError as e:
|
|
116
|
+
>>> raise HTTPException(status_code=401, detail=str(e))
|
|
117
|
+
"""
|
|
118
|
+
# 1. Check required signature
|
|
119
|
+
if not signature:
|
|
120
|
+
raise WebhookVerificationError('Missing X-Simplex-Signature header')
|
|
121
|
+
|
|
122
|
+
# 2. Ensure body is bytes for HMAC computation
|
|
123
|
+
if isinstance(body, str):
|
|
124
|
+
body_bytes = body.encode('utf-8')
|
|
125
|
+
else:
|
|
126
|
+
body_bytes = body
|
|
127
|
+
|
|
128
|
+
# 3. Compute expected signature
|
|
129
|
+
expected_signature = hmac.new(
|
|
130
|
+
webhook_secret.encode('utf-8'),
|
|
131
|
+
body_bytes,
|
|
132
|
+
hashlib.sha256
|
|
133
|
+
).hexdigest()
|
|
134
|
+
|
|
135
|
+
# 4. Compare signatures using constant-time comparison to prevent timing attacks
|
|
136
|
+
if not hmac.compare_digest(expected_signature, signature):
|
|
137
|
+
raise WebhookVerificationError('Invalid webhook signature')
|
|
138
|
+
|
|
139
|
+
# Webhook verified successfully!
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def verify_simplex_webhook_dict(
|
|
143
|
+
body: Union[str, bytes],
|
|
144
|
+
headers: Dict[str, str],
|
|
145
|
+
webhook_secret: str
|
|
146
|
+
) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Verify a Simplex webhook request using a headers dictionary.
|
|
149
|
+
|
|
150
|
+
This is a convenience wrapper around verify_simplex_webhook() that accepts
|
|
151
|
+
a headers dictionary and handles case-insensitive header lookup.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
body: Raw request body as string or bytes
|
|
155
|
+
headers: Dictionary of request headers
|
|
156
|
+
webhook_secret: Your webhook secret from the Simplex dashboard
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
WebhookVerificationError: If verification fails
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
>>> verify_simplex_webhook_dict(
|
|
163
|
+
>>> body=request.body,
|
|
164
|
+
>>> headers=dict(request.headers),
|
|
165
|
+
>>> webhook_secret='your-secret'
|
|
166
|
+
>>> )
|
|
167
|
+
"""
|
|
168
|
+
# Try to find the signature header (case-insensitive)
|
|
169
|
+
signature = None
|
|
170
|
+
for key, value in headers.items():
|
|
171
|
+
if key.lower() == 'x-simplex-signature':
|
|
172
|
+
signature = value
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
verify_simplex_webhook(body, signature, webhook_secret)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.gitignore
|
|
1
3
|
LICENSE
|
|
2
4
|
MANIFEST.in
|
|
3
5
|
README.md
|
|
6
|
+
publish.sh
|
|
4
7
|
pyproject.toml
|
|
8
|
+
requirements-dev.txt
|
|
5
9
|
requirements.txt
|
|
6
10
|
setup.py
|
|
7
11
|
simplex/__init__.py
|
|
@@ -9,6 +13,7 @@ simplex/client.py
|
|
|
9
13
|
simplex/errors.py
|
|
10
14
|
simplex/http_client.py
|
|
11
15
|
simplex/types.py
|
|
16
|
+
simplex/webhook.py
|
|
12
17
|
simplex.egg-info/PKG-INFO
|
|
13
18
|
simplex.egg-info/SOURCES.txt
|
|
14
19
|
simplex.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|