From ff9ee37ade85ce65384bcac1c413d19dbfd9261f Mon Sep 17 00:00:00 2001 From: AMontagu Date: Wed, 13 Mar 2024 18:41:46 +0100 Subject: [PATCH 1/3] to do check if value is string --- django_socio_grpc/grpc_actions/actions.py | 4 +-- .../protobuf/generation_plugin.py | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/django_socio_grpc/grpc_actions/actions.py b/django_socio_grpc/grpc_actions/actions.py index 066d257b..29c02c63 100644 --- a/django_socio_grpc/grpc_actions/actions.py +++ b/django_socio_grpc/grpc_actions/actions.py @@ -176,10 +176,10 @@ def make_proto_rpc(self, action_name: str, service: Type["Service"]) -> ProtoRpc # INFO - AM - 22/02/2024 - Get the actual request name request_name: str = message_name_constructor.construct_request_name() - request: ProtoMessage = req_class.create(value=self.request, name=request_name) + request: Union[ProtoMessage, str] = req_class.create(value=self.request, name=request_name) response_name: str = message_name_constructor.construct_response_name() - response: ProtoMessage = res_class.create(value=self.response, name=response_name) + response: Union[ProtoMessage, str] = res_class.create(value=self.response, name=response_name) for generation_plugin in self.use_generation_plugins: request, response = generation_plugin.run_validation_and_transform( diff --git a/django_socio_grpc/protobuf/generation_plugin.py b/django_socio_grpc/protobuf/generation_plugin.py index ab7af44b..ac98e8dc 100644 --- a/django_socio_grpc/protobuf/generation_plugin.py +++ b/django_socio_grpc/protobuf/generation_plugin.py @@ -20,8 +20,8 @@ class BaseGenerationPlugin: def check_condition( self, service: Type["Service"], - request_message: ProtoMessage, - response_message: ProtoMessage, + request_message: Union[ProtoMessage, str], + response_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> bool: """ @@ -32,7 +32,7 @@ def check_condition( def transform_request_message( self, service: Type["Service"], - proto_message: ProtoMessage, + proto_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> ProtoMessage: """ @@ -43,7 +43,7 @@ def transform_request_message( def transform_response_message( self, service: Type["Service"], - proto_message: ProtoMessage, + proto_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> ProtoMessage: """ @@ -54,8 +54,8 @@ def transform_response_message( def run_validation_and_transform( self, service: Type["Service"], - request_message: ProtoMessage, - response_message: ProtoMessage, + request_message: Union[ProtoMessage, str], + response_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> Tuple[ProtoMessage, ProtoMessage]: """ @@ -92,7 +92,7 @@ def __init__(self, *args, **kwargs): def transform_request_message( self, service: Type["Service"], - proto_message: ProtoMessage, + proto_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ): proto_message.fields.append( @@ -123,8 +123,8 @@ def __init__(self, display_warning_message=True): def check_condition( self, service: Type["Service"], - request_message: ProtoMessage, - response_message: ProtoMessage, + request_message: Union[ProtoMessage, str], + response_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> bool: # INFO - AM - 20/02/2024 - If service don't support filtering we do not add filter field @@ -165,8 +165,8 @@ def __init__(self, display_warning_message=True): def check_condition( self, service: Type["Service"], - request_message: ProtoMessage, - response_message: ProtoMessage, + request_message: Union[ProtoMessage, str], + response_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> bool: # INFO - AM - 20/02/2024 - If service don't support filtering we do not add filter field @@ -204,7 +204,7 @@ class AsListGenerationPlugin(BaseGenerationPlugin): list_field_name: str = "results" def transform_message_to_list( - self, service: Type["Service"], proto_message: ProtoMessage, list_name: str + self, service: Type["Service"], proto_message: Union[ProtoMessage, str], list_name: str ) -> ProtoMessage: try: list_field_name = proto_message.serializer.Meta.message_list_attr @@ -248,7 +248,7 @@ class RequestAsListGenerationPlugin(AsListGenerationPlugin): def transform_request_message( self, service: Type["Service"], - proto_message: ProtoMessage, + proto_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> ProtoMessage: list_name = message_name_constructor.construct_request_list_name() @@ -263,7 +263,7 @@ class ResponseAsListGenerationPlugin(AsListGenerationPlugin): def transform_response_message( self, service: Type["Service"], - proto_message: ProtoMessage, + proto_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ) -> ProtoMessage: list_name = message_name_constructor.construct_response_list_name() From 312d62e9fb800c1bc6be585798930658622d2e04 Mon Sep 17 00:00:00 2001 From: AMontagu Date: Mon, 8 Apr 2024 14:01:13 +0200 Subject: [PATCH 2/3] fix protoMessage can be string in plugin and add ListGenerationPlugin --- django_socio_grpc/decorators.py | 19 +++--- django_socio_grpc/grpc_actions/actions.py | 8 ++- .../protobuf/generation_plugin.py | 40 +++++++++++- .../tests/fakeapp/services/basic_service.py | 13 ++-- .../fakeapp/services/stream_in_service.py | 4 +- ...t_test_model_with_struct_filter_service.py | 4 +- .../tests/test_protobuf_registration.py | 62 ++++++++++++++++--- docker-compose.yml | 2 - docs/features/filters.rst | 4 +- docs/features/grpc-action.rst | 14 +++-- docs/features/pagination.rst | 4 +- docs/features/proto-generation.rst | 7 ++- 12 files changed, 132 insertions(+), 49 deletions(-) diff --git a/django_socio_grpc/decorators.py b/django_socio_grpc/decorators.py index c3f098ad..a451a8d4 100644 --- a/django_socio_grpc/decorators.py +++ b/django_socio_grpc/decorators.py @@ -3,8 +3,7 @@ from django_socio_grpc.protobuf.generation_plugin import ( BaseGenerationPlugin, - RequestAsListGenerationPlugin, - ResponseAsListGenerationPlugin, + ListGenerationPlugin, ) from django_socio_grpc.protobuf.message_name_constructor import MessageNameConstructor from django_socio_grpc.settings import grpc_settings @@ -20,18 +19,14 @@ def _maintain_compat(use_request_list, use_response_list, use_generation_plugins """ internal_plugins = [] if use_generation_plugins is None else use_generation_plugins warning_message = "You are using {0} argument in grpc_action. This argument is deprecated and has been remplaced by a specific GenerationPlugin. Please update following the documentation: https://django-socio-grpc.readthedocs.io/en/stable/features/proto-generation.html#proto-generation-plugins" - if use_request_list: - logger.warning(warning_message.format("use_request_list")) + if use_request_list or use_response_list: + log_text = "use_request_list" if use_request_list else "use_response_list" + if use_request_list and use_response_list: + log_text = "use_request_list and use_response_list" + logger.warning(warning_message.format(log_text)) internal_plugins.insert( 0, - RequestAsListGenerationPlugin(list_field_name="results"), - ) - - if use_response_list: - logger.warning(warning_message.format("use_response_list")) - internal_plugins.insert( - 0, - ResponseAsListGenerationPlugin(list_field_name="results"), + ListGenerationPlugin(request=use_request_list, response=use_response_list), ) return internal_plugins diff --git a/django_socio_grpc/grpc_actions/actions.py b/django_socio_grpc/grpc_actions/actions.py index 29c02c63..ff9084bf 100644 --- a/django_socio_grpc/grpc_actions/actions.py +++ b/django_socio_grpc/grpc_actions/actions.py @@ -176,10 +176,14 @@ def make_proto_rpc(self, action_name: str, service: Type["Service"]) -> ProtoRpc # INFO - AM - 22/02/2024 - Get the actual request name request_name: str = message_name_constructor.construct_request_name() - request: Union[ProtoMessage, str] = req_class.create(value=self.request, name=request_name) + request: Union[ProtoMessage, str] = req_class.create( + value=self.request, name=request_name + ) response_name: str = message_name_constructor.construct_response_name() - response: Union[ProtoMessage, str] = res_class.create(value=self.response, name=response_name) + response: Union[ProtoMessage, str] = res_class.create( + value=self.response, name=response_name + ) for generation_plugin in self.use_generation_plugins: request, response = generation_plugin.run_validation_and_transform( diff --git a/django_socio_grpc/protobuf/generation_plugin.py b/django_socio_grpc/protobuf/generation_plugin.py index ac98e8dc..d66c8682 100644 --- a/django_socio_grpc/protobuf/generation_plugin.py +++ b/django_socio_grpc/protobuf/generation_plugin.py @@ -95,6 +95,10 @@ def transform_request_message( proto_message: Union[ProtoMessage, str], message_name_constructor: MessageNameConstructor, ): + if isinstance(proto_message, str): + logger.warning( + f"Plugin {self.__class__.__name__} can't be used with a string message. Please use the plugin directly on the grpc_action that generate the message" + ) proto_message.fields.append( ProtoField.from_field_dict( { @@ -233,7 +237,7 @@ def transform_message_to_list( ) # INFO - AM - If the original proto message is a serializer then we keep the comment at the serializer level. Else we put them at the list level - if not proto_message.serializer: + if not isinstance(proto_message, str) and not proto_message.serializer: list_message.comments = proto_message.comments proto_message.comments = None @@ -278,3 +282,37 @@ class RequestAndResponseAsListGenerationPlugin( """ ... + + +@dataclass +class ListGenerationPlugin(RequestAsListGenerationPlugin, ResponseAsListGenerationPlugin): + """ + Transform both request and response ProtoMessage in list ProtoMessage + """ + + request: bool = False + response: bool = False + + def transform_response_message( + self, + service: Type["Service"], + proto_message: Union[ProtoMessage, str], + message_name_constructor: MessageNameConstructor, + ) -> ProtoMessage: + if self.response: + return super().transform_response_message( + service, proto_message, message_name_constructor + ) + return proto_message + + def transform_request_message( + self, + service: Type["Service"], + proto_message: Union[ProtoMessage, str], + message_name_constructor: MessageNameConstructor, + ) -> ProtoMessage: + if self.request: + return super().transform_request_message( + service, proto_message, message_name_constructor + ) + return proto_message diff --git a/django_socio_grpc/tests/fakeapp/services/basic_service.py b/django_socio_grpc/tests/fakeapp/services/basic_service.py index c7e1f9fb..0101776b 100644 --- a/django_socio_grpc/tests/fakeapp/services/basic_service.py +++ b/django_socio_grpc/tests/fakeapp/services/basic_service.py @@ -9,8 +9,7 @@ from django_socio_grpc import generics from django_socio_grpc.decorators import grpc_action from django_socio_grpc.protobuf.generation_plugin import ( - RequestAndResponseAsListGenerationPlugin, - ResponseAsListGenerationPlugin, + ListGenerationPlugin, ) from .basic_mixins import ListIdsMixin, ListNameMixin @@ -56,7 +55,7 @@ async def TestEmptyMethod(self, request, context): ... @grpc_action( request=[], response=BasicServiceSerializer, - use_generation_plugins=[ResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(response=True)], ) async def GetMultiple(self, request, context): # INFO - AM - 14/01/2022 - Do something here as filter user with the user name @@ -99,7 +98,7 @@ async def MyMethod(self, request, context): request=[{"name": "user_name", "type": "string"}], response=[{"name": "user_name", "type": "string"}], request_name="CustomMixParamForRequest", - use_generation_plugins=[RequestAndResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)], ) async def MixParam(self, request, context): pass @@ -108,7 +107,7 @@ async def MixParam(self, request, context): request=BasicServiceSerializer, response="google.protobuf.Struct", request_name="BasicParamWithSerializerRequest", - use_generation_plugins=[RequestAndResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)], ) async def MixParamWithSerializer(self, request, context): pass @@ -116,7 +115,7 @@ async def MixParamWithSerializer(self, request, context): @grpc_action( request=BaseProtoExampleSerializer, response=BaseProtoExampleSerializer, - use_generation_plugins=[ResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(response=True)], ) async def TestBaseProtoSerializer(self, request, context): pass @@ -124,7 +123,7 @@ async def TestBaseProtoSerializer(self, request, context): @grpc_action( request=BasicProtoListChildSerializer, response=BasicProtoListChildSerializer, - use_generation_plugins=[RequestAndResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)], ) async def BasicList(self, request, context): serializer = BasicProtoListChildSerializer(message=request, many=True) diff --git a/django_socio_grpc/tests/fakeapp/services/stream_in_service.py b/django_socio_grpc/tests/fakeapp/services/stream_in_service.py index 3a698235..2ce2aff0 100644 --- a/django_socio_grpc/tests/fakeapp/services/stream_in_service.py +++ b/django_socio_grpc/tests/fakeapp/services/stream_in_service.py @@ -4,7 +4,7 @@ from django_socio_grpc import generics from django_socio_grpc.decorators import grpc_action from django_socio_grpc.exceptions import NotFound -from django_socio_grpc.protobuf.generation_plugin import ResponseAsListGenerationPlugin +from django_socio_grpc.protobuf.generation_plugin import ListGenerationPlugin class StreamInService(generics.GenericService): @@ -14,7 +14,7 @@ class StreamInService(generics.GenericService): request=[{"name": "name", "type": "string"}], response=[{"name": "count", "type": "int32"}], request_stream=True, - use_generation_plugins=[ResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(response=True)], ) async def StreamIn(self, request, context): messages = [message async for message in request] diff --git a/django_socio_grpc/tests/fakeapp/services/unit_test_model_with_struct_filter_service.py b/django_socio_grpc/tests/fakeapp/services/unit_test_model_with_struct_filter_service.py index aa760132..f2ef92d9 100644 --- a/django_socio_grpc/tests/fakeapp/services/unit_test_model_with_struct_filter_service.py +++ b/django_socio_grpc/tests/fakeapp/services/unit_test_model_with_struct_filter_service.py @@ -8,8 +8,8 @@ from django_socio_grpc.decorators import grpc_action from django_socio_grpc.protobuf.generation_plugin import ( FilterGenerationPlugin, + ListGenerationPlugin, PaginationGenerationPlugin, - ResponseAsListGenerationPlugin, ) @@ -44,7 +44,7 @@ class UnitTestModelWithStructFilterService( request=[], response=UnitTestModelWithStructFilterSerializer, use_generation_plugins=[ - ResponseAsListGenerationPlugin(), + ListGenerationPlugin(response=True), FilterGenerationPluginForce(), PaginationGenerationPluginForce(), ], diff --git a/django_socio_grpc/tests/test_protobuf_registration.py b/django_socio_grpc/tests/test_protobuf_registration.py index 62541895..d1095114 100644 --- a/django_socio_grpc/tests/test_protobuf_registration.py +++ b/django_socio_grpc/tests/test_protobuf_registration.py @@ -15,9 +15,8 @@ from django_socio_grpc.protobuf import ProtoComment, ProtoRegistrationError from django_socio_grpc.protobuf.generation_plugin import ( FilterGenerationPlugin, + ListGenerationPlugin, PaginationGenerationPlugin, - RequestAndResponseAsListGenerationPlugin, - RequestAsListGenerationPlugin, ) from django_socio_grpc.protobuf.message_name_constructor import ( DefaultMessageNameConstructor, @@ -449,7 +448,7 @@ class MyBaseAction(Service): request=[], response=BasicProtoListChildSerializer, request_name="ReqNameRequest", - use_generation_plugins=[RequestAndResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)], ) async def BasicList(self, request, context): ... @@ -459,6 +458,20 @@ async def BasicList(self, request, context): ... ) async def BasicActionWithString(self, request, context): ... + @grpc_action( + request="TestRequest", + response="TestResponse", + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)], + ) + async def BasicActionListWithString(self, request, context): ... + + @grpc_action( + request=[], + response=BasicProtoListChildSerializer, + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)], + ) + async def BasicListArgPlugin(self, request, context): ... + class MyAction(GenericService): serializer_class = MySerializer @@ -466,20 +479,21 @@ class MyAction(GenericService): request=[], response=BasicProtoListChildSerializer, request_name="ReqNameRequest", - use_generation_plugins=[RequestAndResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)], ) async def BasicList(self, request, context): ... @grpc_action( request=[], response=BasicProtoListChildSerializer, - use_generation_plugins=[RequestAndResponseAsListGenerationPlugin()], + use_response_list=True, + use_request_list=True, ) async def BasicListOldCompat(self, request, context): ... @grpc_action( request="google.protobuf.Struct", - use_generation_plugins=[RequestAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(request=True)], ) async def ImportedReq(self, request, context): ... @@ -505,7 +519,7 @@ class MyActionWithFilter(GenericService): response=BasicProtoListChildSerializer, request_name="ReqNameRequest", use_generation_plugins=[ - RequestAndResponseAsListGenerationPlugin(), + ListGenerationPlugin(request=True, response=True), FilterGenerationPlugin(), ], ) @@ -534,7 +548,7 @@ class MyActionWithPagination(GenericService): response=BasicProtoListChildSerializer, request_name="ReqNameRequest", use_generation_plugins=[ - RequestAndResponseAsListGenerationPlugin(), + ListGenerationPlugin(request=True, response=True), PaginationGenerationPlugin(), ], ) @@ -554,7 +568,7 @@ async def PaginationInEmpty(self, request, context): ... ) async def PaginationInRequest(self, request, context): ... - def test_instanciate_GRPCAction_with_defualt_value(self): + def test_instanciate_GRPCAction_with_default_value(self): fake_func = mock.Mock() grpc_action = GRPCAction( function=fake_func, @@ -575,6 +589,21 @@ def test_register_action_on_base_service_list(self): assert request.name == "ReqNameListRequest" assert request["results"].field_type.name == "ReqNameRequest" + def test_register_action_on_base_service_list_with_arg_plugin(self): + proto_rpc = self.MyBaseAction.BasicListArgPlugin.make_proto_rpc( + "BasicListArgPlugin", self.MyBaseAction + ) + + response = proto_rpc.response + + assert response.name == "BasicProtoListChildListResponse" + assert response["results"].field_type.name == "BasicProtoListChildResponse" + + request = proto_rpc.request + + assert request.name == "MyBaseActionBasicListArgPluginListRequest" + assert request["results"].field_type.name == "MyBaseActionBasicListArgPluginRequest" + def test_register_action_on_base_service_with_message_str(self): proto_rpc = self.MyBaseAction.BasicActionWithString.make_proto_rpc( "BasicActionWithString", self.MyBaseAction @@ -583,6 +612,21 @@ def test_register_action_on_base_service_with_message_str(self): assert proto_rpc.response == "TestResponse" assert proto_rpc.request == "TestRequest" + def test_register_action_on_base_service_with_message_str_and_list_plugin(self): + proto_rpc = self.MyBaseAction.BasicActionListWithString.make_proto_rpc( + "BasicActionListWithString", self.MyBaseAction + ) + + response = proto_rpc.response + + assert response.name == "MyBaseActionBasicActionListWithStringListResponse" + assert response["results"].field_type == "TestResponse" + + request = proto_rpc.request + + assert request.name == "MyBaseActionBasicActionListWithStringListRequest" + assert request["results"].field_type == "TestRequest" + def test_register_action_list_imported(self): proto_rpc = self.MyAction.ImportedReq.make_proto_rpc("ImportedReq", self.MyAction) diff --git a/docker-compose.yml b/docker-compose.yml index 560ef977..1789b33a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.1" - services: django-socio-grpc: image: django-socio-grpc diff --git a/docs/features/filters.rst b/docs/features/filters.rst index f05663c0..c3bc681f 100644 --- a/docs/features/filters.rst +++ b/docs/features/filters.rst @@ -254,7 +254,7 @@ as demonstrated below (:ref:`See Generation Plugin documentation ` combined with - :func:`RequestAsListGenerationPlugin ` and - :func:`ResponseAsListGenerationPlugin ` + :func:`ListGenerationPlugin ` Those arguments are used to encapsulate the message inside a List message. It is useful when returning a list of object with a serializer. Example: :ref:`grpc-action-use-request-and-response-list` @@ -281,7 +283,7 @@ Here the ``UserProtoSerializer`` is used to generate the response message. from rest_framework import serializers from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User - from django_socio_grpc.protobuf.generation_plugin import ResponseAsListGenerationPlugin + from django_socio_grpc.protobuf.generation_plugin import ListGenerationPlugin class UserProtoSerializer(ModelProtoSerializer): username = serializers.CharField() @@ -299,7 +301,7 @@ Here the ``UserProtoSerializer`` is used to generate the response message. @grpc_action( request=[], response=UserProtoSerializer, - use_generation_plugins=[ResponseAsListGenerationPlugin()], + use_generation_plugins=[ListGenerationPlugin(response=True)], ) async def List(self, request, context): ... @@ -341,7 +343,7 @@ Usage of Request And Response List from rest_framework import serializers from django_socio_grpc.decorators import grpc_action from django_socio_grpc.proto_serializers import ModelProtoSerializer - from django_socio_grpc.protobuf.generation_plugin import RequestAndResponseAsListGenerationPlugin + from django_socio_grpc.protobuf.generation_plugin import ListGenerationPlugin class UserProtoSerializer(ModelProtoSerializer): uuid = serializers.UUIDField(read_only=True) @@ -355,7 +357,7 @@ Usage of Request And Response List @grpc_action( request=UserProtoSerializer, response=UserProtoSerializer, - use_generation_plugins=[RequestAndResponseAsListGenerationPlugin()] + use_generation_plugins=[ListGenerationPlugin(request=True, response=True)] ) async def BulkCreate(self, request, context): return await self._bulk_create(request, context) diff --git a/docs/features/pagination.rst b/docs/features/pagination.rst index 5e1a5433..6c954980 100644 --- a/docs/features/pagination.rst +++ b/docs/features/pagination.rst @@ -79,7 +79,7 @@ as demonstrated below (:ref:`See Generation Plugin documentation `: That is the instance of the service that being transformed into protobuf format. -- :func:`request_message `: That is the proto message as a python object of the request -- :func:`response_message `: That is the proto message as a python object of the response +- :func:`request_message ` (can also be an str if :ref:`request_name is set `): That is the proto message as a python object of the request +- :func:`response_message `(can also be an str if :ref:`response_name is set `): That is the proto message as a python object of the response - :func:`message_name_constructor `: That is the instance of the NameConstructor class used to generate the request and response proto name. It is usefull if you need a plugin that need to transform the name of the proto message. By default the class used is :func:`DefaultMessageNameConstructor ` Some helper class for transforming message to list, adding field and other exist. Please refer to :func:`the list of existing plugin ` @@ -234,6 +234,9 @@ Example of a plugin that change the type to all responses fields to string: proto_message, message_name_constructor, ): + # proto_message can be a string if the response_name is set. Be carreful to handle this case in your plugin + if isinstance(proto_message, str): + return proto_message for field in proto_message.fields: field.field_type = self.type_to_put return proto_message From 0b7a3ec5d3f7c74045c4aa6661de302b577d33f9 Mon Sep 17 00:00:00 2001 From: AMontagu Date: Mon, 8 Apr 2024 14:14:24 +0200 Subject: [PATCH 3/3] fix having a pagination class attribute set to None --- django_socio_grpc/protobuf/generation_plugin.py | 2 +- django_socio_grpc/tests/test_protobuf_registration.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django_socio_grpc/protobuf/generation_plugin.py b/django_socio_grpc/protobuf/generation_plugin.py index d66c8682..3fb07721 100644 --- a/django_socio_grpc/protobuf/generation_plugin.py +++ b/django_socio_grpc/protobuf/generation_plugin.py @@ -223,7 +223,7 @@ def transform_message_to_list( ), ] - if hasattr(service, "pagination_class"): + if getattr(service, "pagination_class", None): fields.append( ProtoField( name="count", diff --git a/django_socio_grpc/tests/test_protobuf_registration.py b/django_socio_grpc/tests/test_protobuf_registration.py index d1095114..18c787c8 100644 --- a/django_socio_grpc/tests/test_protobuf_registration.py +++ b/django_socio_grpc/tests/test_protobuf_registration.py @@ -443,6 +443,7 @@ def test_from_serializer_nested(self): class TestGrpcActionProto(TestCase): class MyBaseAction(Service): serializer_class = MySerializer + pagination_class = None @grpc_action( request=[], @@ -583,6 +584,7 @@ def test_register_action_on_base_service_list(self): assert response.name == "BasicProtoListChildListResponse" assert response["results"].field_type.name == "BasicProtoListChildResponse" + assert "count" not in response request = proto_rpc.request @@ -763,6 +765,8 @@ def test_register_action_list_with_struct_pagination(self): assert response.name == "BasicProtoListChildListResponse" assert response["results"].field_type.name == "BasicProtoListChildResponse" + assert "count" in response + assert response["count"].field_type == "int32" request = proto_rpc.request