Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 1a0b111

Browse files
feat: Add a bigframes cell magic for ipython
1 parent 74150c5 commit 1a0b111

3 files changed

Lines changed: 158 additions & 0 deletions

File tree

bigframes/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,24 @@
1616

1717
from bigframes._config import option_context, options
1818
from bigframes._config.bigquery_options import BigQueryOptions
19+
from bigframes._magics import _cell_magic
1920
from bigframes.core.global_session import close_session, get_global_session
2021
import bigframes.enums as enums
2122
import bigframes.exceptions as exceptions
2223
from bigframes.session import connect, Session
2324
from bigframes.version import __version__
2425

26+
_MAGIC_NAMES = ["bigframes"]
27+
28+
29+
def load_ipython_extension(ipython):
30+
"""Called by IPython when this module is loaded as an IPython extension."""
31+
for magic_name in _MAGIC_NAMES:
32+
ipython.register_magic_function(
33+
_cell_magic, magic_kind="cell", magic_name=magic_name
34+
)
35+
36+
2537
__all__ = [
2638
"options",
2739
"BigQueryOptions",

bigframes/_magics.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2026 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+
from IPython.core import magic_arguments # type: ignore
16+
from IPython.core.getipython import get_ipython
17+
from IPython.display import display
18+
19+
import bigframes.pandas
20+
21+
22+
@magic_arguments.magic_arguments()
23+
@magic_arguments.argument(
24+
"destination_var",
25+
nargs="?",
26+
help=("If provided, save the output to this variable instead of displaying it."),
27+
)
28+
@magic_arguments.argument(
29+
"--dry_run",
30+
action="store_true",
31+
default=False,
32+
help=(
33+
"Sets query to be a dry run to estimate costs. "
34+
"Defaults to executing the query instead of dry run if this argument is not used."
35+
"Does not work with engine 'bigframes'. "
36+
),
37+
)
38+
def _cell_magic(line, cell):
39+
ipython = get_ipython()
40+
args = magic_arguments.parse_argstring(_cell_magic, line)
41+
if not cell:
42+
print("Query is missing.")
43+
pyformat_args = ipython.user_ns
44+
dataframe = bigframes.pandas._read_gbq_colab(
45+
cell, pyformat_args=pyformat_args, dry_run=args.dry_run
46+
)
47+
if args.destination_var:
48+
ipython.push({args.destination_var: dataframe})
49+
else:
50+
display(dataframe)
51+
return dataframe

tests/system/small/test_magics.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright 2025 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+
from IPython.testing.globalipapp import get_ipython
16+
from IPython.utils.capture import capture_output
17+
import pandas as pd
18+
import pytest
19+
20+
import bigframes
21+
import bigframes.pandas as bpd
22+
23+
MAGIC_NAME = "bigframes"
24+
25+
26+
@pytest.fixture(scope="module")
27+
def ip():
28+
"""Provides a persistent IPython shell instance for the test session."""
29+
shell = get_ipython()
30+
shell.extension_manager.load_extension("bigframes")
31+
return shell
32+
33+
34+
def test_magic_select_lit_to_var(ip):
35+
bigframes.close_session()
36+
37+
line = "dst_var"
38+
cell_body = "SELECT 3"
39+
40+
ip.run_cell_magic(MAGIC_NAME, line, cell_body)
41+
42+
assert "dst_var" in ip.user_ns
43+
result_df = ip.user_ns["dst_var"]
44+
assert result_df.shape == (1, 1)
45+
assert result_df.loc[0, 0] == 3
46+
47+
48+
def test_magic_select_lit_dry_run(ip):
49+
bigframes.close_session()
50+
51+
line = "dst_var --dry_run"
52+
cell_body = "SELECT 3"
53+
54+
ip.run_cell_magic(MAGIC_NAME, line, cell_body)
55+
56+
assert "dst_var" in ip.user_ns
57+
result_df = ip.user_ns["dst_var"]
58+
assert result_df.totalBytesProcessed == 0
59+
60+
61+
def test_magic_select_lit_display(ip):
62+
bigframes.close_session()
63+
64+
cell_body = "SELECT 3"
65+
66+
with capture_output() as io:
67+
ip.run_cell_magic(MAGIC_NAME, "", cell_body)
68+
assert len(io.outputs) > 0
69+
html_data = io.outputs[0].data["text/html"]
70+
assert "[1 rows x 1 columns in total]" in html_data
71+
72+
73+
def test_magic_select_interpolate(ip):
74+
bigframes.close_session()
75+
df = bpd.read_pandas(
76+
pd.DataFrame({"col_a": [1, 2, 3, 4, 5, 6], "col_b": [1, 2, 1, 3, 1, 2]})
77+
)
78+
const_val = 1
79+
80+
ip.push({"df": df, "const_val": const_val})
81+
82+
query = """
83+
SELECT
84+
SUM(col_a) AS total
85+
FROM
86+
{df}
87+
WHERE col_b={const_val}
88+
"""
89+
90+
ip.run_cell_magic(MAGIC_NAME, "dst_var", query)
91+
92+
assert "dst_var" in ip.user_ns
93+
result_df = ip.user_ns["dst_var"]
94+
assert result_df.shape == (1, 1)
95+
assert result_df.loc[0, 0] == 9

0 commit comments

Comments
 (0)