fastapi-cbx 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.
cbx.py ADDED
@@ -0,0 +1,114 @@
1
+ import inspect
2
+ import logging
3
+ from collections.abc import Callable
4
+ from functools import partial
5
+ from typing import Any
6
+ from typing_extensions import Self
7
+ from fastapi import APIRouter
8
+
9
+
10
+ class CBV:
11
+ def __init__(
12
+ self,
13
+ cls: type[Any],
14
+ router: APIRouter
15
+ ):
16
+ self.logger = logging.getLogger(self.__class__.__name__)
17
+ self.router = router
18
+ self.cls = cls
19
+
20
+ def __call__(self, *args: Any, **kwargs: Any) -> Self:
21
+ self.instance = self.cls(*args, **kwargs)
22
+ for name in [
23
+ "head",
24
+ "get",
25
+ "post",
26
+ "put",
27
+ "delete",
28
+ "patch",
29
+ "options",
30
+ "trace",
31
+ "connect",
32
+ ]:
33
+ if hasattr(self.instance, name):
34
+ method = getattr(self.instance, name)
35
+ self.router.add_api_route(
36
+ path='',
37
+ endpoint=method,
38
+ methods=[name.upper()],
39
+ summary=f'{name.upper()} {self.router.prefix}',
40
+ )
41
+
42
+ return self
43
+
44
+
45
+ class CBR:
46
+ def __init__(
47
+ self,
48
+ cls: type[Any],
49
+ router: APIRouter
50
+ ):
51
+ self.logger = logging.getLogger(self.__class__.__name__)
52
+ self.router = router
53
+ self.cls = cls
54
+
55
+ def __call__(self, *args: Any, **kwargs: Any) -> Self:
56
+ self.instance = self.cls(*args, **kwargs)
57
+
58
+ for _name, endpoint in inspect.getmembers(
59
+ self.instance,
60
+ lambda x: inspect.ismethod(x) or inspect.isfunction(x)
61
+ ):
62
+ if cbx_router := endpoint.__annotations__.get("cbx_router"):
63
+ self.router.add_api_route(
64
+ path=cbx_router['path'],
65
+ endpoint=endpoint,
66
+ methods=[cbx_router["method"]],
67
+ )
68
+ return self
69
+
70
+
71
+ class cbv:
72
+ def __init__(self, router: APIRouter):
73
+ self.router = router
74
+
75
+ def __call__(self, cls: type[Any]) -> CBV:
76
+ return CBV(cls, self.router)
77
+
78
+
79
+ class cbr:
80
+
81
+ class method:
82
+ def __init__(self, method: str, path: str, *args: Any, **kwargs: Any):
83
+ self.method = method
84
+ self.path = path
85
+ self.args = args
86
+ self.kwargs = kwargs
87
+
88
+ def __call__(self, endpoint: Callable[..., Any]) -> Callable[..., Any]:
89
+ endpoint.__annotations__.setdefault(
90
+ "cbx_router",
91
+ {
92
+ "method": self.method,
93
+ "path": self.path,
94
+ "args": self.args,
95
+ "kwargs": self.kwargs,
96
+ },
97
+ )
98
+ return endpoint
99
+
100
+ head = partial(method, "HEAD")
101
+ get = partial(method, "GET")
102
+ post = partial(method, "POST")
103
+ put = partial(method, "PUT")
104
+ delete = partial(method, "DELETE")
105
+ patch = partial(method, "PATCH")
106
+ options = partial(method, "OPTIONS")
107
+ trace = partial(method, "TRACE")
108
+ connect = partial(method, "CONNECT")
109
+
110
+ def __init__(self, router: APIRouter):
111
+ self.router = router
112
+
113
+ def __call__(self, cls: type[Any]) -> CBR:
114
+ return CBR(cls, self.router)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HeHongye
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.
@@ -0,0 +1,364 @@
1
+ Metadata-Version: 2.1
2
+ Name: fastapi-cbx
3
+ Version: 0.1.0
4
+ Summary: Minimal class-based routing extension for FastAPI
5
+ Home-page: https://github.com/HeHongyeFY/fastapi-cbx
6
+ Author: HeHongye
7
+ Author-email: 18348574371@139.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: fastapi>=0.95.0
20
+
21
+ # fastapi-cbx
22
+
23
+ Code is disciplined. Reality is natural selection.
24
+ One scenario, one route.
25
+
26
+ ---
27
+
28
+ ## Overview
29
+ fastapi-cbx is a minimal, class-based routing extension for FastAPI.
30
+ It defines three orthogonal, scenario-driven routing patterns — FR, CBV, and CBR —
31
+ each responsible for a clear, distinct category of business logic.
32
+ Built around non-invasive composition,
33
+ and stays fully aligned with FastAPI's native decorator style.
34
+
35
+ ## Features
36
+ - Complements native FastAPI routing with two scenario-driven class-based patterns
37
+ - Ultra-lightweight: ~110 lines of clean code
38
+ - 100% test coverage
39
+ - No monkey patching, no metaprogramming, no hidden magic
40
+ - Fully backward compatible, no breaking changes
41
+ - Zero runtime overhead
42
+ - Native typing & dependency support
43
+ - Predictable, transparent behavior
44
+ - Minimal API surface, easy to maintain
45
+
46
+
47
+ ## Installation
48
+ ```bash
49
+ pip install fastapi-cbx
50
+ ```
51
+
52
+ ## Hierarchical Routing Patterns
53
+
54
+ ### FR(Function Route): Stateless functional route for simple standalone endpoints.
55
+
56
+ ```python
57
+ @router.get("/")
58
+ def index():
59
+ return {"route": "function"}
60
+ ```
61
+
62
+ ### CBV (Class-Based View):Class-based view for CRUD operations with singleton global dependency injection.
63
+
64
+ ```python
65
+ @cbv(router=router)
66
+ class UserCBV:
67
+
68
+ def __init__(self, db: FakeDB = db):
69
+ self.db = db
70
+
71
+ def get(self, user_id: int, session: FakeSession = Depends(get_session)):
72
+ return {
73
+ "user": self.db.get_user(user_id),
74
+ "current_user_id": session.uid
75
+ }
76
+
77
+ async def post(self, user_id: int, name: str):
78
+ return {"action": "created", "user_id": user_id, "name": name}
79
+
80
+ async def put(self, user_id: int, name: str):
81
+ return {"action": "updated", "user_id": user_id}
82
+
83
+ def delete(self, user_id: int):
84
+ return {"action": "deleted", "user_id": user_id}
85
+ ```
86
+
87
+ ### CBR (Class-Based Route): Class-based route for complex business logic with multiple endpoints and method-level dependencies.
88
+
89
+ ```python
90
+ @cbr(router=router)
91
+ class OrderCBR:
92
+
93
+ _order_prefix = "ORDER-"
94
+
95
+ def __init__(self, db: FakeDB = db):
96
+ self.db = db
97
+
98
+
99
+ @cbr.get("/info")
100
+ def info(self, order_id: int, session: FakeSession = Depends(get_session)):
101
+ return {
102
+ "order_id": f"{self._order_prefix}{order_id}",
103
+ "current_user_id": session.uid,
104
+ "db": self.db
105
+ }
106
+
107
+
108
+ @cbr.post("/create")
109
+ async def create(self, order_id: int, name: str):
110
+ return {
111
+ "order_id": f"{self._order_prefix}{order_id}",
112
+ "user": name,
113
+ "db_tag": str(self.db)
114
+ }
115
+
116
+ @cbr.post("/batch")
117
+ @classmethod
118
+ def batch_create(cls, total: int):
119
+ return {
120
+ "method": "classmethod",
121
+ "order_prefix": cls._order_prefix,
122
+ "batch_total": total
123
+ }
124
+
125
+
126
+ @cbr.get("/validate")
127
+ @staticmethod
128
+ def validate_order(order_id: int):
129
+ return {
130
+ "method": "staticmethod",
131
+ "is_valid": order_id > 0
132
+ }
133
+ ```
134
+
135
+ ## Example
136
+
137
+ ```python
138
+ import uvicorn
139
+ from fastapi import FastAPI, APIRouter, Depends
140
+ from cbx import cbv, cbr
141
+
142
+ # ==============================================
143
+ # Shared mock resources
144
+ # ==============================================
145
+ class FakeDB:
146
+ def __init__(self):
147
+ self.users = {1: "Alice", 2: "Bob"}
148
+ def get_user(self, user_id: int):
149
+ return self.users.get(user_id)
150
+
151
+ db = FakeDB()
152
+
153
+
154
+ class FakeSession:
155
+ def __init__(self):
156
+ self.uid = 1001
157
+
158
+
159
+ def get_session():
160
+ return FakeSession()
161
+
162
+
163
+ # ==============================================
164
+ # Application & Routers (All with prefix)
165
+ # ==============================================
166
+ app = FastAPI(title="fastapi-cbx")
167
+
168
+
169
+ # One scenario, one route
170
+
171
+ # ==============================================
172
+ # 1. FR (Function Route)
173
+ # ==============================================
174
+ router = APIRouter(prefix="/fr")
175
+
176
+ @router.get("/")
177
+ def index():
178
+ return {"route": "function"}
179
+
180
+ app.include_router(router)
181
+
182
+ # ==============================================
183
+ # 2. CBV (Class-Based View)
184
+ # ==============================================
185
+ router = APIRouter(prefix="/user")
186
+ @cbv(router=router)
187
+ class UserCBV:
188
+
189
+ def __init__(self, db: FakeDB = db):
190
+ self.db = db
191
+
192
+ def get(self, user_id: int, session: FakeSession = Depends(get_session)):
193
+ return {
194
+ "user": self.db.get_user(user_id),
195
+ "current_user_id": session.uid
196
+ }
197
+
198
+ async def post(self, user_id: int, name: str):
199
+ return {"action": "created", "user_id": user_id, "name": name}
200
+
201
+ async def put(self, user_id: int, name: str):
202
+ return {"action": "updated", "user_id": user_id}
203
+
204
+ def delete(self, user_id: int):
205
+ return {"action": "deleted", "user_id": user_id}
206
+
207
+
208
+ UserCBV(db)
209
+
210
+ app.include_router(router)
211
+
212
+ # ==============================================
213
+ # 3. CBR (Class-Based Route)
214
+ # ==============================================
215
+ router = APIRouter(prefix="/order")
216
+ @cbr(router=router)
217
+ class OrderCBR:
218
+
219
+ _order_prefix = "ORDER-"
220
+
221
+ def __init__(self, db: FakeDB = db):
222
+ self.db = db
223
+
224
+
225
+ @cbr.get("/info")
226
+ def info(self, order_id: int, session: FakeSession = Depends(get_session)):
227
+ return {
228
+ "order_id": f"{self._order_prefix}{order_id}",
229
+ "current_user_id": session.uid,
230
+ "db": self.db
231
+ }
232
+
233
+
234
+ @cbr.post("/create")
235
+ async def create(self, order_id: int, name: str):
236
+ return {
237
+ "order_id": f"{self._order_prefix}{order_id}",
238
+ "user": name,
239
+ "db_tag": str(self.db)
240
+ }
241
+
242
+ @cbr.post("/batch")
243
+ @classmethod
244
+ def batch_create(cls, total: int):
245
+ return {
246
+ "method": "classmethod",
247
+ "order_prefix": cls._order_prefix,
248
+ "batch_total": total
249
+ }
250
+
251
+
252
+ @cbr.get("/validate")
253
+ @staticmethod
254
+ def validate_order(order_id: int):
255
+ return {
256
+ "method": "staticmethod",
257
+ "is_valid": order_id > 0
258
+ }
259
+
260
+ OrderCBR(db)
261
+
262
+ app.include_router(router)
263
+
264
+ if __name__ == "__main__":
265
+ uvicorn.run(app, host="0.0.0.0", port=8000)
266
+ ```
267
+
268
+
269
+ ## Recommended Pattern
270
+
271
+ ### Resource Lifecycle Placement Rules
272
+
273
+ To build clean, performant, and maintainable Python web services (especially for ML / LLM / inference services), follow this strict but simple rule:
274
+
275
+ ---
276
+
277
+ ### Heavyweight Global Resources
278
+
279
+ **Use case:**
280
+ - Database connections & ORM engines
281
+ - Shared clients (Redis, object storage, third-party SDKs)
282
+ - LLM / transformer models
283
+ - Inference engines (vLLM, TensorRT-LLM)
284
+ - Any expensive-to-initialize component
285
+
286
+ **Where to initialize:**
287
+ Class `__init__` method
288
+
289
+ **Why:**
290
+ - Initialized exactly once at application startup
291
+ - Reused across all requests
292
+ - No redundant memory or CPU overhead
293
+ - Avoids global variables
294
+ - Natural domain encapsulation
295
+
296
+ **Example:**
297
+ ```python
298
+ # Heavy global resource (initialized once)
299
+ class FakeDB:
300
+ def __init__(self):
301
+ self.data = {}
302
+
303
+ db = FakeDB()
304
+
305
+ @cbr(router=router)
306
+ class OrderAPI:
307
+ # Inject into constructor
308
+ def __init__(self, db: FakeDB = db):
309
+ self.db = db
310
+ ```
311
+
312
+
313
+ ### Lightweight Request-Scoped Resources
314
+
315
+ **Use case:**
316
+ - Current logged-in user
317
+ - Request session
318
+ - Auth / token validation
319
+ - Request ID / tracing context
320
+ - Per-request temporary state
321
+
322
+ **Where to initialize:**
323
+ FastAPI `Depends`
324
+
325
+ **Why:**
326
+ - Created once per request
327
+ - Automatically isolated between requests
328
+ - Clean dependency injection
329
+ - Low mental overhead
330
+
331
+ **Example:**
332
+ ```python
333
+ class FakeSession:
334
+ def __init__(self):
335
+ self.user_id = 1
336
+
337
+ def get_session():
338
+ return FakeSession()
339
+
340
+ @cbr.get("/{order_id}")
341
+ def get_order(self, order_id: int, session: FakeSession = Depends(get_session)):
342
+ return {"user": session.user_id, "order_id": order_id}
343
+ ```
344
+
345
+
346
+ ## Notes & Warnings
347
+
348
+ 1. **Focus on Routing Only**
349
+ This project focuses solely on routing extension for FastAPI. It does not handle business logic, data validation (beyond FastAPI's native capabilities), or other non-routing related functionalities.
350
+
351
+ 2. **Resource Competition & Thread Safety**
352
+ The responsibility of handling resource competition and ensuring thread safety (e.g., for shared global resources like DB connections, SDK clients) lies with the user. fastapi-cbx does not provide additional thread safety mechanisms.
353
+
354
+ 3. **Avoid `__annotations__['cbx_router']` Conflict**
355
+ The library uses the `__annotations__['cbx_router']` attribute internally to bind routers. Do not manually modify or use this attribute in your code to avoid conflicts and unexpected behavior.
356
+
357
+ 4. **No Inheritance Support**
358
+ As a lightweight encapsulation for REST APIs, fastapi-cbx does not consider or support class inheritance scenarios for CBV/CBR. Each route class should be independent and self-contained.
359
+
360
+ 5. **Class Initialization Requirement**
361
+ Classes decorated with `@cbv(router=router)` or `@cbr(router=router)` must be initialized (e.g., `UserCBV(db)`, `OrderCBR(db)`) after definition. Failure to do so will result in routing not being registered correctly.
362
+
363
+ 6. **Support & Feedback**
364
+ Feel free to open issues or submit PRs on GitHub. If you find this library useful, please give it a star to support the project.
@@ -0,0 +1,6 @@
1
+ cbx.py,sha256=pVLSUgURvFHl20WkOgOv1E6D6Up1NRBgsAtDyeyyiwg,3168
2
+ fastapi_cbx-0.1.0.dist-info/LICENSE,sha256=TeMD8K7Y6w9VN4QeyriHvAhQ78Avsi8qHKsomXWayh0,1065
3
+ fastapi_cbx-0.1.0.dist-info/METADATA,sha256=va3J2aTLVYFTtwNOh3frsD5K4Wr01RQ8a3HIHF0bKu4,9955
4
+ fastapi_cbx-0.1.0.dist-info/WHEEL,sha256=BNRMDyzLkkcmlv0J8ppDQkk2VED33SesJDynr9ED1gc,91
5
+ fastapi_cbx-0.1.0.dist-info/top_level.txt,sha256=-ArKADyG5d-sxhD9UeTzv7clKtrMt4WzhiowMwaoldE,4
6
+ fastapi_cbx-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.4)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ cbx