Skip to content

Commit c2f1424

Browse files
committed
added the protoc handler if the google protoc being used
1 parent 1b5d668 commit c2f1424

File tree

4 files changed

+209
-15
lines changed

4 files changed

+209
-15
lines changed

src/dubbo/codec/protobuf_codec/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@
1616

1717
from .protobuf_codec import ProtobufTransportCodec, ProtobufTransportDecoder, ProtobufTransportEncoder
1818
from .protobuf_base import ProtobufEncoder, ProtobufDecoder
19-
from .betterproto_handler import BetterprotoMessageHandler, PrimitiveHandler
19+
from .betterproto_handler import BetterprotoMessageHandler
20+
from .protoc_handler import GoogleProtobufMessageHandler
21+
from .primitive_handler import PrimitiveHandler
2022

2123
__all__ = [
2224
"ProtobufTransportCodec",
2325
"ProtobufTransportDecoder",
2426
"ProtobufTransportEncoder",
2527
"ProtobufEncoder",
26-
"ProtobufDecoderBetterprotoMessageHandler",
28+
"ProtobufDecoder",
29+
"BetterprotoMessageHandler",
30+
"PrimitiveHandler",
31+
"GoogleProtobufMessageHandler",
2732
"PrimitiveHandler",
2833
]
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import json
18+
from typing import Any, Optional
19+
20+
from .protobuf_base import ProtobufEncoder, ProtobufDecoder, SerializationException, DeserializationException
21+
22+
23+
__all__ = ["PrimitiveHandler"]
24+
25+
26+
class PrimitiveHandler(ProtobufEncoder, ProtobufDecoder):
27+
"""
28+
The primitive type handler for basic Python types.
29+
"""
30+
31+
_SERIALIZATION_TYPE = "primitive"
32+
33+
@classmethod
34+
def get_serialization_type(cls) -> str:
35+
"""
36+
Get serialization type of current implementation
37+
:return: The serialization type.
38+
:rtype: str
39+
"""
40+
return cls._SERIALIZATION_TYPE
41+
42+
def can_handle(self, obj: Any, obj_type: Optional[type] = None) -> bool:
43+
"""
44+
Check if this handler can handle the given object/type
45+
:param obj: The object to check
46+
:param obj_type: The type to check
47+
:return: True if can handle, False otherwise
48+
:rtype: bool
49+
"""
50+
if obj is not None:
51+
return isinstance(obj, (str, int, float, bool, bytes))
52+
if obj_type is not None:
53+
return obj_type in (str, int, float, bool, bytes)
54+
return False
55+
56+
def encode(self, obj: Any, obj_type: Optional[type] = None) -> bytes:
57+
"""
58+
Encode the primitive object to bytes.
59+
:param obj: The object to encode.
60+
:param obj_type: The type hint for encoding.
61+
:return: The encoded bytes.
62+
:rtype: bytes
63+
"""
64+
try:
65+
if not isinstance(obj, (str, int, float, bool, bytes)):
66+
raise SerializationException(f"Cannot encode {type(obj)} as primitive")
67+
68+
json_str = json.dumps({"value": obj, "type": type(obj).__name__})
69+
return json_str.encode("utf-8")
70+
except Exception as e:
71+
raise SerializationException(f"Primitive encoding failed: {e}") from e
72+
73+
def decode(self, data: bytes, target_type: type) -> Any:
74+
"""
75+
Decode the data to primitive object.
76+
:param data: The data to decode.
77+
:param target_type: The target primitive type.
78+
:return: The decoded object.
79+
:rtype: Any
80+
"""
81+
try:
82+
if target_type not in (str, int, float, bool, bytes):
83+
raise DeserializationException(f"{target_type} is not a supported primitive type")
84+
85+
json_str = data.decode("utf-8")
86+
parsed = json.loads(json_str)
87+
value = parsed.get("value")
88+
89+
if target_type is str:
90+
return str(value)
91+
elif target_type is int:
92+
return int(value)
93+
elif target_type is float:
94+
return float(value)
95+
elif target_type is bool:
96+
return bool(value)
97+
elif target_type is bytes:
98+
if isinstance(value, bytes):
99+
return value
100+
elif isinstance(value, list):
101+
return bytes(value)
102+
else:
103+
return str(value).encode()
104+
else:
105+
return value
106+
107+
except Exception as e:
108+
raise DeserializationException(f"Primitive decoding failed: {e}") from e

src/dubbo/codec/protobuf_codec/protobuf_codec.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from typing import Any, Optional, List
1818

1919
from .protobuf_base import ProtobufEncoder, ProtobufDecoder, SerializationException, DeserializationException
20-
from .protobuf_base import ProtobufEncoder
2120

2221
__all__ = ["ProtobufTransportCodec"]
2322

@@ -83,8 +82,7 @@ def decode(self, data: bytes) -> Any:
8382

8483
class ProtobufTransportCodec:
8584
"""
86-
Main protobuf codec class compatible with extension loader.
87-
This class provides encoder() and decoder() methods as expected by the extension loader.
85+
Main protobuf codec class
8886
"""
8987

9088
def __init__(
@@ -107,35 +105,36 @@ def _load_default_handlers(self):
107105
"""Load default encoding and decoding handlers"""
108106
from dubbo.extension import extensionLoader
109107

108+
# Try BetterProto handler
110109
try:
111-
# Try to load BetterProto handler
112-
name = "message"
113-
message_handler = extensionLoader.get_extension(ProtobufEncoder, name)
114-
betterproto_handler = message_handler()
110+
betterproto_handler = extensionLoader.get_extension(ProtobufEncoder, "betterproto")()
115111
self._encoders.append(betterproto_handler)
116112
self._decoders.append(betterproto_handler)
117113
except ImportError:
118114
print("Warning: BetterProto handler not available")
119115

120-
# Load primitive handler
121-
from dubbo.extension import extensionLoader
116+
# Try Google Protoc handler
117+
try:
118+
protoc_handler = extensionLoader.get_extension(ProtobufEncoder, "googleproto")()
119+
self._encoders.append(protoc_handler)
120+
self._decoders.append(protoc_handler)
121+
except ImportError:
122+
print("Warning: Protoc handler not available")
122123

123-
name = "primitive"
124-
primitive_handler = extensionLoader.get_extension(ProtobufEncoder, name)()
124+
# Always load primitive handler
125+
primitive_handler = extensionLoader.get_extension(ProtobufEncoder, "primitive")()
125126
self._encoders.append(primitive_handler)
126127
self._decoders.append(primitive_handler)
127128

128129
def encoder(self) -> ProtobufTransportEncoder:
129130
"""
130131
Create and return an encoder instance.
131-
This method is called by the extension loader / DubboSerializationService.
132132
"""
133133
return ProtobufTransportEncoder(self._encoders, self._parameter_types)
134134

135135
def decoder(self) -> ProtobufTransportDecoder:
136136
"""
137137
Create and return a decoder instance.
138-
This method is called by the extension loader / DubboSerializationService.
139138
"""
140139
return ProtobufTransportDecoder(self._decoders, self._return_type)
141140

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
from typing import Any, Optional
18+
from .protobuf_base import ProtobufEncoder, ProtobufDecoder, SerializationException, DeserializationException
19+
20+
try:
21+
from google.protobuf.message import Message as GoogleMessage
22+
23+
HAS_PROTOC = True
24+
except ImportError:
25+
HAS_PROTOC = False
26+
27+
28+
class GoogleProtobufMessageHandler(ProtobufEncoder, ProtobufDecoder):
29+
"""
30+
The Google protoc message handler for protobuf messages.
31+
"""
32+
33+
_SERIALIZATION_TYPE = "protoc"
34+
35+
def __init__(self):
36+
if not HAS_PROTOC:
37+
raise ImportError("google.protobuf is required for GoogleProtobufMessageHandler")
38+
39+
@classmethod
40+
def get_serialization_type(cls) -> str:
41+
return cls._SERIALIZATION_TYPE
42+
43+
def can_handle(self, obj: Any, obj_type: Optional[type] = None) -> bool:
44+
if obj is not None and HAS_PROTOC and isinstance(obj, GoogleMessage):
45+
return True
46+
if obj_type is not None:
47+
return self._is_protoc_message(obj_type)
48+
return False
49+
50+
def encode(self, obj: Any, obj_type: Optional[type] = None) -> bytes:
51+
try:
52+
if isinstance(obj, GoogleMessage):
53+
return obj.SerializeToString()
54+
55+
if obj_type and self._is_protoc_message(obj_type):
56+
if isinstance(obj, obj_type):
57+
return obj.SerializeToString()
58+
elif isinstance(obj, dict):
59+
message = obj_type(**obj)
60+
return message.SerializeToString()
61+
else:
62+
raise SerializationException(f"Cannot convert {type(obj)} to {obj_type}")
63+
64+
raise SerializationException(f"Cannot encode {type(obj)} as protoc message")
65+
except Exception as e:
66+
raise SerializationException(f"Protoc encoding failed: {e}") from e
67+
68+
def decode(self, data: bytes, target_type: type) -> Any:
69+
try:
70+
if not self._is_protoc_message(target_type):
71+
raise DeserializationException(f"{target_type} is not a protoc message type")
72+
message = target_type()
73+
message.ParseFromString(data)
74+
return message
75+
except Exception as e:
76+
raise DeserializationException(f"Protoc decoding failed: {e}") from e
77+
78+
def _is_protoc_message(self, obj_type: type) -> bool:
79+
try:
80+
return HAS_PROTOC and issubclass(obj_type, GoogleMessage)
81+
except (TypeError, AttributeError):
82+
return False

0 commit comments

Comments
 (0)