@@ -10,15 +10,18 @@ This page describes:
1010
1111- [X] Controller methods.
1212- [X] API Controllers.
13+ - [X] Controllers inheritance.
1314
1415It is recommended to follow the [ MVC tutorial] ( mvc-project-template.md ) before
1516reading this page.
1617
17- /// admonition | For Flask users
18+ /// admonition | For Flask users.
1819 type: tip
20+
1921If you come from Flask, controllers in BlackSheep can be considered
2022equivalent to Flask's Blueprints, as they allow to group request handlers
2123in dedicated modules and classes.
24+
2225///
2326
2427## The Controller class
@@ -98,6 +101,9 @@ The following example shows how dependency injection can be used in
98101controller constructors, and an implementation of the ` on_request ` method:
99102
100103``` python
104+ from blacksheep import Application
105+ from blacksheep.server.controllers import Controller, get
106+
101107
102108app = Application()
103109
@@ -108,11 +114,12 @@ class Settings:
108114 self .greetings = greetings
109115
110116
117+ app.services.add_instance(Settings(value))
118+
119+
111120class Home (Controller ):
112121
113- def __init__ (self , settings : Settings):
114- # controllers are instantiated dynamically at every web request
115- self .settings = settings
122+ settings: Settings
116123
117124 async def on_request (self , request : Request):
118125 print (" [*] Received a request!!" )
@@ -123,13 +130,6 @@ class Home(Controller):
123130 @get (" /" )
124131 async def index (self , request : Request):
125132 return text(self .greet())
126-
127- # when configuring the application, register
128- # a singleton of the application settings,
129- # this service is automatically injected into request handlers
130- # having a signature parameter type annotated `: Settings`, or
131- # having name "settings", without type annotations
132- app.services.add_instance(Settings(value))
133133```
134134
135135The dependency can also be described as class property:
@@ -146,7 +146,7 @@ If route methods (e.g. `head`, `get`, `post`, `put`, `patch`) from
146146instance for controllers is used. It is also possible to use a specific router,
147147as long as this router is bound to the application object:
148148
149- ``` py
149+ ``` python
150150from blacksheep.server.routing import RoutesRegistry
151151
152152
@@ -155,6 +155,35 @@ app.controllers_router = RoutesRegistry()
155155get = app.controllers_router.get
156156```
157157
158+ ### route classmethod
159+
160+ The ` route ` ` classmethod ` can be used to define base routes for all request
161+ handlers defined on a Controller class. In the following example, the actual
162+ routes become: ` /home ` and ` /home/about ` .
163+
164+ ``` python
165+ from blacksheep import Application
166+ from blacksheep.server.controllers import Controller, get
167+
168+
169+ app = Application()
170+
171+
172+ class Home (Controller ):
173+
174+ @ classmethod
175+ def route (cls ):
176+ return " /home/"
177+
178+ @get (" /" )
179+ def home (self ):
180+ return self .ok({" message" : " Hello!" })
181+
182+ @get (" /about" )
183+ def about (self ):
184+ return self .ok({" message" : " About..." })
185+ ```
186+
158187## The APIController class
159188
160189The ` APIController ` class is a kind of ` Controller ` dedicated to API
@@ -241,3 +270,120 @@ class Cats(APIController):
241270
242271 ...
243272```
273+
274+ ## Controllers inheritance
275+
276+ Since version ` 2.3.0 ` , the framework supports routes inheritance in controllers.
277+ Consider the following example:
278+
279+ ``` python {linenums="1" hl_lines="8-9 12-13 15 21-21"}
280+ from blacksheep import Application
281+ from blacksheep.server.controllers import Controller, get
282+
283+
284+ app = Application()
285+
286+
287+ class BaseController (Controller ):
288+ path: ClassVar[str ] = " base"
289+
290+ @ classmethod
291+ def route (cls ) -> Optional[str ]:
292+ return f " /api/ { cls .path} "
293+
294+ @get (" /foo" ) # /api/base/foo
295+ def foo (self ):
296+ return self .ok(self .__class__ .__name__ )
297+
298+
299+ class Derived (BaseController ):
300+ path = " derived"
301+
302+ # /api/derived/foo (inherited from the base class)
303+ ```
304+
305+ In the example above, the following routes are configured:
306+
307+ - ` /api/base/foo ` , defined in ` BaseController `
308+ - ` /api/derived/foo ` , defined in ` Derived `
309+
310+ To exclude the routes registered in a base controller class, decorate the class
311+ using the ` @abstract() ` decorator imported from ` blacksheep.server.controllers ` .
312+
313+ ``` python
314+ from blacksheep.server.controllers import Controller, abstract, get
315+
316+
317+ @abstract ()
318+ class BaseController (Controller ):
319+ @get (" /hello-world" )
320+ ```
321+
322+ The following example illustrates a scenario in which a base class defines a
323+ ` /hello-world ` route, inherited in sub-classes that each apply a different
324+ base route. The ` ControllerTwo ` class defines one more route, which is also
325+ inherited by ` ControllerTwoBis ` ; and this last class defines one more specific
326+ route.
327+
328+ ``` python
329+ from blacksheep import Application
330+ from blacksheep.server.controllers import Controller, abstract, get
331+
332+
333+ app = Application()
334+
335+
336+ @abstract ()
337+ class BaseController (Controller ):
338+ @get (" /hello-world" )
339+ def index (self ):
340+ # Note: the route /hello-world itself will not be registered in the
341+ # router, because this class is decorated with @abstract()
342+ return self .text(f " Hello, World! { self .__class__ .__name__ } " )
343+
344+
345+ class ControllerOne (BaseController ):
346+ @ classmethod
347+ def route (cls ) -> str :
348+ return " /one"
349+
350+ # /one/hello-world
351+
352+
353+ class ControllerTwo (BaseController ):
354+ @ classmethod
355+ def route (cls ) -> str :
356+ return " /two"
357+
358+ # /two/hello-world
359+
360+ @get (" /specific-route" ) # /two/specific-route
361+ def specific_route (self ):
362+ return self .text(f " This is a specific route in { self .__class__ .__name__ } " )
363+
364+
365+ class ControllerTwoBis (ControllerTwo ):
366+ @ classmethod
367+ def route (cls ) -> str :
368+ return " /two-bis"
369+
370+ # /two-bis/hello-world
371+
372+ # /two-bis/specific-route
373+
374+ @get (" /specific-route-2" ) # /two-bis/specific-route-2
375+ def specific_route (self ):
376+ return self .text(f " This is another route in { self .__class__ .__name__ } " )
377+ ```
378+
379+ All routes of this example, with their respective response texts, are:
380+
381+ - ` /one/hello-world ` :material-arrow-right: "Hello, World! ControllerOne"
382+ - ` /two/hello-world ` :material-arrow-right: "Hello, World! ControllerTwo"
383+ - ` /two-bis/hello-world ` :material-arrow-right: "Hello, World! ControllerTwoBis"
384+ - ` /two/specific-route ` :material-arrow-right: "This is a specific route in ControllerTwo"
385+ - ` /two-bis/specific-route ` :material-arrow-right: "This is a specific route in ControllerTwoBis"
386+ - ` /two-bis/specific-route-2 ` :material-arrow-right: "This is another route in ControllerTwoBis"
387+
388+ Controller types and their dependencies are resolved appropriately for each
389+ request handler,
0 commit comments