Skip to content

Commit d07e40f

Browse files
Fix #591 (#601)
1 parent 99678db commit d07e40f

File tree

4 files changed

+84
-15
lines changed

4 files changed

+84
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [2.4.1] - 2025-09-??
8+
## [2.4.1] - 2025-09-28
99

1010
- Correct bug in controller inheritance that would prevent argument types and
1111
return type hints from working as expected ([#594](https://github.com/Neoteroi/BlackSheep/pull/594)).
@@ -46,6 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4646
Replace the Application `exception_handlers` dictionary with a user defined
4747
dictionary that validates values, and change a piece of code that causes
4848
a recursive error when an exception handler itself is buggy.
49+
- Add support for specifying the status code in view functions
50+
([#591](https://github.com/Neoteroi/BlackSheep/issues/591)).
4951
- Fix `license` field in `pyproject.toml`.
5052

5153
## [2.4.0] - 2025-06-22

blacksheep/server/controllers.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,36 +434,46 @@ def full_view_name(self, name: str) -> str:
434434
return f"{self.class_name()}/{name}"
435435

436436
def view(
437-
self, name: Optional[str] = None, model: Optional[Any] = None, **kwargs
437+
self,
438+
name: Optional[str] = None,
439+
model: Optional[Any] = None,
440+
status: int = 200,
441+
**kwargs,
438442
) -> Response:
439443
"""
440444
Returns a view rendered synchronously.
441445
442446
:param name: name of the template (path to the template file,
443447
optionally without '.html' extension
444448
:param model: optional model, required to render the template.
449+
:param status: optional status code for the response, default 200.
445450
:return: a Response object
446451
"""
447452
if name is None:
448453
name = self.get_default_view_name()
449454

450-
return view(self.full_view_name(name), model, **kwargs)
455+
return view(self.full_view_name(name), model, status, **kwargs)
451456

452457
async def view_async(
453-
self, name: Optional[str] = None, model: Optional[Any] = None, **kwargs
458+
self,
459+
name: Optional[str] = None,
460+
model: Any = None,
461+
status: int = 200,
462+
**kwargs,
454463
) -> Response:
455464
"""
456465
Returns a view rendered asynchronously.
457466
458467
:param name: name of the template (path to the template file,
459468
optionally without '.html' extension
460469
:param model: optional model, required to render the template.
470+
:param status: optional status code for the response, default 200.
461471
:return: a Response object
462472
"""
463473
if name is None:
464474
name = self.get_default_view_name()
465475

466-
return await view_async(self.full_view_name(name), model, **kwargs)
476+
return await view_async(self.full_view_name(name), model, status, **kwargs)
467477

468478

469479
class APIController(Controller):

blacksheep/server/responses.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -320,14 +320,14 @@ def file(
320320
return _file(value, content_type, content_disposition, file_name)
321321

322322

323-
def _create_html_response(html: str):
323+
def _create_html_response(html: str, status: int = 200):
324324
"""Creates a Response to serve dynamic HTML. Caching is disabled."""
325-
return Response(200, [(b"Cache-Control", b"no-cache")]).with_content(
325+
return Response(status, [(b"Cache-Control", b"no-cache")]).with_content(
326326
Content(b"text/html; charset=utf-8", html.encode("utf8"))
327327
)
328328

329329

330-
def view(name: str, model: Any = None, **kwargs) -> Response:
330+
def view(name: str, model: Any = None, status: int = 200, **kwargs) -> Response:
331331
"""
332332
Returns a Response object with HTML obtained using synchronous rendering.
333333
@@ -338,12 +338,15 @@ def view(name: str, model: Any = None, **kwargs) -> Response:
338338
renderer = html_settings.renderer
339339
if model:
340340
return _create_html_response(
341-
renderer.render(name, html_settings.model_to_params(model), **kwargs)
341+
renderer.render(name, html_settings.model_to_params(model), **kwargs),
342+
status,
342343
)
343-
return _create_html_response(renderer.render(name, None, **kwargs))
344+
return _create_html_response(renderer.render(name, None, **kwargs), status)
344345

345346

346-
async def view_async(name: str, model: Any = None, **kwargs) -> Response:
347+
async def view_async(
348+
name: str, model: Any = None, status: int = 200, **kwargs
349+
) -> Response:
347350
"""
348351
Returns a Response object with HTML obtained using asynchronous rendering.
349352
@@ -356,6 +359,9 @@ async def view_async(name: str, model: Any = None, **kwargs) -> Response:
356359
return _create_html_response(
357360
await renderer.render_async(
358361
name, html_settings.model_to_params(model), **kwargs
359-
)
362+
),
363+
status,
360364
)
361-
return _create_html_response(await renderer.render_async(name, None, **kwargs))
365+
return _create_html_response(
366+
await renderer.render_async(name, None, **kwargs), status
367+
)

tests/test_templating.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,11 @@ def specific_text():
125125
</html>"""
126126

127127

128-
async def _home_scenario(app: FakeApplication, url="/", expected_text=None):
128+
async def _home_scenario(
129+
app: FakeApplication, url="/", expected_text=None, status: int = 200
130+
):
129131
await app(get_example_scope("GET", url), MockReceive(), MockSend())
132+
assert app.response is not None
130133
text = await app.response.text()
131134

132135
if expected_text is None:
@@ -143,7 +146,7 @@ async def _home_scenario(app: FakeApplication, url="/", expected_text=None):
143146
</html>"""
144147

145148
assert text == expected_text
146-
assert app.response.status == 200
149+
assert app.response.status == status
147150

148151

149152
async def _view_scenario(app: FakeApplication, expected_text, url="/"):
@@ -163,6 +166,16 @@ async def home():
163166
await _home_scenario(app)
164167

165168

169+
async def test_jinja_async_mode_with_status(home_model, async_jinja_env):
170+
app, render = get_app(True)
171+
172+
@app.router.get("/")
173+
async def home():
174+
return await render("home", home_model, status=500)
175+
176+
await _home_scenario(app, status=500)
177+
178+
166179
async def test_jinja_sync_mode(home_model):
167180
app, render = get_app(False)
168181

@@ -173,6 +186,16 @@ async def home():
173186
await _home_scenario(app)
174187

175188

189+
async def test_jinja_sync_mode_with_status(home_model):
190+
app, render = get_app(False)
191+
192+
@app.router.get("/")
193+
async def home():
194+
return render("home", home_model, 500)
195+
196+
await _home_scenario(app, status=500)
197+
198+
176199
async def test_controller_conventional_view_name(home_model):
177200
app, _ = get_app(False)
178201
app.controllers_router = RoutesRegistry()
@@ -212,6 +235,34 @@ def index(self):
212235
await _home_scenario(app, expected_text=specific_text)
213236

214237

238+
async def test_controller_conventional_view_name_async_with_status(
239+
home_model, async_jinja_env
240+
):
241+
app, _ = get_app(True)
242+
app.controllers_router = RoutesRegistry()
243+
get = app.controllers_router.get
244+
245+
class Lorem(Controller):
246+
@get()
247+
async def index(self):
248+
return await self.view_async(model=home_model, status=404)
249+
250+
await _home_scenario(app, status=404)
251+
252+
253+
async def test_controller_specific_view_name_with_status(home_model, specific_text):
254+
app, _ = get_app(False)
255+
app.controllers_router = RoutesRegistry()
256+
get = app.controllers_router.get
257+
258+
class Lorem(Controller):
259+
@get()
260+
def index(self):
261+
return self.view("specific", home_model, status=404)
262+
263+
await _home_scenario(app, expected_text=specific_text, status=404)
264+
265+
215266
async def test_controller_specific_view_name_async(
216267
home_model, specific_text, async_jinja_env
217268
):

0 commit comments

Comments
 (0)