xime 0.1.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.
- xime-0.1.0/LICENSE +21 -0
- xime-0.1.0/PKG-INFO +339 -0
- xime-0.1.0/README-vn.md +245 -0
- xime-0.1.0/README.md +255 -0
- xime-0.1.0/pyproject.toml +138 -0
- xime-0.1.0/xime/__init__.py +18 -0
- xime-0.1.0/xime/adapters/__init__.py +0 -0
- xime-0.1.0/xime/adapters/grpc/__init__.py +15 -0
- xime-0.1.0/xime/adapters/grpc/_adapter.py +191 -0
- xime-0.1.0/xime/adapters/grpc/_config.py +60 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/__init__.py +57 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_builder.py +397 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_config.py +81 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_generator.py +141 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_lock.py +118 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_marshal.py +54 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_model.py +104 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_pb2_loader.py +52 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_proto_emitter.py +211 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_service_builder.py +192 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_stream_convention.py +42 -0
- xime-0.1.0/xime/adapters/grpc/codefirst/_type_map.py +163 -0
- xime-0.1.0/xime/adapters/grpc/interceptors/__init__.py +17 -0
- xime-0.1.0/xime/adapters/grpc/interceptors/_config.py +70 -0
- xime-0.1.0/xime/adapters/grpc/interceptors/_context.py +78 -0
- xime-0.1.0/xime/adapters/grpc/interceptors/_error.py +100 -0
- xime-0.1.0/xime/adapters/grpc/routing/__init__.py +5 -0
- xime-0.1.0/xime/adapters/grpc/routing/_builder.py +40 -0
- xime-0.1.0/xime/adapters/grpc/routing/_config.py +72 -0
- xime-0.1.0/xime/adapters/grpc/routing/_scanner.py +27 -0
- xime-0.1.0/xime/adapters/grpc/tls/__init__.py +1 -0
- xime-0.1.0/xime/adapters/grpc/tls/_credentials.py +26 -0
- xime-0.1.0/xime/adapters/socket/__init__.py +42 -0
- xime-0.1.0/xime/adapters/socket/_adapter.py +359 -0
- xime-0.1.0/xime/adapters/socket/_client.py +218 -0
- xime-0.1.0/xime/adapters/socket/_config.py +174 -0
- xime-0.1.0/xime/adapters/socket/_peercred.py +88 -0
- xime-0.1.0/xime/adapters/socket/_protocol.py +87 -0
- xime-0.1.0/xime/adapters/socket/_session.py +186 -0
- xime-0.1.0/xime/adapters/socket/routing/__init__.py +20 -0
- xime-0.1.0/xime/adapters/socket/routing/_builder.py +211 -0
- xime-0.1.0/xime/adapters/web/__init__.py +23 -0
- xime-0.1.0/xime/adapters/web/_adapter.py +234 -0
- xime-0.1.0/xime/adapters/web/_registry.py +20 -0
- xime-0.1.0/xime/adapters/web/middleware/__init__.py +3 -0
- xime-0.1.0/xime/adapters/web/middleware/_context.py +31 -0
- xime-0.1.0/xime/adapters/web/openapi/__init__.py +34 -0
- xime-0.1.0/xime/adapters/web/openapi/_builder.py +57 -0
- xime-0.1.0/xime/adapters/web/openapi/_config.py +73 -0
- xime-0.1.0/xime/adapters/web/routing/__init__.py +37 -0
- xime-0.1.0/xime/adapters/web/routing/_builder.py +95 -0
- xime-0.1.0/xime/adapters/web/routing/_config.py +40 -0
- xime-0.1.0/xime/adapters/web/routing/_decorators.py +62 -0
- xime-0.1.0/xime/adapters/web/routing/_scanner.py +80 -0
- xime-0.1.0/xime/adapters/web/ws/__init__.py +3 -0
- xime-0.1.0/xime/adapters/web/ws/_handler.py +97 -0
- xime-0.1.0/xime/cli/__init__.py +11 -0
- xime-0.1.0/xime/cli/_main.py +122 -0
- xime-0.1.0/xime/core/bootstrap/__init__.py +7 -0
- xime-0.1.0/xime/core/bootstrap/adapter.py +40 -0
- xime-0.1.0/xime/core/bootstrap/application.py +295 -0
- xime-0.1.0/xime/core/bootstrap/orchestrator.py +140 -0
- xime-0.1.0/xime/core/config/__init__.py +11 -0
- xime-0.1.0/xime/core/config/binding.py +128 -0
- xime-0.1.0/xime/core/config/loader.py +70 -0
- xime-0.1.0/xime/core/config/runtime.py +66 -0
- xime-0.1.0/xime/core/container/__init__.py +224 -0
- xime-0.1.0/xime/core/container/config_loader.py +115 -0
- xime-0.1.0/xime/core/container/graph.py +236 -0
- xime-0.1.0/xime/core/container/registry.py +105 -0
- xime-0.1.0/xime/core/container/resolver.py +57 -0
- xime-0.1.0/xime/core/container/scanner.py +133 -0
- xime-0.1.0/xime/core/container/validator.py +160 -0
- xime-0.1.0/xime/core/context/__init__.py +3 -0
- xime-0.1.0/xime/core/context/request_context.py +81 -0
- xime-0.1.0/xime/core/contract/__init__.py +30 -0
- xime-0.1.0/xime/core/contract/_decorators.py +69 -0
- xime-0.1.0/xime/core/contract/_scanner.py +88 -0
- xime-0.1.0/xime/core/contract/_streams.py +45 -0
- xime-0.1.0/xime/core/event/__init__.py +7 -0
- xime-0.1.0/xime/core/event/bus.py +70 -0
- xime-0.1.0/xime/core/event/handler.py +27 -0
- xime-0.1.0/xime/core/exception/__init__.py +47 -0
- xime-0.1.0/xime/core/exception/framework.py +246 -0
- xime-0.1.0/xime/core/lifecycle/__init__.py +8 -0
- xime-0.1.0/xime/core/lifecycle/hooks.py +48 -0
- xime-0.1.0/xime/core/lifecycle/manager.py +67 -0
- xime-0.1.0/xime/core/metadata/__init__.py +15 -0
- xime-0.1.0/xime/core/metadata/type_utils.py +97 -0
- xime-0.1.0/xime/core/security/__init__.py +21 -0
- xime-0.1.0/xime/core/security/authentication.py +38 -0
- xime-0.1.0/xime/core/security/authorization.py +36 -0
- xime-0.1.0/xime/core/security/context.py +56 -0
- xime-0.1.0/xime/core/security/enums.py +15 -0
- xime-0.1.0/xime/core/security/session.py +58 -0
- xime-0.1.0/xime/core/transaction/__init__.py +7 -0
- xime-0.1.0/xime/core/transaction/context.py +27 -0
- xime-0.1.0/xime/core/transaction/manager.py +39 -0
- xime-0.1.0/xime/starters/__init__.py +0 -0
- xime-0.1.0/xime/starters/jwt/__init__.py +18 -0
- xime-0.1.0/xime/starters/jwt/_config.py +62 -0
- xime-0.1.0/xime/starters/jwt/_key_context.py +41 -0
- xime-0.1.0/xime/starters/jwt/_middleware.py +82 -0
- xime-0.1.0/xime/starters/jwt/_signer.py +99 -0
- xime-0.1.0/xime/starters/jwt/_verifier.py +85 -0
- xime-0.1.0/xime/starters/scheduler/__init__.py +16 -0
- xime-0.1.0/xime/starters/scheduler/_config.py +108 -0
- xime-0.1.0/xime/starters/scheduler/_job.py +26 -0
- xime-0.1.0/xime/starters/scheduler/_runner.py +142 -0
- xime-0.1.0/xime/starters/sqlalchemy/__init__.py +20 -0
- xime-0.1.0/xime/starters/sqlalchemy/base.py +45 -0
- xime-0.1.0/xime/starters/sqlalchemy/engine.py +53 -0
- xime-0.1.0/xime/starters/sqlalchemy/session.py +59 -0
- xime-0.1.0/xime/starters/sqlalchemy/transaction.py +84 -0
- xime-0.1.0/xime/testing/__init__.py +6 -0
- xime-0.1.0/xime/testing/_app.py +177 -0
- xime-0.1.0/xime/testing/_fakes.py +51 -0
xime-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nguyễn Hữu Thắng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
xime-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Spring Boot-inspired dependency injection and convention framework for Python backends
|
|
5
|
+
Project-URL: Homepage, https://github.com/nguyen-huu-thang/xime-framework
|
|
6
|
+
Project-URL: Repository, https://github.com/nguyen-huu-thang/xime-framework
|
|
7
|
+
Project-URL: Issues, https://github.com/nguyen-huu-thang/xime-framework/issues
|
|
8
|
+
Author-email: Nguyễn Hữu Thắng <nguyen-huu-thang@outlook.com>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2025 Nguyễn Hữu Thắng
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: backend,convention,dependency-injection,fastapi,framework,spring-boot
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Framework :: FastAPI
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
40
|
+
Requires-Python: >=3.11
|
|
41
|
+
Requires-Dist: dependency-injector>=4.41.0
|
|
42
|
+
Requires-Dist: fastapi>=0.110.0
|
|
43
|
+
Requires-Dist: pydantic>=2.0
|
|
44
|
+
Requires-Dist: pyyaml>=6.0
|
|
45
|
+
Provides-Extra: all
|
|
46
|
+
Requires-Dist: apscheduler>=4.0; extra == 'all'
|
|
47
|
+
Requires-Dist: grpcio-tools>=1.60; extra == 'all'
|
|
48
|
+
Requires-Dist: grpcio>=1.60; extra == 'all'
|
|
49
|
+
Requires-Dist: msgpack>=1.0; extra == 'all'
|
|
50
|
+
Requires-Dist: protobuf>=4.25; extra == 'all'
|
|
51
|
+
Requires-Dist: pyjwt>=2.8; extra == 'all'
|
|
52
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'all'
|
|
53
|
+
Requires-Dist: uvicorn[standard]>=0.27; extra == 'all'
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: aiosqlite>=0.20; extra == 'dev'
|
|
56
|
+
Requires-Dist: anyio>=4.0; extra == 'dev'
|
|
57
|
+
Requires-Dist: apscheduler>=4.0; extra == 'dev'
|
|
58
|
+
Requires-Dist: asyncpg>=0.29; extra == 'dev'
|
|
59
|
+
Requires-Dist: grpcio-tools>=1.60; extra == 'dev'
|
|
60
|
+
Requires-Dist: grpcio>=1.60; extra == 'dev'
|
|
61
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
62
|
+
Requires-Dist: msgpack>=1.0; extra == 'dev'
|
|
63
|
+
Requires-Dist: protobuf>=4.25; extra == 'dev'
|
|
64
|
+
Requires-Dist: pyjwt>=2.8; extra == 'dev'
|
|
65
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
66
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
67
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'dev'
|
|
68
|
+
Requires-Dist: uvicorn[standard]>=0.27; extra == 'dev'
|
|
69
|
+
Provides-Extra: grpc
|
|
70
|
+
Requires-Dist: grpcio-tools>=1.60; extra == 'grpc'
|
|
71
|
+
Requires-Dist: grpcio>=1.60; extra == 'grpc'
|
|
72
|
+
Requires-Dist: protobuf>=4.25; extra == 'grpc'
|
|
73
|
+
Provides-Extra: jwt
|
|
74
|
+
Requires-Dist: pyjwt>=2.8; extra == 'jwt'
|
|
75
|
+
Provides-Extra: scheduler
|
|
76
|
+
Requires-Dist: apscheduler>=4.0; extra == 'scheduler'
|
|
77
|
+
Provides-Extra: socket
|
|
78
|
+
Requires-Dist: msgpack>=1.0; extra == 'socket'
|
|
79
|
+
Provides-Extra: sqlalchemy
|
|
80
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'sqlalchemy'
|
|
81
|
+
Provides-Extra: web
|
|
82
|
+
Requires-Dist: uvicorn[standard]>=0.27; extra == 'web'
|
|
83
|
+
Description-Content-Type: text/markdown
|
|
84
|
+
|
|
85
|
+
# XIME Framework
|
|
86
|
+
|
|
87
|
+
**English** | [Tiếng Việt](README-vn.md)
|
|
88
|
+
|
|
89
|
+
> A Python backend framework inspired by Spring Boot's conventions — built to respect Python's philosophy.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
XIME is not another HTTP framework. Taking inspiration from Spring Boot's convention-over-configuration approach, it sits **on top of** FastAPI, SQLAlchemy, and gRPC — providing a convention engine, automatic dependency injection, and architectural guardrails so you can focus on business logic instead of wiring.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# Before XIME — wire everything manually
|
|
97
|
+
container.user_service = providers.Singleton(
|
|
98
|
+
UserService,
|
|
99
|
+
repository=container.user_repository,
|
|
100
|
+
transaction=container.transaction_manager,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# With XIME — just write your class
|
|
104
|
+
class UserService:
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
repository: UserRepository,
|
|
108
|
+
transaction: TransactionManager,
|
|
109
|
+
):
|
|
110
|
+
self.repository = repository
|
|
111
|
+
self.transaction = transaction
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
XIME reads your type hints, scans your packages, builds the dependency graph, validates it at startup, and wires everything together — automatically.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Why XIME?
|
|
119
|
+
|
|
120
|
+
Python has excellent libraries for HTTP, databases, and serialization. What it lacks is a **convention layer** that:
|
|
121
|
+
|
|
122
|
+
- Automatically discovers and wires dependencies from constructor type hints
|
|
123
|
+
- Enforces architectural boundaries through directory structure
|
|
124
|
+
- Validates the dependency graph at startup — not at runtime when a user hits an endpoint
|
|
125
|
+
- Provides a consistent structure for Clean Architecture / DDD / Modular Monolith projects
|
|
126
|
+
|
|
127
|
+
XIME fills that gap. It does not replace FastAPI or SQLAlchemy — it makes them easier to use at scale.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## How It Works
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
Application Code
|
|
135
|
+
↓
|
|
136
|
+
XIME Core ← scanning, DI, lifecycle, config
|
|
137
|
+
↓
|
|
138
|
+
Dependency Injector ← runtime DI engine
|
|
139
|
+
↓
|
|
140
|
+
Python Objects
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
XIME's startup pipeline:
|
|
144
|
+
|
|
145
|
+
1. Load framework configuration (`config/dependency.py`)
|
|
146
|
+
2. Load runtime configuration (`resources/application.yml`)
|
|
147
|
+
3. Scan declared packages
|
|
148
|
+
4. Resolve type hints
|
|
149
|
+
5. Build dependency graph
|
|
150
|
+
6. **Validate graph** — detect cycles, missing implementations, ambiguous bindings
|
|
151
|
+
7. Create singletons
|
|
152
|
+
8. Start adapters (FastAPI, gRPC, ...)
|
|
153
|
+
|
|
154
|
+
If anything is wrong, the app **fails immediately at startup** with a clear error — not later in production.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Installation
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pip install xime
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Optional starters:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pip install xime[web] # uvicorn (ASGI server)
|
|
168
|
+
pip install xime[sqlalchemy] # async SQLAlchemy
|
|
169
|
+
pip install xime[jwt] # JWT auth
|
|
170
|
+
pip install xime[scheduler] # cron-style tasks
|
|
171
|
+
pip install xime[grpc] # gRPC adapter
|
|
172
|
+
pip install xime[all] # everything above
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Quick Start
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# app/main.py — REST only
|
|
181
|
+
from xime import Application
|
|
182
|
+
from xime.adapters.web import WebAdapter
|
|
183
|
+
|
|
184
|
+
app = Application()
|
|
185
|
+
app.use(WebAdapter())
|
|
186
|
+
app.run()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# app/main.py — REST + gRPC simultaneously
|
|
191
|
+
from xime import Application
|
|
192
|
+
from xime.adapters.web import WebAdapter
|
|
193
|
+
from xime.adapters.grpc import GrpcAdapter
|
|
194
|
+
|
|
195
|
+
app = Application()
|
|
196
|
+
app.use(WebAdapter())
|
|
197
|
+
app.use(GrpcAdapter())
|
|
198
|
+
app.run()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
# app/main.py — Multiple servers (public API + internal admin)
|
|
203
|
+
from xime import Application
|
|
204
|
+
from xime.adapters.web import WebAdapter
|
|
205
|
+
|
|
206
|
+
app = Application()
|
|
207
|
+
app.use(WebAdapter()) # server_id="default", port from application.yml
|
|
208
|
+
app.use(WebAdapter("admin", "127.0.0.1", 8081)) # server_id="admin", explicit host/port
|
|
209
|
+
app.run()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# app/config/dependency.py
|
|
214
|
+
from xime import BindingConfig
|
|
215
|
+
|
|
216
|
+
dependency = BindingConfig()
|
|
217
|
+
dependency.scan("application.usecase", "infrastructure.repository")
|
|
218
|
+
dependency.bind({UserRepository: JpaUserRepository})
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
# app/api/rest/user_controller.py
|
|
223
|
+
from xime.adapters.web.routing import get, post
|
|
224
|
+
|
|
225
|
+
class UserController:
|
|
226
|
+
prefix = "/users"
|
|
227
|
+
|
|
228
|
+
def __init__(self, use_case: GetUserUseCase) -> None:
|
|
229
|
+
self._use_case = use_case
|
|
230
|
+
|
|
231
|
+
@get("/{user_id}", response_model=UserResponse)
|
|
232
|
+
async def get_user(self, user_id: int) -> UserResponse:
|
|
233
|
+
return await self._use_case.execute(user_id)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# app/main.py
|
|
238
|
+
from xime import Application
|
|
239
|
+
from xime.adapters.web import WebAdapter
|
|
240
|
+
|
|
241
|
+
app = Application()
|
|
242
|
+
app.use(WebAdapter())
|
|
243
|
+
app.run()
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
python app/main.py
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Features
|
|
253
|
+
|
|
254
|
+
| Feature | Description |
|
|
255
|
+
| --- | --- |
|
|
256
|
+
| **Constructor Injection** | Declare dependencies as constructor params — XIME wires them |
|
|
257
|
+
| **Directory-Driven DI** | Package location determines component role — no annotations |
|
|
258
|
+
| **Interface Binding** | Explicit `Protocol` → implementation mapping, validated at startup |
|
|
259
|
+
| **Fail Fast** | Circular deps, missing implementations, ambiguous bindings → startup error |
|
|
260
|
+
| **Lifecycle Hooks** | `PostConstruct`, `PreDestroy` for managed startup/shutdown |
|
|
261
|
+
| **Initialization Order** | `dependency.order([A, B, C])` — control `post_construct()` execution order across independent classes |
|
|
262
|
+
| **Multi-Server** | Multiple `WebAdapter` / `GrpcAdapter` / `SocketAdapter` per process, each with its own `server_id` |
|
|
263
|
+
| **Event Bus** | Internal pub/sub for decoupled domain events |
|
|
264
|
+
| **Request Context** | Per-request data via `ContextVar`, set by adapters |
|
|
265
|
+
| **Security Context** | `AuthenticationManager`, `AuthorizationManager` in core |
|
|
266
|
+
| **Two-Layer Config** | Framework config (Python) + Runtime config (YAML) |
|
|
267
|
+
| **Transaction API** | Explicit `async with self.transaction():` — no hidden AOP |
|
|
268
|
+
| **Class-Based Controllers** | Controllers are DI singletons, methods map to routes |
|
|
269
|
+
| **Code-First gRPC** | Write Python DTOs, XIME generates `.proto` + stubs; field-number stability via lock file |
|
|
270
|
+
| **Socket Adapter** | Unix Domain Socket IPC for same-host Native Engine calls (Linux); `@command` / `@stream` |
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Starters
|
|
275
|
+
|
|
276
|
+
Optional modules, similar to `spring-boot-starter-*`:
|
|
277
|
+
|
|
278
|
+
| Starter | What it provides | Status |
|
|
279
|
+
| --- | --- | --- |
|
|
280
|
+
| `xime.starters.sqlalchemy` | Async DB session, `SqlAlchemyTransactionManager` | ✅ Implemented |
|
|
281
|
+
| `xime.starters.jwt` | JWT signing, verification, middleware | ✅ Implemented |
|
|
282
|
+
| `xime.starters.scheduler` | Cron-style task scheduling | ✅ Implemented |
|
|
283
|
+
| `xime.starters.redis` | Redis client integration | 🔲 Planned |
|
|
284
|
+
| `xime.starters.cache` | Cache abstraction layer | 🔲 Planned |
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Design Principles
|
|
289
|
+
|
|
290
|
+
- **Explicit over implicit** — binding, routing, config are always declared, never auto-discovered by magic
|
|
291
|
+
- **Constructor injection only** — no `@inject`, no field injection, no `@autowired`
|
|
292
|
+
- **No annotations for roles** — `@service`, `@repository`, `@component` do not exist; directory determines role
|
|
293
|
+
- **Fail fast** — errors surface at startup, not at runtime
|
|
294
|
+
- **Thin wrapper** — XIME does not rewrite FastAPI, SQLAlchemy, or gRPC; it orchestrates them
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Project Status
|
|
299
|
+
|
|
300
|
+
XIME is in **active development** (v0.1.0 — Alpha). The following are implemented: core DI, lifecycle, event bus, security context, configuration, JWT starter, scheduler starter, SQLAlchemy starter, Web adapter (FastAPI + routing), gRPC adapter (proto-first + **code-first**), **Socket adapter** (Unix Domain Socket IPC), multi-server support, and initialization order (`dependency.order()`). WebSocket support is partial. Redis and Cache starters are planned.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Contributing
|
|
305
|
+
|
|
306
|
+
XIME is a solo project that needs community help to grow. There is still ground to cover: completing WebSocket support, Redis/Cache starters, CLI scaffolding, testing utilities, and more.
|
|
307
|
+
|
|
308
|
+
**Ways to contribute:**
|
|
309
|
+
|
|
310
|
+
- Read the [architecture docs](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/architecture.md) to understand the design
|
|
311
|
+
- Pick an open area from the [roadmap](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/contributing.md#roadmap)
|
|
312
|
+
- Open an issue to discuss a feature or bug
|
|
313
|
+
- Submit a pull request
|
|
314
|
+
|
|
315
|
+
Please read [CONTRIBUTING](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/contributing.md) before opening a PR.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Documentation
|
|
320
|
+
|
|
321
|
+
| Document | Description |
|
|
322
|
+
| --- | --- |
|
|
323
|
+
| [Getting Started](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/getting-started.md) | First app in 5 minutes |
|
|
324
|
+
| [Architecture](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/architecture.md) | How XIME is structured internally |
|
|
325
|
+
| [Core Concepts](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/core-concepts.md) | DI, interface binding, scopes |
|
|
326
|
+
| [Configuration](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/configuration.md) | Framework config + runtime YAML |
|
|
327
|
+
| [Routing](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/routing.md) | Class-based controllers, route decorators |
|
|
328
|
+
| [Transaction](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/transaction.md) | Explicit transaction management |
|
|
329
|
+
| [Code-First gRPC](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/grpc-codefirst.md) | Generate `.proto` from Python DTOs; field-number stability; `xime grpc generate/check` |
|
|
330
|
+
| [Socket Adapter](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/socket-adapter.md) | Unix Domain Socket IPC for same-host Native Engine calls |
|
|
331
|
+
| [Starters](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/starters.md) | SQLAlchemy, JWT, Scheduler |
|
|
332
|
+
| [Testing](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/testing.md) | DI overrides, fakes, test utilities |
|
|
333
|
+
| [Contributing](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/en/contributing.md) | How to contribute, roadmap |
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## License
|
|
338
|
+
|
|
339
|
+
MIT
|
xime-0.1.0/README-vn.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# XIME Framework
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **Tiếng Việt**
|
|
4
|
+
|
|
5
|
+
> Framework backend Python lấy cảm hứng từ Spring Boot — nhưng được xây dựng theo triết lý Python.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
XIME không phải một HTTP framework khác. Lấy cảm hứng từ triết lý convention-over-configuration của Spring Boot, nó nằm **phía trên** FastAPI, SQLAlchemy và gRPC — cung cấp convention engine, dependency injection tự động và các guardrail kiến trúc để bạn tập trung vào nghiệp vụ thay vì dây nhợ cấu hình.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# Trước XIME — tự kết nối mọi thứ thủ công
|
|
13
|
+
container.user_service = providers.Singleton(
|
|
14
|
+
UserService,
|
|
15
|
+
repository=container.user_repository,
|
|
16
|
+
transaction=container.transaction_manager,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Với XIME — chỉ cần viết class
|
|
20
|
+
class UserService:
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
repository: UserRepository,
|
|
24
|
+
transaction: TransactionManager,
|
|
25
|
+
):
|
|
26
|
+
self.repository = repository
|
|
27
|
+
self.transaction = transaction
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
XIME đọc type hint, quét package, xây dựng dependency graph, validate tại startup và kết nối mọi thứ — tự động.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Tại sao cần XIME?
|
|
35
|
+
|
|
36
|
+
Python có các thư viện xuất sắc cho HTTP, database và serialization. Điều còn thiếu là một **tầng convention** có thể:
|
|
37
|
+
|
|
38
|
+
- Tự động phát hiện và kết nối dependency từ type hint của constructor
|
|
39
|
+
- Áp dụng ranh giới kiến trúc thông qua cấu trúc thư mục
|
|
40
|
+
- Validate dependency graph lúc startup — không phải lúc runtime khi user gọi endpoint
|
|
41
|
+
- Cung cấp cấu trúc nhất quán cho các project theo Clean Architecture / DDD / Modular Monolith
|
|
42
|
+
|
|
43
|
+
XIME lấp đầy khoảng trống đó. Nó không thay thế FastAPI hay SQLAlchemy — nó giúp chúng dễ dùng hơn ở quy mô lớn.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Cách hoạt động
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
Application Code
|
|
51
|
+
↓
|
|
52
|
+
XIME Core ← scanning, DI, lifecycle, config
|
|
53
|
+
↓
|
|
54
|
+
Dependency Injector ← DI engine runtime
|
|
55
|
+
↓
|
|
56
|
+
Python Objects
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Trình tự khởi động của XIME:
|
|
60
|
+
|
|
61
|
+
1. Nạp framework configuration (`config/dependency.py`)
|
|
62
|
+
2. Nạp runtime configuration (`resources/application.yml`)
|
|
63
|
+
3. Quét các package được khai báo
|
|
64
|
+
4. Phân giải type hint
|
|
65
|
+
5. Xây dựng dependency graph
|
|
66
|
+
6. **Validate graph** — phát hiện vòng lặp, thiếu implementation, binding mơ hồ
|
|
67
|
+
7. Tạo singleton
|
|
68
|
+
8. Khởi động adapter (FastAPI, gRPC, ...)
|
|
69
|
+
|
|
70
|
+
Nếu có vấn đề, app **thất bại ngay lúc startup** với thông báo rõ ràng — không phải sau đó trên production.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Cài đặt
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install xime
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Starters tùy chọn:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install xime[web] # uvicorn (ASGI server)
|
|
84
|
+
pip install xime[sqlalchemy] # async SQLAlchemy
|
|
85
|
+
pip install xime[jwt] # xác thực JWT
|
|
86
|
+
pip install xime[scheduler] # lập lịch tác vụ kiểu cron
|
|
87
|
+
pip install xime[grpc] # gRPC adapter
|
|
88
|
+
pip install xime[all] # tất cả ở trên
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Bắt đầu nhanh
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# app/main.py — REST only
|
|
97
|
+
from xime import Application
|
|
98
|
+
from xime.adapters.web import WebAdapter
|
|
99
|
+
|
|
100
|
+
app = Application()
|
|
101
|
+
app.use(WebAdapter())
|
|
102
|
+
app.run()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# app/main.py — REST + gRPC đồng thời
|
|
107
|
+
from xime import Application
|
|
108
|
+
from xime.adapters.web import WebAdapter
|
|
109
|
+
from xime.adapters.grpc import GrpcAdapter
|
|
110
|
+
|
|
111
|
+
app = Application()
|
|
112
|
+
app.use(WebAdapter())
|
|
113
|
+
app.use(GrpcAdapter())
|
|
114
|
+
app.run()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# app/main.py — Nhiều server (public API + internal admin)
|
|
119
|
+
from xime import Application
|
|
120
|
+
from xime.adapters.web import WebAdapter
|
|
121
|
+
|
|
122
|
+
app = Application()
|
|
123
|
+
app.use(WebAdapter()) # server_id="default", port từ application.yml
|
|
124
|
+
app.use(WebAdapter("admin", "127.0.0.1", 8081)) # server_id="admin", host/port tường minh
|
|
125
|
+
app.run()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# app/config/dependency.py
|
|
130
|
+
from xime import BindingConfig
|
|
131
|
+
|
|
132
|
+
dependency = BindingConfig()
|
|
133
|
+
dependency.scan("application.usecase", "infrastructure.repository")
|
|
134
|
+
dependency.bind({UserRepository: JpaUserRepository})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# app/api/rest/user_controller.py
|
|
139
|
+
from xime.adapters.web.routing import get, post
|
|
140
|
+
|
|
141
|
+
class UserController:
|
|
142
|
+
prefix = "/users"
|
|
143
|
+
|
|
144
|
+
def __init__(self, use_case: GetUserUseCase) -> None:
|
|
145
|
+
self._use_case = use_case
|
|
146
|
+
|
|
147
|
+
@get("/{user_id}", response_model=UserResponse)
|
|
148
|
+
async def get_user(self, user_id: int) -> UserResponse:
|
|
149
|
+
return await self._use_case.execute(user_id)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
python app/main.py
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Tính năng
|
|
159
|
+
|
|
160
|
+
| Tính năng | Mô tả |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| **Constructor Injection** | Khai báo dependency qua tham số constructor — XIME tự kết nối |
|
|
163
|
+
| **Directory-Driven DI** | Vị trí package quyết định vai trò component — không annotation |
|
|
164
|
+
| **Interface Binding** | Ánh xạ `Protocol` → implementation tường minh, validate lúc startup |
|
|
165
|
+
| **Fail Fast** | Vòng lặp, thiếu implementation, binding mơ hồ → lỗi startup |
|
|
166
|
+
| **Lifecycle Hooks** | `PostConstruct`, `PreDestroy` cho startup/shutdown được quản lý |
|
|
167
|
+
| **Thứ tự khởi tạo** | `dependency.order([A, B, C])` — kiểm soát thứ tự chạy `post_construct()` giữa các class độc lập |
|
|
168
|
+
| **Multi-Server** | Nhiều `WebAdapter` / `GrpcAdapter` / `SocketAdapter` cùng tiến trình, mỗi cái có `server_id` riêng |
|
|
169
|
+
| **Event Bus** | Pub/sub nội bộ cho domain event |
|
|
170
|
+
| **Request Context** | Dữ liệu theo request qua `ContextVar`, được thiết lập bởi adapter |
|
|
171
|
+
| **Security Context** | `AuthenticationManager`, `AuthorizationManager` trong core |
|
|
172
|
+
| **Two-Layer Config** | Framework config (Python) + Runtime config (YAML) |
|
|
173
|
+
| **Transaction API** | `async with self.transaction():` tường minh — không có AOP ẩn |
|
|
174
|
+
| **Class-Based Controllers** | Controller là DI singleton, method ánh xạ thành route |
|
|
175
|
+
| **Code-First gRPC** | Viết Python DTO, XIME sinh `.proto` + stub; ổn định field number qua lock file |
|
|
176
|
+
| **Socket Adapter** | IPC qua Unix Domain Socket cho Native Engine cùng máy (Linux); `@command` / `@stream` |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Starters
|
|
181
|
+
|
|
182
|
+
Module tùy chọn, tương tự `spring-boot-starter-*`:
|
|
183
|
+
|
|
184
|
+
| Starter | Cung cấp gì | Trạng thái |
|
|
185
|
+
| --- | --- | --- |
|
|
186
|
+
| `xime.starters.sqlalchemy` | Async DB session, `SqlAlchemyTransactionManager` | ✅ Đã implement |
|
|
187
|
+
| `xime.starters.jwt` | JWT signing, verification, middleware | ✅ Đã implement |
|
|
188
|
+
| `xime.starters.scheduler` | Lập lịch tác vụ kiểu cron | ✅ Đã implement |
|
|
189
|
+
| `xime.starters.redis` | Tích hợp Redis client | 🔲 Đang kế hoạch |
|
|
190
|
+
| `xime.starters.cache` | Abstraction layer cho caching | 🔲 Đang kế hoạch |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Nguyên tắc thiết kế
|
|
195
|
+
|
|
196
|
+
- **Explicit hơn implicit** — binding, routing, config luôn được khai báo tường minh, không tự động phát hiện bằng magic
|
|
197
|
+
- **Chỉ constructor injection** — không có `@inject`, field injection, hay `@autowired`
|
|
198
|
+
- **Không annotation cho vai trò** — `@service`, `@repository`, `@component` không tồn tại; thư mục quyết định vai trò
|
|
199
|
+
- **Fail fast** — lỗi xuất hiện lúc startup, không phải lúc runtime
|
|
200
|
+
- **Wrapper mỏng** — XIME không viết lại FastAPI, SQLAlchemy hay gRPC; nó điều phối chúng
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Trạng thái dự án
|
|
205
|
+
|
|
206
|
+
XIME đang trong **giai đoạn phát triển tích cực** (v0.1.0 — Alpha). Đã implement: core DI, lifecycle, event bus, security context, configuration, JWT starter, scheduler starter, SQLAlchemy starter, Web adapter (FastAPI + routing), gRPC adapter (proto-first + **code-first**), **Socket adapter** (Unix Domain Socket IPC), multi-server support và thứ tự khởi tạo (`dependency.order()`). WebSocket đang hoàn thiện. Redis và Cache starter đang được kế hoạch.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Đóng góp
|
|
211
|
+
|
|
212
|
+
XIME là dự án cá nhân cần sự giúp đỡ của cộng đồng để phát triển. Còn việc cần làm: hoàn thiện WebSocket, Redis/Cache starter, CLI scaffolding, testing utilities và nhiều hơn nữa.
|
|
213
|
+
|
|
214
|
+
**Cách đóng góp:**
|
|
215
|
+
|
|
216
|
+
- Đọc [tài liệu kiến trúc](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/architecture.md) để hiểu thiết kế
|
|
217
|
+
- Chọn một mảng từ [roadmap](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/contributing.md#roadmap)
|
|
218
|
+
- Mở issue để thảo luận tính năng hoặc bug
|
|
219
|
+
- Gửi pull request
|
|
220
|
+
|
|
221
|
+
Vui lòng đọc [CONTRIBUTING](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/contributing.md) trước khi mở PR.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Tài liệu
|
|
226
|
+
|
|
227
|
+
| Tài liệu | Mô tả |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| [Bắt đầu nhanh](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/getting-started.md) | App đầu tiên trong 5 phút |
|
|
230
|
+
| [Kiến trúc](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/architecture.md) | Cấu trúc nội bộ của XIME |
|
|
231
|
+
| [Khái niệm cốt lõi](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/core-concepts.md) | DI, interface binding, scope |
|
|
232
|
+
| [Cấu hình](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/configuration.md) | Framework config + runtime YAML |
|
|
233
|
+
| [Routing](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/routing.md) | Class-based controller, route decorator |
|
|
234
|
+
| [Transaction](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/transaction.md) | Quản lý transaction tường minh |
|
|
235
|
+
| [Code-First gRPC](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/grpc-codefirst.md) | Sinh `.proto` từ Python DTO; ổn định field number; `xime grpc generate/check` |
|
|
236
|
+
| [Socket Adapter](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/socket-adapter.md) | IPC qua Unix Domain Socket cho Native Engine cùng máy |
|
|
237
|
+
| [Starters](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/starters.md) | SQLAlchemy, JWT, Scheduler |
|
|
238
|
+
| [Testing](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/testing.md) | DI override, fake, test utilities |
|
|
239
|
+
| [Đóng góp](https://github.com/nguyen-huu-thang/xime-framework/blob/main/docs/vn/contributing.md) | Cách đóng góp, roadmap |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Giấy phép
|
|
244
|
+
|
|
245
|
+
MIT
|