Skip to content

Commit ba53bde

Browse files
committed
Initial support of SQLite Cloud to API-DB 2.0/SQLite3.
- Compatibility with Python DB-API 2.0 - Aim for full compatibility with SQLite3 API
1 parent 063fad1 commit ba53bde

File tree

15 files changed

+1494
-118
lines changed

15 files changed

+1494
-118
lines changed

.devcontainer/devcontainer.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,22 @@
2020
// "postCreateCommand": "pip3 install --user -r requirements.txt",
2121

2222
// Configure tool-specific properties.
23+
// Py3.6 support (switch extensions to `pre-release` and `install another version`):
24+
// Pylance v2022.6.30
25+
// Python v2022.8.1
26+
// Python Debugger v2023.1.XXX (pre-release version | debugpy v1.5.1)
27+
// Black Formatter v2022.2.0
28+
// Isort v2022.1.11601002 (pre-release)
2329
"customizations": {
2430
"vscode": {
2531
"extensions": [
2632
"littlefoxteam.vscode-python-test-adapter",
27-
"jkillian.custom-local-formatters"
33+
"jkillian.custom-local-formatters",
34+
"ms-python.vscode-pylance",
35+
"ms-python.python",
36+
"ms-python.debugpy",
37+
"ms-python.black-formatter",
38+
"ms-python.isort"
2839
]
2940
}
3041
}

.pre-commit-config.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ repos:
1010
- id: detect-private-key
1111
- id: check-merge-conflict
1212
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
13+
- repo: https://github.com/pycqa/isort
14+
rev: 5.10.1
15+
hooks:
16+
- id: isort
17+
name: isort
1318
- repo: https://github.com/psf/black-pre-commit-mirror
1419
rev: 22.8.0
1520
hooks:
@@ -19,11 +24,6 @@ repos:
1924
# pre-commit's default_language_version, see
2025
# https://pre-commit.com/#top_level-default_language_version
2126
language_version: python3.6
22-
- repo: https://github.com/pycqa/isort
23-
rev: 5.10.1
24-
hooks:
25-
- id: isort
26-
name: isort
2727
- repo: https://github.com/PyCQA/autoflake
2828
rev: v1.4
2929
hooks:
@@ -34,7 +34,7 @@ repos:
3434
- "--expand-star-imports"
3535
- "--remove-duplicate-keys"
3636
- "--remove-unused-variables"
37-
- "--remove-unused-variables"
37+
- "--remove-all-unused-imports"
3838
- repo: https://github.com/pycqa/flake8
3939
rev: 5.0.4
4040
hooks:

src/sqlitecloud/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1+
# To replicate the public interface of sqlite3, we need to import
2+
# the classes and functions from the dbapi2 module.
3+
# eg: sqlite3.connect() -> sqlitecloud.connect()
4+
#
5+
from .dbapi2 import Connection, Cursor, connect
6+
7+
__all__ = ["VERSION", "Connection", "Cursor", "connect"]
8+
19
VERSION = "0.0.77"

src/sqlitecloud/client.py

Lines changed: 41 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
""" Module to interact with remote SqliteCloud database
22
33
"""
4-
from typing import Optional
5-
from urllib import parse
4+
from typing import Dict, Optional, Tuple, Union
65

76
from sqlitecloud.driver import Driver
87
from sqlitecloud.resultset import SqliteCloudResultSet
98
from sqlitecloud.types import (
10-
SQCLOUD_DEFAULT,
119
SQCloudConfig,
1210
SQCloudConnect,
1311
SQCloudException,
1412
SqliteCloudAccount,
13+
SQLiteCloudDataTypes,
1514
)
1615

1716

@@ -39,10 +38,11 @@ def __init__(
3938
self.config = SQCloudConfig()
4039

4140
if connection_str:
42-
self.config = self._parse_connection_string(connection_str)
41+
self.config = SQCloudConfig(connection_str)
4342
elif cloud_account:
4443
self.config.account = cloud_account
45-
else:
44+
45+
if self.config.account is None:
4646
raise SQCloudException("Missing connection parameters")
4747

4848
def open_connection(self) -> SQCloudConnect:
@@ -80,7 +80,7 @@ def exec_query(self, query: str, conn: SQCloudConnect) -> SqliteCloudResultSet:
8080
"""Executes a SQL query on the SQLite Cloud database.
8181
8282
Args:
83-
query (str): The SQL query to be executed.
83+
query (str): The SQL query to execute.
8484
8585
Returns:
8686
SqliteCloudResultSet: The result set of the executed query.
@@ -92,6 +92,41 @@ def exec_query(self, query: str, conn: SQCloudConnect) -> SqliteCloudResultSet:
9292

9393
return SqliteCloudResultSet(result)
9494

95+
def exec_statement(
96+
self,
97+
query: str,
98+
parameters: Union[
99+
Tuple[SQLiteCloudDataTypes], Dict[Union[str, int], SQLiteCloudDataTypes]
100+
],
101+
conn: SQCloudConnect,
102+
) -> SqliteCloudResultSet:
103+
"""
104+
Prepare and execute a SQL statement (either a query or command) to the SQLite Cloud database.
105+
This function supports two styles of parameter markers:
106+
107+
1. Question Mark Style: Parameters are passed as a tuple. For example:
108+
"SELECT * FROM table WHERE id = ?"
109+
110+
2. Named Style: Parameters are passed as a dictionary. For example:
111+
"SELECT * FROM table WHERE id = :id"
112+
113+
In both cases, the parameters replace the placeholders in the SQL statement.
114+
115+
Args:
116+
query (str): The SQL query to execute.
117+
parameters (Union[Tuple[SQLiteCloudDataTypes], Dict[Union[str, int], SQLiteCloudDataTypes]]):
118+
The parameters to be used in the query. It can be a tuple or a dictionary.
119+
conn (SQCloudConnect): The connection object to use for executing the query.
120+
121+
Returns:
122+
SqliteCloudResultSet: The result set obtained from executing the query.
123+
"""
124+
prepared_statement = self._driver.prepare_statement(query, parameters)
125+
126+
result = self._driver.execute(prepared_statement, conn)
127+
128+
return SqliteCloudResultSet(result)
129+
95130
def sendblob(self, blob: bytes, conn: SQCloudConnect) -> SqliteCloudResultSet:
96131
"""Sends a blob to the SQLite database.
97132
@@ -100,58 +135,3 @@ def sendblob(self, blob: bytes, conn: SQCloudConnect) -> SqliteCloudResultSet:
100135
conn (SQCloudConnect): The connection to the database.
101136
"""
102137
return self._driver.send_blob(blob, conn)
103-
104-
def _parse_connection_string(self, connection_string) -> SQCloudConfig:
105-
# URL STRING FORMAT
106-
# sqlitecloud://user:[email protected]:port/dbname?timeout=10&key2=value2&key3=value3
107-
# or sqlitecloud://host.sqlite.cloud:8860/dbname?apikey=zIiAARzKm9XBVllbAzkB1wqrgijJ3Gx0X5z1A4m4xBA
108-
109-
config = SQCloudConfig()
110-
config.account = SqliteCloudAccount()
111-
112-
try:
113-
params = parse.urlparse(connection_string)
114-
115-
options = {}
116-
query = params.query
117-
options = parse.parse_qs(query)
118-
for option, values in options.items():
119-
opt = option.lower()
120-
value = values.pop()
121-
122-
if value.lower() in ["true", "false"]:
123-
value = bool(value)
124-
elif value.isdigit():
125-
value = int(value)
126-
else:
127-
value = value
128-
129-
if hasattr(config, opt):
130-
setattr(config, opt, value)
131-
elif hasattr(config.account, opt):
132-
setattr(config.account, opt, value)
133-
134-
# apikey or username/password is accepted
135-
if not config.account.apikey:
136-
config.account.username = (
137-
parse.unquote(params.username) if params.username else ""
138-
)
139-
config.account.password = (
140-
parse.unquote(params.password) if params.password else ""
141-
)
142-
143-
path = params.path
144-
database = path.strip("/")
145-
if database:
146-
config.account.dbname = database
147-
148-
config.account.hostname = params.hostname
149-
config.account.port = (
150-
int(params.port) if params.port else SQCLOUD_DEFAULT.PORT.value
151-
)
152-
153-
return config
154-
except Exception as e:
155-
raise SQCloudException(
156-
f"Invalid connection string {connection_string}"
157-
) from e

0 commit comments

Comments
 (0)