Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reverse lookups for CBV error #474

Merged
merged 6 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion esmerald/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .routing.apis import APIView, Controller, SimpleAPIView
from .routing.gateways import Gateway, WebhookGateway, WebSocketGateway
from .routing.handlers import delete, get, head, options, patch, post, put, route, trace, websocket
from .routing.router import Include, Router
from .routing.router import Host, Include, Router
from .routing.webhooks import (
whdelete,
whead,
Expand Down Expand Up @@ -75,6 +75,7 @@
"Include",
"Inject",
"Factory",
"Host",
"Injects",
"ImproperlyConfigured",
"JSON",
Expand Down
18 changes: 13 additions & 5 deletions esmerald/core/directives/operations/show_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from rich.console import Console
from rich.table import Table

from esmerald import Gateway
from esmerald import Gateway, Router
from esmerald.core.directives.constants import ESMERALD_DISCOVER_APP
from esmerald.core.directives.env import DirectiveEnv
from esmerald.core.terminal import OutputColour, Print, Terminal
Expand All @@ -19,7 +19,6 @@
from lilya.routing import BasePath

from esmerald.applications import ChildEsmerald, Esmerald
from esmerald.routing.router import Router

printer = Print()
writer = Terminal()
Expand Down Expand Up @@ -48,6 +47,10 @@ def get_http_verb(mapping: Any) -> str:
return HttpMethod.DELETE.value
elif getattr(mapping, "header", None):
return HttpMethod.HEAD.value
elif getattr(mapping, "trace", None):
return HttpMethod.TRACE.value
elif getattr(mapping, "options", None):
return HttpMethod.OPTIONS.value
return HttpMethod.GET.value


Expand Down Expand Up @@ -77,7 +80,7 @@ def get_routes_table(app: Optional[Union["Esmerald", "ChildEsmerald"]], table: T
"""Prints the routing system"""
table.add_column("Path", style=OutputColour.GREEN, vertical="middle")
table.add_column("Path Parameters", style=OutputColour.BRIGHT_CYAN, vertical="middle")
table.add_column("Name", style=OutputColour.CYAN, vertical="middle")
table.add_column("Name & Path Lookup", style=OutputColour.CYAN, vertical="middle")
table.add_column("Type", style=OutputColour.YELLOW, vertical="middle")
table.add_column("HTTP Methods", style=OutputColour.RED, vertical="middle")

Expand Down Expand Up @@ -106,9 +109,15 @@ def parse_routes(
fn_type = "sync"

# Http methods
names = route.handler.get_lookup_path()

# We need to escape the character ':' to avoid the error
# of the table not being able to render the string
route_name = ":\u200d".join(names)

http_methods = ", ".join(sorted(route.methods))
parameters = ", ".join(sorted(route.stringify_parameters))
table.add_row(path, parameters, route.name, fn_type, http_methods)
table.add_row(path, parameters, route_name, fn_type, http_methods)
continue

route_app = getattr(route, "app", None)
Expand All @@ -118,7 +127,6 @@ def parse_routes(
path = clean_path(prefix + route.path) # type: ignore
if any(element in path for element in DOCS_ELEMENTS):
continue

parse_routes(route, table, prefix=f"{path}")

parse_routes(app, table)
Expand Down
5 changes: 3 additions & 2 deletions esmerald/openapi/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ def get_openapi_operation(
if route.handler.summary:
operation.summary = route.handler.summary
else:
operation.summary = route.handler.name.replace("_", " ").replace("-", " ").title()
name = route.handler.name or route.name
operation.summary = name.replace("_", " ").replace("-", " ").title()

# Handle the handler description
if route.handler.description:
Expand All @@ -190,7 +191,7 @@ def get_openapi_operation(

if operation_id in operation_ids:
message = (
f"Duplicate Operation ID {operation_id} for function " + f"{route.handler.__name__}"
f"Duplicate Operation ID {operation_id} for function " + f"{route.handler.fn.__name__}"
)
file_name = getattr(route.handler, "__globals__", {}).get("__file__")
if file_name:
Expand Down
8 changes: 7 additions & 1 deletion esmerald/routing/apis/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,18 @@ def get_routes(
route_kwargs = {
"path": path,
"handler": route_handler,
"name": 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)
Expand Down
23 changes: 23 additions & 0 deletions esmerald/routing/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,29 @@ def parent_levels(self) -> List[Any]:
current = current.parent
return list(reversed(levels))

def get_lookup_path(self) -> List[str]:
"""
Constructs and returns the lookup path for the current object by traversing
its parent hierarchy.

The method collects the 'name' attribute of the current object and its
ancestors, if they exist, and returns them as a list in reverse order
(from the root ancestor to the current object).

Returns:
List[str]: A list of names representing the lookup path from the root
ancestor to the current object.
"""

names = []
current: Any = self

while current:
if getattr(current, "name", None) is not None:
names.append(current.name)
current = current.parent
return list(reversed(names))

@property
def dependency_names(self) -> Set[str]:
"""
Expand Down
32 changes: 21 additions & 11 deletions esmerald/routing/gateways.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def handle_middleware(


class GatewayUtil:

def is_class_based(
self, handler: Union["HTTPHandler", "WebSocketHandler", "ParentType"]
) -> bool:
Expand All @@ -60,9 +59,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

Expand Down Expand Up @@ -286,10 +283,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(
Expand Down Expand Up @@ -328,17 +331,17 @@ def __init__(
self.operation_id = operation_id

if self.is_handler(self.handler): # type: ignore
self.handler.name = self.name

if self.operation_id or handler.operation_id is not None:
handler_id = self.generate_operation_id(
name=self.name, handler=self.handler # type: ignore
name=self.name or "",
handler=self.handler, # type: ignore
)
self.operation_id = f"{operation_id}_{handler_id}" if operation_id else handler_id

elif not handler.operation_id:
handler.operation_id = self.generate_operation_id(
name=self.name, handler=self.handler # type: ignore
name=self.name or "",
handler=self.handler, # type: ignore
)

async def handle_dispatch(self, scope: "Scope", receive: "Receive", send: "Send") -> None:
Expand Down Expand Up @@ -503,10 +506,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(
Expand Down Expand Up @@ -691,5 +700,6 @@ def __init__(

if not handler.operation_id:
handler.operation_id = self.generate_operation_id(
name=self.name, handler=self.handler # type: ignore
name=self.name,
handler=self.handler, # type: ignore
)
Loading
Loading