fastcrud icon indicating copy to clipboard operation
fastcrud copied to clipboard

Add Nested Routes Support and Documentation

Open igorbenav opened this issue 1 year ago • 7 comments

Describe the bug or question Handling something like this scenario:

router = APIRouter(prefix="/user/{user_id}")


@router.get("/posts/{post_id}")
def get_post(user_id: int, post_id: int):
    ...
    return {"user": user_id, "post:": post_id, "text": post_text}

curl localhost:8000/user/1/posts/2

{
  "user": 1,
  "post:": 2
  "text": "sample text"
}

igorbenav avatar Feb 24 '24 06:02 igorbenav

I tried addressing this. After short research I thought that something like this would work out of the box:

class UserCRUD(fastcrud.FastCRUD):
    async def read(self, db: AsyncSession, root_param: int, read_param: int) -> Any:
        return {
            'root_param': root_param,
            'read_param': read_param,
        }

user_router = crud_router(
    crud=UserCRUD(User),
    model=User,
    session=deps.get_async_db,
    create_schema=UserIn,
    update_schema=UserUpdate,
    endpoint_names={'read': 'read/{read_param}'}
)

my_router = APIRouter()
my_router.include_router(user_router, prefix='root_router/{root_param}')

It turns out, that while the route gets properly built, the path params are not resolved dynamically as integers (they are parsed as raw path strings). Only default id gets recognized as a path param. I can see they are all gettin passed in

            self.router.add_api_route(
                f"{self.path}/{endpoint_name}/{_primary_keys_path_suffix}",
                ...
            )

but not sure what magic makes the _primary_keys_path_suffix resolve the params but not endpoint_name. Any thoughts on that? Is that approach ok or we want something else?

image

(note I have one more layer of /user/ nesting in my project but that's irrelevant here)

JakNowy avatar Apr 24 '24 11:04 JakNowy

@igorbenav

JakNowy avatar Apr 25 '24 10:04 JakNowy

I'll take a proper look at it later today

igorbenav avatar Apr 25 '24 12:04 igorbenav

Sorry, had a really long day. I'll try to do it on friday

igorbenav avatar Apr 26 '24 03:04 igorbenav

I have tested that even further. Turns out it's just swagger docs not fully recognizing the nesting, but the API works flawlessly out of the box!!!

class MyEndpointCreator(EndpointCreator):
    def _read_item(self):
        """Creates an endpoint for reading a single item from the database."""

        @apply_model_pk(**self._primary_keys_types)
        async def endpoint(root_param, read_param, db: AsyncSession = Depends(self.session), **pkeys,):
            return {
                'root_param': root_param,
                'read_param': read_param,
                'pkes': pkeys,
            }

        return endpoint


user_router = crud_router(
    crud=UserCRUD(User),
    model=User,
    session=deps.get_async_db,
    create_schema=UserIn,
    update_schema=UserYoutubeTOS,
    endpoint_names={'read': 'read/{read_param}'},
    endpoint_creator=MyEndpointCreator,
    path='test/{root_param}',  # this solves even swagger
)

my_router = APIRouter()
my_router.include_router(user_router)

image

UPDATE: passing path to crud_router instead of prefix to my_router.include_router() makes it also work on swagger!

JakNowy avatar Jun 19 '24 17:06 JakNowy

@igorbenav

JakNowy avatar Jun 19 '24 17:06 JakNowy

That's great!

igorbenav avatar Jun 23 '24 00:06 igorbenav