sqlite-utils 3.36__tar.gz → 3.37__tar.gz
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-3.36/sqlite_utils.egg-info → sqlite_utils-3.37}/PKG-INFO +4 -4
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/changelog.rst +9 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/cli-reference.rst +19 -18
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/cli.rst +5 -1
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/python-api.rst +3 -2
- {sqlite-utils-3.36 → sqlite_utils-3.37}/setup.py +4 -4
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/cli.py +18 -14
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/db.py +33 -26
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/utils.py +1 -4
- {sqlite-utils-3.36 → sqlite_utils-3.37/sqlite_utils.egg-info}/PKG-INFO +4 -4
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils.egg-info/requires.txt +1 -1
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_cli.py +16 -10
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_cli_insert.py +10 -5
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_insert_files.py +10 -2
- {sqlite-utils-3.36 → sqlite_utils-3.37}/LICENSE +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/MANIFEST.in +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/README.md +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/contributing.rst +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/index.rst +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/installation.rst +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/plugins.rst +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/docs/reference.rst +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/setup.cfg +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/__init__.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/__main__.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/hookspecs.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/plugins.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/py.typed +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils/recipes.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils.egg-info/SOURCES.txt +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils.egg-info/dependency_links.txt +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils.egg-info/entry_points.txt +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils.egg-info/not-zip-safe +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/sqlite_utils.egg-info/top_level.txt +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/__init__.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/conftest.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_analyze.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_analyze_tables.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_attach.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_cli_bulk.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_cli_convert.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_cli_memory.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_column_affinity.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_constructor.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_conversions.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_convert.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_create.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_create_view.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_default_value.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_delete.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_docs.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_duplicate.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_enable_counts.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_extract.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_extracts.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_fts.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_get.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_gis.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_hypothesis.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_introspect.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_lookup.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_m2m.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_plugins.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_query.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_recipes.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_recreate.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_register_function.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_rows.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_rows_from_file.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_sniff.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_suggest_column_types.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_tracer.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_transform.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_update.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_upsert.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_utils.py +0 -0
- {sqlite-utils-3.36 → sqlite_utils-3.37}/tests/test_wal.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sqlite-utils
|
|
3
|
-
Version: 3.
|
|
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
|
-
|
|
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
|
|
@@ -33,7 +33,7 @@ Requires-Dist: python-dateutil
|
|
|
33
33
|
Requires-Dist: pluggy
|
|
34
34
|
Provides-Extra: test
|
|
35
35
|
Requires-Dist: pytest; extra == "test"
|
|
36
|
-
Requires-Dist: black; extra == "test"
|
|
36
|
+
Requires-Dist: black>=24.1.1; extra == "test"
|
|
37
37
|
Requires-Dist: hypothesis; extra == "test"
|
|
38
38
|
Requires-Dist: cogapp; extra == "test"
|
|
39
39
|
Provides-Extra: docs
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
Changelog
|
|
5
5
|
===========
|
|
6
6
|
|
|
7
|
+
.. _v3_37:
|
|
8
|
+
|
|
9
|
+
3.37 (2024-07-18)
|
|
10
|
+
-----------------
|
|
11
|
+
|
|
12
|
+
- The ``create-table`` and ``insert-files`` commands all now accept multiple ``--pk`` options for compound primary keys. (:issue:`620`)
|
|
13
|
+
- Now tested against Python 3.13 pre-release. (`#619 <https://github.com/simonw/sqlite-utils/pull/619>`__)
|
|
14
|
+
- Fixed a crash that can occur in environments with a broken ``numpy`` installation, producing a ``module 'numpy' has no attribute 'int8'``. (:issue:`632`)
|
|
15
|
+
|
|
7
16
|
.. _v3_36:
|
|
8
17
|
|
|
9
18
|
3.36 (2023-12-07)
|
|
@@ -85,6 +85,7 @@ This page lists the ``--help`` for every ``sqlite-utils`` CLI sub-command.
|
|
|
85
85
|
cog.out("::\n\n")
|
|
86
86
|
result = CliRunner().invoke(cli.cli, [command, "--help"])
|
|
87
87
|
output = result.output.replace("Usage: cli ", "Usage: sqlite-utils ")
|
|
88
|
+
output = output.replace('\b', '')
|
|
88
89
|
cog.out(textwrap.indent(output, ' '))
|
|
89
90
|
cog.out("\n\n")
|
|
90
91
|
.. ]]]
|
|
@@ -603,9 +604,9 @@ See :ref:`cli_convert`.
|
|
|
603
604
|
|
|
604
605
|
Convert columns using Python code you supply. For example:
|
|
605
606
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
607
|
+
sqlite-utils convert my.db mytable mycolumn \
|
|
608
|
+
'"\n".join(textwrap.wrap(value, 10))' \
|
|
609
|
+
--import=textwrap
|
|
609
610
|
|
|
610
611
|
"value" is a variable with the column value to be converted.
|
|
611
612
|
|
|
@@ -615,30 +616,30 @@ See :ref:`cli_convert`.
|
|
|
615
616
|
|
|
616
617
|
r.jsonsplit(value, delimiter=',', type=<class 'str'>)
|
|
617
618
|
|
|
618
|
-
|
|
619
|
+
Convert a string like a,b,c into a JSON array ["a", "b", "c"]
|
|
619
620
|
|
|
620
621
|
r.parsedate(value, dayfirst=False, yearfirst=False, errors=None)
|
|
621
622
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
623
|
+
Parse a date and convert it to ISO date format: yyyy-mm-dd
|
|
624
|
+
|
|
625
|
+
- dayfirst=True: treat xx as the day in xx/yy/zz
|
|
626
|
+
- yearfirst=True: treat xx as the year in xx/yy/zz
|
|
627
|
+
- errors=r.IGNORE to ignore values that cannot be parsed
|
|
628
|
+
- errors=r.SET_NULL to set values that cannot be parsed to null
|
|
628
629
|
|
|
629
630
|
r.parsedatetime(value, dayfirst=False, yearfirst=False, errors=None)
|
|
630
631
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
632
|
+
Parse a datetime and convert it to ISO datetime format: yyyy-mm-ddTHH:MM:SS
|
|
633
|
+
|
|
634
|
+
- dayfirst=True: treat xx as the day in xx/yy/zz
|
|
635
|
+
- yearfirst=True: treat xx as the year in xx/yy/zz
|
|
636
|
+
- errors=r.IGNORE to ignore values that cannot be parsed
|
|
637
|
+
- errors=r.SET_NULL to set values that cannot be parsed to null
|
|
637
638
|
|
|
638
639
|
You can use these recipes like so:
|
|
639
640
|
|
|
640
|
-
|
|
641
|
-
|
|
641
|
+
sqlite-utils convert my.db mytable mycolumn \
|
|
642
|
+
'r.jsonsplit(value, delimiter=":")'
|
|
642
643
|
|
|
643
644
|
Options:
|
|
644
645
|
--import TEXT Python modules to import
|
|
@@ -1088,11 +1088,13 @@ You can import all three records into an automatically created ``dogs`` table an
|
|
|
1088
1088
|
|
|
1089
1089
|
sqlite-utils insert dogs.db dogs dogs.json --pk=id
|
|
1090
1090
|
|
|
1091
|
+
Pass ``--pk`` multiple times to define a compound primary key.
|
|
1092
|
+
|
|
1091
1093
|
You can skip inserting any records that have a primary key that already exists using ``--ignore``:
|
|
1092
1094
|
|
|
1093
1095
|
.. code-block:: bash
|
|
1094
1096
|
|
|
1095
|
-
sqlite-utils insert dogs.db dogs dogs.json --ignore
|
|
1097
|
+
sqlite-utils insert dogs.db dogs dogs.json --pk=id --ignore
|
|
1096
1098
|
|
|
1097
1099
|
You can delete all the existing rows in the table before inserting the new records using ``--truncate``:
|
|
1098
1100
|
|
|
@@ -1909,6 +1911,8 @@ This will create a table called ``mytable`` with two columns - an integer ``id``
|
|
|
1909
1911
|
|
|
1910
1912
|
You can pass as many column-name column-type pairs as you like. Valid types are ``integer``, ``text``, ``float`` and ``blob``.
|
|
1911
1913
|
|
|
1914
|
+
Pass ``--pk`` more than once for a compound primary key that covers multiple columns.
|
|
1915
|
+
|
|
1912
1916
|
You can specify columns that should be NOT NULL using ``--not-null colname``. You can specify default values for columns using ``--default colname defaultvalue``.
|
|
1913
1917
|
|
|
1914
1918
|
.. code-block:: bash
|
|
@@ -890,7 +890,8 @@ You can delete all records in a table that match a specific WHERE statement usin
|
|
|
890
890
|
|
|
891
891
|
>>> db = sqlite_utils.Database("dogs.db")
|
|
892
892
|
>>> # Delete every dog with age less than 3
|
|
893
|
-
>>> db
|
|
893
|
+
>>> with db.conn:
|
|
894
|
+
>>> db["dogs"].delete_where("age < ?", [3])
|
|
894
895
|
|
|
895
896
|
Calling ``table.delete_where()`` with no other arguments will delete every row in the table.
|
|
896
897
|
|
|
@@ -2177,7 +2178,7 @@ The ``.has_counts_triggers`` property shows if a table has been configured with
|
|
|
2177
2178
|
>>> db["authors"].has_counts_triggers
|
|
2178
2179
|
True
|
|
2179
2180
|
|
|
2180
|
-
.. _python_api_introspection_supports_strict
|
|
2181
|
+
.. _python_api_introspection_supports_strict:
|
|
2181
2182
|
|
|
2182
2183
|
db.supports_strict
|
|
2183
2184
|
------------------
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
import io
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
-
VERSION = "3.
|
|
5
|
+
VERSION = "3.37"
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def get_long_description():
|
|
@@ -32,7 +32,7 @@ setup(
|
|
|
32
32
|
"pluggy",
|
|
33
33
|
],
|
|
34
34
|
extras_require={
|
|
35
|
-
"test": ["pytest", "black", "hypothesis", "cogapp"],
|
|
35
|
+
"test": ["pytest", "black>=24.1.1", "hypothesis", "cogapp"],
|
|
36
36
|
"docs": [
|
|
37
37
|
"furo",
|
|
38
38
|
"sphinx-autobuild",
|
|
@@ -64,7 +64,7 @@ setup(
|
|
|
64
64
|
"Issues": "https://github.com/simonw/sqlite-utils/issues",
|
|
65
65
|
"CI": "https://github.com/simonw/sqlite-utils/actions",
|
|
66
66
|
},
|
|
67
|
-
python_requires=">=3.
|
|
67
|
+
python_requires=">=3.8",
|
|
68
68
|
classifiers=[
|
|
69
69
|
"Development Status :: 5 - Production/Stable",
|
|
70
70
|
"Intended Audience :: Developers",
|
|
@@ -72,12 +72,12 @@ setup(
|
|
|
72
72
|
"Intended Audience :: End Users/Desktop",
|
|
73
73
|
"Topic :: Database",
|
|
74
74
|
"License :: OSI Approved :: Apache Software License",
|
|
75
|
-
"Programming Language :: Python :: 3.7",
|
|
76
75
|
"Programming Language :: Python :: 3.8",
|
|
77
76
|
"Programming Language :: Python :: 3.9",
|
|
78
77
|
"Programming Language :: Python :: 3.10",
|
|
79
78
|
"Programming Language :: Python :: 3.11",
|
|
80
79
|
"Programming Language :: Python :: 3.12",
|
|
80
|
+
"Programming Language :: Python :: 3.13",
|
|
81
81
|
],
|
|
82
82
|
# Needed to bundle py.typed so mypy can see it:
|
|
83
83
|
zip_safe=False,
|
|
@@ -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
|
-
|
|
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=
|
|
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,
|
|
@@ -2594,7 +2594,7 @@ def extract(
|
|
|
2594
2594
|
multiple=True,
|
|
2595
2595
|
help="Column definitions for the table",
|
|
2596
2596
|
)
|
|
2597
|
-
@click.option("--pk",
|
|
2597
|
+
@click.option("pks", "--pk", help="Column to use as primary key", multiple=True)
|
|
2598
2598
|
@click.option("--alter", is_flag=True, help="Alter table to add missing columns")
|
|
2599
2599
|
@click.option("--replace", is_flag=True, help="Replace files with matching primary key")
|
|
2600
2600
|
@click.option("--upsert", is_flag=True, help="Upsert files with matching primary key")
|
|
@@ -2611,7 +2611,7 @@ def insert_files(
|
|
|
2611
2611
|
table,
|
|
2612
2612
|
file_or_dir,
|
|
2613
2613
|
column,
|
|
2614
|
-
|
|
2614
|
+
pks,
|
|
2615
2615
|
alter,
|
|
2616
2616
|
replace,
|
|
2617
2617
|
upsert,
|
|
@@ -2641,8 +2641,8 @@ def insert_files(
|
|
|
2641
2641
|
column = ["path:path", "content_text:content_text", "size:size"]
|
|
2642
2642
|
else:
|
|
2643
2643
|
column = ["path:path", "content:content", "size:size"]
|
|
2644
|
-
if not
|
|
2645
|
-
|
|
2644
|
+
if not pks:
|
|
2645
|
+
pks = ["path"]
|
|
2646
2646
|
|
|
2647
2647
|
def yield_paths_and_relative_paths():
|
|
2648
2648
|
for f_or_d in file_or_dir:
|
|
@@ -2712,7 +2712,11 @@ def insert_files(
|
|
|
2712
2712
|
try:
|
|
2713
2713
|
with db.conn:
|
|
2714
2714
|
db[table].insert_all(
|
|
2715
|
-
to_insert(),
|
|
2715
|
+
to_insert(),
|
|
2716
|
+
pk=pks[0] if len(pks) == 1 else pks,
|
|
2717
|
+
alter=alter,
|
|
2718
|
+
replace=replace,
|
|
2719
|
+
upsert=upsert,
|
|
2716
2720
|
)
|
|
2717
2721
|
except UnicodeDecodeErrorForPath as e:
|
|
2718
2722
|
raise click.ClickException(
|
|
@@ -2871,9 +2875,9 @@ def _generate_convert_help():
|
|
|
2871
2875
|
Convert columns using Python code you supply. For example:
|
|
2872
2876
|
|
|
2873
2877
|
\b
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2878
|
+
sqlite-utils convert my.db mytable mycolumn \\
|
|
2879
|
+
'"\\n".join(textwrap.wrap(value, 10))' \\
|
|
2880
|
+
--import=textwrap
|
|
2877
2881
|
|
|
2878
2882
|
"value" is a variable with the column value to be converted.
|
|
2879
2883
|
|
|
@@ -2892,7 +2896,7 @@ def _generate_convert_help():
|
|
|
2892
2896
|
for name in recipe_names:
|
|
2893
2897
|
fn = getattr(recipes, name)
|
|
2894
2898
|
help += "\n\nr.{}{}\n\n\b{}".format(
|
|
2895
|
-
name, str(inspect.signature(fn)), fn.__doc__.rstrip()
|
|
2899
|
+
name, str(inspect.signature(fn)), textwrap.dedent(fn.__doc__.rstrip())
|
|
2896
2900
|
)
|
|
2897
2901
|
help += "\n\n"
|
|
2898
2902
|
help += textwrap.dedent(
|
|
@@ -2900,8 +2904,8 @@ def _generate_convert_help():
|
|
|
2900
2904
|
You can use these recipes like so:
|
|
2901
2905
|
|
|
2902
2906
|
\b
|
|
2903
|
-
|
|
2904
|
-
|
|
2907
|
+
sqlite-utils convert my.db mytable mycolumn \\
|
|
2908
|
+
'r.jsonsplit(value, delimiter=":")'
|
|
2905
2909
|
"""
|
|
2906
2910
|
).strip()
|
|
2907
2911
|
return help
|
|
@@ -206,21 +206,25 @@ COLUMN_TYPE_MAPPING = {
|
|
|
206
206
|
}
|
|
207
207
|
# If numpy is available, add more types
|
|
208
208
|
if np:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
224
228
|
|
|
225
229
|
# If pandas is available, add more types
|
|
226
230
|
if pd:
|
|
@@ -457,8 +461,7 @@ class Database:
|
|
|
457
461
|
fn_name, arity, fn, **dict(kwargs, deterministic=True)
|
|
458
462
|
)
|
|
459
463
|
registered = True
|
|
460
|
-
except
|
|
461
|
-
# TypeError is Python 3.7 "function takes at most 3 arguments"
|
|
464
|
+
except sqlite3.NotSupportedError:
|
|
462
465
|
pass
|
|
463
466
|
if not registered:
|
|
464
467
|
self.conn.create_function(fn_name, arity, fn, **kwargs)
|
|
@@ -930,9 +933,9 @@ class Database:
|
|
|
930
933
|
" [{column_name}] {column_type}{column_extras}".format(
|
|
931
934
|
column_name=column_name,
|
|
932
935
|
column_type=COLUMN_TYPE_MAPPING[column_type],
|
|
933
|
-
column_extras=(
|
|
934
|
-
|
|
935
|
-
|
|
936
|
+
column_extras=(
|
|
937
|
+
(" " + " ".join(column_extras)) if column_extras else ""
|
|
938
|
+
),
|
|
936
939
|
)
|
|
937
940
|
)
|
|
938
941
|
extra_pk = ""
|
|
@@ -1481,9 +1484,11 @@ class Table(Queryable):
|
|
|
1481
1484
|
def __repr__(self) -> str:
|
|
1482
1485
|
return "<Table {}{}>".format(
|
|
1483
1486
|
self.name,
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
+
(
|
|
1488
|
+
" (does not exist yet)"
|
|
1489
|
+
if not self.exists()
|
|
1490
|
+
else " ({})".format(", ".join(c.name for c in self.columns))
|
|
1491
|
+
),
|
|
1487
1492
|
)
|
|
1488
1493
|
|
|
1489
1494
|
@property
|
|
@@ -2940,9 +2945,11 @@ class Table(Queryable):
|
|
|
2940
2945
|
value = jsonify_if_needed(
|
|
2941
2946
|
record.get(
|
|
2942
2947
|
key,
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2948
|
+
(
|
|
2949
|
+
None
|
|
2950
|
+
if key != hash_id
|
|
2951
|
+
else hash_record(record, hash_id_columns)
|
|
2952
|
+
),
|
|
2946
2953
|
)
|
|
2947
2954
|
)
|
|
2948
2955
|
if key in extracts:
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
@@ -33,7 +33,7 @@ Requires-Dist: python-dateutil
|
|
|
33
33
|
Requires-Dist: pluggy
|
|
34
34
|
Provides-Extra: test
|
|
35
35
|
Requires-Dist: pytest; extra == "test"
|
|
36
|
-
Requires-Dist: black; extra == "test"
|
|
36
|
+
Requires-Dist: black>=24.1.1; extra == "test"
|
|
37
37
|
Requires-Dist: hypothesis; extra == "test"
|
|
38
38
|
Requires-Dist: cogapp; extra == "test"
|
|
39
39
|
Provides-Extra: docs
|
|
@@ -1180,6 +1180,14 @@ def test_upsert_alter(db_path, tmpdir):
|
|
|
1180
1180
|
["age", "integer", "--default", "age", "3"],
|
|
1181
1181
|
("CREATE TABLE [t] (\n" " [age] INTEGER DEFAULT '3'\n" ")"),
|
|
1182
1182
|
),
|
|
1183
|
+
# Compound primary key
|
|
1184
|
+
(
|
|
1185
|
+
["category", "text", "name", "text", "--pk", "category", "--pk", "name"],
|
|
1186
|
+
(
|
|
1187
|
+
"CREATE TABLE [t] (\n [category] TEXT,\n [name] TEXT,\n"
|
|
1188
|
+
" PRIMARY KEY ([category], [name])\n)"
|
|
1189
|
+
),
|
|
1190
|
+
),
|
|
1183
1191
|
],
|
|
1184
1192
|
)
|
|
1185
1193
|
def test_create_table(args, schema):
|
|
@@ -2082,11 +2090,10 @@ def test_schema(tmpdir, options, expected):
|
|
|
2082
2090
|
def test_long_csv_column_value(tmpdir):
|
|
2083
2091
|
db_path = str(tmpdir / "test.db")
|
|
2084
2092
|
csv_path = str(tmpdir / "test.csv")
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
csv_file.close()
|
|
2093
|
+
with open(csv_path, "w") as csv_file:
|
|
2094
|
+
long_string = "a" * 131073
|
|
2095
|
+
csv_file.write("id,text\n")
|
|
2096
|
+
csv_file.write("1,{}\n".format(long_string))
|
|
2090
2097
|
result = CliRunner().invoke(
|
|
2091
2098
|
cli.cli,
|
|
2092
2099
|
["insert", db_path, "bigtable", csv_path, "--csv"],
|
|
@@ -2110,11 +2117,10 @@ def test_long_csv_column_value(tmpdir):
|
|
|
2110
2117
|
def test_import_no_headers(tmpdir, args, tsv):
|
|
2111
2118
|
db_path = str(tmpdir / "test.db")
|
|
2112
2119
|
csv_path = str(tmpdir / "test.csv")
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
csv_file.close()
|
|
2120
|
+
with open(csv_path, "w") as csv_file:
|
|
2121
|
+
sep = "\t" if tsv else ","
|
|
2122
|
+
csv_file.write("Cleo{sep}Dog{sep}5\n".format(sep=sep))
|
|
2123
|
+
csv_file.write("Tracy{sep}Spider{sep}7\n".format(sep=sep))
|
|
2118
2124
|
result = CliRunner().invoke(
|
|
2119
2125
|
cli.cli,
|
|
2120
2126
|
["insert", db_path, "creatures", csv_path] + args,
|
|
@@ -77,19 +77,24 @@ def test_insert_json_flatten_nl(tmpdir):
|
|
|
77
77
|
]
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
@pytest.mark.parametrize(
|
|
81
|
+
"args,expected_pks",
|
|
82
|
+
(
|
|
83
|
+
(["--pk", "id"], ["id"]),
|
|
84
|
+
(["--pk", "id", "--pk", "name"], ["id", "name"]),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
def test_insert_with_primary_keys(db_path, tmpdir, args, expected_pks):
|
|
81
88
|
json_path = str(tmpdir / "dog.json")
|
|
82
89
|
with open(json_path, "w") as fp:
|
|
83
90
|
fp.write(json.dumps({"id": 1, "name": "Cleo", "age": 4}))
|
|
84
|
-
result = CliRunner().invoke(
|
|
85
|
-
cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id"]
|
|
86
|
-
)
|
|
91
|
+
result = CliRunner().invoke(cli.cli, ["insert", db_path, "dogs", json_path] + args)
|
|
87
92
|
assert result.exit_code == 0
|
|
88
93
|
assert [{"id": 1, "age": 4, "name": "Cleo"}] == list(
|
|
89
94
|
Database(db_path).query("select * from dogs")
|
|
90
95
|
)
|
|
91
96
|
db = Database(db_path)
|
|
92
|
-
assert
|
|
97
|
+
assert db["dogs"].pks == expected_pks
|
|
93
98
|
|
|
94
99
|
|
|
95
100
|
def test_insert_multiple_with_primary_key(db_path, tmpdir):
|
|
@@ -7,7 +7,14 @@ import sys
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@pytest.mark.parametrize("silent", (False, True))
|
|
10
|
-
|
|
10
|
+
@pytest.mark.parametrize(
|
|
11
|
+
"pk_args,expected_pks",
|
|
12
|
+
(
|
|
13
|
+
(["--pk", "path"], ["path"]),
|
|
14
|
+
(["--pk", "path", "--pk", "name"], ["path", "name"]),
|
|
15
|
+
),
|
|
16
|
+
)
|
|
17
|
+
def test_insert_files(silent, pk_args, expected_pks):
|
|
11
18
|
runner = CliRunner()
|
|
12
19
|
with runner.isolated_filesystem():
|
|
13
20
|
tmpdir = pathlib.Path(".")
|
|
@@ -42,7 +49,7 @@ def test_insert_files(silent):
|
|
|
42
49
|
cli.cli,
|
|
43
50
|
["insert-files", db_path, "files", str(tmpdir)]
|
|
44
51
|
+ cols
|
|
45
|
-
+
|
|
52
|
+
+ pk_args
|
|
46
53
|
+ (["--silent"] if silent else []),
|
|
47
54
|
catch_exceptions=False,
|
|
48
55
|
)
|
|
@@ -105,6 +112,7 @@ def test_insert_files(silent):
|
|
|
105
112
|
for colname, expected_type in expected_types.items():
|
|
106
113
|
for row in (one, two, three):
|
|
107
114
|
assert isinstance(row[colname], expected_type)
|
|
115
|
+
assert set(db["files"].pks) == set(expected_pks)
|
|
108
116
|
|
|
109
117
|
|
|
110
118
|
@pytest.mark.parametrize(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|