GitHub

@@ -9,7 +9,7 @@

99

import json

1010

import pathlib

1111

import uuid

12-

from collections.abc import Callable, Mapping, Sequence

12+

from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence

1313

from contextlib import contextmanager

1414

from dataclasses import asdict, is_dataclass

1515

from typing import Any, TextIO, TypeVar, cast

@@ -199,8 +199,10 @@ def _decode_binary_value(value: Any) -> Any:

199199200200

def _read_records(

201201

filename: str, *, nl: bool, csv: bool, tsv: bool

202-

) -> tuple[list[dict[str, Any]], bool]:

202+

) -> tuple[Iterable[dict[str, Any]], bool]:

203203

input_format = _selected_input_format(filename, nl=nl, csv=csv, tsv=tsv)

204+

if input_format in ("csv", "tsv"):

205+

return _read_delimited_records(filename, input_format=input_format), False

204206

with click.open_file(filename, mode="r", encoding="utf-8-sig") as file:

205207

stream = cast(TextIO, file)

206208

if input_format == "json":

@@ -218,12 +220,20 @@ def _read_records(

218220

for line in stream

219221

if line.strip()

220222

], False

223+224+225+

def _read_delimited_records(

226+

filename: str, *, input_format: str

227+

) -> Iterator[dict[str, Any]]:

228+

with click.open_file(filename, mode="r", encoding="utf-8-sig") as file:

229+

stream = cast(TextIO, file)

221230

reader = csv_stdlib.DictReader(

222231

stream, dialect="excel-tab" if input_format == "tsv" else "excel"

223232

)

224233

if reader.fieldnames is None:

225234

raise click.ClickException("CSV/TSV input must include a header row")

226-

return [dict(row) for row in reader], False

235+

for row in reader:

236+

yield dict(row)

227237228238229239

def _coerce_value(

@@ -287,25 +297,25 @@ def _python_type_for_name(type_name: str) -> type[Any]:

287297288298289299

def _coerce_records(

290-

records: list[dict[str, Any]],

300+

records: Iterable[dict[str, Any]],

291301

reflected_types: Mapping[str, type[Any]],

292302

explicit_types: Mapping[str, str],

293303

*,

294304

strict: bool,

295-

) -> list[dict[str, Any]]:

305+

) -> Iterator[dict[str, Any]]:

296306

types = dict(reflected_types)

297307

types.update(

298308

{name: _python_type_for_name(type_name) for name, type_name in explicit_types.items()}

299309

)

300-

return [

310+

return (

301311

{

302312

name: _coerce_value(

303313

value, types.get(name, str), column_name=name, strict=strict

304314

)

305315

for name, value in record.items()

306316

}

307317

for record in records

308-

]

318+

)

309319310320311321

def _serializable_columns(table: Any) -> list[dict[str, Any]]:

@@ -431,6 +441,7 @@ def _write_options(function: F) -> F:

431441

click.option("--nl", is_flag=True, help="Read newline-delimited JSON."),

432442

click.option("--csv", is_flag=True, help="Read CSV with a header row."),

433443

click.option("--tsv", is_flag=True, help="Read TSV with a header row."),

444+

click.option("--batch-size", type=click.IntRange(min=1), default=100, show_default=True, help="Number of records to insert per batch."),

434445

click.option("types", "--type", multiple=True, type=(str, click.Choice(VALID_COLUMN_TYPES, case_sensitive=False)), help="Column and type to use when creating the table."),

435446

click.option("--alter", is_flag=True, help="Add nullable columns missing from an existing table."),

436447

click.option("not_null", "--not-null", multiple=True, help="Column to make NOT NULL when creating the table."),

@@ -450,6 +461,7 @@ def _perform_write(

450461

nl: bool,

451462

csv: bool,

452463

tsv: bool,

464+

batch_size: int,

453465

types: tuple[tuple[str, str], ...],

454466

alter: bool,

455467

not_null: tuple[str, ...],

@@ -461,6 +473,7 @@ def _perform_write(

461473

) -> None:

462474

if ignore and replace:

463475

raise click.ClickException("Use either --ignore or --replace, not both")

476+

input_format = _selected_input_format(file, nl=nl, csv=csv, tsv=tsv)

464477

records, single = _read_records(file, nl=nl, csv=csv, tsv=tsv)

465478

type_overrides = {name: type_name.upper() for name, type_name in types}

466479

pk: str | tuple[str, ...] | None = None

@@ -485,15 +498,21 @@ def _perform_write(

485498

"not_null": not_null,

486499

"defaults": _parse_defaults(defaults),

487500

"columns": type_overrides,

501+

"batch_size": batch_size,

502+

"stream": input_format in ("csv", "tsv"),

488503

}

489504

if upsert:

490505

if single:

491-

table.upsert(records[0], **kwargs)

506+

table.upsert(next(iter(records)), **kwargs)

492507

else:

493508

table.upsert_all(records, **kwargs)

494509

elif single:

495510

table.insert(

496-

records[0], ignore=ignore, replace=replace, truncate=truncate, **kwargs

511+

next(iter(records)),

512+

ignore=ignore,

513+

replace=replace,

514+

truncate=truncate,

515+

**kwargs,

497516

)

498517

else:

499518

table.insert_all(

@@ -536,9 +555,9 @@ def update(

536555

raise click.ClickException("update input must be one JSON object")

537556

with _database(database) as db:

538557

table = db[table_name]

539-

updates = _coerce_records(

540-

records, table.columns_dict, {}, strict=table.exists()

541-

)[0]

558+

updates = next(

559+

_coerce_records(records, table.columns_dict, {}, strict=table.exists())

560+

)

542561

table.update(_primary_key_value(table, pk_value), updates, alter=alter)

543562544563

Read the original on github.com ↗