sweatstack 0.60.0__py3-none-any.whl → 0.62.0__py3-none-any.whl
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.
- sweatstack/fastapi/__init__.py +74 -7
- sweatstack/fastapi/config.py +68 -2
- sweatstack/fastapi/dependencies.py +309 -64
- sweatstack/fastapi/models.py +175 -0
- sweatstack/fastapi/routes.py +223 -17
- sweatstack/fastapi/token_stores.py +238 -0
- sweatstack/fastapi/webhooks.py +201 -0
- {sweatstack-0.60.0.dist-info → sweatstack-0.62.0.dist-info}/METADATA +1 -1
- {sweatstack-0.60.0.dist-info → sweatstack-0.62.0.dist-info}/RECORD +11 -8
- {sweatstack-0.60.0.dist-info → sweatstack-0.62.0.dist-info}/WHEEL +0 -0
- {sweatstack-0.60.0.dist-info → sweatstack-0.62.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Token store implementations for development use.
|
|
2
|
+
|
|
3
|
+
WARNING: These implementations are for LOCAL DEVELOPMENT ONLY.
|
|
4
|
+
Do not use in production. Implement your own TokenStore with proper
|
|
5
|
+
database infrastructure, encryption, and monitoring.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import sqlite3
|
|
12
|
+
import threading
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from .models import StoredTokens, TokenStore
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SQLiteTokenStore(TokenStore):
|
|
22
|
+
"""SQLite-based TokenStore for local development.
|
|
23
|
+
|
|
24
|
+
WARNING: This implementation is for LOCAL DEVELOPMENT ONLY.
|
|
25
|
+
Do not use in production. For production, implement your own
|
|
26
|
+
TokenStore with proper database infrastructure, encryption,
|
|
27
|
+
and monitoring.
|
|
28
|
+
|
|
29
|
+
Features:
|
|
30
|
+
- File-based storage (no external database required)
|
|
31
|
+
- Thread-safe with connection-per-thread pattern
|
|
32
|
+
- Auto-creates table on first use
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
db_path: Path to SQLite database file. Defaults to "sweatstack_tokens.db"
|
|
36
|
+
in current directory.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, db_path: str | Path = "sweatstack_tokens.db"):
|
|
40
|
+
self.db_path = str(db_path)
|
|
41
|
+
self._local = threading.local()
|
|
42
|
+
|
|
43
|
+
logger.warning(
|
|
44
|
+
"SQLiteTokenStore is for LOCAL DEVELOPMENT ONLY. "
|
|
45
|
+
"Do not use in production. Implement a proper TokenStore "
|
|
46
|
+
"with your production database."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
self._init_db()
|
|
50
|
+
|
|
51
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
52
|
+
"""Get thread-local database connection."""
|
|
53
|
+
if not hasattr(self._local, "connection"):
|
|
54
|
+
self._local.connection = sqlite3.connect(
|
|
55
|
+
self.db_path,
|
|
56
|
+
check_same_thread=False,
|
|
57
|
+
)
|
|
58
|
+
self._local.connection.row_factory = sqlite3.Row
|
|
59
|
+
return self._local.connection
|
|
60
|
+
|
|
61
|
+
def _init_db(self) -> None:
|
|
62
|
+
"""Create tokens table if it doesn't exist."""
|
|
63
|
+
conn = self._get_connection()
|
|
64
|
+
conn.execute("""
|
|
65
|
+
CREATE TABLE IF NOT EXISTS sweatstack_tokens (
|
|
66
|
+
user_id TEXT PRIMARY KEY,
|
|
67
|
+
access_token TEXT NOT NULL,
|
|
68
|
+
refresh_token TEXT NOT NULL,
|
|
69
|
+
expires_at TEXT NOT NULL
|
|
70
|
+
)
|
|
71
|
+
""")
|
|
72
|
+
conn.commit()
|
|
73
|
+
|
|
74
|
+
def save(self, tokens: StoredTokens) -> None:
|
|
75
|
+
"""Save or update tokens for a user."""
|
|
76
|
+
conn = self._get_connection()
|
|
77
|
+
conn.execute(
|
|
78
|
+
"""
|
|
79
|
+
INSERT INTO sweatstack_tokens (user_id, access_token, refresh_token, expires_at)
|
|
80
|
+
VALUES (?, ?, ?, ?)
|
|
81
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
82
|
+
access_token = excluded.access_token,
|
|
83
|
+
refresh_token = excluded.refresh_token,
|
|
84
|
+
expires_at = excluded.expires_at
|
|
85
|
+
""",
|
|
86
|
+
(
|
|
87
|
+
tokens.user_id,
|
|
88
|
+
tokens.access_token,
|
|
89
|
+
tokens.refresh_token,
|
|
90
|
+
tokens.expires_at.isoformat(),
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
conn.commit()
|
|
94
|
+
|
|
95
|
+
def load(self, user_id: str) -> StoredTokens | None:
|
|
96
|
+
"""Load tokens for a user. Returns None if not found."""
|
|
97
|
+
conn = self._get_connection()
|
|
98
|
+
row = conn.execute(
|
|
99
|
+
"SELECT * FROM sweatstack_tokens WHERE user_id = ?",
|
|
100
|
+
(user_id,),
|
|
101
|
+
).fetchone()
|
|
102
|
+
|
|
103
|
+
if row:
|
|
104
|
+
return StoredTokens(
|
|
105
|
+
user_id=row["user_id"],
|
|
106
|
+
access_token=row["access_token"],
|
|
107
|
+
refresh_token=row["refresh_token"],
|
|
108
|
+
expires_at=datetime.fromisoformat(row["expires_at"]),
|
|
109
|
+
)
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def delete(self, user_id: str) -> None:
|
|
113
|
+
"""Delete tokens for a user. Idempotent."""
|
|
114
|
+
conn = self._get_connection()
|
|
115
|
+
conn.execute(
|
|
116
|
+
"DELETE FROM sweatstack_tokens WHERE user_id = ?",
|
|
117
|
+
(user_id,),
|
|
118
|
+
)
|
|
119
|
+
conn.commit()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class EncryptedSQLiteTokenStore(TokenStore):
|
|
123
|
+
"""Encrypted SQLite TokenStore for local development.
|
|
124
|
+
|
|
125
|
+
WARNING: This implementation is for LOCAL DEVELOPMENT ONLY.
|
|
126
|
+
Do not use in production. This demonstrates the encryption pattern -
|
|
127
|
+
adapt it for your production database.
|
|
128
|
+
|
|
129
|
+
Encrypts access_token and refresh_token using Fernet (AES-128-CBC + HMAC).
|
|
130
|
+
user_id and expires_at are stored unencrypted for queries.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
encryption_key: Fernet key for encryption. Generate with:
|
|
134
|
+
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
135
|
+
db_path: Path to SQLite database file.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(
|
|
139
|
+
self,
|
|
140
|
+
encryption_key: str | bytes,
|
|
141
|
+
db_path: str | Path = "sweatstack_tokens_encrypted.db",
|
|
142
|
+
):
|
|
143
|
+
# Import here so cryptography is optional for basic usage
|
|
144
|
+
from cryptography.fernet import Fernet
|
|
145
|
+
|
|
146
|
+
self.db_path = str(db_path)
|
|
147
|
+
self._local = threading.local()
|
|
148
|
+
|
|
149
|
+
if isinstance(encryption_key, str):
|
|
150
|
+
encryption_key = encryption_key.encode()
|
|
151
|
+
self._fernet = Fernet(encryption_key)
|
|
152
|
+
|
|
153
|
+
logger.warning(
|
|
154
|
+
"EncryptedSQLiteTokenStore is for LOCAL DEVELOPMENT ONLY. "
|
|
155
|
+
"Do not use in production. This demonstrates the encryption "
|
|
156
|
+
"pattern - implement with your production database."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
self._init_db()
|
|
160
|
+
|
|
161
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
162
|
+
"""Get thread-local database connection."""
|
|
163
|
+
if not hasattr(self._local, "connection"):
|
|
164
|
+
self._local.connection = sqlite3.connect(
|
|
165
|
+
self.db_path,
|
|
166
|
+
check_same_thread=False,
|
|
167
|
+
)
|
|
168
|
+
self._local.connection.row_factory = sqlite3.Row
|
|
169
|
+
return self._local.connection
|
|
170
|
+
|
|
171
|
+
def _init_db(self) -> None:
|
|
172
|
+
"""Create tokens table if it doesn't exist."""
|
|
173
|
+
conn = self._get_connection()
|
|
174
|
+
conn.execute("""
|
|
175
|
+
CREATE TABLE IF NOT EXISTS sweatstack_tokens (
|
|
176
|
+
user_id TEXT PRIMARY KEY,
|
|
177
|
+
access_token_encrypted TEXT NOT NULL,
|
|
178
|
+
refresh_token_encrypted TEXT NOT NULL,
|
|
179
|
+
expires_at TEXT NOT NULL
|
|
180
|
+
)
|
|
181
|
+
""")
|
|
182
|
+
conn.commit()
|
|
183
|
+
|
|
184
|
+
def _encrypt(self, value: str) -> str:
|
|
185
|
+
"""Encrypt a string value."""
|
|
186
|
+
return self._fernet.encrypt(value.encode()).decode()
|
|
187
|
+
|
|
188
|
+
def _decrypt(self, value: str) -> str:
|
|
189
|
+
"""Decrypt a string value."""
|
|
190
|
+
return self._fernet.decrypt(value.encode()).decode()
|
|
191
|
+
|
|
192
|
+
def save(self, tokens: StoredTokens) -> None:
|
|
193
|
+
"""Save or update tokens for a user."""
|
|
194
|
+
conn = self._get_connection()
|
|
195
|
+
conn.execute(
|
|
196
|
+
"""
|
|
197
|
+
INSERT INTO sweatstack_tokens
|
|
198
|
+
(user_id, access_token_encrypted, refresh_token_encrypted, expires_at)
|
|
199
|
+
VALUES (?, ?, ?, ?)
|
|
200
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
201
|
+
access_token_encrypted = excluded.access_token_encrypted,
|
|
202
|
+
refresh_token_encrypted = excluded.refresh_token_encrypted,
|
|
203
|
+
expires_at = excluded.expires_at
|
|
204
|
+
""",
|
|
205
|
+
(
|
|
206
|
+
tokens.user_id,
|
|
207
|
+
self._encrypt(tokens.access_token),
|
|
208
|
+
self._encrypt(tokens.refresh_token),
|
|
209
|
+
tokens.expires_at.isoformat(),
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
conn.commit()
|
|
213
|
+
|
|
214
|
+
def load(self, user_id: str) -> StoredTokens | None:
|
|
215
|
+
"""Load tokens for a user. Returns None if not found."""
|
|
216
|
+
conn = self._get_connection()
|
|
217
|
+
row = conn.execute(
|
|
218
|
+
"SELECT * FROM sweatstack_tokens WHERE user_id = ?",
|
|
219
|
+
(user_id,),
|
|
220
|
+
).fetchone()
|
|
221
|
+
|
|
222
|
+
if row:
|
|
223
|
+
return StoredTokens(
|
|
224
|
+
user_id=row["user_id"],
|
|
225
|
+
access_token=self._decrypt(row["access_token_encrypted"]),
|
|
226
|
+
refresh_token=self._decrypt(row["refresh_token_encrypted"]),
|
|
227
|
+
expires_at=datetime.fromisoformat(row["expires_at"]),
|
|
228
|
+
)
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
def delete(self, user_id: str) -> None:
|
|
232
|
+
"""Delete tokens for a user. Idempotent."""
|
|
233
|
+
conn = self._get_connection()
|
|
234
|
+
conn.execute(
|
|
235
|
+
"DELETE FROM sweatstack_tokens WHERE user_id = ?",
|
|
236
|
+
(user_id,),
|
|
237
|
+
)
|
|
238
|
+
conn.commit()
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Webhook handling for the FastAPI plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import hmac
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Annotated
|
|
12
|
+
|
|
13
|
+
from fastapi import Depends, HTTPException, Request
|
|
14
|
+
|
|
15
|
+
from .config import get_config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Exceptions
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WebhookError(Exception):
|
|
24
|
+
"""Base class for webhook-related errors."""
|
|
25
|
+
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WebhookVerificationError(WebhookError):
|
|
30
|
+
"""Raised when webhook signature verification fails."""
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WebhookTokenStoreError(WebhookError):
|
|
36
|
+
"""Raised when TokenStore is required but not configured."""
|
|
37
|
+
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WebhookUserNotFoundError(WebhookError):
|
|
42
|
+
"""Raised when no stored tokens exist for the webhook's user_id."""
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WebhookTokenRefreshError(WebhookError):
|
|
48
|
+
"""Raised when token refresh fails in webhook context."""
|
|
49
|
+
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Webhook payload model
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class WebhookPayloadModel:
|
|
60
|
+
"""Verified webhook payload data.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
user_id: The SweatStack user ID this webhook relates to.
|
|
64
|
+
event_type: The type of event (e.g., "activity_created").
|
|
65
|
+
resource_id: The ID of the affected resource.
|
|
66
|
+
timestamp: When the event occurred.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
user_id: str
|
|
70
|
+
event_type: str
|
|
71
|
+
resource_id: str
|
|
72
|
+
timestamp: datetime
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Signature verification
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
TIMESTAMP_TOLERANCE = 300 # 5 minutes
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def verify_signature(
|
|
83
|
+
payload: bytes,
|
|
84
|
+
signature_header: str,
|
|
85
|
+
secret: str,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Verify webhook signature.
|
|
88
|
+
|
|
89
|
+
SweatStack uses HMAC-SHA256 signatures. The signature header format is:
|
|
90
|
+
t={timestamp},v1={signature}
|
|
91
|
+
|
|
92
|
+
The signed payload is: {timestamp}.{raw_json_body}
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
payload: The raw request body bytes.
|
|
96
|
+
signature_header: The X-Sweatstack-Signature header value.
|
|
97
|
+
secret: The webhook secret.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
WebhookVerificationError: If signature is invalid or timestamp too old.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
parts = dict(p.split("=", 1) for p in signature_header.split(","))
|
|
104
|
+
timestamp = int(parts["t"])
|
|
105
|
+
signature = parts["v1"]
|
|
106
|
+
except (KeyError, ValueError) as e:
|
|
107
|
+
raise WebhookVerificationError("Invalid signature header format") from e
|
|
108
|
+
|
|
109
|
+
# Check timestamp to prevent replay attacks
|
|
110
|
+
if abs(time.time() - timestamp) > TIMESTAMP_TOLERANCE:
|
|
111
|
+
raise WebhookVerificationError("Timestamp outside tolerance window")
|
|
112
|
+
|
|
113
|
+
# Compute expected signature
|
|
114
|
+
signed_payload = f"{timestamp}.".encode() + payload
|
|
115
|
+
expected = hmac.new(
|
|
116
|
+
secret.encode(),
|
|
117
|
+
signed_payload,
|
|
118
|
+
hashlib.sha256,
|
|
119
|
+
).hexdigest()
|
|
120
|
+
|
|
121
|
+
# Constant-time comparison to prevent timing attacks
|
|
122
|
+
if not hmac.compare_digest(expected, signature):
|
|
123
|
+
raise WebhookVerificationError("Invalid signature")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# FastAPI dependencies
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def _detect_webhook_context(request: Request) -> WebhookPayloadModel | None:
|
|
132
|
+
"""Detect if this is a webhook request and return verified payload.
|
|
133
|
+
|
|
134
|
+
This dependency is cached per-request by FastAPI. It's used by both
|
|
135
|
+
WebhookPayload (which requires it) and AuthenticatedUser (which uses it
|
|
136
|
+
to detect webhook context for token loading).
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
WebhookPayloadModel if this is a verified webhook request, None otherwise.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
WebhookVerificationError: If signature header is present but invalid.
|
|
143
|
+
"""
|
|
144
|
+
signature = request.headers.get("X-Sweatstack-Signature")
|
|
145
|
+
if not signature:
|
|
146
|
+
return None # Not a webhook request - fast path
|
|
147
|
+
|
|
148
|
+
config = get_config()
|
|
149
|
+
|
|
150
|
+
if not config.webhook_secret:
|
|
151
|
+
raise WebhookVerificationError(
|
|
152
|
+
"Webhook received but webhook_secret not configured. "
|
|
153
|
+
"Add webhook_secret to configure() to enable webhook handling."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
body = await request.body()
|
|
157
|
+
secret = (
|
|
158
|
+
config.webhook_secret.get_secret_value()
|
|
159
|
+
if hasattr(config.webhook_secret, "get_secret_value")
|
|
160
|
+
else config.webhook_secret
|
|
161
|
+
)
|
|
162
|
+
verify_signature(body, signature, secret)
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
data = json.loads(body)
|
|
166
|
+
return WebhookPayloadModel(
|
|
167
|
+
user_id=data["user_id"],
|
|
168
|
+
event_type=data["event_type"],
|
|
169
|
+
resource_id=data["resource_id"],
|
|
170
|
+
timestamp=datetime.fromisoformat(data["timestamp"]),
|
|
171
|
+
)
|
|
172
|
+
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
|
173
|
+
raise WebhookVerificationError(f"Invalid webhook payload: {e}") from e
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def _require_webhook_payload(
|
|
177
|
+
webhook_context: Annotated[WebhookPayloadModel | None, Depends(_detect_webhook_context)],
|
|
178
|
+
) -> WebhookPayloadModel:
|
|
179
|
+
"""Dependency that requires a verified webhook payload.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
HTTPException: If request is not a valid webhook.
|
|
183
|
+
"""
|
|
184
|
+
if webhook_context is None:
|
|
185
|
+
raise HTTPException(status_code=400, detail="Missing or invalid webhook signature")
|
|
186
|
+
return webhook_context
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# Public type alias for use in endpoint signatures
|
|
190
|
+
WebhookPayload = Annotated[WebhookPayloadModel, Depends(_require_webhook_payload)]
|
|
191
|
+
"""Dependency that returns a verified webhook payload.
|
|
192
|
+
|
|
193
|
+
The signature is verified before your handler runs. If verification fails,
|
|
194
|
+
a 400 response is returned automatically.
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
@app.post("/webhooks/sweatstack")
|
|
198
|
+
def handle_webhook(payload: WebhookPayload):
|
|
199
|
+
print(f"Event: {payload.event_type} for user {payload.user_id}")
|
|
200
|
+
return {"status": "received"}
|
|
201
|
+
"""
|
|
@@ -11,12 +11,15 @@ sweatstack/streamlit.py,sha256=wnabWhife9eMAdkECPjRKkzE82KZoi_H8YzucZl_m9s,19604
|
|
|
11
11
|
sweatstack/sweatshell.py,sha256=MYLNcWbOdceqKJ3S0Pe8dwHXEeYsGJNjQoYUXpMTftA,333
|
|
12
12
|
sweatstack/utils.py,sha256=AwHRdC1ziOZ5o9RBIB21Uxm-DoClVRAJSVvgsmSmvps,1801
|
|
13
13
|
sweatstack/Sweat Stack examples/Getting started.ipynb,sha256=k2hiSffWecoQ0VxjdpDcgFzBXDQiYEebhnAYlu8cgX8,6335204
|
|
14
|
-
sweatstack/fastapi/__init__.py,sha256=
|
|
15
|
-
sweatstack/fastapi/config.py,sha256=
|
|
16
|
-
sweatstack/fastapi/dependencies.py,sha256=
|
|
17
|
-
sweatstack/fastapi/
|
|
14
|
+
sweatstack/fastapi/__init__.py,sha256=BteLHro6KCVknIgNAp6OS5ZJRWmIXHS4iXOLOSyN2Mc,3216
|
|
15
|
+
sweatstack/fastapi/config.py,sha256=nNiZOeAXbipK4l2mxaPfaLwz2Ml-Eu7n-G46yGlvjA4,8764
|
|
16
|
+
sweatstack/fastapi/dependencies.py,sha256=8kfKqe-js1G7LLe2AR8xpChtoCKrDG3-F0VkZyXMlIo,12620
|
|
17
|
+
sweatstack/fastapi/models.py,sha256=vBImNywlKiw-10ZODRAHZSrWG3Xfla8uuXA0Y-p0Opc,5499
|
|
18
|
+
sweatstack/fastapi/routes.py,sha256=qNRSod_ZWziFptInplmwmrOe5ZwpJbL7kE5LHWJIxC8,13106
|
|
18
19
|
sweatstack/fastapi/session.py,sha256=BtRPCmIEaToJPwFyZ0fqWGlmnDHuWKy8nri9dJrPXaA,2717
|
|
19
|
-
sweatstack
|
|
20
|
-
sweatstack
|
|
21
|
-
sweatstack-0.
|
|
22
|
-
sweatstack-0.
|
|
20
|
+
sweatstack/fastapi/token_stores.py,sha256=-waq0CQLszIp-2uLwWgkMz8IbsmUC7xopvM2F12sbMo,8207
|
|
21
|
+
sweatstack/fastapi/webhooks.py,sha256=ShRQRkarJ3vuEkT1lkl1-_oQbPQTLNkgQ2E6Mm4UepU,6066
|
|
22
|
+
sweatstack-0.62.0.dist-info/METADATA,sha256=SqQNBCJZibHU6qQaQi1ij-OLHAWt082zfxPrXSFiJY4,994
|
|
23
|
+
sweatstack-0.62.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
+
sweatstack-0.62.0.dist-info/entry_points.txt,sha256=kCzOUQI3dqbTpEYqtgYDeiKFaqaA7BMlV6D24BMzCFU,208
|
|
25
|
+
sweatstack-0.62.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|