sqlite-utils 3.35.2__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.
Files changed (77) hide show
  1. {sqlite-utils-3.35.2/sqlite_utils.egg-info → sqlite_utils-3.37}/PKG-INFO +4 -4
  2. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/changelog.rst +20 -0
  3. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/cli-reference.rst +23 -19
  4. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/cli.rst +33 -3
  5. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/plugins.rst +2 -2
  6. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/python-api.rst +31 -2
  7. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/setup.py +4 -4
  8. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/cli.py +47 -15
  9. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/db.py +72 -29
  10. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/utils.py +1 -4
  11. {sqlite-utils-3.35.2 → sqlite_utils-3.37/sqlite_utils.egg-info}/PKG-INFO +4 -4
  12. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils.egg-info/requires.txt +1 -1
  13. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_cli.py +55 -10
  14. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_cli_insert.py +10 -5
  15. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_create.py +43 -0
  16. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_insert_files.py +10 -2
  17. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_lookup.py +6 -0
  18. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_transform.py +9 -0
  19. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/LICENSE +0 -0
  20. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/MANIFEST.in +0 -0
  21. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/README.md +0 -0
  22. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/contributing.rst +0 -0
  23. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/index.rst +0 -0
  24. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/installation.rst +0 -0
  25. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/docs/reference.rst +0 -0
  26. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/setup.cfg +0 -0
  27. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/__init__.py +0 -0
  28. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/__main__.py +0 -0
  29. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/hookspecs.py +0 -0
  30. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/plugins.py +0 -0
  31. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/py.typed +0 -0
  32. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils/recipes.py +0 -0
  33. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils.egg-info/SOURCES.txt +0 -0
  34. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils.egg-info/dependency_links.txt +0 -0
  35. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils.egg-info/entry_points.txt +0 -0
  36. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils.egg-info/not-zip-safe +0 -0
  37. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/sqlite_utils.egg-info/top_level.txt +0 -0
  38. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/__init__.py +0 -0
  39. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/conftest.py +0 -0
  40. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_analyze.py +0 -0
  41. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_analyze_tables.py +0 -0
  42. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_attach.py +0 -0
  43. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_cli_bulk.py +0 -0
  44. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_cli_convert.py +0 -0
  45. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_cli_memory.py +0 -0
  46. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_column_affinity.py +0 -0
  47. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_constructor.py +0 -0
  48. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_conversions.py +0 -0
  49. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_convert.py +0 -0
  50. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_create_view.py +0 -0
  51. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_default_value.py +0 -0
  52. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_delete.py +0 -0
  53. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_docs.py +0 -0
  54. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_duplicate.py +0 -0
  55. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_enable_counts.py +0 -0
  56. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_extract.py +0 -0
  57. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_extracts.py +0 -0
  58. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_fts.py +0 -0
  59. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_get.py +0 -0
  60. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_gis.py +0 -0
  61. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_hypothesis.py +0 -0
  62. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_introspect.py +0 -0
  63. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_m2m.py +0 -0
  64. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_plugins.py +0 -0
  65. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_query.py +0 -0
  66. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_recipes.py +0 -0
  67. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_recreate.py +0 -0
  68. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_register_function.py +0 -0
  69. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_rows.py +0 -0
  70. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_rows_from_file.py +0 -0
  71. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_sniff.py +0 -0
  72. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_suggest_column_types.py +0 -0
  73. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_tracer.py +0 -0
  74. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_update.py +0 -0
  75. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_upsert.py +0 -0
  76. {sqlite-utils-3.35.2 → sqlite_utils-3.37}/tests/test_utils.py +0 -0
  77. {sqlite-utils-3.35.2 → 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.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
@@ -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,26 @@
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
+
16
+ .. _v3_36:
17
+
18
+ 3.36 (2023-12-07)
19
+ -----------------
20
+
21
+ - Support for creating tables in `SQLite STRICT mode <https://www.sqlite.org/stricttables.html>`__. Thanks, `Taj Khattra <https://github.com/tkhattra>`__. (:issue:`344`)
22
+ - CLI commands ``create-table``, ``insert`` and ``upsert`` all now accept a ``--strict`` option.
23
+ - Python methods that can create a table - ``table.create()`` and ``insert/upsert/insert_all/upsert_all`` all now accept an optional ``strict=True`` parameter.
24
+ - The ``transform`` command and ``table.transform()`` method preserve strict mode when transforming a table.
25
+ - The ``sqlite-utils create-table`` command now accepts ``str``, ``int`` and ``bytes`` as aliases for ``text``, ``integer`` and ``blob`` respectively. (:issue:`606`)
26
+
7
27
  .. _v3_35_2:
8
28
 
9
29
  3.35.2 (2023-11-03)
@@ -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
  .. ]]]
@@ -289,6 +290,7 @@ See :ref:`cli_inserting_data`, :ref:`cli_insert_csv_tsv`, :ref:`cli_insert_unstr
289
290
  --analyze Run ANALYZE at the end of this operation
290
291
  --load-extension TEXT Path to SQLite extension, with optional :entrypoint
291
292
  --silent Do not show progress bar
293
+ --strict Apply STRICT mode to created table
292
294
  --ignore Ignore records if pk already exists
293
295
  --replace Replace records if pk already exists
294
296
  --truncate Truncate table before inserting records, if table
@@ -345,6 +347,7 @@ See :ref:`cli_upsert`.
345
347
  --analyze Run ANALYZE at the end of this operation
346
348
  --load-extension TEXT Path to SQLite extension, with optional :entrypoint
347
349
  --silent Do not show progress bar
350
+ --strict Apply STRICT mode to created table
348
351
  -h, --help Show this message and exit.
349
352
 
350
353
 
@@ -601,9 +604,9 @@ See :ref:`cli_convert`.
601
604
 
602
605
  Convert columns using Python code you supply. For example:
603
606
 
604
- sqlite-utils convert my.db mytable mycolumn \
605
- '"\n".join(textwrap.wrap(value, 10))' \
606
- --import=textwrap
607
+ sqlite-utils convert my.db mytable mycolumn \
608
+ '"\n".join(textwrap.wrap(value, 10))' \
609
+ --import=textwrap
607
610
 
608
611
  "value" is a variable with the column value to be converted.
609
612
 
@@ -613,30 +616,30 @@ See :ref:`cli_convert`.
613
616
 
614
617
  r.jsonsplit(value, delimiter=',', type=<class 'str'>)
615
618
 
616
- Convert a string like a,b,c into a JSON array ["a", "b", "c"]
619
+ Convert a string like a,b,c into a JSON array ["a", "b", "c"]
617
620
 
618
621
  r.parsedate(value, dayfirst=False, yearfirst=False, errors=None)
619
622
 
620
- Parse a date and convert it to ISO date format: yyyy-mm-dd
621
- 
622
- - dayfirst=True: treat xx as the day in xx/yy/zz
623
- - yearfirst=True: treat xx as the year in xx/yy/zz
624
- - errors=r.IGNORE to ignore values that cannot be parsed
625
- - errors=r.SET_NULL to set values that cannot be parsed to null
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
626
629
 
627
630
  r.parsedatetime(value, dayfirst=False, yearfirst=False, errors=None)
628
631
 
629
- Parse a datetime and convert it to ISO datetime format: yyyy-mm-ddTHH:MM:SS
630
- 
631
- - dayfirst=True: treat xx as the day in xx/yy/zz
632
- - yearfirst=True: treat xx as the year in xx/yy/zz
633
- - errors=r.IGNORE to ignore values that cannot be parsed
634
- - errors=r.SET_NULL to set values that cannot be parsed to null
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
635
638
 
636
639
  You can use these recipes like so:
637
640
 
638
- sqlite-utils convert my.db mytable mycolumn \
639
- 'r.jsonsplit(value, delimiter=":")'
641
+ sqlite-utils convert my.db mytable mycolumn \
642
+ 'r.jsonsplit(value, delimiter=":")'
640
643
 
641
644
  Options:
642
645
  --import TEXT Python modules to import
@@ -920,6 +923,7 @@ See :ref:`cli_create_table`.
920
923
  --replace If table already exists, replace it
921
924
  --transform If table already exists, try to transform the schema
922
925
  --load-extension TEXT Path to SQLite extension, with optional :entrypoint
926
+ --strict Apply STRICT mode to created table
923
927
  -h, --help Show this message and exit.
924
928
 
925
929
 
@@ -1157,7 +1161,7 @@ See :ref:`cli_add_column`.
1157
1161
  ::
1158
1162
 
1159
1163
  Usage: sqlite-utils add-column [OPTIONS] PATH TABLE COL_NAME
1160
- [[integer|float|blob|text|INTEGER|FLOAT|BLOB|TEXT]]
1164
+ [[integer|int|float|text|str|blob|bytes]]
1161
1165
 
1162
1166
  Add a column to the specified table
1163
1167
 
@@ -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
@@ -1972,6 +1976,25 @@ You can specify foreign key relationships between the tables you are creating us
1972
1976
  [author_id] INTEGER REFERENCES [authors]([id])
1973
1977
  )
1974
1978
 
1979
+ You can create a table in `SQLite STRICT mode <https://www.sqlite.org/stricttables.html>`__ using ``--strict``:
1980
+
1981
+ .. code-block:: bash
1982
+
1983
+ sqlite-utils create-table mydb.db mytable id integer name text --strict
1984
+
1985
+ .. code-block:: bash
1986
+
1987
+ sqlite-utils tables mydb.db --schema -t
1988
+
1989
+ .. code-block:: output
1990
+
1991
+ table schema
1992
+ ------- ------------------------
1993
+ mytable CREATE TABLE [mytable] (
1994
+ [id] INTEGER,
1995
+ [name] TEXT
1996
+ ) STRICT
1997
+
1975
1998
  If a table with the same name already exists, you will get an error. You can choose to silently ignore this error with ``--ignore``, or you can replace the existing table with a new, empty table using ``--replace``.
1976
1999
 
1977
2000
  You can also pass ``--transform`` to transform the existing table to match the new schema. See :ref:`python_api_explicit_create` in the Python library documentation for details of how this option works.
@@ -2018,7 +2041,7 @@ Use ``--ignore`` to ignore the error if the table does not exist.
2018
2041
  Transforming tables
2019
2042
  ===================
2020
2043
 
2021
- The ``transform`` command allows you to apply complex transformations to a table that cannot be implemented using a regular SQLite ``ALTER TABLE`` command. See :ref:`python_api_transform` for details of how this works.
2044
+ The ``transform`` command allows you to apply complex transformations to a table that cannot be implemented using a regular SQLite ``ALTER TABLE`` command. See :ref:`python_api_transform` for details of how this works. The ``transform`` command preserves a table's ``STRICT`` mode.
2022
2045
 
2023
2046
  .. code-block:: bash
2024
2047
 
@@ -2310,7 +2333,14 @@ You can add a column using the ``add-column`` command:
2310
2333
 
2311
2334
  sqlite-utils add-column mydb.db mytable nameofcolumn text
2312
2335
 
2313
- The last argument here is the type of the column to be created. You can use one of ``text``, ``integer``, ``float`` or ``blob``. If you leave it off, ``text`` will be used.
2336
+ The last argument here is the type of the column to be created. This can be one of:
2337
+
2338
+ - ``text`` or ``str``
2339
+ - ``integer`` or ``int``
2340
+ - ``float``
2341
+ - ``blob`` or ``bytes``
2342
+
2343
+ This argument is optional and defaults to ``text``.
2314
2344
 
2315
2345
  You can add a column that is a foreign key reference to another table using the ``--fk`` option:
2316
2346
 
@@ -49,7 +49,7 @@ In that folder create two files. The first is a ``pyproject.toml`` file describi
49
49
  [project.entry-points.sqlite_utils]
50
50
  hello_world = "sqlite_utils_hello_world"
51
51
 
52
- The ```[project.entry-points.sqlite_utils]`` section tells ``sqlite-tils`` which module to load when executing the plugin.
52
+ The ``[project.entry-points.sqlite_utils]`` section tells ``sqlite-utils`` which module to load when executing the plugin.
53
53
 
54
54
  Then create ``sqlite_utils_hello_world.py`` with the following content:
55
55
 
@@ -75,7 +75,7 @@ Or pass the path to your plugin directory:
75
75
 
76
76
  .. code-block:: bash
77
77
 
78
- sqlite-utils install -e `/dev/sqlite-utils-hello-world
78
+ sqlite-utils install -e /dev/sqlite-utils-hello-world
79
79
 
80
80
  Now, running this should execute your new command:
81
81
 
@@ -117,6 +117,12 @@ By default, any :ref:`sqlite-utils plugins <plugins>` that implement the :ref:`p
117
117
 
118
118
  db = Database(memory=True, execute_plugins=False)
119
119
 
120
+ You can pass ``strict=True`` to enable `SQLite STRICT mode <https://www.sqlite.org/stricttables.html>`__ for all tables created using this database object:
121
+
122
+ .. code-block:: python
123
+
124
+ db = Database("my_database.db", strict=True)
125
+
120
126
  .. _python_api_attach:
121
127
 
122
128
  Attaching additional databases
@@ -581,6 +587,15 @@ The ``transform=True`` option will update the table schema if any of the followi
581
587
 
582
588
  Changes to ``foreign_keys=`` are not currently detected and applied by ``transform=True``.
583
589
 
590
+ You can pass ``strict=True`` to create a table in ``STRICT`` mode:
591
+
592
+ .. code-block:: python
593
+
594
+ db["cats"].create({
595
+ "id": int,
596
+ "name": str,
597
+ }, strict=True)
598
+
584
599
  .. _python_api_compound_primary_keys:
585
600
 
586
601
  Compound primary keys
@@ -661,7 +676,7 @@ You can set default values for these methods by accessing the table through the
661
676
  # Now you can call .insert() like so:
662
677
  table.insert({"id": 1, "name": "Tracy", "score": 5})
663
678
 
664
- The configuration options that can be specified in this way are ``pk``, ``foreign_keys``, ``column_order``, ``not_null``, ``defaults``, ``batch_size``, ``hash_id``, ``hash_id_columns``, ``alter``, ``ignore``, ``replace``, ``extracts``, ``conversions``, ``columns``. These are all documented below.
679
+ The configuration options that can be specified in this way are ``pk``, ``foreign_keys``, ``column_order``, ``not_null``, ``defaults``, ``batch_size``, ``hash_id``, ``hash_id_columns``, ``alter``, ``ignore``, ``replace``, ``extracts``, ``conversions``, ``columns``, ``strict``. These are all documented below.
665
680
 
666
681
  .. _python_api_defaults_not_null:
667
682
 
@@ -875,7 +890,8 @@ You can delete all records in a table that match a specific WHERE statement usin
875
890
 
876
891
  >>> db = sqlite_utils.Database("dogs.db")
877
892
  >>> # Delete every dog with age less than 3
878
- >>> db["dogs"].delete_where("age < ?", [3])
893
+ >>> with db.conn:
894
+ >>> db["dogs"].delete_where("age < ?", [3])
879
895
 
880
896
  Calling ``table.delete_where()`` with no other arguments will delete every row in the table.
881
897
 
@@ -1011,6 +1027,7 @@ The first time this is called the record will be created for ``name="Palm"``. An
1011
1027
  - ``extracts``
1012
1028
  - ``conversions``
1013
1029
  - ``columns``
1030
+ - ``strict``
1014
1031
 
1015
1032
  .. _python_api_extracts:
1016
1033
 
@@ -2161,6 +2178,18 @@ The ``.has_counts_triggers`` property shows if a table has been configured with
2161
2178
  >>> db["authors"].has_counts_triggers
2162
2179
  True
2163
2180
 
2181
+ .. _python_api_introspection_supports_strict:
2182
+
2183
+ db.supports_strict
2184
+ ------------------
2185
+
2186
+ This property on the database object returns ``True`` if the available SQLite version supports `STRICT mode <https://www.sqlite.org/stricttables.html>`__, which was added in SQLite 3.37.0 (on 2021-11-27).
2187
+
2188
+ ::
2189
+
2190
+ >>> db.supports_strict
2191
+ True
2192
+
2164
2193
  .. _python_api_fts:
2165
2194
 
2166
2195
  Full-text search
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
  import io
3
3
  import os
4
4
 
5
- VERSION = "3.35.2"
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.7",
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,
@@ -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