Skip to content
Open
3 changes: 3 additions & 0 deletions docs/_newsfragments/2167.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Resources without responder methods now emit a ``UserWarning`` for non-suffix responders.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could expand the newsfragment a bit, telling a story what the inconsistency was, and how it was addressed.

Resources must define at least one responder method (e.g., ``on_get()``, ``on_post()``).
This will raise an error in Falcon 5.0.
19 changes: 14 additions & 5 deletions falcon/routing/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING
import warnings

from falcon import constants
from falcon import responders
Expand Down Expand Up @@ -69,11 +70,19 @@ def map_http_methods(resource: object, suffix: str | None = None) -> MethodDict:
if callable(responder):
method_map[method] = responder

# If suffix is specified and doesn't map to any methods, raise an error
if suffix and not method_map:
raise SuffixedMethodNotFoundError(
'No responders found for the specified suffix'
)
# If doesn't map to any methods, raise an error
if not method_map:
resource_name = resource.__class__.__name__
if suffix:
raise SuffixedMethodNotFoundError(
f'No responders found for the specified resource: '
f'{resource_name} and suffix: {suffix}'
)
else:
warnings.warn(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it emitting our flavour of DeprecationWarning would be more consistent with the behaviour and evolution of the framework so far. We could also expand the wording a bit, explaining that adding resources without any responders is deprecated, and it would result in an error in Falcon 5.0.

'No responders (on_get, on_post, etc.) '
+ f'found for the specified resource: {resource_name}'
)

return method_map

Expand Down
3 changes: 2 additions & 1 deletion tests/asgi/test_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,8 @@ async def on_websocket(self, req, ws):

async def test_ws_simulator_collect_edge_cases(conductor):
class Resource:
pass
async def on_websocket(self, req, ws):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe adding an unrelated method (such as on_delete or whatever) would match the original intent of this test case better? (I.e., not having any on_websocket() responder.)

pass

conductor.app.add_route('/', Resource())

Expand Down
17 changes: 0 additions & 17 deletions tests/test_http_method_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@
]


@pytest.fixture
def stonewall():
return Stonewall()


@pytest.fixture
def resource_things():
return ThingsResource()
Expand All @@ -64,8 +59,6 @@ def resource_get_with_faulty_put():
def client(asgi, util):
app = util.create_app(asgi)

app.add_route('/stonewall', Stonewall())

resource_things = ThingsResource()
app.add_route('/things', resource_things)
app.add_route('/things/{id}/stuff/{sid}', resource_things)
Expand Down Expand Up @@ -115,10 +108,6 @@ def on_websocket(self, req, resp, id, sid):
self.called = True


class Stonewall:
pass


def capture(func):
@wraps(func)
def with_capture(*args, **kwargs):
Expand Down Expand Up @@ -238,12 +227,6 @@ def test_misc(self, client, resource_misc, catch_wsgiref_query_warning):
assert resource_misc.called
assert resource_misc.req.method == method

def test_methods_not_allowed_simple(self, client, stonewall):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave this stonewall test intact (until Falcon 5.0, that is), just assert with pytest.warns(...) that a deprecation warning was emitted by add_route below.

client.app.add_route('/stonewall', stonewall)
for method in ['GET', 'HEAD', 'PUT', 'PATCH']:
response = client.simulate_request(path='/stonewall', method=method)
assert response.status == falcon.HTTP_405

def test_methods_not_allowed_complex(
self, client, resource_things, catch_wsgiref_query_warning
):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_uri_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,3 +602,11 @@ def test_custom_error_on_suffix_route_not_found(client):
resource_with_suffix_routes,
suffix='bad-alt',
)


def test_custom_error_route_not_found(client):
class EmptyResource:
pass

with pytest.warns(UserWarning):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave the stonewall part, maybe this test becomes somewhat redundant. But, OTOH, it doesn't hurt.

client.app.add_route('/empty', EmptyResource())