Skip to content

Commit 91cdd32

Browse files
committed
cqltypes: fix SyntaxWarning for invalid escape sequence in UDT names
In cqltype_to_python(), double-quoted UDT names containing backslashes or single quotes were passed to ast.literal_eval without proper escaping, producing SyntaxWarning on Python 3.12+ or SyntaxError for single quotes. Escape both backslashes and single quotes before wrapping the token for ast.literal_eval. The reverse operation in python_to_cqltype() is updated to unescape both sequences as well.
1 parent 9c53d78 commit 91cdd32

2 files changed

Lines changed: 40 additions & 3 deletions

File tree

cassandra/cqltypes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def cqltype_to_python(cql_string):
130130
(r'<', lambda s, t: ', ['),
131131
(r'>', lambda s, t: ']'),
132132
(r'[, ]', lambda s, t: t),
133-
(r'".*?"', lambda s, t: "'{}'".format(t)),
133+
(r'".*?"', lambda s, t: "'{}'".format(t.replace('\\', '\\\\').replace("'", "\\'"))),
134134
))
135135

136136
scanned_tokens = scanner.scan(cql_string)[0]
@@ -156,7 +156,7 @@ def python_to_cqltype(types):
156156
))
157157

158158
scanned_tokens = scanner.scan(repr(types))[0]
159-
cql = ''.join(scanned_tokens).replace('\\\\', '\\')
159+
cql = ''.join(scanned_tokens).replace('\\\\', '\\').replace("\\'", "'")
160160
return cql
161161

162162

tests/unit/test_metadata.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import unittest
15+
import warnings
1516

1617
from binascii import unhexlify
1718
import logging
@@ -21,7 +22,7 @@
2122
import uuid
2223

2324
import cassandra
24-
from cassandra.cqltypes import strip_frozen
25+
from cassandra.cqltypes import cqltype_to_python, python_to_cqltype, strip_frozen
2526
from cassandra.marshal import uint16_unpack, uint16_pack
2627
from cassandra.metadata import (Murmur3Token, MD5Token,
2728
BytesToken, ReplicationStrategy,
@@ -846,3 +847,39 @@ def test_strip_frozen(self):
846847
for argument, expected_result in argument_to_expected_results:
847848
result = strip_frozen(argument)
848849
assert result == expected_result, "strip_frozen() arg: {}".format(argument)
850+
851+
def test_cqltype_backslash_escape(self):
852+
"""Verify that UDT names containing backslashes don't trigger SyntaxWarning (python-driver#750)."""
853+
udt_input = r'map<"!@#$%^&*()[]\ frozen >>>", int>'
854+
855+
# Parsing must not emit SyntaxWarning
856+
with warnings.catch_warnings(record=True) as caught:
857+
warnings.simplefilter('always')
858+
result = cqltype_to_python(udt_input)
859+
syntax_warnings = [w for w in caught if issubclass(w.category, SyntaxWarning)]
860+
self.assertEqual(syntax_warnings, [], 'cqltype_to_python emitted SyntaxWarning')
861+
862+
# Parsed result should preserve the quoted UDT name with backslash
863+
self.assertEqual(result[0], 'map')
864+
self.assertIsInstance(result[1], list)
865+
self.assertEqual(result[1][0], r'"!@#$%^&*()[]\ frozen >>>"')
866+
self.assertEqual(result[1][1], 'int')
867+
868+
# Round-trip: python_to_cqltype(cqltype_to_python(x)) == x
869+
round_tripped = python_to_cqltype(result)
870+
self.assertEqual(round_tripped, udt_input)
871+
872+
def test_cqltype_single_quote_in_identifier(self):
873+
"""Verify that UDT names containing single quotes parse and round-trip correctly."""
874+
udt_input = 'map<"it\'s", int>'
875+
876+
result = cqltype_to_python(udt_input)
877+
878+
self.assertEqual(result[0], 'map')
879+
self.assertIsInstance(result[1], list)
880+
self.assertEqual(result[1][0], '"it\'s"')
881+
self.assertEqual(result[1][1], 'int')
882+
883+
# Round-trip: python_to_cqltype(cqltype_to_python(x)) == x
884+
round_tripped = python_to_cqltype(result)
885+
self.assertEqual(round_tripped, udt_input)

0 commit comments

Comments
 (0)