sqlite-utils 3.35.2__py3-none-any.whl → 3.37__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sqlite_utils/cli.py CHANGED
@@ -60,6 +60,14 @@ It's often worth trying: --encoding=latin-1
60
60
  maximize_csv_field_size_limit()
61
61
 
62
62
 
63
+ class CaseInsensitiveChoice(click.Choice):
64
+ def __init__(self, choices):
65
+ super().__init__([choice.lower() for choice in choices])
66
+
67
+ def convert(self, value, param, ctx):
68
+ return super().convert(value.lower(), param, ctx)
69
+
70
+
63
71
  def output_options(fn):
64
72
  for decorator in reversed(
65
73
  (
@@ -412,7 +420,8 @@ def dump(path, load_extension):
412
420
  @click.argument(
413
421
  "col_type",
414
422
  type=click.Choice(
415
- ["integer", "float", "blob", "text", "INTEGER", "FLOAT", "BLOB", "TEXT"]
423
+ ["integer", "int", "float", "text", "str", "blob", "bytes"],
424
+ case_sensitive=False,
416
425
  ),
417
426
  required=False,
418
427
  )
@@ -900,6 +909,12 @@ def insert_upsert_options(*, require_pk=False):
900
909
  ),
901
910
  load_extension_option,
902
911
  click.option("--silent", is_flag=True, help="Do not show progress bar"),
912
+ click.option(
913
+ "--strict",
914
+ is_flag=True,
915
+ default=False,
916
+ help="Apply STRICT mode to created table",
917
+ ),
903
918
  )
904
919
  ):
905
920
  fn = decorator(fn)
@@ -942,6 +957,7 @@ def insert_upsert_implementation(
942
957
  silent=False,
943
958
  bulk_sql=None,
944
959
  functions=None,
960
+ strict=False,
945
961
  ):
946
962
  db = sqlite_utils.Database(path)
947
963
  _load_extensions(db, load_extension)
@@ -1057,6 +1073,7 @@ def insert_upsert_implementation(
1057
1073
  "replace": replace,
1058
1074
  "truncate": truncate,
1059
1075
  "analyze": analyze,
1076
+ "strict": strict,
1060
1077
  }
1061
1078
  if not_null:
1062
1079
  extra_kwargs["not_null"] = set(not_null)
@@ -1177,6 +1194,7 @@ def insert(
1177
1194
  truncate,
1178
1195
  not_null,
1179
1196
  default,
1197
+ strict,
1180
1198
  ):
1181
1199
  """
1182
1200
  Insert records from FILE into a table, creating the table if it
@@ -1255,6 +1273,7 @@ def insert(
1255
1273
  silent=silent,
1256
1274
  not_null=not_null,
1257
1275
  default=default,
1276
+ strict=strict,
1258
1277
  )
1259
1278
  except UnicodeDecodeError as ex:
1260
1279
  raise click.ClickException(UNICODE_ERROR.format(ex))
@@ -1290,6 +1309,7 @@ def upsert(
1290
1309
  analyze,
1291
1310
  load_extension,
1292
1311
  silent,
1312
+ strict,
1293
1313
  ):
1294
1314
  """
1295
1315
  Upsert records based on their primary key. Works like 'insert' but if
@@ -1334,6 +1354,7 @@ def upsert(
1334
1354
  analyze=analyze,
1335
1355
  load_extension=load_extension,
1336
1356
  silent=silent,
1357
+ strict=strict,
1337
1358
  )
1338
1359
  except UnicodeDecodeError as ex:
1339
1360
  raise click.ClickException(UNICODE_ERROR.format(ex))
@@ -1468,7 +1489,7 @@ def create_database(path, enable_wal, init_spatialite, load_extension):
1468
1489
  )
1469
1490
  @click.argument("table")
1470
1491
  @click.argument("columns", nargs=-1, required=True)
1471
- @click.option("--pk", help="Column to use as primary key")
1492
+ @click.option("pks", "--pk", help="Column to use as primary key", multiple=True)
1472
1493
  @click.option(
1473
1494
  "--not-null",
1474
1495
  multiple=True,
@@ -1502,11 +1523,16 @@ def create_database(path, enable_wal, init_spatialite, load_extension):
1502
1523
  help="If table already exists, try to transform the schema",
1503
1524
  )
1504
1525
  @load_extension_option
1526
+ @click.option(
1527
+ "--strict",
1528
+ is_flag=True,
1529
+ help="Apply STRICT mode to created table",
1530
+ )
1505
1531
  def create_table(
1506
1532
  path,
1507
1533
  table,
1508
1534
  columns,
1509
- pk,
1535
+ pks,
1510
1536
  not_null,
1511
1537
  default,
1512
1538
  fk,
@@ -1514,6 +1540,7 @@ def create_table(
1514
1540
  replace,
1515
1541
  transform,
1516
1542
  load_extension,
1543
+ strict,
1517
1544
  ):
1518
1545
  """
1519
1546
  Add a table with the specified columns. Columns should be specified using
@@ -1554,13 +1581,14 @@ def create_table(
1554
1581
  )
1555
1582
  db[table].create(
1556
1583
  coltypes,
1557
- pk=pk,
1584
+ pk=pks[0] if len(pks) == 1 else pks,
1558
1585
  not_null=not_null,
1559
1586
  defaults=dict(default),
1560
1587
  foreign_keys=fk,
1561
1588
  ignore=ignore,
1562
1589
  replace=replace,
1563
1590
  transform=transform,
1591
+ strict=strict,
1564
1592
  )
1565
1593
 
1566
1594
 
@@ -2566,7 +2594,7 @@ def extract(
2566
2594
  multiple=True,
2567
2595
  help="Column definitions for the table",
2568
2596
  )
2569
- @click.option("--pk", type=str, help="Column to use as primary key")
2597
+ @click.option("pks", "--pk", help="Column to use as primary key", multiple=True)
2570
2598
  @click.option("--alter", is_flag=True, help="Alter table to add missing columns")
2571
2599
  @click.option("--replace", is_flag=True, help="Replace files with matching primary key")
2572
2600
  @click.option("--upsert", is_flag=True, help="Upsert files with matching primary key")
@@ -2583,7 +2611,7 @@ def insert_files(
2583
2611
  table,
2584
2612
  file_or_dir,
2585
2613
  column,
2586
- pk,
2614
+ pks,
2587
2615
  alter,
2588
2616
  replace,
2589
2617
  upsert,
@@ -2613,8 +2641,8 @@ def insert_files(
2613
2641
  column = ["path:path", "content_text:content_text", "size:size"]
2614
2642
  else:
2615
2643
  column = ["path:path", "content:content", "size:size"]
2616
- if not pk:
2617
- pk = "path"
2644
+ if not pks:
2645
+ pks = ["path"]
2618
2646
 
2619
2647
  def yield_paths_and_relative_paths():
2620
2648
  for f_or_d in file_or_dir:
@@ -2684,7 +2712,11 @@ def insert_files(
2684
2712
  try:
2685
2713
  with db.conn:
2686
2714
  db[table].insert_all(
2687
- to_insert(), pk=pk, alter=alter, replace=replace, upsert=upsert
2715
+ to_insert(),
2716
+ pk=pks[0] if len(pks) == 1 else pks,
2717
+ alter=alter,
2718
+ replace=replace,
2719
+ upsert=upsert,
2688
2720
  )
2689
2721
  except UnicodeDecodeErrorForPath as e:
2690
2722
  raise click.ClickException(
@@ -2843,9 +2875,9 @@ def _generate_convert_help():
2843
2875
  Convert columns using Python code you supply. For example:
2844
2876
 
2845
2877
  \b
2846
- sqlite-utils convert my.db mytable mycolumn \\
2847
- '"\\n".join(textwrap.wrap(value, 10))' \\
2848
- --import=textwrap
2878
+ sqlite-utils convert my.db mytable mycolumn \\
2879
+ '"\\n".join(textwrap.wrap(value, 10))' \\
2880
+ --import=textwrap
2849
2881
 
2850
2882
  "value" is a variable with the column value to be converted.
2851
2883
 
@@ -2864,7 +2896,7 @@ def _generate_convert_help():
2864
2896
  for name in recipe_names:
2865
2897
  fn = getattr(recipes, name)
2866
2898
  help += "\n\nr.{}{}\n\n\b{}".format(
2867
- name, str(inspect.signature(fn)), fn.__doc__.rstrip()
2899
+ name, str(inspect.signature(fn)), textwrap.dedent(fn.__doc__.rstrip())
2868
2900
  )
2869
2901
  help += "\n\n"
2870
2902
  help += textwrap.dedent(
@@ -2872,8 +2904,8 @@ def _generate_convert_help():
2872
2904
  You can use these recipes like so:
2873
2905
 
2874
2906
  \b
2875
- sqlite-utils convert my.db mytable mycolumn \\
2876
- 'r.jsonsplit(value, delimiter=":")'
2907
+ sqlite-utils convert my.db mytable mycolumn \\
2908
+ 'r.jsonsplit(value, delimiter=":")'
2877
2909
  """
2878
2910
  ).strip()
2879
2911
  return help
sqlite_utils/db.py CHANGED
@@ -197,27 +197,34 @@ COLUMN_TYPE_MAPPING = {
197
197
  "FLOAT": "FLOAT",
198
198
  "BLOB": "BLOB",
199
199
  "text": "TEXT",
200
+ "str": "TEXT",
200
201
  "integer": "INTEGER",
202
+ "int": "INTEGER",
201
203
  "float": "FLOAT",
202
204
  "blob": "BLOB",
205
+ "bytes": "BLOB",
203
206
  }
204
207
  # If numpy is available, add more types
205
208
  if np:
206
- COLUMN_TYPE_MAPPING.update(
207
- {
208
- np.int8: "INTEGER",
209
- np.int16: "INTEGER",
210
- np.int32: "INTEGER",
211
- np.int64: "INTEGER",
212
- np.uint8: "INTEGER",
213
- np.uint16: "INTEGER",
214
- np.uint32: "INTEGER",
215
- np.uint64: "INTEGER",
216
- np.float16: "FLOAT",
217
- np.float32: "FLOAT",
218
- np.float64: "FLOAT",
219
- }
220
- )
209
+ try:
210
+ COLUMN_TYPE_MAPPING.update(
211
+ {
212
+ np.int8: "INTEGER",
213
+ np.int16: "INTEGER",
214
+ np.int32: "INTEGER",
215
+ np.int64: "INTEGER",
216
+ np.uint8: "INTEGER",
217
+ np.uint16: "INTEGER",
218
+ np.uint32: "INTEGER",
219
+ np.uint64: "INTEGER",
220
+ np.float16: "FLOAT",
221
+ np.float32: "FLOAT",
222
+ np.float64: "FLOAT",
223
+ }
224
+ )
225
+ except AttributeError:
226
+ # https://github.com/simonw/sqlite-utils/issues/632
227
+ pass
221
228
 
222
229
  # If pandas is available, add more types
223
230
  if pd:
@@ -300,6 +307,7 @@ class Database:
300
307
  ``sql, parameters`` every time a SQL query is executed
301
308
  :param use_counts_table: set to ``True`` to use a cached counts table, if available. See
302
309
  :ref:`python_api_cached_table_counts`
310
+ :param strict: Apply STRICT mode to all created tables (unless overridden)
303
311
  """
304
312
 
305
313
  _counts_table_name = "_counts"
@@ -315,6 +323,7 @@ class Database:
315
323
  tracer: Optional[Callable] = None,
316
324
  use_counts_table: bool = False,
317
325
  execute_plugins: bool = True,
326
+ strict: bool = False,
318
327
  ):
319
328
  assert (filename_or_conn is not None and (not memory and not memory_name)) or (
320
329
  filename_or_conn is None and (memory or memory_name)
@@ -348,6 +357,7 @@ class Database:
348
357
  self.use_counts_table = use_counts_table
349
358
  if execute_plugins:
350
359
  pm.hook.prepare_connection(conn=self.conn)
360
+ self.strict = strict
351
361
 
352
362
  def close(self):
353
363
  "Close the SQLite connection, and the underlying database file"
@@ -451,8 +461,7 @@ class Database:
451
461
  fn_name, arity, fn, **dict(kwargs, deterministic=True)
452
462
  )
453
463
  registered = True
454
- except (sqlite3.NotSupportedError, TypeError):
455
- # TypeError is Python 3.7 "function takes at most 3 arguments"
464
+ except sqlite3.NotSupportedError:
456
465
  pass
457
466
  if not registered:
458
467
  self.conn.create_function(fn_name, arity, fn, **kwargs)
@@ -534,8 +543,11 @@ class Database:
534
543
 
535
544
  :param table_name: Name of the table
536
545
  """
537
- klass = View if table_name in self.view_names() else Table
538
- return klass(self, table_name, **kwargs)
546
+ if table_name in self.view_names():
547
+ return View(self, table_name, **kwargs)
548
+ else:
549
+ kwargs.setdefault("strict", self.strict)
550
+ return Table(self, table_name, **kwargs)
539
551
 
540
552
  def quote(self, value: str) -> str:
541
553
  """
@@ -821,6 +833,7 @@ class Database:
821
833
  hash_id_columns: Optional[Iterable[str]] = None,
822
834
  extracts: Optional[Union[Dict[str, str], List[str]]] = None,
823
835
  if_not_exists: bool = False,
836
+ strict: bool = False,
824
837
  ) -> str:
825
838
  """
826
839
  Returns the SQL ``CREATE TABLE`` statement for creating the specified table.
@@ -836,6 +849,7 @@ class Database:
836
849
  :param hash_id_columns: List of columns to be used when calculating the hash ID for a row
837
850
  :param extracts: List or dictionary of columns to be extracted during inserts, see :ref:`python_api_extracts`
838
851
  :param if_not_exists: Use ``CREATE TABLE IF NOT EXISTS``
852
+ :param strict: Apply STRICT mode to table
839
853
  """
840
854
  if hash_id_columns and (hash_id is None):
841
855
  hash_id = "id"
@@ -919,9 +933,9 @@ class Database:
919
933
  " [{column_name}] {column_type}{column_extras}".format(
920
934
  column_name=column_name,
921
935
  column_type=COLUMN_TYPE_MAPPING[column_type],
922
- column_extras=(" " + " ".join(column_extras))
923
- if column_extras
924
- else "",
936
+ column_extras=(
937
+ (" " + " ".join(column_extras)) if column_extras else ""
938
+ ),
925
939
  )
926
940
  )
927
941
  extra_pk = ""
@@ -932,12 +946,13 @@ class Database:
932
946
  columns_sql = ",\n".join(column_defs)
933
947
  sql = """CREATE TABLE {if_not_exists}[{table}] (
934
948
  {columns_sql}{extra_pk}
935
- );
949
+ ){strict};
936
950
  """.format(
937
951
  if_not_exists="IF NOT EXISTS " if if_not_exists else "",
938
952
  table=name,
939
953
  columns_sql=columns_sql,
940
954
  extra_pk=extra_pk,
955
+ strict=" STRICT" if strict and self.supports_strict else "",
941
956
  )
942
957
  return sql
943
958
 
@@ -957,6 +972,7 @@ class Database:
957
972
  replace: bool = False,
958
973
  ignore: bool = False,
959
974
  transform: bool = False,
975
+ strict: bool = False,
960
976
  ) -> "Table":
961
977
  """
962
978
  Create a table with the specified name and the specified ``{column_name: type}`` columns.
@@ -977,6 +993,7 @@ class Database:
977
993
  :param replace: Drop and replace table if it already exists
978
994
  :param ignore: Silently do nothing if table already exists
979
995
  :param transform: If table already exists transform it to fit the specified schema
996
+ :param strict: Apply STRICT mode to table
980
997
  """
981
998
  # Transform table to match the new definition if table already exists:
982
999
  if self[name].exists():
@@ -1048,6 +1065,7 @@ class Database:
1048
1065
  hash_id_columns=hash_id_columns,
1049
1066
  extracts=extracts,
1050
1067
  if_not_exists=if_not_exists,
1068
+ strict=strict,
1051
1069
  )
1052
1070
  self.execute(sql)
1053
1071
  created_table = self.table(
@@ -1416,6 +1434,7 @@ class Table(Queryable):
1416
1434
  :param extracts: Dictionary or list of column names to extract into a separate table on inserts
1417
1435
  :param conversions: Dictionary of column names and conversion functions
1418
1436
  :param columns: Dictionary of column names to column types
1437
+ :param strict: If True, apply STRICT mode to table
1419
1438
  """
1420
1439
 
1421
1440
  #: The ``rowid`` of the last inserted, updated or selected row.
@@ -1441,6 +1460,7 @@ class Table(Queryable):
1441
1460
  extracts: Optional[Union[Dict[str, str], List[str]]] = None,
1442
1461
  conversions: Optional[dict] = None,
1443
1462
  columns: Optional[Dict[str, Any]] = None,
1463
+ strict: bool = False,
1444
1464
  ):
1445
1465
  super().__init__(db, name)
1446
1466
  self._defaults = dict(
@@ -1458,14 +1478,17 @@ class Table(Queryable):
1458
1478
  extracts=extracts,
1459
1479
  conversions=conversions or {},
1460
1480
  columns=columns,
1481
+ strict=strict,
1461
1482
  )
1462
1483
 
1463
1484
  def __repr__(self) -> str:
1464
1485
  return "<Table {}{}>".format(
1465
1486
  self.name,
1466
- " (does not exist yet)"
1467
- if not self.exists()
1468
- else " ({})".format(", ".join(c.name for c in self.columns)),
1487
+ (
1488
+ " (does not exist yet)"
1489
+ if not self.exists()
1490
+ else " ({})".format(", ".join(c.name for c in self.columns))
1491
+ ),
1469
1492
  )
1470
1493
 
1471
1494
  @property
@@ -1639,6 +1662,7 @@ class Table(Queryable):
1639
1662
  replace: bool = False,
1640
1663
  ignore: bool = False,
1641
1664
  transform: bool = False,
1665
+ strict: bool = False,
1642
1666
  ) -> "Table":
1643
1667
  """
1644
1668
  Create a table with the specified columns.
@@ -1658,6 +1682,7 @@ class Table(Queryable):
1658
1682
  :param replace: Drop and replace table if it already exists
1659
1683
  :param ignore: Silently do nothing if table already exists
1660
1684
  :param transform: If table already exists transform it to fit the specified schema
1685
+ :param strict: Apply STRICT mode to table
1661
1686
  """
1662
1687
  columns = {name: value for (name, value) in columns.items()}
1663
1688
  with self.db.conn:
@@ -1676,6 +1701,7 @@ class Table(Queryable):
1676
1701
  replace=replace,
1677
1702
  ignore=ignore,
1678
1703
  transform=transform,
1704
+ strict=strict,
1679
1705
  )
1680
1706
  return self
1681
1707
 
@@ -1909,6 +1935,7 @@ class Table(Queryable):
1909
1935
  defaults=create_table_defaults,
1910
1936
  foreign_keys=create_table_foreign_keys,
1911
1937
  column_order=column_order,
1938
+ strict=self.strict,
1912
1939
  ).strip()
1913
1940
  )
1914
1941
 
@@ -2918,9 +2945,11 @@ class Table(Queryable):
2918
2945
  value = jsonify_if_needed(
2919
2946
  record.get(
2920
2947
  key,
2921
- None
2922
- if key != hash_id
2923
- else hash_record(record, hash_id_columns),
2948
+ (
2949
+ None
2950
+ if key != hash_id
2951
+ else hash_record(record, hash_id_columns)
2952
+ ),
2924
2953
  )
2925
2954
  )
2926
2955
  if key in extracts:
@@ -3111,6 +3140,7 @@ class Table(Queryable):
3111
3140
  extracts: Optional[Union[Dict[str, str], List[str], Default]] = DEFAULT,
3112
3141
  conversions: Optional[Union[Dict[str, str], Default]] = DEFAULT,
3113
3142
  columns: Optional[Union[Dict[str, Any], Default]] = DEFAULT,
3143
+ strict: Optional[Union[bool, Default]] = DEFAULT,
3114
3144
  ) -> "Table":
3115
3145
  """
3116
3146
  Insert a single record into the table. The table will be created with a schema that matches
@@ -3143,6 +3173,7 @@ class Table(Queryable):
3143
3173
  is being inserted, for example ``{"name": "upper(?)"}``. See :ref:`python_api_conversions`.
3144
3174
  :param columns: Dictionary over-riding the detected types used for the columns, for example
3145
3175
  ``{"age": int, "weight": float}``.
3176
+ :param strict: Boolean, apply STRICT mode if creating the table.
3146
3177
  """
3147
3178
  return self.insert_all(
3148
3179
  [record],
@@ -3159,6 +3190,7 @@ class Table(Queryable):
3159
3190
  extracts=extracts,
3160
3191
  conversions=conversions,
3161
3192
  columns=columns,
3193
+ strict=strict,
3162
3194
  )
3163
3195
 
3164
3196
  def insert_all(
@@ -3181,6 +3213,7 @@ class Table(Queryable):
3181
3213
  columns=DEFAULT,
3182
3214
  upsert=False,
3183
3215
  analyze=False,
3216
+ strict=DEFAULT,
3184
3217
  ) -> "Table":
3185
3218
  """
3186
3219
  Like ``.insert()`` but takes a list of records and ensures that the table
@@ -3202,6 +3235,7 @@ class Table(Queryable):
3202
3235
  extracts = self.value_or_default("extracts", extracts)
3203
3236
  conversions = self.value_or_default("conversions", conversions) or {}
3204
3237
  columns = self.value_or_default("columns", columns)
3238
+ strict = self.value_or_default("strict", strict)
3205
3239
 
3206
3240
  if hash_id_columns and hash_id is None:
3207
3241
  hash_id = "id"
@@ -3257,6 +3291,7 @@ class Table(Queryable):
3257
3291
  hash_id=hash_id,
3258
3292
  hash_id_columns=hash_id_columns,
3259
3293
  extracts=extracts,
3294
+ strict=strict,
3260
3295
  )
3261
3296
  all_columns_set = set()
3262
3297
  for record in chunk:
@@ -3307,6 +3342,7 @@ class Table(Queryable):
3307
3342
  extracts=DEFAULT,
3308
3343
  conversions=DEFAULT,
3309
3344
  columns=DEFAULT,
3345
+ strict=DEFAULT,
3310
3346
  ) -> "Table":
3311
3347
  """
3312
3348
  Like ``.insert()`` but performs an ``UPSERT``, where records are inserted if they do
@@ -3327,6 +3363,7 @@ class Table(Queryable):
3327
3363
  extracts=extracts,
3328
3364
  conversions=conversions,
3329
3365
  columns=columns,
3366
+ strict=strict,
3330
3367
  )
3331
3368
 
3332
3369
  def upsert_all(
@@ -3345,6 +3382,7 @@ class Table(Queryable):
3345
3382
  conversions=DEFAULT,
3346
3383
  columns=DEFAULT,
3347
3384
  analyze=False,
3385
+ strict=DEFAULT,
3348
3386
  ) -> "Table":
3349
3387
  """
3350
3388
  Like ``.upsert()`` but can be applied to a list of records.
@@ -3365,6 +3403,7 @@ class Table(Queryable):
3365
3403
  columns=columns,
3366
3404
  upsert=True,
3367
3405
  analyze=analyze,
3406
+ strict=strict,
3368
3407
  )
3369
3408
 
3370
3409
  def add_missing_columns(self, records: Iterable[Dict[str, Any]]) -> "Table":
@@ -3387,6 +3426,7 @@ class Table(Queryable):
3387
3426
  extracts: Optional[Union[Dict[str, str], List[str]]] = None,
3388
3427
  conversions: Optional[Dict[str, str]] = None,
3389
3428
  columns: Optional[Dict[str, Any]] = None,
3429
+ strict: Optional[bool] = False,
3390
3430
  ):
3391
3431
  """
3392
3432
  Create or populate a lookup table with the specified values.
@@ -3409,6 +3449,7 @@ class Table(Queryable):
3409
3449
 
3410
3450
  :param lookup_values: Dictionary specifying column names and values to use for the lookup
3411
3451
  :param extra_values: Additional column values to be used only if creating a new record
3452
+ :param strict: Boolean, apply STRICT mode if creating the table.
3412
3453
  """
3413
3454
  assert isinstance(lookup_values, dict)
3414
3455
  if extra_values is not None:
@@ -3440,6 +3481,7 @@ class Table(Queryable):
3440
3481
  extracts=extracts,
3441
3482
  conversions=conversions,
3442
3483
  columns=columns,
3484
+ strict=strict,
3443
3485
  ).last_pk
3444
3486
  else:
3445
3487
  pk = self.insert(
@@ -3452,6 +3494,7 @@ class Table(Queryable):
3452
3494
  extracts=extracts,
3453
3495
  conversions=conversions,
3454
3496
  columns=columns,
3497
+ strict=strict,
3455
3498
  ).last_pk
3456
3499
  self.create_index(lookup_values.keys(), unique=True)
3457
3500
  return pk
sqlite_utils/utils.py CHANGED
@@ -304,10 +304,7 @@ def rows_from_file(
304
304
  rows = rows_from_file(
305
305
  fp, format=Format.CSV, dialect=csv.excel_tab, encoding=encoding
306
306
  )[0]
307
- return (
308
- _extra_key_strategy(rows, ignore_extras, extras_key),
309
- Format.TSV,
310
- )
307
+ return _extra_key_strategy(rows, ignore_extras, extras_key), Format.TSV
311
308
  elif format is None:
312
309
  # Detect the format, then call this recursively
313
310
  buffered = io.BufferedReader(cast(io.RawIOBase, fp), buffer_size=4096)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sqlite-utils
3
- Version: 3.35.2
3
+ Version: 3.37
4
4
  Summary: CLI tool and Python library for manipulating SQLite databases
5
5
  Home-page: https://github.com/simonw/sqlite-utils
6
6
  Author: Simon Willison
@@ -16,13 +16,13 @@ Classifier: Intended Audience :: Science/Research
16
16
  Classifier: Intended Audience :: End Users/Desktop
17
17
  Classifier: Topic :: Database
18
18
  Classifier: License :: OSI Approved :: Apache Software License
19
- Classifier: Programming Language :: Python :: 3.7
20
19
  Classifier: Programming Language :: Python :: 3.8
21
20
  Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
23
  Classifier: Programming Language :: Python :: 3.12
25
- Requires-Python: >=3.7
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Requires-Python: >=3.8
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: sqlite-fts4
@@ -49,7 +49,7 @@ Requires-Dist: types-pluggy ; extra == 'mypy'
49
49
  Requires-Dist: data-science-types ; extra == 'mypy'
50
50
  Provides-Extra: test
51
51
  Requires-Dist: pytest ; extra == 'test'
52
- Requires-Dist: black ; extra == 'test'
52
+ Requires-Dist: black >=24.1.1 ; extra == 'test'
53
53
  Requires-Dist: hypothesis ; extra == 'test'
54
54
  Requires-Dist: cogapp ; extra == 'test'
55
55
  Provides-Extra: tui
@@ -0,0 +1,15 @@
1
+ sqlite_utils/__init__.py,sha256=eyDK-5KOo-ARS3i0JcuxGiJmk5T-58Z1p_bhACbBrGc,201
2
+ sqlite_utils/__main__.py,sha256=8hDtWlaFZK24KhfNq_ZKgtXqYHsDQDetukOCMlsbW0Q,59
3
+ sqlite_utils/cli.py,sha256=sBAF9cxvJ108OBWspzQoz1dvN_fLBhlJSaAbmWbYlHc,89771
4
+ sqlite_utils/db.py,sha256=na--6HxI5db7_LaZwwY2nAusVJaZ2AlC-DYTNHwY1A8,146365
5
+ sqlite_utils/hookspecs.py,sha256=NFA0BFZ8WUhnI_NRz4oFVDcomQYlguuAgUCBKAN792o,395
6
+ sqlite_utils/plugins.py,sha256=V5nXD4r0o1wSbIHHOAiUBBV66ZOfKVnywKfZzCUqdsA,771
7
+ sqlite_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ sqlite_utils/recipes.py,sha256=_npzti0nPV6XxvMj69Ca4QF-pXwZZQp_f_VY3i7pQDs,1687
9
+ sqlite_utils/utils.py,sha256=PRqoITtqmaUI7xzyB2exaTPg6oXbTMhGDzld7srNOG4,17099
10
+ sqlite_utils-3.37.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
+ sqlite_utils-3.37.dist-info/METADATA,sha256=DdfINwbgTnsxBvYUgSaz0pePxd9PqJCcyfn107VNkJA,7561
12
+ sqlite_utils-3.37.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
13
+ sqlite_utils-3.37.dist-info/entry_points.txt,sha256=33jbVHROlRBNhoXoSI-QI2rN6JDkJIkKIX7P5sNIWdY,54
14
+ sqlite_utils-3.37.dist-info/top_level.txt,sha256=_dw_n5BWKUEtCYB2DTlmPMQfdRZSuFsmRQe2ZhNYwnQ,13
15
+ sqlite_utils-3.37.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.3)
2
+ Generator: setuptools (71.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,15 +0,0 @@
1
- sqlite_utils/__init__.py,sha256=eyDK-5KOo-ARS3i0JcuxGiJmk5T-58Z1p_bhACbBrGc,201
2
- sqlite_utils/__main__.py,sha256=8hDtWlaFZK24KhfNq_ZKgtXqYHsDQDetukOCMlsbW0Q,59
3
- sqlite_utils/cli.py,sha256=kHYDOWFbAbas5rkdr67BanbqHBVIlsymKkd0BRSsags,88859
4
- sqlite_utils/db.py,sha256=a89cAwKNxrx0eAmhurorKDRWGyQ_u4II9-HwRSG0cuE,144794
5
- sqlite_utils/hookspecs.py,sha256=NFA0BFZ8WUhnI_NRz4oFVDcomQYlguuAgUCBKAN792o,395
6
- sqlite_utils/plugins.py,sha256=V5nXD4r0o1wSbIHHOAiUBBV66ZOfKVnywKfZzCUqdsA,771
7
- sqlite_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- sqlite_utils/recipes.py,sha256=_npzti0nPV6XxvMj69Ca4QF-pXwZZQp_f_VY3i7pQDs,1687
9
- sqlite_utils/utils.py,sha256=jvlsGQ5F_Z7-bAiP7gw4hmov_58PrVMvY_5UZ3nXfAg,17136
10
- sqlite_utils-3.35.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
- sqlite_utils-3.35.2.dist-info/METADATA,sha256=sWiT3qYyM8iNmyXtU993w5y-Y7LNu5l3sk1k4ZKHSos,7553
12
- sqlite_utils-3.35.2.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
13
- sqlite_utils-3.35.2.dist-info/entry_points.txt,sha256=33jbVHROlRBNhoXoSI-QI2rN6JDkJIkKIX7P5sNIWdY,54
14
- sqlite_utils-3.35.2.dist-info/top_level.txt,sha256=_dw_n5BWKUEtCYB2DTlmPMQfdRZSuFsmRQe2ZhNYwnQ,13
15
- sqlite_utils-3.35.2.dist-info/RECORD,,