Skip to content

Commit 13ac860

Browse files
plamuttseaver
andauthored
fix: error if eval()-ing repr(SchemaField) (#1046)
* fix: error if eval()-ing repr(SchemaField) * Make repr(PolicyTagList) evaluable * Fix SchemaField repr with policy tags The repr() should be evaluable. Co-authored-by: Tres Seaver <[email protected]>
1 parent c3dbeeb commit 13ac860

File tree

2 files changed

+60
-7
lines changed

2 files changed

+60
-7
lines changed

google/cloud/bigquery/schema.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def _key(self):
268268
field_type = f"{field_type}({self.precision})"
269269

270270
policy_tags = (
271-
() if self.policy_tags is None else tuple(sorted(self.policy_tags.names))
271+
None if self.policy_tags is None else tuple(sorted(self.policy_tags.names))
272272
)
273273

274274
return (
@@ -336,7 +336,11 @@ def __hash__(self):
336336
return hash(self._key())
337337

338338
def __repr__(self):
339-
return "SchemaField{}".format(self._key())
339+
key = self._key()
340+
policy_tags = key[-1]
341+
policy_tags_inst = None if policy_tags is None else PolicyTagList(policy_tags)
342+
adjusted_key = key[:-1] + (policy_tags_inst,)
343+
return f"{self.__class__.__name__}{adjusted_key}"
340344

341345

342346
def _parse_schema_resource(info):
@@ -407,7 +411,7 @@ class PolicyTagList(object):
407411
`projects/*/locations/*/taxonomies/*/policyTags/*`.
408412
"""
409413

410-
def __init__(self, names=()):
414+
def __init__(self, names: Iterable[str] = ()):
411415
self._properties = {}
412416
self._properties["names"] = tuple(names)
413417

@@ -425,7 +429,7 @@ def _key(self):
425429
Returns:
426430
Tuple: The contents of this :class:`~google.cloud.bigquery.schema.PolicyTagList`.
427431
"""
428-
return tuple(sorted(self._properties.items()))
432+
return tuple(sorted(self._properties.get("names", ())))
429433

430434
def __eq__(self, other):
431435
if not isinstance(other, PolicyTagList):
@@ -439,7 +443,7 @@ def __hash__(self):
439443
return hash(self._key())
440444

441445
def __repr__(self):
442-
return "PolicyTagList{}".format(self._key())
446+
return f"{self.__class__.__name__}(names={self._key()})"
443447

444448
@classmethod
445449
def from_api_repr(cls, api_repr: dict) -> "PolicyTagList":
@@ -478,5 +482,5 @@ def to_api_repr(self) -> dict:
478482
A dictionary representing the PolicyTagList object in
479483
serialized form.
480484
"""
481-
answer = {"names": [name for name in self.names]}
485+
answer = {"names": list(self.names)}
482486
return answer

tests/unit/test_schema.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -510,9 +510,30 @@ def test___hash__not_equals(self):
510510

511511
def test___repr__(self):
512512
field1 = self._make_one("field1", "STRING")
513-
expected = "SchemaField('field1', 'STRING', 'NULLABLE', None, (), ())"
513+
expected = "SchemaField('field1', 'STRING', 'NULLABLE', None, (), None)"
514514
self.assertEqual(repr(field1), expected)
515515

516+
def test___repr__evaluable_no_policy_tags(self):
517+
field = self._make_one("field1", "STRING", "REQUIRED", "Description")
518+
field_repr = repr(field)
519+
SchemaField = self._get_target_class() # needed for eval # noqa
520+
521+
evaled_field = eval(field_repr)
522+
523+
assert field == evaled_field
524+
525+
def test___repr__evaluable_with_policy_tags(self):
526+
policy_tags = PolicyTagList(names=["foo", "bar"])
527+
field = self._make_one(
528+
"field1", "STRING", "REQUIRED", "Description", policy_tags=policy_tags,
529+
)
530+
field_repr = repr(field)
531+
SchemaField = self._get_target_class() # needed for eval # noqa
532+
533+
evaled_field = eval(field_repr)
534+
535+
assert field == evaled_field
536+
516537

517538
# TODO: dedup with the same class in test_table.py.
518539
class _SchemaBase(object):
@@ -786,6 +807,34 @@ def test___hash__not_equals(self):
786807
set_two = {policy2}
787808
self.assertNotEqual(set_one, set_two)
788809

810+
def test___repr__no_tags(self):
811+
policy = self._make_one()
812+
assert repr(policy) == "PolicyTagList(names=())"
813+
814+
def test___repr__with_tags(self):
815+
policy1 = self._make_one(["foo", "bar", "baz"])
816+
policy2 = self._make_one(["baz", "bar", "foo"])
817+
expected_repr = "PolicyTagList(names=('bar', 'baz', 'foo'))" # alphabetical
818+
819+
assert repr(policy1) == expected_repr
820+
assert repr(policy2) == expected_repr
821+
822+
def test___repr__evaluable_no_tags(self):
823+
policy = self._make_one(names=[])
824+
policy_repr = repr(policy)
825+
826+
evaled_policy = eval(policy_repr)
827+
828+
assert policy == evaled_policy
829+
830+
def test___repr__evaluable_with_tags(self):
831+
policy = self._make_one(names=["foo", "bar"])
832+
policy_repr = repr(policy)
833+
834+
evaled_policy = eval(policy_repr)
835+
836+
assert policy == evaled_policy
837+
789838

790839
@pytest.mark.parametrize(
791840
"api,expect,key2",

0 commit comments

Comments
 (0)