diff --git a/docs/advanced/crud.md b/docs/advanced/crud.md index 709a088..fe45d3d 100644 --- a/docs/advanced/crud.md +++ b/docs/advanced/crud.md @@ -292,6 +292,33 @@ items = await item_crud.upsert_multi( # this will return the upserted data in the form of ItemSchema ``` +### Customizing the Update Logic + +To allow more granular control over the SQL `UPDATE` operation during an upsert, `upsert_multi` can accept an `update_override` argument. This allows for the specification of custom update logic using SQL expressions, like the `case` statement, to handle complex conditions. + +```python +from sqlalchemy.sql import case + +update_override = { + "name": case( + (Item.name.is_(None), stmt.excluded.name), + else_=Item.name + ) +} + +items = await item_crud.upsert_multi( + db=db, + instances=[ + CreateItemSchema(name="Gadget", price=15.99), + ], + update_override=update_override, + schema_to_select=ItemSchema, + return_as_model=True, +) +``` + +In the example above, the `name` field of the `Item` model will be updated to the new value only if the existing `name` field is `None`. Otherwise, it retains the existing `name`. + #### Example: Joining `User`, `Tier`, and `Department` Models Consider a scenario where you want to retrieve users along with their associated tier and department information. Here's how you can achieve this using `get_multi_joined`. diff --git a/docs/advanced/endpoint.md b/docs/advanced/endpoint.md index caff2a0..f65a869 100644 --- a/docs/advanced/endpoint.md +++ b/docs/advanced/endpoint.md @@ -5,19 +5,19 @@ FastCRUD automates the creation of CRUD (Create, Read, Update, Delete) endpoints ### Create -- **Endpoint**: `/create` +- **Endpoint**: `/{model}` - **Method**: `POST` - **Description**: Creates a new item in the database. - **Request Body**: JSON object based on the `create_schema`. -- **Example Request**: `POST /items/create` with JSON body. +- **Example Request**: `POST /items` with JSON body. ### Read -- **Endpoint**: `/get/{id}` +- **Endpoint**: `/{model}/{id}` - **Method**: `GET` - **Description**: Retrieves a single item by its ID. - **Path Parameters**: `id` - The ID of the item to retrieve. -- **Example Request**: `GET /items/get/1`. +- **Example Request**: `GET /items/1`. - **Example Return**: ```javascript { @@ -33,13 +33,15 @@ FastCRUD automates the creation of CRUD (Create, Read, Update, Delete) endpoints ### Read Multiple -- **Endpoint**: `/get_multi` +- **Endpoint**: `/{model}` - **Method**: `GET` - **Description**: Retrieves multiple items with optional pagination. - **Query Parameters**: - `offset` (optional): The offset from where to start fetching items. - `limit` (optional): The maximum number of items to return. -- **Example Request**: `GET /items/get_multi?offset=3&limit=4`. + - `page` (optional): The page number, starting from 1. + - `itemsPerPage` (optional): The number of items per page. +- **Example Request**: `GET /items?offset=3&limit=4`. - **Example Return**: ```javascript { @@ -83,19 +85,9 @@ FastCRUD automates the creation of CRUD (Create, Read, Update, Delete) endpoints ], "total_count": 50 } - ``` - -### Read Paginated - -- **Endpoint**: `/get_paginated` -- **Method**: `GET` -- **Description**: Retrieves multiple items with pagination. -- **Query Parameters**: - - `page`: The page number, starting from 1. - - `itemsPerPage`: The number of items per page. -- **Example Request**: `GET /items/get_paginated?page=1&itemsPerPage=3`. -- **Example Return**: +- **Example Paginated Request**: `GET /items?page=1&itemsPerPage=3`. +- **Example Paginated Return**: ```javascript { "data": [ @@ -134,48 +126,48 @@ FastCRUD automates the creation of CRUD (Create, Read, Update, Delete) endpoints } ``` -!!! WARNING +!!! NOTE - `_read_paginated` endpoint is getting deprecated and mixed into `_read_items` in the next major release. - Please use `_read_items` with optional `page` and `items_per_page` query params instead, to achieve pagination as before. + `_read_paginated` endpoint was deprecated and mixed into `_read_items` in the release `0.15.0`. Simple `_read_items` behaviour persists with no breaking changes. Read items paginated: ```sh $ curl -X 'GET' \ - 'http://localhost:8000/users/get_multi?page=2&itemsPerPage=10' \ + 'http://localhost:8000/users?page=2&itemsPerPage=10' \ -H 'accept: application/json' ``` Read items unpaginated: ```sh $ curl -X 'GET' \ - 'http://localhost:8000/users/get_multi?offset=0&limit=100' \ + 'http://localhost:8000/users?offset=0&limit=100' \ -H 'accept: application/json' ``` + ### Update -- **Endpoint**: `/update/{id}` +- **Endpoint**: `/{model}/{id}` - **Method**: `PATCH` - **Description**: Updates an existing item by its ID. - **Path Parameters**: `id` - The ID of the item to update. - **Request Body**: JSON object based on the `update_schema`. -- **Example Request**: `PATCH /items/update/1` with JSON body. +- **Example Request**: `PATCH /items/1` with JSON body. - **Example Return**: `None` ### Delete -- **Endpoint**: `/delete/{id}` +- **Endpoint**: `/{model}/{id}` - **Method**: `DELETE` - **Description**: Deletes (soft delete if configured) an item by its ID. - **Path Parameters**: `id` - The ID of the item to delete. -- **Example Request**: `DELETE /items/delete/1`. +- **Example Request**: `DELETE /items/1`. - **Example Return**: `None` ### DB Delete (Hard Delete) -- **Endpoint**: `/db_delete/{id}` (Available if a `delete_schema` is provided) +- **Endpoint**: `/{model}/db_delete/{id}` (Available if a `delete_schema` is provided) - **Method**: `DELETE` - **Description**: Permanently deletes an item by its ID, bypassing the soft delete mechanism. - **Path Parameters**: `id` - The ID of the item to hard delete. @@ -251,7 +243,7 @@ app.include_router(my_router) ## Customizing Endpoint Names -You can customize the names of the auto generated endpoints by passing an `endpoint_names` dictionary when initializing the `EndpointCreator` or calling the `crud_router` function. This dictionary should map the CRUD operation names (`create`, `read`, `update`, `delete`, `db_delete`, `read_multi`, `read_paginated`) to your desired endpoint names. +You can customize the names of the auto generated endpoints by passing an `endpoint_names` dictionary when initializing the `EndpointCreator` or calling the `crud_router` function. This dictionary should map the CRUD operation names (`create`, `read`, `update`, `delete`, `db_delete`, `read_multi`) to your desired endpoint names. ### Example: Using `crud_router` @@ -274,7 +266,6 @@ custom_endpoint_names = { "update": "modify", "delete": "remove", "read_multi": "list", - "read_paginated": "paginate", } # Setup CRUD router with custom endpoint names @@ -326,9 +317,9 @@ app.include_router(endpoint_creator.router) You only need to pass the names of the endpoints you want to change in the `endpoint_names` `dict`. -!!! WARNING +!!! NOTE - `default_endpoint_names` for `EndpointCreator` are going to be changed to empty strings in the next major release. + `default_endpoint_names` for `EndpointCreator` were changed to empty strings in `0.15.0`. See [this issue](https://github.com/igorbenav/fastcrud/issues/67) for more details. ## Extending `EndpointCreator` @@ -551,7 +542,7 @@ app.include_router(endpoint_creator( ## Using Filters in FastCRUD -FastCRUD provides filtering capabilities, allowing you to filter query results based on various conditions. Filters can be applied to `read_multi` and `read_paginated` endpoints. This section explains how to configure and use filters in FastCRUD. +FastCRUD provides filtering capabilities, allowing you to filter query results based on various conditions. Filters can be applied to `read_multi` endpoint. This section explains how to configure and use filters in FastCRUD. ### Defining Filters @@ -607,7 +598,7 @@ app.include_router( Once filters are configured, you can use them in your API requests. Filters are passed as query parameters. Here's an example of how to use filters in a request to a paginated endpoint: ```http -GET /yourmodel/get_paginated?page=1&itemsPerPage=3&tier_id=1&name=Alice +GET /yourmodel?page=1&itemsPerPage=3&tier_id=1&name=Alice ``` ### Custom Filter Validation diff --git a/fastcrud/endpoint/crud_router.py b/fastcrud/endpoint/crud_router.py index eeaa402..c8cc0cd 100644 --- a/fastcrud/endpoint/crud_router.py +++ b/fastcrud/endpoint/crud_router.py @@ -27,7 +27,6 @@ def crud_router( create_deps: Sequence[Callable] = [], read_deps: Sequence[Callable] = [], read_multi_deps: Sequence[Callable] = [], - read_paginated_deps: Sequence[Callable] = [], update_deps: Sequence[Callable] = [], delete_deps: Sequence[Callable] = [], db_delete_deps: Sequence[Callable] = [], @@ -60,7 +59,6 @@ def crud_router( create_deps: Optional list of functions to be injected as dependencies for the create endpoint. read_deps: Optional list of functions to be injected as dependencies for the read endpoint. read_multi_deps: Optional list of functions to be injected as dependencies for the read multiple items endpoint. - read_paginated_deps: Optional list of functions to be injected as dependencies for the read paginated endpoint. update_deps: Optional list of functions to be injected as dependencies for the update endpoint. delete_deps: Optional list of functions to be injected as dependencies for the delete endpoint. db_delete_deps: Optional list of functions to be injected as dependencies for the hard delete endpoint. @@ -71,9 +69,9 @@ def crud_router( deleted_at_column: Optional column name to use for storing the timestamp of a soft delete. Defaults to `"deleted_at"`. updated_at_column: Optional column name to use for storing the timestamp of an update. Defaults to `"updated_at"`. endpoint_names: Optional dictionary to customize endpoint names for CRUD operations. Keys are operation types - (`"create"`, `"read"`, `"update"`, `"delete"`, `"db_delete"`, `"read_multi"`, `"read_paginated"`), and + (`"create"`, `"read"`, `"update"`, `"delete"`, `"db_delete"`, `"read_multi"`), and values are the custom names to use. Unspecified operations will use default names. - filter_config: Optional `FilterConfig` instance or dictionary to configure filters for the `read_multi` and `read_paginated` endpoints. + filter_config: Optional `FilterConfig` instance or dictionary to configure filters for the `read_multi` endpoint. Returns: Configured `APIRouter` instance with the CRUD endpoints. @@ -457,7 +455,6 @@ async def add_routes_to_router(self, ...): "delete": "remove_task", "db_delete": "permanently_remove_task", "read_multi": "list_tasks", - "read_paginated": "paginate_tasks", }, ) ``` @@ -550,7 +547,6 @@ async def add_routes_to_router(self, ...): create_deps=create_deps, read_deps=read_deps, read_multi_deps=read_multi_deps, - read_paginated_deps=read_paginated_deps, update_deps=update_deps, delete_deps=delete_deps, db_delete_deps=db_delete_deps, diff --git a/fastcrud/endpoint/endpoint_creator.py b/fastcrud/endpoint/endpoint_creator.py index 8dd5d82..f4f9694 100644 --- a/fastcrud/endpoint/endpoint_creator.py +++ b/fastcrud/endpoint/endpoint_creator.py @@ -1,6 +1,5 @@ from typing import Type, Optional, Callable, Sequence, Union from enum import Enum -import warnings from fastapi import Depends, Body, Query, APIRouter from pydantic import ValidationError @@ -13,7 +12,11 @@ ModelType, UpdateSchemaType, ) -from ..exceptions.http_exceptions import DuplicateValueException, NotFoundException +from ..exceptions.http_exceptions import ( + DuplicateValueException, + NotFoundException, + BadRequestException, +) from ..paginated.helper import compute_offset from ..paginated.response import paginated_response from .helper import ( @@ -52,9 +55,9 @@ class EndpointCreator: deleted_at_column: Optional column name to use for storing the timestamp of a soft delete. Defaults to `"deleted_at"`. updated_at_column: Optional column name to use for storing the timestamp of an update. Defaults to `"updated_at"`. endpoint_names: Optional dictionary to customize endpoint names for CRUD operations. Keys are operation types - (`"create"`, `"read"`, `"update"`, `"delete"`, `"db_delete"`, `"read_multi"`, `"read_paginated"`), and + (`"create"`, `"read"`, `"update"`, `"delete"`, `"db_delete"`, `"read_multi"`), and values are the custom names to use. Unspecified operations will use default names. - filter_config: Optional `FilterConfig` instance or dictionary to configure filters for the `read_multi` and `read_paginated` endpoints. + filter_config: Optional `FilterConfig` instance or dictionary to configure filters for the `read_multi` endpoint. Raises: ValueError: If both `included_methods` and `deleted_methods` are provided. @@ -273,24 +276,14 @@ def __init__( self.deleted_at_column = deleted_at_column self.updated_at_column = updated_at_column self.default_endpoint_names = { - "create": "create", - "read": "get", - "update": "update", - "delete": "delete", + "create": "", + "read": "", + "update": "", + "delete": "", "db_delete": "db_delete", - "read_multi": "get_multi", - "read_paginated": "get_paginated", + "read_multi": "", } self.endpoint_names = {**self.default_endpoint_names, **(endpoint_names or {})} - if self.endpoint_names == self.default_endpoint_names: - warnings.warn( - "Old default_endpoint_names are getting deprecated. " - "Default values are going to be replaced by empty strings, " - "resulting in plain endpoint names. " - "For details see:" - " https://github.com/igorbenav/fastcrud/issues/67", - DeprecationWarning, - ) if filter_config: if isinstance(filter_config, dict): filter_config = FilterConfig(**filter_config) @@ -347,59 +340,49 @@ def _read_items(self): async def endpoint( db: AsyncSession = Depends(self.session), + offset: Optional[int] = Query( + None, description="Offset for unpaginated queries" + ), + limit: Optional[int] = Query( + None, description="Limit for unpaginated queries" + ), page: Optional[int] = Query(None, alias="page", description="Page number"), items_per_page: Optional[int] = Query( None, alias="itemsPerPage", description="Number of items per page" ), filters: dict = Depends(dynamic_filters), ): - if not (page and items_per_page): - return await self.crud.get_multi(db, offset=0, limit=100, **filters) - - offset = compute_offset(page=page, items_per_page=items_per_page) - crud_data = await self.crud.get_multi( - db, offset=offset, limit=items_per_page, **filters - ) + is_paginated = (page is not None) and (items_per_page is not None) + has_offset_limit = (offset is not None) and (limit is not None) - return paginated_response( - crud_data=crud_data, page=page, items_per_page=items_per_page - ) # pragma: no cover - - return endpoint + if is_paginated and has_offset_limit: + raise BadRequestException( + detail="Conflicting parameters: Use either 'page' and 'itemsPerPage' for paginated results or 'offset' and 'limit' for specific range queries." + ) - def _read_paginated(self): - """Creates an endpoint for reading multiple items from the database with pagination.""" - dynamic_filters = _create_dynamic_filters(self.filter_config, self.column_types) - warnings.warn( - "_read_paginated endpoint is getting deprecated and mixed " - "into _read_items in the next major release. " - "Please use _read_items with optional page and items_per_page " - "query params instead, to achieve pagination as before." - "Simple _read_items behaviour persists with no breaking changes.", - DeprecationWarning, - ) + if is_paginated: + offset = compute_offset(page=page, items_per_page=items_per_page) # type: ignore + limit = items_per_page + crud_data = await self.crud.get_multi( + db, offset=offset, limit=limit, **filters + ) + return paginated_response( + crud_data=crud_data, + page=page, # type: ignore + items_per_page=items_per_page, # type: ignore + ) - async def endpoint( - db: AsyncSession = Depends(self.session), - page: int = Query( - 1, alias="page", description="Page number, starting from 1" - ), - items_per_page: int = Query( - 10, alias="itemsPerPage", description="Number of items per page" - ), - filters: dict = Depends(dynamic_filters), - ): - if not (page and items_per_page): # pragma: no cover - return await self.crud.get_multi(db, offset=0, limit=100, **filters) + if not has_offset_limit: + offset = 0 + limit = 100 - offset = compute_offset(page=page, items_per_page=items_per_page) crud_data = await self.crud.get_multi( - db, offset=offset, limit=items_per_page, **filters + db, + offset=offset, # type: ignore + limit=limit, # type: ignore + **filters, ) - - return paginated_response( - crud_data=crud_data, page=page, items_per_page=items_per_page - ) # pragma: no cover + return crud_data # pragma: no cover return endpoint @@ -463,7 +446,6 @@ def add_routes_to_router( create_deps: Sequence[Callable] = [], read_deps: Sequence[Callable] = [], read_multi_deps: Sequence[Callable] = [], - read_paginated_deps: Sequence[Callable] = [], update_deps: Sequence[Callable] = [], delete_deps: Sequence[Callable] = [], db_delete_deps: Sequence[Callable] = [], @@ -480,7 +462,6 @@ def add_routes_to_router( create_deps: List of functions to be injected as dependencies for the create endpoint. read_deps: List of functions to be injected as dependencies for the read endpoint. read_multi_deps: List of functions to be injected as dependencies for the read multiple items endpoint. - read_paginated_deps: List of functions to be injected as dependencies for the read paginated endpoint. update_deps: List of functions to be injected as dependencies for the update endpoint. delete_deps: List of functions to be injected as dependencies for the delete endpoint. db_delete_deps: List of functions to be injected as dependencies for the hard delete endpoint. @@ -538,7 +519,6 @@ def get_current_user(...): "create", "read", "read_multi", - "read_paginated", "update", "delete", "db_delete", @@ -598,19 +578,6 @@ def get_current_user(...): description=f"Read multiple {self.model.__name__} rows from the database with a limit and an offset.", ) - if ("read_paginated" in included_methods) and ( - "read_paginated" not in deleted_methods - ): - self.router.add_api_route( - self._get_endpoint_path(operation="read_paginated"), - self._read_paginated(), - methods=["GET"], - include_in_schema=self.include_in_schema, - tags=self.tags, - dependencies=_inject_dependencies(read_paginated_deps), - description=f"Read multiple {self.model.__name__} rows from the database with pagination.", - ) - if ("update" in included_methods) and ("update" not in deleted_methods): self.router.add_api_route( self._get_endpoint_path(operation="update"), diff --git a/fastcrud/endpoint/helper.py b/fastcrud/endpoint/helper.py index 4971026..32677d1 100644 --- a/fastcrud/endpoint/helper.py +++ b/fastcrud/endpoint/helper.py @@ -1,6 +1,5 @@ import inspect from typing import Optional, Union, Annotated, Sequence, Callable, TypeVar, Any -import warnings from pydantic import BaseModel, Field from pydantic.functional_validators import field_validator @@ -22,7 +21,6 @@ class CRUDMethods(BaseModel): "create", "read", "read_multi", - "read_paginated", "update", "delete", "db_delete", @@ -36,7 +34,6 @@ def check_valid_method(cls, values: Sequence[str]) -> Sequence[str]: "create", "read", "read_multi", - "read_paginated", "update", "delete", "db_delete", @@ -127,42 +124,20 @@ def _extract_unique_columns( return unique_columns -def _temporary_dependency_handling( - funcs: Optional[Sequence[Callable]] = None, -) -> Union[Sequence[params.Depends], None]: # pragma: no cover - """ - Checks if any function in the provided sequence is an instance of params.Depends. - Issues a deprecation warning once if such instances are found, and returns the sequence if any params.Depends are found. - - Args: - funcs: Optional sequence of callables or params.Depends instances. - """ - if funcs is not None: - if any(isinstance(func, params.Depends) for func in funcs): - warnings.warn( - "Passing a function wrapped in `Depends` directly to dependency handlers is deprecated and will be removed in version 0.15.0.", - DeprecationWarning, - stacklevel=2, - ) - return [ - func if isinstance(func, params.Depends) else Depends(func) - for func in funcs - ] - return None - - def _inject_dependencies( funcs: Optional[Sequence[Callable]] = None, ) -> Optional[Sequence[params.Depends]]: """Wraps a list of functions in FastAPI's Depends.""" - temp_handling = _temporary_dependency_handling(funcs) - if temp_handling is not None: # pragma: no cover - return temp_handling + if funcs is None: + return None - if funcs is not None: - return [Depends(func) for func in funcs] + for func in funcs: + if not callable(func): + raise TypeError( + f"All dependencies must be callable. Got {type(func)} instead." + ) - return None + return [Depends(func) for func in funcs] def _apply_model_pk(**pkeys: dict[str, type]): diff --git a/tests/sqlalchemy/conftest.py b/tests/sqlalchemy/conftest.py index c8283ee..af429a7 100644 --- a/tests/sqlalchemy/conftest.py +++ b/tests/sqlalchemy/conftest.py @@ -481,6 +481,14 @@ def client( delete_schema=delete_schema, path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -494,6 +502,14 @@ def client( delete_schema=tier_delete_schema, path="/tier", tags=["tier"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -508,6 +524,14 @@ def client( read_deps=[test_read_dep], path="/multi_pk", tags=["multi_pk"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -531,6 +555,14 @@ def filtered_client( filter_config=FilterConfig(tier_id=None, name=None), path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -554,6 +586,14 @@ def dict_filtered_client( filter_config={"tier_id": None, "name": None}, path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -578,6 +618,14 @@ def invalid_filtered_client( filter_config=filter_config, path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) @@ -593,4 +641,12 @@ def endpoint_creator(test_model, async_session) -> EndpointCreator: delete_schema=DeleteSchemaTest, path="/custom_test", tags=["custom_test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) diff --git a/tests/sqlalchemy/core/test_valid_methods.py b/tests/sqlalchemy/core/test_valid_methods.py index 0d9465b..cc23468 100644 --- a/tests/sqlalchemy/core/test_valid_methods.py +++ b/tests/sqlalchemy/core/test_valid_methods.py @@ -22,7 +22,6 @@ def test_crud_methods_default_methods(): "create", "read", "read_multi", - "read_paginated", "update", "delete", "db_delete", diff --git a/tests/sqlalchemy/endpoint/test_custom_endpoint_creator.py b/tests/sqlalchemy/endpoint/test_custom_endpoint_creator.py index 0b5969a..c13ac6b 100644 --- a/tests/sqlalchemy/endpoint/test_custom_endpoint_creator.py +++ b/tests/sqlalchemy/endpoint/test_custom_endpoint_creator.py @@ -19,7 +19,6 @@ def add_routes_to_router( create_deps: list[Callable] = [], read_deps: list[Callable] = [], read_multi_deps: list[Callable] = [], - read_paginated_deps: list[Callable] = [], update_deps: list[Callable] = [], delete_deps: list[Callable] = [], db_delete_deps: list[Callable] = [], @@ -30,7 +29,6 @@ def add_routes_to_router( create_deps, read_deps, read_multi_deps, - read_paginated_deps, update_deps, delete_deps, db_delete_deps, @@ -59,6 +57,14 @@ async def test_custom_endpoint_creator( endpoint_creator=CustomEndpointCreator, path="/custom", tags=["Test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) client.app.include_router(custom_router) diff --git a/tests/sqlalchemy/endpoint/test_deleted_methods.py b/tests/sqlalchemy/endpoint/test_deleted_methods.py index bfa8209..633c766 100644 --- a/tests/sqlalchemy/endpoint/test_deleted_methods.py +++ b/tests/sqlalchemy/endpoint/test_deleted_methods.py @@ -16,6 +16,14 @@ async def test_deleted_methods( deleted_methods=["delete"], path="/test_custom", tags=["Test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) client.app.include_router(custom_router) diff --git a/tests/sqlalchemy/endpoint/test_get_paginated.py b/tests/sqlalchemy/endpoint/test_get_paginated.py index 27785d1..d6183ab 100644 --- a/tests/sqlalchemy/endpoint/test_get_paginated.py +++ b/tests/sqlalchemy/endpoint/test_get_paginated.py @@ -2,94 +2,6 @@ from fastapi.testclient import TestClient -@pytest.mark.asyncio -async def test_read_paginated(client: TestClient, async_session, test_model, test_data): - for data in test_data: - new_item = test_model(**data) - async_session.add(new_item) - await async_session.commit() - - page = 1 - items_per_page = 5 - - response = client.get( - f"/test/get_paginated?page={page}&itemsPerPage={items_per_page}" - ) - - assert response.status_code == 200 - - data = response.json() - - assert "data" in data - assert "total_count" in data - assert "page" in data - assert "items_per_page" in data - assert "has_more" in data - - assert len(data["data"]) > 0 - assert len(data["data"]) <= items_per_page - - test_item = test_data[0] - assert any(item["name"] == test_item["name"] for item in data["data"]) - - assert data["page"] == page - assert data["items_per_page"] == items_per_page - - -@pytest.mark.asyncio -async def test_read_paginated_with_filters( - filtered_client: TestClient, async_session, test_model, test_data -): - for data in test_data: - new_item = test_model(**data) - async_session.add(new_item) - await async_session.commit() - - page = 1 - items_per_page = 5 - - tier_id = 1 - response = filtered_client.get( - f"/test/get_paginated?page={page}&itemsPerPage={items_per_page}&tier_id={tier_id}" - ) - - assert response.status_code == 200 - data = response.json() - - assert "data" in data - assert "total_count" in data - assert "page" in data - assert "items_per_page" in data - assert "has_more" in data - - assert len(data["data"]) > 0 - assert len(data["data"]) <= items_per_page - - for item in data["data"]: - assert item["tier_id"] == tier_id - - name = "Alice" - response = filtered_client.get( - f"/test/get_paginated?page={page}&itemsPerPage={items_per_page}&name={name}" - ) - - assert response.status_code == 200 - data = response.json() - - assert "data" in data - assert "total_count" in data - assert "page" in data - assert "items_per_page" in data - assert "has_more" in data - - assert len(data["data"]) > 0 - assert len(data["data"]) <= items_per_page - - for item in data["data"]: - assert item["name"] == name - - -# ------ the following tests will completely replace the current ones in the next version of fastcrud ------ @pytest.mark.asyncio async def test_read_items_with_pagination( client: TestClient, async_session, test_model, test_data diff --git a/tests/sqlalchemy/endpoint/test_included_methods.py b/tests/sqlalchemy/endpoint/test_included_methods.py index 149805c..6d12887 100644 --- a/tests/sqlalchemy/endpoint/test_included_methods.py +++ b/tests/sqlalchemy/endpoint/test_included_methods.py @@ -16,6 +16,14 @@ async def test_included_methods( included_methods=["create", "read"], path="/test_custom", tags=["Test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) client.app.include_router(custom_router) diff --git a/tests/sqlmodel/conftest.py b/tests/sqlmodel/conftest.py index 63baef3..95232a2 100644 --- a/tests/sqlmodel/conftest.py +++ b/tests/sqlmodel/conftest.py @@ -469,6 +469,14 @@ def client( delete_schema=delete_schema, path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -482,6 +490,14 @@ def client( delete_schema=tier_delete_schema, path="/tier", tags=["tier"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -496,6 +512,14 @@ def client( delete_schema=multi_pk_test_schema, path="/multi_pk", tags=["multi_pk"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -523,6 +547,14 @@ def filtered_client( filter_config=FilterConfig(tier_id=None, name=None), path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -550,6 +582,14 @@ def dict_filtered_client( filter_config={"tier_id": None, "name": None}, path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) ) @@ -578,6 +618,14 @@ def invalid_filtered_client( filter_config=filter_config, path="/test", tags=["test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) @@ -593,4 +641,12 @@ def endpoint_creator(test_model, async_session) -> EndpointCreator: delete_schema=DeleteSchemaTest, path="/custom_test", tags=["custom_test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) diff --git a/tests/sqlmodel/core/test_valid_methods.py b/tests/sqlmodel/core/test_valid_methods.py index 0d9465b..cc23468 100644 --- a/tests/sqlmodel/core/test_valid_methods.py +++ b/tests/sqlmodel/core/test_valid_methods.py @@ -22,7 +22,6 @@ def test_crud_methods_default_methods(): "create", "read", "read_multi", - "read_paginated", "update", "delete", "db_delete", diff --git a/tests/sqlmodel/endpoint/test_custom_endpoint_creator.py b/tests/sqlmodel/endpoint/test_custom_endpoint_creator.py index 0b5969a..c13ac6b 100644 --- a/tests/sqlmodel/endpoint/test_custom_endpoint_creator.py +++ b/tests/sqlmodel/endpoint/test_custom_endpoint_creator.py @@ -19,7 +19,6 @@ def add_routes_to_router( create_deps: list[Callable] = [], read_deps: list[Callable] = [], read_multi_deps: list[Callable] = [], - read_paginated_deps: list[Callable] = [], update_deps: list[Callable] = [], delete_deps: list[Callable] = [], db_delete_deps: list[Callable] = [], @@ -30,7 +29,6 @@ def add_routes_to_router( create_deps, read_deps, read_multi_deps, - read_paginated_deps, update_deps, delete_deps, db_delete_deps, @@ -59,6 +57,14 @@ async def test_custom_endpoint_creator( endpoint_creator=CustomEndpointCreator, path="/custom", tags=["Test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) client.app.include_router(custom_router) diff --git a/tests/sqlmodel/endpoint/test_deleted_methods.py b/tests/sqlmodel/endpoint/test_deleted_methods.py index bfa8209..633c766 100644 --- a/tests/sqlmodel/endpoint/test_deleted_methods.py +++ b/tests/sqlmodel/endpoint/test_deleted_methods.py @@ -16,6 +16,14 @@ async def test_deleted_methods( deleted_methods=["delete"], path="/test_custom", tags=["Test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) client.app.include_router(custom_router) diff --git a/tests/sqlmodel/endpoint/test_get_paginated.py b/tests/sqlmodel/endpoint/test_get_paginated.py index 27785d1..d6183ab 100644 --- a/tests/sqlmodel/endpoint/test_get_paginated.py +++ b/tests/sqlmodel/endpoint/test_get_paginated.py @@ -2,94 +2,6 @@ from fastapi.testclient import TestClient -@pytest.mark.asyncio -async def test_read_paginated(client: TestClient, async_session, test_model, test_data): - for data in test_data: - new_item = test_model(**data) - async_session.add(new_item) - await async_session.commit() - - page = 1 - items_per_page = 5 - - response = client.get( - f"/test/get_paginated?page={page}&itemsPerPage={items_per_page}" - ) - - assert response.status_code == 200 - - data = response.json() - - assert "data" in data - assert "total_count" in data - assert "page" in data - assert "items_per_page" in data - assert "has_more" in data - - assert len(data["data"]) > 0 - assert len(data["data"]) <= items_per_page - - test_item = test_data[0] - assert any(item["name"] == test_item["name"] for item in data["data"]) - - assert data["page"] == page - assert data["items_per_page"] == items_per_page - - -@pytest.mark.asyncio -async def test_read_paginated_with_filters( - filtered_client: TestClient, async_session, test_model, test_data -): - for data in test_data: - new_item = test_model(**data) - async_session.add(new_item) - await async_session.commit() - - page = 1 - items_per_page = 5 - - tier_id = 1 - response = filtered_client.get( - f"/test/get_paginated?page={page}&itemsPerPage={items_per_page}&tier_id={tier_id}" - ) - - assert response.status_code == 200 - data = response.json() - - assert "data" in data - assert "total_count" in data - assert "page" in data - assert "items_per_page" in data - assert "has_more" in data - - assert len(data["data"]) > 0 - assert len(data["data"]) <= items_per_page - - for item in data["data"]: - assert item["tier_id"] == tier_id - - name = "Alice" - response = filtered_client.get( - f"/test/get_paginated?page={page}&itemsPerPage={items_per_page}&name={name}" - ) - - assert response.status_code == 200 - data = response.json() - - assert "data" in data - assert "total_count" in data - assert "page" in data - assert "items_per_page" in data - assert "has_more" in data - - assert len(data["data"]) > 0 - assert len(data["data"]) <= items_per_page - - for item in data["data"]: - assert item["name"] == name - - -# ------ the following tests will completely replace the current ones in the next version of fastcrud ------ @pytest.mark.asyncio async def test_read_items_with_pagination( client: TestClient, async_session, test_model, test_data diff --git a/tests/sqlmodel/endpoint/test_included_methods.py b/tests/sqlmodel/endpoint/test_included_methods.py index 149805c..6d12887 100644 --- a/tests/sqlmodel/endpoint/test_included_methods.py +++ b/tests/sqlmodel/endpoint/test_included_methods.py @@ -16,6 +16,14 @@ async def test_included_methods( included_methods=["create", "read"], path="/test_custom", tags=["Test"], + endpoint_names={ + "create": "create", + "read": "get", + "update": "update", + "delete": "delete", + "db_delete": "db_delete", + "read_multi": "get_multi", + }, ) client.app.include_router(custom_router)