From d453f1fc98322a98dbf598f6c83efb3d126e812e Mon Sep 17 00:00:00 2001 From: Daniele Briggi Date: Thu, 22 Aug 2024 07:46:41 +0000 Subject: [PATCH] feat(dbapi2): compatibility specs --- .gitignore | 2 +- src/README.md | 1 + src/sqlitecloud/__init__.py | 3 +- src/sqlitecloud/datatypes.py | 39 ++++++++--- src/sqlitecloud/dbapi2.py | 131 ++++++++++++++++++++++++++++++++--- 5 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 src/README.md diff --git a/.gitignore b/.gitignore index ee6b11f..d3b29de 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ build/ experiments/ sdk/ -venv*/ +.venv*/ main.dSYM/ .env *.pyc diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..48cdce8 --- /dev/null +++ b/src/README.md @@ -0,0 +1 @@ +placeholder diff --git a/src/sqlitecloud/__init__.py b/src/sqlitecloud/__init__.py index 30bc204..aecc566 100644 --- a/src/sqlitecloud/__init__.py +++ b/src/sqlitecloud/__init__.py @@ -2,6 +2,7 @@ # the classes and functions from the dbapi2 module. # eg: sqlite3.connect() -> sqlitecloud.connect() # + from .dbapi2 import ( PARSE_COLNAMES, PARSE_DECLTYPES, @@ -28,5 +29,3 @@ "converters", "Row", ] - -VERSION = "0.0.79" diff --git a/src/sqlitecloud/datatypes.py b/src/sqlitecloud/datatypes.py index 5e4035c..457fb24 100644 --- a/src/sqlitecloud/datatypes.py +++ b/src/sqlitecloud/datatypes.py @@ -228,14 +228,6 @@ def _parse_connection_string(self, connection_string) -> None: ) from e -class SQLiteCloudException(Exception): - def __init__(self, message: str, code: int = -1, xerrcode: int = 0) -> None: - super().__init__(message) - self.errmsg = str(message) - self.errcode = code - self.xerrcode = xerrcode - - class SQLiteCloudNumber: """ Represents the parsed number or the error code. @@ -256,3 +248,34 @@ def __init__(self) -> None: self.value: Optional[SQLiteCloudDataTypes] = None self.len: int = 0 self.cellsize: int = 0 + + +class SQLiteCloudWarning(Exception): + def __init__(self, message: str, code: int = -1, xerrcode: int = 0) -> None: + super().__init__(message) + self.errmsg = str(message) + self.errcode = code + self.xerrcode = xerrcode + + +class SQLiteCloudError(Exception): + def __init__(self, message: str, code: int = -1, xerrcode: int = 0) -> None: + super().__init__(message) + self.errmsg = str(message) + self.errcode = code + self.xerrcode = xerrcode + + +# class SQLiteCloudInterfaceError(SQLiteCloudError): +# def __init__(self, message: str, code: int = -1, xerrcode: int = 0) -> None: +# super().__init__(message, code, xerrcode) + + +# class SQLiteCloudDatabaseError(SQLiteCloudError): +# def __init__(self, message: str, code: int = -1, xerrcode: int = 0) -> None: +# super().__init__(message, code, xerrcode) + + +class SQLiteCloudException(SQLiteCloudError): + def __init__(self, message: str, code: int = -1, xerrcode: int = 0) -> None: + super().__init__(message, code, xerrcode) diff --git a/src/sqlitecloud/dbapi2.py b/src/sqlitecloud/dbapi2.py index b03682b..f19630f 100644 --- a/src/sqlitecloud/dbapi2.py +++ b/src/sqlitecloud/dbapi2.py @@ -3,10 +3,12 @@ # PEP 249 – Python Database API Specification v2.0 # https://peps.python.org/pep-0249/ # +import collections +import datetime import logging import re import sys -from datetime import date, datetime +import time from typing import ( Any, Callable, @@ -25,7 +27,9 @@ SQLiteCloudAccount, SQLiteCloudConfig, SQLiteCloudConnect, + SQLiteCloudError, SQLiteCloudException, + SQLiteCloudWarning, SQLiteDataTypes, ) from sqlitecloud.driver import Driver @@ -36,6 +40,36 @@ SQLiteCloudResult, ) +version = "0.1.0" +version_info = (0, 1, 0) + +# version from sqlite3 in py3.6 +sqlite_version = "3.34.1" +sqlite_version_info = (3, 34, 1) + +Binary = bytes +Date = datetime.date +Time = datetime.time +Timestamp = datetime.datetime + +Warning = SQLiteCloudWarning +Error = SQLiteCloudError +InterfaceError = SQLiteCloudException +DatabaseError = SQLiteCloudException +DataError = SQLiteCloudException +OperationalError = SQLiteCloudException +IntegrityError = SQLiteCloudException +InternalError = SQLiteCloudException +ProgrammingError = SQLiteCloudException +NotSupportedError = SQLiteCloudException + +# Map for types for SQLite +STRING = "TEXT" +BINARY = "BINARY" +NUMBER = "INTEGER" +DATETIME = "TIMESTAMP" +ROWID = "INTEGER PRIMARY KEY" + # SQLite supported types SQLiteTypes = Union[int, float, str, bytes, None] @@ -193,6 +227,7 @@ def __init__( self, sqlitecloud_connection: SQLiteCloudConnect, detect_types: int = 0 ) -> None: self._driver = Driver() + self._autocommit = True self.sqlitecloud_connection = sqlitecloud_connection self.row_factory: Optional[Callable[["Cursor", Tuple], object]] = None @@ -212,6 +247,17 @@ def sqlcloud_connection(self) -> SQLiteCloudConnect: """ return self.sqlitecloud_connection + @property + def autocommit(self) -> bool: + """Autocommit enabled is the only currently supported option in SQLite Cloud.""" + return self._autocommit + + @autocommit.setter + def autocommit(self, value: bool) -> None: + # TODO: raise NotSupportedError + if not value: + raise SQLiteCloudException("Disable Autocommit is not supported.") + def execute( self, sql: str, @@ -253,6 +299,50 @@ def executemany( cursor = self.cursor() return cursor.executemany(sql, seq_of_parameters) + def executescript(self, sql_script: str): + # TODO: raise NotSupportedError + raise SQLiteCloudException("executescript() is not supported.") + + def create_function(self, name, num_params, func): + # TODO: raise NotSupportedError + raise SQLiteCloudException("create_function() is not supported.") + + def create_aggregate(self, name, num_params, aggregate_class): + # TODO: raise NotSupportedError + raise SQLiteCloudException("create_aggregate() is not supported.") + + def create_collation(self, name, func): + # TODO: raise NotSupportedError + raise SQLiteCloudException("create_collation() is not supported.") + + def interrupt(self): + # TODO: raise NotSupportedError + raise SQLiteCloudException("interrupt() is not supported.") + + def set_authorizer(self, authorizer): + # TODO: raise NotSupportedError + raise SQLiteCloudException("set_authorizer() is not supported.") + + def set_progress_handler(self, handler, n): + # TODO: raise NotSupportedError + raise SQLiteCloudException("set_progress_handler() is not supported.") + + def set_trace_callback(self, trace_callback): + # TODO: raise NotSupportedError + raise SQLiteCloudException("set_trace_callback() is not supported.") + + def enable_load_extension(self, enable): + # TODO: raise NotSupportedError + raise SQLiteCloudException("enable_load_extension() is not supported.") + + def load_extension(path): + # TODO: raise NotSupportedError + raise SQLiteCloudException("load_extension() is not supported.") + + def iterdump(self): + # TODO: raise NotSupportedError + raise SQLiteCloudException("iterdump() is not supported.") + def close(self): """ Closes the database connection. @@ -277,7 +367,7 @@ def commit(self): and "no transaction is active" in e.errmsg ): # compliance to sqlite3 - logging.warning(e) + logging.debug(e) def rollback(self): """ @@ -297,7 +387,7 @@ def rollback(self): and "no transaction is active" in e.errmsg ): # compliance to sqlite3 - logging.warning(e) + logging.debug(e) def cursor(self): """ @@ -606,10 +696,20 @@ def fetchall(self) -> List[Any]: return self.fetchmany(self.rowcount) def setinputsizes(self, sizes) -> None: - pass + # TODO: raise NotSupportedError + raise SQLiteCloudException("setinputsizes() is not supported.") def setoutputsize(self, size, column=None) -> None: - pass + # TODO: raise NotSupportedError + raise SQLiteCloudException("setoutputsize() is not supported.") + + def scroll(value, mode="relative"): + # TODO: raise NotSupportedError + raise SQLiteCloudException("scroll() is not supported.") + + def messages(self): + # TODO: raise NotSupportedError + raise SQLiteCloudException("messages() is not supported.") def _call_row_factory(self, row: Tuple) -> object: if self.row_factory is None: @@ -842,6 +942,18 @@ def __init__(self, message: str) -> None: self.message = message +def DateFromTicks(ticks): + return Date(*time.localtime(ticks)[:3]) + + +def TimeFromTicks(ticks): + return Time(*time.localtime(ticks)[3:6]) + + +def TimestampFromTicks(ticks): + return Timestamp(*time.localtime(ticks)[:6]) + + def register_adapters_and_converters(): """ sqlite3 default adapters and converters. @@ -858,7 +970,7 @@ def adapt_datetime(val): return val.isoformat(" ") def convert_date(val): - return date(*map(int, val.split(b"-"))) + return datetime.date(*map(int, val.split(b"-"))) def convert_timestamp(val): datepart, timepart = val.split(b" ") @@ -870,13 +982,14 @@ def convert_timestamp(val): else: microseconds = 0 - val = datetime(year, month, day, hours, minutes, seconds, microseconds) + val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds) return val - register_adapter(date, adapt_date) - register_adapter(datetime, adapt_datetime) + register_adapter(datetime.date, adapt_date) + register_adapter(datetime.datetime, adapt_datetime) register_converter("date", convert_date) register_converter("timestamp", convert_timestamp) register_adapters_and_converters() +collections.abc.Sequence.register(Row)