perd-contracts 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,209 @@
1
+ r"""Derive an :class:`OperationSpec` manifest from a ``FileDescriptorSet``.
2
+
3
+ This module is the mechanism that makes contract drift structurally impossible.
4
+
5
+ In the v1 system the operation manifest was untyped JSON written to Firestore by
6
+ the scaffold builder, while the ``.proto`` describing the same operations was
7
+ uploaded separately to GCS. Two independently-shipped descriptions of one
8
+ contract, cross-checked by nothing. A redeploy that updated one and not the
9
+ other surfaced as a field-level parse error deep inside a running job.
10
+
11
+ Here there is exactly one artifact -- the ``FileDescriptorSet`` embedded in the
12
+ worker image -- and the manifest is a *projection* of it. There is no second
13
+ description to drift from, and
14
+
15
+ .. math::
16
+
17
+ \mathrm{contract\_hash} = \operatorname{SHA256}\!\left(\mathrm{FileDescriptorSet}\right)
18
+
19
+ is the identity that the publisher, the client, and the orchestrator all pin to.
20
+
21
+ where:
22
+
23
+ - :math:`\mathrm{FileDescriptorSet}`: the serialized protobuf descriptor of a workflow's generated schema, produced once at build time and embedded in the worker image
24
+ - :math:`\mathrm{contract\_hash}`: the value stored on the published version, pinned by the client, and re-verified by the orchestrator at worker registration
25
+
26
+ Naming convention consumed here, emitted by the worker SDK's code generator:
27
+
28
+ - an operation ``foo_bar`` produces RPC ``FooBar`` with messages ``FooBarRequest`` and ``FooBarResponse``
29
+ - input-stream item fields live on the request message and are declared in the SDK-emitted ``perd.operation`` option; absent that, they are inferred from the RPC's client-streaming flag
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import hashlib
35
+ import re
36
+ from dataclasses import dataclass
37
+
38
+ from google.protobuf import descriptor_pb2
39
+
40
+ # perd.v1.CallMode values, mirrored here so this tool has no import cycle back
41
+ # through generated code it may itself be used to produce.
42
+ CALL_MODE_UNARY = 1
43
+ CALL_MODE_INPUT_STREAM = 2
44
+ CALL_MODE_OUTPUT_STREAM = 3
45
+ CALL_MODE_BI_DI = 4
46
+
47
+ _MODE_BY_STREAMING = {
48
+ (False, False): CALL_MODE_UNARY,
49
+ (True, False): CALL_MODE_INPUT_STREAM,
50
+ (False, True): CALL_MODE_OUTPUT_STREAM,
51
+ (True, True): CALL_MODE_BI_DI,
52
+ }
53
+
54
+ _PROTO_TYPE_NAMES = {
55
+ descriptor_pb2.FieldDescriptorProto.TYPE_DOUBLE: "double",
56
+ descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT: "float",
57
+ descriptor_pb2.FieldDescriptorProto.TYPE_INT64: "int64",
58
+ descriptor_pb2.FieldDescriptorProto.TYPE_UINT64: "uint64",
59
+ descriptor_pb2.FieldDescriptorProto.TYPE_INT32: "int32",
60
+ descriptor_pb2.FieldDescriptorProto.TYPE_BOOL: "bool",
61
+ descriptor_pb2.FieldDescriptorProto.TYPE_STRING: "string",
62
+ descriptor_pb2.FieldDescriptorProto.TYPE_BYTES: "bytes",
63
+ descriptor_pb2.FieldDescriptorProto.TYPE_UINT32: "uint32",
64
+ }
65
+
66
+ _STREAM_ITEM_RE = re.compile(r"^item_(\d+)$")
67
+
68
+
69
+ class ManifestError(ValueError):
70
+ """Raised when a descriptor set cannot yield a well-formed manifest."""
71
+
72
+
73
+ @dataclass(frozen=True, slots=True)
74
+ class Field:
75
+ """One parameter or result field projected from a descriptor."""
76
+
77
+ name: str
78
+ proto_type: str
79
+ field_number: int
80
+ optional: bool
81
+
82
+
83
+ @dataclass(frozen=True, slots=True)
84
+ class Operation:
85
+ """One callable operation, fully described by the descriptor set."""
86
+
87
+ function_name: str
88
+ rpc_name: str
89
+ mode: int
90
+ params: tuple[Field, ...]
91
+ input_stream_fields: tuple[Field, ...]
92
+ output_fields: tuple[Field, ...]
93
+ docstring: str | None = None
94
+
95
+
96
+ def contract_hash(descriptor_set: bytes) -> str:
97
+ r"""Return the canonical contract identity for a descriptor set.
98
+
99
+ .. math::
100
+
101
+ h = \operatorname{SHA256}(D)
102
+
103
+ where:
104
+
105
+ - :math:`D`: the serialized ``FileDescriptorSet`` bytes exactly as embedded in the worker image
106
+ - :math:`h`: lowercase hex digest, compared with :func:`hmac.compare_digest` at registration
107
+ """
108
+ return hashlib.sha256(descriptor_set).hexdigest()
109
+
110
+
111
+ def _snake_case(name: str) -> str:
112
+ """``FooBar`` -> ``foo_bar``. Inverse of the SDK's RPC naming rule."""
113
+ return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
114
+
115
+
116
+ def _fields_of(msg: descriptor_pb2.DescriptorProto) -> tuple[Field, ...]:
117
+ out = []
118
+ for f in msg.field:
119
+ proto_type = _PROTO_TYPE_NAMES.get(f.type)
120
+ if proto_type is None:
121
+ raise ManifestError(
122
+ f"Field '{msg.name}.{f.name}' has unsupported type {f.type}. "
123
+ "Workflow signatures may only use scalar types."
124
+ )
125
+ out.append(
126
+ Field(
127
+ name=f.name,
128
+ proto_type=proto_type,
129
+ field_number=f.number,
130
+ # proto3 explicit presence: the SDK marks every result field
131
+ # `optional` so a returned 0 / "" / False is distinguishable
132
+ # from an absent field. v1's MessageToDict elided all of them.
133
+ optional=f.proto3_optional,
134
+ )
135
+ )
136
+ return tuple(out)
137
+
138
+
139
+ def _partition_request_fields(
140
+ fields: tuple[Field, ...], client_streaming: bool
141
+ ) -> tuple[tuple[Field, ...], tuple[Field, ...]]:
142
+ """Split request fields into (per-call params, per-stream-item fields).
143
+
144
+ Stream item fields are named ``item_1``, ``item_2``, ... by the SDK's
145
+ generator. Everything else is a trailing unary parameter, which is the
146
+ shape DCA_PINN uses: a 7-tuple input stream plus 12 hyperparameters.
147
+ """
148
+ if not client_streaming:
149
+ return fields, ()
150
+ matches = {f.name: _STREAM_ITEM_RE.match(f.name) for f in fields}
151
+ items = [f for f in fields if matches[f.name]]
152
+ params = [f for f in fields if not matches[f.name]]
153
+
154
+ def item_index(f: Field) -> int:
155
+ match = matches[f.name]
156
+ assert match is not None # items was filtered on exactly this
157
+ return int(match.group(1))
158
+
159
+ items.sort(key=item_index)
160
+ return tuple(params), tuple(items)
161
+
162
+
163
+ def build_manifest(descriptor_set: bytes) -> tuple[Operation, ...]:
164
+ """Project every RPC in ``descriptor_set`` into an :class:`Operation`.
165
+
166
+ Raises:
167
+ ManifestError: if the set has no service, or references a message type
168
+ that is not defined within the same set.
169
+ """
170
+ fds = descriptor_pb2.FileDescriptorSet()
171
+ fds.ParseFromString(descriptor_set)
172
+
173
+ messages: dict[str, descriptor_pb2.DescriptorProto] = {}
174
+ services: list[descriptor_pb2.ServiceDescriptorProto] = []
175
+ for file in fds.file:
176
+ pkg = file.package
177
+ for msg in file.message_type:
178
+ messages[f".{pkg}.{msg.name}" if pkg else f".{msg.name}"] = msg
179
+ services.extend(file.service)
180
+
181
+ if not services:
182
+ raise ManifestError("Descriptor set defines no service.")
183
+
184
+ operations: list[Operation] = []
185
+ for service in services:
186
+ for method in service.method:
187
+ req = messages.get(method.input_type)
188
+ res = messages.get(method.output_type)
189
+ if req is None or res is None:
190
+ raise ManifestError(
191
+ f"RPC '{method.name}' references a message outside the "
192
+ f"descriptor set ({method.input_type} / {method.output_type})."
193
+ )
194
+ params, stream_fields = _partition_request_fields(
195
+ _fields_of(req), method.client_streaming
196
+ )
197
+ operations.append(
198
+ Operation(
199
+ function_name=_snake_case(method.name),
200
+ rpc_name=method.name,
201
+ mode=_MODE_BY_STREAMING[
202
+ (method.client_streaming, method.server_streaming)
203
+ ],
204
+ params=params,
205
+ input_stream_fields=stream_fields,
206
+ output_fields=_fields_of(res),
207
+ )
208
+ )
209
+ return tuple(operations)
@@ -0,0 +1,253 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT!
3
+ # source: perd/v1/account.proto
4
+
5
+ from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping
6
+ from typing import Protocol
7
+
8
+ from connectrpc.client import ConnectClient, ConnectClientSync
9
+ from connectrpc.code import Code
10
+ from connectrpc.compression import Compression
11
+ from connectrpc.errors import ConnectError
12
+ from connectrpc.interceptor import Interceptor, InterceptorSync
13
+ from connectrpc.method import IdempotencyLevel, MethodInfo
14
+ from connectrpc.request import Headers, RequestContext
15
+ from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync
16
+ import perd.v1.account_pb2 as perd_dot_v1_dot_account__pb2
17
+
18
+
19
+ class AccountService(Protocol):
20
+ async def list_api_keys(self, request: perd_dot_v1_dot_account__pb2.ListApiKeysRequest, ctx: RequestContext) -> perd_dot_v1_dot_account__pb2.ListApiKeysResponse:
21
+ raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
22
+
23
+ async def issue_api_key(self, request: perd_dot_v1_dot_account__pb2.IssueApiKeyRequest, ctx: RequestContext) -> perd_dot_v1_dot_account__pb2.IssueApiKeyResponse:
24
+ raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
25
+
26
+ async def revoke_api_key(self, request: perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest, ctx: RequestContext) -> perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse:
27
+ raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
28
+
29
+
30
+ class AccountServiceASGIApplication(ConnectASGIApplication[AccountService]):
31
+ def __init__(self, service: AccountService | AsyncGenerator[AccountService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None) -> None:
32
+ super().__init__(
33
+ service=service,
34
+ endpoints=lambda svc: {
35
+ "/perd.v1.AccountService/ListApiKeys": Endpoint.unary(
36
+ method=MethodInfo(
37
+ name="ListApiKeys",
38
+ service_name="perd.v1.AccountService",
39
+ input=perd_dot_v1_dot_account__pb2.ListApiKeysRequest,
40
+ output=perd_dot_v1_dot_account__pb2.ListApiKeysResponse,
41
+ idempotency_level=IdempotencyLevel.UNKNOWN,
42
+ ),
43
+ function=svc.list_api_keys,
44
+ ),
45
+ "/perd.v1.AccountService/IssueApiKey": Endpoint.unary(
46
+ method=MethodInfo(
47
+ name="IssueApiKey",
48
+ service_name="perd.v1.AccountService",
49
+ input=perd_dot_v1_dot_account__pb2.IssueApiKeyRequest,
50
+ output=perd_dot_v1_dot_account__pb2.IssueApiKeyResponse,
51
+ idempotency_level=IdempotencyLevel.UNKNOWN,
52
+ ),
53
+ function=svc.issue_api_key,
54
+ ),
55
+ "/perd.v1.AccountService/RevokeApiKey": Endpoint.unary(
56
+ method=MethodInfo(
57
+ name="RevokeApiKey",
58
+ service_name="perd.v1.AccountService",
59
+ input=perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest,
60
+ output=perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse,
61
+ idempotency_level=IdempotencyLevel.UNKNOWN,
62
+ ),
63
+ function=svc.revoke_api_key,
64
+ ),
65
+ },
66
+ interceptors=interceptors,
67
+ read_max_bytes=read_max_bytes,
68
+ compressions=compressions,
69
+ )
70
+
71
+ @property
72
+ def path(self) -> str:
73
+ """Returns the URL path to mount the application to when serving multiple applications."""
74
+ return "/perd.v1.AccountService"
75
+
76
+
77
+ class AccountServiceClient(ConnectClient):
78
+ async def list_api_keys(
79
+ self,
80
+ request: perd_dot_v1_dot_account__pb2.ListApiKeysRequest,
81
+ *,
82
+ headers: Headers | Mapping[str, str] | None = None,
83
+ timeout_ms: int | None = None,
84
+ ) -> perd_dot_v1_dot_account__pb2.ListApiKeysResponse:
85
+ return await self.execute_unary(
86
+ request=request,
87
+ method=MethodInfo(
88
+ name="ListApiKeys",
89
+ service_name="perd.v1.AccountService",
90
+ input=perd_dot_v1_dot_account__pb2.ListApiKeysRequest,
91
+ output=perd_dot_v1_dot_account__pb2.ListApiKeysResponse,
92
+ idempotency_level=IdempotencyLevel.UNKNOWN,
93
+ ),
94
+ headers=headers,
95
+ timeout_ms=timeout_ms,
96
+ )
97
+
98
+ async def issue_api_key(
99
+ self,
100
+ request: perd_dot_v1_dot_account__pb2.IssueApiKeyRequest,
101
+ *,
102
+ headers: Headers | Mapping[str, str] | None = None,
103
+ timeout_ms: int | None = None,
104
+ ) -> perd_dot_v1_dot_account__pb2.IssueApiKeyResponse:
105
+ return await self.execute_unary(
106
+ request=request,
107
+ method=MethodInfo(
108
+ name="IssueApiKey",
109
+ service_name="perd.v1.AccountService",
110
+ input=perd_dot_v1_dot_account__pb2.IssueApiKeyRequest,
111
+ output=perd_dot_v1_dot_account__pb2.IssueApiKeyResponse,
112
+ idempotency_level=IdempotencyLevel.UNKNOWN,
113
+ ),
114
+ headers=headers,
115
+ timeout_ms=timeout_ms,
116
+ )
117
+
118
+ async def revoke_api_key(
119
+ self,
120
+ request: perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest,
121
+ *,
122
+ headers: Headers | Mapping[str, str] | None = None,
123
+ timeout_ms: int | None = None,
124
+ ) -> perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse:
125
+ return await self.execute_unary(
126
+ request=request,
127
+ method=MethodInfo(
128
+ name="RevokeApiKey",
129
+ service_name="perd.v1.AccountService",
130
+ input=perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest,
131
+ output=perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse,
132
+ idempotency_level=IdempotencyLevel.UNKNOWN,
133
+ ),
134
+ headers=headers,
135
+ timeout_ms=timeout_ms,
136
+ )
137
+
138
+
139
+ class AccountServiceSync(Protocol):
140
+ def list_api_keys(self, request: perd_dot_v1_dot_account__pb2.ListApiKeysRequest, ctx: RequestContext) -> perd_dot_v1_dot_account__pb2.ListApiKeysResponse:
141
+ raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
142
+ def issue_api_key(self, request: perd_dot_v1_dot_account__pb2.IssueApiKeyRequest, ctx: RequestContext) -> perd_dot_v1_dot_account__pb2.IssueApiKeyResponse:
143
+ raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
144
+ def revoke_api_key(self, request: perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest, ctx: RequestContext) -> perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse:
145
+ raise ConnectError(Code.UNIMPLEMENTED, "Not implemented")
146
+
147
+
148
+ class AccountServiceWSGIApplication(ConnectWSGIApplication):
149
+ def __init__(self, service: AccountServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None) -> None:
150
+ super().__init__(
151
+ endpoints={
152
+ "/perd.v1.AccountService/ListApiKeys": EndpointSync.unary(
153
+ method=MethodInfo(
154
+ name="ListApiKeys",
155
+ service_name="perd.v1.AccountService",
156
+ input=perd_dot_v1_dot_account__pb2.ListApiKeysRequest,
157
+ output=perd_dot_v1_dot_account__pb2.ListApiKeysResponse,
158
+ idempotency_level=IdempotencyLevel.UNKNOWN,
159
+ ),
160
+ function=service.list_api_keys,
161
+ ),
162
+ "/perd.v1.AccountService/IssueApiKey": EndpointSync.unary(
163
+ method=MethodInfo(
164
+ name="IssueApiKey",
165
+ service_name="perd.v1.AccountService",
166
+ input=perd_dot_v1_dot_account__pb2.IssueApiKeyRequest,
167
+ output=perd_dot_v1_dot_account__pb2.IssueApiKeyResponse,
168
+ idempotency_level=IdempotencyLevel.UNKNOWN,
169
+ ),
170
+ function=service.issue_api_key,
171
+ ),
172
+ "/perd.v1.AccountService/RevokeApiKey": EndpointSync.unary(
173
+ method=MethodInfo(
174
+ name="RevokeApiKey",
175
+ service_name="perd.v1.AccountService",
176
+ input=perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest,
177
+ output=perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse,
178
+ idempotency_level=IdempotencyLevel.UNKNOWN,
179
+ ),
180
+ function=service.revoke_api_key,
181
+ ),
182
+ },
183
+ interceptors=interceptors,
184
+ read_max_bytes=read_max_bytes,
185
+ compressions=compressions,
186
+ )
187
+
188
+ @property
189
+ def path(self) -> str:
190
+ """Returns the URL path to mount the application to when serving multiple applications."""
191
+ return "/perd.v1.AccountService"
192
+
193
+
194
+ class AccountServiceClientSync(ConnectClientSync):
195
+ def list_api_keys(
196
+ self,
197
+ request: perd_dot_v1_dot_account__pb2.ListApiKeysRequest,
198
+ *,
199
+ headers: Headers | Mapping[str, str] | None = None,
200
+ timeout_ms: int | None = None,
201
+ ) -> perd_dot_v1_dot_account__pb2.ListApiKeysResponse:
202
+ return self.execute_unary(
203
+ request=request,
204
+ method=MethodInfo(
205
+ name="ListApiKeys",
206
+ service_name="perd.v1.AccountService",
207
+ input=perd_dot_v1_dot_account__pb2.ListApiKeysRequest,
208
+ output=perd_dot_v1_dot_account__pb2.ListApiKeysResponse,
209
+ idempotency_level=IdempotencyLevel.UNKNOWN,
210
+ ),
211
+ headers=headers,
212
+ timeout_ms=timeout_ms,
213
+ )
214
+
215
+ def issue_api_key(
216
+ self,
217
+ request: perd_dot_v1_dot_account__pb2.IssueApiKeyRequest,
218
+ *,
219
+ headers: Headers | Mapping[str, str] | None = None,
220
+ timeout_ms: int | None = None,
221
+ ) -> perd_dot_v1_dot_account__pb2.IssueApiKeyResponse:
222
+ return self.execute_unary(
223
+ request=request,
224
+ method=MethodInfo(
225
+ name="IssueApiKey",
226
+ service_name="perd.v1.AccountService",
227
+ input=perd_dot_v1_dot_account__pb2.IssueApiKeyRequest,
228
+ output=perd_dot_v1_dot_account__pb2.IssueApiKeyResponse,
229
+ idempotency_level=IdempotencyLevel.UNKNOWN,
230
+ ),
231
+ headers=headers,
232
+ timeout_ms=timeout_ms,
233
+ )
234
+
235
+ def revoke_api_key(
236
+ self,
237
+ request: perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest,
238
+ *,
239
+ headers: Headers | Mapping[str, str] | None = None,
240
+ timeout_ms: int | None = None,
241
+ ) -> perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse:
242
+ return self.execute_unary(
243
+ request=request,
244
+ method=MethodInfo(
245
+ name="RevokeApiKey",
246
+ service_name="perd.v1.AccountService",
247
+ input=perd_dot_v1_dot_account__pb2.RevokeApiKeyRequest,
248
+ output=perd_dot_v1_dot_account__pb2.RevokeApiKeyResponse,
249
+ idempotency_level=IdempotencyLevel.UNKNOWN,
250
+ ),
251
+ headers=headers,
252
+ timeout_ms=timeout_ms,
253
+ )
perd/v1/account_pb2.py ADDED
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: perd/v1/account.proto
5
+ # Protobuf Python Version: 5.29.3
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 5,
15
+ 29,
16
+ 3,
17
+ '',
18
+ 'perd/v1/account.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
26
+
27
+
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15perd/v1/account.proto\x12\x07perd.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xcc\x01\n\x06\x41piKey\x12\x15\n\x06key_id\x18\x01 \x01(\tR\x05keyId\x12!\n\x0c\x64isplay_name\x18\x02 \x01(\tR\x0b\x64isplayName\x12\x39\n\ncreated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12>\n\nrevoked_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00R\trevokedAt\x88\x01\x01\x42\r\n\x0b_revoked_at\"\x14\n\x12ListApiKeysRequest\":\n\x13ListApiKeysResponse\x12#\n\x04keys\x18\x01 \x03(\x0b\x32\x0f.perd.v1.ApiKeyR\x04keys\"7\n\x12IssueApiKeyRequest\x12!\n\x0c\x64isplay_name\x18\x01 \x01(\tR\x0b\x64isplayName\"Q\n\x13IssueApiKeyResponse\x12!\n\x03key\x18\x01 \x01(\x0b\x32\x0f.perd.v1.ApiKeyR\x03key\x12\x17\n\x07\x61pi_key\x18\x02 \x01(\tR\x06\x61piKey\",\n\x13RevokeApiKeyRequest\x12\x15\n\x06key_id\x18\x01 \x01(\tR\x05keyId\"9\n\x14RevokeApiKeyResponse\x12!\n\x03key\x18\x01 \x01(\x0b\x32\x0f.perd.v1.ApiKeyR\x03key2\xf1\x01\n\x0e\x41\x63\x63ountService\x12H\n\x0bListApiKeys\x12\x1b.perd.v1.ListApiKeysRequest\x1a\x1c.perd.v1.ListApiKeysResponse\x12H\n\x0bIssueApiKey\x12\x1b.perd.v1.IssueApiKeyRequest\x1a\x1c.perd.v1.IssueApiKeyResponse\x12K\n\x0cRevokeApiKey\x12\x1c.perd.v1.RevokeApiKeyRequest\x1a\x1d.perd.v1.RevokeApiKeyResponseBX\n\x0b\x63om.perd.v1B\x0c\x41\x63\x63ountProtoP\x01\xa2\x02\x03PXX\xaa\x02\x07Perd.V1\xca\x02\x07Perd\\V1\xe2\x02\x13Perd\\V1\\GPBMetadata\xea\x02\x08Perd::V1b\x06proto3')
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'perd.v1.account_pb2', _globals)
33
+ if not _descriptor._USE_C_DESCRIPTORS:
34
+ _globals['DESCRIPTOR']._loaded_options = None
35
+ _globals['DESCRIPTOR']._serialized_options = b'\n\013com.perd.v1B\014AccountProtoP\001\242\002\003PXX\252\002\007Perd.V1\312\002\007Perd\\V1\342\002\023Perd\\V1\\GPBMetadata\352\002\010Perd::V1'
36
+ _globals['_APIKEY']._serialized_start=68
37
+ _globals['_APIKEY']._serialized_end=272
38
+ _globals['_LISTAPIKEYSREQUEST']._serialized_start=274
39
+ _globals['_LISTAPIKEYSREQUEST']._serialized_end=294
40
+ _globals['_LISTAPIKEYSRESPONSE']._serialized_start=296
41
+ _globals['_LISTAPIKEYSRESPONSE']._serialized_end=354
42
+ _globals['_ISSUEAPIKEYREQUEST']._serialized_start=356
43
+ _globals['_ISSUEAPIKEYREQUEST']._serialized_end=411
44
+ _globals['_ISSUEAPIKEYRESPONSE']._serialized_start=413
45
+ _globals['_ISSUEAPIKEYRESPONSE']._serialized_end=494
46
+ _globals['_REVOKEAPIKEYREQUEST']._serialized_start=496
47
+ _globals['_REVOKEAPIKEYREQUEST']._serialized_end=540
48
+ _globals['_REVOKEAPIKEYRESPONSE']._serialized_start=542
49
+ _globals['_REVOKEAPIKEYRESPONSE']._serialized_end=599
50
+ _globals['_ACCOUNTSERVICE']._serialized_start=602
51
+ _globals['_ACCOUNTSERVICE']._serialized_end=843
52
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,55 @@
1
+ from google.protobuf import timestamp_pb2 as _timestamp_pb2
2
+ from google.protobuf.internal import containers as _containers
3
+ from google.protobuf import descriptor as _descriptor
4
+ from google.protobuf import message as _message
5
+ from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
6
+
7
+ DESCRIPTOR: _descriptor.FileDescriptor
8
+
9
+ class ApiKey(_message.Message):
10
+ __slots__ = ("key_id", "display_name", "created_at", "revoked_at")
11
+ KEY_ID_FIELD_NUMBER: _ClassVar[int]
12
+ DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int]
13
+ CREATED_AT_FIELD_NUMBER: _ClassVar[int]
14
+ REVOKED_AT_FIELD_NUMBER: _ClassVar[int]
15
+ key_id: str
16
+ display_name: str
17
+ created_at: _timestamp_pb2.Timestamp
18
+ revoked_at: _timestamp_pb2.Timestamp
19
+ def __init__(self, key_id: _Optional[str] = ..., display_name: _Optional[str] = ..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., revoked_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
20
+
21
+ class ListApiKeysRequest(_message.Message):
22
+ __slots__ = ()
23
+ def __init__(self) -> None: ...
24
+
25
+ class ListApiKeysResponse(_message.Message):
26
+ __slots__ = ("keys",)
27
+ KEYS_FIELD_NUMBER: _ClassVar[int]
28
+ keys: _containers.RepeatedCompositeFieldContainer[ApiKey]
29
+ def __init__(self, keys: _Optional[_Iterable[_Union[ApiKey, _Mapping]]] = ...) -> None: ...
30
+
31
+ class IssueApiKeyRequest(_message.Message):
32
+ __slots__ = ("display_name",)
33
+ DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int]
34
+ display_name: str
35
+ def __init__(self, display_name: _Optional[str] = ...) -> None: ...
36
+
37
+ class IssueApiKeyResponse(_message.Message):
38
+ __slots__ = ("key", "api_key")
39
+ KEY_FIELD_NUMBER: _ClassVar[int]
40
+ API_KEY_FIELD_NUMBER: _ClassVar[int]
41
+ key: ApiKey
42
+ api_key: str
43
+ def __init__(self, key: _Optional[_Union[ApiKey, _Mapping]] = ..., api_key: _Optional[str] = ...) -> None: ...
44
+
45
+ class RevokeApiKeyRequest(_message.Message):
46
+ __slots__ = ("key_id",)
47
+ KEY_ID_FIELD_NUMBER: _ClassVar[int]
48
+ key_id: str
49
+ def __init__(self, key_id: _Optional[str] = ...) -> None: ...
50
+
51
+ class RevokeApiKeyResponse(_message.Message):
52
+ __slots__ = ("key",)
53
+ KEY_FIELD_NUMBER: _ClassVar[int]
54
+ key: ApiKey
55
+ def __init__(self, key: _Optional[_Union[ApiKey, _Mapping]] = ...) -> None: ...
perd/v1/common_pb2.py ADDED
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: perd/v1/common.proto
5
+ # Protobuf Python Version: 5.29.3
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 5,
15
+ 29,
16
+ 3,
17
+ '',
18
+ 'perd/v1/common.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
26
+
27
+
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14perd/v1/common.proto\x12\x07perd.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x02\n\x07\x46\x61ilure\x12(\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x14.perd.v1.FailureCodeR\x04\x63ode\x12\x18\n\x07message\x18\x02 \x01(\tR\x07message\x12\x1c\n\tretryable\x18\x03 \x01(\x08R\tretryable\x12)\n\x0eretry_after_ms\x18\x04 \x01(\rH\x00R\x0cretryAfterMs\x88\x01\x01\x12%\n\x0bremediation\x18\x05 \x01(\tH\x01R\x0bremediation\x88\x01\x01\x12\x1e\n\x08trace_id\x18\x06 \x01(\tH\x02R\x07traceId\x88\x01\x01\x42\x11\n\x0f_retry_after_msB\x0e\n\x0c_remediationB\x0b\n\t_trace_id\"m\n\x0bWorkflowRef\x12\x1f\n\x0bworkflow_id\x18\x01 \x01(\tR\nworkflowId\x12\x18\n\x07version\x18\x02 \x01(\tR\x07version\x12#\n\rcontract_hash\x18\x03 \x01(\tR\x0c\x63ontractHash\"b\n\x05\x41\x63tor\x12\x17\n\x07user_id\x18\x01 \x01(\tR\x06userId\x12,\n\x0forganization_id\x18\x02 \x01(\tH\x00R\x0eorganizationId\x88\x01\x01\x42\x12\n\x10_organization_id\"\xc5\x01\n\nAuditStamp\x12\x39\n\ncreated_at\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12>\n\nupdated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00R\tupdatedAt\x88\x01\x01\x12-\n\ncreated_by\x18\x03 \x01(\x0b\x32\x0e.perd.v1.ActorR\tcreatedByB\r\n\x0b_updated_at\"]\n\x0bPageRequest\x12\x1b\n\tpage_size\x18\x01 \x01(\rR\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tH\x00R\tpageToken\x88\x01\x01\x42\r\n\x0b_page_token\"\x82\x01\n\x0cPageResponse\x12+\n\x0fnext_page_token\x18\x01 \x01(\tH\x00R\rnextPageToken\x88\x01\x01\x12\"\n\ntotal_size\x18\x02 \x01(\rH\x01R\ttotalSize\x88\x01\x01\x42\x12\n\x10_next_page_tokenB\r\n\x0b_total_size*\x84\x04\n\x0b\x46\x61ilureCode\x12\x1c\n\x18\x46\x41ILURE_CODE_UNSPECIFIED\x10\x00\x12!\n\x1d\x46\x41ILURE_CODE_INVALID_ARGUMENT\x10\x01\x12 \n\x1c\x46\x41ILURE_CODE_UNAUTHENTICATED\x10\x02\x12\"\n\x1e\x46\x41ILURE_CODE_PERMISSION_DENIED\x10\x03\x12\x1a\n\x16\x46\x41ILURE_CODE_NOT_FOUND\x10\x04\x12\x1f\n\x1b\x46\x41ILURE_CODE_ALREADY_EXISTS\x10\x05\x12#\n\x1f\x46\x41ILURE_CODE_RESOURCE_EXHAUSTED\x10\x06\x12\"\n\x1e\x46\x41ILURE_CODE_DEADLINE_EXCEEDED\x10\x07\x12\x1a\n\x16\x46\x41ILURE_CODE_CANCELLED\x10\x08\x12\"\n\x1e\x46\x41ILURE_CODE_CONTRACT_MISMATCH\x10\x14\x12&\n\"FAILURE_CODE_RESUME_WINDOW_EXPIRED\x10\x15\x12\x19\n\x15\x46\x41ILURE_CODE_INTERNAL\x10(\x12\x1c\n\x18\x46\x41ILURE_CODE_UNAVAILABLE\x10)\x12&\n\"FAILURE_CODE_WORKSTATION_NOT_READY\x10*\x12\x1f\n\x1b\x46\x41ILURE_CODE_QUOTA_EXCEEDED\x10+*\xaf\x01\n\x0c\x43omputeClass\x12\x1d\n\x19\x43OMPUTE_CLASS_UNSPECIFIED\x10\x00\x12\x17\n\x13\x43OMPUTE_CLASS_SMALL\x10\x01\x12\x18\n\x14\x43OMPUTE_CLASS_MEDIUM\x10\x02\x12\x17\n\x13\x43OMPUTE_CLASS_LARGE\x10\x03\x12\x18\n\x14\x43OMPUTE_CLASS_GPU_L4\x10\n\x12\x1a\n\x16\x43OMPUTE_CLASS_GPU_A100\x10\x0b\x42W\n\x0b\x63om.perd.v1B\x0b\x43ommonProtoP\x01\xa2\x02\x03PXX\xaa\x02\x07Perd.V1\xca\x02\x07Perd\\V1\xe2\x02\x13Perd\\V1\\GPBMetadata\xea\x02\x08Perd::V1b\x06proto3')
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'perd.v1.common_pb2', _globals)
33
+ if not _descriptor._USE_C_DESCRIPTORS:
34
+ _globals['DESCRIPTOR']._loaded_options = None
35
+ _globals['DESCRIPTOR']._serialized_options = b'\n\013com.perd.v1B\013CommonProtoP\001\242\002\003PXX\252\002\007Perd.V1\312\002\007Perd\\V1\342\002\023Perd\\V1\\GPBMetadata\352\002\010Perd::V1'
36
+ _globals['_FAILURECODE']._serialized_start=978
37
+ _globals['_FAILURECODE']._serialized_end=1494
38
+ _globals['_COMPUTECLASS']._serialized_start=1497
39
+ _globals['_COMPUTECLASS']._serialized_end=1672
40
+ _globals['_FAILURE']._serialized_start=67
41
+ _globals['_FAILURE']._serialized_end=336
42
+ _globals['_WORKFLOWREF']._serialized_start=338
43
+ _globals['_WORKFLOWREF']._serialized_end=447
44
+ _globals['_ACTOR']._serialized_start=449
45
+ _globals['_ACTOR']._serialized_end=547
46
+ _globals['_AUDITSTAMP']._serialized_start=550
47
+ _globals['_AUDITSTAMP']._serialized_end=747
48
+ _globals['_PAGEREQUEST']._serialized_start=749
49
+ _globals['_PAGEREQUEST']._serialized_end=842
50
+ _globals['_PAGERESPONSE']._serialized_start=845
51
+ _globals['_PAGERESPONSE']._serialized_end=975
52
+ # @@protoc_insertion_point(module_scope)