xime 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. xime/__init__.py +18 -0
  2. xime/adapters/__init__.py +0 -0
  3. xime/adapters/grpc/__init__.py +15 -0
  4. xime/adapters/grpc/_adapter.py +191 -0
  5. xime/adapters/grpc/_config.py +60 -0
  6. xime/adapters/grpc/codefirst/__init__.py +57 -0
  7. xime/adapters/grpc/codefirst/_builder.py +397 -0
  8. xime/adapters/grpc/codefirst/_config.py +81 -0
  9. xime/adapters/grpc/codefirst/_generator.py +141 -0
  10. xime/adapters/grpc/codefirst/_lock.py +118 -0
  11. xime/adapters/grpc/codefirst/_marshal.py +54 -0
  12. xime/adapters/grpc/codefirst/_model.py +104 -0
  13. xime/adapters/grpc/codefirst/_pb2_loader.py +52 -0
  14. xime/adapters/grpc/codefirst/_proto_emitter.py +211 -0
  15. xime/adapters/grpc/codefirst/_service_builder.py +192 -0
  16. xime/adapters/grpc/codefirst/_stream_convention.py +42 -0
  17. xime/adapters/grpc/codefirst/_type_map.py +163 -0
  18. xime/adapters/grpc/interceptors/__init__.py +17 -0
  19. xime/adapters/grpc/interceptors/_config.py +70 -0
  20. xime/adapters/grpc/interceptors/_context.py +78 -0
  21. xime/adapters/grpc/interceptors/_error.py +100 -0
  22. xime/adapters/grpc/routing/__init__.py +5 -0
  23. xime/adapters/grpc/routing/_builder.py +40 -0
  24. xime/adapters/grpc/routing/_config.py +72 -0
  25. xime/adapters/grpc/routing/_scanner.py +27 -0
  26. xime/adapters/grpc/tls/__init__.py +1 -0
  27. xime/adapters/grpc/tls/_credentials.py +26 -0
  28. xime/adapters/socket/__init__.py +42 -0
  29. xime/adapters/socket/_adapter.py +359 -0
  30. xime/adapters/socket/_client.py +218 -0
  31. xime/adapters/socket/_config.py +174 -0
  32. xime/adapters/socket/_peercred.py +88 -0
  33. xime/adapters/socket/_protocol.py +87 -0
  34. xime/adapters/socket/_session.py +186 -0
  35. xime/adapters/socket/routing/__init__.py +20 -0
  36. xime/adapters/socket/routing/_builder.py +211 -0
  37. xime/adapters/web/__init__.py +23 -0
  38. xime/adapters/web/_adapter.py +234 -0
  39. xime/adapters/web/_registry.py +20 -0
  40. xime/adapters/web/middleware/__init__.py +3 -0
  41. xime/adapters/web/middleware/_context.py +31 -0
  42. xime/adapters/web/openapi/__init__.py +34 -0
  43. xime/adapters/web/openapi/_builder.py +57 -0
  44. xime/adapters/web/openapi/_config.py +73 -0
  45. xime/adapters/web/routing/__init__.py +37 -0
  46. xime/adapters/web/routing/_builder.py +95 -0
  47. xime/adapters/web/routing/_config.py +40 -0
  48. xime/adapters/web/routing/_decorators.py +62 -0
  49. xime/adapters/web/routing/_scanner.py +80 -0
  50. xime/adapters/web/ws/__init__.py +3 -0
  51. xime/adapters/web/ws/_handler.py +97 -0
  52. xime/cli/__init__.py +11 -0
  53. xime/cli/_main.py +122 -0
  54. xime/core/bootstrap/__init__.py +7 -0
  55. xime/core/bootstrap/adapter.py +40 -0
  56. xime/core/bootstrap/application.py +295 -0
  57. xime/core/bootstrap/orchestrator.py +140 -0
  58. xime/core/config/__init__.py +11 -0
  59. xime/core/config/binding.py +128 -0
  60. xime/core/config/loader.py +70 -0
  61. xime/core/config/runtime.py +66 -0
  62. xime/core/container/__init__.py +224 -0
  63. xime/core/container/config_loader.py +115 -0
  64. xime/core/container/graph.py +236 -0
  65. xime/core/container/registry.py +105 -0
  66. xime/core/container/resolver.py +57 -0
  67. xime/core/container/scanner.py +133 -0
  68. xime/core/container/validator.py +160 -0
  69. xime/core/context/__init__.py +3 -0
  70. xime/core/context/request_context.py +81 -0
  71. xime/core/contract/__init__.py +30 -0
  72. xime/core/contract/_decorators.py +69 -0
  73. xime/core/contract/_scanner.py +88 -0
  74. xime/core/contract/_streams.py +45 -0
  75. xime/core/event/__init__.py +7 -0
  76. xime/core/event/bus.py +70 -0
  77. xime/core/event/handler.py +27 -0
  78. xime/core/exception/__init__.py +47 -0
  79. xime/core/exception/framework.py +246 -0
  80. xime/core/lifecycle/__init__.py +8 -0
  81. xime/core/lifecycle/hooks.py +48 -0
  82. xime/core/lifecycle/manager.py +67 -0
  83. xime/core/metadata/__init__.py +15 -0
  84. xime/core/metadata/type_utils.py +97 -0
  85. xime/core/security/__init__.py +21 -0
  86. xime/core/security/authentication.py +38 -0
  87. xime/core/security/authorization.py +36 -0
  88. xime/core/security/context.py +56 -0
  89. xime/core/security/enums.py +15 -0
  90. xime/core/security/session.py +58 -0
  91. xime/core/transaction/__init__.py +7 -0
  92. xime/core/transaction/context.py +27 -0
  93. xime/core/transaction/manager.py +39 -0
  94. xime/starters/__init__.py +0 -0
  95. xime/starters/jwt/__init__.py +18 -0
  96. xime/starters/jwt/_config.py +62 -0
  97. xime/starters/jwt/_key_context.py +41 -0
  98. xime/starters/jwt/_middleware.py +82 -0
  99. xime/starters/jwt/_signer.py +99 -0
  100. xime/starters/jwt/_verifier.py +85 -0
  101. xime/starters/scheduler/__init__.py +16 -0
  102. xime/starters/scheduler/_config.py +108 -0
  103. xime/starters/scheduler/_job.py +26 -0
  104. xime/starters/scheduler/_runner.py +142 -0
  105. xime/starters/sqlalchemy/__init__.py +20 -0
  106. xime/starters/sqlalchemy/base.py +45 -0
  107. xime/starters/sqlalchemy/engine.py +53 -0
  108. xime/starters/sqlalchemy/session.py +59 -0
  109. xime/starters/sqlalchemy/transaction.py +84 -0
  110. xime/testing/__init__.py +6 -0
  111. xime/testing/_app.py +177 -0
  112. xime/testing/_fakes.py +51 -0
  113. xime-0.1.0.dist-info/METADATA +339 -0
  114. xime-0.1.0.dist-info/RECORD +117 -0
  115. xime-0.1.0.dist-info/WHEEL +4 -0
  116. xime-0.1.0.dist-info/entry_points.txt +2 -0
  117. xime-0.1.0.dist-info/licenses/LICENSE +21 -0
xime/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ """
2
+ Xime Framework — Python backend framework inspired by Spring Boot.
3
+
4
+ Entry-point for application code:
5
+ from xime import Application, BindingConfig
6
+
7
+ Sub-packages:
8
+ xime.adapters.web — HTTP adapter (FastAPI), WebSocket, route decorators
9
+ xime.adapters.web.openapi — OpenAPI / Swagger configuration
10
+ xime.starters.jwt — JWT authentication
11
+ xime.starters.sqlalchemy — Async SQLAlchemy integration
12
+ xime.starters.scheduler — Task scheduling (APScheduler)
13
+ """
14
+
15
+ from xime.core.bootstrap.application import Application
16
+ from xime.core.config.binding import BindingConfig
17
+
18
+ __all__ = ["Application", "BindingConfig"]
File without changes
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from ._adapter import GrpcAdapter
4
+ from .interceptors._config import (
5
+ configure_grpc_error_mappings,
6
+ configure_grpc_interceptors,
7
+ )
8
+ from .routing._config import configure_grpc_services
9
+
10
+ __all__ = [
11
+ "GrpcAdapter",
12
+ "configure_grpc_services",
13
+ "configure_grpc_interceptors",
14
+ "configure_grpc_error_mappings",
15
+ ]
@@ -0,0 +1,191 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import grpc.aio
6
+
7
+ from ._config import GrpcServerConfig
8
+ from .interceptors._config import grpc_interceptor_registry
9
+ from .interceptors._context import RequestContextInterceptor
10
+ from .interceptors._error import ErrorMappingInterceptor
11
+ from .routing._builder import GrpcServiceBuilder
12
+ from .routing._config import grpc_service_registry
13
+ from .routing._scanner import GrpcServiceScanner
14
+ from .tls._credentials import build_server_credentials
15
+
16
+ if TYPE_CHECKING:
17
+ from xime.core.bootstrap.application import Application
18
+
19
+
20
+ class GrpcAdapter:
21
+ """gRPC adapter — wraps grpc.aio.Server into the Xime adapter lifecycle.
22
+
23
+ Register via app.use() and start via app.run():
24
+
25
+ app = Application()
26
+ app.use(WebAdapter())
27
+ app.use(GrpcAdapter())
28
+ app.run()
29
+
30
+ Hỗ trợ nhiều server gRPC trên các port khác nhau:
31
+
32
+ app.use(GrpcAdapter()) # server_id="default"
33
+ app.use(GrpcAdapter("internal", "0.0.0.0", 50052)) # server_id="internal"
34
+
35
+ Quy tắc:
36
+ - server_id="default" (mặc định): port đọc từ application.yml (grpc.port).
37
+ - server_id khác "default": port bắt buộc phải truyền vào constructor.
38
+ - Không được có hai GrpcAdapter cùng server_id — Application.use() sẽ báo lỗi.
39
+
40
+ Servicer thuộc server nào khai báo qua class variable server_id:
41
+
42
+ class InternalUserServicer:
43
+ server_id = "internal" # chỉ đăng ký vào GrpcAdapter("internal", ...)
44
+
45
+ class ExternalUserServicer:
46
+ # không khai báo → mặc định "default"
47
+
48
+ TLS / mTLS chỉ áp dụng cho server default, cấu hình qua application.yml:
49
+ grpc:
50
+ port: 50051
51
+ tls:
52
+ enabled: true
53
+ cert_file: certs/server.crt
54
+ key_file: certs/server.key
55
+ ca_file: certs/ca.crt # optional — needed for mTLS
56
+ mutual: true # true = require client certificate
57
+
58
+ Interceptor order (outermost first):
59
+ 1. RequestContextInterceptor — always present, sets request_id + cleans up
60
+ 2. ErrorMappingInterceptor — always present, maps exceptions → StatusCode
61
+ 3. user-declared interceptors — registered via configure_grpc_interceptors()
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ server_id: str = "default",
67
+ host: str | None = None,
68
+ port: int | None = None,
69
+ ) -> None:
70
+ if server_id != "default" and port is None:
71
+ raise ValueError(
72
+ f"GrpcAdapter(server_id='{server_id}'): "
73
+ "port is required for non-default servers."
74
+ )
75
+ self._server_id = server_id
76
+ self._host_override = host
77
+ self._port_override = port
78
+ self._server: grpc.aio.Server | None = None
79
+ self._app: "Application | None" = None
80
+
81
+ # ------------------------------------------------------------------
82
+ # Adapter protocol
83
+ # ------------------------------------------------------------------
84
+
85
+ async def start(self, app: "Application") -> None:
86
+ """Build and start the gRPC server.
87
+
88
+ Called by Application._run_async() after the DI container is fully built,
89
+ so GrpcServiceBuilder can fetch handler instances from the container.
90
+ Blocks until the server is stopped (via stop() or SIGINT).
91
+ """
92
+ from xime.core.config.runtime import RuntimeConfig
93
+ runtime: RuntimeConfig = app.get(RuntimeConfig) # type: ignore[assignment]
94
+
95
+ self._app = app
96
+
97
+ # Default server reads full config from application.yml (port, TLS, etc.).
98
+ # Non-default servers use constructor-provided port; TLS is not supported.
99
+ if self._server_id == "default":
100
+ config = GrpcServerConfig.from_runtime(runtime)
101
+ else:
102
+ config = GrpcServerConfig(port=self._port_override) # type: ignore[arg-type]
103
+
104
+ bind_host = self._host_override or "[::]"
105
+ interceptors = self._build_interceptors()
106
+
107
+ self._server = grpc.aio.server(interceptors=interceptors)
108
+
109
+ # Validate packages are importable before trying to fetch instances.
110
+ scanner = GrpcServiceScanner()
111
+ scanner.validate_packages(*grpc_service_registry.get_packages())
112
+
113
+ # Register only servicers whose server_id matches this adapter.
114
+ builder = GrpcServiceBuilder(app, self._server_id)
115
+ builder.register_all(self._server, grpc_service_registry.get_bindings())
116
+
117
+ # Register code-first controllers (@command/@stream) for this server, if any.
118
+ # Đăng ký controller code-first cho server này (nếu có).
119
+ self._register_codefirst(app)
120
+
121
+ # Bind port — TLS/mTLS only for default server.
122
+ if self._server_id == "default" and config.tls.enabled:
123
+ credentials = build_server_credentials(config.tls)
124
+ self._server.add_secure_port(f"{bind_host}:{config.port}", credentials)
125
+ else:
126
+ self._server.add_insecure_port(f"{bind_host}:{config.port}")
127
+
128
+ await self._server.start()
129
+ await self._server.wait_for_termination()
130
+
131
+ async def stop(self) -> None:
132
+ """Gracefully stop the gRPC server.
133
+
134
+ Waits up to 5 seconds for in-flight RPCs to complete before forcing
135
+ a shutdown. No-op if start() was never called.
136
+ """
137
+ if self._server is not None:
138
+ await self._server.stop(grace=5)
139
+ self._server = None
140
+
141
+ # ------------------------------------------------------------------
142
+ # Internal helpers
143
+ # ------------------------------------------------------------------
144
+
145
+ def _register_codefirst(self, app: "Application") -> None:
146
+ """Build + register code-first controllers for this server, if configured.
147
+
148
+ No-op when configure_grpc_codefirst() was never called. Imports the
149
+ code-first modules lazily so projects that don't use it (and may not have
150
+ protobuf/grpc-tools artifacts) are unaffected.
151
+ Không làm gì nếu chưa configure_grpc_codefirst(). Import lười phần code-first.
152
+ """
153
+ from xime.adapters.grpc.codefirst._config import codefirst_registry
154
+
155
+ packages = codefirst_registry.get_packages()
156
+ if not packages:
157
+ return
158
+
159
+ from xime.core.contract import ControllerScanner
160
+ from xime.adapters.grpc.codefirst._builder import ContractBuilder
161
+ from xime.adapters.grpc.codefirst._lock import LockFile
162
+ from xime.adapters.grpc.codefirst._pb2_loader import load_message_classes
163
+ from xime.adapters.grpc.codefirst._service_builder import CodeFirstGrpcBuilder
164
+
165
+ controllers = ControllerScanner().find_controllers(*packages)
166
+ # Only build/serve when at least one controller targets this server.
167
+ # Chỉ build/serve khi có ít nhất một controller thuộc server này.
168
+ if not any(getattr(c, "server_id", "default") == self._server_id for c in controllers):
169
+ return
170
+
171
+ output_dir = codefirst_registry.output_dir()
172
+ lock = LockFile.load(codefirst_registry.lock_file())
173
+ model = ContractBuilder(self._server_id, lock).build(controllers)
174
+ if not model.services:
175
+ return
176
+
177
+ messages = load_message_classes(output_dir, self._server_id)
178
+ CodeFirstGrpcBuilder(app, model, messages).register_all(self._server)
179
+
180
+ def _build_interceptors(self) -> list[grpc.aio.ServerInterceptor]:
181
+ """Compose the full interceptor stack.
182
+
183
+ Built-in interceptors always lead so that context setup and error
184
+ handling wrap every user-declared interceptor and every handler.
185
+ """
186
+ mappings = grpc_interceptor_registry.get_error_mappings()
187
+ built_in: list[grpc.aio.ServerInterceptor] = [
188
+ RequestContextInterceptor(),
189
+ ErrorMappingInterceptor(mappings),
190
+ ]
191
+ return built_in + grpc_interceptor_registry.get_interceptors()
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from xime.core.config.runtime import RuntimeConfig
6
+
7
+
8
+ class GrpcTlsConfig(BaseModel):
9
+ """TLS / mTLS configuration for the gRPC server.
10
+
11
+ All fields map 1-to-1 with the 'grpc.tls' block in application.yml:
12
+
13
+ grpc:
14
+ tls:
15
+ enabled: true
16
+ cert_file: certs/server.crt
17
+ key_file: certs/server.key
18
+ ca_file: certs/ca.crt # optional — needed for mTLS
19
+ mutual: true # true = require client certificate
20
+ """
21
+
22
+ enabled: bool = False
23
+ cert_file: str = ""
24
+ key_file: str = ""
25
+ ca_file: str | None = None
26
+ mutual: bool = False
27
+
28
+
29
+ class GrpcServerConfig(BaseModel):
30
+ """Runtime configuration for the gRPC adapter.
31
+
32
+ Populated automatically from the 'grpc' key in application.yml.
33
+ All fields have sensible defaults so the block is optional.
34
+
35
+ grpc:
36
+ port: 50051
37
+ max_workers: 10
38
+ tls:
39
+ enabled: true
40
+ cert_file: certs/server.crt
41
+ key_file: certs/server.key
42
+ ca_file: certs/ca.crt
43
+ mutual: true
44
+ """
45
+
46
+ port: int = 50051
47
+ max_workers: int = 10
48
+ tls: GrpcTlsConfig = Field(default_factory=GrpcTlsConfig)
49
+
50
+ @classmethod
51
+ def from_runtime(cls, runtime: RuntimeConfig) -> GrpcServerConfig:
52
+ """Build a GrpcServerConfig from a RuntimeConfig instance.
53
+
54
+ Reads the 'grpc' key via dot-notation. Returns all-defaults when the
55
+ key is absent or not a dict (e.g. grpc: null in YAML).
56
+ """
57
+ raw = runtime.get("grpc")
58
+ if not isinstance(raw, dict):
59
+ return cls()
60
+ return cls.model_validate(raw)
@@ -0,0 +1,57 @@
1
+ """Code-First gRPC — generate .proto from Python controllers + DTOs.
2
+
3
+ Public API:
4
+ from xime.adapters.grpc.codefirst import (
5
+ configure_grpc_codefirst,
6
+ generate, check,
7
+ ProtoInt32, ProtoUInt64, ..., # Annotated int-width overrides
8
+ )
9
+
10
+ The serving glue (CodeFirstGrpcBuilder) and marshalling import grpc/protobuf, so
11
+ they are imported lazily where used (GrpcAdapter) — keeping plain generation
12
+ usable even where the grpc runtime is absent.
13
+ Glue serving import grpc/protobuf nên được import lười tại nơi dùng (GrpcAdapter).
14
+ """
15
+
16
+ from ._config import (
17
+ CodeFirstConfig,
18
+ codefirst_registry,
19
+ configure_grpc_codefirst,
20
+ )
21
+ from ._generator import (
22
+ CheckResult,
23
+ GenerateResult,
24
+ build_proto_files,
25
+ check,
26
+ generate,
27
+ )
28
+ from ._type_map import (
29
+ ProtoInt32,
30
+ ProtoInt64,
31
+ ProtoSInt32,
32
+ ProtoSInt64,
33
+ ProtoUInt32,
34
+ ProtoUInt64,
35
+ UnsupportedTypeError,
36
+ )
37
+
38
+ __all__ = [
39
+ # Config
40
+ "configure_grpc_codefirst",
41
+ "CodeFirstConfig",
42
+ "codefirst_registry",
43
+ # Generation
44
+ "generate",
45
+ "check",
46
+ "build_proto_files",
47
+ "GenerateResult",
48
+ "CheckResult",
49
+ # Type overrides
50
+ "ProtoInt32",
51
+ "ProtoInt64",
52
+ "ProtoUInt32",
53
+ "ProtoUInt64",
54
+ "ProtoSInt32",
55
+ "ProtoSInt64",
56
+ "UnsupportedTypeError",
57
+ ]