Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: enable tracebacks for "user"/custom sqlite functions #5383

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ name: Test
on:
pull_request:
push:
branches:
- master

env:
PY_COLORS: 1

Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ run-name: Lint code
on:
pull_request:
push:
branches:
- master

env:
PYTHON_VERSION: 3.8
Expand Down
34 changes: 30 additions & 4 deletions beets/dbcore/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
import os
import re
import sqlite3
import sys
import threading
import time
from abc import ABC
from collections import defaultdict
from sqlite3 import Connection
from sqlite3 import Connection, sqlite_version_info
from types import TracebackType
from typing import (
Any,
Expand Down Expand Up @@ -73,6 +74,16 @@
"""


class DBCustomFunctionError(Exception):
"""A sqlite function registered by beets failed."""

def __init__(self):
super().__init__(
"beets defined SQLite function failed; "
"see the other errors above for details"
)


class FormattedMapping(Mapping[str, str]):
"""A `dict`-like formatted view of a model.

Expand Down Expand Up @@ -286,7 +297,7 @@
"""The flex field SQLite table name.
"""

_fields: Dict[str, types.Type] = {}

Check failure on line 300 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "Type"
"""A mapping indicating available "fixed" fields on this type. The
keys are field names and the values are `Type` objects.
"""
Expand All @@ -296,7 +307,7 @@
terms.
"""

_types: Dict[str, types.Type] = {}

Check failure on line 310 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "Type"
"""Optional Types for non-fixed (i.e., flexible and computed) fields.
"""

Expand All @@ -305,7 +316,7 @@
are subclasses of `Sort`.
"""

_queries: Dict[str, Type[FieldQuery]] = {}

Check failure on line 319 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "FieldQuery"
"""Named queries that use a field-like `name:value` syntax but which
do not relate to any specific field.
"""
Expand All @@ -324,7 +335,7 @@
@cached_classproperty
def _relation(cls) -> type[Model]:
"""The model that this model is closely related to."""
return cls

Check failure on line 338 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Incompatible return value type (got "Model", expected "Type[Model]")

@cached_classproperty
def relation_join(cls) -> str:
Expand Down Expand Up @@ -439,7 +450,7 @@
# Essential field accessors.

@classmethod
def _type(cls, key) -> types.Type:

Check failure on line 453 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "Type"
"""Get the type of a field, a `Type` instance.

If the field has no explicit type, it is given the base `Type`,
Expand Down Expand Up @@ -730,16 +741,16 @@
cls,
field,
pattern,
query_cls: Type[FieldQuery] = MatchQuery,

Check failure on line 744 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "FieldQuery"
) -> FieldQuery:

Check failure on line 745 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "FieldQuery"
"""Get a `FieldQuery` for this model."""
return query_cls(field, pattern, field in cls._fields)

@classmethod
def all_fields_query(
cls: Type["Model"],
pats: Mapping,

Check failure on line 752 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "Mapping"
query_cls: Type[FieldQuery] = MatchQuery,

Check failure on line 753 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "FieldQuery"
):
"""Get a query that matches many fields with different patterns.

Expand All @@ -765,7 +776,7 @@
def __init__(
self,
model_class: Type[AnyModel],
rows: List[Mapping],

Check failure on line 779 in beets/dbcore/db.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Missing type parameters for generic type "Mapping"
db: "Database",
flex_rows,
query: Optional[Query] = None,
Expand Down Expand Up @@ -970,6 +981,12 @@
self._mutated = False
self.db._db_lock.release()

if (
isinstance(exc_value, sqlite3.OperationalError)
and exc_value.args[0] == "user-defined function raised exception"
):
raise DBCustomFunctionError()

def query(self, statement: str, subvals: Sequence = ()) -> List:
"""Execute an SQL statement with substitution values and return
a list of rows from the database.
Expand Down Expand Up @@ -1028,6 +1045,10 @@
"sqlite3 must be compiled with multi-threading support"
)

# Print tracebacks for exceptions in user defined functions
# See also `self.add_functions` and `DBCustomFunctionError`.
sqlite3.enable_callback_tracebacks(True)

self.path = path
self.timeout = timeout

Expand Down Expand Up @@ -1123,9 +1144,14 @@

return bytestring

conn.create_function("regexp", 2, regexp)
conn.create_function("unidecode", 1, unidecode)
conn.create_function("bytelower", 1, bytelower)
deterministic = {}
if sys.version_info >= (3, 8) and sqlite_version_info >= (3, 8, 3):
# Let sqlite make extra optimizations
deterministic["deterministic"] = True

conn.create_function("regexp", 2, regexp, **deterministic)
conn.create_function("unidecode", 1, unidecode, **deterministic)
conn.create_function("bytelower", 1, bytelower, **deterministic)

def _close(self):
"""Close the all connections to the underlying SQLite database
Expand Down
Loading