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.

Files changed (33) hide show
  1. svc_infra/api/fastapi/apf_payments/setup.py +0 -2
  2. svc_infra/api/fastapi/auth/add.py +0 -4
  3. svc_infra/api/fastapi/auth/routers/oauth_router.py +19 -4
  4. svc_infra/api/fastapi/cache/add.py +9 -5
  5. svc_infra/api/fastapi/db/nosql/mongo/add.py +33 -27
  6. svc_infra/api/fastapi/db/sql/add.py +8 -5
  7. svc_infra/api/fastapi/db/sql/crud_router.py +4 -4
  8. svc_infra/api/fastapi/docs/scoped.py +41 -6
  9. svc_infra/api/fastapi/setup.py +10 -12
  10. svc_infra/api/fastapi/versioned.py +101 -0
  11. svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
  12. svc_infra/db/sql/templates/setup/env_async.py.tmpl +25 -11
  13. svc_infra/db/sql/templates/setup/env_sync.py.tmpl +20 -5
  14. svc_infra/docs/acceptance-matrix.md +17 -0
  15. svc_infra/docs/adr/0012-generic-file-storage.md +498 -0
  16. svc_infra/docs/api.md +127 -0
  17. svc_infra/docs/storage.md +982 -0
  18. svc_infra/docs/versioned-integrations.md +146 -0
  19. svc_infra/security/models.py +27 -7
  20. svc_infra/security/oauth_models.py +59 -0
  21. svc_infra/storage/__init__.py +93 -0
  22. svc_infra/storage/add.py +250 -0
  23. svc_infra/storage/backends/__init__.py +11 -0
  24. svc_infra/storage/backends/local.py +331 -0
  25. svc_infra/storage/backends/memory.py +214 -0
  26. svc_infra/storage/backends/s3.py +329 -0
  27. svc_infra/storage/base.py +239 -0
  28. svc_infra/storage/easy.py +182 -0
  29. svc_infra/storage/settings.py +192 -0
  30. {svc_infra-0.1.640.dist-info → svc_infra-0.1.664.dist-info}/METADATA +8 -3
  31. {svc_infra-0.1.640.dist-info → svc_infra-0.1.664.dist-info}/RECORD +33 -19
  32. {svc_infra-0.1.640.dist-info → svc_infra-0.1.664.dist-info}/WHEEL +0 -0
  33. {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.640
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](ssrc/svc_infra/docs/ecurity.md) |
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](rsrc/svc_infra/docs/ate-limiting.md) |
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=bk_LLLXyqTA-lqf0v-mdpqLEMbXB17U1IQjG-qSBejQ,2677
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=-GOLZv-O53wfegmMQsi1wgex_IKJWBJJ4vEQNONQpnU,10014
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=vrqrIJSCnsWJLtA6OdE4xdWMIXQp35flf_EulXzeM2Y,26361
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=CX4TWsxrZ2EnN_TjVDOhQf382k8aUj2CXTG1GaZ5jU0,338
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=CGk0fWLgg4xzsRvBtob4nwYOCOv0_mH6gEi4cTwtWKU,4183
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=xGZnbGnP9PQtWvr5vuXA3_PXzTKldPM_cD3L0uztAvo,4768
57
- svc_infra/api/fastapi/db/sql/crud_router.py,sha256=GlIYwuIBb5vKJzPG2ikLvjHqoZt87zdWUGdhhCahH-o,11533
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=AuN35Op-9fUvHQCLOBtRjd5eWSpB5C9EAW_7-Boxmfo,7540
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=4ElbdfhzY14o366uKeWXWlpTCBuK1iMksApdiqrrWI0,10082
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=9ji7yIAh2Cy942B8f67LBZpuWjtcEclplD0bCvoQd68,9453
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=XZ5jcZXaYdBca5cRaY_m4Zuhz5IGekO2cqLuwiVSK8Y,13201
221
- svc_infra/db/sql/templates/setup/env_sync.py.tmpl,sha256=Q0GbhtHj45Kt3hep27V_pFqeZ53y08lamOZiWa7p0GU,14560
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=1J0722pdxNGVtjI5tAZA2wzzr1UiagjvPgzB8zo0Ks8,2383
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/api.md,sha256=AlPL9kBS6_dM0NrOteDQ9WqalSfKf_p9_zdy1CtGJdU,2384
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=US5jxgeZf7C_tWW3QZRj5RTuRZE_yS6RHZBEK0ea9tA,9535
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.640.dist-info/METADATA,sha256=X32-JTUY-XFNvnNmOFE9kAGy5mvlLmbHmM_wcsfWZJg,9543
348
- svc_infra-0.1.640.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
349
- svc_infra-0.1.640.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
350
- svc_infra-0.1.640.dist-info/RECORD,,
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,,