Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default value/not working update default grpc value #258

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 56 additions & 11 deletions django_socio_grpc/proto_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from asgiref.sync import sync_to_async
from django.core.validators import MaxLengthValidator
from django.db.models.fields import NOT_PROVIDED
from django.utils.translation import gettext as _
from rest_framework.exceptions import ValidationError
from rest_framework.fields import empty
Expand Down Expand Up @@ -42,7 +43,7 @@ def __init__(self, *args, **kwargs):
def message_to_data(self, message):
"""Protobuf message -> Dict of python primitive datatypes."""
data_dict = message_to_dict(message)
data_dict = self.populate_dict_with_none_if_not_required(data_dict, message)
data_dict = self.populate_dict_with_none_if_not_required(data_dict, message=message)
return data_dict

def populate_dict_with_none_if_not_required(self, data_dict, message=None):
Expand All @@ -53,9 +54,11 @@ def populate_dict_with_none_if_not_required(self, data_dict, message=None):
We can't rely only on required True/False as in DSG if a field is required it will have the default value of it's type (empty string for string type) and not None

When refactoring serializer to only use message we will be able to determine the default value of the field depending of the same logic followed here

set default value for field except if optional or partial update
"""
# INFO - AM - 04/01/2024 - If we are in a partial serializer with a message we need to have the PARTIAL_UPDATE_FIELD_NAME in the message. If not we raise an exception
if self.partial and not hasattr(message, PARTIAL_UPDATE_FIELD_NAME):
# INFO - AM - 04/01/2024 - If we are in a partial serializer with a message we need to have the PARTIAL_UPDATE_FIELD_NAME in the data_dict. If not we raise an exception
if self.partial and PARTIAL_UPDATE_FIELD_NAME not in data_dict:
raise ValidationError(
{
PARTIAL_UPDATE_FIELD_NAME: [
Expand All @@ -65,23 +68,65 @@ def populate_dict_with_none_if_not_required(self, data_dict, message=None):
code="missing_partial_message_attribute",
)

is_update_process = bool(data_dict.get(self.Meta.model._meta.pk.name, ""))
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
for field in self.fields.values():
# INFO - AM - 04/01/2024 - If we are in a partial serializer we need to only have field specified in PARTIAL_UPDATE_FIELD_NAME attribute. Meaning bot deleting field that should not be here and not adding None to allow_null field that are not specified
if (
message
and self.partial
and hasattr(message, PARTIAL_UPDATE_FIELD_NAME)
and field.field_name not in getattr(message, PARTIAL_UPDATE_FIELD_NAME)
# INFO - AM - 04/01/2024 - If we are in a partial serializer we only need to have field specified in PARTIAL_UPDATE_FIELD_NAME attribute in the data. Meaning deleting fields that should not be here and not adding None to allow_null field that are not specified
if self.partial and field.field_name not in data_dict.get(
PARTIAL_UPDATE_FIELD_NAME, {}
):
if field.field_name in data_dict:
del data_dict[field.field_name]
continue
# INFO - AM - 04/01/2024 - if field already existing in the data_dict we do not need to do something else
if field.field_name in data_dict:
continue
# INFO - AM - 04/01/2024 - Adding default None value only for optional field that are required and allowing null or having a default value
if field.allow_null or field.default in [None, empty] and field.required is True:

if self.partial and field.field_name in data_dict.get(
PARTIAL_UPDATE_FIELD_NAME, {}
):
if field.allow_null:
data_dict[field.field_name] = None
continue
if field.default not in [None, empty]:
# TODO - AM - 12/01/2024 - is field.default a possible callable here ?
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
data_dict[field.field_name] = field.default
continue

data_dict[field.field_name] = message.DESCRIPTOR.fields_by_name[
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
field.field_name
].default_value

# INFO - AM - 12/01/2024 - if field name is not in data_dict but is required that mean that it's a default value deleted when transforming proto to dict
AMontagu marked this conversation as resolved.
Show resolved Hide resolved
if field.required and field.field_name in message.DESCRIPTOR.fields_by_name:
data_dict[field.field_name] = message.DESCRIPTOR.fields_by_name[
field.field_name
].default_value
continue

if field.allow_null or (field.default in [None, empty] and field.required is True):
if is_update_process:
data_dict[field.field_name] = None
continue

if field.default not in [None, empty]:
data_dict[field.field_name] = None
continue

if (
hasattr(self, "Meta")
and hasattr(self.Meta, "model")
and hasattr(self.Meta.model, field.field_name)
):
deferred_attribute = getattr(self.Meta.model, field.field_name)
if deferred_attribute.field.default != NOT_PROVIDED:
data_dict[field.field_name] = deferred_attribute.field.default
continue

data_dict[field.field_name] = None
continue

# elif field.required and field.field_name in message.DESCRIPTOR.fields_by_name:
# data_dict[field.field_name] = message.DESCRIPTOR.fields_by_name[field.field_name].default_value
Comment on lines +128 to +129
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? but yes to delete

return data_dict

def data_to_message(self, data):
Expand Down
2 changes: 1 addition & 1 deletion django_socio_grpc/protobuf/proto_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def _get_cardinality(self, field: serializers.Field):
"""
INFO - AM - 04/01/2023
If field can be null -> optional
if field is not required -> optional
if field is not required -> optional. Since DRF 3.0 When using model default, only required is set to False. The model default is not set into the field as just passing None will result in model default. https://github.com/encode/django-rest-framework/issues/2683
if field.default is set (meaning not None or empty) -> optional

Not dealing with field.allow_blank now as it doesn't seem to be related to OPTIONAl and more about validation and only exist for charfield
Expand Down
58 changes: 47 additions & 11 deletions django_socio_grpc/tests/assets/generated_protobuf_files_old_way.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
int32 id = 1;
string title = 2;
string text = 3;
int32 some_default_counter = 4;
bool is_validated = 5;
}

message UnitTestModelListRequest {
Expand Down Expand Up @@ -67,8 +65,6 @@
int32 id = 1;
string title = 2;
string text = 3;
int32 some_default_counter = 4;
bool is_validated = 5;
}

message UnitTestModelListRequest {
Expand Down Expand Up @@ -145,12 +141,18 @@
rpc Destroy(RecursiveTestModelDestroyRequest) returns (google.protobuf.Empty) {}
}

service DefaultValueModelController {
rpc List(DefaultValueModelListRequest) returns (DefaultValueModelListResponse) {}
rpc Create(DefaultValueModel) returns (DefaultValueModel) {}
rpc Retrieve(DefaultValueModelRetrieveRequest) returns (DefaultValueModel) {}
rpc Update(DefaultValueModel) returns (DefaultValueModel) {}
rpc Destroy(DefaultValueModelDestroyRequest) returns (google.protobuf.Empty) {}
}

message UnitTestModel {
int32 id = 1;
string title = 2;
string text = 3;
int32 some_default_counter = 4;
bool is_validated = 5;
}

message UnitTestModelListRequest {
Expand Down Expand Up @@ -284,6 +286,44 @@
string uuid = 1;
}

message DefaultValueModel {
string id = 1;
string string_required = 2;
string string_blank = 3;
string string_nullable = 4;
string string_default = 5;
string string_default_and_blank = 6;
string string_null_default_and_blank = 7;
string string_required_but_serializer_default = 8;
string string_default_but_serializer_default = 9;
string string_nullable_default_but_serializer_default = 10;
int32 int_required = 11;
int32 int_nullable = 12;
int32 int_default = 13;
int32 int_required_but_serializer_default = 14;
bool boolean_required = 15;
bool boolean_nullable = 16;
bool boolean_default_false = 17;
bool boolean_default_true = 18;
bool boolean_required_but_serializer_default = 19;
}

message DefaultValueModelListRequest {
}

message DefaultValueModelListResponse {
repeated DefaultValueModel results = 1;
int32 count = 2;
}

message DefaultValueModelRetrieveRequest {
string id = 1;
}

message DefaultValueModelDestroyRequest {
string id = 1;
}

"""

CUSTOM_APP_MODEL_GENERATED = """syntax = "proto3";
Expand Down Expand Up @@ -387,8 +427,6 @@
message UnitTestModel {
int32 id = 1;
string text = 2;
int32 some_default_counter = 3;
bool is_validated = 4;
}

message UnitTestModelListRequest {
Expand Down Expand Up @@ -430,9 +468,7 @@
message UnitTestModel {
int32 id = 1;
string text = 2;
int32 some_default_counter = 3;
bool is_validated = 4;
string title = 5;
string title = 3;
}

message UnitTestModelListRequest {
Expand Down
124 changes: 103 additions & 21 deletions django_socio_grpc/tests/fakeapp/grpc/fakeapp.proto
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ service BasicController {
rpc TestEmptyMethod(google.protobuf.Empty) returns (google.protobuf.Empty) {}
}

service DefaultValueController {
rpc Create(DefaultValueRequest) returns (DefaultValueResponse) {}
rpc Destroy(DefaultValueDestroyRequest) returns (google.protobuf.Empty) {}
rpc List(DefaultValueListRequest) returns (DefaultValueListResponse) {}
rpc PartialUpdate(DefaultValuePartialUpdateRequest) returns (DefaultValueResponse) {}
rpc Retrieve(DefaultValueRetrieveRequest) returns (DefaultValueResponse) {}
rpc Update(DefaultValueRequest) returns (DefaultValueResponse) {}
}

service ExceptionController {
rpc APIException(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc GRPCException(google.protobuf.Empty) returns (google.protobuf.Empty) {}
Expand Down Expand Up @@ -174,18 +183,14 @@ message BasicProtoListChildListResponse {

message BasicProtoListChildRequest {
optional int32 id = 1;
optional int32 some_default_counter = 2;
string title = 3;
optional string text = 4;
optional bool is_validated = 5;
string title = 2;
optional string text = 3;
}

message BasicProtoListChildResponse {
optional int32 id = 1;
optional int32 some_default_counter = 2;
string title = 3;
optional string text = 4;
optional bool is_validated = 5;
string title = 2;
optional string text = 3;
}

message BasicServiceListResponse {
Expand Down Expand Up @@ -239,6 +244,89 @@ message CustomRetrieveResponseSpecialFieldsModelResponse {
repeated google.protobuf.Struct custom_method_field = 3;
}

message DefaultValueDestroyRequest {
int32 id = 1;
}

message DefaultValueListRequest {
}

message DefaultValueListResponse {
repeated DefaultValueResponse results = 1;
int32 count = 2;
}

message DefaultValuePartialUpdateRequest {
optional int32 id = 1;
optional string string_required_but_serializer_default = 2;
optional int32 int_required_but_serializer_default = 3;
optional bool boolean_required_but_serializer_default = 4;
optional string string_default_but_serializer_default = 5;
optional string string_nullable_default_but_serializer_default = 6;
repeated string _partial_update_fields = 7;
string string_required = 8;
optional string string_blank = 9;
optional string string_nullable = 10;
optional string string_default = 11;
optional string string_default_and_blank = 12;
optional string string_null_default_and_blank = 13;
int32 int_required = 14;
optional int32 int_nullable = 15;
optional int32 int_default = 16;
bool boolean_required = 17;
optional bool boolean_nullable = 18;
optional bool boolean_default_false = 19;
optional bool boolean_default_true = 20;
}

message DefaultValueRequest {
optional int32 id = 1;
optional string string_required_but_serializer_default = 2;
optional int32 int_required_but_serializer_default = 3;
optional bool boolean_required_but_serializer_default = 4;
optional string string_default_but_serializer_default = 5;
optional string string_nullable_default_but_serializer_default = 6;
string string_required = 7;
optional string string_blank = 8;
optional string string_nullable = 9;
optional string string_default = 10;
optional string string_default_and_blank = 11;
optional string string_null_default_and_blank = 12;
int32 int_required = 13;
optional int32 int_nullable = 14;
optional int32 int_default = 15;
bool boolean_required = 16;
optional bool boolean_nullable = 17;
optional bool boolean_default_false = 18;
optional bool boolean_default_true = 19;
}

message DefaultValueResponse {
optional int32 id = 1;
optional string string_required_but_serializer_default = 2;
optional int32 int_required_but_serializer_default = 3;
optional bool boolean_required_but_serializer_default = 4;
optional string string_default_but_serializer_default = 5;
optional string string_nullable_default_but_serializer_default = 6;
string string_required = 7;
optional string string_blank = 8;
optional string string_nullable = 9;
optional string string_default = 10;
optional string string_default_and_blank = 11;
optional string string_null_default_and_blank = 12;
int32 int_required = 13;
optional int32 int_nullable = 14;
optional int32 int_default = 15;
bool boolean_required = 16;
optional bool boolean_nullable = 17;
optional bool boolean_default_false = 18;
optional bool boolean_default_true = 19;
}

message DefaultValueRetrieveRequest {
int32 id = 1;
}

message ExceptionStreamRaiseExceptionResponse {
string id = 1;
}
Expand Down Expand Up @@ -499,27 +587,21 @@ message UnitTestModelListWithExtraArgsRequest {

message UnitTestModelPartialUpdateRequest {
optional int32 id = 1;
optional int32 some_default_counter = 2;
optional bool is_validated = 3;
repeated string _partial_update_fields = 4;
string title = 5;
optional string text = 6;
repeated string _partial_update_fields = 2;
string title = 3;
optional string text = 4;
}

message UnitTestModelRequest {
optional int32 id = 1;
optional int32 some_default_counter = 2;
optional bool is_validated = 3;
string title = 4;
optional string text = 5;
string title = 2;
optional string text = 3;
}

message UnitTestModelResponse {
optional int32 id = 1;
optional int32 some_default_counter = 2;
optional bool is_validated = 3;
string title = 4;
optional string text = 5;
string title = 2;
optional string text = 3;
}

message UnitTestModelRetrieveRequest {
Expand Down
318 changes: 167 additions & 151 deletions django_socio_grpc/tests/fakeapp/grpc/fakeapp_pb2.py

Large diffs are not rendered by default.

Loading