digitalkin 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.
Files changed (59) hide show
  1. digitalkin/__init__.py +18 -0
  2. digitalkin/__version__.py +11 -0
  3. digitalkin/grpc/__init__.py +31 -0
  4. digitalkin/grpc/_base_server.py +488 -0
  5. digitalkin/grpc/module_server.py +233 -0
  6. digitalkin/grpc/module_servicer.py +304 -0
  7. digitalkin/grpc/registry_server.py +63 -0
  8. digitalkin/grpc/registry_servicer.py +451 -0
  9. digitalkin/grpc/utils/exceptions.py +33 -0
  10. digitalkin/grpc/utils/factory.py +178 -0
  11. digitalkin/grpc/utils/models.py +169 -0
  12. digitalkin/grpc/utils/types.py +24 -0
  13. digitalkin/logger.py +17 -0
  14. digitalkin/models/__init__.py +11 -0
  15. digitalkin/models/module/__init__.py +5 -0
  16. digitalkin/models/module/module.py +31 -0
  17. digitalkin/models/services/__init__.py +6 -0
  18. digitalkin/models/services/cost.py +53 -0
  19. digitalkin/models/services/storage.py +10 -0
  20. digitalkin/modules/__init__.py +7 -0
  21. digitalkin/modules/_base_module.py +177 -0
  22. digitalkin/modules/archetype_module.py +14 -0
  23. digitalkin/modules/job_manager.py +158 -0
  24. digitalkin/modules/tool_module.py +14 -0
  25. digitalkin/modules/trigger_module.py +14 -0
  26. digitalkin/py.typed +0 -0
  27. digitalkin/services/__init__.py +28 -0
  28. digitalkin/services/agent/__init__.py +6 -0
  29. digitalkin/services/agent/agent_strategy.py +22 -0
  30. digitalkin/services/agent/default_agent.py +16 -0
  31. digitalkin/services/cost/__init__.py +6 -0
  32. digitalkin/services/cost/cost_strategy.py +15 -0
  33. digitalkin/services/cost/default_cost.py +13 -0
  34. digitalkin/services/default_service.py +13 -0
  35. digitalkin/services/development_service.py +10 -0
  36. digitalkin/services/filesystem/__init__.py +6 -0
  37. digitalkin/services/filesystem/default_filesystem.py +29 -0
  38. digitalkin/services/filesystem/filesystem_strategy.py +31 -0
  39. digitalkin/services/identity/__init__.py +6 -0
  40. digitalkin/services/identity/default_identity.py +15 -0
  41. digitalkin/services/identity/identity_strategy.py +12 -0
  42. digitalkin/services/registry/__init__.py +6 -0
  43. digitalkin/services/registry/default_registry.py +13 -0
  44. digitalkin/services/registry/registry_strategy.py +17 -0
  45. digitalkin/services/service_provider.py +27 -0
  46. digitalkin/services/snapshot/__init__.py +6 -0
  47. digitalkin/services/snapshot/default_snapshot.py +39 -0
  48. digitalkin/services/snapshot/snapshot_strategy.py +31 -0
  49. digitalkin/services/storage/__init__.py +6 -0
  50. digitalkin/services/storage/default_storage.py +91 -0
  51. digitalkin/services/storage/grpc_storage.py +207 -0
  52. digitalkin/services/storage/storage_strategy.py +42 -0
  53. digitalkin/utils/__init__.py +1 -0
  54. digitalkin/utils/arg_parser.py +136 -0
  55. digitalkin-0.1.1.dist-info/METADATA +588 -0
  56. digitalkin-0.1.1.dist-info/RECORD +59 -0
  57. digitalkin-0.1.1.dist-info/WHEEL +5 -0
  58. digitalkin-0.1.1.dist-info/licenses/LICENSE +430 -0
  59. digitalkin-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,451 @@
1
+ """Registry servicer implementation for DigitalKin.
2
+
3
+ This module provides the gRPC service implementation for the Module Registry,
4
+ which handles registration, deregistration, discovery, and status management
5
+ of DigitalKin modules.
6
+ """
7
+
8
+ import logging
9
+ from collections.abc import Iterator
10
+ from enum import Enum
11
+
12
+ import grpc
13
+ from digitalkin_proto.digitalkin.module_registry.v2 import (
14
+ discover_pb2,
15
+ metadata_pb2,
16
+ module_registry_service_pb2_grpc,
17
+ registration_pb2,
18
+ status_pb2,
19
+ )
20
+ from pydantic import BaseModel
21
+ from typing_extensions import Self
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class ExtendedEnum(Enum):
27
+ """Tool enum class."""
28
+
29
+ @classmethod
30
+ def list(cls) -> list:
31
+ """Classmethod to generate a list of enum values.
32
+
33
+ Returns:
34
+ list: Enum members' values
35
+ """
36
+ return [c.value for c in cls]
37
+
38
+
39
+ class ModuleStatus(ExtendedEnum):
40
+ """Describe a Module current status.
41
+
42
+ Represents the possible states a module can be in during its lifecycle.
43
+ """
44
+
45
+ # RUNNING: Module alive.
46
+ RUNNING = 0
47
+ # IDLE: Module waiting for an event / update.
48
+ IDLE = 1
49
+ # ENDED: Module signals the end of task or have been killed.
50
+ ENDED = 2
51
+
52
+
53
+ class Tag(BaseModel):
54
+ """Words representing a module capabilities.
55
+
56
+ Used for module discovery and categorization.
57
+ """
58
+
59
+ # tag: Describe a Module function.
60
+ tag: str
61
+
62
+ def to_proto(self) -> metadata_pb2.Tag:
63
+ """Convert Tag object from Pydantic to Proto.
64
+
65
+ Returns:
66
+ metadata_pb2.Tag: The protobuf representation of this tag.
67
+ """
68
+ return metadata_pb2.Tag(tag=self.tag)
69
+
70
+
71
+ class Metadata(BaseModel):
72
+ """Different informations to index and describe a module.
73
+
74
+ Contains human-readable information about a module and its capabilities.
75
+ """
76
+
77
+ # name: Module's name
78
+ name: str
79
+ # tags: List of tag to describe a module functionalities.
80
+ tags: list[Tag]
81
+ # description: Module's description for search and indexing
82
+ description: str | None
83
+
84
+ def to_proto(self) -> metadata_pb2.Metadata:
85
+ """Convert Metadata object from Pydantic to Proto.
86
+
87
+ Returns:
88
+ metadata_pb2.Metadata: The protobuf representation of this metadata.
89
+ """
90
+ return metadata_pb2.Metadata(
91
+ name=self.name, tags=(t.to_proto() for t in self.tags), description=self.description
92
+ )
93
+
94
+ @classmethod
95
+ def from_proto(cls, request_metadata: metadata_pb2.Metadata) -> Self:
96
+ """Create Metadata object from Proto message.
97
+
98
+ Args:
99
+ request_metadata: The protobuf metadata to convert.
100
+
101
+ Returns:
102
+ Metadata: The Pydantic model representation of the metadata.
103
+ """
104
+ return cls(
105
+ name=request_metadata.name,
106
+ tags=[Tag(tag=t.tag) for t in request_metadata.tags],
107
+ description=request_metadata.description,
108
+ )
109
+
110
+
111
+ class RegistryModule(BaseModel):
112
+ """Module's technical representation to index, search and monitor.
113
+
114
+ Contains all the information needed to identify, locate and communicate
115
+ with a module in the system.
116
+ """
117
+
118
+ # module_id: Id of the module
119
+ module_id: str
120
+ # module_type: Type of the module (trigger, tool, kin, view)
121
+ module_type: str
122
+ # address: Address used to communicate with the module
123
+ address: str
124
+ # port: Port used to communicate with the module
125
+ port: int
126
+ # version: Current module version.
127
+ version: str
128
+ # metadata: user defined module name, description and tags
129
+ metadata: Metadata
130
+ # status: Representation of the Module current state (running, idle, ended...).
131
+ status: ModuleStatus
132
+ # message: (Optional) Details about the status.
133
+ message: str | None
134
+
135
+ def to_proto(self) -> discover_pb2.DiscoverInfoResponse:
136
+ """Convert RegistryModule object from Pydantic to Proto.
137
+
138
+ Returns:
139
+ metadata_pb2.Metadata: The protobuf representation of this metadata.
140
+ """
141
+ return discover_pb2.DiscoverInfoResponse(
142
+ module_id=self.module_id,
143
+ module_type=self.module_type,
144
+ address=self.address,
145
+ port=self.port,
146
+ version=self.version,
147
+ metadata=self.metadata.to_proto(),
148
+ )
149
+
150
+
151
+ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceServicer):
152
+ """Implementation of the ModuleRegistryService.
153
+
154
+ This servicer handles the registration, deregistration, and discovery of modules.
155
+ It maintains an in-memory registry of all active modules and their metadata.
156
+
157
+ Attributes:
158
+ registered_modules: Dictionary mapping module_id to RegistryModule objects.
159
+ """
160
+
161
+ registered_modules: dict[str, RegistryModule]
162
+
163
+ def __init__(self) -> None:
164
+ """Initialize the registry servicer with an empty module registry."""
165
+ self.registered_modules = {} # TODO replace with a database
166
+
167
+ def RegisterModule( # noqa: N802
168
+ self,
169
+ request: registration_pb2.RegisterRequest,
170
+ context: grpc.ServicerContext,
171
+ ) -> registration_pb2.RegisterResponse:
172
+ """Register a module with the registry.
173
+
174
+ Adds a new module to the registry with its connection information and metadata.
175
+ Fails if a module with the same ID is already registered.
176
+
177
+ Args:
178
+ request: The register request containing module info and address.
179
+ context: The gRPC context for setting status codes and details.
180
+
181
+ Returns:
182
+ registration_pb2.RegisterResponse: A response indicating success or failure.
183
+ """
184
+ module_id = request.module_id
185
+ logger.info("Registering module: %s", module_id)
186
+
187
+ # Check if module is already registered
188
+ if module_id in self.registered_modules:
189
+ message = f"Module '{module_id}' already registered"
190
+ logger.warning(message)
191
+
192
+ context.set_code(grpc.StatusCode.ALREADY_EXISTS)
193
+ context.set_details(message)
194
+ return registration_pb2.RegisterResponse(success=False)
195
+
196
+ # Store the module info with address
197
+ self.registered_modules[module_id] = RegistryModule(
198
+ module_id=request.module_id,
199
+ module_type=request.module_type,
200
+ address=request.address,
201
+ port=request.port,
202
+ version=request.version,
203
+ metadata=Metadata.from_proto(request.metadata),
204
+ status=ModuleStatus.RUNNING,
205
+ message=None,
206
+ )
207
+
208
+ logger.info("Module %s registered at %s:%d", module_id, request.address, request.port)
209
+ return registration_pb2.RegisterResponse(success=True)
210
+
211
+ def DeregisterModule( # noqa: N802
212
+ self,
213
+ request: registration_pb2.DeregisterRequest,
214
+ context: grpc.ServicerContext,
215
+ ) -> registration_pb2.DeregisterResponse:
216
+ """Deregister a module from the registry.
217
+
218
+ Removes a module from the registry based on its ID.
219
+ Fails if the specified module is not found in the registry.
220
+
221
+ Args:
222
+ request: The deregister request containing the module ID.
223
+ context: The gRPC context for setting status codes and details.
224
+
225
+ Returns:
226
+ registration_pb2.DeregisterResponse: A response indicating success or failure.
227
+ """
228
+ module_id = request.module_id
229
+ logger.info("Deregistering module: %s", module_id)
230
+
231
+ # Check if module exists in registry
232
+ if module_id not in self.registered_modules:
233
+ message = f"Module {module_id} not found in registry"
234
+ logger.warning(message)
235
+
236
+ context.set_code(grpc.StatusCode.NOT_FOUND)
237
+ context.set_details(message)
238
+ return registration_pb2.DeregisterResponse()
239
+
240
+ # Remove the module
241
+ del self.registered_modules[module_id]
242
+
243
+ logger.info("Module %s deregistered", module_id)
244
+ return registration_pb2.DeregisterResponse(success=True)
245
+
246
+ def DiscoverInfoModule( # noqa: N802
247
+ self,
248
+ request: discover_pb2.DiscoverInfoRequest,
249
+ context: grpc.ServicerContext,
250
+ ) -> discover_pb2.DiscoverInfoResponse:
251
+ """Discover detailed information about a specific module.
252
+
253
+ Retrieves complete information about a module based on its ID.
254
+
255
+ Args:
256
+ request: The discover request containing the module ID.
257
+ context: The gRPC context (unused).
258
+
259
+ Returns:
260
+ discover_pb2.DiscoverInfoResponse: A response containing the module's information.
261
+ """
262
+ logger.info("Discovering module: %s", request.module_id)
263
+
264
+ # Check if module exists in registry
265
+ if request.module_id not in self.registered_modules:
266
+ message = f"Module {request.module_id} not found in registry"
267
+ logger.warning(message)
268
+ context.set_code(grpc.StatusCode.NOT_FOUND)
269
+ context.set_details(message)
270
+ return discover_pb2.DiscoverInfoResponse()
271
+ return self.registered_modules[request.module_id].to_proto()
272
+
273
+ def DiscoverSearchModule( # noqa: N802
274
+ self,
275
+ request: discover_pb2.DiscoverSearchRequest,
276
+ context: grpc.ServicerContext, # noqa: ARG002
277
+ ) -> discover_pb2.DiscoverSearchResponse:
278
+ """Discover modules based on the specified criteria.
279
+
280
+ Searches for modules that match the provided filters such as name,
281
+ type, and tags.
282
+
283
+ Args:
284
+ request: The discover request containing search criteria.
285
+ context: The gRPC context (unused).
286
+
287
+ Returns:
288
+ discover_pb2.DiscoverSearchResponse: A response containing matching modules.
289
+ """
290
+ logger.info("Discovering modules with criteria:")
291
+
292
+ # Start with all modules
293
+ results = list(self.registered_modules.values())
294
+ logger.info("%s", list(results))
295
+ # Filter by name if specified
296
+ if request.name:
297
+ logger.info("\tname %s", request.name)
298
+ results = [m for m in results if request.name in m.metadata.name]
299
+
300
+ # Filter by type if specified
301
+ if request.module_type:
302
+ logger.info("\tmodule_type %s", request.module_type)
303
+ results = [m for m in results if m.module_type == request.module_type]
304
+
305
+ # Filter by tags if specified
306
+ if request.tags:
307
+ logger.info("\ttags %s", request.tags)
308
+ results = [m for m in results if any(tag in m.metadata.tags for tag in request.tags)]
309
+
310
+ # Filter by description if specified
311
+ """
312
+ if request.description:
313
+ results = [m for m in results if request.description in m.metadata.description]
314
+ """
315
+
316
+ logger.info("Found %d matching modules", len(results))
317
+ return discover_pb2.DiscoverSearchResponse(modules=[r.to_proto() for r in results])
318
+
319
+ def GetModuleStatus( # noqa: N802
320
+ self,
321
+ request: status_pb2.ModuleStatusRequest,
322
+ context: grpc.ServicerContext,
323
+ ) -> status_pb2.ModuleStatusResponse:
324
+ """Query a specific module's status.
325
+
326
+ Retrieves the current status of a module based on its ID.
327
+
328
+ Args:
329
+ request: The status request containing the module ID.
330
+ context: The gRPC context (unused).
331
+
332
+ Returns:
333
+ status_pb2.ModuleStatusResponse: A response containing the module's status.
334
+ """
335
+ logger.info("Getting status for module: %s", request.module_id)
336
+
337
+ # Check if module exists in registry
338
+ if request.module_id not in self.registered_modules:
339
+ message = f"Module {request.module_id} not found in registry"
340
+ logger.warning(message)
341
+ context.set_code(grpc.StatusCode.NOT_FOUND)
342
+ context.set_details(message)
343
+ return status_pb2.ModuleStatusResponse()
344
+
345
+ module = self.registered_modules[request.module_id]
346
+ return status_pb2.ModuleStatusResponse(module_id=module.module_id, status=module.status.name)
347
+
348
+ def ListModuleStatus( # noqa: N802
349
+ self,
350
+ request: status_pb2.ListModulesStatusRequest,
351
+ context: grpc.ServicerContext,
352
+ ) -> status_pb2.ListModulesStatusResponse:
353
+ """Get a paginated list of registered modules and their statuses.
354
+
355
+ Returns a subset of registered modules based on pagination parameters.
356
+
357
+ Args:
358
+ request: The request containing offset and list_size for pagination.
359
+ context: The gRPC context (unused).
360
+
361
+ Returns:
362
+ status_pb2.ListModulesStatusResponse: A response containing a list of module statuses.
363
+ """
364
+ logger.info("Getting registered modules with offset %d and limit %d", request.offset, request.list_size)
365
+ if request.offset > len(self.registered_modules):
366
+ message = f"Out of range {request.offset}"
367
+ logger.warning(message)
368
+ context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
369
+ context.set_details(message)
370
+ return status_pb2.ListModulesStatusResponse()
371
+
372
+ list_size = request.list_size
373
+ if list_size == 0:
374
+ list_size = len(self.registered_modules)
375
+
376
+ modules_statuses = [
377
+ status_pb2.ModuleStatusResponse(module_id=module.module_id, status=module.status.name)
378
+ for module in list(self.registered_modules.values())[request.offset : request.offset + list_size]
379
+ ]
380
+
381
+ logger.info("Found %d registered modules", len(modules_statuses))
382
+ return status_pb2.ListModulesStatusResponse(
383
+ list_size=len(modules_statuses),
384
+ modules_statuses=modules_statuses,
385
+ )
386
+
387
+ def GetAllModuleStatus( # noqa: N802
388
+ self,
389
+ request: status_pb2.GetAllModulesStatusRequest, # noqa: ARG002
390
+ context: grpc.ServicerContext, # noqa: ARG002
391
+ ) -> Iterator[status_pb2.ModuleStatusResponse]:
392
+ """Get all registered modules via a stream.
393
+
394
+ Streams the status of all registered modules one by one.
395
+
396
+ Args:
397
+ request: The get all modules request (unused).
398
+ context: The gRPC context (unused).
399
+
400
+ Yields:
401
+ status_pb2.ModuleStatusResponse: Responses containing individual module statuses.
402
+ """
403
+ logger.info("Streaming all %d registered modules", len(self.registered_modules))
404
+ for module in self.registered_modules.values():
405
+ yield status_pb2.ModuleStatusResponse(
406
+ module_id=module.module_id,
407
+ status=module.status.name,
408
+ )
409
+
410
+ def UpdateModuleStatus( # noqa: N802
411
+ self,
412
+ request: status_pb2.UpdateStatusRequest,
413
+ context: grpc.ServicerContext,
414
+ ) -> status_pb2.UpdateStatusResponse:
415
+ """Update the status of a registered module.
416
+
417
+ Changes the current status of a module based on the provided request.
418
+ Fails if the specified module is not found in the registry.
419
+
420
+ Args:
421
+ request: The update status request with module ID and new status.
422
+ context: The gRPC context (unused).
423
+
424
+ Returns:
425
+ status_pb2.UpdateStatusResponse: A response indicating success or failure.
426
+ """
427
+ module_id = request.module_id
428
+ logger.info("Updating status for module: %s to %s", module_id, request.status)
429
+
430
+ # Check if module exists in registry
431
+ if request.module_id not in self.registered_modules:
432
+ message = f"Module {request.module_id} not found in registry"
433
+ logger.warning(message)
434
+ context.set_code(grpc.StatusCode.NOT_FOUND)
435
+ context.set_details(message)
436
+ return status_pb2.UpdateStatusResponse()
437
+
438
+ # Check if module status is correct
439
+ if request.status not in ModuleStatus.list() or request.status is None:
440
+ message = f"ModuleStatus {request.status} is unknonw, please check the requested status"
441
+ logger.warning(message)
442
+ context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
443
+ context.set_details(message)
444
+ return status_pb2.UpdateStatusResponse(success=False)
445
+
446
+ # Update module status
447
+ module_info = self.registered_modules[module_id]
448
+ module_info.status = ModuleStatus(request.status)
449
+
450
+ logger.info("Status for module %s updated to %s", module_id, request.status)
451
+ return status_pb2.UpdateStatusResponse(success=True)
@@ -0,0 +1,33 @@
1
+ """Exceptions for the DigitalKin gRPC package."""
2
+
3
+
4
+ class DigitalKinError(Exception):
5
+ """Base exception for all DigitalKin errors."""
6
+
7
+
8
+ class ServerError(DigitalKinError):
9
+ """Base class for server-related errors."""
10
+
11
+
12
+ class ConfigurationError(ServerError):
13
+ """Error related to server configuration."""
14
+
15
+
16
+ class ServicerError(ServerError):
17
+ """Error related to servicer operations."""
18
+
19
+
20
+ class SecurityError(ServerError):
21
+ """Error related to security configuration."""
22
+
23
+
24
+ class ServerStateError(ServerError):
25
+ """Error related to server state (e.g., already started, not started)."""
26
+
27
+
28
+ class ReflectionError(ServerError):
29
+ """Error related to gRPC reflection service."""
30
+
31
+
32
+ class HealthCheckError(ServerError):
33
+ """Error related to gRPC health check service."""
@@ -0,0 +1,178 @@
1
+ """Factory functions for creating gRPC servers."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from digitalkin.grpc.module_server import ModuleServer
7
+ from digitalkin.grpc.registry_server import RegistryServer
8
+ from digitalkin.grpc.utils.models import (
9
+ ModuleServerConfig,
10
+ RegistryServerConfig,
11
+ SecurityMode,
12
+ ServerCredentials,
13
+ ServerMode,
14
+ )
15
+ from digitalkin.modules._base_module import BaseModule
16
+
17
+
18
+ def create_module_server(
19
+ module: type[BaseModule],
20
+ host: str = "0.0.0.0", # noqa: S104
21
+ port: int = 50051,
22
+ max_workers: int = 10,
23
+ mode: str = "sync",
24
+ security: str = "insecure",
25
+ registry_address: str | None = None,
26
+ server_key_path: str | None = None,
27
+ server_cert_path: str | None = None,
28
+ root_cert_path: str | None = None,
29
+ server_options: list[tuple[str, Any]] | None = None,
30
+ ) -> ModuleServer:
31
+ """Create a new module server.
32
+
33
+ Args:
34
+ module: The module to serve.
35
+ host: The host address to bind to.
36
+ port: The port to listen on.
37
+ max_workers: Maximum number of workers for the thread pool (sync mode only).
38
+ mode: Server mode ("sync" or "async").
39
+ security: Security mode ("secure" or "insecure").
40
+ registry_address: Optional address of a registry server for auto-registration.
41
+ server_key_path: Path to server private key (required for secure mode).
42
+ server_cert_path: Path to server certificate (required for secure mode).
43
+ root_cert_path: Optional path to root certificate.
44
+ server_options: Additional server options.
45
+
46
+ Returns:
47
+ A configured ModuleServer instance.
48
+
49
+ Raises:
50
+ ValueError: If secure mode is requested but credentials are missing.
51
+ """
52
+ # Create configuration with credentials if needed
53
+ config = _create_server_config(
54
+ ModuleServerConfig,
55
+ host=host,
56
+ port=port,
57
+ max_workers=max_workers,
58
+ mode=mode,
59
+ security=security,
60
+ server_key_path=server_key_path,
61
+ server_cert_path=server_cert_path,
62
+ root_cert_path=root_cert_path,
63
+ server_options=server_options,
64
+ registry_address=registry_address,
65
+ )
66
+
67
+ # Create and return the server
68
+ return ModuleServer(module, config)
69
+
70
+
71
+ def create_registry_server(
72
+ host: str = "0.0.0.0", # noqa: S104
73
+ port: int = 50052,
74
+ max_workers: int = 10,
75
+ mode: str = "sync",
76
+ security: str = "insecure",
77
+ database_url: str | None = None,
78
+ server_key_path: str | None = None,
79
+ server_cert_path: str | None = None,
80
+ root_cert_path: str | None = None,
81
+ server_options: list[tuple[str, Any]] | None = None,
82
+ ) -> RegistryServer:
83
+ """Create a new registry server.
84
+
85
+ Args:
86
+ host: The host address to bind to.
87
+ port: The port to listen on.
88
+ max_workers: Maximum number of workers for the thread pool (sync mode only).
89
+ mode: Server mode ("sync" or "async").
90
+ security: Security mode ("secure" or "insecure").
91
+ database_url: Optional database URL for registry data storage.
92
+ server_key_path: Path to server private key (required for secure mode).
93
+ server_cert_path: Path to server certificate (required for secure mode).
94
+ root_cert_path: Optional path to root certificate.
95
+ server_options: Additional server options.
96
+
97
+ Returns:
98
+ A configured RegistryServer instance.
99
+
100
+ Raises:
101
+ ValueError: If secure mode is requested but credentials are missing.
102
+ """
103
+ # Create configuration with credentials if needed
104
+ config = _create_server_config(
105
+ RegistryServerConfig,
106
+ host=host,
107
+ port=port,
108
+ max_workers=max_workers,
109
+ mode=mode,
110
+ security=security,
111
+ server_key_path=server_key_path,
112
+ server_cert_path=server_cert_path,
113
+ root_cert_path=root_cert_path,
114
+ server_options=server_options,
115
+ database_url=database_url,
116
+ )
117
+
118
+ # Create and return the server
119
+ return RegistryServer(config)
120
+
121
+
122
+ def _create_server_config(
123
+ config_class: Any,
124
+ host: str,
125
+ port: int,
126
+ max_workers: int,
127
+ mode: str,
128
+ security: str,
129
+ server_key_path: str | None,
130
+ server_cert_path: str | None,
131
+ root_cert_path: str | None,
132
+ server_options: list[tuple[str, Any]] | None,
133
+ **kwargs,
134
+ ) -> ModuleServerConfig | RegistryServerConfig:
135
+ """Create a server configuration with appropriate settings.
136
+
137
+ Args:
138
+ config_class: The configuration class to instantiate.
139
+ host: The host address.
140
+ port: The port number.
141
+ max_workers: Maximum number of workers.
142
+ mode: Server mode.
143
+ security: Security mode.
144
+ server_key_path: Path to server key.
145
+ server_cert_path: Path to server certificate.
146
+ root_cert_path: Path to root certificate.
147
+ server_options: Additional server options.
148
+ **kwargs: Additional configuration parameters.
149
+
150
+ Returns:
151
+ A server configuration instance.
152
+
153
+ Raises:
154
+ ValueError: If secure mode is requested but credentials are missing.
155
+ """
156
+ # Create basic config
157
+ config_params = {
158
+ "host": host,
159
+ "port": port,
160
+ "max_workers": max_workers,
161
+ "mode": ServerMode(mode),
162
+ "security": SecurityMode(security),
163
+ "server_options": server_options or [],
164
+ **kwargs,
165
+ }
166
+
167
+ # Add credentials if secure mode
168
+ if security == "secure":
169
+ if not server_key_path or not server_cert_path:
170
+ raise ValueError("Server key and certificate paths are required for secure mode")
171
+
172
+ config_params["credentials"] = ServerCredentials(
173
+ server_key_path=Path(server_key_path),
174
+ server_cert_path=Path(server_cert_path),
175
+ root_cert_path=Path(root_cert_path) if root_cert_path else None,
176
+ )
177
+
178
+ return config_class(**config_params)