Skip to content

Commit e14c7a9

Browse files
authored
fix: allow __repr__ to work with uninitialed DataFrame/Series/Index (#778)
1 parent 36b43f2 commit e14c7a9

File tree

6 files changed

+106
-3
lines changed

6 files changed

+106
-3
lines changed

bigframes/core/indexes/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ def query_job(self) -> Optional[bigquery.QueryJob]:
243243
return self._query_job
244244

245245
def __repr__(self) -> str:
246+
# Protect against errors with uninitialized Series. See:
247+
# https://github.com/googleapis/python-bigquery-dataframes/issues/728
248+
if not hasattr(self, "_block"):
249+
return object.__repr__(self)
250+
246251
# TODO(swast): Add a timeout here? If the query is taking a long time,
247252
# maybe we just print the job metadata that we have so far?
248253
# TODO(swast): Avoid downloading the whole series by using job

bigframes/dataframe.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -574,9 +574,18 @@ def _getitem_bool_series(self, key: bigframes.series.Series) -> DataFrame:
574574
return DataFrame(block)
575575

576576
def __getattr__(self, key: str):
577+
# Protect against recursion errors with uninitialized DataFrame
578+
# objects. See:
579+
# https://github.com/googleapis/python-bigquery-dataframes/issues/728
580+
# and
581+
# https://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html
582+
if key == "_block":
583+
raise AttributeError("_block")
584+
577585
if key in self._block.column_labels:
578586
return self.__getitem__(key)
579-
elif hasattr(pandas.DataFrame, key):
587+
588+
if hasattr(pandas.DataFrame, key):
580589
raise AttributeError(
581590
textwrap.dedent(
582591
f"""
@@ -585,8 +594,7 @@ def __getattr__(self, key: str):
585594
"""
586595
)
587596
)
588-
else:
589-
raise AttributeError(key)
597+
raise AttributeError(key)
590598

591599
def __setattr__(self, key: str, value):
592600
if key in ["_block", "_query_job"]:
@@ -616,6 +624,11 @@ def __repr__(self) -> str:
616624
617625
Only represents the first `bigframes.options.display.max_rows`.
618626
"""
627+
# Protect against errors with uninitialized DataFrame. See:
628+
# https://github.com/googleapis/python-bigquery-dataframes/issues/728
629+
if not hasattr(self, "_block"):
630+
return object.__repr__(self)
631+
619632
opts = bigframes.options.display
620633
max_results = opts.max_rows
621634
if opts.repr_mode == "deferred":

bigframes/series.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ def reset_index(
281281
return bigframes.dataframe.DataFrame(block)
282282

283283
def __repr__(self) -> str:
284+
# Protect against errors with uninitialized Series. See:
285+
# https://github.com/googleapis/python-bigquery-dataframes/issues/728
286+
if not hasattr(self, "_block"):
287+
return object.__repr__(self)
288+
284289
# TODO(swast): Add a timeout here? If the query is taking a long time,
285290
# maybe we just print the job metadata that we have so far?
286291
# TODO(swast): Avoid downloading the whole series by using job

tests/unit/core/test_indexes.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2024 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import bigframes.core.indexes
16+
17+
18+
def test_index_repr_with_uninitialized_object():
19+
"""Ensures Index.__init__ can be paused in a visual debugger without crashing.
20+
21+
Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728
22+
"""
23+
# Avoid calling __init__ to simulate pausing __init__ in a debugger.
24+
# https://stackoverflow.com/a/6384982/101923
25+
index = object.__new__(bigframes.core.indexes.Index)
26+
got = repr(index)
27+
assert "Index" in got
28+
29+
30+
def test_multiindex_repr_with_uninitialized_object():
31+
"""Ensures MultiIndex.__init__ can be paused in a visual debugger without crashing.
32+
33+
Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728
34+
"""
35+
# Avoid calling __init__ to simulate pausing __init__ in a debugger.
36+
# https://stackoverflow.com/a/6384982/101923
37+
index = object.__new__(bigframes.core.indexes.MultiIndex)
38+
got = repr(index)
39+
assert "MultiIndex" in got

tests/unit/test_dataframe.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,23 @@
1515
import google.cloud.bigquery
1616
import pytest
1717

18+
import bigframes.dataframe
19+
1820
from . import resources
1921

2022

23+
def test_dataframe_repr_with_uninitialized_object():
24+
"""Ensures DataFrame.__init__ can be paused in a visual debugger without crashing.
25+
26+
Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728
27+
"""
28+
# Avoid calling __init__ to simulate pausing __init__ in a debugger.
29+
# https://stackoverflow.com/a/6384982/101923
30+
dataframe = bigframes.dataframe.DataFrame.__new__(bigframes.dataframe.DataFrame)
31+
got = repr(dataframe)
32+
assert "DataFrame" in got
33+
34+
2135
def test_dataframe_to_gbq_invalid_destination(monkeypatch: pytest.MonkeyPatch):
2236
dataframe = resources.create_dataframe(monkeypatch)
2337

tests/unit/test_series.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2024 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import bigframes.series
16+
17+
18+
def test_series_repr_with_uninitialized_object():
19+
"""Ensures Series.__init__ can be paused in a visual debugger without crashing.
20+
21+
Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728
22+
"""
23+
# Avoid calling __init__ to simulate pausing __init__ in a debugger.
24+
# https://stackoverflow.com/a/6384982/101923
25+
series = bigframes.series.Series.__new__(bigframes.series.Series)
26+
got = repr(series)
27+
assert "Series" in got

0 commit comments

Comments
 (0)