sqlite-utils 3.36__py3-none-any.whl → 3.38__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
@@ -1489,7 +1489,7 @@ def create_database(path, enable_wal, init_spatialite, load_extension):
1489
1489
  )
1490
1490
  @click.argument("table")
1491
1491
  @click.argument("columns", nargs=-1, required=True)
1492
- @click.option("--pk", help="Column to use as primary key")
1492
+ @click.option("pks", "--pk", help="Column to use as primary key", multiple=True)
1493
1493
  @click.option(
1494
1494
  "--not-null",
1495
1495
  multiple=True,
@@ -1532,7 +1532,7 @@ def create_table(
1532
1532
  path,
1533
1533
  table,
1534
1534
  columns,
1535
- pk,
1535
+ pks,
1536
1536
  not_null,
1537
1537
  default,
1538
1538
  fk,
@@ -1581,7 +1581,7 @@ def create_table(
1581
1581
  )
1582
1582
  db[table].create(
1583
1583
  coltypes,
1584
- pk=pk,
1584
+ pk=pks[0] if len(pks) == 1 else pks,
1585
1585
  not_null=not_null,
1586
1586
  defaults=dict(default),
1587
1587
  foreign_keys=fk,
@@ -1894,6 +1894,7 @@ def memory(
1894
1894
  save,
1895
1895
  analyze,
1896
1896
  load_extension,
1897
+ return_db=False,
1897
1898
  ):
1898
1899
  """Execute SQL query against an in-memory database, optionally populated by imported data
1899
1900
 
@@ -1922,6 +1923,7 @@ def memory(
1922
1923
  sqlite-utils memory animals.csv --schema
1923
1924
  """
1924
1925
  db = sqlite_utils.Database(memory=True)
1926
+
1925
1927
  # If --dump or --save or --analyze used but no paths detected, assume SQL query is a path:
1926
1928
  if (dump or save or schema or analyze) and not paths:
1927
1929
  paths = [sql]
@@ -1954,6 +1956,7 @@ def memory(
1954
1956
  rows = tracker.wrap(rows)
1955
1957
  if flatten:
1956
1958
  rows = (_flatten(row) for row in rows)
1959
+
1957
1960
  db[file_table].insert_all(rows, alter=True)
1958
1961
  if tracker is not None:
1959
1962
  db[file_table].transform(types=tracker.types)
@@ -1964,6 +1967,7 @@ def memory(
1964
1967
  for view_name in view_names:
1965
1968
  if not db[view_name].exists():
1966
1969
  db.create_view(view_name, "select * from [{}]".format(file_table))
1970
+
1967
1971
  if fp:
1968
1972
  fp.close()
1969
1973
 
@@ -1994,6 +1998,9 @@ def memory(
1994
1998
  if functions:
1995
1999
  _register_functions(db, functions)
1996
2000
 
2001
+ if return_db:
2002
+ return db
2003
+
1997
2004
  _execute_query(
1998
2005
  db,
1999
2006
  sql,
@@ -2594,7 +2601,7 @@ def extract(
2594
2601
  multiple=True,
2595
2602
  help="Column definitions for the table",
2596
2603
  )
2597
- @click.option("--pk", type=str, help="Column to use as primary key")
2604
+ @click.option("pks", "--pk", help="Column to use as primary key", multiple=True)
2598
2605
  @click.option("--alter", is_flag=True, help="Alter table to add missing columns")
2599
2606
  @click.option("--replace", is_flag=True, help="Replace files with matching primary key")
2600
2607
  @click.option("--upsert", is_flag=True, help="Upsert files with matching primary key")
@@ -2611,7 +2618,7 @@ def insert_files(
2611
2618
  table,
2612
2619
  file_or_dir,
2613
2620
  column,
2614
- pk,
2621
+ pks,
2615
2622
  alter,
2616
2623
  replace,
2617
2624
  upsert,
@@ -2641,8 +2648,8 @@ def insert_files(
2641
2648
  column = ["path:path", "content_text:content_text", "size:size"]
2642
2649
  else:
2643
2650
  column = ["path:path", "content:content", "size:size"]
2644
- if not pk:
2645
- pk = "path"
2651
+ if not pks:
2652
+ pks = ["path"]
2646
2653
 
2647
2654
  def yield_paths_and_relative_paths():
2648
2655
  for f_or_d in file_or_dir:
@@ -2712,7 +2719,11 @@ def insert_files(
2712
2719
  try:
2713
2720
  with db.conn:
2714
2721
  db[table].insert_all(
2715
- to_insert(), pk=pk, alter=alter, replace=replace, upsert=upsert
2722
+ to_insert(),
2723
+ pk=pks[0] if len(pks) == 1 else pks,
2724
+ alter=alter,
2725
+ replace=replace,
2726
+ upsert=upsert,
2716
2727
  )
2717
2728
  except UnicodeDecodeErrorForPath as e:
2718
2729
  raise click.ClickException(
@@ -2871,9 +2882,9 @@ def _generate_convert_help():
2871
2882
  Convert columns using Python code you supply. For example:
2872
2883
 
2873
2884
  \b
2874
- sqlite-utils convert my.db mytable mycolumn \\
2875
- '"\\n".join(textwrap.wrap(value, 10))' \\
2876
- --import=textwrap
2885
+ sqlite-utils convert my.db mytable mycolumn \\
2886
+ '"\\n".join(textwrap.wrap(value, 10))' \\
2887
+ --import=textwrap
2877
2888
 
2878
2889
  "value" is a variable with the column value to be converted.
2879
2890
 
@@ -2892,7 +2903,7 @@ def _generate_convert_help():
2892
2903
  for name in recipe_names:
2893
2904
  fn = getattr(recipes, name)
2894
2905
  help += "\n\nr.{}{}\n\n\b{}".format(
2895
- name, str(inspect.signature(fn)), fn.__doc__.rstrip()
2906
+ name, str(inspect.signature(fn)), textwrap.dedent(fn.__doc__.rstrip())
2896
2907
  )
2897
2908
  help += "\n\n"
2898
2909
  help += textwrap.dedent(
@@ -2900,8 +2911,8 @@ def _generate_convert_help():
2900
2911
  You can use these recipes like so:
2901
2912
 
2902
2913
  \b
2903
- sqlite-utils convert my.db mytable mycolumn \\
2904
- 'r.jsonsplit(value, delimiter=":")'
2914
+ sqlite-utils convert my.db mytable mycolumn \\
2915
+ 'r.jsonsplit(value, delimiter=":")'
2905
2916
  """
2906
2917
  ).strip()
2907
2918
  return help
sqlite_utils/db.py CHANGED
@@ -156,6 +156,10 @@ XIndexColumn = namedtuple(
156
156
  Trigger = namedtuple("Trigger", ("name", "table", "sql"))
157
157
 
158
158
 
159
+ class TransformError(Exception):
160
+ pass
161
+
162
+
159
163
  ForeignKeyIndicator = Union[
160
164
  str,
161
165
  ForeignKey,
@@ -206,21 +210,25 @@ COLUMN_TYPE_MAPPING = {
206
210
  }
207
211
  # If numpy is available, add more types
208
212
  if np:
209
- COLUMN_TYPE_MAPPING.update(
210
- {
211
- np.int8: "INTEGER",
212
- np.int16: "INTEGER",
213
- np.int32: "INTEGER",
214
- np.int64: "INTEGER",
215
- np.uint8: "INTEGER",
216
- np.uint16: "INTEGER",
217
- np.uint32: "INTEGER",
218
- np.uint64: "INTEGER",
219
- np.float16: "FLOAT",
220
- np.float32: "FLOAT",
221
- np.float64: "FLOAT",
222
- }
223
- )
213
+ try:
214
+ COLUMN_TYPE_MAPPING.update(
215
+ {
216
+ np.int8: "INTEGER",
217
+ np.int16: "INTEGER",
218
+ np.int32: "INTEGER",
219
+ np.int64: "INTEGER",
220
+ np.uint8: "INTEGER",
221
+ np.uint16: "INTEGER",
222
+ np.uint32: "INTEGER",
223
+ np.uint64: "INTEGER",
224
+ np.float16: "FLOAT",
225
+ np.float32: "FLOAT",
226
+ np.float64: "FLOAT",
227
+ }
228
+ )
229
+ except AttributeError:
230
+ # https://github.com/simonw/sqlite-utils/issues/632
231
+ pass
224
232
 
225
233
  # If pandas is available, add more types
226
234
  if pd:
@@ -321,6 +329,8 @@ class Database:
321
329
  execute_plugins: bool = True,
322
330
  strict: bool = False,
323
331
  ):
332
+ self.memory_name = None
333
+ self.memory = False
324
334
  assert (filename_or_conn is not None and (not memory and not memory_name)) or (
325
335
  filename_or_conn is None and (memory or memory_name)
326
336
  ), "Either specify a filename_or_conn or pass memory=True"
@@ -331,8 +341,11 @@ class Database:
331
341
  uri=True,
332
342
  check_same_thread=False,
333
343
  )
344
+ self.memory = True
345
+ self.memory_name = memory_name
334
346
  elif memory or filename_or_conn == ":memory:":
335
347
  self.conn = sqlite3.connect(":memory:")
348
+ self.memory = True
336
349
  elif isinstance(filename_or_conn, (str, pathlib.Path)):
337
350
  if recreate and os.path.exists(filename_or_conn):
338
351
  try:
@@ -457,8 +470,7 @@ class Database:
457
470
  fn_name, arity, fn, **dict(kwargs, deterministic=True)
458
471
  )
459
472
  registered = True
460
- except (sqlite3.NotSupportedError, TypeError):
461
- # TypeError is Python 3.7 "function takes at most 3 arguments"
473
+ except sqlite3.NotSupportedError:
462
474
  pass
463
475
  if not registered:
464
476
  self.conn.create_function(fn_name, arity, fn, **kwargs)
@@ -926,13 +938,18 @@ class Database:
926
938
  other_column=foreign_keys_by_column[column_name].other_column,
927
939
  )
928
940
  )
941
+ column_type_str = COLUMN_TYPE_MAPPING[column_type]
942
+ # Special case for strict tables to map FLOAT to REAL
943
+ # Refs https://github.com/simonw/sqlite-utils/issues/644
944
+ if strict and column_type_str == "FLOAT":
945
+ column_type_str = "REAL"
929
946
  column_defs.append(
930
947
  " [{column_name}] {column_type}{column_extras}".format(
931
948
  column_name=column_name,
932
- column_type=COLUMN_TYPE_MAPPING[column_type],
933
- column_extras=(" " + " ".join(column_extras))
934
- if column_extras
935
- else "",
949
+ column_type=column_type_str,
950
+ column_extras=(
951
+ (" " + " ".join(column_extras)) if column_extras else ""
952
+ ),
936
953
  )
937
954
  )
938
955
  extra_pk = ""
@@ -1481,9 +1498,11 @@ class Table(Queryable):
1481
1498
  def __repr__(self) -> str:
1482
1499
  return "<Table {}{}>".format(
1483
1500
  self.name,
1484
- " (does not exist yet)"
1485
- if not self.exists()
1486
- else " ({})".format(", ".join(c.name for c in self.columns)),
1501
+ (
1502
+ " (does not exist yet)"
1503
+ if not self.exists()
1504
+ else " ({})".format(", ".join(c.name for c in self.columns))
1505
+ ),
1487
1506
  )
1488
1507
 
1489
1508
  @property
@@ -1962,6 +1981,30 @@ class Table(Queryable):
1962
1981
  sqls.append(
1963
1982
  "ALTER TABLE [{}] RENAME TO [{}];".format(new_table_name, self.name)
1964
1983
  )
1984
+ # Re-add existing indexes
1985
+ for index in self.indexes:
1986
+ if index.origin != "pk":
1987
+ index_sql = self.db.execute(
1988
+ """SELECT sql FROM sqlite_master WHERE type = 'index' AND name = :index_name;""",
1989
+ {"index_name": index.name},
1990
+ ).fetchall()[0][0]
1991
+ if index_sql is None:
1992
+ raise TransformError(
1993
+ f"Index '{index.name}' on table '{self.name}' does not have a "
1994
+ "CREATE INDEX statement. You must manually drop this index prior to running this "
1995
+ "transformation and manually recreate the new index after running this transformation."
1996
+ )
1997
+ if keep_table:
1998
+ sqls.append(f"DROP INDEX IF EXISTS [{index.name}];")
1999
+ for col in index.columns:
2000
+ if col in rename.keys() or col in drop:
2001
+ raise TransformError(
2002
+ f"Index '{index.name}' column '{col}' is not in updated table '{self.name}'. "
2003
+ f"You must manually drop this index prior to running this transformation "
2004
+ f"and manually recreate the new index after running this transformation. "
2005
+ f"The original index sql statement is: `{index_sql}`. No changes have been applied to this table."
2006
+ )
2007
+ sqls.append(index_sql)
1965
2008
  return sqls
1966
2009
 
1967
2010
  def extract(
@@ -2640,6 +2683,7 @@ class Table(Queryable):
2640
2683
  offset: Optional[int] = None,
2641
2684
  where: Optional[str] = None,
2642
2685
  where_args: Optional[Union[Iterable, dict]] = None,
2686
+ include_rank: bool = False,
2643
2687
  quote: bool = False,
2644
2688
  ) -> Generator[dict, None, None]:
2645
2689
  """
@@ -2653,6 +2697,7 @@ class Table(Queryable):
2653
2697
  :param offset: Optional integer SQL offset.
2654
2698
  :param where: Extra SQL fragment for the WHERE clause
2655
2699
  :param where_args: Arguments to use for :param placeholders in the extra WHERE clause
2700
+ :param include_rank: Select the search rank column in the final query
2656
2701
  :param quote: Apply quoting to disable any special characters in the search query
2657
2702
 
2658
2703
  See :ref:`python_api_fts_search`.
@@ -2672,6 +2717,7 @@ class Table(Queryable):
2672
2717
  limit=limit,
2673
2718
  offset=offset,
2674
2719
  where=where,
2720
+ include_rank=include_rank,
2675
2721
  ),
2676
2722
  args,
2677
2723
  )
@@ -2940,9 +2986,11 @@ class Table(Queryable):
2940
2986
  value = jsonify_if_needed(
2941
2987
  record.get(
2942
2988
  key,
2943
- None
2944
- if key != hash_id
2945
- else hash_record(record, hash_id_columns),
2989
+ (
2990
+ None
2991
+ if key != hash_id
2992
+ else hash_record(record, hash_id_columns)
2993
+ ),
2946
2994
  )
2947
2995
  )
2948
2996
  if key in extracts:
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.36
3
+ Version: 3.38
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,44 +16,44 @@ 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
29
29
  Requires-Dist: click
30
- Requires-Dist: click-default-group >=1.2.3
30
+ Requires-Dist: click-default-group>=1.2.3
31
31
  Requires-Dist: tabulate
32
32
  Requires-Dist: python-dateutil
33
33
  Requires-Dist: pluggy
34
+ Provides-Extra: test
35
+ Requires-Dist: pytest; extra == "test"
36
+ Requires-Dist: black>=24.1.1; extra == "test"
37
+ Requires-Dist: hypothesis; extra == "test"
38
+ Requires-Dist: cogapp; extra == "test"
34
39
  Provides-Extra: docs
35
- Requires-Dist: furo ; extra == 'docs'
36
- Requires-Dist: sphinx-autobuild ; extra == 'docs'
37
- Requires-Dist: codespell ; extra == 'docs'
38
- Requires-Dist: sphinx-copybutton ; extra == 'docs'
39
- Requires-Dist: beanbag-docutils >=2.0 ; extra == 'docs'
40
- Requires-Dist: pygments-csv-lexer ; extra == 'docs'
41
- Provides-Extra: flake8
42
- Requires-Dist: flake8 ; extra == 'flake8'
40
+ Requires-Dist: furo; extra == "docs"
41
+ Requires-Dist: sphinx-autobuild; extra == "docs"
42
+ Requires-Dist: codespell; extra == "docs"
43
+ Requires-Dist: sphinx-copybutton; extra == "docs"
44
+ Requires-Dist: beanbag-docutils>=2.0; extra == "docs"
45
+ Requires-Dist: pygments-csv-lexer; extra == "docs"
43
46
  Provides-Extra: mypy
44
- Requires-Dist: mypy ; extra == 'mypy'
45
- Requires-Dist: types-click ; extra == 'mypy'
46
- Requires-Dist: types-tabulate ; extra == 'mypy'
47
- Requires-Dist: types-python-dateutil ; extra == 'mypy'
48
- Requires-Dist: types-pluggy ; extra == 'mypy'
49
- Requires-Dist: data-science-types ; extra == 'mypy'
50
- Provides-Extra: test
51
- Requires-Dist: pytest ; extra == 'test'
52
- Requires-Dist: black ; extra == 'test'
53
- Requires-Dist: hypothesis ; extra == 'test'
54
- Requires-Dist: cogapp ; extra == 'test'
47
+ Requires-Dist: mypy; extra == "mypy"
48
+ Requires-Dist: types-click; extra == "mypy"
49
+ Requires-Dist: types-tabulate; extra == "mypy"
50
+ Requires-Dist: types-python-dateutil; extra == "mypy"
51
+ Requires-Dist: types-pluggy; extra == "mypy"
52
+ Requires-Dist: data-science-types; extra == "mypy"
53
+ Provides-Extra: flake8
54
+ Requires-Dist: flake8; extra == "flake8"
55
55
  Provides-Extra: tui
56
- Requires-Dist: trogon ; extra == 'tui'
56
+ Requires-Dist: trogon; extra == "tui"
57
57
 
58
58
  # sqlite-utils
59
59
 
@@ -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=r1cQ70T1KE-0va-ob-AC76Rr81SaSeEVOKp3FAbyPHM,89832
4
+ sqlite_utils/db.py,sha256=6minoeWeXkdVS7XwFUUPymePwYtOokwrVIn0qZ1HiI0,148509
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.38.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
+ sqlite_utils-3.38.dist-info/METADATA,sha256=12ik5_GloHZ2sO5GCbvFjzQVU1NG_zubIMPfF6V35k4,7540
12
+ sqlite_utils-3.38.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
13
+ sqlite_utils-3.38.dist-info/entry_points.txt,sha256=33jbVHROlRBNhoXoSI-QI2rN6JDkJIkKIX7P5sNIWdY,54
14
+ sqlite_utils-3.38.dist-info/top_level.txt,sha256=_dw_n5BWKUEtCYB2DTlmPMQfdRZSuFsmRQe2ZhNYwnQ,13
15
+ sqlite_utils-3.38.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (75.6.0)
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=C69OYAbz5Tu_PK8-Rtkuu8UhpJZpCztLkiG2orkNhUU,89593
4
- sqlite_utils/db.py,sha256=rXIeGnA3TM6QvjpvQE5p4oHV79dKfa7hch2eJ1fJTHA,146180
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.36.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
- sqlite_utils-3.36.dist-info/METADATA,sha256=kcoJW64Z6uM0yyzjyaKrdxM21ev9EEbHmrMORB5m3TA,7551
12
- sqlite_utils-3.36.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
13
- sqlite_utils-3.36.dist-info/entry_points.txt,sha256=33jbVHROlRBNhoXoSI-QI2rN6JDkJIkKIX7P5sNIWdY,54
14
- sqlite_utils-3.36.dist-info/top_level.txt,sha256=_dw_n5BWKUEtCYB2DTlmPMQfdRZSuFsmRQe2ZhNYwnQ,13
15
- sqlite_utils-3.36.dist-info/RECORD,,