arian created THRIFT-6369:
-----------------------------

             Summary: py:enum + accelerated protocols: THRIFT-5885 fix misses 
typedef'd enums, enums in containers, and immutable structs
                 Key: THRIFT-6369
                 URL: https://issues.apache.org/jira/browse/THRIFT-6369
             Project: Thrift
          Issue Type: Bug
          Components: Python - Compiler
    Affects Versions: 0.24.0
         Environment: Thrift compiler 0.24.0 (--gen py:enum)
Python library thrift==0.24.0, with the fastbinary C extension built
Python 3.11+
Linux and Windows
            Reporter: arian


THRIFT-5885 (fixed in 0.23.0) added a generated __setattr__ to mutable structs 
with py:enum. It converts the raw i32 that fastbinary's _fast_decode sets into 
the enum member. The fix only handles fields whose declared type is the enum 
itself. With TBinaryProtocolAcceleratedFactory / 
TCompactProtocolAcceleratedFactory, three cases still decode incorrectly:
 # Enum field declared through a typedef (mutable struct). In t_py_generator.cc 
the check is (*m_iter)->get_type()->is_enum(), which is false for a t_typedef. 
No __setattr__ is generated, and the field stays a raw int. The pure-Python 
read() resolves the typedef correctly (Color(iprot.readI32())), so the two 
paths give different results.
 # Enum values inside containers (mutable struct). The generated __setattr__ 
only converts scalar enum fields. Elements of list<Enum>, set<Enum> and map<…, 
Enum> stay raw ints.
 # Immutable structs (python.immutable). Here _fast_decode calls the generated 
__init__, which still contains the original THRIFT-5885 bug:

{code:java}
super().__setattr__('color', color if hasattr(color, 'value') else 
Color.__members__.get(color)){code}
__members__ is keyed by name, so an int gives None.

*Steps to reproduce*

repro.thrift

 
{code:java}
namespace py repro
enum Color {
  RED = 1,
  GREEN = 2,
}
typedef Color ColorAlias
// control case: fixed by THRIFT-5885
struct MutableDirect {
  1: required Color color,
}
// case 1: typedef'd enum
struct MutableTypedef {
  1: required ColorAlias color,
}
// case 2: enum inside a container
struct MutableList {
  1: required list<Color> colors,
}
// case 3: immutable struct
struct ImmutableDirect {
  1: required Color color,
} (python.immutable = "")
{code}
Generate
{code:java}
thrift --gen py:enum repro.thrift{code}
repro.py

 
{code:java}
import sys
sys.path.insert(0, "gen-py")
from thrift.protocol import TBinaryProtocol, TJSONProtocol
from thrift.transport import TTransport
from repro.ttypes import Color, ImmutableDirect, MutableDirect, MutableList, 
MutableTypedef
ACCELERATED = TBinaryProtocol.TBinaryProtocolAcceleratedFactory()
PLAIN = TBinaryProtocol.TBinaryProtocolFactory()
JSON = TJSONProtocol.TJSONProtocolFactory()

def serialize(obj, factory):
    buf = TTransport.TMemoryBuffer()
    obj.write(factory.getProtocol(buf))
    return buf.getvalue()

def deserialize(cls, data, factory):
    protocol = factory.getProtocol(TTransport.TMemoryBuffer(data))
    if isinstance(cls.__dict__.get("read"), classmethod):  # python.immutable
        return cls.read(protocol)
    obj = cls()
    obj.read(protocol)
    return obj

assert ACCELERATED.getProtocol(TTransport.TMemoryBuffer(b""))._fast_decode is 
not None, (
    "fastbinary C extension is not available"
)
cases = [
    ("MutableDirect  ", MutableDirect(color=Color.GREEN), lambda o: o.color),
    ("MutableTypedef ", MutableTypedef(color=Color.GREEN), lambda o: o.color),
    ("MutableList    ", MutableList(colors=[Color.RED, Color.GREEN]), lambda o: 
o.colors),
    ("ImmutableDirect", ImmutableDirect(color=Color.GREEN), lambda o: o.color),
]
for label, obj, get in cases:
    data = serialize(obj, PLAIN)
    plain_value = get(deserialize(type(obj), data, PLAIN))
    fast_obj = deserialize(type(obj), data, ACCELERATED)
    fast_value = get(fast_obj)
    try:
        serialize(fast_obj, JSON)
        reserialize = "ok"
    except Exception as e:
        reserialize = f"{type(e).__name__}: {e}"
    print(f"{label} plain={plain_value!r:<35} accelerated={fast_value!r:<20} 
json re-serialize: {reserialize}")
{code}
 

Result
{code:java}
MutableDirect   plain=<Color.GREEN: 2>  accelerated=<Color.GREEN: 2>  json 
re-serialize: ok
MutableTypedef  plain=<Color.GREEN: 2>  accelerated=2                 json 
re-serialize: AttributeError: 'int' object has no attribute 'value'
MutableList     plain=[<Color.RED: 1>, <Color.GREEN: 2>]  accelerated=[1, 2]  
json re-serialize: AttributeError: 'int' object has no attribute 'value'
ImmutableDirect plain=<Color.GREEN: 2>  accelerated=None              json 
re-serialize: TProtocolException: Required field color is unset!{code}
 



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to