THRIFT-5927 Cannot use reserved language keyword "None" with target language Python
Client: py
Patch: Jens Geyer
Generated-by: Opencode big-pickle
diff --git a/lib/py/Makefile.am b/lib/py/Makefile.am
index 2be72de..0abd349 100644
--- a/lib/py/Makefile.am
+++ b/lib/py/Makefile.am
@@ -58,6 +58,7 @@
$(PYTHON) test/thrift_TCompactProtocol.py
$(PYTHON) test/thrift_TNonblockingServer.py
$(PYTHON) test/thrift_TSerializer.py
+ $(PYTHON) test/test_compiler/test_keyword_escape.py
clean-local:
diff --git a/lib/py/test/test_compiler/Thrift5927.thrift b/lib/py/test/test_compiler/Thrift5927.thrift
new file mode 100644
index 0000000..6b612a5
--- /dev/null
+++ b/lib/py/test/test_compiler/Thrift5927.thrift
@@ -0,0 +1,41 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+namespace py thrift5927
+
+enum Lambda {
+ None = 0,
+ FluxCapacitor = 1
+}
+
+struct False {
+ 1: Lambda finally
+}
+
+union import {
+ 1: import print
+ 2: False while
+}
+
+exception True {
+ 1: import print
+}
+
+service continue {
+ import return(1: False while) throws (1: True yield)
+}
+
diff --git a/lib/py/test/test_compiler/test_keyword_escape.py b/lib/py/test/test_compiler/test_keyword_escape.py
new file mode 100755
index 0000000..37baccc
--- /dev/null
+++ b/lib/py/test/test_compiler/test_keyword_escape.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import os
+import sys
+import subprocess
+import tempfile
+import py_compile
+import glob
+import shutil
+
+
+def find_thrift():
+ """Find the thrift compiler."""
+ # Check THRIFT environment variable (set by build system)
+ if 'THRIFT' in os.environ:
+ thrift_path = os.environ['THRIFT']
+ if os.path.exists(thrift_path):
+ return thrift_path
+
+ # Check common locations in PATH
+ paths = [
+ 'thrift',
+ '/usr/local/bin/thrift',
+ '/usr/bin/thrift',
+ ]
+
+ for path in paths:
+ if shutil.which(path):
+ return path
+
+ # Check if we're in the source tree with built compiler
+ possible_build_thrift = os.path.join(os.path.dirname(__file__), '..', '..', '..', 'build', 'compiler', 'cpp', 'bin', 'thrift')
+ if os.path.exists(possible_build_thrift):
+ return possible_build_thrift
+
+ # Check for cpp compiler location
+ possible_cpp_thrift = os.path.join(os.path.dirname(__file__), '..', '..', '..', 'compiler', 'cpp', 'thrift')
+ if os.path.exists(possible_cpp_thrift):
+ return possible_cpp_thrift
+
+ return None
+
+
+def test_keyword_escape_compilation():
+ """
+ Test that the Python generator produces valid Python code
+ when the Thrift schema contains Python keywords.
+
+ This verifies THRIFT-5927: Python code generator should escape
+ identifiers that are Python reserved keywords.
+ """
+ thrift_file = os.path.join(os.path.dirname(__file__), 'Thrift5927.thrift')
+
+ if not os.path.exists(thrift_file):
+ print(f"ERROR: Test file not found: {thrift_file}")
+ return 1
+
+ thrift_bin = find_thrift()
+ if not thrift_bin:
+ print("WARNING: thrift compiler not found, skipping test")
+ print("(In CI, thrift should be available via THRIFT env var)")
+ return 0 # Skip gracefully rather than fail
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ result = subprocess.run(
+ [thrift_bin, '-r', '-gen', 'py', '-out', tmpdir, thrift_file],
+ capture_output=True,
+ text=True
+ )
+
+ if result.returncode != 0:
+ print(f"ERROR: thrift compiler failed")
+ print(f"stdout: {result.stdout}")
+ print(f"stderr: {result.stderr}")
+ return 1
+
+ py_files = glob.glob(os.path.join(tmpdir, '**', '*.py'), recursive=True)
+
+ if not py_files:
+ print("ERROR: No Python files generated")
+ return 1
+
+ failed = []
+ for py_file in py_files:
+ try:
+ py_compile.compile(py_file, doraise=True)
+ except py_compile.PyCompileError as e:
+ failed.append((py_file, str(e)))
+
+ if failed:
+ print("ERROR: Generated Python files have syntax errors:")
+ for file_path, error in failed:
+ print(f" {file_path}: {error}")
+ return 1
+
+ print(f"OK: All {len(py_files)} generated Python files compile successfully")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(test_keyword_escape_compilation())
\ No newline at end of file