Skip to content
This repository was archived by the owner on Jun 11, 2025. It is now read-only.

Commit ca99866

Browse files
committed
refactor: move SQLiteDiaclect_libsql to its own file
1 parent 194f616 commit ca99866

File tree

2 files changed

+149
-148
lines changed

2 files changed

+149
-148
lines changed

sqlalchemy_libsql/__init__.py

Lines changed: 1 addition & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,8 @@
1-
r"""
2-
.. dialect:: sqlite+libsql
3-
:name: libsql
4-
:dbapi: libsql_client.dbapi2
5-
:connectstring: sqlite+libsql://your-db.your-server.com?authToken=JWT_HERE&secure=true
6-
:url: https://github.com/libsql/libsql-client-py/
7-
8-
Note that this driver is based on the standard SQLAlchemy ``pysqlite``
9-
dialect, the only change is how to connect, accepting remote URL in
10-
addition to the file dialects
11-
12-
Driver
13-
------
14-
15-
The ``libsql_client.dbapi2`` offers compatibility with standard library's
16-
``sqlite3.dbapi2``. For local files or ``:memory:``, the standard library
17-
connection is used. Whenever a host is provided, then the connection
18-
will use LibSQL network protocol via ``ws`` (WebSocket) or ``wss``
19-
(secure WebSocket), the decision depends on the presence of ``secure=true``
20-
query parameter.
21-
22-
Connect Strings
23-
---------------
24-
25-
In addition to `Pysqlite
26-
<https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#connect-strings>`_,
27-
this driver accepts URL with user, password, hostname and port.
28-
29-
These will use the LibSQL network protocol on top of WebSockets. The selection
30-
between ``ws://`` and ``wss://` (secure) is defined by the query/search
31-
parameter ``secure=true``. It defaults to ``secure=false``.
32-
33-
If the given URL provides a hostname, then it will default to ``uri=true``.
34-
35-
""" # noqa: E501
36-
37-
import os
38-
import urllib.parse
39-
40-
from sqlalchemy import util
411
from sqlalchemy.dialects import registry as _registry
42-
from sqlalchemy.dialects.sqlite.pysqlite import SQLiteDialect_pysqlite
2+
from sqlalchemy_libsql.libsql import SQLiteDialect_libsql
433

444
__version__ = "0.1.0-pre"
455

466
_registry.register(
477
"sqlite.libsql", "sqlalchemy_libsql", "SQLiteDialect_libsql"
488
)
49-
50-
51-
def _build_connection_url(url, query, secure):
52-
# sorting of keys is for unit test support
53-
query_str = urllib.parse.urlencode(sorted(query.items()))
54-
55-
if not url.host:
56-
if query_str:
57-
return f"{url.database}?{query_str}"
58-
return url.database
59-
elif secure: # yes, pop to remove
60-
scheme = "wss"
61-
else:
62-
scheme = "ws"
63-
64-
if url.username and url.password:
65-
netloc = f"{url.username}:{url.password}@{url.host}"
66-
elif url.username:
67-
netloc = f"{url.username}@{url.host}"
68-
else:
69-
netloc = url.host
70-
71-
if url.port:
72-
netloc += f":{url.port}"
73-
74-
return urllib.parse.urlunsplit(
75-
(
76-
scheme,
77-
netloc,
78-
url.database or "",
79-
query_str,
80-
"", # fragment
81-
)
82-
)
83-
84-
85-
class SQLiteDialect_libsql(SQLiteDialect_pysqlite):
86-
driver = "libsql"
87-
# need to be set explicitly
88-
supports_statement_cache = SQLiteDialect_pysqlite.supports_statement_cache
89-
90-
@classmethod
91-
def import_dbapi(cls):
92-
from libsql_client import dbapi2 as libsql_client
93-
94-
return libsql_client
95-
96-
def on_connect(self):
97-
from libsql_client.dbapi2 import Connection
98-
99-
sqlite3_connect = super().on_connect()
100-
101-
def connect(conn):
102-
# LibSQL: there is no support for create_function()
103-
if isinstance(conn, Connection):
104-
return
105-
return sqlite3_connect(conn)
106-
107-
return connect
108-
109-
def create_connect_args(self, url):
110-
pysqlite_args = (
111-
("uri", bool),
112-
("timeout", float),
113-
("isolation_level", str),
114-
("detect_types", int),
115-
("check_same_thread", bool),
116-
("cached_statements", int),
117-
("secure", bool), # LibSQL extra, selects between ws and wss
118-
)
119-
opts = url.query
120-
libsql_opts = {}
121-
for key, type_ in pysqlite_args:
122-
util.coerce_kw_type(opts, key, type_, dest=libsql_opts)
123-
124-
if url.host:
125-
libsql_opts["uri"] = True
126-
127-
if libsql_opts.get("uri", False):
128-
uri_opts = dict(opts)
129-
# here, we are actually separating the parameters that go to
130-
# sqlite3/pysqlite vs. those that go the SQLite URI. What if
131-
# two names conflict? again, this seems to be not the case right
132-
# now, and in the case that new names are added to
133-
# either side which overlap, again the sqlite3/pysqlite parameters
134-
# can be passed through connect_args instead of in the URL.
135-
# If SQLite native URIs add a parameter like "timeout" that
136-
# we already have listed here for the python driver, then we need
137-
# to adjust for that here.
138-
for key, type_ in pysqlite_args:
139-
uri_opts.pop(key, None)
140-
141-
secure = libsql_opts.pop("secure", False)
142-
connect_url = _build_connection_url(url, uri_opts, secure)
143-
else:
144-
connect_url = url.database or ":memory:"
145-
if connect_url != ":memory:":
146-
connect_url = os.path.abspath(connect_url)
147-
148-
libsql_opts.setdefault(
149-
"check_same_thread", not self._is_url_file_db(url)
150-
)
151-
152-
return ([connect_url], libsql_opts)
153-
154-
155-
dialect = SQLiteDialect_libsql

sqlalchemy_libsql/libsql.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
r"""
2+
.. dialect:: sqlite+libsql
3+
:name: libsql
4+
:dbapi: libsql_client.dbapi2
5+
:connectstring: sqlite+libsql://your-db.your-server.com?authToken=JWT_HERE&secure=true
6+
:url: https://github.com/libsql/libsql-client-py/
7+
8+
Note that this driver is based on the standard SQLAlchemy ``pysqlite``
9+
dialect, the only change is how to connect, accepting remote URL in
10+
addition to the file dialects
11+
12+
Driver
13+
------
14+
15+
The ``libsql_client.dbapi2`` offers compatibility with standard library's
16+
``sqlite3.dbapi2``. For local files or ``:memory:``, the standard library
17+
connection is used. Whenever a host is provided, then the connection
18+
will use LibSQL network protocol via ``ws`` (WebSocket) or ``wss``
19+
(secure WebSocket), the decision depends on the presence of ``secure=true``
20+
query parameter.
21+
22+
Connect Strings
23+
---------------
24+
25+
In addition to `Pysqlite
26+
<https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#connect-strings>`_,
27+
this driver accepts URL with user, password, hostname and port.
28+
29+
These will use the LibSQL network protocol on top of WebSockets. The selection
30+
between ``ws://`` and ``wss://` (secure) is defined by the query/search
31+
parameter ``secure=true``. It defaults to ``secure=false``.
32+
33+
If the given URL provides a hostname, then it will default to ``uri=true``.
34+
35+
""" # noqa: E501
36+
37+
import os
38+
import urllib.parse
39+
40+
from sqlalchemy import util
41+
from sqlalchemy.dialects.sqlite.pysqlite import SQLiteDialect_pysqlite
42+
43+
44+
def _build_connection_url(url, query, secure):
45+
# sorting of keys is for unit test support
46+
query_str = urllib.parse.urlencode(sorted(query.items()))
47+
48+
if not url.host:
49+
if query_str:
50+
return f"{url.database}?{query_str}"
51+
return url.database
52+
elif secure: # yes, pop to remove
53+
scheme = "wss"
54+
else:
55+
scheme = "ws"
56+
57+
if url.username and url.password:
58+
netloc = f"{url.username}:{url.password}@{url.host}"
59+
elif url.username:
60+
netloc = f"{url.username}@{url.host}"
61+
else:
62+
netloc = url.host
63+
64+
if url.port:
65+
netloc += f":{url.port}"
66+
67+
return urllib.parse.urlunsplit(
68+
(
69+
scheme,
70+
netloc,
71+
url.database or "",
72+
query_str,
73+
"", # fragment
74+
)
75+
)
76+
77+
78+
class SQLiteDialect_libsql(SQLiteDialect_pysqlite):
79+
driver = "libsql"
80+
# need to be set explicitly
81+
supports_statement_cache = SQLiteDialect_pysqlite.supports_statement_cache
82+
83+
@classmethod
84+
def import_dbapi(cls):
85+
from libsql_client import dbapi2 as libsql_client
86+
87+
return libsql_client
88+
89+
def on_connect(self):
90+
from libsql_client.dbapi2 import Connection
91+
92+
sqlite3_connect = super().on_connect()
93+
94+
def connect(conn):
95+
# LibSQL: there is no support for create_function()
96+
if isinstance(conn, Connection):
97+
return
98+
return sqlite3_connect(conn)
99+
100+
return connect
101+
102+
def create_connect_args(self, url):
103+
pysqlite_args = (
104+
("uri", bool),
105+
("timeout", float),
106+
("isolation_level", str),
107+
("detect_types", int),
108+
("check_same_thread", bool),
109+
("cached_statements", int),
110+
("secure", bool), # LibSQL extra, selects between ws and wss
111+
)
112+
opts = url.query
113+
libsql_opts = {}
114+
for key, type_ in pysqlite_args:
115+
util.coerce_kw_type(opts, key, type_, dest=libsql_opts)
116+
117+
if url.host:
118+
libsql_opts["uri"] = True
119+
120+
if libsql_opts.get("uri", False):
121+
uri_opts = dict(opts)
122+
# here, we are actually separating the parameters that go to
123+
# sqlite3/pysqlite vs. those that go the SQLite URI. What if
124+
# two names conflict? again, this seems to be not the case right
125+
# now, and in the case that new names are added to
126+
# either side which overlap, again the sqlite3/pysqlite parameters
127+
# can be passed through connect_args instead of in the URL.
128+
# If SQLite native URIs add a parameter like "timeout" that
129+
# we already have listed here for the python driver, then we need
130+
# to adjust for that here.
131+
for key, type_ in pysqlite_args:
132+
uri_opts.pop(key, None)
133+
134+
secure = libsql_opts.pop("secure", False)
135+
connect_url = _build_connection_url(url, uri_opts, secure)
136+
else:
137+
connect_url = url.database or ":memory:"
138+
if connect_url != ":memory:":
139+
connect_url = os.path.abspath(connect_url)
140+
141+
libsql_opts.setdefault(
142+
"check_same_thread", not self._is_url_file_db(url)
143+
)
144+
145+
return ([connect_url], libsql_opts)
146+
147+
148+
dialect = SQLiteDialect_libsql

0 commit comments

Comments
 (0)