Skip to content

Commit

Permalink
feat: slightly more-specific exceptions raised
Browse files Browse the repository at this point in the history
To help exception handling just that little bit more
  • Loading branch information
michalc committed Mar 15, 2024
1 parent d6301d1 commit 4939132
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 15 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,25 @@ This means that sqlite-s3-query is not for all use cases of querying SQLite data
This is not necessarily a permanent decision - it is possible that in future sqlite-s3-query will support unversioned buckets.


## Exception hierarchy

- `SQLiteS3QueryError`

The base class for explicitly raised exceptions.

- `VersioningNotEnabledError`

Versioning is not enabled on the bucket.

- `QueryContextClosedError`

A results iterable has been attempted to be used after the close of its surrounding query context.

- `SQLiteError`

SQLite has detected an error. The first element of the `args` member of the raised exception is the description of the error as provided by SQLite.


## Compatibility

- Linux (tested on Ubuntu 20.04), Windows (tested on Windows Server 2019), or macOS (tested on macOS 11)
Expand Down
26 changes: 21 additions & 5 deletions sqlite_s3_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ def sqlite_s3_query_multi(url, get_credentials=lambda now: (
def run(func, *args):
res = func(*args)
if res != 0:
raise Exception(libsqlite3.sqlite3_errstr(res).decode())
raise SQLiteError(libsqlite3.sqlite3_errstr(res).decode())

def run_with_db(db, func, *args):
if func(*args) != 0:
raise Exception(libsqlite3.sqlite3_errmsg(db).decode())
raise SQLiteError(libsqlite3.sqlite3_errmsg(db).decode())

@contextmanager
def make_auth_request(http_client, method, params, headers):
Expand Down Expand Up @@ -148,7 +148,7 @@ def get_vfs(http_client):
try:
version_id = head_headers['x-amz-version-id']
except KeyError:
raise Exception('The bucket must have versioning enabled')
raise VersioningNotEnabledError('The bucket must have versioning enabled')

size = int(head_headers['content-length'])

Expand Down Expand Up @@ -292,7 +292,7 @@ def get_pp_stmt(statement):
try:
return statements[statement]
except KeyError:
raise Exception('Attempting to use finalized statement') from None
raise ContextClosedError('Attempting to use finalized statement') from None

def finalize(statement):
pp_stmt = statements.pop(statement)
Expand Down Expand Up @@ -328,7 +328,7 @@ def rows(get_pp_stmt, columns):
if res == SQLITE_DONE:
break
if res != SQLITE_ROW:
raise Exception(libsqlite3.sqlite3_errstr(res).decode())
raise SQLiteError(libsqlite3.sqlite3_errstr(res).decode())

yield tuple(
extract[libsqlite3.sqlite3_column_type(pp_stmt, i)](pp_stmt, i)
Expand Down Expand Up @@ -394,3 +394,19 @@ def query(query_base, sql, params=(), named_params=()):
) as query_base:

yield partial(query, query_base)


class SQLiteS3QueryError(Exception):
pass


class VersioningNotEnabledError(SQLiteS3QueryError):
pass


class SQLiteError(SQLiteS3QueryError):
pass


class QueryContextClosedError(SQLiteS3QueryError):
pass
26 changes: 16 additions & 10 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@

import httpx

from sqlite_s3_query import sqlite_s3_query, sqlite_s3_query_multi
from sqlite_s3_query import (
VersioningNotEnabledError
SQLiteError,
QueryContextClosedError,
sqlite_s3_query,
sqlite_s3_query_multi,
)


class TestSqliteS3Query(unittest.TestCase):
Expand All @@ -36,7 +42,7 @@ def test_without_versioning(self):
]) as db:
put_object_without_versioning('bucket-without-versioning', 'my.db', db)

with self.assertRaisesRegex(Exception, 'The bucket must have versioning enabled'):
with self.assertRaisesRegex(VersioningNotEnabledError, 'The bucket must have versioning enabled'):
sqlite_s3_query('http://localhost:9000/bucket-without-versioning/my.db', get_credentials=lambda now: (
'us-east-1',
'AKIAIOSFODNN7EXAMPLE',
Expand Down Expand Up @@ -98,7 +104,7 @@ def test_select(self):

self.assertEqual(rows, [('some-text-a',)] * 500)

with self.assertRaisesRegex(Exception, 'Attempting to use finalized statement'):
with self.assertRaisesRegex(QueryContextClosedError, 'Attempting to use finalized statement'):
with sqlite_s3_query('http://localhost:9000/my-bucket/my.db', get_credentials=lambda now: (
'us-east-1',
'AKIAIOSFODNN7EXAMPLE',
Expand All @@ -110,7 +116,7 @@ def test_select(self):
break
next(rows)

with self.assertRaisesRegex(Exception, 'Attempting to use finalized statement'):
with self.assertRaisesRegex(QueryContextClosedError, 'Attempting to use finalized statement'):
with sqlite_s3_query('http://localhost:9000/my-bucket/my.db', get_credentials=lambda now: (
'us-east-1',
'AKIAIOSFODNN7EXAMPLE',
Expand Down Expand Up @@ -257,7 +263,7 @@ def test_select_multi(self):
next(rows_2_it)
raise Exception('Multiple open statements')

with self.assertRaisesRegex(Exception, 'Attempting to use finalized statement'):
with self.assertRaisesRegex(QueryContextClosedError, 'Attempting to use finalized statement'):
with sqlite_s3_query_multi('http://localhost:9000/my-bucket/my.db', get_credentials=lambda now: (
'us-east-1',
'AKIAIOSFODNN7EXAMPLE',
Expand Down Expand Up @@ -393,7 +399,7 @@ def test_non_existant_table(self):
'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
None,
), get_libsqlite3=get_libsqlite3) as query:
with self.assertRaisesRegex(Exception, 'no such table: non_table'):
with self.assertRaisesRegex(SQLiteError, 'no such table: non_table'):
query("SELECT * FROM non_table").__enter__()

def test_empty_object(self):
Expand All @@ -405,7 +411,7 @@ def test_empty_object(self):
'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
None,
), get_libsqlite3=get_libsqlite3) as query:
with self.assertRaisesRegex(Exception, 'disk I/O error'):
with self.assertRaisesRegex(SQLiteError, 'disk I/O error'):
query('SELECT 1').__enter__()

def test_bad_db_header(self):
Expand All @@ -417,7 +423,7 @@ def test_bad_db_header(self):
'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
None,
), get_libsqlite3=get_libsqlite3) as query:
with self.assertRaisesRegex(Exception, 'disk I/O error'):
with self.assertRaisesRegex(SQLiteError, 'disk I/O error'):
query("SELECT * FROM non_table").__enter__()

def test_bad_db_second_half(self):
Expand All @@ -435,7 +441,7 @@ def test_bad_db_second_half(self):
'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
None,
), get_libsqlite3=get_libsqlite3) as query:
with self.assertRaisesRegex(Exception, 'database disk image is malformed'):
with self.assertRaisesRegex(SQLiteError, 'database disk image is malformed'):
with query("SELECT * FROM my_table") as (columns, rows):
list(rows)

Expand Down Expand Up @@ -649,7 +655,7 @@ def iter_bytes(chunk_size=None):
'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
None,
), get_http_client=get_http_client, get_libsqlite3=get_libsqlite3) as query:
with self.assertRaisesRegex(Exception, 'disk I/O error'):
with self.assertRaisesRegex(SQLiteError, 'disk I/O error'):
query('SELECT my_col_a FROM my_table').__enter__()

def test_disconnection(self):
Expand Down

0 comments on commit 4939132

Please sign in to comment.