Skip to content

Commit ef43b54

Browse files
committed
fix: replace eval with ast.literal_eval; ruff linting
strings.py: When reading csv export files, use the safer literal_eval to evaluate the fields. Issue pointed out by ruff linter. Other Cleanup: sort imports replace if, else, if, else with if, elif, else use isinstance with tuples rather than 'isinstance() or isinstance()' test_anypy.py: Added tests for tuples, and booleans. Also added exception handling for malformed strings, booleans, tuples.
1 parent 97b17f7 commit ef43b54

File tree

3 files changed

+64
-38
lines changed

3 files changed

+64
-38
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ Fixed:
9292
classhelp for users now shows the requested properties. (Found by
9393
team-3 of the UMass-Boston CS682 Spring 2024 class; fix John
9494
Rouillard)
95+
- use ast.eval_literal() rather than eval() to turn CSV exported
96+
string values into Python object/values.
9597

9698
Features:
9799

roundup/anypy/strings.py

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
# few places, generally for interacting with external modules
55
# requiring that type to be used.
66

7-
import sys
7+
import ast
88
import io
9+
import sys
910

1011
_py3 = sys.version_info[0] > 2
1112

@@ -63,22 +64,20 @@ def us2u(s, errors='strict'):
6364
"""Convert a string or Unicode string to a Unicode string."""
6465
if _py3:
6566
return s
67+
elif isinstance(s, unicode): # noqa: F821
68+
return s
6669
else:
67-
if isinstance(s, unicode): # noqa: F821
68-
return s
69-
else:
70-
return unicode(s, 'utf-8', errors) # noqa: F821
70+
return unicode(s, 'utf-8', errors) # noqa: F821
7171

7272

7373
def us2s(u):
7474
"""Convert a string or Unicode string to the internal string format."""
7575
if _py3:
7676
return u
77+
elif isinstance(u, unicode): # noqa: F821
78+
return u.encode('utf-8')
7779
else:
78-
if isinstance(u, unicode): # noqa: F821
79-
return u.encode('utf-8')
80-
else:
81-
return u
80+
return u
8281

8382

8483
def uany2s(u):
@@ -87,19 +86,18 @@ def uany2s(u):
8786
Objects that are not Unicode strings are passed to str()."""
8887
if _py3:
8988
return str(u)
89+
elif isinstance(u, unicode): # noqa: F821
90+
return u.encode('utf-8')
9091
else:
91-
if isinstance(u, unicode): # noqa: F821
92-
return u.encode('utf-8')
93-
else:
94-
return str(u)
92+
return str(u)
9593

9694

9795
def is_us(s):
9896
"""Return whether an object is a string or Unicode string."""
9997
if _py3:
10098
return isinstance(s, str)
10199
else:
102-
return isinstance(s, str) or isinstance(s, unicode) # noqa: F821
100+
return isinstance(s, (str, unicode)) # noqa: F821
103101

104102

105103
def uchr(c):
@@ -141,28 +139,37 @@ def repr_export(v):
141139

142140
def eval_import(s):
143141
"""Evaluate a Python-2-style value imported from a CSV file."""
144-
if _py3:
145-
try:
146-
v = eval(s)
147-
except SyntaxError:
148-
# handle case where link operation reports id a long int
149-
# ('issue', 5002L, "status") rather than as a string.
150-
# This was a bug that existed and was fixed before or with v1.2.0
151-
import re
152-
v = eval(re.sub(r', ([0-9]+)L,', r', \1,', s))
153-
154-
if isinstance(v, str):
155-
return v.encode('iso-8859-1').decode('utf-8')
156-
elif isinstance(v, dict):
157-
v_mod = {}
158-
for key, value in v.items():
159-
if isinstance(key, str):
160-
key = key.encode('iso-8859-1').decode('utf-8')
161-
if isinstance(value, str):
162-
value = value.encode('iso-8859-1').decode('utf-8')
163-
v_mod[key] = value
164-
return v_mod
142+
try:
143+
if _py3:
144+
try:
145+
v = ast.literal_eval(s)
146+
except SyntaxError:
147+
# handle case where link operation reports id a long
148+
# int ('issue', 5002L, "status") rather than as a
149+
# string. This was a bug that existed and was fixed
150+
# before or with v1.2.0
151+
import re
152+
v = ast.literal_eval(re.sub(r', ([0-9]+)L,', r', \1,', s))
153+
154+
if isinstance(v, str):
155+
return v.encode('iso-8859-1').decode('utf-8')
156+
elif isinstance(v, dict):
157+
v_mod = {}
158+
# ruff: noqa: PLW2901
159+
for key, value in v.items():
160+
if isinstance(key, str):
161+
key = key.encode('iso-8859-1').decode('utf-8')
162+
if isinstance(value, str):
163+
value = value.encode('iso-8859-1').decode('utf-8')
164+
v_mod[key] = value
165+
return v_mod
166+
else:
167+
return v
165168
else:
166-
return v
167-
else:
168-
return eval(s)
169+
return ast.literal_eval(s)
170+
171+
except (ValueError, SyntaxError) as e:
172+
raise ValueError(
173+
("Error %(exception)s trying to parse value '%(value)s'") %
174+
{ 'exception': e, 'value': s})
175+

test/test_anypy.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@ def test_import_params(self):
1717
val = eval_import("('issue', 2345L, 'status')")
1818
self.assertSequenceEqual(val, ('issue', 2345, 'status'))
1919

20+
# eval a tuple e.g. date representation
21+
val = eval_import("(2022, 9, 6, 3, 58, 4.776, 0, 0, 0)")
22+
self.assertSequenceEqual(val, (2022, 9, 6, 3, 58, 4.776, 0, 0, 0))
23+
24+
# eval a boolean
25+
val = eval_import("False")
26+
self.assertEqual(val, False)
27+
val = eval_import("True")
28+
self.assertEqual(val, True)
29+
30+
# check syntax error
31+
for testcase in ['true', '(2004, 10, 20', "2000, 10, 22)",
32+
"test'", '"test']:
33+
with self.assertRaises(ValueError) as m:
34+
val = eval_import(testcase)
35+
print(m.exception)
36+
2037
# python3 export with id as number
2138
val = eval_import("('issue', 2345, 'status')")
2239
self.assertSequenceEqual(val, ('issue', 2345, 'status'))

0 commit comments

Comments
 (0)