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

BaseRouter and Router have different signatures for add_route #1035

Open
dave42w opened this issue Nov 17, 2024 · 5 comments
Open

BaseRouter and Router have different signatures for add_route #1035

dave42w opened this issue Nov 17, 2024 · 5 comments
Labels
bug Something isn't working

Comments

@dave42w
Copy link
Contributor

dave42w commented Nov 17, 2024

Bug Description

According to mypy None of the subclasses of BaseRouter correctly implement the abstractmethod add_route.

class BaseRouter(ABC):
    @abstractmethod
    def add_route(*args) -> Union[Callable, CoroutineType, WebSocket]: ...

vs

class Router(BaseRouter):
...
    def add_route(
        self,
        route_type: HttpMethod,
        endpoint: str,
        handler: Callable,
        is_const: bool,
        exception_handler: Optional[Callable],
        injected_dependencies: dict,
    ) -> Union[Callable, CoroutineType]:

vs

class MiddlewareRouter(BaseRouter):
...
    def add_route(
        self,
        middleware_type: MiddlewareType,
        endpoint: str,
        handler: Callable,
        injected_dependencies: dict,
    ) -> Callable:

vs

class WebSocketRouter(BaseRouter):
...
    def add_route(self, endpoint: str, web_socket: WebSocket) -> None:
        self.routes[endpoint] = web_socket

The mypy errors are:

Signature of "add_route" incompatible with supertype "BaseRouter"
...
     Superclass:
         def add_route(*args: BaseRouter) -> Callable[..., Any] | CoroutineType[Any, Any, Any] | WebSocket
     Subclass:
         def add_route(self, route_type: HttpMethod, endpoint: str, handler: Callable[..., Any], is_const: bool, exception_handler: Callable[..., Any] | None, injected_dependencies: dict[Any, Any]) -> Callable[..., Any] | CoroutineType[Any, Any, Any]
...
     Superclass:
         def add_route(*args: BaseRouter) -> Callable[..., Any] | CoroutineType[Any, Any, Any] | WebSocket
     Subclass:
         def add_route(self, middleware_type: MiddlewareType, endpoint: str, handler: Callable[..., Any], injected_dependencies: dict[Any, Any]) -> Callable[..., Any]
...
     Superclass:
         def add_route(*args: BaseRouter) -> Callable[..., Any] | CoroutineType[Any, Any, Any] | WebSocket
     Subclass:
         def add_route(self, endpoint: str, web_socket: WebSocket) -> None

I can't find any use of BaseRouter in the codebase.

Options:

  1. fix the signatures in the 3 subclasses to match BaseRouter
  2. remove the abstractmethod add_route from BaseRouter (all tests still pass)
  3. remove the whole BaseRouter abstract class and make the others just classes (all tests still pass)

Steps to Reproduce

  1. run mypy 1.13 on the codebase

Your operating system

Linux

Your Python version (python --version)

3.12

Your Robyn version

main branch

Additional Info

No response

@dave42w dave42w added the bug Something isn't working label Nov 17, 2024
@sansyrox
Copy link
Member

Hey @dave42w 👋

I just want to show that every router needs to have an add_route method. It can have variable args though and the base class is supposed to show that. How can we achieve that otherwise?

@dave42w
Copy link
Contributor Author

dave42w commented Nov 19, 2024

Hey @dave42w 👋

I just want to show that every router needs to have an add_route method. It can have variable args though and the base class is supposed to show that. How can we achieve that otherwise?

I'm not sure :-) I'll have a think

@dave42w
Copy link
Contributor Author

dave42w commented Nov 19, 2024

Hey @dave42w 👋

I just want to show that every router needs to have an add_route method. It can have variable args though and the base class is supposed to show that. How can we achieve that otherwise?

I think we could fix by creating two more ABCs. One for the parameters eg BaseRouteArg and one for the return type eg BaseRouteReturn

For each type of Router we need two classes eg

RouterRouteArg, RouterRouteReturn
MiddlewareRouteArg, MiddlewareRouteReturn
WebSocketRouteArg, WebSocketRouteReturn

That seems a bit clumsy, but if we create these as dataclasses, we can move argument validation into them. That would simplify the add_route methods, which are currently long with deep nesting.

So now we have

class BaseRouter(ABC):
    @abstractmethod
    def add_route(self, params: BaseRouterArg) -> BaseRouterReturn: ...

@dave42w
Copy link
Contributor Author

dave42w commented Nov 19, 2024

If the constructor for RouterRouteArg has exactly the same arguments as the Router.add_route currently does then code changes should be minimal. eg In Robyn.add_route we construct a RouterRouteArg before passing it to add_route_response = self.router.add_route

@dave42w
Copy link
Contributor Author

dave42w commented Nov 19, 2024

Hi @sansyrox

This passes mypy. It's slightly odd to me that the ABC class needs to be used for the argument but the specific subclass can be used for the return. That's why I've added the assert.

class BaseRouterArg(ABC):
    pass

class RouterArg(BaseRouterArg):
    def __init__(self, arg1: str) -> None:
        super().__init__()
        self.arg1: str = arg1

class MiddlewareRouterArg(BaseRouterArg):
    def __init__(self, arg2: int) -> None:
        super().__init__()
        self.arg2: int = arg2

class BaseRouterReturn(ABC):
    pass

class RouterReturn(BaseRouterReturn):
    def __init__(self, arg1: str) -> None:
        super().__init__()
        self.arg1: str = arg1

class MiddlewareRouterReturn(BaseRouterReturn):
    def __init__(self, arg2: int) -> None:
        super().__init__()
        self.arg2: int = arg2

class BaseRouter(ABC):
    @abstractmethod
    def add_route(self, bra: BaseRouterArg) -> BaseRouterReturn: ...

class Router(BaseRouter):
    def add_route(self, bra: BaseRouterArg) -> RouterReturn:
        assert isinstance(bra, RouterArg)
        return RouterReturn("test")

class MiddlewareRouter(BaseRouter):
    def add_route(self, bra: BaseRouterArg) -> MiddlewareRouterReturn:
        assert isinstance(bra, MiddlewareRouterArg)
        return MiddlewareRouterReturn(1)

Are you interested in a PR for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants