diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index c7b05c4..7668f1b 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -20,7 +20,7 @@ jobs: cache: pip cache-dependency-path: pyproject.toml - name: Install SpatiaLite - run: sudo apt-get update && sudo apt-get install -y libsqlite3-mod-spatialite + run: sudo apt-get install libsqlite3-mod-spatialite - name: Install Python dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a1cb09b..5924fd8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,18 +10,18 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15-dev"] numpy: [0, 1] os: [ubuntu-latest, macos-latest, windows-latest, macos-14] steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v7 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true cache: pip cache-dependency-path: pyproject.toml - check-latest: true - name: Install dependencies run: | pip install . --group dev @@ -30,7 +30,7 @@ jobs: run: pip install numpy - name: Install SpatiaLite if: matrix.os == 'ubuntu-latest' - run: sudo apt-get update && sudo apt-get install -y libsqlite3-mod-spatialite + run: sudo apt-get install libsqlite3-mod-spatialite - name: Build extension for --load-extension test if: matrix.os == 'ubuntu-latest' run: |- diff --git a/Justfile b/Justfile index 7347534..e93075f 100644 --- a/Justfile +++ b/Justfile @@ -2,12 +2,9 @@ @default: test lint # Run pytest with supplied options -@test *options: test-no-dev-dependencies +@test *options: uv run pytest {{options}} -@test-no-dev-dependencies: - uv run --isolated --no-default-groups sqlite-utils --help > /dev/null - @run *options: uv run -- {{options}} diff --git a/docs/changelog.rst b/docs/changelog.rst index 5d024e1..31bd961 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,13 +4,6 @@ Changelog =========== -.. _v4_2_1: - -4.2.1 (2026-08-13) ------------------- - -- Fix for ``No module named 'typing_extensions'`` crashing bug accidentally shipped in version 4.2. (:issue:`842`) - .. _v4_2: 4.2 (2026-08-13) diff --git a/pyproject.toml b/pyproject.toml index 92650e2..6dac11c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sqlite-utils" -version = "4.2.1" +version = "4.2" description = "CLI tool and Python library for manipulating SQLite databases" readme = { file = "README.md", content-type = "text/markdown" } authors = [ diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py index 91fd24d..ee6695b 100644 --- a/sqlite_utils/utils.py +++ b/sqlite_utils/utils.py @@ -380,14 +380,10 @@ def rows_from_file( "rows_from_file() requires a file-like object that supports peek(), such as io.BytesIO" ) if not first_bytes: - buffered.close() return (), Format.CSV if first_bytes.startswith((b"[", b"{")): - # JSON is read eagerly, so the detection wrapper can close now, - # including when parsing raises an error. # TODO: Detect newline-JSON - with buffered: - return rows_from_file(buffered, format=Format.JSON) + return rows_from_file(buffered, format=Format.JSON) else: dialect = csv.Sniffer().sniff( first_bytes.decode(encoding or "utf-8-sig", "ignore") diff --git a/tests/test_rows_from_file.py b/tests/test_rows_from_file.py index a8e7f9d..3de3582 100644 --- a/tests/test_rows_from_file.py +++ b/tests/test_rows_from_file.py @@ -1,5 +1,3 @@ -import io -import json from io import BytesIO, StringIO import pytest @@ -63,71 +61,3 @@ def test_rows_from_file_error_on_string_io(): assert ex.value.args == ( "rows_from_file() requires a file-like object that supports peek(), such as io.BytesIO", ) - - -@pytest.fixture -def buffered_readers(monkeypatch): - # Keep wrappers alive so these checks cannot pass due to garbage collection. - readers = [] - original = io.BufferedReader - - def buffered_reader(*args, **kwargs): - reader = original(*args, **kwargs) - readers.append(reader) - return reader - - monkeypatch.setattr(io, "BufferedReader", buffered_reader) - yield readers - for reader in readers: - reader.close() - - -@pytest.mark.parametrize( - "content, expected_format, expected_rows", - [ - (b'[{"id": 1}]', Format.JSON, [{"id": 1}]), - (b'{"id": 1}', Format.JSON, [{"id": 1}]), - (b"[]", Format.JSON, []), - (b"", Format.CSV, []), - (b" \n\t", Format.CSV, []), - ], -) -def test_detect_format_closes_eager_reader( - tmp_path, buffered_readers, content, expected_format, expected_rows -): - path = tmp_path / "input" - path.write_bytes(content) - with path.open("rb") as fp: - rows, detected = rows_from_file(fp) - assert detected == expected_format - assert list(rows) == expected_rows - assert len(buffered_readers) == 1 - assert buffered_readers[0].closed - - -@pytest.mark.parametrize("content", [b"[", b'{"id":']) -def test_detect_format_closes_reader_on_invalid_json( - tmp_path, buffered_readers, content -): - path = tmp_path / "input.json" - path.write_bytes(content) - with path.open("rb") as fp: - with pytest.raises(json.JSONDecodeError): - rows_from_file(fp) - assert len(buffered_readers) == 1 - assert buffered_readers[0].closed - - -@pytest.mark.parametrize( - "delimiter, expected_format", [(b",", Format.CSV), (b"\t", Format.TSV)] -) -def test_detect_format_keeps_streaming_reader_open( - buffered_readers, delimiter, expected_format -): - content = b"id" + delimiter + b"name\n" + (b"1" + delimiter + b"Cleo\n") * 2000 - rows, detected = rows_from_file(BytesIO(content)) - assert detected == expected_format - assert len(buffered_readers) == 1 - assert not buffered_readers[0].closed - assert len(list(rows)) == 2000 - assert buffered_readers[0].closed