spakky-grpc¶
gRPC 서비스 컨트롤러 통합 — code-first, 타입 안전 프로토콜 생성
스테레오타입¶
gRPC controller stereotype for service grouping.
Provides the @GrpcController stereotype for marking classes as gRPC service controllers with automatic service registration and protobuf package configuration.
GrpcController(package, service_name=None, *, name='', scope=Scope.SINGLETON)
dataclass
¶
Bases: Controller
Stereotype for gRPC service controllers.
Marks a class as a gRPC service controller with automatic service registration. Methods decorated with @rpc will be registered as gRPC service methods.
Attributes:
| Name | Type | Description |
|---|---|---|
package |
str
|
Protobuf package name for the service. |
service_name |
str | None
|
gRPC service name. Defaults to the class name if not provided. |
package
instance-attribute
¶
Protobuf package name for the service.
service_name = None
class-attribute
instance-attribute
¶
gRPC service name. Defaults to the class name.
__call__(obj)
¶
Apply the gRPC controller stereotype to a class.
Automatically generates the service name from the class name if not provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
type[T]
|
The class to decorate. |
required |
Returns:
| Type | Description |
|---|---|
type[T]
|
The decorated class registered as a Pod. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/stereotypes/grpc_controller.py
데코레이터¶
RPC method decorator for gRPC service methods.
Provides the @rpc decorator for marking controller methods as gRPC service methods with support for all four gRPC streaming patterns.
RpcMethodType
¶
Bases: StrEnum
gRPC method streaming patterns.
Attributes:
| Name | Type | Description |
|---|---|---|
UNARY |
Single request, single response. |
|
SERVER_STREAMING |
Single request, stream of responses. |
|
CLIENT_STREAMING |
Stream of requests, single response. |
|
BIDI_STREAMING |
Stream of requests, stream of responses. |
Rpc(method_type=RpcMethodType.UNARY, request_type=None, response_type=None)
dataclass
¶
Bases: FunctionAnnotation
Function annotation for marking methods as gRPC RPC endpoints.
Stores RPC configuration including the streaming pattern and request/response type metadata.
Attributes:
| Name | Type | Description |
|---|---|---|
method_type |
RpcMethodType
|
gRPC streaming pattern for this method. |
request_type |
type[BaseModel] | None
|
Request message model, or |
response_type |
type[BaseModel] | None
|
Response message model, or |
__call__(obj)
¶
Annotate a method as an RPC endpoint.
Extracts request and response types from type hints if not explicitly provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Callable[..., T]
|
The method to annotate. |
required |
Returns:
| Type | Description |
|---|---|
Callable[..., T]
|
The annotated method. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/decorators/rpc.py
rpc(method_type=RpcMethodType.UNARY, request_type=None, response_type=None)
¶
Decorator to mark a controller method as a gRPC RPC endpoint.
Attaches RPC configuration to the method including streaming pattern and message type metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method_type
|
RpcMethodType
|
gRPC streaming pattern for this method. |
UNARY
|
request_type
|
type[BaseModel] | None
|
Request message model. Auto-extracted from type hints if not provided. |
None
|
response_type
|
type[BaseModel] | None
|
Response message model. Auto-extracted from type hints if not provided. |
None
|
Returns:
| Type | Description |
|---|---|
Callable[[Callable[..., T]], Callable[..., T]]
|
A decorator function that attaches the RPC configuration. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/decorators/rpc.py
어노테이션¶
Protobuf field annotation for pydantic-based message definitions.
Provides the ProtoField annotation for specifying protobuf field numbers
on pydantic BaseModel fields using the Annotated type hint pattern.
Example::
from pydantic import BaseModel
from typing import Annotated
class HelloRequest(BaseModel):
name: Annotated[str, ProtoField(number=1)]
greeting_count: Annotated[int, ProtoField(number=2)]
ProtoField(number)
dataclass
¶
Protobuf field number annotation for pydantic model fields.
Used with typing.Annotated to specify the protobuf field number
for a pydantic BaseModel field, enabling code-first protobuf
message definition. The annotation is read at runtime from the
model's model_fields[name].metadata tuple.
Attributes:
| Name | Type | Description |
|---|---|---|
number |
int
|
The protobuf field number. Must be a positive integer. |
number
instance-attribute
¶
The protobuf field number.
Handler¶
Generic RPC handler for code-first gRPC service dispatch.
Routes incoming gRPC calls to @GrpcController methods by matching
the fully-qualified method name, performing protobuf ↔ pydantic
BaseModel conversion via the google.protobuf.json_format
bridge.
GrpcServiceHandler(*, controller_type, package, service_name, container, application_context, registry)
¶
Bases: GenericRpcHandler
Generic handler dispatching gRPC calls to @GrpcController methods.
For each @rpc-decorated method, builds a grpc.RpcMethodHandler
with serialiser/deserialiser that convert between protobuf wire
format and pydantic BaseModel instances.
Attributes:
| Name | Type | Description |
|---|---|---|
_full_service_name |
str
|
Fully-qualified |
_controller_type |
type
|
The |
_container |
IContainer
|
IoC container for obtaining fresh controller instances. |
_application_context |
IApplicationContext
|
Application context for request-scoped isolation. |
_registry |
DescriptorRegistry
|
Descriptor registry for message class lookup. |
_handlers |
dict[str, RpcMethodHandler]
|
Pre-built map of |
Initialise the handler and pre-build per-method dispatchers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
controller_type
|
type
|
The |
required |
package
|
str
|
Protobuf package name. |
required |
service_name
|
str
|
gRPC service name. |
required |
container
|
IContainer
|
IoC container for obtaining controller instances. |
required |
application_context
|
IApplicationContext
|
Application context for request isolation. |
required |
registry
|
DescriptorRegistry
|
Descriptor registry for message class lookup. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/handler.py
service(handler_call_details)
¶
Resolve an RPC method handler for the incoming call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler_call_details
|
HandlerCallDetails
|
Describes the incoming RPC. |
required |
Returns:
| Type | Description |
|---|---|
RpcMethodHandler | None
|
The matched handler, or |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/handler.py
클라이언트¶
Typed caller for a code-first @GrpcController service.
Without this, a caller has to rebuild the protobuf descriptor from its own copy of the pydantic models. Because field numbers are derived from field names, a single renamed field on either side moves that field's wire number and the value silently disappears from the decoded message.
:class:GrpcClient removes the second copy: it takes the controller class
itself — the same declaration the server registers — builds the descriptor
from it, and derives every callable's serialiser from that descriptor. The
RPC method is identified by referencing the controller method rather than
by spelling its name in a string, so a renamed method breaks at import time
instead of at call time.
GrpcClient(channel, controller_type, registry=None)
¶
Builds gRPC callables for one @GrpcController service.
The multicallable return types are written as strings because the gRPC
runtime classes are only generic in the type stubs — subscripting them at
class-definition time raises TypeError.
Attributes:
| Name | Type | Description |
|---|---|---|
registry |
DescriptorRegistry
|
Registry holding the descriptor built from the controller. |
Register the controller's descriptor and bind the client to a channel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
Channel
|
Open channel to the server hosting the service. |
required |
controller_type
|
type
|
The |
required |
registry
|
DescriptorRegistry | None
|
Registry to compile the descriptor into. |
None
|
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/client.py
unary_unary(method)
¶
Build a callable for a single-request, single-response method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
Callable[[SelfT, RequestT], Coroutine[object, object, ResponseT]]
|
The |
required |
Returns:
| Type | Description |
|---|---|
UnaryUnaryMultiCallable[RequestT, ResponseT]
|
A multicallable accepting the request model and awaiting the |
UnaryUnaryMultiCallable[RequestT, ResponseT]
|
response model. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/client.py
unary_stream(method)
¶
Build a callable for a server-streaming method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
Callable[[SelfT, RequestT], AsyncIterator[ResponseT]]
|
The |
required |
Returns:
| Type | Description |
|---|---|
UnaryStreamMultiCallable[RequestT, ResponseT]
|
A multicallable accepting the request model and yielding response |
UnaryStreamMultiCallable[RequestT, ResponseT]
|
models. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/client.py
stream_unary(method)
¶
Build a callable for a client-streaming method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
Callable[[SelfT, AsyncIterator[RequestT]], Coroutine[object, object, ResponseT]]
|
The |
required |
Returns:
| Type | Description |
|---|---|
StreamUnaryMultiCallable[RequestT, ResponseT]
|
A multicallable accepting an async iterator of request models and |
StreamUnaryMultiCallable[RequestT, ResponseT]
|
awaiting the response model. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/client.py
stream_stream(method)
¶
Build a callable for a bidirectional-streaming method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
Callable[[SelfT, AsyncIterator[RequestT]], AsyncIterator[ResponseT]]
|
The |
required |
Returns:
| Type | Description |
|---|---|
StreamStreamMultiCallable[RequestT, ResponseT]
|
A multicallable accepting an async iterator of request models and |
StreamStreamMultiCallable[RequestT, ResponseT]
|
yielding response models. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/client.py
메시지 변환¶
protobuf ↔ pydantic BaseModel translation shared by server and client.
Both the server-side generic handler and the client-side helper must encode the wire payload identically: a mismatch between the two directions silently drops or corrupts fields. Keeping the translation in one module makes that symmetry structural rather than a convention two call sites have to remember.
Every conversion routes through the google.protobuf.json_format bridge,
using the message classes compiled from the shared
:class:~spakky.plugins.grpc.schema.registry.DescriptorRegistry so that field
numbers always come from the same descriptor the peer uses.
basemodel_to_protobuf(model, message_class)
¶
Convert a pydantic BaseModel instance into a protobuf Message.
The model is serialised to JSON via pydantic's v2 model_dump_json API
and parsed into a protobuf Message by json_format.Parse. None
values from optional fields are emitted as JSON null which
json_format treats as "field unset" for proto3 optional fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
BaseModel
|
The pydantic |
required |
message_class
|
type[Message]
|
The target protobuf message class. |
required |
Returns:
| Type | Description |
|---|---|
Message
|
A populated protobuf |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/codec.py
protobuf_to_basemodel(message, model_type)
¶
Convert a protobuf Message into a pydantic BaseModel instance.
The message is serialised to JSON with preserving_proto_field_name=True
so field names round-trip unchanged into model_validate_json.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
Message
|
The protobuf message. |
required |
model_type
|
type[BaseModelT]
|
The target |
required |
Returns:
| Type | Description |
|---|---|
BaseModelT
|
An instance of |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/codec.py
serializer_for(registry, full_name)
¶
Return a BaseModel → wire-bytes serialiser for a registered type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DescriptorRegistry
|
Registry holding the compiled descriptor for full_name. |
required |
full_name
|
str
|
Fully-qualified protobuf message name. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[BaseModel], bytes]
|
A callable encoding a |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/codec.py
deserializer_for(registry, full_name, model_type)
¶
Return a wire-bytes → BaseModel deserialiser for a registered type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
DescriptorRegistry
|
Registry holding the compiled descriptor for full_name. |
required |
full_name
|
str
|
Fully-qualified protobuf message name. |
required |
model_type
|
type[BaseModelT]
|
The |
required |
Returns:
| Type | Description |
|---|---|
Callable[[bytes], BaseModelT]
|
A callable decoding protobuf wire bytes into model_type. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/codec.py
인증 경계¶
gRPC auth boundary helpers.
seed_grpc_auth_context(*, container, application_context, context, operation)
¶
Authenticate gRPC metadata and seed AuthContext when credentials exist.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/auth.py
설정¶
gRPC plugin configuration.
SPAKKY_GRPC_CONFIG_ENV_PREFIX = 'SPAKKY_GRPC_'
module-attribute
¶
Environment prefix for gRPC plugin settings.
GrpcServerOptions = dict[str, int | str]
¶
gRPC channel arguments keyed by their documented grpc.* option name.
GrpcConfig()
¶
Bases: BaseSettings
Configuration for the gRPC server integration.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/config.py
bind_addresses = ()
class-attribute
instance-attribute
¶
Addresses the server listens on, each in host:port form.
server_options = {}
class-attribute
instance-attribute
¶
Channel arguments forwarded verbatim to grpc.aio.server(options=...).
Any documented gRPC channel argument is accepted, so keepalive intervals
(grpc.keepalive_time_ms), message size caps
(grpc.max_receive_message_length) and connection lifetime limits
(grpc.max_connection_age_ms) are all tunable without this plugin
enumerating them one by one.
tls_certificate_chain_file = None
class-attribute
instance-attribute
¶
PEM file with the server certificate chain. None means TLS is off.
tls_private_key_file = None
class-attribute
instance-attribute
¶
PEM file with the private key matching the certificate chain.
tls_client_ca_file = None
class-attribute
instance-attribute
¶
PEM file of authorities trusted to sign client certificates (mutual TLS).
require_client_auth = False
class-attribute
instance-attribute
¶
Whether clients must present a certificate signed by tls_client_ca_file.
health_service_enabled = True
class-attribute
instance-attribute
¶
Whether to expose the standard grpc.health.v1.Health service.
reflection_service_enabled = True
class-attribute
instance-attribute
¶
Whether to expose the standard server reflection service.
전송 보안¶
Transport security credentials for the gRPC listener.
Translates the PEM file paths declared in :class:GrpcConfig into a
grpc.ServerCredentials object. A half-configured key pair is rejected
up front rather than silently degrading to a plaintext listener, because a
deployment that asked for TLS must never end up serving cleartext.
build_server_credentials(config)
¶
Build listener credentials from the configured PEM files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
GrpcConfig
|
Plugin configuration carrying the TLS file paths. |
required |
Returns:
| Type | Description |
|---|---|
ServerCredentials | None
|
Credentials for a TLS listener, or |
ServerCredentials | None
|
security setting is present and the listener stays plaintext. |
Raises:
| Type | Description |
|---|---|
IncompleteTlsCredentialsError
|
If some transport security setting is present but the certificate chain or private key is missing. |
MissingClientCertificateAuthorityError
|
If client certificate authentication is required without a client CA file. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/credentials.py
서버 명세¶
Deferred gRPC server configuration.
grpc.aio.server() binds to the current event loop at creation time, so
the real server must be instantiated on the event loop that eventually
runs it. :class:GrpcServerSpec collects everything needed to build the
server (interceptors, generic handlers, bind targets, channel arguments,
standard-service registrations) during post-processing, and
:class:GrpcServerService materialises it at start_async time on the
correct loop.
ServerServiceRegistrar = Callable[[grpc.aio.Server], Awaitable[None]]
¶
Callback attaching a service to the server once it has been instantiated.
The standard health and reflection services ship as servicers that must be
registered on a concrete grpc.aio.Server, which only exists after
:meth:GrpcServerSpec.build_async. Collecting them as callbacks keeps that
registration on the same deferred timeline as everything else in the spec.
They are awaitable because reporting an initial health status goes through the
servicer's async API on the serving loop.
GrpcBindTarget(address, credentials)
dataclass
¶
One listener address together with the credentials protecting it.
grpc.ServerCredentials is an opaque handle from the gRPC C core, so
this pairing is a plain dataclass rather than a pydantic model.
Attributes:
| Name | Type | Description |
|---|---|---|
address |
str
|
Listener address in |
credentials |
ServerCredentials | None
|
TLS credentials, or |
GrpcServerSpec(options=None)
¶
Configuration collected during post-processing for deferred server creation.
Attributes:
| Name | Type | Description |
|---|---|---|
handlers |
list[GenericRpcHandler]
|
Generic RPC handlers to register on the server. |
interceptors |
list[ServerInterceptor]
|
Server interceptors to apply at creation time. |
bind_targets |
list[GrpcBindTarget]
|
Listener addresses with their transport credentials. |
service_registrars |
list[ServerServiceRegistrar]
|
Callbacks attaching standard services once the server object exists. |
options |
tuple[tuple[str, int | str], ...]
|
Channel arguments passed to |
bound_ports |
list[int]
|
Ports returned when each bind target is attached,
populated when :meth: |
Initialise an empty spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
options
|
Mapping[str, int | str] | None
|
Channel arguments for |
None
|
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/server_spec.py
bind_addresses
property
¶
Listener addresses in registration order.
add_handler(handler)
¶
Register a generic RPC handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
GenericRpcHandler
|
The handler to add to the server. |
required |
add_interceptor(interceptor)
¶
Register a server interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interceptor
|
ServerInterceptor
|
The interceptor to install on the server. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/server_spec.py
add_service_registrar(registrar)
¶
Register a callback that attaches a service at build time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registrar
|
ServerServiceRegistrar
|
Callback invoked with the instantiated server. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/server_spec.py
add_insecure_port(address)
¶
Register a plaintext bind address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
Address in |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/server_spec.py
add_secure_port(address, credentials)
¶
Register a TLS-protected bind address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
Address in |
required |
credentials
|
ServerCredentials
|
Credentials terminating TLS on that address. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/server_spec.py
build_async()
async
¶
Instantiate the underlying grpc.aio.Server on the current loop.
Must be awaited from the event loop that will run the server; see the module docstring for the rationale.
Returns:
| Type | Description |
|---|---|
Server
|
The fully-configured server ready for |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/server_spec.py
표준 서비스¶
Standard gRPC services exposed alongside the application's own services.
Two services come from the gRPC ecosystem rather than from user code:
grpc.health.v1.Healthis what Kubernetes' native gRPC probe calls, so a gRPC-only deployment has no other way to report liveness.- Server reflection is the only way to discover what a running server exposes
when the schema is built at runtime from pydantic models and no
.protoartifact is ever produced.
Both are attached through :meth:GrpcServerSpec.add_service_registrar,
because their servicers need the concrete grpc.aio.Server that only
exists once the spec is built on the serving event loop.
enable_health_service(spec, registry, servicer)
¶
Expose the standard health checking service on the server.
Every registered service is reported SERVING when the server comes up.
A probe that names a service — grpc_health_probe -service=<name> or the
Kubernetes grpc.service field — answers NOT_FOUND for any name the
servicer has never been told about, so a healthy server would otherwise be
judged unready. Applications flip individual services afterwards through
the injected servicer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
GrpcServerSpec
|
Spec collecting the deferred server configuration. |
required |
registry
|
DescriptorRegistry
|
Registry the health schema is mirrored into so reflection can describe the service it advertises, and whose service list seeds the initial statuses. |
required |
servicer
|
HealthServicer
|
Servicer holding the reported serving statuses. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/standard_services.py
enable_reflection_service(spec, registry)
¶
Expose server reflection over the code-first descriptor pool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
GrpcServerSpec
|
Spec collecting the deferred server configuration. |
required |
registry
|
DescriptorRegistry
|
Registry holding every descriptor built from controllers; its service list is read at build time so services registered later during post-processing are still advertised. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/standard_services.py
스키마¶
Registry¶
Descriptor pool registry for compiled protobuf descriptors.
Manages FileDescriptorProto registration in a descriptor_pool, provides caching, and returns compiled message classes and service descriptors.
DescriptorRegistry(pool=None)
¶
Registry for protobuf descriptors backed by a DescriptorPool.
Registers FileDescriptorProto instances, prevents duplicates, and provides access to compiled message classes and service descriptors.
Attributes:
| Name | Type | Description |
|---|---|---|
pool |
DescriptorPool
|
The underlying DescriptorPool. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/registry.py
service_names
property
¶
Fully-qualified names of every service registered so far, sorted.
Server reflection advertises exactly this list, so it is read at server-build time rather than captured earlier: controllers keep registering while post-processing runs.
register(file_proto)
¶
Register a FileDescriptorProto in the pool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_proto
|
FileDescriptorProto
|
The file descriptor proto to register. |
required |
Returns:
| Type | Description |
|---|---|
FileDescriptor
|
The compiled FileDescriptor. |
Raises:
| Type | Description |
|---|---|
DescriptorAlreadyRegisteredError
|
If the file is already registered. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/registry.py
is_registered(file_name)
¶
Check if a file is already registered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_name
|
str
|
The proto file name. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the file has been registered. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/registry.py
find_message_descriptor(full_name)
¶
Find a message descriptor by its fully-qualified name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
full_name
|
str
|
The fully-qualified protobuf message name
(e.g. |
required |
Returns:
| Type | Description |
|---|---|
Descriptor
|
The message Descriptor. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/registry.py
get_message_class(full_name)
¶
Get a runtime message class for the given type name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
full_name
|
str
|
The fully-qualified protobuf message name. |
required |
Returns:
| Type | Description |
|---|---|
type[Message]
|
A Message subclass that can be instantiated. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/registry.py
find_service_descriptor(full_name)
¶
Find a service descriptor by its fully-qualified name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
full_name
|
str
|
The fully-qualified protobuf service name
(e.g. |
required |
Returns:
| Type | Description |
|---|---|
ServiceDescriptor
|
The ServiceDescriptor. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/registry.py
Descriptor Builder¶
Pydantic BaseModel to protobuf FileDescriptorProto builder.
Converts pydantic BaseModel subclasses and @rpc-decorated
controller methods into protobuf FileDescriptorProto instances. Field
numbers are assigned deterministically from field names (see
:mod:spakky.plugins.grpc.schema.field_number); an explicit
ProtoField annotation overrides the derived number for a field.
build_message_descriptor(model_type, collected=None)
¶
Build a DescriptorProto from a pydantic BaseModel subclass.
Recursively processes nested BaseModel fields into nested message
descriptors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_type
|
type[BaseModel]
|
The |
required |
collected
|
dict[str, DescriptorProto] | None
|
Accumulator for all message descriptors encountered during recursive processing. Used internally. |
None
|
Returns:
| Type | Description |
|---|---|
DescriptorProto
|
A tuple of (root |
dict[str, DescriptorProto]
|
descriptors). |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/descriptor_builder.py
build_service_descriptor(controller_type, package, service_name, collected)
¶
Build a ServiceDescriptorProto from an @GrpcController class.
Inspects all @rpc-decorated methods on the controller and generates
method descriptors with fully-qualified type names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
controller_type
|
type
|
The controller class to inspect. |
required |
package
|
str
|
The protobuf package name. |
required |
service_name
|
str
|
The gRPC service name. |
required |
collected
|
dict[str, DescriptorProto]
|
Accumulator for message descriptors found in method signatures. |
required |
Returns:
| Type | Description |
|---|---|
ServiceDescriptorProto
|
A |
Raises:
| Type | Description |
|---|---|
MessagelessRpcMethodError
|
If an |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/descriptor_builder.py
build_file_descriptor(controller_type)
¶
Build a complete FileDescriptorProto from an @GrpcController class.
Generates all message descriptors referenced by @rpc methods and
the service descriptor, packaged into a single FileDescriptorProto.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
controller_type
|
type
|
The |
required |
Returns:
| Type | Description |
|---|---|
FileDescriptorProto
|
A |
Raises:
| Type | Description |
|---|---|
MessagelessRpcMethodError
|
If an |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/descriptor_builder.py
타입 맵¶
Python type to protobuf type mapping.
Maps Python built-in types and composite types (list, Optional,
nested BaseModel) to their protobuf FieldDescriptorProto
equivalents. Protobuf field numbers are assigned separately by
:mod:spakky.plugins.grpc.schema.field_number.
PYTHON_TO_PROTO_TYPE = {str: FieldDescriptorProto.TYPE_STRING, int: FieldDescriptorProto.TYPE_INT64, float: FieldDescriptorProto.TYPE_DOUBLE, bool: FieldDescriptorProto.TYPE_BOOL, bytes: FieldDescriptorProto.TYPE_BYTES}
module-attribute
¶
Mapping of Python primitive types to protobuf field type constants.
ResolvedFieldType(proto_type, *, is_repeated=False, is_optional=False, is_message=False, message_type=None)
¶
Result of resolving a Python type annotation to protobuf metadata.
Attributes:
| Name | Type | Description |
|---|---|---|
proto_type |
Protobuf field type constant from |
|
is_repeated |
Whether the field is a repeated (list) field. |
|
is_optional |
Whether the field is optional. |
|
is_message |
Whether the field references a nested message type. |
|
message_type |
The nested |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/type_map.py
resolve_type(annotation)
¶
Resolve a Python type annotation to protobuf field metadata.
Handles:
- Primitive types (str, int, float, bool, bytes)
- list[T] → repeated field
- Optional[T] (T | None) → optional field
- Nested BaseModel → message type
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
object
|
The Python type annotation to resolve. |
required |
Returns:
| Type | Description |
|---|---|
ResolvedFieldType
|
A ResolvedFieldType with protobuf mapping information. |
Raises:
| Type | Description |
|---|---|
UnsupportedFieldTypeError
|
If the type cannot be mapped. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/type_map.py
Descriptor 스냅샷¶
Wire-layout snapshot of the descriptors generated from controllers.
Field numbers are derived from field names, so renaming a pydantic field is
a wire-breaking change that no .proto artifact exists to catch. This
module renders the generated layout — message → field → number and type — as
deterministic JSON, and exposes it as the spakky-grpc-descriptor-snapshot
command so a project can commit the snapshot and fail its own build when the
wire layout moves.
ProtoFieldSnapshot
¶
Bases: BaseModel
One protobuf field's wire-visible identity.
type_name
instance-attribute
¶
Fully-qualified type for message fields, empty for scalar fields.
proto3_optional
instance-attribute
¶
Whether the field tracks explicit presence.
Turning str into str | None keeps the number, the type and the label
identical while changing what a peer decodes for an unset field between
"" and None. Without this the snapshot diff would stay clean through
that break.
ProtoMessageSnapshot
¶
Bases: BaseModel
One protobuf message with its fields ordered by field number.
ProtoMethodSnapshot
¶
Bases: BaseModel
One RPC method with its request/response types and streaming shape.
ProtoServiceSnapshot
¶
Bases: BaseModel
One gRPC service with its methods ordered by name.
DescriptorSnapshot
¶
Bases: BaseModel
The full wire layout generated from a set of controllers.
build_descriptor_snapshot(controller_types)
¶
Render the wire layout generated from @GrpcController classes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
controller_types
|
Sequence[type]
|
Controller classes to describe. |
required |
Returns:
| Type | Description |
|---|---|
DescriptorSnapshot
|
A snapshot whose services, messages and fields are in a stable order |
DescriptorSnapshot
|
so two runs over the same declarations produce identical output. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/snapshot.py
collect_controller_types(module_names)
¶
Import the named modules and collect every @GrpcController in them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module_names
|
Sequence[str]
|
Dotted module or package paths to scan. |
required |
Returns:
| Type | Description |
|---|---|
list[type]
|
The discovered controller classes, ordered by qualified name. |
Raises:
| Type | Description |
|---|---|
NoControllerFoundError
|
If the named modules declare no controller. An empty snapshot would otherwise be committed as the baseline and every later comparison against it would pass, leaving the wire layout unguarded. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/snapshot.py
main(argv=None)
¶
Print the descriptor snapshot for the modules named on the command line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
argv
|
Sequence[str] | None
|
Command-line arguments. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The process exit status, which is always success once the modules |
int
|
imported and the descriptors built. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/schema/snapshot.py
인터셉터¶
Tracing interceptor for W3C Trace Context propagation over gRPC.
Extracts trace context from incoming gRPC metadata, activates a child span for the RPC lifetime, and injects trace context into trailing metadata.
TracingInterceptor(*, propagator)
¶
Bases: ServerInterceptor
Interceptor that propagates W3C Trace Context across gRPC boundaries.
Extracts traceparent / tracestate from incoming request
metadata, activates a child span for the RPC lifetime, and injects
the current trace context into trailing metadata.
Initialize the tracing interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
propagator
|
ITracePropagator
|
Trace context propagator for extract/inject. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/interceptors/tracing.py
intercept_service(continuation, handler_call_details)
async
¶
Intercept an RPC and set up W3C Trace Context.
Extracts trace context from incoming metadata, creates a child span (or a new root when no parent exists), and wraps the handler to inject trace context into trailing metadata and clear it after completion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
continuation
|
Callable[[HandlerCallDetails], Awaitable[RpcMethodHandler[RequestT, ResponseT] | None]]
|
Calls the next interceptor or resolves the handler. |
required |
handler_call_details
|
HandlerCallDetails
|
Describes the incoming RPC. |
required |
Returns:
| Type | Description |
|---|---|
RpcMethodHandler[RequestT, ResponseT] | None
|
A handler with trace-context lifecycle wrappers. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/interceptors/tracing.py
Error handling interceptor for gRPC servers.
Catches domain exceptions and maps them to appropriate gRPC status codes.
Unexpected exceptions are logged and returned as INTERNAL status.
ErrorHandlingInterceptor(*, debug=False)
¶
Bases: ServerInterceptor
Interceptor that converts exceptions to gRPC status codes.
AbstractGrpcStatusError subclasses are mapped to their declared
status_code. All other exceptions become INTERNAL.
Attributes:
| Name | Type | Description |
|---|---|---|
__debug |
bool
|
When True, include tracebacks in error details. |
Initialize the error handling interceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
debug
|
bool
|
Whether to include full tracebacks in error details. |
False
|
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/interceptors/error_handling.py
intercept_service(continuation, handler_call_details)
async
¶
Intercept an RPC and wrap the handler with error handling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
continuation
|
Callable[[HandlerCallDetails], Awaitable[RpcMethodHandler[RequestT, ResponseT] | None]]
|
Calls the next interceptor or resolves the handler. |
required |
handler_call_details
|
HandlerCallDetails
|
Describes the incoming RPC. |
required |
Returns:
| Type | Description |
|---|---|
RpcMethodHandler[RequestT, ResponseT] | None
|
A handler with error-catching wrappers on its behavior methods. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/interceptors/error_handling.py
후처리기s¶
Post-processor for registering gRPC services from controllers.
Scans @GrpcController-decorated Pods, builds protobuf descriptors
at runtime, and appends generic RPC handlers to the shared
:class:GrpcServerSpec.
RegisterServicesPostProcessor
¶
Bases: IPostProcessor, IContainerAware, IApplicationContextAware
Post-processor that registers gRPC services from controllers.
When a @GrpcController Pod is created, this processor:
- Builds a
FileDescriptorProtofrom the controller's@rpcmethods and dataclass message types. - Registers the descriptor in the shared
DescriptorRegistry. - Creates a
GrpcServiceHandler(generic handler) and appends it to the shared :class:GrpcServerSpec.
Runs at @Order(0) — first in the gRPC post-processor chain.
set_container(container)
¶
Inject the IoC container.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
container
|
IContainer
|
The IoC container. |
required |
set_application_context(application_context)
¶
Inject the application context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
application_context
|
IApplicationContext
|
The application context. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/register_services.py
post_process(pod)
¶
Register a gRPC service if pod is a @GrpcController.
Non-controller Pods are returned unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pod
|
object
|
The Pod instance to process. |
required |
Returns:
| Type | Description |
|---|---|
object
|
The unmodified Pod. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/register_services.py
Post-processor for recording interceptors on the gRPC server spec.
Adds ErrorHandlingInterceptor and (when the tracing plugin is loaded)
TracingInterceptor to the shared :class:GrpcServerSpec. The actual
grpc.aio.Server is instantiated later, on the event loop that will
run it (see :mod:spakky.plugins.grpc.server_spec).
AddInterceptorsPostProcessor
¶
Bases: IPostProcessor, IContainerAware, IApplicationContextAware
Post-processor that records interceptors on the shared server spec.
Interceptors added (in order):
ErrorHandlingInterceptor— always.TracingInterceptor— only when anITracePropagatoris available in the application context.
Runs at @Order(1) — after service registration.
set_container(container)
¶
Inject the IoC container.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
container
|
IContainer
|
The IoC container. |
required |
set_application_context(application_context)
¶
Inject the application context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
application_context
|
IApplicationContext
|
The application context. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/add_interceptors.py
post_process(pod)
¶
Record interceptors on the server spec once per spec instance.
The spec is resolved lazily so that interceptor registration runs
exactly once: the first time a GrpcServerSpec Pod is seen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pod
|
object
|
The Pod instance to process. |
required |
Returns:
| Type | Description |
|---|---|
object
|
The pod unchanged. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/add_interceptors.py
Post-processor for binding gRPC server lifecycle.
Wires a :class:GrpcServerSpec Pod into the ApplicationContext so
that the underlying grpc.aio.Server is materialised on the context's
event loop and started/stopped alongside the application.
GRACEFUL_SHUTDOWN_SECONDS = 5.0
module-attribute
¶
Default grace period (seconds) for server shutdown.
GrpcServerService(spec)
¶
Bases: IAsyncService
Async service wrapper that instantiates the gRPC server on the right loop.
grpc.aio.server() binds to whatever event loop is running when it
is called, so the real server is created inside :meth:start_async
from the captured :class:GrpcServerSpec.
Attributes:
| Name | Type | Description |
|---|---|---|
_spec |
GrpcServerSpec
|
Configuration collected during post-processing. |
_server |
Server | None
|
The materialised server, set once :meth: |
_stop_event |
Event
|
Async event passed by the application context; unused
internally because shutdown is driven by :meth: |
Initialise the service with a server spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
GrpcServerSpec
|
Collected server configuration. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/bind_server.py
set_stop_event(stop_event)
¶
Store the async stop event from the application context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stop_event
|
Event
|
Async event forwarded by the application context. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/bind_server.py
start_async()
async
¶
Build the gRPC server on the current loop and start it.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/bind_server.py
stop_async()
async
¶
Gracefully stop the gRPC server if it was started.
Clears _server after a successful stop so that repeated
stop_async calls are safe no-ops.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/bind_server.py
BindServerPostProcessor
¶
Bases: IPostProcessor, IContainerAware, IApplicationContextAware
Post-processor that binds a :class:GrpcServerSpec to the ApplicationContext.
Wraps the spec in a :class:GrpcServerService and registers it with
the ApplicationContext for automatic start/stop management.
Runs at @Order(2) — last in the gRPC post-processor chain.
set_container(container)
¶
Inject the IoC container.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
container
|
IContainer
|
The IoC container. |
required |
set_application_context(application_context)
¶
Inject the application context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
application_context
|
IApplicationContext
|
The application context. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/bind_server.py
post_process(pod)
¶
Bind server lifecycle if pod is a GrpcServerSpec.
Non-spec Pods are returned unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pod
|
object
|
The Pod instance to process. |
required |
Returns:
| Type | Description |
|---|---|
object
|
The unmodified spec Pod. |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/post_processors/bind_server.py
에러¶
gRPC plugin error hierarchy.
Provides base error classes, gRPC status-mapped errors, and schema errors.
AbstractSpakkyGrpcError
¶
AbstractGrpcStatusError
¶
Bases: AbstractSpakkyGrpcError, ABC
Base for gRPC errors that map to a specific status code.
Subclasses must define status_code to specify which gRPC status
code the error maps to.
InvalidArgument
¶
NotFound
¶
AlreadyExists
¶
PermissionDenied
¶
Unauthenticated
¶
FailedPrecondition
¶
Unavailable
¶
InternalError
¶
UnsupportedFieldTypeError(field_type)
¶
Bases: AbstractSpakkyGrpcError
Raised when a Python type cannot be mapped to a protobuf type.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
UnsupportedResponseTypeError(value_type)
¶
Bases: AbstractSpakkyGrpcError
Raised when a serializer receives an object it cannot encode.
The gRPC response serializer accepts either a protobuf Message
(passed through verbatim) or a pydantic BaseModel (encoded via
the json_format bridge). Any other type signals a controller
returned an unsupported value.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
DescriptorAlreadyRegisteredError(file_name)
¶
Bases: AbstractSpakkyGrpcError
Raised when a FileDescriptorProto is registered more than once.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
ProtoFieldNumberConflictError(model_type, explicit_field_name, derived_field_name, number)
¶
Bases: AbstractSpakkyGrpcError
Raised when an explicit ProtoField number collides with an auto-derived one.
An explicit ProtoField(number=N) reserves N for its field. If an
auto-numbered field in the same message natively hashes to N, silently
re-hashing the auto field would change its wire number and break
compatibility. This conflict is surfaced as a build error instead so the
author pins the auto field's number too or chooses a different N.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
InvalidProtoFieldNumberError(model_type, field_name, number)
¶
Bases: AbstractSpakkyGrpcError
Raised when an explicit ProtoField number is not a valid protobuf number.
An explicit ProtoField(number=N) must fall in the assignable protobuf
range: 1 .. 536_870_911 (2**29 - 1) and outside the protobuf
reserved band 19_000 .. 19_999. The auto-numbering path already
honors these constraints deterministically, but an explicit override
bypassed them and only failed later at descriptor-pool build time with an
opaque protobuf message. This error surfaces the violation at schema-build
time with the offending field and number so the author corrects it.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
DuplicateProtoFieldNumberError(model_type, first_field_name, second_field_name, number)
¶
Bases: AbstractSpakkyGrpcError
Raised when two explicit ProtoField numbers collide in one message.
Each protobuf field number must be unique within its message. Two fields
carrying the same explicit ProtoField(number=N) would later be rejected
by the descriptor pool with an opaque message. This error surfaces the
duplicate at schema-build time, naming both colliding fields and the shared
number so the author repins one of them.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
IncompleteTlsCredentialsError(certificate_chain_file, private_key_file)
¶
Bases: AbstractSpakkyGrpcError
Raised when TLS is configured with only one half of the key pair.
Terminating TLS needs both the server certificate chain and the matching private key. Configuring one without the other would otherwise fall back to a plaintext listener, silently downgrading transport security on a deployment that asked for TLS.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
MissingClientCertificateAuthorityError
¶
Bases: AbstractSpakkyGrpcError
Raised when mutual TLS is requested without a client CA to verify against.
require_client_auth makes the server reject peers whose certificate is
not signed by a trusted authority. Without a client CA file there is no
authority to check against, so the setting could not be honoured.
NotAnRpcMethodError(method_name)
¶
Bases: AbstractSpakkyGrpcError
Raised when a client callable is built from a method without @rpc.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
RpcMethodTypeMismatchError(method_name, expected, actual)
¶
Bases: AbstractSpakkyGrpcError
Raised when a client callable is built for the wrong streaming pattern.
Each GrpcClient factory produces a multicallable of one specific gRPC
streaming shape. Building a unary callable for a server-streaming method
would otherwise fail deep inside the transport with an opaque error.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
MessagelessRpcMethodError(method_name)
¶
Bases: AbstractSpakkyGrpcError
Raised when an @rpc method declares no request or no response model.
protobuf requires every method to name an input and an output message, so a method missing either cannot be addressed over the wire. Reporting it here names the offending method instead of surfacing an opaque descriptor-pool rejection about an unresolvable empty type name.
Raised from two places that reach the same invalid declaration: controller
registration (build_service_descriptor) and client callable
construction. Both carry method_name as <controller>.<method> so a
log line identifies the declaration even when several controllers share a
method name.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
NoControllerFoundError(module_names)
¶
Bases: AbstractSpakkyGrpcError
Raised when a descriptor snapshot is requested for modules with no controller.
An empty snapshot looks like a valid baseline: committing it and comparing later runs against it passes forever, so the wire layout the snapshot exists to protect goes unguarded. Naming the scanned modules instead points at the usual cause, a wrong module path on the command line.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/error.py
추가 모듈¶
Plugin initialization for gRPC integration.
Registers post-processors that enable automatic gRPC service registration, interceptor injection, and server lifecycle management.
initialize(app)
¶
Initialize the gRPC plugin.
Registers post-processors for automatic gRPC service registration, interceptor injection, and server lifecycle management. This function is called automatically by the Spakky framework during plugin loading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
SpakkyApplication
|
The Spakky application instance. |
required |
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/main.py
descriptor_registry()
¶
grpc_health_servicer()
¶
Create the servicer backing the standard health checking service.
Applications inject this Pod to report their own serving status, for
example flipping a service to NOT_SERVING while a dependency is down.
Source code in plugins/spakky-grpc/src/spakky/plugins/grpc/main.py
grpc_server_spec(config, descriptor_registry, grpc_health_servicer)
¶
Create the shared gRPC server spec from plugin configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
GrpcConfig
|
Plugin configuration. |
required |
descriptor_registry
|
DescriptorRegistry
|
Registry the standard services publish into. |
required |
grpc_health_servicer
|
HealthServicer
|
Servicer holding the reported serving statuses. |
required |
Returns:
| Type | Description |
|---|---|
GrpcServerSpec
|
The spec collecting everything the server is built from. |