Skip to content

Commit

Permalink
🐛 Fix resolution for routes within nested resources
Browse files Browse the repository at this point in the history
  • Loading branch information
perdy authored and migduroli committed Sep 3, 2024
1 parent 2dcc0a0 commit 518a4eb
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 12 deletions.
1 change: 1 addition & 0 deletions flama/resources/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def build(self, app: t.Optional["Flama"] = None) -> None:
from flama import Flama

super().build(app)

if (root := (self.app if isinstance(self.app, Flama) else app)) and "ddd" in self.resource._meta.namespaces:
root.resources.worker.add_repository(
self.resource._meta.name, self.resource._meta.namespaces["ddd"]["repository"]
Expand Down
10 changes: 7 additions & 3 deletions flama/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,18 +536,22 @@ def route_scope(self, scope: types.Scope) -> types.Scope:
"""
from flama import Flama

app = self.app if isinstance(self.app, Flama) else scope["app"]
path = scope["path"]
root_path = scope.get("root_path", "")
matched_params = self.path.values(path)
remaining_path = matched_params.pop("path")
matched_path = path[: -len(remaining_path)]
if isinstance(self.app, Flama):
app = self.app
root_path = ""
else:
app = scope["app"]
root_path = scope.get("root_path", "") + matched_path
return types.Scope(
{
"app": app,
"path_params": {**dict(scope.get("path_params", {})), **matched_params},
"endpoint": self.endpoint,
"root_path": root_path + matched_path,
"root_path": root_path,
"path": remaining_path,
}
)
Expand Down
59 changes: 52 additions & 7 deletions tests/resources/test_routing.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import pytest

from flama.applications import Flama
from flama.resources import BaseResource
from flama.resources.crud import CRUDResourceType
from flama.client import AsyncClient
from flama.resources import BaseResource, ResourceType
from flama.resources.crud import CRUDListResourceType
from flama.resources.routing import ResourceRoute, resource_method
from flama.routing import Route
from flama.routing import Mount, Route
from flama.sqlalchemy import SQLAlchemyModule


Expand All @@ -15,7 +16,7 @@ def app(self):

@pytest.fixture(scope="function")
def resource(self, puppy_model, puppy_schema):
class PuppyResource(BaseResource, metaclass=CRUDResourceType):
class PuppyResource(BaseResource, metaclass=CRUDListResourceType):
name = "puppy"
model = puppy_model
schema = puppy_schema
Expand All @@ -31,6 +32,7 @@ def test_init(self, resource):
"retrieve": {"tag": "retrieve"},
"update": {"tag": "update"},
"delete": {"tag": "delete"},
"list": {"tag": "list"},
},
)

Expand All @@ -43,6 +45,7 @@ def test_init(self, resource):
("/{element_id}/", {"GET", "HEAD"}, resource_route.resource.retrieve, {"tag": "retrieve"}),
("/{element_id}/", {"PUT"}, resource_route.resource.update, {"tag": "update"}),
("/{element_id}/", {"DELETE"}, resource_route.resource.delete, {"tag": "delete"}),
("/", {"GET", "HEAD"}, resource_route.resource.list, {"tag": "list"}),
]

def test_init_wrong_tags(self, resource):
Expand All @@ -55,6 +58,7 @@ def test_init_wrong_tags(self, resource):
"retrieve": {"tag": "retrieve"},
"update": {"tag": "update"},
"delete": {"tag": "delete"},
"list": {"tag": "list"},
"wrong": "wrong",
},
)
Expand All @@ -69,16 +73,57 @@ def test_mount_resource_declarative(self, resource):
assert len(app.routes) == 2
resource_route = app.routes[1]
assert isinstance(resource_route, ResourceRoute)
assert len(resource_route.routes) == 4
for route in resource_route.routes:
assert isinstance(route, Route)
assert [(route.path, route.methods, route.endpoint) for route in resource_route.routes] == [
("/", {"POST"}, resource_route.resource.create),
("/{element_id}/", {"GET", "HEAD"}, resource_route.resource.retrieve),
("/{element_id}/", {"PUT"}, resource_route.resource.update),
("/{element_id}/", {"DELETE"}, resource_route.resource.delete),
("/", {"GET", "HEAD"}, resource_route.resource.list),
]

def test_nested_mount_resource(self, resource):
app = Flama(schema=None, docs=None)
app.add_route(route=Route("/", lambda: {"Hello": "world"}))

sub_app = Flama(schema=None, docs=None)
sub_app.resources.add_resource("/puppy/", resource)
app.mount("/", sub_app)

assert len(app.router.routes) == 2

assert len(app.routes) == 2
mount = app.routes[1]
assert isinstance(mount, Mount)
assert len(mount.routes) == 1
resource_route = mount.routes[0]
assert isinstance(resource_route, ResourceRoute)
assert [(route.path, route.methods, route.endpoint) for route in resource_route.routes] == [
("/", {"POST"}, resource_route.resource.create),
("/{element_id}/", {"GET", "HEAD"}, resource_route.resource.retrieve),
("/{element_id}/", {"PUT"}, resource_route.resource.update),
("/{element_id}/", {"DELETE"}, resource_route.resource.delete),
("/", {"GET", "HEAD"}, resource_route.resource.list),
]

async def test_request_nested_resource(self, app):
class PuppyResource(BaseResource, metaclass=ResourceType):
name = "puppy"
verbose_name = "Puppy"

@resource_method("/", methods=["GET"], name="puppy-list", tags={"foo": "bar"})
async def list(self):
return {"name": "Canna"}

sub_app = Flama(schema=None, docs=None)
sub_app.resources.add_resource("/puppy/", PuppyResource)
app.mount("/", sub_app)
app.mark = 1
sub_app.mark = 2

async with AsyncClient(app) as client:
response = await client.get("/puppy/")
assert response.status_code == 200


class TestCaseResourceMethod:
def test_resource_method(self):
Expand Down
16 changes: 14 additions & 2 deletions tests/test_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from flama import endpoints, exceptions, types, url, websockets
from flama.applications import Flama
from flama.client import AsyncClient
from flama.endpoints import HTTPEndpoint, WebSocketEndpoint
from flama.injection import Component, Components
from flama.routing import BaseRoute, EndpointWrapper, Match, Mount, Route, Router, WebSocketRoute
Expand Down Expand Up @@ -415,7 +416,7 @@ def bar():
"endpoint": mount.app,
"path": "/bar",
"path_params": {"x": 1},
"root_path": "/foo/1",
"root_path": "" if used else "/foo/1",
}

@pytest.mark.parametrize(
Expand Down Expand Up @@ -830,7 +831,7 @@ async def foo():
assert route.path == "/foo/"
assert route_scope is not None
assert route_scope["path"] == "/foo/"
assert route_scope["root_path"] == "/router"
assert route_scope["root_path"] == ""
assert route_scope["endpoint"] == foo

def test_resolve_route_mount_router(self, app, router, asgi_scope):
Expand Down Expand Up @@ -928,3 +929,14 @@ def test_resolve_url(self, router, routes, result, exception):

with exception:
assert router.resolve_url("foo") == result

async def test_request_nested_app(self, app):
sub_app = Flama(docs=None, schema=None)
sub_app.add_route("/bar/", lambda: {"foo": "bar"}, ["GET"])

app.mount("/foo", sub_app)

async with AsyncClient(app) as client:
response = await client.get("/foo/bar/")
assert response.status_code == 200
assert response.json() == {"foo": "bar"}

0 comments on commit 518a4eb

Please sign in to comment.