hexastack-grpc 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hexastack-grpc might be problematic. Click here for more details.
- hexastack_grpc-0.0.0/PKG-INFO +142 -0
- hexastack_grpc-0.0.0/README.md +127 -0
- hexastack_grpc-0.0.0/pyproject.toml +53 -0
- hexastack_grpc-0.0.0/pyproject.toml.orig +52 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/__init__.py +7 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/adapters/__init__.py +0 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/adapters/server.py +46 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/domain/__init__.py +0 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/domain/exceptions.py +24 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/__init__.py +0 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/autodiscovery.py +64 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/bootstrap.py +113 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/config.py +49 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/decorators.py +62 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/dispatch.py +101 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/interceptors/__init__.py +0 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/interceptors/correlation.py +67 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/interceptors/exception.py +78 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/interceptors/generic.py +180 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/interceptors/logging.py +63 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/registries/__init__.py +0 -0
- hexastack_grpc-0.0.0/src/hexastack_grpc/infra/registries/service.py +133 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: hexastack-grpc
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: High-performance gRPC presentation adapter and interceptors for Hexastack
|
|
5
|
+
Author: Richard West
|
|
6
|
+
Author-email: Richard West <dopplereffect.us@gmail.com>
|
|
7
|
+
Requires-Dist: grpcio>=1.68.0
|
|
8
|
+
Requires-Dist: hexastack-core
|
|
9
|
+
Requires-Dist: hexastack-cqrs
|
|
10
|
+
Requires-Dist: protobuf>=5.29.0
|
|
11
|
+
Requires-Dist: grpcio-reflection>=1.68.0 ; extra == 'reflection'
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Provides-Extra: reflection
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# hexastack-grpc
|
|
17
|
+
|
|
18
|
+
> High-performance gRPC presentation adapter, interceptors, and CQRS service integration for Hexastack.
|
|
19
|
+
|
|
20
|
+
[](https://www.python.org/downloads/)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Overview & Capabilities
|
|
25
|
+
|
|
26
|
+
`hexastack-grpc` provides a binary RPC presentation adapter for Hexastack applications:
|
|
27
|
+
|
|
28
|
+
- **Declarative Service Registration (`@grpc_service`)**: Mounts generated protobuf RPC servicers onto the `grpc.Server` with automatic DI resolution.
|
|
29
|
+
- **Cross-Cutting Interceptor Pipeline**:
|
|
30
|
+
- `CorrelationServerInterceptor`: Propagates `x-correlation-id` from incoming metadata into `ContextVar`.
|
|
31
|
+
- `LoggingServerInterceptor`: Structured telemetry for RPC method invocations.
|
|
32
|
+
- `TimingServerInterceptor`: Measures RPC execution latency in milliseconds.
|
|
33
|
+
- **Single-Pass Reflection**: Automatically discovers and registers decorated servicers in Phase 3 module scanning via `create_grpc_visitor`.
|
|
34
|
+
- **gRPC Server Reflection**: Automatically enables gRPC Server Reflection Protocol when `grpcio-reflection` is installed.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Package Anatomy & Key Components
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
hexastack_grpc/
|
|
42
|
+
├── domain/ # GrpcError, ServiceRegistrationError, RpcExecutionError
|
|
43
|
+
└── infra/
|
|
44
|
+
├── bootstrap.py # GrpcBootstrapper (order=40)
|
|
45
|
+
├── config.py # HexastackGrpcConfig
|
|
46
|
+
├── decorators.py# @grpc_service
|
|
47
|
+
├── autodiscovery.py # create_grpc_visitor, autodiscover_grpc_services
|
|
48
|
+
├── interceptors/# correlation, logging, timing interceptors
|
|
49
|
+
└── registries/ # service.py (GrpcServiceRegistry)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 3. Monorepo & Sibling Relationships
|
|
55
|
+
|
|
56
|
+
```mermaid
|
|
57
|
+
graph TD
|
|
58
|
+
subgraph Clients ["gRPC Clients (Microservices, Mobile, Gateway)"]
|
|
59
|
+
CLIENT["gRPC Protobuf Client"]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
subgraph GrpcAdapter ["hexastack-grpc"]
|
|
63
|
+
SERVER["grpc.Server"]
|
|
64
|
+
INTERCEPTORS["Interceptors (Correlation, Logging, Timing)"]
|
|
65
|
+
REG["GrpcServiceRegistry"]
|
|
66
|
+
SERVICER["CQRS Protobuf Servicer"]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
subgraph CQRSExecution ["hexastack-cqrs"]
|
|
70
|
+
CBUS["CommandBusPort"]
|
|
71
|
+
QBUS["QueryBusPort"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
CLIENT --> SERVER
|
|
75
|
+
SERVER --> INTERCEPTORS
|
|
76
|
+
INTERCEPTORS --> SERVICER
|
|
77
|
+
SERVICER -->|dispatches commands| CBUS
|
|
78
|
+
SERVICER -->|dispatches queries| QBUS
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Explicit Dependencies (Direct)
|
|
82
|
+
- `hexastack-core`: DI container (`rodi`), configuration registry, context variables.
|
|
83
|
+
- `hexastack-cqrs`: `CommandBusPort` and `QueryBusPort`.
|
|
84
|
+
- `grpcio>=1.68.0`, `protobuf>=5.29.0`.
|
|
85
|
+
|
|
86
|
+
### Optional Integrations (Extras)
|
|
87
|
+
- `[reflection]`: Installs `grpcio-reflection>=1.68.0` for runtime service reflection tools (`grpcurl`, Postman).
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 4. Installation
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Standalone installation
|
|
95
|
+
pip install hexastack-grpc
|
|
96
|
+
|
|
97
|
+
# With Server Reflection support
|
|
98
|
+
pip install "hexastack-grpc[reflection]"
|
|
99
|
+
|
|
100
|
+
# Via umbrella package
|
|
101
|
+
pip install "hexastack[grpc]"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 5. Configuration Reference
|
|
107
|
+
|
|
108
|
+
```toml
|
|
109
|
+
[hexastack.grpc]
|
|
110
|
+
host = "0.0.0.0"
|
|
111
|
+
port = 50051
|
|
112
|
+
max_workers = 10
|
|
113
|
+
enable_reflection = true
|
|
114
|
+
auto_start = false
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 6. Quickstart Example
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from hexastack_core.infra.bootstrap import bootstrap
|
|
123
|
+
from hexastack_grpc.infra.decorators import grpc_service
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# 1. Define Servicer implementing generated protobuf class
|
|
127
|
+
class GreeterServicer:
|
|
128
|
+
def SayHello(self, request, context):
|
|
129
|
+
return HelloReply(message=f"Hello, {request.name}!")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# 2. Register Servicer with generated add hook
|
|
133
|
+
@grpc_service(add_GreeterServicer_to_server)
|
|
134
|
+
class GreeterService(GreeterServicer):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# 3. Bootstrap Runtime and Start Server
|
|
139
|
+
runtime = bootstrap(packages_to_scan=[__name__])
|
|
140
|
+
grpc_server = runtime.get("grpc_server")
|
|
141
|
+
grpc_server.start()
|
|
142
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# hexastack-grpc
|
|
2
|
+
|
|
3
|
+
> High-performance gRPC presentation adapter, interceptors, and CQRS service integration for Hexastack.
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Overview & Capabilities
|
|
10
|
+
|
|
11
|
+
`hexastack-grpc` provides a binary RPC presentation adapter for Hexastack applications:
|
|
12
|
+
|
|
13
|
+
- **Declarative Service Registration (`@grpc_service`)**: Mounts generated protobuf RPC servicers onto the `grpc.Server` with automatic DI resolution.
|
|
14
|
+
- **Cross-Cutting Interceptor Pipeline**:
|
|
15
|
+
- `CorrelationServerInterceptor`: Propagates `x-correlation-id` from incoming metadata into `ContextVar`.
|
|
16
|
+
- `LoggingServerInterceptor`: Structured telemetry for RPC method invocations.
|
|
17
|
+
- `TimingServerInterceptor`: Measures RPC execution latency in milliseconds.
|
|
18
|
+
- **Single-Pass Reflection**: Automatically discovers and registers decorated servicers in Phase 3 module scanning via `create_grpc_visitor`.
|
|
19
|
+
- **gRPC Server Reflection**: Automatically enables gRPC Server Reflection Protocol when `grpcio-reflection` is installed.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. Package Anatomy & Key Components
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
hexastack_grpc/
|
|
27
|
+
├── domain/ # GrpcError, ServiceRegistrationError, RpcExecutionError
|
|
28
|
+
└── infra/
|
|
29
|
+
├── bootstrap.py # GrpcBootstrapper (order=40)
|
|
30
|
+
├── config.py # HexastackGrpcConfig
|
|
31
|
+
├── decorators.py# @grpc_service
|
|
32
|
+
├── autodiscovery.py # create_grpc_visitor, autodiscover_grpc_services
|
|
33
|
+
├── interceptors/# correlation, logging, timing interceptors
|
|
34
|
+
└── registries/ # service.py (GrpcServiceRegistry)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 3. Monorepo & Sibling Relationships
|
|
40
|
+
|
|
41
|
+
```mermaid
|
|
42
|
+
graph TD
|
|
43
|
+
subgraph Clients ["gRPC Clients (Microservices, Mobile, Gateway)"]
|
|
44
|
+
CLIENT["gRPC Protobuf Client"]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subgraph GrpcAdapter ["hexastack-grpc"]
|
|
48
|
+
SERVER["grpc.Server"]
|
|
49
|
+
INTERCEPTORS["Interceptors (Correlation, Logging, Timing)"]
|
|
50
|
+
REG["GrpcServiceRegistry"]
|
|
51
|
+
SERVICER["CQRS Protobuf Servicer"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
subgraph CQRSExecution ["hexastack-cqrs"]
|
|
55
|
+
CBUS["CommandBusPort"]
|
|
56
|
+
QBUS["QueryBusPort"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
CLIENT --> SERVER
|
|
60
|
+
SERVER --> INTERCEPTORS
|
|
61
|
+
INTERCEPTORS --> SERVICER
|
|
62
|
+
SERVICER -->|dispatches commands| CBUS
|
|
63
|
+
SERVICER -->|dispatches queries| QBUS
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Explicit Dependencies (Direct)
|
|
67
|
+
- `hexastack-core`: DI container (`rodi`), configuration registry, context variables.
|
|
68
|
+
- `hexastack-cqrs`: `CommandBusPort` and `QueryBusPort`.
|
|
69
|
+
- `grpcio>=1.68.0`, `protobuf>=5.29.0`.
|
|
70
|
+
|
|
71
|
+
### Optional Integrations (Extras)
|
|
72
|
+
- `[reflection]`: Installs `grpcio-reflection>=1.68.0` for runtime service reflection tools (`grpcurl`, Postman).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. Installation
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Standalone installation
|
|
80
|
+
pip install hexastack-grpc
|
|
81
|
+
|
|
82
|
+
# With Server Reflection support
|
|
83
|
+
pip install "hexastack-grpc[reflection]"
|
|
84
|
+
|
|
85
|
+
# Via umbrella package
|
|
86
|
+
pip install "hexastack[grpc]"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 5. Configuration Reference
|
|
92
|
+
|
|
93
|
+
```toml
|
|
94
|
+
[hexastack.grpc]
|
|
95
|
+
host = "0.0.0.0"
|
|
96
|
+
port = 50051
|
|
97
|
+
max_workers = 10
|
|
98
|
+
enable_reflection = true
|
|
99
|
+
auto_start = false
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 6. Quickstart Example
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from hexastack_core.infra.bootstrap import bootstrap
|
|
108
|
+
from hexastack_grpc.infra.decorators import grpc_service
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# 1. Define Servicer implementing generated protobuf class
|
|
112
|
+
class GreeterServicer:
|
|
113
|
+
def SayHello(self, request, context):
|
|
114
|
+
return HelloReply(message=f"Hello, {request.name}!")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# 2. Register Servicer with generated add hook
|
|
118
|
+
@grpc_service(add_GreeterServicer_to_server)
|
|
119
|
+
class GreeterService(GreeterServicer):
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# 3. Bootstrap Runtime and Start Server
|
|
124
|
+
runtime = bootstrap(packages_to_scan=[__name__])
|
|
125
|
+
grpc_server = runtime.get("grpc_server")
|
|
126
|
+
grpc_server.start()
|
|
127
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hexastack-grpc"
|
|
3
|
+
version = "0.0.0"
|
|
4
|
+
description = "High-performance gRPC presentation adapter and interceptors for Hexastack"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"grpcio>=1.68.0",
|
|
9
|
+
"hexastack-core",
|
|
10
|
+
"hexastack-cqrs",
|
|
11
|
+
"protobuf>=5.29.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[project.authors]]
|
|
15
|
+
name = "Richard West"
|
|
16
|
+
email = "dopplereffect.us@gmail.com"
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
reflection = ["grpcio-reflection>=1.68.0"]
|
|
20
|
+
|
|
21
|
+
[project.entry-points."hexastack.bootstrappers"]
|
|
22
|
+
grpc = "hexastack_grpc.infra.bootstrap:GrpcBootstrapper"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
26
|
+
build-backend = "uv_build"
|
|
27
|
+
|
|
28
|
+
[tool.uv.sources.hexastack-core]
|
|
29
|
+
workspace = true
|
|
30
|
+
|
|
31
|
+
[tool.uv.sources.hexastack-cqrs]
|
|
32
|
+
workspace = true
|
|
33
|
+
|
|
34
|
+
[tool.importlinter]
|
|
35
|
+
root_packages = ["hexastack_grpc"]
|
|
36
|
+
|
|
37
|
+
[[tool.importlinter.contracts]]
|
|
38
|
+
name = "Hexagonal architecture layer hierarchy"
|
|
39
|
+
type = "layers"
|
|
40
|
+
containers = ["hexastack_grpc"]
|
|
41
|
+
layers = [
|
|
42
|
+
"adapters",
|
|
43
|
+
"domain",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[[tool.importlinter.contracts]]
|
|
47
|
+
name = "Forbidden imports for domain"
|
|
48
|
+
type = "forbidden"
|
|
49
|
+
source_modules = ["hexastack_grpc.domain"]
|
|
50
|
+
forbidden_modules = [
|
|
51
|
+
"hexastack_grpc.adapters",
|
|
52
|
+
"hexastack_grpc.infra",
|
|
53
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hexastack-grpc"
|
|
3
|
+
version = "0.0.0"
|
|
4
|
+
description = "High-performance gRPC presentation adapter and interceptors for Hexastack"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Richard West", email = "dopplereffect.us@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"grpcio>=1.68.0",
|
|
12
|
+
"hexastack-core",
|
|
13
|
+
"hexastack-cqrs",
|
|
14
|
+
"protobuf>=5.29.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
reflection = [
|
|
19
|
+
"grpcio-reflection>=1.68.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.entry-points."hexastack.bootstrappers"]
|
|
23
|
+
grpc = "hexastack_grpc.infra.bootstrap:GrpcBootstrapper"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
27
|
+
build-backend = "uv_build"
|
|
28
|
+
|
|
29
|
+
[tool.uv.sources]
|
|
30
|
+
hexastack-core = { workspace = true }
|
|
31
|
+
hexastack-cqrs = { workspace = true }
|
|
32
|
+
|
|
33
|
+
[tool.importlinter]
|
|
34
|
+
root_packages = ["hexastack_grpc"]
|
|
35
|
+
|
|
36
|
+
[[tool.importlinter.contracts]]
|
|
37
|
+
name = "Hexagonal architecture layer hierarchy"
|
|
38
|
+
type = "layers"
|
|
39
|
+
containers = ["hexastack_grpc"]
|
|
40
|
+
layers = [
|
|
41
|
+
"adapters",
|
|
42
|
+
"domain",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[[tool.importlinter.contracts]]
|
|
46
|
+
name = "Forbidden imports for domain"
|
|
47
|
+
type = "forbidden"
|
|
48
|
+
source_modules = ["hexastack_grpc.domain"]
|
|
49
|
+
forbidden_modules = [
|
|
50
|
+
"hexastack_grpc.adapters",
|
|
51
|
+
"hexastack_grpc.infra",
|
|
52
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
|
|
3
|
+
import grpc
|
|
4
|
+
|
|
5
|
+
from hexastack_grpc.infra.config import HexastackGrpcConfig
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"create_async_grpc_server",
|
|
9
|
+
"run_grpc_server",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_async_grpc_server(
|
|
14
|
+
config: HexastackGrpcConfig,
|
|
15
|
+
interceptors: Sequence[grpc.aio.ServerInterceptor] | None = None,
|
|
16
|
+
) -> grpc.aio.Server:
|
|
17
|
+
"""Create an asynchronous grpc.aio.Server instance.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config: HexastackGrpcConfig options.
|
|
21
|
+
interceptors: Optional async server interceptors.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Configured grpc.aio.Server instance.
|
|
25
|
+
"""
|
|
26
|
+
server = grpc.aio.server(interceptors=interceptors or ())
|
|
27
|
+
server.add_insecure_port(f"{config.host}:{config.port}")
|
|
28
|
+
return server
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_grpc_server(
|
|
32
|
+
server: grpc.Server,
|
|
33
|
+
block: bool = True,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Start the gRPC server and optionally block the current thread.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
server: Configured grpc.Server instance.
|
|
39
|
+
block: If True, blocks thread with server.wait_for_termination().
|
|
40
|
+
"""
|
|
41
|
+
server.start()
|
|
42
|
+
if block:
|
|
43
|
+
try:
|
|
44
|
+
server.wait_for_termination()
|
|
45
|
+
except KeyboardInterrupt:
|
|
46
|
+
server.stop(grace=5.0)
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from hexastack_core.domain.exceptions import HexastackError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GrpcError(HexastackError):
|
|
5
|
+
"""Base exception for all gRPC presentation layer errors.
|
|
6
|
+
|
|
7
|
+
Notes/Architectural Intent:
|
|
8
|
+
Inherits from HexastackError to maintain unified error hierarchy across the framework.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServiceRegistrationError(GrpcError):
|
|
13
|
+
"""Exception raised when a gRPC servicer fails registration or lacks a valid add_to_server callback."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RpcExecutionError(GrpcError):
|
|
17
|
+
"""Exception raised when an RPC handler execution fails during dispatch."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"GrpcError",
|
|
22
|
+
"RpcExecutionError",
|
|
23
|
+
"ServiceRegistrationError",
|
|
24
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from types import ModuleType
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from hexastack_core.infra.autodiscovery import (
|
|
6
|
+
DiscoveryVisitor,
|
|
7
|
+
scan_modules,
|
|
8
|
+
)
|
|
9
|
+
from hexastack_grpc.infra.decorators import _GRPC_SERVICE_ATTR
|
|
10
|
+
from hexastack_grpc.infra.registries.service import (
|
|
11
|
+
GrpcServiceRegistration,
|
|
12
|
+
GrpcServiceRegistry,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"autodiscover_grpc_services",
|
|
17
|
+
"create_grpc_visitor",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def autodiscover_grpc_services(
|
|
22
|
+
packages_to_scan: Sequence[str | ModuleType],
|
|
23
|
+
registry: GrpcServiceRegistry,
|
|
24
|
+
) -> GrpcServiceRegistry:
|
|
25
|
+
"""Discover decorated gRPC servicers from packages.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
packages_to_scan: Sequence of package names or module objects to inspect.
|
|
29
|
+
registry: Target GrpcServiceRegistry instance.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
The populated GrpcServiceRegistry instance.
|
|
33
|
+
"""
|
|
34
|
+
visitor = create_grpc_visitor(registry)
|
|
35
|
+
scan_modules(packages_to_scan, [visitor])
|
|
36
|
+
return registry
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_grpc_visitor(
|
|
40
|
+
registry: GrpcServiceRegistry,
|
|
41
|
+
) -> DiscoveryVisitor:
|
|
42
|
+
"""Create a DiscoveryVisitor callback for single-pass gRPC service discovery.
|
|
43
|
+
|
|
44
|
+
Notes/Architectural Intent:
|
|
45
|
+
Inspects discovered classes and objects for @grpc_service metadata,
|
|
46
|
+
registering servicers into the supplied service registry during single-pass reflection.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
registry: Target GrpcServiceRegistry instance.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
DiscoveryVisitor callable accepting (member, module).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def visitor(obj: Any, module: ModuleType) -> None:
|
|
56
|
+
meta: GrpcServiceRegistration | None = getattr(obj, _GRPC_SERVICE_ATTR, None)
|
|
57
|
+
if meta is not None:
|
|
58
|
+
registry.register_service(
|
|
59
|
+
servicer=meta.servicer,
|
|
60
|
+
add_to_server_fn=meta.add_to_server_fn,
|
|
61
|
+
service_names=meta.service_names,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return visitor
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
import grpc
|
|
4
|
+
|
|
5
|
+
from hexastack_core.infra.bootstrap import BootstrapContext
|
|
6
|
+
from hexastack_core.infra.registries.config import ConfigRegistry
|
|
7
|
+
from hexastack_core.ports.bootstrap import BootstrapperPort
|
|
8
|
+
from hexastack_grpc.infra.autodiscovery import create_grpc_visitor
|
|
9
|
+
from hexastack_grpc.infra.config import (
|
|
10
|
+
HexastackGrpcConfig,
|
|
11
|
+
register_grpc_config,
|
|
12
|
+
)
|
|
13
|
+
from hexastack_grpc.infra.decorators import get_grpc_registry
|
|
14
|
+
from hexastack_grpc.infra.interceptors.correlation import (
|
|
15
|
+
CorrelationServerInterceptor,
|
|
16
|
+
)
|
|
17
|
+
from hexastack_grpc.infra.interceptors.exception import (
|
|
18
|
+
ExceptionServerInterceptor,
|
|
19
|
+
)
|
|
20
|
+
from hexastack_grpc.infra.interceptors.logging import (
|
|
21
|
+
LoggingServerInterceptor,
|
|
22
|
+
TimingServerInterceptor,
|
|
23
|
+
)
|
|
24
|
+
from hexastack_grpc.infra.registries.service import GrpcServiceRegistry
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class GrpcBootstrapResult:
|
|
29
|
+
"""Dataclass holding initialized gRPC server and configuration."""
|
|
30
|
+
|
|
31
|
+
config: HexastackGrpcConfig
|
|
32
|
+
server: grpc.Server
|
|
33
|
+
registry: GrpcServiceRegistry
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GrpcBootstrapper(BootstrapperPort):
|
|
37
|
+
"""Bootstrap extension configuring and building the gRPC server.
|
|
38
|
+
|
|
39
|
+
Notes/Architectural Intent:
|
|
40
|
+
Implements BootstrapperPort with order=40 (executing after CQRS order=20),
|
|
41
|
+
registering the autodiscovery visitor, compiling the gRPC Server with
|
|
42
|
+
telemetry interceptors (correlation, exception mapping, logging, timing),
|
|
43
|
+
and binding it to the DI container.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
name: str = "grpc"
|
|
47
|
+
order: int = 40
|
|
48
|
+
|
|
49
|
+
def configure(self, context: BootstrapContext) -> None:
|
|
50
|
+
"""Phase 2: Register visitor, assemble gRPC server with interceptors.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
context: BootstrapContext containing DI container, config, and properties.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
None.
|
|
57
|
+
"""
|
|
58
|
+
cfg = context.get_config("grpc", HexastackGrpcConfig)
|
|
59
|
+
|
|
60
|
+
registry = get_grpc_registry()
|
|
61
|
+
|
|
62
|
+
# Register visitor for single-pass reflective scanning (Phase 3)
|
|
63
|
+
visitor = create_grpc_visitor(registry)
|
|
64
|
+
context.register_visitor(visitor)
|
|
65
|
+
|
|
66
|
+
# 1. Assemble standard interceptors in order
|
|
67
|
+
interceptors = [
|
|
68
|
+
CorrelationServerInterceptor(),
|
|
69
|
+
ExceptionServerInterceptor(),
|
|
70
|
+
LoggingServerInterceptor(),
|
|
71
|
+
TimingServerInterceptor(),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# 2. Build grpc.Server instance
|
|
75
|
+
server = registry.build_server(
|
|
76
|
+
config=cfg,
|
|
77
|
+
interceptors=interceptors,
|
|
78
|
+
container=context.container,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# 3. Register Server and Registry into DI container
|
|
82
|
+
context.container.add_instance(server, declared_class=grpc.Server)
|
|
83
|
+
context.container.add_instance(registry)
|
|
84
|
+
|
|
85
|
+
# 4. Auto-start if configured
|
|
86
|
+
if cfg.auto_start:
|
|
87
|
+
server.start()
|
|
88
|
+
|
|
89
|
+
# 5. Store in context properties
|
|
90
|
+
result = GrpcBootstrapResult(
|
|
91
|
+
config=cfg,
|
|
92
|
+
server=server,
|
|
93
|
+
registry=registry,
|
|
94
|
+
)
|
|
95
|
+
context.properties["grpc_result"] = result
|
|
96
|
+
context.properties["grpc_server"] = server
|
|
97
|
+
|
|
98
|
+
def register_config(self, registry: ConfigRegistry) -> None:
|
|
99
|
+
"""Phase 1: Register gRPC configuration schema under 'grpc'.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
registry: Target ConfigRegistry instance.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
None.
|
|
106
|
+
"""
|
|
107
|
+
register_grpc_config(registry)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
__all__ = [
|
|
111
|
+
"GrpcBootstrapper",
|
|
112
|
+
"GrpcBootstrapResult",
|
|
113
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
from hexastack_core.infra.decorators import config_section
|
|
4
|
+
from hexastack_core.infra.registries.config import ConfigRegistry
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@config_section("grpc")
|
|
8
|
+
class HexastackGrpcConfig(BaseModel):
|
|
9
|
+
"""Configuration schema for Hexastack gRPC server adapter.
|
|
10
|
+
|
|
11
|
+
Notes/Architectural Intent:
|
|
12
|
+
Configures host binding, port, worker thread pools, and reflection support.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
host: str = Field(
|
|
16
|
+
default="0.0.0.0",
|
|
17
|
+
description="Host interface address to bind the gRPC server to.",
|
|
18
|
+
)
|
|
19
|
+
port: int = Field(
|
|
20
|
+
default=50051,
|
|
21
|
+
description="Port number to listen for incoming gRPC connections.",
|
|
22
|
+
)
|
|
23
|
+
max_workers: int = Field(
|
|
24
|
+
default=10,
|
|
25
|
+
description="Maximum worker threads allocated in the gRPC thread pool executor.",
|
|
26
|
+
)
|
|
27
|
+
enable_reflection: bool = Field(
|
|
28
|
+
default=True,
|
|
29
|
+
description="Enable gRPC Server Reflection protocol for grpcurl and postman discovery.",
|
|
30
|
+
)
|
|
31
|
+
auto_start: bool = Field(
|
|
32
|
+
default=False,
|
|
33
|
+
description="Automatically start the gRPC server in a background thread on bootstrap.",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"HexastackGrpcConfig",
|
|
39
|
+
"register_grpc_config",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def register_grpc_config(registry: ConfigRegistry) -> None:
|
|
44
|
+
"""Register gRPC configuration schema under 'grpc'.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
registry: Target ConfigRegistry instance.
|
|
48
|
+
"""
|
|
49
|
+
registry.register_config_section("grpc", HexastackGrpcConfig)
|