Better validation for upsert --pk, closes #390

This commit is contained in:
Simon Willison 2022-01-25 19:19:39 -08:00
commit 2b20957b18
3 changed files with 79 additions and 59 deletions

View file

@ -253,8 +253,6 @@ See :ref:`cli_upsert`.
incoming record has a primary key that matches an existing record the existing incoming record has a primary key that matches an existing record the existing
record will be updated. record will be updated.
The --pk option is required.
Example: Example:
echo '[ echo '[
@ -264,6 +262,7 @@ See :ref:`cli_upsert`.
Options: Options:
--pk TEXT Columns to use as the primary key, e.g. id --pk TEXT Columns to use as the primary key, e.g. id
[required]
--flatten Flatten nested JSON objects, so {"a": {"b": 1}} --flatten Flatten nested JSON objects, so {"a": {"b": 1}}
becomes {"a_b": 1} becomes {"a_b": 1}
--nl Expect newline-delimited JSON --nl Expect newline-delimited JSON

View file

@ -813,59 +813,65 @@ def import_options(fn):
return fn return fn
def insert_upsert_options(fn): def insert_upsert_options(*, require_pk=False):
for decorator in reversed( def inner(fn):
( for decorator in reversed(
click.argument( (
"path", click.argument(
type=click.Path(file_okay=True, dir_okay=False, allow_dash=False), "path",
required=True, type=click.Path(file_okay=True, dir_okay=False, allow_dash=False),
), required=True,
click.argument("table"), ),
click.argument("file", type=click.File("rb"), required=True), click.argument("table"),
click.option( click.argument("file", type=click.File("rb"), required=True),
"--pk", help="Columns to use as the primary key, e.g. id", multiple=True click.option(
), "--pk",
) help="Columns to use as the primary key, e.g. id",
+ _import_options multiple=True,
+ ( required=require_pk,
click.option( ),
"--batch-size", type=int, default=100, help="Commit every X records" )
), + _import_options
click.option( + (
"--alter", click.option(
is_flag=True, "--batch-size", type=int, default=100, help="Commit every X records"
help="Alter existing table to add any missing columns", ),
), click.option(
click.option( "--alter",
"--not-null", is_flag=True,
multiple=True, help="Alter existing table to add any missing columns",
help="Columns that should be created as NOT NULL", ),
), click.option(
click.option( "--not-null",
"--default", multiple=True,
multiple=True, help="Columns that should be created as NOT NULL",
type=(str, str), ),
help="Default value that should be set for a column", click.option(
), "--default",
click.option( multiple=True,
"-d", type=(str, str),
"--detect-types", help="Default value that should be set for a column",
is_flag=True, ),
envvar="SQLITE_UTILS_DETECT_TYPES", click.option(
help="Detect types for columns in CSV/TSV data", "-d",
), "--detect-types",
click.option( is_flag=True,
"--analyze", envvar="SQLITE_UTILS_DETECT_TYPES",
is_flag=True, help="Detect types for columns in CSV/TSV data",
help="Run ANALYZE at the end of this operation", ),
), click.option(
load_extension_option, "--analyze",
click.option("--silent", is_flag=True, help="Do not show progress bar"), is_flag=True,
) help="Run ANALYZE at the end of this operation",
): ),
fn = decorator(fn) load_extension_option,
return fn click.option("--silent", is_flag=True, help="Do not show progress bar"),
)
):
fn = decorator(fn)
return fn
return inner
def insert_upsert_implementation( def insert_upsert_implementation(
@ -1060,7 +1066,7 @@ def _find_variables(tb, vars):
@cli.command() @cli.command()
@insert_upsert_options @insert_upsert_options()
@click.option( @click.option(
"--ignore", is_flag=True, default=False, help="Ignore records if pk already exists" "--ignore", is_flag=True, default=False, help="Ignore records if pk already exists"
) )
@ -1168,7 +1174,7 @@ def insert(
@cli.command() @cli.command()
@insert_upsert_options @insert_upsert_options(require_pk=True)
def upsert( def upsert(
path, path,
table, table,
@ -1201,8 +1207,6 @@ def upsert(
an incoming record has a primary key that matches an existing record an incoming record has a primary key that matches an existing record
the existing record will be updated. the existing record will be updated.
The --pk option is required.
Example: Example:
\b \b

View file

@ -922,6 +922,23 @@ def test_upsert(db_path, tmpdir):
] ]
def test_upsert_pk_required(db_path, tmpdir):
json_path = str(tmpdir / "dogs.json")
db = Database(db_path)
insert_dogs = [
{"id": 1, "name": "Cleo", "age": 4},
{"id": 2, "name": "Nixie", "age": 4},
]
open(json_path, "w").write(json.dumps(insert_dogs))
result = CliRunner().invoke(
cli.cli,
["upsert", db_path, "dogs", json_path],
catch_exceptions=False,
)
assert result.exit_code == 2
assert "Error: Missing option '--pk'" in result.output
def test_upsert_analyze(db_path, tmpdir): def test_upsert_analyze(db_path, tmpdir):
db = Database(db_path) db = Database(db_path)
db["rows"].insert({"id": 1, "foo": "x", "n": 3}, pk="id") db["rows"].insert({"id": 1, "foo": "x", "n": 3}, pk="id")