Skip to content

Commit eb91431

Browse files
authored
Added typing stub file (#48)
This adds a stub file, adding type hints in Python. This added the `connect` function as well as the `Connection` and `Cursor` classes. Everything is in the `libsql_experimental.pyi` file. According to [this](https://pyo3.rs/v0.21.0-beta.0/python-typing-hints#if-you-do-not-have-other-python-files) PyO3 documentation, as long as there is no other Python files (excluding examples) Maturin will automatically install this file correctly, adding `__init__.pyi` and `py.typed` to site-packages. Below is a before and after example for the `connect` function. If there is anything I missed or misunderstood, let me know and ill fix it :) ![before](https://github.com/tursodatabase/libsql-experimental-python/assets/42066957/e890f7c7-1f06-4980-8d89-1e3ee604190f) ![image](https://github.com/tursodatabase/libsql-experimental-python/assets/42066957/b4c6c23e-df29-4c0b-96de-8d2cc72caed6)
2 parents 8e342c0 + 7fa1884 commit eb91431

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed

.github/workflows/pr-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ jobs:
3636
run: |
3737
python3 -m venv .env # maturin requires a virtualenv
3838
source .env/bin/activate
39-
pip3 install maturin pytest
39+
pip3 install maturin pytest mypy
4040
maturin develop
4141
pytest

libsql_experimental.pyi

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""libSQL's experimental Python implementation"""
2+
3+
from typing import Any, Self, final
4+
5+
6+
paramstyle = "qmark"
7+
sqlite_version_info = (3, 42, 0)
8+
Error = Exception
9+
LEGACY_TRANSACTION_CONTROL: int = -1
10+
11+
12+
@final
13+
class Cursor:
14+
"""libSQL database cursor.
15+
16+
Implements a superset of the [DB-API 2.0 (PEP249)](https://peps.python.org/pep-0249/) Cursor object protocol.""" # noqa: E501
17+
arraysize: int
18+
19+
@property
20+
def description(self) -> tuple[tuple[Any, ...], ...] | None: ...
21+
22+
@property
23+
def rowcount(self) -> int: ...
24+
25+
@property
26+
def lastrowid(self) -> int | None: ...
27+
28+
def close(self) -> None: ...
29+
def execute(self, sql: str, parameters: tuple[Any, ...] = ...) -> Self: ...
30+
def executemany(self, sql: str, parameters: list[tuple[Any, ...]] = ...) -> Self: ... # noqa: E501
31+
def executescript(self, script: str) -> Self: ...
32+
def fetchone(self) -> tuple[Any, ...] | None: ...
33+
def fetchmany(self, size: int = ...) -> list[tuple[Any, ...]]: ... # noqa: E501
34+
def fetchall(self) -> list[tuple[Any, ...]]: ...
35+
36+
37+
@final
38+
class Connection:
39+
"""libSQL database connection.
40+
41+
Implements a superset of the [DB-API 2.0 (PEP249)](https://peps.python.org/pep-0249/) Connection object protocol.""" # noqa: E501
42+
@property
43+
def isolation_level(self) -> str | None: ...
44+
45+
@property
46+
def in_transaction(self) -> bool: ...
47+
48+
def commit(self) -> None: ...
49+
def cursor(self) -> Cursor: ...
50+
def sync(self) -> None: ...
51+
def rollback(self) -> None: ...
52+
def execute(self, sql: str, parameters: tuple[Any, ...] = ...) -> Cursor: ... # noqa: E501
53+
def executemany(self, sql: str, parameters: list[tuple[Any, ...]] = ...) -> Cursor: ... # noqa: E501
54+
def executescript(self, script: str) -> None: ...
55+
56+
57+
def connect(database: str,
58+
isolation_level: str | None = ...,
59+
check_same_thread: bool = True,
60+
uri: bool = False,
61+
sync_url: str = ...,
62+
sync_interval: float = ...,
63+
auth_token: str = ...,
64+
encryption_key: str = ...) -> Connection:
65+
"""Open a new libSQL connection, return a Connection object."""

tests/test_suite.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,33 @@ def test_int64(provider):
298298
res = cur.execute("SELECT * FROM data")
299299
assert [(1, 1099511627776)] == res.fetchall()
300300

301+
def test_type_adherence(capsys):
302+
try:
303+
from mypy.stubtest import test_stubs, parse_options
304+
except (ImportError, ModuleNotFoundError):
305+
# skip test if mypy not installed
306+
pytest.skip("Cant test type stubs without mypy installed")
307+
308+
# run mypy stubtest tool. Equivalent to running the following the terminal
309+
"""
310+
stubtest --concise libsql_experimental | \
311+
grep -v 'which is incompatible with stub argument type'
312+
"""
313+
test_stubs(parse_options(["--concise", "libsql_experimental"]))
314+
cap = capsys.readouterr()
315+
316+
# this is part of error reported if is default parameter is ellipsis
317+
# `arg: type = ...` which is a nicer way to hide implementation from user
318+
# than having the more "correct" `arg: type | None = None` everywhere
319+
ellipsis_err = "which is incompatible with stub argument type"
320+
321+
lines = cap.out.split("\n")
322+
lines = filter(lambda x: ellipsis_err not in x, lines) # filter false positives from ellipsis
323+
lines = filter(lambda x: len(x) != 0, lines) # filter empty lines
324+
325+
# there will always be one error which i dont know how to get rid of
326+
# `libsql_experimental.libsql_experimental failed to find stubs`
327+
assert len(list(lines)) == 1
301328

302329
def connect(provider, database, isolation_level="DEFERRED", autocommit=-1):
303330
if provider == "libsql-remote":

0 commit comments

Comments
 (0)