svc-infra 0.1.640__py3-none-any.whl → 0.1.664__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.
Potentially problematic release.
This version of svc-infra might be problematic. Click here for more details.
- svc_infra/api/fastapi/apf_payments/setup.py +0 -2
- svc_infra/api/fastapi/auth/add.py +0 -4
- svc_infra/api/fastapi/auth/routers/oauth_router.py +19 -4
- svc_infra/api/fastapi/cache/add.py +9 -5
- svc_infra/api/fastapi/db/nosql/mongo/add.py +33 -27
- svc_infra/api/fastapi/db/sql/add.py +8 -5
- svc_infra/api/fastapi/db/sql/crud_router.py +4 -4
- svc_infra/api/fastapi/docs/scoped.py +41 -6
- svc_infra/api/fastapi/setup.py +10 -12
- svc_infra/api/fastapi/versioned.py +101 -0
- svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
- svc_infra/db/sql/templates/setup/env_async.py.tmpl +25 -11
- svc_infra/db/sql/templates/setup/env_sync.py.tmpl +20 -5
- svc_infra/docs/acceptance-matrix.md +17 -0
- svc_infra/docs/adr/0012-generic-file-storage.md +498 -0
- svc_infra/docs/api.md +127 -0
- svc_infra/docs/storage.md +982 -0
- svc_infra/docs/versioned-integrations.md +146 -0
- svc_infra/security/models.py +27 -7
- svc_infra/security/oauth_models.py +59 -0
- svc_infra/storage/__init__.py +93 -0
- svc_infra/storage/add.py +250 -0
- svc_infra/storage/backends/__init__.py +11 -0
- svc_infra/storage/backends/local.py +331 -0
- svc_infra/storage/backends/memory.py +214 -0
- svc_infra/storage/backends/s3.py +329 -0
- svc_infra/storage/base.py +239 -0
- svc_infra/storage/easy.py +182 -0
- svc_infra/storage/settings.py +192 -0
- {svc_infra-0.1.640.dist-info → svc_infra-0.1.664.dist-info}/METADATA +8 -3
- {svc_infra-0.1.640.dist-info → svc_infra-0.1.664.dist-info}/RECORD +33 -19
- {svc_infra-0.1.640.dist-info → svc_infra-0.1.664.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.640.dist-info → svc_infra-0.1.664.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Easy storage backend builder with auto-detection.
|
|
3
|
+
|
|
4
|
+
Simplifies storage backend initialization with sensible defaults.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from .backends import LocalBackend, MemoryBackend, S3Backend
|
|
12
|
+
from .base import StorageBackend
|
|
13
|
+
from .settings import StorageSettings
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def easy_storage(
|
|
19
|
+
backend: Optional[str] = None,
|
|
20
|
+
**kwargs,
|
|
21
|
+
) -> StorageBackend:
|
|
22
|
+
"""
|
|
23
|
+
Create a storage backend with auto-detection or explicit selection.
|
|
24
|
+
|
|
25
|
+
This is the recommended way to initialize storage in most applications.
|
|
26
|
+
It handles environment-based configuration and provides sensible defaults.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
backend: Explicit backend type ("local", "s3", "gcs", "cloudinary", "memory")
|
|
30
|
+
If None, auto-detects from environment variables
|
|
31
|
+
**kwargs: Backend-specific configuration overrides
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Initialized storage backend
|
|
35
|
+
|
|
36
|
+
Auto-Detection Order:
|
|
37
|
+
1. Explicit backend parameter
|
|
38
|
+
2. STORAGE_BACKEND environment variable
|
|
39
|
+
3. Railway volume (RAILWAY_VOLUME_MOUNT_PATH) → LocalBackend
|
|
40
|
+
4. S3 credentials (AWS_ACCESS_KEY_ID or STORAGE_S3_BUCKET) → S3Backend
|
|
41
|
+
5. GCS credentials (GOOGLE_APPLICATION_CREDENTIALS) → GCSBackend
|
|
42
|
+
6. Cloudinary credentials (CLOUDINARY_URL) → CloudinaryBackend
|
|
43
|
+
7. Default: MemoryBackend (with warning)
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
>>> # Auto-detect backend from environment
|
|
47
|
+
>>> storage = easy_storage()
|
|
48
|
+
>>>
|
|
49
|
+
>>> # Explicit local backend
|
|
50
|
+
>>> storage = easy_storage(
|
|
51
|
+
... backend="local",
|
|
52
|
+
... base_path="/data/uploads"
|
|
53
|
+
... )
|
|
54
|
+
>>>
|
|
55
|
+
>>> # Explicit S3 backend
|
|
56
|
+
>>> storage = easy_storage(
|
|
57
|
+
... backend="s3",
|
|
58
|
+
... bucket="my-uploads",
|
|
59
|
+
... region="us-west-2"
|
|
60
|
+
... )
|
|
61
|
+
>>>
|
|
62
|
+
>>> # DigitalOcean Spaces
|
|
63
|
+
>>> storage = easy_storage(
|
|
64
|
+
... backend="s3",
|
|
65
|
+
... bucket="my-uploads",
|
|
66
|
+
... region="nyc3",
|
|
67
|
+
... endpoint="https://nyc3.digitaloceanspaces.com"
|
|
68
|
+
... )
|
|
69
|
+
|
|
70
|
+
Environment Variables:
|
|
71
|
+
See StorageSettings for full list of environment variables.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: If backend type is unsupported or configuration is invalid
|
|
75
|
+
ImportError: If required backend dependencies are not installed
|
|
76
|
+
|
|
77
|
+
Note:
|
|
78
|
+
For production deployments, it's recommended to set STORAGE_BACKEND
|
|
79
|
+
explicitly to avoid unexpected auto-detection behavior.
|
|
80
|
+
"""
|
|
81
|
+
# Load settings
|
|
82
|
+
settings = StorageSettings()
|
|
83
|
+
|
|
84
|
+
# Determine backend type
|
|
85
|
+
backend_type = backend or settings.detect_backend()
|
|
86
|
+
|
|
87
|
+
logger.info(f"Initializing {backend_type} storage backend")
|
|
88
|
+
|
|
89
|
+
# Create backend instance
|
|
90
|
+
if backend_type == "memory":
|
|
91
|
+
# Memory backend
|
|
92
|
+
if backend_type == settings.detect_backend() and not backend:
|
|
93
|
+
logger.warning(
|
|
94
|
+
"Using MemoryBackend (in-memory storage). "
|
|
95
|
+
"Data will be lost on restart. "
|
|
96
|
+
"Set STORAGE_BACKEND environment variable for production."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
max_size = kwargs.get("max_size", 100_000_000)
|
|
100
|
+
return MemoryBackend(max_size=max_size)
|
|
101
|
+
|
|
102
|
+
elif backend_type == "local":
|
|
103
|
+
# Local filesystem backend
|
|
104
|
+
base_path = kwargs.get("base_path") or settings.storage_base_path
|
|
105
|
+
|
|
106
|
+
# Check for Railway volume
|
|
107
|
+
railway_volume = os.getenv("RAILWAY_VOLUME_MOUNT_PATH")
|
|
108
|
+
if railway_volume and not kwargs.get("base_path"):
|
|
109
|
+
base_path = railway_volume
|
|
110
|
+
logger.info(f"Detected Railway volume at {base_path}")
|
|
111
|
+
|
|
112
|
+
base_url = kwargs.get("base_url") or settings.storage_base_url
|
|
113
|
+
signing_secret = kwargs.get("signing_secret") or settings.storage_signing_secret
|
|
114
|
+
|
|
115
|
+
return LocalBackend(
|
|
116
|
+
base_path=base_path,
|
|
117
|
+
base_url=base_url,
|
|
118
|
+
signing_secret=signing_secret,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
elif backend_type == "s3":
|
|
122
|
+
# S3-compatible backend
|
|
123
|
+
bucket = kwargs.get("bucket") or settings.storage_s3_bucket
|
|
124
|
+
if not bucket:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
"S3 bucket is required. "
|
|
127
|
+
"Set STORAGE_S3_BUCKET environment variable or pass bucket parameter."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
region = kwargs.get("region") or settings.storage_s3_region
|
|
131
|
+
endpoint = kwargs.get("endpoint") or settings.storage_s3_endpoint
|
|
132
|
+
|
|
133
|
+
# Get credentials with fallback
|
|
134
|
+
access_key = kwargs.get("access_key")
|
|
135
|
+
secret_key = kwargs.get("secret_key")
|
|
136
|
+
|
|
137
|
+
if not access_key or not secret_key:
|
|
138
|
+
access_key_from_settings, secret_key_from_settings = settings.get_s3_credentials()
|
|
139
|
+
access_key = access_key or access_key_from_settings
|
|
140
|
+
secret_key = secret_key or secret_key_from_settings
|
|
141
|
+
|
|
142
|
+
# Log provider detection
|
|
143
|
+
if endpoint:
|
|
144
|
+
if "digitalocean" in endpoint:
|
|
145
|
+
logger.info("Detected DigitalOcean Spaces")
|
|
146
|
+
elif "wasabi" in endpoint:
|
|
147
|
+
logger.info("Detected Wasabi")
|
|
148
|
+
elif "backblaze" in endpoint:
|
|
149
|
+
logger.info("Detected Backblaze B2")
|
|
150
|
+
else:
|
|
151
|
+
logger.info(f"Using custom S3 endpoint: {endpoint}")
|
|
152
|
+
else:
|
|
153
|
+
logger.info("Using AWS S3")
|
|
154
|
+
|
|
155
|
+
return S3Backend(
|
|
156
|
+
bucket=bucket,
|
|
157
|
+
region=region,
|
|
158
|
+
endpoint=endpoint,
|
|
159
|
+
access_key=access_key,
|
|
160
|
+
secret_key=secret_key,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
elif backend_type == "gcs":
|
|
164
|
+
# Google Cloud Storage backend
|
|
165
|
+
raise NotImplementedError(
|
|
166
|
+
"GCS backend not yet implemented. " "Use 'local' or 's3' backend for now."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
elif backend_type == "cloudinary":
|
|
170
|
+
# Cloudinary backend
|
|
171
|
+
raise NotImplementedError(
|
|
172
|
+
"Cloudinary backend not yet implemented. " "Use 'local' or 's3' backend for now."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"Unsupported storage backend: {backend_type}. "
|
|
178
|
+
f"Supported: local, s3, memory (gcs, cloudinary coming soon)"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
__all__ = ["easy_storage"]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Storage configuration and settings.
|
|
3
|
+
|
|
4
|
+
Handles environment-based configuration and auto-detection of storage backends.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Literal, Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
from pydantic_settings import BaseSettings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StorageSettings(BaseSettings):
|
|
15
|
+
"""
|
|
16
|
+
Storage system configuration.
|
|
17
|
+
|
|
18
|
+
Supports multiple backends with auto-detection from environment variables.
|
|
19
|
+
|
|
20
|
+
Environment Variables:
|
|
21
|
+
STORAGE_BACKEND: Explicit backend selection ("local", "s3", "gcs", "cloudinary", "memory")
|
|
22
|
+
|
|
23
|
+
Local Backend:
|
|
24
|
+
STORAGE_BASE_PATH: Base directory for file storage (default: /data/uploads)
|
|
25
|
+
STORAGE_BASE_URL: Base URL for file serving (default: http://localhost:8000/files)
|
|
26
|
+
STORAGE_SIGNING_SECRET: Secret key for URL signing (auto-generated if not set)
|
|
27
|
+
|
|
28
|
+
S3 Backend:
|
|
29
|
+
STORAGE_S3_BUCKET: S3 bucket name (required for S3)
|
|
30
|
+
STORAGE_S3_REGION: AWS region (default: us-east-1)
|
|
31
|
+
STORAGE_S3_ENDPOINT: Custom endpoint for S3-compatible services (optional)
|
|
32
|
+
STORAGE_S3_ACCESS_KEY: AWS access key (optional, uses AWS_ACCESS_KEY_ID if not set)
|
|
33
|
+
STORAGE_S3_SECRET_KEY: AWS secret key (optional, uses AWS_SECRET_ACCESS_KEY if not set)
|
|
34
|
+
|
|
35
|
+
GCS Backend:
|
|
36
|
+
STORAGE_GCS_BUCKET: GCS bucket name (required for GCS)
|
|
37
|
+
STORAGE_GCS_PROJECT: GCP project ID (optional)
|
|
38
|
+
STORAGE_GCS_CREDENTIALS_PATH: Path to service account JSON (optional)
|
|
39
|
+
|
|
40
|
+
Cloudinary Backend:
|
|
41
|
+
STORAGE_CLOUDINARY_CLOUD_NAME: Cloudinary cloud name (required)
|
|
42
|
+
STORAGE_CLOUDINARY_API_KEY: Cloudinary API key (required)
|
|
43
|
+
STORAGE_CLOUDINARY_API_SECRET: Cloudinary API secret (required)
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> # Auto-detect backend from environment
|
|
47
|
+
>>> settings = StorageSettings()
|
|
48
|
+
>>> backend = settings.detect_backend()
|
|
49
|
+
>>>
|
|
50
|
+
>>> # Explicit backend selection
|
|
51
|
+
>>> settings = StorageSettings(storage_backend="s3")
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
# Backend selection
|
|
55
|
+
storage_backend: Optional[Literal["local", "s3", "gcs", "cloudinary", "memory"]] = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
description="Storage backend type (auto-detected if not set)",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Local backend settings
|
|
61
|
+
storage_base_path: str = Field(
|
|
62
|
+
default="/data/uploads",
|
|
63
|
+
description="Base directory for local file storage",
|
|
64
|
+
)
|
|
65
|
+
storage_base_url: str = Field(
|
|
66
|
+
default="http://localhost:8000/files",
|
|
67
|
+
description="Base URL for serving files",
|
|
68
|
+
)
|
|
69
|
+
storage_signing_secret: Optional[str] = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="Secret key for URL signing (auto-generated if not set)",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# S3 backend settings
|
|
75
|
+
storage_s3_bucket: Optional[str] = Field(
|
|
76
|
+
default=None,
|
|
77
|
+
description="S3 bucket name",
|
|
78
|
+
)
|
|
79
|
+
storage_s3_region: str = Field(
|
|
80
|
+
default="us-east-1",
|
|
81
|
+
description="AWS region",
|
|
82
|
+
)
|
|
83
|
+
storage_s3_endpoint: Optional[str] = Field(
|
|
84
|
+
default=None,
|
|
85
|
+
description="Custom S3 endpoint (for DigitalOcean Spaces, Wasabi, etc.)",
|
|
86
|
+
)
|
|
87
|
+
storage_s3_access_key: Optional[str] = Field(
|
|
88
|
+
default=None,
|
|
89
|
+
description="S3 access key (falls back to AWS_ACCESS_KEY_ID)",
|
|
90
|
+
)
|
|
91
|
+
storage_s3_secret_key: Optional[str] = Field(
|
|
92
|
+
default=None,
|
|
93
|
+
description="S3 secret key (falls back to AWS_SECRET_ACCESS_KEY)",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# GCS backend settings
|
|
97
|
+
storage_gcs_bucket: Optional[str] = Field(
|
|
98
|
+
default=None,
|
|
99
|
+
description="Google Cloud Storage bucket name",
|
|
100
|
+
)
|
|
101
|
+
storage_gcs_project: Optional[str] = Field(
|
|
102
|
+
default=None,
|
|
103
|
+
description="GCP project ID",
|
|
104
|
+
)
|
|
105
|
+
storage_gcs_credentials_path: Optional[str] = Field(
|
|
106
|
+
default=None,
|
|
107
|
+
description="Path to GCP service account JSON",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Cloudinary backend settings
|
|
111
|
+
storage_cloudinary_cloud_name: Optional[str] = Field(
|
|
112
|
+
default=None,
|
|
113
|
+
description="Cloudinary cloud name",
|
|
114
|
+
)
|
|
115
|
+
storage_cloudinary_api_key: Optional[str] = Field(
|
|
116
|
+
default=None,
|
|
117
|
+
description="Cloudinary API key",
|
|
118
|
+
)
|
|
119
|
+
storage_cloudinary_api_secret: Optional[str] = Field(
|
|
120
|
+
default=None,
|
|
121
|
+
description="Cloudinary API secret",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
model_config = {
|
|
125
|
+
"env_file": ".env",
|
|
126
|
+
"case_sensitive": False,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def detect_backend(self) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Auto-detect storage backend from environment.
|
|
132
|
+
|
|
133
|
+
Detection order:
|
|
134
|
+
1. Explicit STORAGE_BACKEND setting
|
|
135
|
+
2. Railway volume (RAILWAY_VOLUME_MOUNT_PATH) → local
|
|
136
|
+
3. S3 credentials (AWS_ACCESS_KEY_ID or STORAGE_S3_BUCKET) → s3
|
|
137
|
+
4. GCS credentials (GOOGLE_APPLICATION_CREDENTIALS or STORAGE_GCS_BUCKET) → gcs
|
|
138
|
+
5. Cloudinary credentials (CLOUDINARY_URL or STORAGE_CLOUDINARY_CLOUD_NAME) → cloudinary
|
|
139
|
+
6. Default → memory (with warning)
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Backend type string
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
>>> settings = StorageSettings()
|
|
146
|
+
>>> backend_type = settings.detect_backend()
|
|
147
|
+
>>> print(f"Using {backend_type} backend")
|
|
148
|
+
"""
|
|
149
|
+
# Explicit setting takes precedence
|
|
150
|
+
if self.storage_backend:
|
|
151
|
+
return self.storage_backend
|
|
152
|
+
|
|
153
|
+
# Check for Railway volume
|
|
154
|
+
railway_volume = os.getenv("RAILWAY_VOLUME_MOUNT_PATH")
|
|
155
|
+
if railway_volume:
|
|
156
|
+
return "local"
|
|
157
|
+
|
|
158
|
+
# Check for S3
|
|
159
|
+
has_s3_key = os.getenv("AWS_ACCESS_KEY_ID") or self.storage_s3_access_key
|
|
160
|
+
if has_s3_key or self.storage_s3_bucket:
|
|
161
|
+
return "s3"
|
|
162
|
+
|
|
163
|
+
# Check for GCS
|
|
164
|
+
has_gcs_creds = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
|
165
|
+
if has_gcs_creds or self.storage_gcs_bucket:
|
|
166
|
+
return "gcs"
|
|
167
|
+
|
|
168
|
+
# Check for Cloudinary
|
|
169
|
+
has_cloudinary = os.getenv("CLOUDINARY_URL")
|
|
170
|
+
if has_cloudinary or self.storage_cloudinary_cloud_name:
|
|
171
|
+
return "cloudinary"
|
|
172
|
+
|
|
173
|
+
# Default to memory (for development/testing)
|
|
174
|
+
return "memory"
|
|
175
|
+
|
|
176
|
+
def get_s3_credentials(self) -> tuple[Optional[str], Optional[str]]:
|
|
177
|
+
"""
|
|
178
|
+
Get S3 credentials with fallback to AWS environment variables.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Tuple of (access_key, secret_key)
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
>>> settings = StorageSettings()
|
|
185
|
+
>>> access_key, secret_key = settings.get_s3_credentials()
|
|
186
|
+
"""
|
|
187
|
+
access_key = self.storage_s3_access_key or os.getenv("AWS_ACCESS_KEY_ID")
|
|
188
|
+
secret_key = self.storage_s3_secret_key or os.getenv("AWS_SECRET_ACCESS_KEY")
|
|
189
|
+
return access_key, secret_key
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
__all__ = ["StorageSettings"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: svc-infra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.664
|
|
4
4
|
Summary: Infrastructure for building and deploying prod-ready services
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: fastapi,sqlalchemy,alembic,auth,infra,async,pydantic
|
|
@@ -24,10 +24,13 @@ Provides-Extra: mysql
|
|
|
24
24
|
Provides-Extra: pg
|
|
25
25
|
Provides-Extra: pg2
|
|
26
26
|
Provides-Extra: redshift
|
|
27
|
+
Provides-Extra: s3
|
|
27
28
|
Provides-Extra: snowflake
|
|
28
29
|
Provides-Extra: sqlite
|
|
29
30
|
Requires-Dist: adyen (>=13.4.0,<14.0.0)
|
|
30
31
|
Requires-Dist: ai-infra (>=0.1.63,<0.2.0)
|
|
32
|
+
Requires-Dist: aioboto3 (>=13.0.0,<14.0.0) ; extra == "s3"
|
|
33
|
+
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
|
31
34
|
Requires-Dist: aiosqlite (>=0.20.0,<0.21.0) ; extra == "sqlite"
|
|
32
35
|
Requires-Dist: alembic (>=1.13.2,<2.0.0)
|
|
33
36
|
Requires-Dist: asyncpg (>=0.30.0,<0.31.0) ; extra == "pg"
|
|
@@ -89,11 +92,12 @@ svc-infra packages the shared building blocks we use to ship production FastAPI
|
|
|
89
92
|
| Environment | Feature switches and env vars | [Environment](src/svc_infra/docs/environment.md) |
|
|
90
93
|
| API | FastAPI bootstrap, middleware, docs wiring | [API guide](src/svc_infra/docs/api.md) |
|
|
91
94
|
| Auth | Sessions, OAuth/OIDC, MFA, SMTP delivery | [Auth](src/svc_infra/docs/auth.md) |
|
|
92
|
-
| Security | Password policy, lockout, signed cookies, headers | [Security](
|
|
95
|
+
| Security | Password policy, lockout, signed cookies, headers | [Security](src/svc_infra/docs/security.md) |
|
|
93
96
|
| Database | SQL + Mongo wiring, Alembic helpers, inbox/outbox patterns | [Database](src/svc_infra/docs/database.md) |
|
|
97
|
+
| Storage | File storage with S3, local, memory backends | [Storage](src/svc_infra/docs/storage.md) |
|
|
94
98
|
| Tenancy | Multi-tenant boundaries and helpers | [Tenancy](src/svc_infra/docs/tenancy.md) |
|
|
95
99
|
| Idempotency | Idempotent endpoints and middleware | [Idempotency](src/svc_infra/docs/idempotency.md) |
|
|
96
|
-
| Rate Limiting | Middleware, dependency limiter, headers | [Rate limiting](
|
|
100
|
+
| Rate Limiting | Middleware, dependency limiter, headers | [Rate limiting](src/svc_infra/docs/rate-limiting.md) |
|
|
97
101
|
| Cache | cashews decorators, namespace management, TTL helpers | [Cache](src/svc_infra/docs/cache.md) |
|
|
98
102
|
| Jobs | JobQueue, scheduler, CLI worker | [Jobs](src/svc_infra/docs/jobs.md) |
|
|
99
103
|
| Observability | Prometheus, Grafana, OpenTelemetry | [Observability](src/svc_infra/docs/observability.md) |
|
|
@@ -146,6 +150,7 @@ async def handle_webhook(payload = Depends(require_signature(lambda: ["current",
|
|
|
146
150
|
- **API** – toggle logging/observability and docs exposure with `ENABLE_LOGGING`, `LOG_LEVEL`, `LOG_FORMAT`, `ENABLE_OBS`, `METRICS_PATH`, `OBS_SKIP_PATHS`, and `CORS_ALLOW_ORIGINS`. 【F:src/svc_infra/api/fastapi/ease.py†L67-L111】【F:src/svc_infra/api/fastapi/setup.py†L47-L88】
|
|
147
151
|
- **Auth** – configure JWT secrets, SMTP, cookies, and policy using the `AUTH_…` settings family (e.g., `AUTH_JWT__SECRET`, `AUTH_SMTP_HOST`, `AUTH_SESSION_COOKIE_SECURE`). 【F:src/svc_infra/api/fastapi/auth/settings.py†L23-L91】
|
|
148
152
|
- **Database** – set connection URLs or components via `SQL_URL`/`SQL_URL_FILE`, `DB_DIALECT`, `DB_HOST`, `DB_USER`, `DB_PASSWORD`, plus Mongo knobs like `MONGO_URL`, `MONGO_DB`, and `MONGO_URL_FILE`. 【F:src/svc_infra/api/fastapi/db/sql/add.py†L55-L114】【F:src/svc_infra/db/sql/utils.py†L85-L206】【F:src/svc_infra/db/nosql/mongo/settings.py†L9-L13】【F:src/svc_infra/db/nosql/utils.py†L56-L113】
|
|
153
|
+
- **Storage** – choose backend with `STORAGE_BACKEND` (local, s3, memory) and configure with `STORAGE_S3_BUCKET`, `STORAGE_S3_REGION`, `STORAGE_BASE_PATH`, or auto-detect from `RAILWAY_VOLUME_MOUNT_PATH` / AWS credentials. 【F:src/svc_infra/storage/settings.py】【F:src/svc_infra/docs/storage.md】
|
|
149
154
|
- **Jobs** – choose the queue backend with `JOBS_DRIVER` and provide Redis via `REDIS_URL`; interval schedules can be declared with `JOBS_SCHEDULE_JSON`. 【F:src/svc_infra/jobs/easy.py†L11-L27】【F:src/svc_infra/docs/jobs.md†L11-L48】
|
|
150
155
|
- **Cache** – namespace keys and lifetimes through `CACHE_PREFIX`, `CACHE_VERSION`, and TTL overrides `CACHE_TTL_DEFAULT`, `CACHE_TTL_SHORT`, `CACHE_TTL_LONG`. 【F:src/svc_infra/cache/README.md†L20-L173】【F:src/svc_infra/cache/ttl.py†L26-L55】
|
|
151
156
|
- **Observability** – turn metrics on/off or adjust scrape paths with `ENABLE_OBS`, `METRICS_PATH`, `OBS_SKIP_PATHS`, and Prometheus/Grafana flags like `SVC_INFRA_DISABLE_PROMETHEUS`, `SVC_INFRA_RATE_WINDOW`, `SVC_INFRA_DASHBOARD_REFRESH`, `SVC_INFRA_DASHBOARD_RANGE`. 【F:src/svc_infra/api/fastapi/ease.py†L67-L111】【F:src/svc_infra/obs/metrics/asgi.py†L49-L206】【F:src/svc_infra/obs/cloud_dash.py†L85-L108】
|
|
@@ -17,10 +17,10 @@ svc_infra/api/fastapi/admin/__init__.py,sha256=AmuCHI5EQRCqwM40Gs1245z7MTcYZkg5x
|
|
|
17
17
|
svc_infra/api/fastapi/admin/add.py,sha256=9mCfvKvwBf_sr-SkpCbF1YMNvg-KRovrM_WV3hHs8m8,8649
|
|
18
18
|
svc_infra/api/fastapi/apf_payments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
svc_infra/api/fastapi/apf_payments/router.py,sha256=JIMCAZ1_Vie_EfvOS999iYSDH9ZBx1Nfizud-F_b5T0,36788
|
|
20
|
-
svc_infra/api/fastapi/apf_payments/setup.py,sha256=
|
|
20
|
+
svc_infra/api/fastapi/apf_payments/setup.py,sha256=qFxImRUiaBg6YZaCcKIIsr7JTyMr_UTo9dP0MbuqpPg,2553
|
|
21
21
|
svc_infra/api/fastapi/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
svc_infra/api/fastapi/auth/_cookies.py,sha256=U4heUmMnLezHx8U6ksuUEpSZ6sNMJcIO0gdLpmZ5FXw,1367
|
|
23
|
-
svc_infra/api/fastapi/auth/add.py,sha256
|
|
23
|
+
svc_infra/api/fastapi/auth/add.py,sha256=nQX1pdhJHMP7kn0D7LLZBrB5nRmkhd6R8b8C_yntL58,9846
|
|
24
24
|
svc_infra/api/fastapi/auth/gaurd.py,sha256=CXT5oMjxy_uQNyDRxQCxY2C4JFpMJqTTAz2j8pY5jOQ,8743
|
|
25
25
|
svc_infra/api/fastapi/auth/mfa/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
svc_infra/api/fastapi/auth/mfa/models.py,sha256=Te1cpGEBguUgul2NS50W_tHgybuu-TOIMPEBy53y9xc,1232
|
|
@@ -34,7 +34,7 @@ svc_infra/api/fastapi/auth/providers.py,sha256=q-ftVn04dqYxx0rnc28uFKieQqMpc_Jnf
|
|
|
34
34
|
svc_infra/api/fastapi/auth/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
svc_infra/api/fastapi/auth/routers/account.py,sha256=IzLCk9e6ST8oorI8GWTuU0bfPyGvONqAjxadoE6L6Fg,1614
|
|
36
36
|
svc_infra/api/fastapi/auth/routers/apikey_router.py,sha256=EoX-u1uZ0_r1dZ1hDO_PmG2sfSYZPa7uzj-4c520h8Y,5314
|
|
37
|
-
svc_infra/api/fastapi/auth/routers/oauth_router.py,sha256=
|
|
37
|
+
svc_infra/api/fastapi/auth/routers/oauth_router.py,sha256=Aiz3-PZNerRF7sbdcHXN9YvfOmvUlaqoqU5zbEIq9YU,27319
|
|
38
38
|
svc_infra/api/fastapi/auth/routers/session_router.py,sha256=CVCum8Tczdj6ailRm1oRxys_EcNrOgBMYJT25yV4u3c,2359
|
|
39
39
|
svc_infra/api/fastapi/auth/security.py,sha256=FU_XlaXHO1jocUbxeMOX3w2GWkR5ZXAcWbIUKTmOYvw,6482
|
|
40
40
|
svc_infra/api/fastapi/auth/sender.py,sha256=7a47HXuP0JLR4NlFQVb3TpoQHOPYybKPJ06C2fMJaec,1811
|
|
@@ -43,18 +43,18 @@ svc_infra/api/fastapi/auth/state.py,sha256=_FAGOG36EPFAl2VbB6fdsFX0W9JNIdWN9X1_O
|
|
|
43
43
|
svc_infra/api/fastapi/billing/router.py,sha256=vy9Zsf49f3c5Ni9xnBxUVmfgWhNBbkCWiZDLNxl8uOg,2132
|
|
44
44
|
svc_infra/api/fastapi/billing/setup.py,sha256=N4_yvNdvCt2l2OUmREUdUAClGwLUvCwqN1IHgY99ZMI,621
|
|
45
45
|
svc_infra/api/fastapi/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
-
svc_infra/api/fastapi/cache/add.py,sha256=
|
|
46
|
+
svc_infra/api/fastapi/cache/add.py,sha256=eIgv8zP1Zp5Bg0i1ZeDIJlP9wtnOePx7x0e0LaEtpCM,429
|
|
47
47
|
svc_infra/api/fastapi/db/__init__.py,sha256=ixNtgx8afhMDnLMEFis672r3t5yVC-Bt8VkTX15S95A,355
|
|
48
48
|
svc_infra/api/fastapi/db/http.py,sha256=tD1Tj0S1G1hYBFSHdu0KfPURs0QoEmOx4zNQdKkZhN8,2280
|
|
49
49
|
svc_infra/api/fastapi/db/nosql/__init__.py,sha256=Z23rgAMrWCotsUMZVr-5Z78m5w6DIzsstuwxSvqaKR8,175
|
|
50
50
|
svc_infra/api/fastapi/db/nosql/mongo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
-
svc_infra/api/fastapi/db/nosql/mongo/add.py,sha256=
|
|
51
|
+
svc_infra/api/fastapi/db/nosql/mongo/add.py,sha256=LkFxqyjEetfH64uaOv1XZJP2ArdPX3MEaiBMpGVXgQo,4468
|
|
52
52
|
svc_infra/api/fastapi/db/nosql/mongo/crud_router.py,sha256=9z_H-3SFCFurHv_NajtljnBfF6qH6j4GsJvj_jlfsvs,4223
|
|
53
53
|
svc_infra/api/fastapi/db/nosql/mongo/health.py,sha256=OM4_Pig3IRSrBhz2yHweIhGlSBrW1bubY_mDt5uyORA,507
|
|
54
54
|
svc_infra/api/fastapi/db/sql/README.md,sha256=uF_k4wbNSeWR6JF_8evWkeSFidBEXMXcIiYyR6sEv48,3082
|
|
55
55
|
svc_infra/api/fastapi/db/sql/__init__.py,sha256=R9P1Vy2Uqf9gFISxChMhO9tOGciIjEymVPhpXmsY3zc,255
|
|
56
|
-
svc_infra/api/fastapi/db/sql/add.py,sha256=
|
|
57
|
-
svc_infra/api/fastapi/db/sql/crud_router.py,sha256
|
|
56
|
+
svc_infra/api/fastapi/db/sql/add.py,sha256=9Pg45gwdvfGaKu_c1NxEC2lJYozz_uS6LyzPt5VtfxU,4833
|
|
57
|
+
svc_infra/api/fastapi/db/sql/crud_router.py,sha256=-K6UyP_wOGZu7tXnb1CVqbySNu7wZQ9KvWJfVvi7vPs,11573
|
|
58
58
|
svc_infra/api/fastapi/db/sql/health.py,sha256=ELLgQerooHHnvZRhGueSAc4QJsb3C4RojUGIu_U-hA4,792
|
|
59
59
|
svc_infra/api/fastapi/db/sql/session.py,sha256=5MpwHignl2OmlgWY-SOU7TQ7phJ58enOf2u1nBMZH5w,2567
|
|
60
60
|
svc_infra/api/fastapi/db/sql/users.py,sha256=68HGJgYVTEjKJm4-DPPC8-6nwXJoCukmgrYIIOHEUjs,5346
|
|
@@ -62,7 +62,7 @@ svc_infra/api/fastapi/dependencies/ratelimit.py,sha256=DiOC-MJfqTtSydM6RAaeAsiXX
|
|
|
62
62
|
svc_infra/api/fastapi/docs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
63
|
svc_infra/api/fastapi/docs/add.py,sha256=41asodVG_0Iokl9dpcGGmFO8z5ak3gNRmsmx6rNHnaM,5689
|
|
64
64
|
svc_infra/api/fastapi/docs/landing.py,sha256=GYFF-2gcYALZOlhJJ5JCrfOpv9j_q_6cfVjY0UGCLFM,4620
|
|
65
|
-
svc_infra/api/fastapi/docs/scoped.py,sha256=
|
|
65
|
+
svc_infra/api/fastapi/docs/scoped.py,sha256=3zFGPTrGQtwoNSa0gUXIKzv4ZL6SQStwcmyQ0qGFcxY,9003
|
|
66
66
|
svc_infra/api/fastapi/dual/__init__.py,sha256=scHLcNFkGbgX_R21V702xnAv6GMCkQ4n7yUtNDNgliM,552
|
|
67
67
|
svc_infra/api/fastapi/dual/dualize.py,sha256=OkWmuscJlBUlwt3S_P8CssXFsyf5c7F0fCA6bdSl_LU,3829
|
|
68
68
|
svc_infra/api/fastapi/dual/protected.py,sha256=6e3ojMKJmgrNOEHwfCNo5UNXaqsHbp10Wzt0cCJneJI,3189
|
|
@@ -107,9 +107,10 @@ svc_infra/api/fastapi/paths/prefix.py,sha256=I1xEt1lKDlOPn2Oa-y_1B-Q8JCwAPpu3_F_
|
|
|
107
107
|
svc_infra/api/fastapi/paths/user.py,sha256=z8xv_A3dPhG366ezJU1c839oHbU3tEg06rbx3HUUuL0,323
|
|
108
108
|
svc_infra/api/fastapi/routers/__init__.py,sha256=pbyrfVZzrMFgX11K47TvTS94yN0q-t-BdrVrGG9QyDk,9163
|
|
109
109
|
svc_infra/api/fastapi/routers/ping.py,sha256=71DGaklMAkCL8g_NHdQk9LXG7C_cVem1p8qtapGtnBk,576
|
|
110
|
-
svc_infra/api/fastapi/setup.py,sha256=
|
|
110
|
+
svc_infra/api/fastapi/setup.py,sha256=p2nTdmhsZw9kDTgtOPiQNdez8cRiv_Z4YHO2ztyzfu4,9989
|
|
111
111
|
svc_infra/api/fastapi/tenancy/add.py,sha256=47lhWoHjuqbgSckJBTNGz-Mo5R6W7SQ0hQDiOGC0HU0,509
|
|
112
112
|
svc_infra/api/fastapi/tenancy/context.py,sha256=nYnUKKP88Xy5mZCY7B7ob9PTDAnaKIhrj7pN6s-lBqg,3308
|
|
113
|
+
svc_infra/api/fastapi/versioned.py,sha256=O7FlSyHssvLy4Qo-pagu1zX7Y2m-40WaR1WaDe-VOHk,3238
|
|
113
114
|
svc_infra/app/README.md,sha256=MqnKTVjE9txaqlcl41wAv34hWhNfc_fJXCqCTsEWNdk,5070
|
|
114
115
|
svc_infra/app/__init__.py,sha256=za20ALo_kvJMgf7R_kF98DWk3h9acmvhYrCMhThcSmU,209
|
|
115
116
|
svc_infra/app/env.py,sha256=AOs4ksLZDXoAWrXeSnkYZ2yyS4UP68QUYfaWDcQ1QfA,4426
|
|
@@ -211,14 +212,14 @@ svc_infra/db/sql/service.py,sha256=Jtb89FQgXWNRrh_bPLCvNyM7Up0_rn_wx1_CGWfiqNs,2
|
|
|
211
212
|
svc_infra/db/sql/service_with_hooks.py,sha256=E2VHC-nqDp3AAop37WIM9nWyfTOsxzsa7W6jCFWxl9Q,721
|
|
212
213
|
svc_infra/db/sql/templates/__init__.py,sha256=atHi2TYS0oe8ZzUv2JIMSYdPduuyvu8RcOQASo7SqwM,48
|
|
213
214
|
svc_infra/db/sql/templates/models_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
214
|
-
svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl,sha256=
|
|
215
|
+
svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl,sha256=ROR13DZETuDOTrIXqb0XIdJeK-FMLkkfMor2lMMw-Kw,7573
|
|
215
216
|
svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl,sha256=EVLGBBtQIlYXz0MN4yqAgbYLw0BdYawpZSFlPiw879g,2872
|
|
216
217
|
svc_infra/db/sql/templates/models_schemas/entity/models.py.tmpl,sha256=cSUAfYgATv2SI0NQoHkn9Bzvt7RbOZ3_V8_p_VQsNMk,4349
|
|
217
218
|
svc_infra/db/sql/templates/models_schemas/entity/schemas.py.tmpl,sha256=xHTXk8u2uiqmQe8XpXjGAPg8pDpdr0rLtsSOC_fo9vo,1032
|
|
218
219
|
svc_infra/db/sql/templates/setup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
219
220
|
svc_infra/db/sql/templates/setup/alembic.ini.tmpl,sha256=7SHSOyUq9S7XqVdKpWLJW7gIWadv58t4AIooa306gDo,771
|
|
220
|
-
svc_infra/db/sql/templates/setup/env_async.py.tmpl,sha256=
|
|
221
|
-
svc_infra/db/sql/templates/setup/env_sync.py.tmpl,sha256=
|
|
221
|
+
svc_infra/db/sql/templates/setup/env_async.py.tmpl,sha256=yJyf4zw5fHaHOvurfFJb-XEqEArlnOksMNeGWgo-7dc,13908
|
|
222
|
+
svc_infra/db/sql/templates/setup/env_sync.py.tmpl,sha256=EiH8OS9uN-6IUT4p-XtmziqSwbq6-NjoojW9X6o1NSo,15357
|
|
222
223
|
svc_infra/db/sql/templates/setup/script.py.mako.tmpl,sha256=RiEMqF6dTN0S_lSRr90w5K2VtyK0_C81kBPZ02qQDZU,588
|
|
223
224
|
svc_infra/db/sql/tenant.py,sha256=erCB_TjZ62NXXIiOjsTOV_u3mM8kj0-R7gy4RFc-NrE,2779
|
|
224
225
|
svc_infra/db/sql/types.py,sha256=aDcYS-lEb3Aw9nlc8D67wyS5rmE9ZOkblxPjPFbMM_0,863
|
|
@@ -227,7 +228,7 @@ svc_infra/db/sql/uniq_hooks.py,sha256=6gCnO0_Y-rhB0p-VuY0mZ9m1u3haiLWI3Ns_iUTqF_
|
|
|
227
228
|
svc_infra/db/sql/utils.py,sha256=fm6wihJ90MUNqP8WBG4hScOndUoNiwkBFzSvOg2AzI4,33574
|
|
228
229
|
svc_infra/db/sql/versioning.py,sha256=okZu2ad5RAFXNLXJgGpcQvZ5bc6gPjRWzwiBT0rEJJw,400
|
|
229
230
|
svc_infra/db/utils.py,sha256=aTD49VJSEu319kIWJ1uijUoP51co4lNJ3S0_tvuyGio,802
|
|
230
|
-
svc_infra/docs/acceptance-matrix.md,sha256=
|
|
231
|
+
svc_infra/docs/acceptance-matrix.md,sha256=Z2YUb0BICmRQwb9aPwUInhVdhefkOUU69mdDY7KHXUw,3425
|
|
231
232
|
svc_infra/docs/acceptance.md,sha256=1e9Ym4YOwnKHLfCTjx9garKlSKEnxzIYunx4cqoWhZ8,2327
|
|
232
233
|
svc_infra/docs/admin.md,sha256=_KCrfNbpfEJhAcl1kuMQrSkRLXt2VIivRalvakgn4r8,12124
|
|
233
234
|
svc_infra/docs/adr/0002-background-jobs-and-scheduling.md,sha256=iscWFmDmMFcaON3W1FePZT9aHvlBLWCoB4QE7iA5BBI,2418
|
|
@@ -240,7 +241,8 @@ svc_infra/docs/adr/0008-billing-primitives.md,sha256=6em0RYeDAQScN7oSZfD_XslzrzI
|
|
|
240
241
|
svc_infra/docs/adr/0009-acceptance-harness.md,sha256=jDmoWn2uJTeK28YZo75YR1ym6NdgcmPOlMfupZlCCBs,2146
|
|
241
242
|
svc_infra/docs/adr/0010-timeouts-and-resource-limits.md,sha256=tpOTjncKJAjTsDN8jSUOTNqEKHfhVcfooxfW0nnbnro,2815
|
|
242
243
|
svc_infra/docs/adr/0011-admin-scope-and-impersonation.md,sha256=tHg0vXzyefr6qEQOes2OyQByZsK-ogDe1VQ1dQn2Ibc,4850
|
|
243
|
-
svc_infra/docs/
|
|
244
|
+
svc_infra/docs/adr/0012-generic-file-storage.md,sha256=jzDm9aLzzqbIn7sHKYb-tzGUEOjebbAOIrx_V96DUHM,15397
|
|
245
|
+
svc_infra/docs/api.md,sha256=PbTbp6LSNfKgxKAgDmirRaUgNsycM9ddeE2ZkAful8U,6076
|
|
244
246
|
svc_infra/docs/auth.md,sha256=PRl9G4UW78cT_7c4koVh5NDlheNAr02CpJT2YFbEXto,1333
|
|
245
247
|
svc_infra/docs/billing.md,sha256=MArKbKhzFwMLaOMABNDRtT_2D0zGgyFZ2r54o-99v68,7884
|
|
246
248
|
svc_infra/docs/cache.md,sha256=mwObz4F_9KwGO2ftcYSvWfieYekJs1dva3UzaIHFI64,2454
|
|
@@ -258,8 +260,10 @@ svc_infra/docs/ops.md,sha256=Tvqg5qvJ2RqLWbHAUlH3JPgK8yn-lNpyHb-lIpYUakg,1508
|
|
|
258
260
|
svc_infra/docs/rate-limiting.md,sha256=j1df-D9t5sJtv6HVaATfSxQrv5S8zLTo2N9S_3HHzkM,4156
|
|
259
261
|
svc_infra/docs/repo-review.md,sha256=REz2zT1LXURQUB9yZiBn9ICXHkpdpBSJCRTp-AKH190,8153
|
|
260
262
|
svc_infra/docs/security.md,sha256=DMOg58ZvFrmRyGDmuAYdJtF_q22DnhK4d3_q6ijdry0,7081
|
|
263
|
+
svc_infra/docs/storage.md,sha256=6_u2HAdiLJeJU0KcuXoZCo8oqP0YLyQF3zp3yiOhkL0,26218
|
|
261
264
|
svc_infra/docs/tenancy.md,sha256=k7mOvIQWZF1b3-CETyE8UsIPdhGjNNa9Q4j5AXAqQrQ,2023
|
|
262
265
|
svc_infra/docs/timeouts-and-resource-limits.md,sha256=M3AoKQlCmMvgXAGOoBWeVQ-0Fwm0t5Zz0dYUCf54Mbo,6559
|
|
266
|
+
svc_infra/docs/versioned-integrations.md,sha256=bpEAOexrBqHiajTCBn6mQMp83jr2HUGtdatk1zmpwcA,4681
|
|
263
267
|
svc_infra/docs/webhooks.md,sha256=b0F2vnkxOWdYWwCBAo9i39xdi11nbgEtl05JE3e0lrE,3671
|
|
264
268
|
svc_infra/dx/add.py,sha256=FAnLGP0BPm_q_VCEcpUwfj-b0mEse988chh9DHeS7GU,1474
|
|
265
269
|
svc_infra/dx/changelog.py,sha256=9SD29ZzKzbGTA6kHQXiPLtb7uueL1wrRiiLE2qMzz8o,1941
|
|
@@ -331,12 +335,22 @@ svc_infra/security/headers.py,sha256=CcdNC1j14oj6LS8-TMW2K-l7LIfXYlL12mw8R32b2R0
|
|
|
331
335
|
svc_infra/security/hibp.py,sha256=dTJXEdUNxLpxS5Eq8vUuWUdzDXh_b3_OjmvlR5jPHvQ,2894
|
|
332
336
|
svc_infra/security/jwt_rotation.py,sha256=VXPRQeSCoJEl2kQnIJZELWIS0-rGUZrxCdH4Fr47vig,1767
|
|
333
337
|
svc_infra/security/lockout.py,sha256=KdKN9FWejuzHRKS9jXzi_f3-lNF6QZyiEDBXCej0LSY,2804
|
|
334
|
-
svc_infra/security/models.py,sha256=
|
|
338
|
+
svc_infra/security/models.py,sha256=sX6bDpLdv1pUypIANab3eISL-bFPbqcPQTxodrQrYHU,10003
|
|
339
|
+
svc_infra/security/oauth_models.py,sha256=zrJnf69xB3mwEcV2wZIIBJkXcjpESv_1vBFexSTgLbM,2125
|
|
335
340
|
svc_infra/security/org_invites.py,sha256=TuXEstZp5GfRQflz8OR2q6m7GpSOonSxm0QU7ojkbH0,3876
|
|
336
341
|
svc_infra/security/passwords.py,sha256=zUiduHFOWYT7USzMkBntI3-LNEyVMn2A78CvaKpB7MY,2459
|
|
337
342
|
svc_infra/security/permissions.py,sha256=gQijNud6jh0yY2JIuZazAVE8i9zYhbwAR6tSjbncv5o,4556
|
|
338
343
|
svc_infra/security/session.py,sha256=JkClqoZ-Moo9yqHzCREXMVSpzyjbn2Zh6zCjtWO93Ik,2848
|
|
339
344
|
svc_infra/security/signed_cookies.py,sha256=2t61BgjsBaTzU46bt7IUJo7lwDRE9_eS4vmAQXJ8mlY,2219
|
|
345
|
+
svc_infra/storage/__init__.py,sha256=M_djbln1nv1YhOANHu_XZDi_fZ37hTnp2qMXiHYOlJo,2828
|
|
346
|
+
svc_infra/storage/add.py,sha256=tGAvuUEgb5HbQ-8sGCTVLeykZJtIfj1R9hRHLVYxjeo,8214
|
|
347
|
+
svc_infra/storage/backends/__init__.py,sha256=zlbPU7TbDKg-VhWSZaRA-qTHfD17bx-ZQ445kDqlB-o,205
|
|
348
|
+
svc_infra/storage/backends/local.py,sha256=r6dPrq0VcMbQBdY1U9m-FrXOteD8iIipnLx-JH9Q9xc,10655
|
|
349
|
+
svc_infra/storage/backends/memory.py,sha256=AQg7GNkpZcL9rdPtCu7tPMB3XKlIfho65YDw_lC_gcA,6467
|
|
350
|
+
svc_infra/storage/backends/s3.py,sha256=hUpzpNKFSkLBC5vaGz-Sm_t3F3sb70jf7W7xViBnkic,11284
|
|
351
|
+
svc_infra/storage/base.py,sha256=l6BmP9TVAwCZbjHtXaaLSnCSvYzTAx0Du2zadnQRmmg,6225
|
|
352
|
+
svc_infra/storage/easy.py,sha256=DDUUqFKN2gJBtuU4ftF6HMqePMDjkJIOPV436iceANA,6138
|
|
353
|
+
svc_infra/storage/settings.py,sha256=FmHEPGm4QSRooekQF6zgaGEeunmmXJO08mIBaNXEksk,6620
|
|
340
354
|
svc_infra/utils.py,sha256=VX1yjTx61-YvAymyRhGy18DhybiVdPddiYD_FlKTbJU,952
|
|
341
355
|
svc_infra/webhooks/__init__.py,sha256=fvPhbFoS6whoT67DWp43pL3m1o-et104vwqxunCUAPA,398
|
|
342
356
|
svc_infra/webhooks/add.py,sha256=u9Spfwg0ztQmXg7uXP1sZ9-_qwnagnW4UnV9HvQtPwc,12191
|
|
@@ -344,7 +358,7 @@ svc_infra/webhooks/fastapi.py,sha256=BCNvGNxukf6dC2a4i-6en-PrjBGV19YvCWOot5lXWsA
|
|
|
344
358
|
svc_infra/webhooks/router.py,sha256=6JvAVPMEth_xxHX-IsIOcyMgHX7g1H0OVxVXKLuMp9w,1596
|
|
345
359
|
svc_infra/webhooks/service.py,sha256=hh-rw0otc00vipZ998XaV5mHsk0IDGYqon0FnhaGr60,2229
|
|
346
360
|
svc_infra/webhooks/signing.py,sha256=NCwdZzmravUe7HVIK_uXK0qqf12FG-_MVsgPvOw6lsM,784
|
|
347
|
-
svc_infra-0.1.
|
|
348
|
-
svc_infra-0.1.
|
|
349
|
-
svc_infra-0.1.
|
|
350
|
-
svc_infra-0.1.
|
|
361
|
+
svc_infra-0.1.664.dist-info/METADATA,sha256=28hGY3d7_RmbdcyvA4O0L4vnPhmIacLgJodyh1bU2kQ,10070
|
|
362
|
+
svc_infra-0.1.664.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
363
|
+
svc_infra-0.1.664.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
364
|
+
svc_infra-0.1.664.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|