Repository: awtkns/fastapi-crudrouter (language: python)

Issue:
Route overload ignored
Hello,

Me again, just reporting this so I do not forget.

I found a workaround but I observe the following behavior and I am not sure if that is a bug or not.

Router declared as:

```python
orders_router = SQLAlchemyCRUDRouter(
    schema=OrderSchema,
    create_schema=OrderCreate,
    update_schema=OrderEdit,
    db_model=Order,
    db=get_db,
    prefix="orders",
    dependencies=[Depends(token_auth)],
)
```

Update route overloaded like this:

```python
@orders_router.put("/{order_id}")
async def update_order(
    order_id: str, update_data: OrderEdit, db=Depends(get_db), user=Depends(token_auth)
):
    print(f"Overloaded {order_id}")
```

And it is ignored, the default Update route from SQLAlchemyCRUDRouter is preferred.

However with a create route it works:

```python
@orders_router.post("")
async def create_order(
    data: OrderCreate, db=Depends(get_db), user=Depends(token_auth),
):
    print("Overloaded")
```

And if I do this:


```python

orders_router = SQLAlchemyCRUDRouter(
    schema=OrderSchema,
    create_schema=OrderCreate,
    update_schema=OrderEdit,
    db_model=Order,
    db=get_db,
    prefix="orders",
    dependencies=[Depends(token_auth)],
    update_route=False,
)
```

Then the default one is not enabled apparently and my overloaded Update route is correctly used.
This also happens with a delete one route: orders_routers.delete("/{order_id}").


Discussion / hints:
Hi @DorskFR. The documentation definitely needs to be more detailed on this, but I believe your overloading is failing because your path is `/{order_id}` instead of `/{item_id}`.  Overloading it as `/{order_id}` is actually creating a separate here. 

The code shown below should work.
```python
@orders_router.put("/{item_id}")
async def update_order(
    order_id: str, update_data: OrderEdit, db=Depends(get_db), user=Depends(token_auth)
):
    print(f"Overloaded {order_id}")
```

I'll be looking at ways of eliminating this confusion the the future (maybe by treating all text in the `{...}` the same). Let me know if this fixes the issue.
Hi @awtkns ,

Oh, my bad I did not realize that `item_id` had to be named just like this.

Thank you very much for your answer and sorry for the noise then.

No worries, it needs to be documented better


Fix patch:
diff --git a/docs/en/docs/routing.md b/docs/en/docs/routing.md
index 586f355..ca0f3a0 100644
--- a/docs/en/docs/routing.md
+++ b/docs/en/docs/routing.md
@@ -46,7 +46,6 @@ As an example, the *delete all* route can be disabled by doing the following:
 router = MemmoryCRUDRouter(schema=MyModel, deleta_all_route=False)
 ```
 
-
 ## Overriding Routes
 Should you need to add custom functionality to any of your routes any of the included routers allows you to do so. 
 Should you wish to disable a route from being generated, it can be done [here](../routing/#disabling-routes).
@@ -64,7 +63,7 @@ Routes in the CRUDRouter can be overridden by using the standard fastapi route d
     meaning that they can be customized to your heart's content.
 
 ### Overriding Example
-Below is an example where we are overriding the route `/potato/{item_id}` while using the MemoryCRUDRouter.
+Below is an example where we are overriding the routes `/potato/{item_id}` and `/potato` while using the MemoryCRUDRouter.
 
 ```python
 from pydantic import BaseModel
@@ -79,8 +78,11 @@ class Potato(BaseModel):
 app = FastAPI()
 router = CRUDRouter(model=mymodel)
 
-@router.get('/{item_id}')
+@router.get('')
 def overloaded_get_all():
-    return 'My overloaded route'
-```
+    return 'My overloaded route that returns all the items'
 
+@router.get('/{item_id}')
+def overloaded_get_one():
+    return 'My overloaded route that returns one item'
+```


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index a0584f3..1e4a230 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,23 +4,29 @@
 from .implementations import *
 
 
-@pytest.fixture(params=implementations, scope="class")
-def client(request):
-    impl = request.param
-
+def yield_test_client(app, impl):
     if impl.__name__ == "tortoise_implementation":
         from tortoise.contrib.test import initializer, finalizer
 
         initializer(["tests.implementations.tortoise_"])
-        with TestClient(impl()) as c:
+        with TestClient(app) as c:
             yield c
         finalizer()
 
     else:
-        with TestClient(impl()) as c:
+        with TestClient(app) as c:
             yield c
 
 
+@pytest.fixture(params=implementations, scope="class")
+def client(request):
+    impl = request.param
+
+    app, routers = impl()
+    [app.include_router(r) for r in routers]
+    yield from yield_test_client(app, impl)
+
+
 @pytest.fixture(
     params=[
         sqlalchemy_implementation_custom_ids,
@@ -29,13 +35,7 @@ def client(request):
     ]
 )
 def custom_id_client(request):
-    with TestClient(request.param()) as c:
-        yield c
-
-
-@pytest.fixture
-def overloaded_client():
-    yield TestClient(overloaded_app())
+    yield from yield_test_client(request.param(), request.param)
 
 
 @pytest.fixture(
@@ -47,8 +47,7 @@ def overloaded_client():
     scope="function",
 )
 def string_pk_client(request):
-    with TestClient(request.param()) as c:
-        yield c
+    yield from yield_test_client(request.param(), request.param)
 
 
 @pytest.fixture(
@@ -59,5 +58,4 @@ def string_pk_client(request):
     scope="function",
 )
 def integrity_errors_client(request):
-    with TestClient(request.param()) as c:
-        yield c
+    yield from yield_test_client(request.param(), request.param)
diff --git a/tests/implementations/__init__.py b/tests/implementations/__init__.py
index 87075e4..e09fb78 100644
--- a/tests/implementations/__init__.py
+++ b/tests/implementations/__init__.py
@@ -4,7 +4,6 @@
     databases_implementation_string_pk,
 )
 from .memory import memory_implementation
-from .overloaded import overloaded_app
 from .sqlalchemy_ import (
     sqlalchemy_implementation,
     sqlalchemy_implementation_custom_ids,
diff --git a/tests/implementations/databases_.py b/tests/implementations/databases_.py
index 3d5727a..90d7614 100644
--- a/tests/implementations/databases_.py
+++ b/tests/implementations/databases_.py
@@ -77,10 +77,8 @@ async def shutdown():
         prefix="carrot",
         tags=CUSTOM_TAGS,
     )
-    app.include_router(potato_router)
-    app.include_router(carrot_router)
 
-    return app
+    return app, [potato_router, carrot_router]
 
 
 def databases_implementation_custom_ids():
diff --git a/tests/implementations/memory.py b/tests/implementations/memory.py
index 1a0086b..cced145 100644
--- a/tests/implementations/memory.py
+++ b/tests/implementations/memory.py
@@ -6,16 +6,12 @@
 
 def memory_implementation():
     app = FastAPI()
-    app.include_router(CRUDRouter(schema=Potato, paginate=PAGINATION_SIZE))
-    app.include_router(
-        CRUDRouter(
-            schema=Carrot,
-            update_schema=CarrotUpdate,
-            tags=CUSTOM_TAGS,
-        )
+    potato_router = CRUDRouter(schema=Potato, paginate=PAGINATION_SIZE)
+    carrot_router = CRUDRouter(
+        schema=Carrot, update_schema=CarrotUpdate, tags=CUSTOM_TAGS
     )
 
-    return app
+    return app, [potato_router, carrot_router]
 
 
 if __name__ == "__main__":
diff --git a/tests/implementations/ormar_.py b/tests/implementations/ormar_.py
index 282358e..7de353b 100644
--- a/tests/implementations/ormar_.py
+++ b/tests/implementations/ormar_.py
@@ -108,23 +108,19 @@ async def shutdown():
 def ormar_implementation():
     app = get_app()
 
-    app.include_router(
-        OrmarCRUDRouter(
-            schema=PotatoModel,
-            prefix="potato",
-            paginate=PAGINATION_SIZE,
-        )
+    potato_router = OrmarCRUDRouter(
+        schema=PotatoModel,
+        prefix="potato",
+        paginate=PAGINATION_SIZE,
     )
-    app.include_router(
-        OrmarCRUDRouter(
-            schema=CarrotModel,
-            update_schema=CarrotUpdate,
-            prefix="carrot",
-            tags=CUSTOM_TAGS,
-        )
+    carrot_router = OrmarCRUDRouter(
+        schema=CarrotModel,
+        update_schema=CarrotUpdate,
+        prefix="carrot",
+        tags=CUSTOM_TAGS,
     )
 
-    return app
+    return app, [potato_router, carrot_router]
 
 
 # noinspection DuplicatedCode
diff --git a/tests/implementations/overloaded.py b/tests/implementations/overloaded.py
deleted file mode 100644
index e9b5897..0000000
--- a/tests/implementations/overloaded.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from fastapi import FastAPI
-
-from fastapi_crudrouter import MemoryCRUDRouter as CRUDRouter
-from tests import Potato
-
-
-def overloaded_app():
-    app = FastAPI()
-
-    router = CRUDRouter(schema=Potato)
-
-    @router.api_route("")
-    def overloaded_get():
-        return "Overloaded"
-
-    @router.get("/{item_id}")
-    def overloaded_get_all():
-        return "Overloaded Get All"
-
-    @router.put("/{item_id}")
-    def overloaded_put():
-        return "Overloaded Put"
-
-    @router.delete("/{item_id}")
-    def overloaded_delete():
-        return "Overloaded DELETE"
-
-    @router.post("")
-    def overloaded_get():
-        return "Overloaded Post"
-
-    @router.api_route("", methods=["DELETE"])
-    def overloaded_get():
-        return "Overloaded DELETE All"
-
-    app.include_router(router)
-
-    return app
diff --git a/tests/implementations/sqlalchemy_.py b/tests/implementations/sqlalchemy_.py
index 8cdfd92..8f630f5 100644
--- a/tests/implementations/sqlalchemy_.py
+++ b/tests/implementations/sqlalchemy_.py
@@ -63,28 +63,24 @@ class CarrotModel(Base):
         color = Column(String)
 
     Base.metadata.create_all(bind=engine)
-    app.include_router(
-        SQLAlchemyCRUDRouter(
-            schema=Potato,
-            db_model=PotatoModel,
-            db=session,
-            prefix="potato",
-            paginate=PAGINATION_SIZE,
-        )
+    potato_router = SQLAlchemyCRUDRouter(
+        schema=Potato,
+        db_model=PotatoModel,
+        db=session,
+        prefix="potato",
+        paginate=PAGINATION_SIZE,
     )
-    app.include_router(
-        SQLAlchemyCRUDRouter(
-            schema=Carrot,
-            db_model=CarrotModel,
-            db=session,
-            create_schema=CarrotCreate,
-            update_schema=CarrotUpdate,
-            prefix="carrot",
-            tags=CUSTOM_TAGS,
-        )
+    carrot_router = SQLAlchemyCRUDRouter(
+        schema=Carrot,
+        db_model=CarrotModel,
+        db=session,
+        create_schema=CarrotCreate,
+        update_schema=CarrotUpdate,
+        prefix="carrot",
+        tags=CUSTOM_TAGS,
     )
 
-    return app
+    return app, [potato_router, carrot_router]
 
 
 # noinspection DuplicatedCode
diff --git a/tests/implementations/tortoise_.py b/tests/implementations/tortoise_.py
index a9bcf1b..b650e9b 100644
--- a/tests/implementations/tortoise_.py
+++ b/tests/implementations/tortoise_.py
@@ -53,23 +53,20 @@ def tortoise_implementation():
     Tortoise.init(config=TORTOISE_ORM)
     Tortoise.generate_schemas()
 
-    app.include_router(
-        TortoiseCRUDRouter(
-            schema=Potato,
-            db_model=PotatoModel,
-            prefix="potato",
-            paginate=PAGINATION_SIZE,
-        )
+    potato_router = TortoiseCRUDRouter(
+        schema=Potato,
+        db_model=PotatoModel,
+        prefix="potato",
+        paginate=PAGINATION_SIZE,
     )
-    app.include_router(
-        TortoiseCRUDRouter(
-            schema=Carrot,
-            db_model=CarrotModel,
-            create_schema=CarrotCreate,
-            update_schema=CarrotUpdate,
-            prefix="carrot",
-            tags=CUSTOM_TAGS,
-        )
+
+    carrot_router = TortoiseCRUDRouter(
+        schema=Carrot,
+        db_model=CarrotModel,
+        create_schema=CarrotCreate,
+        update_schema=CarrotUpdate,
+        prefix="carrot",
+        tags=CUSTOM_TAGS,
     )
 
-    return app
+    return app, [potato_router, carrot_router]
diff --git a/tests/test_overloads.py b/tests/test_overloads.py
index e876505..5676b89 100644
--- a/tests/test_overloads.py
+++ b/tests/test_overloads.py
@@ -1,36 +1,86 @@
 import pytest
+from fastapi import APIRouter
 
-URL = "/potato"
+from .implementations import implementations
+from .conftest import yield_test_client
+
+
+URLs = ["/potato", "/carrot"]
 PARAMS = [-1, 0, 1, 14, "ten"]
 
+GET_ALL = "Overloaded Get All"
+GET_ONE = "Overloaded Get One"
+CREATE_ONE = "Overloaded Post One"
+UPDATE_ONE = "Overloaded Update One"
+DELETE_ONE = "Overloaded Delete One"
+DELETE_ALL = "Overloaded Delete All"
+
+
+@pytest.fixture(params=implementations, scope="class")
+def overloaded_client(request):
+    impl = request.param
+
+    app, routers = impl()
+    for r in routers:
+        r: APIRouter
+
+        @r.api_route("", methods=["GET"])
+        def overloaded_get_all():
+            return GET_ALL
+
+        @r.get("/{item_id}")
+        def overloaded_get_one():
+            return GET_ONE
+
+        @r.post("")
+        def overloaded_get():
+            return CREATE_ONE
+
+        @r.put("/{item_id}")
+        def overloaded_update():
+            return UPDATE_ONE
+
+        @r.delete("/{item_id}")
+        def overloaded_delete():
+            return DELETE_ONE
 
-def check_response(res):
-    assert res.status_code == 200
-    assert "Overloaded" in res.text
+        @r.api_route("", methods=["DELETE"])
+        def overloaded_delete():
+            return DELETE_ALL
 
+        app.include_router(r)
 
-def test_get_all(overloaded_client):
-    check_response(overloaded_client.get(URL))
+    yield from yield_test_client(app, impl)
 
 
-@pytest.mark.parametrize("id_", PARAMS)
-def test_get_one(overloaded_client, id_):
-    check_response(overloaded_client.get(f"{URL}/{id_}"))
+@pytest.fixture(params=URLs)
+def url(request):
+    yield request.param
 
 
-def test_create(overloaded_client):
-    check_response(overloaded_client.post(URL))
+class TestOverloads:
+    @staticmethod
+    def check_response(res, expected):
+        assert res.status_code == 200
+        assert expected in res.text
 
+    def test_get_all(self, overloaded_client, url):
+        return self.check_response(overloaded_client.get(url), GET_ALL)
 
-@pytest.mark.parametrize("id_", PARAMS)
-def test_update(overloaded_client, id_):
-    check_response(overloaded_client.put(f"{URL}/{id_}"))
+    @pytest.mark.parametrize("id_", PARAMS)
+    def test_get_one(self, overloaded_client, url, id_):
+        self.check_response(overloaded_client.get(f"{url}/{id_}"), GET_ONE)
 
+    def test_create(self, overloaded_client, url):
+        self.check_response(overloaded_client.post(url), CREATE_ONE)
 
-@pytest.mark.parametrize("id_", PARAMS)
-def test_delete(overloaded_client, id_):
-    check_response(overloaded_client.delete(f"{URL}/{id_}"))
+    @pytest.mark.parametrize("id_", PARAMS)
+    def test_update(self, overloaded_client, url, id_):
+        self.check_response(overloaded_client.put(f"{url}/{id_}"), UPDATE_ONE)
 
+    @pytest.mark.parametrize("id_", PARAMS)
+    def test_delete(self, overloaded_client, url, id_):
+        self.check_response(overloaded_client.delete(f"{url}/{id_}"), DELETE_ONE)
 
-def test_delete_all(overloaded_client):
-    check_response(overloaded_client.get(URL))
+    def test_delete_all(self, overloaded_client, url):
+        self.check_response(overloaded_client.delete(url), DELETE_ALL)