diff --git a/esmerald/routing/apis/base.py b/esmerald/routing/apis/base.py index 19458aee..bfe26a65 100644 --- a/esmerald/routing/apis/base.py +++ b/esmerald/routing/apis/base.py @@ -387,12 +387,18 @@ def get_routes( route_kwargs = { "path": path, "handler": route_handler, - "name": name or route_handler.name or route_handler.fn.__name__, "middleware": middleware, "interceptors": interceptors, "permissions": permissions, "exception_handlers": exception_handlers, } + + route_name_list = [ + self.parent.name, + ] + + # Make sure the proper name is set for the route + route_kwargs["name"] = ":".join(route_name_list) route_path = ( Gateway(**route_kwargs) # type: ignore if isinstance(route_handler, HTTPHandler) diff --git a/esmerald/routing/gateways.py b/esmerald/routing/gateways.py index d028e87c..bf897a1b 100644 --- a/esmerald/routing/gateways.py +++ b/esmerald/routing/gateways.py @@ -60,9 +60,7 @@ def generate_operation_id( We need to be able to handle with edge cases when a view does not default a path like `/format` and a default name needs to be passed when its a class based view. """ if self.is_class_based(handler.parent): - operation_id = ( - handler.parent.__class__.__name__.lower() + f"_{name}" + handler.path_format - ) + operation_id = handler.parent.__class__.__name__.lower() + handler.path_format else: operation_id = name + handler.path_format @@ -297,6 +295,12 @@ def __init__( else: name = clean_string(handler.__class__.__name__) + else: + route_name_list = [name] + if not isinstance(handler, View) and handler.name: + route_name_list.append(handler.name) + name = ":".join(route_name_list) + # Handle middleware self.middleware = middleware or [] self._middleware: List["Middleware"] = self.handle_middleware( @@ -510,10 +514,16 @@ def __init__( if not name: if not isinstance(handler, View): - name = clean_string(handler.fn.__name__) + name = handler.name or clean_string(handler.fn.__name__) else: name = clean_string(handler.__class__.__name__) + else: + route_name_list = [name] + if not isinstance(handler, View) and handler.name: + route_name_list.append(handler.name) + name = ":".join(route_name_list) + # Handle middleware self.middleware = middleware or [] self._middleware: List["Middleware"] = self.handle_middleware( diff --git a/tests/openapi/test_duplicate_operation_id.py b/tests/openapi/test_duplicate_operation_id.py index 1d270383..54efa8f8 100644 --- a/tests/openapi/test_duplicate_operation_id.py +++ b/tests/openapi/test_duplicate_operation_id.py @@ -64,9 +64,9 @@ def test_open_api_schema(test_client_factory): "/api/v1/admin/users": { "post": { "tags": ["User"], - "summary": "Create", + "summary": "Userapiview", "description": "", - "operationId": "userapiview_create__post", + "operationId": "userapiview__post", "responses": { "201": { "description": "Successful response", @@ -79,9 +79,9 @@ def test_open_api_schema(test_client_factory): "/api/v1/admin/profiles": { "post": { "tags": ["Profile"], - "summary": "Create", + "summary": "Profileapiview", "description": "", - "operationId": "profileapiview_create__post", + "operationId": "profileapiview__post", "responses": { "201": { "description": "Successful response", diff --git a/tests/openapi/test_include_with_apiview.py b/tests/openapi/test_include_with_apiview.py index 7f65374c..1de56479 100644 --- a/tests/openapi/test_include_with_apiview.py +++ b/tests/openapi/test_include_with_apiview.py @@ -25,6 +25,7 @@ class MyAPI(value): @get( "/item", + summary="Read Item", description="Read an item", responses={ 200: OpenAPIResponse(model=Item, description="The SKU information of an item") @@ -57,7 +58,7 @@ async def read_item(self) -> JSON: ... "get": { "summary": "Read Item", "description": "Read an item", - "operationId": "myapi_read_item_item_get", + "operationId": "myapi_item_get", "responses": { "200": { "description": "The SKU information of an item", diff --git a/tests/routing/test_path_lookup.py b/tests/routing/test_path_lookup.py index 3deb4c45..1bfc8509 100644 --- a/tests/routing/test_path_lookup.py +++ b/tests/routing/test_path_lookup.py @@ -14,12 +14,30 @@ async def post_new() -> str: return "New World" +@get("/home", name="home") +async def home() -> str: + return "Hello World" + + +@post("/new-home", name="new-home") +async def post_new_home() -> str: + return "New World" + + class TestController(Controller): @get("/int", name="int") async def get_int(self, id: int) -> int: return id +class Test2Controller(Controller): + path = "/test" + + @get("/int", name="int") + async def get_int(self, id: int) -> int: + return id + + routes = [ Include( "/api", @@ -28,7 +46,10 @@ async def get_int(self, id: int) -> int: routes=[ Gateway(handler=get_hello, name="hello"), Gateway(handler=post_new, name="new"), - Gateway(handler=TestController), + Gateway(handler=home, name="home"), + Gateway(handler=post_new_home, name="new-home"), + Gateway(handler=TestController, name="test"), + Gateway(handler=Test2Controller), ], name="v1", ), @@ -38,14 +59,62 @@ async def get_int(self, id: int) -> int: ] -def test_can_reverse_lookup(test_client_factory): - with create_client(routes=routes) as client: +def test_can_reverse_simple(test_client_factory): + with create_client(routes=routes, enable_openapi=False) as client: + app = client.app + + assert app.path_for("api:v1:hello") == "/api/hello" + assert app.path_for("api:v1:new") == "/api/new" + + assert reverse("api:v1:hello") == "/api/hello" + assert reverse("api:v1:new") == "/api/new" + + +def test_can_reverse_with_gateway_and_handler_name(test_client_factory): + with create_client(routes=routes, enable_openapi=False) as client: + app = client.app + + assert app.path_for("api:v1:home:home") == "/api/home" + assert reverse("api:v1:home:home") == "/api/home" + + assert app.path_for("api:v1:new-home:new-home") == "/api/new-home" + assert reverse("api:v1:new-home:new-home") == "/api/new-home" + + +def test_can_reverse_with_controller_and_handler_name(test_client_factory): + with create_client(routes=routes, enable_openapi=False) as client: + app = client.app + + assert app.path_for("api:v1:test:int") == "/api/int" + assert reverse("api:v1:test:int") == "/api/int" + + +def test_can_reverse_with_no_controller_name_and_handler_name(test_client_factory): + with create_client(routes=routes, enable_openapi=False) as client: + app = client.app + + assert app.path_for("api:v1:test2controller:int") == "/api/test/int" + assert reverse("api:v1:test2controller:int") == "/api/test/int" + + +def test_can_reverse_lookup_all(test_client_factory): + with create_client(routes=routes, enable_openapi=False) as client: app = client.app assert app.path_for("api:v1:hello") == "/api/hello" assert app.path_for("api:v1:new") == "/api/new" + assert reverse("api:v1:hello") == "/api/hello" assert reverse("api:v1:new") == "/api/new" - assert app.path_for("api:v1:int") == "/api/int" - assert reverse("api:v1:int") == "/api/int" + assert app.path_for("api:v1:home:home") == "/api/home" + assert reverse("api:v1:home:home") == "/api/home" + + assert app.path_for("api:v1:new-home:new-home") == "/api/new-home" + assert reverse("api:v1:new-home:new-home") == "/api/new-home" + + assert app.path_for("api:v1:test:int") == "/api/int" + assert reverse("api:v1:test:int") == "/api/int" + + assert app.path_for("api:v1:test2controller:int") == "/api/test/int" + assert reverse("api:v1:test2controller:int") == "/api/test/int"