plexspaces 0.1.1__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.
- plexspaces/__init__.py +80 -0
- plexspaces/actor_id.py +114 -0
- plexspaces/decorators.py +691 -0
- plexspaces/generated/__init__.py +0 -0
- plexspaces/generated/buf/__init__.py +0 -0
- plexspaces/generated/buf/validate/__init__.py +3799 -0
- plexspaces/generated/google/__init__.py +0 -0
- plexspaces/generated/google/api/__init__.py +379 -0
- plexspaces/generated/grpc/__init__.py +0 -0
- plexspaces/generated/grpc/gateway/__init__.py +0 -0
- plexspaces/generated/grpc/gateway/protoc_gen_openapiv2/__init__.py +0 -0
- plexspaces/generated/grpc/gateway/protoc_gen_openapiv2/options/__init__.py +1022 -0
- plexspaces/generated/plexspaces/__init__.py +0 -0
- plexspaces/generated/plexspaces/actor/__init__.py +0 -0
- plexspaces/generated/plexspaces/actor/v1/__init__.py +3538 -0
- plexspaces/generated/plexspaces/application/__init__.py +0 -0
- plexspaces/generated/plexspaces/application/v1/__init__.py +827 -0
- plexspaces/generated/plexspaces/behaviors/__init__.py +0 -0
- plexspaces/generated/plexspaces/behaviors/v1/__init__.py +63 -0
- plexspaces/generated/plexspaces/channel/__init__.py +0 -0
- plexspaces/generated/plexspaces/channel/v1/__init__.py +915 -0
- plexspaces/generated/plexspaces/circuitbreaker/__init__.py +0 -0
- plexspaces/generated/plexspaces/circuitbreaker/prv/__init__.py +398 -0
- plexspaces/generated/plexspaces/common/__init__.py +0 -0
- plexspaces/generated/plexspaces/common/v1/__init__.py +691 -0
- plexspaces/generated/plexspaces/config/__init__.py +0 -0
- plexspaces/generated/plexspaces/config/v1/__init__.py +109 -0
- plexspaces/generated/plexspaces/dashboard/__init__.py +0 -0
- plexspaces/generated/plexspaces/dashboard/v1/__init__.py +647 -0
- plexspaces/generated/plexspaces/facets/__init__.py +0 -0
- plexspaces/generated/plexspaces/facets/v1/__init__.py +333 -0
- plexspaces/generated/plexspaces/firecracker/__init__.py +0 -0
- plexspaces/generated/plexspaces/firecracker/v1/__init__.py +890 -0
- plexspaces/generated/plexspaces/grpc/__init__.py +0 -0
- plexspaces/generated/plexspaces/grpc/v1/__init__.py +679 -0
- plexspaces/generated/plexspaces/journaling/__init__.py +0 -0
- plexspaces/generated/plexspaces/journaling/v1/__init__.py +1006 -0
- plexspaces/generated/plexspaces/keyvalue/__init__.py +0 -0
- plexspaces/generated/plexspaces/keyvalue/v1/__init__.py +1007 -0
- plexspaces/generated/plexspaces/locks/__init__.py +0 -0
- plexspaces/generated/plexspaces/locks/prv/__init__.py +122 -0
- plexspaces/generated/plexspaces/mailbox/__init__.py +0 -0
- plexspaces/generated/plexspaces/mailbox/v1/__init__.py +246 -0
- plexspaces/generated/plexspaces/metrics/__init__.py +0 -0
- plexspaces/generated/plexspaces/metrics/v1/__init__.py +878 -0
- plexspaces/generated/plexspaces/node/__init__.py +0 -0
- plexspaces/generated/plexspaces/node/v1/__init__.py +2564 -0
- plexspaces/generated/plexspaces/object_registry/__init__.py +0 -0
- plexspaces/generated/plexspaces/object_registry/v1/__init__.py +759 -0
- plexspaces/generated/plexspaces/persistence/__init__.py +0 -0
- plexspaces/generated/plexspaces/persistence/prv/__init__.py +539 -0
- plexspaces/generated/plexspaces/pool/__init__.py +0 -0
- plexspaces/generated/plexspaces/pool/v1/__init__.py +791 -0
- plexspaces/generated/plexspaces/processgroups/__init__.py +0 -0
- plexspaces/generated/plexspaces/processgroups/v1/__init__.py +622 -0
- plexspaces/generated/plexspaces/scheduling/__init__.py +0 -0
- plexspaces/generated/plexspaces/scheduling/v1/__init__.py +364 -0
- plexspaces/generated/plexspaces/security/__init__.py +0 -0
- plexspaces/generated/plexspaces/security/v1/__init__.py +254 -0
- plexspaces/generated/plexspaces/services/__init__.py +0 -0
- plexspaces/generated/plexspaces/services/prv/__init__.py +112 -0
- plexspaces/generated/plexspaces/storage/__init__.py +0 -0
- plexspaces/generated/plexspaces/storage/v1/__init__.py +747 -0
- plexspaces/generated/plexspaces/supervision/__init__.py +0 -0
- plexspaces/generated/plexspaces/supervision/v1/__init__.py +536 -0
- plexspaces/generated/plexspaces/system/__init__.py +0 -0
- plexspaces/generated/plexspaces/system/v1/__init__.py +1602 -0
- plexspaces/generated/plexspaces/timer/__init__.py +0 -0
- plexspaces/generated/plexspaces/timer/v1/__init__.py +402 -0
- plexspaces/generated/plexspaces/tuplespace/__init__.py +0 -0
- plexspaces/generated/plexspaces/tuplespace/v1/__init__.py +869 -0
- plexspaces/generated/plexspaces/wasm/__init__.py +0 -0
- plexspaces/generated/plexspaces/wasm/v1/__init__.py +920 -0
- plexspaces/generated/plexspaces/workflow/__init__.py +0 -0
- plexspaces/generated/plexspaces/workflow/v1/__init__.py +870 -0
- plexspaces/host.py +2093 -0
- plexspaces/leader_worker.py +148 -0
- plexspaces/proto_wire.py +1357 -0
- plexspaces/runtime.py +284 -0
- plexspaces/workflow.py +105 -0
- plexspaces-0.1.1.dist-info/METADATA +505 -0
- plexspaces-0.1.1.dist-info/RECORD +87 -0
- plexspaces-0.1.1.dist-info/WHEEL +5 -0
- plexspaces-0.1.1.dist-info/entry_points.txt +2 -0
- plexspaces-0.1.1.dist-info/top_level.txt +2 -0
- plexspaces_cli/__init__.py +8 -0
- plexspaces_cli/build.py +374 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
2
|
+
# sources: plexspaces/v1/storage/blob.proto, plexspaces/v1/storage/storage_config.proto
|
|
3
|
+
# plugin: python-betterproto
|
|
4
|
+
# This file has been @generated
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import (
|
|
8
|
+
datetime,
|
|
9
|
+
timedelta,
|
|
10
|
+
)
|
|
11
|
+
from typing import (
|
|
12
|
+
TYPE_CHECKING,
|
|
13
|
+
AsyncIterator,
|
|
14
|
+
Dict,
|
|
15
|
+
List,
|
|
16
|
+
Optional,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
import betterproto
|
|
20
|
+
import grpclib
|
|
21
|
+
from betterproto.grpc.grpclib_server import ServiceBase
|
|
22
|
+
|
|
23
|
+
from ...common import v1 as __common_v1__
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
import grpclib.server
|
|
27
|
+
from betterproto.grpc.grpclib_client import MetadataLike
|
|
28
|
+
from grpclib.metadata import Deadline
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class StorageProvider(betterproto.Enum):
|
|
32
|
+
"""
|
|
33
|
+
Storage provider type
|
|
34
|
+
|
|
35
|
+
## Purpose
|
|
36
|
+
Defines the storage provider type for database backends.
|
|
37
|
+
Follows Kubernetes-style provider pattern for consistency.
|
|
38
|
+
Note: Message queue providers (Kafka, NATS) are defined in channel.proto.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
UNSPECIFIED = 0
|
|
42
|
+
POSTGRES = 1
|
|
43
|
+
"""Relational database providers"""
|
|
44
|
+
|
|
45
|
+
SQLITE = 2
|
|
46
|
+
REDIS = 3
|
|
47
|
+
"""NoSQL providers"""
|
|
48
|
+
|
|
49
|
+
DYNAMODB = 4
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(eq=False, repr=False)
|
|
53
|
+
class BlobMetadata(betterproto.Message):
|
|
54
|
+
"""BlobMetadata represents metadata about a stored blob"""
|
|
55
|
+
|
|
56
|
+
blob_id: str = betterproto.string_field(1)
|
|
57
|
+
"""Unique blob identifier (ULID or SHA256-based)"""
|
|
58
|
+
|
|
59
|
+
tenant_id: str = betterproto.string_field(2)
|
|
60
|
+
"""Tenant identifier for multi-tenancy isolation"""
|
|
61
|
+
|
|
62
|
+
namespace: str = betterproto.string_field(3)
|
|
63
|
+
"""Namespace within tenant (e.g., "production", "staging", "dev")"""
|
|
64
|
+
|
|
65
|
+
name: str = betterproto.string_field(4)
|
|
66
|
+
"""Display name for the blob"""
|
|
67
|
+
|
|
68
|
+
sha256: str = betterproto.string_field(5)
|
|
69
|
+
"""SHA256 hash of blob contents (for deduplication)"""
|
|
70
|
+
|
|
71
|
+
content_type: str = betterproto.string_field(6)
|
|
72
|
+
"""Content type (MIME type)"""
|
|
73
|
+
|
|
74
|
+
content_length: int = betterproto.int64_field(7)
|
|
75
|
+
"""Content length in bytes"""
|
|
76
|
+
|
|
77
|
+
etag: str = betterproto.string_field(8)
|
|
78
|
+
"""ETag from object store"""
|
|
79
|
+
|
|
80
|
+
blob_group: str = betterproto.string_field(9)
|
|
81
|
+
"""Blob group/category (optional grouping)"""
|
|
82
|
+
|
|
83
|
+
kind: str = betterproto.string_field(10)
|
|
84
|
+
"""Blob kind/type (e.g., "LOGS", "ARTIFACTS", "CACHE", "USER_UPLOADED")"""
|
|
85
|
+
|
|
86
|
+
metadata: Dict[str, str] = betterproto.map_field(
|
|
87
|
+
11, betterproto.TYPE_STRING, betterproto.TYPE_STRING
|
|
88
|
+
)
|
|
89
|
+
"""Custom metadata (key-value pairs)"""
|
|
90
|
+
|
|
91
|
+
tags: Dict[str, str] = betterproto.map_field(
|
|
92
|
+
12, betterproto.TYPE_STRING, betterproto.TYPE_STRING
|
|
93
|
+
)
|
|
94
|
+
"""Custom tags (key-value pairs)"""
|
|
95
|
+
|
|
96
|
+
expires_at: datetime = betterproto.message_field(13)
|
|
97
|
+
"""Expiration time (when blob should be deleted)"""
|
|
98
|
+
|
|
99
|
+
created_at: datetime = betterproto.message_field(14)
|
|
100
|
+
"""Creation time"""
|
|
101
|
+
|
|
102
|
+
updated_at: datetime = betterproto.message_field(15)
|
|
103
|
+
"""Last update time"""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(eq=False, repr=False)
|
|
107
|
+
class BlobConfig(betterproto.Message):
|
|
108
|
+
"""
|
|
109
|
+
Blob storage configuration
|
|
110
|
+
|
|
111
|
+
## Purpose
|
|
112
|
+
Configuration for S3-compatible blob storage backend (S3, embedded, GCP, Azure).
|
|
113
|
+
Used for storing large binary objects with metadata management.
|
|
114
|
+
|
|
115
|
+
## Usage
|
|
116
|
+
This config is typically included in RuntimeConfig for node-level configuration,
|
|
117
|
+
but can also be used standalone for service-specific configuration.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
backend: str = betterproto.string_field(1)
|
|
121
|
+
"""
|
|
122
|
+
Backend type (s3, embedded, gcp, azure)
|
|
123
|
+
"embedded" uses a local S3-compatible subprocess (default for local dev/test)
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
bucket: str = betterproto.string_field(2)
|
|
127
|
+
"""Bucket name"""
|
|
128
|
+
|
|
129
|
+
endpoint: str = betterproto.string_field(3)
|
|
130
|
+
"""Endpoint URL (for SeaweedFS or custom S3-compatible)"""
|
|
131
|
+
|
|
132
|
+
region: str = betterproto.string_field(4)
|
|
133
|
+
"""Region (for S3/GCP/Azure)"""
|
|
134
|
+
|
|
135
|
+
access_key_id: str = betterproto.string_field(5)
|
|
136
|
+
"""Access key ID"""
|
|
137
|
+
|
|
138
|
+
secret_access_key: str = betterproto.string_field(6)
|
|
139
|
+
"""Secret access key (should be from env var, not config file)"""
|
|
140
|
+
|
|
141
|
+
use_ssl: bool = betterproto.bool_field(7)
|
|
142
|
+
"""Use SSL/TLS"""
|
|
143
|
+
|
|
144
|
+
prefix: str = betterproto.string_field(8)
|
|
145
|
+
"""Path prefix for all blobs (default: /plexspaces)"""
|
|
146
|
+
|
|
147
|
+
gcp_service_account_json: str = betterproto.string_field(9)
|
|
148
|
+
"""GCP-specific: Service account JSON (base64 encoded)"""
|
|
149
|
+
|
|
150
|
+
azure_account_name: str = betterproto.string_field(10)
|
|
151
|
+
"""Azure-specific: Account name"""
|
|
152
|
+
|
|
153
|
+
azure_account_key: str = betterproto.string_field(11)
|
|
154
|
+
"""Azure-specific: Account key"""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(eq=False, repr=False)
|
|
158
|
+
class UploadBlobRequest(betterproto.Message):
|
|
159
|
+
"""Upload blob request (HTTP multipart/form-data or raw body)"""
|
|
160
|
+
|
|
161
|
+
tenant_id: str = betterproto.string_field(1)
|
|
162
|
+
"""Tenant ID"""
|
|
163
|
+
|
|
164
|
+
namespace: str = betterproto.string_field(2)
|
|
165
|
+
"""Namespace"""
|
|
166
|
+
|
|
167
|
+
name: str = betterproto.string_field(3)
|
|
168
|
+
"""Blob name"""
|
|
169
|
+
|
|
170
|
+
content_type: str = betterproto.string_field(4)
|
|
171
|
+
"""Content type (optional, will be detected if not provided)"""
|
|
172
|
+
|
|
173
|
+
blob_group: str = betterproto.string_field(5)
|
|
174
|
+
"""Blob group (optional)"""
|
|
175
|
+
|
|
176
|
+
kind: str = betterproto.string_field(6)
|
|
177
|
+
"""Blob kind (optional)"""
|
|
178
|
+
|
|
179
|
+
metadata: Dict[str, str] = betterproto.map_field(
|
|
180
|
+
7, betterproto.TYPE_STRING, betterproto.TYPE_STRING
|
|
181
|
+
)
|
|
182
|
+
"""Custom metadata"""
|
|
183
|
+
|
|
184
|
+
tags: Dict[str, str] = betterproto.map_field(
|
|
185
|
+
8, betterproto.TYPE_STRING, betterproto.TYPE_STRING
|
|
186
|
+
)
|
|
187
|
+
"""Custom tags"""
|
|
188
|
+
|
|
189
|
+
expires_after: timedelta = betterproto.message_field(9)
|
|
190
|
+
"""Expiration duration (optional)"""
|
|
191
|
+
|
|
192
|
+
data: bytes = betterproto.bytes_field(10)
|
|
193
|
+
"""Blob data (for gRPC streaming or HTTP body)"""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass(eq=False, repr=False)
|
|
197
|
+
class UploadBlobResponse(betterproto.Message):
|
|
198
|
+
"""Upload blob response"""
|
|
199
|
+
|
|
200
|
+
metadata: "BlobMetadata" = betterproto.message_field(1)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@dataclass(eq=False, repr=False)
|
|
204
|
+
class DownloadBlobRequest(betterproto.Message):
|
|
205
|
+
"""Download blob request"""
|
|
206
|
+
|
|
207
|
+
blob_id: str = betterproto.string_field(1)
|
|
208
|
+
"""Blob ID"""
|
|
209
|
+
|
|
210
|
+
tenant_id: str = betterproto.string_field(2)
|
|
211
|
+
"""Tenant ID (for authorization)"""
|
|
212
|
+
|
|
213
|
+
namespace: str = betterproto.string_field(3)
|
|
214
|
+
"""Namespace (for authorization)"""
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass(eq=False, repr=False)
|
|
218
|
+
class DownloadBlobResponse(betterproto.Message):
|
|
219
|
+
"""Download blob response (streaming)"""
|
|
220
|
+
|
|
221
|
+
metadata: "BlobMetadata" = betterproto.message_field(1)
|
|
222
|
+
"""Blob metadata (sent first)"""
|
|
223
|
+
|
|
224
|
+
data: bytes = betterproto.bytes_field(2)
|
|
225
|
+
"""Blob data chunks (streamed)"""
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass(eq=False, repr=False)
|
|
229
|
+
class GetBlobMetadataRequest(betterproto.Message):
|
|
230
|
+
"""Get blob metadata request"""
|
|
231
|
+
|
|
232
|
+
blob_id: str = betterproto.string_field(1)
|
|
233
|
+
tenant_id: str = betterproto.string_field(2)
|
|
234
|
+
namespace: str = betterproto.string_field(3)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@dataclass(eq=False, repr=False)
|
|
238
|
+
class GetBlobMetadataResponse(betterproto.Message):
|
|
239
|
+
"""Get blob metadata response"""
|
|
240
|
+
|
|
241
|
+
metadata: "BlobMetadata" = betterproto.message_field(1)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@dataclass(eq=False, repr=False)
|
|
245
|
+
class ListBlobsRequest(betterproto.Message):
|
|
246
|
+
"""List blobs request"""
|
|
247
|
+
|
|
248
|
+
tenant_id: str = betterproto.string_field(1)
|
|
249
|
+
namespace: str = betterproto.string_field(2)
|
|
250
|
+
name_prefix: str = betterproto.string_field(3)
|
|
251
|
+
"""Filter by name (prefix match)"""
|
|
252
|
+
|
|
253
|
+
blob_group: str = betterproto.string_field(4)
|
|
254
|
+
"""Filter by blob_group"""
|
|
255
|
+
|
|
256
|
+
kind: str = betterproto.string_field(5)
|
|
257
|
+
"""Filter by kind"""
|
|
258
|
+
|
|
259
|
+
sha256: str = betterproto.string_field(6)
|
|
260
|
+
"""Filter by SHA256"""
|
|
261
|
+
|
|
262
|
+
page: "__common_v1__.PageRequest" = betterproto.message_field(7)
|
|
263
|
+
"""Pagination"""
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@dataclass(eq=False, repr=False)
|
|
267
|
+
class ListBlobsResponse(betterproto.Message):
|
|
268
|
+
"""List blobs response"""
|
|
269
|
+
|
|
270
|
+
blobs: List["BlobMetadata"] = betterproto.message_field(1)
|
|
271
|
+
page: "__common_v1__.PageResponse" = betterproto.message_field(2)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@dataclass(eq=False, repr=False)
|
|
275
|
+
class DeleteBlobRequest(betterproto.Message):
|
|
276
|
+
"""Delete blob request"""
|
|
277
|
+
|
|
278
|
+
blob_id: str = betterproto.string_field(1)
|
|
279
|
+
tenant_id: str = betterproto.string_field(2)
|
|
280
|
+
namespace: str = betterproto.string_field(3)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@dataclass(eq=False, repr=False)
|
|
284
|
+
class DeleteBlobResponse(betterproto.Message):
|
|
285
|
+
"""Delete blob response"""
|
|
286
|
+
|
|
287
|
+
pass
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@dataclass(eq=False, repr=False)
|
|
291
|
+
class GeneratePresignedUrlRequest(betterproto.Message):
|
|
292
|
+
"""Generate presigned URL request"""
|
|
293
|
+
|
|
294
|
+
blob_id: str = betterproto.string_field(1)
|
|
295
|
+
operation: str = betterproto.string_field(2)
|
|
296
|
+
"""Operation: "GET" or "PUT"""
|
|
297
|
+
|
|
298
|
+
expires_after: timedelta = betterproto.message_field(3)
|
|
299
|
+
"""Expiration duration (default: 1 hour)"""
|
|
300
|
+
|
|
301
|
+
tenant_id: str = betterproto.string_field(4)
|
|
302
|
+
namespace: str = betterproto.string_field(5)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass(eq=False, repr=False)
|
|
306
|
+
class GeneratePresignedUrlResponse(betterproto.Message):
|
|
307
|
+
"""Generate presigned URL response"""
|
|
308
|
+
|
|
309
|
+
url: str = betterproto.string_field(1)
|
|
310
|
+
expires_at: datetime = betterproto.message_field(2)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@dataclass(eq=False, repr=False)
|
|
314
|
+
class SharedDbConfig(betterproto.Message):
|
|
315
|
+
"""
|
|
316
|
+
Shared database configuration
|
|
317
|
+
|
|
318
|
+
## Purpose
|
|
319
|
+
Configuration for components that use a shared relational database:
|
|
320
|
+
- scheduler (crates/scheduler/migrations)
|
|
321
|
+
- workflow (crates/workflow/migrations)
|
|
322
|
+
- object-registry (via KeyValueStore SQL backend)
|
|
323
|
+
- journaling (crates/journaling/migrations)
|
|
324
|
+
- blob (crates/blob/migrations)
|
|
325
|
+
- locks (crates/locks/migrations)
|
|
326
|
+
- channel (crates/channel/migrations)
|
|
327
|
+
- keyvalue (crates/keyvalue/migrations)
|
|
328
|
+
- tuplespace (crates/tuplespace/migrations)
|
|
329
|
+
|
|
330
|
+
## Convention
|
|
331
|
+
All these components share the same database connection by default.
|
|
332
|
+
Can be overridden per-component if needed.
|
|
333
|
+
|
|
334
|
+
## Environment Variables
|
|
335
|
+
- PLEXSPACES_DATABASE_URL: Connection string (can use ${VAR} syntax)
|
|
336
|
+
- PLEXSPACES_DATABASE_POOL_SIZE: Connection pool size (default: 10)
|
|
337
|
+
- PLEXSPACES_DATABASE_AUTO_MIGRATE: Auto-migrate on startup (default: true)
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
connection_string: str = betterproto.string_field(1)
|
|
341
|
+
"""
|
|
342
|
+
Connection string (can use environment variable: ${DATABASE_URL})
|
|
343
|
+
Format: postgres://user:password@host:port/database
|
|
344
|
+
sqlite:///path/to/database.db
|
|
345
|
+
sqlite::memory:
|
|
346
|
+
|
|
347
|
+
Security: Passwords should be in environment variables, not config files
|
|
348
|
+
Example: "postgres://user:${DB_PASSWORD}@localhost:5432/plexspaces"
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
pool_size: int = betterproto.uint32_field(2)
|
|
352
|
+
"""Connection pool size (default: 10)"""
|
|
353
|
+
|
|
354
|
+
auto_migrate: bool = betterproto.bool_field(3)
|
|
355
|
+
"""
|
|
356
|
+
Auto-migrate on startup (default: true)
|
|
357
|
+
Runs migrations from migration_paths on node startup
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
migration_paths: List[str] = betterproto.string_field(4)
|
|
361
|
+
"""
|
|
362
|
+
Migration paths (default: auto-discover from crates/*/migrations)
|
|
363
|
+
Paths are relative to workspace root or absolute
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@dataclass(eq=False, repr=False)
|
|
368
|
+
class RedisBackendConfig(betterproto.Message):
|
|
369
|
+
"""
|
|
370
|
+
Redis backend configuration
|
|
371
|
+
|
|
372
|
+
## Purpose
|
|
373
|
+
Configuration for Redis-based backends (locks, channel, keyvalue, tuplespace).
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
url: str = betterproto.string_field(1)
|
|
377
|
+
"""
|
|
378
|
+
Redis connection URL
|
|
379
|
+
Format: redis://[password@]host:port[/database]
|
|
380
|
+
redis://localhost:6379/0
|
|
381
|
+
redis://:password@redis-cluster:6379/0
|
|
382
|
+
|
|
383
|
+
Security: Passwords should be in environment variables
|
|
384
|
+
Example: "redis://:${REDIS_PASSWORD}@localhost:6379/0"
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
pool_size: int = betterproto.uint32_field(2)
|
|
388
|
+
"""Connection pool size (default: 10)"""
|
|
389
|
+
|
|
390
|
+
key_prefix: str = betterproto.string_field(3)
|
|
391
|
+
"""
|
|
392
|
+
Key prefix for namespace isolation (default: "plexspaces:")
|
|
393
|
+
All keys will be prefixed with this value
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
connect_timeout: timedelta = betterproto.message_field(4)
|
|
397
|
+
"""Connection timeout (default: 5 seconds)"""
|
|
398
|
+
|
|
399
|
+
cluster_mode: bool = betterproto.bool_field(5)
|
|
400
|
+
"""
|
|
401
|
+
Enable cluster mode (default: false)
|
|
402
|
+
If true, url should be comma-separated list of nodes
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@dataclass(eq=False, repr=False)
|
|
407
|
+
class DynamoDbBackendConfig(betterproto.Message):
|
|
408
|
+
"""
|
|
409
|
+
DynamoDB backend configuration
|
|
410
|
+
|
|
411
|
+
## Purpose
|
|
412
|
+
Configuration for AWS DynamoDB-based backends (locks, keyvalue, tuplespace).
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
region: str = betterproto.string_field(1)
|
|
416
|
+
"""AWS region (e.g., "us-east-1")"""
|
|
417
|
+
|
|
418
|
+
table_prefix: str = betterproto.string_field(2)
|
|
419
|
+
"""
|
|
420
|
+
Table name prefix (default: "plexspaces-")
|
|
421
|
+
Actual table names will be: {prefix}{component_name}
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
endpoint_url: str = betterproto.string_field(3)
|
|
425
|
+
"""
|
|
426
|
+
Endpoint URL (for local testing with DynamoDB Local)
|
|
427
|
+
Leave empty for production (uses AWS service)
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
access_key_id: str = betterproto.string_field(4)
|
|
431
|
+
"""AWS access key ID (should be from env var, not config file)"""
|
|
432
|
+
|
|
433
|
+
secret_access_key: str = betterproto.string_field(5)
|
|
434
|
+
"""AWS secret access key (should be from env var, not config file)"""
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@dataclass(eq=False, repr=False)
|
|
438
|
+
class StorageProviderConfig(betterproto.Message):
|
|
439
|
+
"""
|
|
440
|
+
Storage provider configuration
|
|
441
|
+
|
|
442
|
+
## Purpose
|
|
443
|
+
Configuration for components that support multiple storage providers.
|
|
444
|
+
Uses provider pattern (similar to Kubernetes StorageClass) for cleaner design.
|
|
445
|
+
|
|
446
|
+
## Industry Best Practices
|
|
447
|
+
- Provider-based: One provider per config (not oneof with all options)
|
|
448
|
+
- Explicit: Must specify provider (no magic defaults)
|
|
449
|
+
- Extensible: Easy to add new providers without breaking changes
|
|
450
|
+
|
|
451
|
+
## Backend Selection Logic
|
|
452
|
+
1. Provider must be explicitly specified (no auto-detection)
|
|
453
|
+
2. If provider is UNSPECIFIED, config_manager sets defaults:
|
|
454
|
+
- locks: REDIS (if available), else use shared db
|
|
455
|
+
3. Validation: Fail if provider not available (feature flags)
|
|
456
|
+
|
|
457
|
+
## Environment Variables
|
|
458
|
+
- PLEXSPACES_{COMPONENT}_PROVIDER: Provider type (e.g., "redis", "postgres")
|
|
459
|
+
- Provider-specific env vars follow pattern: PLEXSPACES_{COMPONENT}_{PROVIDER}_{SETTING}
|
|
460
|
+
"""
|
|
461
|
+
|
|
462
|
+
provider: "StorageProvider" = betterproto.enum_field(1)
|
|
463
|
+
"""Storage provider type (required)"""
|
|
464
|
+
|
|
465
|
+
postgres: "SharedDbConfig" = betterproto.message_field(2, group="config")
|
|
466
|
+
"""PostgreSQL configuration (uses shared db config)"""
|
|
467
|
+
|
|
468
|
+
sqlite: "SqliteBackendConfig" = betterproto.message_field(3, group="config")
|
|
469
|
+
"""SQLite configuration"""
|
|
470
|
+
|
|
471
|
+
redis: "RedisBackendConfig" = betterproto.message_field(4, group="config")
|
|
472
|
+
"""Redis configuration"""
|
|
473
|
+
|
|
474
|
+
dynamodb: "DynamoDbBackendConfig" = betterproto.message_field(5, group="config")
|
|
475
|
+
"""DynamoDB configuration"""
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@dataclass(eq=False, repr=False)
|
|
479
|
+
class SqliteBackendConfig(betterproto.Message):
|
|
480
|
+
"""
|
|
481
|
+
SQLite backend configuration
|
|
482
|
+
|
|
483
|
+
## Purpose
|
|
484
|
+
Configuration for SQLite-based backends (primarily for channel).
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
database_path: str = betterproto.string_field(1)
|
|
488
|
+
"""
|
|
489
|
+
SQLite database path
|
|
490
|
+
- ":memory:" for in-memory database
|
|
491
|
+
- File path for persistent database
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
wal_mode: bool = betterproto.bool_field(2)
|
|
495
|
+
"""Enable WAL mode for better concurrency (default: true)"""
|
|
496
|
+
|
|
497
|
+
synchronous: str = betterproto.string_field(3)
|
|
498
|
+
"""Synchronous mode: "NORMAL", "FULL", "OFF" (default: "NORMAL")"""
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
@dataclass(eq=False, repr=False)
|
|
502
|
+
class FrameworkInfo(betterproto.Message):
|
|
503
|
+
"""
|
|
504
|
+
Framework information
|
|
505
|
+
|
|
506
|
+
## Purpose
|
|
507
|
+
Runtime information about the PlexSpaces framework version and build.
|
|
508
|
+
Auto-populated at build time from Cargo.toml.
|
|
509
|
+
"""
|
|
510
|
+
|
|
511
|
+
version: str = betterproto.string_field(1)
|
|
512
|
+
"""Framework version (from Cargo.toml, e.g., "0.1.0")"""
|
|
513
|
+
|
|
514
|
+
build_date: str = betterproto.string_field(2)
|
|
515
|
+
"""Build date (ISO 8601 format, e.g., "2025-01-25T10:30:00Z")"""
|
|
516
|
+
|
|
517
|
+
git_commit: str = betterproto.string_field(3)
|
|
518
|
+
"""Git commit hash (if available)"""
|
|
519
|
+
|
|
520
|
+
build_target: str = betterproto.string_field(4)
|
|
521
|
+
"""Build target (e.g., "x86_64-unknown-linux-gnu")"""
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class BlobServiceStub(betterproto.ServiceStub):
|
|
525
|
+
async def upload_blob(
|
|
526
|
+
self,
|
|
527
|
+
upload_blob_request: "UploadBlobRequest",
|
|
528
|
+
*,
|
|
529
|
+
timeout: Optional[float] = None,
|
|
530
|
+
deadline: Optional["Deadline"] = None,
|
|
531
|
+
metadata: Optional["MetadataLike"] = None
|
|
532
|
+
) -> "UploadBlobResponse":
|
|
533
|
+
return await self._unary_unary(
|
|
534
|
+
"/plexspaces.storage.v1.BlobService/UploadBlob",
|
|
535
|
+
upload_blob_request,
|
|
536
|
+
UploadBlobResponse,
|
|
537
|
+
timeout=timeout,
|
|
538
|
+
deadline=deadline,
|
|
539
|
+
metadata=metadata,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
async def download_blob(
|
|
543
|
+
self,
|
|
544
|
+
download_blob_request: "DownloadBlobRequest",
|
|
545
|
+
*,
|
|
546
|
+
timeout: Optional[float] = None,
|
|
547
|
+
deadline: Optional["Deadline"] = None,
|
|
548
|
+
metadata: Optional["MetadataLike"] = None
|
|
549
|
+
) -> AsyncIterator[DownloadBlobResponse]:
|
|
550
|
+
async for response in self._unary_stream(
|
|
551
|
+
"/plexspaces.storage.v1.BlobService/DownloadBlob",
|
|
552
|
+
download_blob_request,
|
|
553
|
+
DownloadBlobResponse,
|
|
554
|
+
timeout=timeout,
|
|
555
|
+
deadline=deadline,
|
|
556
|
+
metadata=metadata,
|
|
557
|
+
):
|
|
558
|
+
yield response
|
|
559
|
+
|
|
560
|
+
async def get_blob_metadata(
|
|
561
|
+
self,
|
|
562
|
+
get_blob_metadata_request: "GetBlobMetadataRequest",
|
|
563
|
+
*,
|
|
564
|
+
timeout: Optional[float] = None,
|
|
565
|
+
deadline: Optional["Deadline"] = None,
|
|
566
|
+
metadata: Optional["MetadataLike"] = None
|
|
567
|
+
) -> "GetBlobMetadataResponse":
|
|
568
|
+
return await self._unary_unary(
|
|
569
|
+
"/plexspaces.storage.v1.BlobService/GetBlobMetadata",
|
|
570
|
+
get_blob_metadata_request,
|
|
571
|
+
GetBlobMetadataResponse,
|
|
572
|
+
timeout=timeout,
|
|
573
|
+
deadline=deadline,
|
|
574
|
+
metadata=metadata,
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
async def list_blobs(
|
|
578
|
+
self,
|
|
579
|
+
list_blobs_request: "ListBlobsRequest",
|
|
580
|
+
*,
|
|
581
|
+
timeout: Optional[float] = None,
|
|
582
|
+
deadline: Optional["Deadline"] = None,
|
|
583
|
+
metadata: Optional["MetadataLike"] = None
|
|
584
|
+
) -> "ListBlobsResponse":
|
|
585
|
+
return await self._unary_unary(
|
|
586
|
+
"/plexspaces.storage.v1.BlobService/ListBlobs",
|
|
587
|
+
list_blobs_request,
|
|
588
|
+
ListBlobsResponse,
|
|
589
|
+
timeout=timeout,
|
|
590
|
+
deadline=deadline,
|
|
591
|
+
metadata=metadata,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
async def delete_blob(
|
|
595
|
+
self,
|
|
596
|
+
delete_blob_request: "DeleteBlobRequest",
|
|
597
|
+
*,
|
|
598
|
+
timeout: Optional[float] = None,
|
|
599
|
+
deadline: Optional["Deadline"] = None,
|
|
600
|
+
metadata: Optional["MetadataLike"] = None
|
|
601
|
+
) -> "DeleteBlobResponse":
|
|
602
|
+
return await self._unary_unary(
|
|
603
|
+
"/plexspaces.storage.v1.BlobService/DeleteBlob",
|
|
604
|
+
delete_blob_request,
|
|
605
|
+
DeleteBlobResponse,
|
|
606
|
+
timeout=timeout,
|
|
607
|
+
deadline=deadline,
|
|
608
|
+
metadata=metadata,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
async def generate_presigned_url(
|
|
612
|
+
self,
|
|
613
|
+
generate_presigned_url_request: "GeneratePresignedUrlRequest",
|
|
614
|
+
*,
|
|
615
|
+
timeout: Optional[float] = None,
|
|
616
|
+
deadline: Optional["Deadline"] = None,
|
|
617
|
+
metadata: Optional["MetadataLike"] = None
|
|
618
|
+
) -> "GeneratePresignedUrlResponse":
|
|
619
|
+
return await self._unary_unary(
|
|
620
|
+
"/plexspaces.storage.v1.BlobService/GeneratePresignedUrl",
|
|
621
|
+
generate_presigned_url_request,
|
|
622
|
+
GeneratePresignedUrlResponse,
|
|
623
|
+
timeout=timeout,
|
|
624
|
+
deadline=deadline,
|
|
625
|
+
metadata=metadata,
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
class BlobServiceBase(ServiceBase):
|
|
630
|
+
|
|
631
|
+
async def upload_blob(
|
|
632
|
+
self, upload_blob_request: "UploadBlobRequest"
|
|
633
|
+
) -> "UploadBlobResponse":
|
|
634
|
+
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
|
635
|
+
|
|
636
|
+
async def download_blob(
|
|
637
|
+
self, download_blob_request: "DownloadBlobRequest"
|
|
638
|
+
) -> AsyncIterator[DownloadBlobResponse]:
|
|
639
|
+
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
|
640
|
+
yield DownloadBlobResponse()
|
|
641
|
+
|
|
642
|
+
async def get_blob_metadata(
|
|
643
|
+
self, get_blob_metadata_request: "GetBlobMetadataRequest"
|
|
644
|
+
) -> "GetBlobMetadataResponse":
|
|
645
|
+
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
|
646
|
+
|
|
647
|
+
async def list_blobs(
|
|
648
|
+
self, list_blobs_request: "ListBlobsRequest"
|
|
649
|
+
) -> "ListBlobsResponse":
|
|
650
|
+
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
|
651
|
+
|
|
652
|
+
async def delete_blob(
|
|
653
|
+
self, delete_blob_request: "DeleteBlobRequest"
|
|
654
|
+
) -> "DeleteBlobResponse":
|
|
655
|
+
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
|
656
|
+
|
|
657
|
+
async def generate_presigned_url(
|
|
658
|
+
self, generate_presigned_url_request: "GeneratePresignedUrlRequest"
|
|
659
|
+
) -> "GeneratePresignedUrlResponse":
|
|
660
|
+
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
|
|
661
|
+
|
|
662
|
+
async def __rpc_upload_blob(
|
|
663
|
+
self, stream: "grpclib.server.Stream[UploadBlobRequest, UploadBlobResponse]"
|
|
664
|
+
) -> None:
|
|
665
|
+
request = await stream.recv_message()
|
|
666
|
+
response = await self.upload_blob(request)
|
|
667
|
+
await stream.send_message(response)
|
|
668
|
+
|
|
669
|
+
async def __rpc_download_blob(
|
|
670
|
+
self, stream: "grpclib.server.Stream[DownloadBlobRequest, DownloadBlobResponse]"
|
|
671
|
+
) -> None:
|
|
672
|
+
request = await stream.recv_message()
|
|
673
|
+
await self._call_rpc_handler_server_stream(
|
|
674
|
+
self.download_blob,
|
|
675
|
+
stream,
|
|
676
|
+
request,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
async def __rpc_get_blob_metadata(
|
|
680
|
+
self,
|
|
681
|
+
stream: "grpclib.server.Stream[GetBlobMetadataRequest, GetBlobMetadataResponse]",
|
|
682
|
+
) -> None:
|
|
683
|
+
request = await stream.recv_message()
|
|
684
|
+
response = await self.get_blob_metadata(request)
|
|
685
|
+
await stream.send_message(response)
|
|
686
|
+
|
|
687
|
+
async def __rpc_list_blobs(
|
|
688
|
+
self, stream: "grpclib.server.Stream[ListBlobsRequest, ListBlobsResponse]"
|
|
689
|
+
) -> None:
|
|
690
|
+
request = await stream.recv_message()
|
|
691
|
+
response = await self.list_blobs(request)
|
|
692
|
+
await stream.send_message(response)
|
|
693
|
+
|
|
694
|
+
async def __rpc_delete_blob(
|
|
695
|
+
self, stream: "grpclib.server.Stream[DeleteBlobRequest, DeleteBlobResponse]"
|
|
696
|
+
) -> None:
|
|
697
|
+
request = await stream.recv_message()
|
|
698
|
+
response = await self.delete_blob(request)
|
|
699
|
+
await stream.send_message(response)
|
|
700
|
+
|
|
701
|
+
async def __rpc_generate_presigned_url(
|
|
702
|
+
self,
|
|
703
|
+
stream: "grpclib.server.Stream[GeneratePresignedUrlRequest, GeneratePresignedUrlResponse]",
|
|
704
|
+
) -> None:
|
|
705
|
+
request = await stream.recv_message()
|
|
706
|
+
response = await self.generate_presigned_url(request)
|
|
707
|
+
await stream.send_message(response)
|
|
708
|
+
|
|
709
|
+
def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
|
|
710
|
+
return {
|
|
711
|
+
"/plexspaces.storage.v1.BlobService/UploadBlob": grpclib.const.Handler(
|
|
712
|
+
self.__rpc_upload_blob,
|
|
713
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
714
|
+
UploadBlobRequest,
|
|
715
|
+
UploadBlobResponse,
|
|
716
|
+
),
|
|
717
|
+
"/plexspaces.storage.v1.BlobService/DownloadBlob": grpclib.const.Handler(
|
|
718
|
+
self.__rpc_download_blob,
|
|
719
|
+
grpclib.const.Cardinality.UNARY_STREAM,
|
|
720
|
+
DownloadBlobRequest,
|
|
721
|
+
DownloadBlobResponse,
|
|
722
|
+
),
|
|
723
|
+
"/plexspaces.storage.v1.BlobService/GetBlobMetadata": grpclib.const.Handler(
|
|
724
|
+
self.__rpc_get_blob_metadata,
|
|
725
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
726
|
+
GetBlobMetadataRequest,
|
|
727
|
+
GetBlobMetadataResponse,
|
|
728
|
+
),
|
|
729
|
+
"/plexspaces.storage.v1.BlobService/ListBlobs": grpclib.const.Handler(
|
|
730
|
+
self.__rpc_list_blobs,
|
|
731
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
732
|
+
ListBlobsRequest,
|
|
733
|
+
ListBlobsResponse,
|
|
734
|
+
),
|
|
735
|
+
"/plexspaces.storage.v1.BlobService/DeleteBlob": grpclib.const.Handler(
|
|
736
|
+
self.__rpc_delete_blob,
|
|
737
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
738
|
+
DeleteBlobRequest,
|
|
739
|
+
DeleteBlobResponse,
|
|
740
|
+
),
|
|
741
|
+
"/plexspaces.storage.v1.BlobService/GeneratePresignedUrl": grpclib.const.Handler(
|
|
742
|
+
self.__rpc_generate_presigned_url,
|
|
743
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
744
|
+
GeneratePresignedUrlRequest,
|
|
745
|
+
GeneratePresignedUrlResponse,
|
|
746
|
+
),
|
|
747
|
+
}
|