azizjon-aliev / python_clean_architecture

Python Inspiration: Clean Architecture Project Pattern hon development.
MIT License
6 stars 1 forks source link

Offer #33

Closed Azimjonm2333 closed 1 month ago

Azimjonm2333 commented 4 months ago

I would like to suggest making one BaseAPIView at least for CRUD models For example you can see it in CurrencyAPIView If there are many models, there will be a lot of code duplication Why not do it like this

class CurrencyAPIView(BaseAPIView, CurrencyViewInterface):
    list_serializer = ListCurrencyResponseSerializer
    detail_serializer = DetailCurrencyResponseSerializer
    create_serializer = CreateCurrencyRequestSerializer
    create_response_serializer = CreateCurrencyResponseSerializer
    update_serializer = UpdateCurrencyRequestSerializer
    update_response_serializer = UpdateCurrencyResponseSerializer

    list_use_case = ListCurrencyUseCase
    detail_use_case = DetailCurrencyUseCase
    create_use_case = CreateCurrencyUseCase
    update_use_case = UpdateCurrencyUseCase

    list_input_dto = ListCurrencyInputDto
    detail_input_dto = DetailCurrencyInputDto
    create_input_dto = CreateCurrencyInputDto
    update_input_dto = UpdateCurrencyInputDto

After that, we don't have to write a CRUD for the models every time like this:

# src/presentation/rest_api/apps/currency/views.py
class CurrencyAPIView(ViewSet, CurrencyViewInterface):
    authentication_classes = ()

    @extend_schema(
        responses={HTTPStatus.OK: ListCurrencyResponseSerializer},
        methods=[HTTPMethod.POST],
        parameters=[
            OpenApiParameter(
                "skip",
                int,
                OpenApiParameter.QUERY,
                description="Number of items to skip.",
            ),
            OpenApiParameter(
                "limit",
                int,
                OpenApiParameter.QUERY,
                description="Maximum number of items to retrieve.",
            ),
        ],
    )
    def list(self, request: Request) -> Response:
        # request
        parameters = {
            "skip": int(request.query_params.get("skip", 0)),
            "limit": int(request.query_params.get("limit", 100)),
        }

        # logic
        use_case = container.resolve(ListCurrencyUseCase)
        input_dto = mapper.to(ListCurrencyInputDto).map(parameters)
        result = use_case.execute(input_dto)

        # response
        return Response(
            data=ListCurrencyResponseSerializer(result).data, status=status.HTTP_200_OK
        )

    @extend_schema(
        responses={
            HTTPStatus.OK: DetailCurrencyResponseSerializer,
            HTTPStatus.NOT_FOUND: NotFoundResponseSerializer,
        },
        methods=[HTTPMethod.GET],
        parameters=[OpenApiParameter("id", CurrencyId, OpenApiParameter.PATH)],
    )
    def retrieve(self, request: Request, pk: Optional[CurrencyId]) -> Response:
        # logic
        try:
            use_case = container.resolve(DetailCurrencyUseCase)
            result = use_case.execute(pk)
        except EntityDoesNotExist as e:
            raise Http404() from e

        # response
        return Response(
            data=DetailCurrencyResponseSerializer(result).data,
            status=status.HTTP_200_OK,
        )

    @extend_schema(
        request=CreateCurrencyRequestSerializer,
        responses={HTTPStatus.CREATED: CreateCurrencyResponseSerializer},
        methods=[HTTPMethod.POST],
    )
    def create(self, request: Request) -> Response:
        # request
        serializer = CreateCurrencyRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # logic
        use_case = container.resolve(CreateCurrencyUseCase)
        input_dto = CreateCurrencyInputDto(**serializer.data)
        result = use_case.execute(input_dto)

        # response
        return Response(
            data=CreateCurrencyResponseSerializer(result).data,
            status=status.HTTP_201_CREATED,
        )

    @extend_schema(
        request=UpdateCurrencyRequestSerializer,
        responses={
            HTTPStatus.OK: UpdateCurrencyResponseSerializer,
            HTTPStatus.NOT_FOUND: NotFoundResponseSerializer,
        },
        methods=[HTTPMethod.PATCH],
        parameters=[OpenApiParameter("id", CurrencyId, OpenApiParameter.PATH)],
    )
    def partial_update(self, request: Request, pk: Optional[CurrencyId]) -> Response:
        # request
        serializer = UpdateCurrencyRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # logic
        try:
            use_case = container.resolve(UpdateCurrencyUseCase)
            input_dto = UpdateCurrencyInputDto(**serializer.data)
            result = use_case.execute(pk, input_dto)
        except EntityDoesNotExist as e:
            raise Http404() from e

        # response
        return Response(
            data=UpdateCurrencyResponseSerializer(result).data,
            status=status.HTTP_200_OK,
        )

    @extend_schema(
        responses={
            HTTPStatus.NO_CONTENT: None,
            HTTPStatus.NOT_FOUND: NotFoundResponseSerializer,
        },
        methods=[HTTPMethod.DELETE],
        parameters=[OpenApiParameter("id", CurrencyId, OpenApiParameter.PATH)],
    )
    def destroy(self, request: Request, pk: Optional[CurrencyId]) -> Response:
        # logic
        try:
            use_case = container.resolve(DeleteCurrencyUseCase)
            use_case.execute(pk)
        except EntityDoesNotExist as e:
            raise Http404() from e

        return Response(data=None, status=status.HTTP_204_NO_CONTENT)
azizjon-aliev commented 4 months ago

Hi @Azimjonm2333,

Thank you for your suggestion! Your idea of creating a BaseAPIView to reduce code duplication for CRUD operations is excellent. This approach would indeed simplify the code and make it more maintainable.

  1. Implementation Plan

  1. Update the CurrencyAPIView to use BaseAPIView:

With these changes, any new API view for CRUD operations can inherit from BaseAPIView and define its own serializers, use cases, and input DTOs, significantly reducing code duplication.

Please let me know if you have any questions or further suggestions!

Best regards, @azizjon-aliev