Skip to content
This repository has been archived by the owner on Jul 28, 2024. It is now read-only.

Commit

Permalink
Add client route sort
Browse files Browse the repository at this point in the history
  • Loading branch information
Chaoyingz committed Mar 28, 2024
1 parent 323ce5b commit 19359fc
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 51 deletions.
5 changes: 3 additions & 2 deletions src/python-flect/src/flect/routing/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ROOT_ROUTE_PREFIX,
)
from flect.response import PageResponse
from flect.routing.sort import path_priority
from flect.sitemap import Sitemap
from flect.utils import load_module

Expand All @@ -32,7 +33,7 @@ class ClientRoute(BaseModel):
)
absolute_path: str = Field(
...,
description="The complete path of the route from the root, useful for matching routes.",
description="The complete path of the route from the root, useful for matching group route.",
)
loader_path: str = Field(
...,
Expand Down Expand Up @@ -183,4 +184,4 @@ def get_client_routes(
)
]

return routes
return sorted(routes, key=lambda route: path_priority(route.loader_path))
28 changes: 2 additions & 26 deletions src/python-flect/src/flect/routing/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from flect.render import generate_html, render_server_side_html
from flect.response import PageResponse
from flect.routing.client import ClientRoute, get_client_routes, parse_route_info_from_folder
from flect.routing.sort import path_priority
from flect.sitemap import generate_sitemap_xml
from flect.utils import load_module

Expand Down Expand Up @@ -163,31 +164,6 @@ async def get_sitemap_route(request: Request) -> Response:
yield APIRoute("/sitemap.xml", get_sitemap_route, methods=["GET"])


def sort_routes_by_path(route: APIRoute) -> tuple:
"""
Sorts an APIRoute object based on its path, prioritizing static paths over dynamic paths,
and ensuring catch-all routes are sorted last.
Parameters
----------
route : APIRoute
The APIRoute object to sort.
Returns
-------
tuple
A tuple that ensures the correct sorting of routes, with catch-all routes placed last.
"""
path = route.path
level = path.count("/")
is_dynamic = "{" in path
num_dynamic = path.count("{")
is_catch_all = "{path:path}" in path
first_dynamic = path.find("{") if is_dynamic else float("inf")

return is_catch_all, level, is_dynamic, num_dynamic, first_dynamic


def get_app_router(
app: ModuleType,
prebuilt_uri: Optional[str] = None,
Expand Down Expand Up @@ -228,7 +204,7 @@ def get_app_router(
chain(
navigation_routes, api_routes, (route for route in loader_routes), server_side_render_routes, sitemap_routes
),
key=sort_routes_by_path,
key=lambda r: path_priority(r.path),
):
root_router.routes.append(route)
return root_router
14 changes: 14 additions & 0 deletions src/python-flect/src/flect/routing/sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import re

from flect.constants import DYNAMIC_ROUTE_PREFIX


def path_priority(path: str) -> tuple:
path = re.sub(rf"{DYNAMIC_ROUTE_PREFIX}[^/]+/", "", path)
parts = path.split("/")

catch_all_weight = float("inf") if "{path:path}" in path else 0
depth_weight = len(parts)
dynamic_weight = int(path.count("{"))
segment_weight = parts[-2] if len(parts) >= 2 else ""
return catch_all_weight, depth_weight, segment_weight, dynamic_weight
5 changes: 3 additions & 2 deletions src/python-flect/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pytest
from fastapi import FastAPI
from flect.routing.client import get_client_routes
from flect.routing.server import generate_loader_routes, sort_routes_by_path
from flect.routing.server import generate_loader_routes
from flect.routing.sort import path_priority
from starlette.testclient import TestClient

# from flect.routing import get_client_loader_router, get_client_routes
Expand Down Expand Up @@ -31,7 +32,7 @@ def client_routes(app_folder):
@pytest.fixture(scope="session")
def loader_routes(client_routes):
loader_routes = generate_loader_routes(client_routes)
return sorted(loader_routes, key=sort_routes_by_path)
return sorted(loader_routes, key=lambda r: path_priority(r.path))


@pytest.fixture
Expand Down
71 changes: 70 additions & 1 deletion src/python-flect/tests/test_routing/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,73 @@ def test_parse_route_info_from_folder(

def test_get_client_routes(app_folder):
routes = get_client_routes(app_folder)
assert routes
expected_routes = [
{
"segment": "",
"path": "/",
"absolute_path": "/flect/",
"loader_path": "/flect/_layout/",
"index": False,
"children": [
{
"segment": "segment1",
"path": "/segment1/",
"absolute_path": "/flect/segment1/",
"loader_path": "/flect/segment1/_layout/",
"index": False,
"children": [
{
"segment": "",
"path": "/segment1/",
"absolute_path": "/flect/segment1/group__segment2/",
"loader_path": "/flect/segment1/group__segment2/_layout/",
"index": False,
"children": [
{
"segment": "segment3",
"path": "/segment1/segment3/",
"absolute_path": "/flect/segment1/group__segment2/segment3/",
"loader_path": "/flect/segment1/segment3/",
"index": False,
"children": [],
}
],
},
{
"segment": "{segment_id}",
"path": "/segment1/{segment_id}/",
"absolute_path": "/flect/segment1/dynamic__segment_id/",
"loader_path": "/flect/segment1/{segment_id}/",
"index": False,
"children": [],
},
{
"segment": "segment2",
"path": "/segment1/segment2/",
"absolute_path": "/flect/segment1/segment2/",
"loader_path": "/flect/segment1/segment2/",
"index": False,
"children": [],
},
{
"segment": "segment1",
"path": "/segment1/",
"absolute_path": "/flect/segment1/",
"loader_path": "/flect/segment1/",
"index": True,
"children": [],
},
],
},
{
"segment": "",
"path": "/",
"absolute_path": "/flect/",
"loader_path": "/flect/",
"index": True,
"children": [],
},
],
}
]
assert [route.model_dump() for route in routes] == expected_routes
20 changes: 0 additions & 20 deletions src/python-flect/tests/test_routing/test_server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from fastapi.routing import APIRoute
from flect.constants import NAVIGATION_ROUTE_PATH, ROOT_ROUTE_PREFIX
from flect.routing.server import (
generate_api_routes,
Expand All @@ -7,7 +6,6 @@
generate_server_side_render_routes,
generate_sitemap_routes,
get_app_router,
sort_routes_by_path,
)


Expand Down Expand Up @@ -48,24 +46,6 @@ def test_generate_sitemap_routes(client_routes, client):
assert response.text


def test_sort_routes():
routes = [
APIRoute("/a/", lambda: None, methods=["GET"]),
APIRoute("/a/{id}/", lambda: None, methods=["GET"]),
APIRoute("/a/b/", lambda: None, methods=["GET"]),
APIRoute("/{path:path}", lambda: None, methods=["GET"]),
APIRoute("/a/{id}/b/", lambda: None, methods=["GET"]),
]
sorted_routes = sorted(routes, key=sort_routes_by_path)
assert [route.path for route in sorted_routes] == [
"/a/",
"/a/b/",
"/a/{id}/",
"/a/{id}/b/",
"/{path:path}",
]


def test_get_app_router(app_module):
router = get_app_router(app_module)
assert router.routes
27 changes: 27 additions & 0 deletions src/python-flect/tests/test_routing/test_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from flect.routing.sort import path_priority


def test_sort_path():
paths = [
"/flect/_layout/",
"/flect/segment1/_layout/",
"/flect/segment1/segment3/",
"/flect/segment1/{segment_id}/",
"/flect/segment1/segment2/",
"/flect/segment1/",
"/flect/",
"{path:path}",
"/flect/segment1/dynamic__segment_id/",
]

assert sorted(paths, key=path_priority) == [
"/flect/",
"/flect/_layout/",
"/flect/segment1/",
"/flect/segment1/dynamic__segment_id/",
"/flect/segment1/_layout/",
"/flect/segment1/segment2/",
"/flect/segment1/segment3/",
"/flect/segment1/{segment_id}/",
"{path:path}",
]

0 comments on commit 19359fc

Please sign in to comment.