Skip to content

Commit b5ccaad

Browse files
authored
Merge pull request #2 from KiraPC/class-depends-attributes
Introduce FastAPI Depends support
2 parents 8233bf9 + c2bb270 commit b5ccaad

File tree

6 files changed

+83
-16
lines changed

6 files changed

+83
-16
lines changed

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,27 @@ pip install fastapi-router-controller
1313
In a Class module
1414

1515
```python
16-
from fastapi import APIRouter
16+
from fastapi import APIRouter, Depends
1717
from fastapi_router_controller import Controller
1818

1919
router = APIRouter()
2020
controller = Controller(router)
2121

22+
async def amazing_fn():
23+
return 'amazing_variable'
24+
2225
@controller.resource()
2326
class ExampleController():
27+
# you can define in the Controller init some FastApi Dependency and them are automatically loaded in controller methods
28+
def __init__(self, x: Foo = Depends(amazing_fn)):
29+
self.x = x
30+
2431
@controller.route.get(
2532
'/some_aoi',
2633
summary='A sample description')
27-
def sample_api(_):
34+
def sample_api(self):
35+
print(self.x) # -> amazing_variable
36+
2837
return 'A sample response'
2938
```
3039

example/sample_app/app.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import unittest
12
from fastapi import FastAPI
3+
from fastapi.testclient import TestClient
24
from fastapi_router_controller import ControllersTags
35
from controller.sample_controller import SampleController
46
from controller.sample_controller_2 import AnotherSampleController
@@ -14,4 +16,17 @@
1416
app.include_router(sample_controller.router())
1517
app.include_router(another_sample_controller.router())
1618

17-
print(app.openapi())
19+
class TestRoutes(unittest.TestCase):
20+
def setUp(self):
21+
self.client = TestClient(app)
22+
23+
def test_get_sample_controller(self):
24+
response = self.client.get("/sample_controller/?id=1234")
25+
self.assertEqual(response.status_code, 200)
26+
27+
def test_post_sample_controller(self):
28+
response = self.client.post("/sample_controller/", json={"id": "test"})
29+
self.assertEqual(response.status_code, 201)
30+
31+
if __name__ == "__main__":
32+
unittest.main()

example/sample_app/controller/sample_controller.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter, status, Query, Body
1+
from fastapi import APIRouter, status, Query, Body, Depends
22
from fastapi_router_controller import Controller
33
from fastapi.responses import JSONResponse
44
from pydantic import BaseModel, Field
@@ -12,20 +12,34 @@
1212
class SampleObject(BaseModel):
1313
id: str = Field(..., description='sample id')
1414

15+
def to_json(self):
16+
return {'id': self.id}
17+
18+
class Foo():
19+
def create_foo(_, item):
20+
print('Created Foo', str(item))
21+
22+
def get_foo():
23+
return Foo()
24+
1525
# With the "resource" decorator define the controller Class linked to the Controller router arguments
1626
@controller.resource()
1727
class SampleController():
28+
def __init__(self, foo: Foo = Depends(get_foo)):
29+
self.foo = foo
30+
1831
@controller.route.get(
1932
'/',
2033
tags=['sample_controller'],
2134
summary='return a sample object')
2235
def sample_get_request(self, id: str = Query(..., title="itemId", description="The id of the sample object")):
23-
return JSONResponse(status_code=status.HTTP_200_OK, content=SampleObject(id))
36+
return JSONResponse(status_code=status.HTTP_200_OK, content=SampleObject(**{'id': id}).to_json())
2437

2538
@controller.route.post(
2639
'/',
2740
tags=['sample_controller'],
2841
summary='create another sample object',
2942
status_code=201)
30-
def sample_post_request(self, simple_object: SampleObject = Body(None, title="SampleObject", description="A sample object model")):
43+
def sample_post_request(self, simple_object: SampleObject = Body({}, title="SampleObject", description="A sample object model")):
44+
self.foo.create_foo(simple_object)
3145
return JSONResponse(status_code=status.HTTP_201_CREATED, content={})

example/sample_app_with_auto_import/app.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import unittest
12
from fastapi import FastAPI
3+
from fastapi.testclient import TestClient
24
from fastapi_router_controller import Controller, ControllersTags
35

46
# just import the main package to load all the controllers in
@@ -13,4 +15,17 @@
1315
for router in Controller.routers():
1416
app.include_router(router)
1517

16-
print(app.openapi())
18+
class TestRoutes(unittest.TestCase):
19+
def setUp(self):
20+
self.client = TestClient(app)
21+
22+
def test_get_sample_extended_controller(self):
23+
response = self.client.get("/sample_extended_controller/?id=1234")
24+
self.assertEqual(response.status_code, 200)
25+
26+
def test_get_sample_extended_controller_parent_api(self):
27+
response = self.client.get("/sample_extended_controller/parent_api")
28+
self.assertEqual(response.status_code, 200)
29+
30+
if __name__ == "__main__":
31+
unittest.main()

example/sample_app_with_auto_import/controller/sample_controller.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
class SampleObject(BaseModel):
1515
id: str = Field(..., description='sample id')
1616

17+
def to_json(self):
18+
return {'id': self.id}
19+
1720
# With the "use" decorator the lib save the Controller Class to load it automatically
1821
@controller.use()
1922
# With the "resource" decorator define the controller Class linked to the Controller router arguments
@@ -24,7 +27,7 @@ class SampleController(SampleParentController):
2427
tags=['sample_extended_controller'],
2528
summary='return a sample object')
2629
def sample_get_request(self, id: str = Query(..., title="itemId", description="The id of the sample object")):
27-
return JSONResponse(status_code=status.HTTP_200_OK, content=SampleObject(id))
30+
return JSONResponse(status_code=status.HTTP_200_OK, content=SampleObject(**{'id': id}).to_json())
2831

2932
@controller.route.post(
3033
'/',

fastapi_router_controller/lib/controller.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from fastapi import APIRouter
1+
import inspect
2+
from fastapi import APIRouter, Depends
23

34
OPEN_API_TAGS = []
45
__app_controllers__ = []
@@ -32,7 +33,8 @@ class Controller():
3233
3334
It expose some utilities and decorator functions to define a router controller class
3435
'''
35-
router: APIRouter
36+
RC_KEY = '__router__'
37+
SIGNATURE_KEY = '__signature__'
3638

3739
def __init__(self, router: APIRouter, openapi_tag: dict = None) -> None:
3840
'''
@@ -63,7 +65,8 @@ def resource(self):
6365
A decorator function to mark a Class as a Controller
6466
'''
6567
def wrapper(cls):
66-
if hasattr(cls, '__router__'):
68+
# check if cls was extended from another Controller
69+
if hasattr(cls, Controller.RC_KEY):
6770
self.__get_parent_routes(cls.__router__)
6871

6972
cls.__router__ = self.router
@@ -87,12 +90,20 @@ def __parse_controller_router(controller):
8790
'''
8891
Private utility to parse the router controller property and extract the correct functions handlers
8992
'''
90-
for route in controller.__router__.routes:
91-
func = route.endpoint
92-
if hasattr(func, '__get__'):
93-
route.endpoint = func.__get__(controller, controller.__class__)
93+
router = getattr(controller, Controller.RC_KEY)
94+
95+
for route in router.routes:
96+
# get the signature of the endpoint function
97+
signature = inspect.signature(route.endpoint)
98+
# get the parameters of the endpoint function
99+
signature_parameters = list(signature.parameters.values())
100+
101+
# replace the class instance with the itself FastApi Dependecy
102+
signature_parameters[0] = signature_parameters[0].replace(default=Depends(controller.__class__))
103+
new_signature = signature.replace(parameters=signature_parameters)
104+
setattr(route.endpoint, Controller.SIGNATURE_KEY, new_signature)
94105

95-
return controller.__router__
106+
return router
96107

97108
@staticmethod
98109
def routers():

0 commit comments

Comments
 (0)