Skip to content
This repository was archived by the owner on Sep 2, 2025. It is now read-only.

Commit e678489

Browse files
Adap 1162/merge agate lazy load (#1250)
* lazy load agate * Add test and documentation. * Fix test. * Don't need a test for this. --------- Co-authored-by: dwreeves <[email protected]> Co-authored-by: Mila Page <[email protected]>
1 parent 995ebcb commit e678489

File tree

4 files changed

+63
-23
lines changed

4 files changed

+63
-23
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Under the Hood
2+
body: Lazy load `agate`
3+
time: 2024-03-31T10:14:18.260074-04:00
4+
custom:
5+
Author: dwreeves
6+
Issue: "1162"

dbt/adapters/bigquery/connections.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
from mashumaro.helper import pass_through
1111

1212
from functools import lru_cache
13-
import agate
1413
from requests.exceptions import ConnectionError
15-
from typing import Optional, Any, Dict, Tuple
14+
from typing import Optional, Any, Dict, Tuple, TYPE_CHECKING
1615

1716
import google.auth
1817
import google.auth.exceptions
@@ -26,7 +25,6 @@
2625
)
2726

2827
from dbt.adapters.bigquery import gcloud
29-
from dbt_common.clients import agate_helper
3028
from dbt.adapters.contracts.connection import ConnectionState, AdapterResponse, Credentials
3129
from dbt_common.exceptions import (
3230
DbtRuntimeError,
@@ -44,6 +42,10 @@
4442

4543
from dbt_common.dataclass_schema import ExtensibleDbtClassMixin, StrEnum
4644

45+
if TYPE_CHECKING:
46+
# Indirectly imported via agate_helper, which is lazy loaded further downfile.
47+
# Used by mypy for earlier type hints.
48+
import agate
4749

4850
logger = AdapterLogger("BigQuery")
4951

@@ -432,7 +434,9 @@ def get_job_retry_deadline_seconds(cls, conn):
432434
return credentials.job_retry_deadline_seconds
433435

434436
@classmethod
435-
def get_table_from_response(cls, resp):
437+
def get_table_from_response(cls, resp) -> "agate.Table":
438+
from dbt_common.clients import agate_helper
439+
436440
column_names = [field.name for field in resp.schema]
437441
return agate_helper.table_from_data_flat(resp, column_names)
438442

@@ -499,14 +503,16 @@ def fn():
499503

500504
def execute(
501505
self, sql, auto_begin=False, fetch=None, limit: Optional[int] = None
502-
) -> Tuple[BigQueryAdapterResponse, agate.Table]:
506+
) -> Tuple[BigQueryAdapterResponse, "agate.Table"]:
503507
sql = self._add_query_comment(sql)
504508
# auto_begin is ignored on bigquery, and only included for consistency
505509
query_job, iterator = self.raw_execute(sql, limit=limit)
506510

507511
if fetch:
508512
table = self.get_table_from_response(iterator)
509513
else:
514+
from dbt_common.clients import agate_helper
515+
510516
table = agate_helper.empty_table()
511517

512518
message = "OK"

dbt/adapters/bigquery/impl.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,20 @@
55
from multiprocessing.context import SpawnContext
66

77
import time
8-
from typing import Any, Dict, List, Optional, Type, Set, Union, FrozenSet, Tuple, Iterable
8+
from typing import (
9+
Any,
10+
Dict,
11+
List,
12+
Optional,
13+
Type,
14+
Set,
15+
Union,
16+
FrozenSet,
17+
Tuple,
18+
Iterable,
19+
TYPE_CHECKING,
20+
)
921

10-
import agate
1122
from dbt.adapters.contracts.relation import RelationConfig
1223

1324
import dbt_common.exceptions.base
@@ -24,7 +35,6 @@
2435
from dbt.adapters.base.impl import FreshnessResponse
2536
from dbt.adapters.cache import _make_ref_key_dict # type: ignore
2637
from dbt.adapters.capability import Capability, CapabilityDict, CapabilitySupport, Support
27-
import dbt_common.clients.agate_helper
2838
from dbt.adapters.contracts.connection import AdapterResponse
2939
from dbt.adapters.contracts.macros import MacroResolverProtocol
3040
from dbt_common.contracts.constraints import ColumnLevelConstraint, ConstraintType, ModelLevelConstraint # type: ignore
@@ -58,6 +68,10 @@
5868
)
5969
from dbt.adapters.bigquery.utility import sql_escape
6070

71+
if TYPE_CHECKING:
72+
# Indirectly imported via agate_helper, which is lazy loaded further downfile.
73+
# Used by mypy for earlier type hints.
74+
import agate
6175

6276
logger = AdapterLogger("BigQuery")
6377

@@ -334,32 +348,34 @@ def quote(cls, identifier: str) -> str:
334348
return "`{}`".format(identifier)
335349

336350
@classmethod
337-
def convert_text_type(cls, agate_table: agate.Table, col_idx: int) -> str:
351+
def convert_text_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
338352
return "string"
339353

340354
@classmethod
341-
def convert_number_type(cls, agate_table: agate.Table, col_idx: int) -> str:
355+
def convert_number_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
356+
import agate
357+
342358
decimals = agate_table.aggregate(agate.MaxPrecision(col_idx)) # type: ignore[attr-defined]
343359
return "float64" if decimals else "int64"
344360

345361
@classmethod
346-
def convert_integer_type(cls, agate_table: agate.Table, col_idx: int) -> str:
362+
def convert_integer_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
347363
return "int64"
348364

349365
@classmethod
350-
def convert_boolean_type(cls, agate_table: agate.Table, col_idx: int) -> str:
366+
def convert_boolean_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
351367
return "bool"
352368

353369
@classmethod
354-
def convert_datetime_type(cls, agate_table: agate.Table, col_idx: int) -> str:
370+
def convert_datetime_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
355371
return "datetime"
356372

357373
@classmethod
358-
def convert_date_type(cls, agate_table: agate.Table, col_idx: int) -> str:
374+
def convert_date_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
359375
return "date"
360376

361377
@classmethod
362-
def convert_time_type(cls, agate_table: agate.Table, col_idx: int) -> str:
378+
def convert_time_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
363379
return "time"
364380

365381
###
@@ -387,7 +403,7 @@ def _get_dbt_columns_from_bq_table(self, table) -> List[BigQueryColumn]:
387403
return columns
388404

389405
def _agate_to_schema(
390-
self, agate_table: agate.Table, column_override: Dict[str, str]
406+
self, agate_table: "agate.Table", column_override: Dict[str, str]
391407
) -> List[SchemaField]:
392408
"""Convert agate.Table with column names to a list of bigquery schemas."""
393409
bq_schema = []
@@ -655,7 +671,13 @@ def alter_table_add_columns(self, relation, columns):
655671

656672
@available.parse_none
657673
def load_dataframe(
658-
self, database, schema, table_name, agate_table, column_override, field_delimiter
674+
self,
675+
database,
676+
schema,
677+
table_name,
678+
agate_table: "agate.Table",
679+
column_override,
680+
field_delimiter,
659681
):
660682
bq_schema = self._agate_to_schema(agate_table, column_override)
661683
conn = self.connections.get_thread_connection()
@@ -667,7 +689,7 @@ def load_dataframe(
667689
load_config.skip_leading_rows = 1
668690
load_config.schema = bq_schema
669691
load_config.field_delimiter = field_delimiter
670-
with open(agate_table.original_abspath, "rb") as f:
692+
with open(agate_table.original_abspath, "rb") as f: # type: ignore
671693
job = client.load_table_from_file(f, table_ref, rewind=True, job_config=load_config)
672694

673695
timeout = self.connections.get_job_execution_timeout_seconds(conn) or 300
@@ -699,8 +721,8 @@ def upload_file(
699721

700722
@classmethod
701723
def _catalog_filter_table(
702-
cls, table: agate.Table, used_schemas: FrozenSet[Tuple[str, str]]
703-
) -> agate.Table:
724+
cls, table: "agate.Table", used_schemas: FrozenSet[Tuple[str, str]]
725+
) -> "agate.Table":
704726
table = table.rename(
705727
column_names={col.name: col.name.replace("__", ":") for col in table.columns}
706728
)

dbt/adapters/bigquery/relation_configs/_base.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from dataclasses import dataclass
2-
from typing import Optional, Dict
2+
from typing import Optional, Dict, TYPE_CHECKING
33

4-
import agate
54
from dbt.adapters.base.relation import Policy
65
from dbt.adapters.relation_configs import RelationConfigBase
76
from google.cloud.bigquery import Table as BigQueryTable
@@ -13,6 +12,11 @@
1312
)
1413
from dbt.adapters.contracts.relation import ComponentName, RelationConfig
1514

15+
if TYPE_CHECKING:
16+
# Indirectly imported via agate_helper, which is lazy loaded further downfile.
17+
# Used by mypy for earlier type hints.
18+
import agate
19+
1620

1721
@dataclass(frozen=True, eq=True, unsafe_hash=True)
1822
class BigQueryBaseRelationConfig(RelationConfigBase):
@@ -55,8 +59,10 @@ def _render_part(cls, component: ComponentName, value: Optional[str]) -> Optiona
5559
return None
5660

5761
@classmethod
58-
def _get_first_row(cls, results: agate.Table) -> agate.Row:
62+
def _get_first_row(cls, results: "agate.Table") -> "agate.Row":
5963
try:
6064
return results.rows[0]
6165
except IndexError:
66+
import agate
67+
6268
return agate.Row(values=set())

0 commit comments

Comments
 (0)