mirror of
https://github.com/rembo10/headphones.git
synced 2026-07-19 23:44:01 +01:00
update Beets
This commit is contained in:
@@ -16,12 +16,20 @@
|
||||
Library.
|
||||
"""
|
||||
|
||||
from .db import Model, Database
|
||||
from .query import Query, FieldQuery, MatchQuery, AndQuery, OrQuery
|
||||
from .db import Database, Model, Results
|
||||
from .query import (
|
||||
AndQuery,
|
||||
FieldQuery,
|
||||
InvalidQueryError,
|
||||
MatchQuery,
|
||||
OrQuery,
|
||||
Query,
|
||||
)
|
||||
from .queryparse import (
|
||||
parse_sorted_query,
|
||||
query_from_strings,
|
||||
sort_from_strings,
|
||||
)
|
||||
from .types import Type
|
||||
from .queryparse import query_from_strings
|
||||
from .queryparse import sort_from_strings
|
||||
from .queryparse import parse_sorted_query
|
||||
from .query import InvalidQueryError
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
+432
-238
File diff suppressed because it is too large
Load Diff
+431
-288
File diff suppressed because it is too large
Load Diff
@@ -12,30 +12,33 @@
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Parsing of strings into DBCore queries.
|
||||
"""
|
||||
"""Parsing of strings into DBCore queries."""
|
||||
|
||||
import re
|
||||
import itertools
|
||||
from . import query
|
||||
import re
|
||||
from typing import Collection, Dict, List, Optional, Sequence, Tuple, Type
|
||||
|
||||
from . import Model, query
|
||||
from .query import Sort
|
||||
|
||||
PARSE_QUERY_PART_REGEX = re.compile(
|
||||
# Non-capturing optional segment for the keyword.
|
||||
r'(-|\^)?' # Negation prefixes.
|
||||
|
||||
r'(?:'
|
||||
r'(\S+?)' # The field key.
|
||||
r'(?<!\\):' # Unescaped :
|
||||
r')?'
|
||||
|
||||
r'(.*)', # The term itself.
|
||||
|
||||
re.I # Case-insensitive.
|
||||
r"(-|\^)?" # Negation prefixes.
|
||||
r"(?:"
|
||||
r"(\S+?)" # The field key.
|
||||
r"(?<!\\):" # Unescaped :
|
||||
r")?"
|
||||
r"(.*)", # The term itself.
|
||||
re.I, # Case-insensitive.
|
||||
)
|
||||
|
||||
|
||||
def parse_query_part(part, query_classes={}, prefixes={},
|
||||
default_class=query.SubstringQuery):
|
||||
def parse_query_part(
|
||||
part: str,
|
||||
query_classes: Dict[str, Type[query.FieldQuery]] = {},
|
||||
prefixes: Dict = {},
|
||||
default_class: Type[query.SubstringQuery] = query.SubstringQuery,
|
||||
) -> Tuple[Optional[str], str, Type[query.FieldQuery], bool]:
|
||||
"""Parse a single *query part*, which is a chunk of a complete query
|
||||
string representing a single criterion.
|
||||
|
||||
@@ -86,13 +89,13 @@ def parse_query_part(part, query_classes={}, prefixes={},
|
||||
assert match # Regex should always match
|
||||
negate = bool(match.group(1))
|
||||
key = match.group(2)
|
||||
term = match.group(3).replace('\\:', ':')
|
||||
term = match.group(3).replace("\\:", ":")
|
||||
|
||||
# Check whether there's a prefix in the query and use the
|
||||
# corresponding query type.
|
||||
for pre, query_class in prefixes.items():
|
||||
if term.startswith(pre):
|
||||
return key, term[len(pre):], query_class, negate
|
||||
return key, term[len(pre) :], query_class, negate
|
||||
|
||||
# No matching prefix, so use either the query class determined by
|
||||
# the field or the default as a fallback.
|
||||
@@ -100,7 +103,11 @@ def parse_query_part(part, query_classes={}, prefixes={},
|
||||
return key, term, query_class, negate
|
||||
|
||||
|
||||
def construct_query_part(model_cls, prefixes, query_part):
|
||||
def construct_query_part(
|
||||
model_cls: Type[Model],
|
||||
prefixes: Dict,
|
||||
query_part: str,
|
||||
) -> query.Query:
|
||||
"""Parse a *query part* string and return a :class:`Query` object.
|
||||
|
||||
:param model_cls: The :class:`Model` class that this is a query for.
|
||||
@@ -116,40 +123,44 @@ def construct_query_part(model_cls, prefixes, query_part):
|
||||
if not query_part:
|
||||
return query.TrueQuery()
|
||||
|
||||
out_query: query.Query
|
||||
|
||||
# Use `model_cls` to build up a map from field (or query) names to
|
||||
# `Query` classes.
|
||||
query_classes = {}
|
||||
for k, t in itertools.chain(model_cls._fields.items(),
|
||||
model_cls._types.items()):
|
||||
query_classes: Dict[str, Type[query.FieldQuery]] = {}
|
||||
for k, t in itertools.chain(
|
||||
model_cls._fields.items(), model_cls._types.items()
|
||||
):
|
||||
query_classes[k] = t.query
|
||||
query_classes.update(model_cls._queries) # Non-field queries.
|
||||
|
||||
# Parse the string.
|
||||
key, pattern, query_class, negate = \
|
||||
parse_query_part(query_part, query_classes, prefixes)
|
||||
key, pattern, query_class, negate = parse_query_part(
|
||||
query_part, query_classes, prefixes
|
||||
)
|
||||
|
||||
# If there's no key (field name) specified, this is a "match
|
||||
# anything" query.
|
||||
if key is None:
|
||||
if issubclass(query_class, query.FieldQuery):
|
||||
# The query type matches a specific field, but none was
|
||||
# specified. So we use a version of the query that matches
|
||||
# any field.
|
||||
out_query = query.AnyFieldQuery(pattern, model_cls._search_fields,
|
||||
query_class)
|
||||
else:
|
||||
# Non-field query type.
|
||||
out_query = query_class(pattern)
|
||||
# The query type matches a specific field, but none was
|
||||
# specified. So we use a version of the query that matches
|
||||
# any field.
|
||||
out_query = query.AnyFieldQuery(
|
||||
pattern, model_cls._search_fields, query_class
|
||||
)
|
||||
|
||||
# Field queries get constructed according to the name of the field
|
||||
# they are querying.
|
||||
elif issubclass(query_class, query.FieldQuery):
|
||||
key = key.lower()
|
||||
out_query = query_class(key.lower(), pattern, key in model_cls._fields)
|
||||
|
||||
# Non-field (named) query.
|
||||
else:
|
||||
out_query = query_class(pattern)
|
||||
field = table = key.lower()
|
||||
if field in model_cls.shared_db_fields:
|
||||
# This field exists in both tables, so SQLite will encounter
|
||||
# an OperationalError if we try to query it in a join.
|
||||
# Using an explicit table name resolves this.
|
||||
table = f"{model_cls._table}.{field}"
|
||||
|
||||
field_in_db = field in model_cls.all_db_fields
|
||||
out_query = query_class(table, pattern, field_in_db)
|
||||
|
||||
# Apply negation.
|
||||
if negate:
|
||||
@@ -158,7 +169,13 @@ def construct_query_part(model_cls, prefixes, query_part):
|
||||
return out_query
|
||||
|
||||
|
||||
def query_from_strings(query_cls, model_cls, prefixes, query_parts):
|
||||
# TYPING ERROR
|
||||
def query_from_strings(
|
||||
query_cls: Type[query.CollectionQuery],
|
||||
model_cls: Type[Model],
|
||||
prefixes: Dict,
|
||||
query_parts: Collection[str],
|
||||
) -> query.Query:
|
||||
"""Creates a collection query of type `query_cls` from a list of
|
||||
strings in the format used by parse_query_part. `model_cls`
|
||||
determines how queries are constructed from strings.
|
||||
@@ -171,7 +188,11 @@ def query_from_strings(query_cls, model_cls, prefixes, query_parts):
|
||||
return query_cls(subqueries)
|
||||
|
||||
|
||||
def construct_sort_part(model_cls, part, case_insensitive=True):
|
||||
def construct_sort_part(
|
||||
model_cls: Type[Model],
|
||||
part: str,
|
||||
case_insensitive: bool = True,
|
||||
) -> Sort:
|
||||
"""Create a `Sort` from a single string criterion.
|
||||
|
||||
`model_cls` is the `Model` being queried. `part` is a single string
|
||||
@@ -183,12 +204,13 @@ def construct_sort_part(model_cls, part, case_insensitive=True):
|
||||
field = part[:-1]
|
||||
assert field, "field is missing"
|
||||
direction = part[-1]
|
||||
assert direction in ('+', '-'), "part must end with + or -"
|
||||
is_ascending = direction == '+'
|
||||
assert direction in ("+", "-"), "part must end with + or -"
|
||||
is_ascending = direction == "+"
|
||||
|
||||
if field in model_cls._sorts:
|
||||
sort = model_cls._sorts[field](model_cls, is_ascending,
|
||||
case_insensitive)
|
||||
sort = model_cls._sorts[field](
|
||||
model_cls, is_ascending, case_insensitive
|
||||
)
|
||||
elif field in model_cls._fields:
|
||||
sort = query.FixedFieldSort(field, is_ascending, case_insensitive)
|
||||
else:
|
||||
@@ -197,23 +219,31 @@ def construct_sort_part(model_cls, part, case_insensitive=True):
|
||||
return sort
|
||||
|
||||
|
||||
def sort_from_strings(model_cls, sort_parts, case_insensitive=True):
|
||||
"""Create a `Sort` from a list of sort criteria (strings).
|
||||
"""
|
||||
def sort_from_strings(
|
||||
model_cls: Type[Model],
|
||||
sort_parts: Sequence[str],
|
||||
case_insensitive: bool = True,
|
||||
) -> Sort:
|
||||
"""Create a `Sort` from a list of sort criteria (strings)."""
|
||||
if not sort_parts:
|
||||
sort = query.NullSort()
|
||||
return query.NullSort()
|
||||
elif len(sort_parts) == 1:
|
||||
sort = construct_sort_part(model_cls, sort_parts[0], case_insensitive)
|
||||
return construct_sort_part(model_cls, sort_parts[0], case_insensitive)
|
||||
else:
|
||||
sort = query.MultipleSort()
|
||||
for part in sort_parts:
|
||||
sort.add_sort(construct_sort_part(model_cls, part,
|
||||
case_insensitive))
|
||||
return sort
|
||||
sort.add_sort(
|
||||
construct_sort_part(model_cls, part, case_insensitive)
|
||||
)
|
||||
return sort
|
||||
|
||||
|
||||
def parse_sorted_query(model_cls, parts, prefixes={},
|
||||
case_insensitive=True):
|
||||
def parse_sorted_query(
|
||||
model_cls: Type[Model],
|
||||
parts: List[str],
|
||||
prefixes: Dict = {},
|
||||
case_insensitive: bool = True,
|
||||
) -> Tuple[query.Query, Sort]:
|
||||
"""Given a list of strings, create the `Query` and `Sort` that they
|
||||
represent.
|
||||
"""
|
||||
@@ -224,24 +254,24 @@ def parse_sorted_query(model_cls, parts, prefixes={},
|
||||
# Split up query in to comma-separated subqueries, each representing
|
||||
# an AndQuery, which need to be joined together in one OrQuery
|
||||
subquery_parts = []
|
||||
for part in parts + [',']:
|
||||
if part.endswith(','):
|
||||
for part in parts + [","]:
|
||||
if part.endswith(","):
|
||||
# Ensure we can catch "foo, bar" as well as "foo , bar"
|
||||
last_subquery_part = part[:-1]
|
||||
if last_subquery_part:
|
||||
subquery_parts.append(last_subquery_part)
|
||||
# Parse the subquery in to a single AndQuery
|
||||
# TODO: Avoid needlessly wrapping AndQueries containing 1 subquery?
|
||||
query_parts.append(query_from_strings(
|
||||
query.AndQuery, model_cls, prefixes, subquery_parts
|
||||
))
|
||||
query_parts.append(
|
||||
query_from_strings(
|
||||
query.AndQuery, model_cls, prefixes, subquery_parts
|
||||
)
|
||||
)
|
||||
del subquery_parts[:]
|
||||
else:
|
||||
# Sort parts (1) end in + or -, (2) don't have a field, and
|
||||
# (3) consist of more than just the + or -.
|
||||
if part.endswith(('+', '-')) \
|
||||
and ':' not in part \
|
||||
and len(part) > 1:
|
||||
if part.endswith(("+", "-")) and ":" not in part and len(part) > 1:
|
||||
sort_parts.append(part)
|
||||
else:
|
||||
subquery_parts.append(part)
|
||||
|
||||
+162
-67
@@ -14,28 +14,46 @@
|
||||
|
||||
"""Representation of type information for DBCore model fields.
|
||||
"""
|
||||
import typing
|
||||
from abc import ABC
|
||||
from typing import Any, Generic, List, TypeVar, Union, cast
|
||||
|
||||
from . import query
|
||||
from beets.util import str2bool
|
||||
|
||||
from .query import BooleanQuery, FieldQuery, NumericQuery, SubstringQuery
|
||||
|
||||
# Abstract base.
|
||||
|
||||
class Type:
|
||||
class ModelType(typing.Protocol):
|
||||
"""Protocol that specifies the required constructor for model types,
|
||||
i.e. a function that takes any argument and attempts to parse it to the
|
||||
given type.
|
||||
"""
|
||||
|
||||
def __init__(self, value: Any = None): ...
|
||||
|
||||
|
||||
# Generic type variables, used for the value type T and null type N (if
|
||||
# nullable, else T and N are set to the same type for the concrete subclasses
|
||||
# of Type).
|
||||
N = TypeVar("N")
|
||||
T = TypeVar("T", bound=ModelType)
|
||||
|
||||
|
||||
class Type(ABC, Generic[T, N]):
|
||||
"""An object encapsulating the type of a model field. Includes
|
||||
information about how to store, query, format, and parse a given
|
||||
field.
|
||||
"""
|
||||
|
||||
sql = 'TEXT'
|
||||
sql: str = "TEXT"
|
||||
"""The SQLite column type for the value.
|
||||
"""
|
||||
|
||||
query = query.SubstringQuery
|
||||
query: typing.Type[FieldQuery] = SubstringQuery
|
||||
"""The `Query` subclass to be used when querying the field.
|
||||
"""
|
||||
|
||||
model_type = str
|
||||
model_type: typing.Type[T]
|
||||
"""The Python type that is used to represent the value in the model.
|
||||
|
||||
The model is guaranteed to return a value of this type if the field
|
||||
@@ -44,12 +62,14 @@ class Type:
|
||||
"""
|
||||
|
||||
@property
|
||||
def null(self):
|
||||
"""The value to be exposed when the underlying value is None.
|
||||
"""
|
||||
return self.model_type()
|
||||
def null(self) -> N:
|
||||
"""The value to be exposed when the underlying value is None."""
|
||||
# Note that this default implementation only makes sense for T = N.
|
||||
# It would be better to implement `null()` only in subclasses, or
|
||||
# have a field null_type similar to `model_type` and use that here.
|
||||
return cast(N, self.model_type())
|
||||
|
||||
def format(self, value):
|
||||
def format(self, value: Union[N, T]) -> str:
|
||||
"""Given a value of this type, produce a Unicode string
|
||||
representing the value. This is used in template evaluation.
|
||||
"""
|
||||
@@ -57,13 +77,13 @@ class Type:
|
||||
value = self.null
|
||||
# `self.null` might be `None`
|
||||
if value is None:
|
||||
value = ''
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode('utf-8', 'ignore')
|
||||
return ""
|
||||
elif isinstance(value, bytes):
|
||||
return value.decode("utf-8", "ignore")
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
return str(value)
|
||||
|
||||
def parse(self, string):
|
||||
def parse(self, string: str) -> Union[T, N]:
|
||||
"""Parse a (possibly human-written) string and return the
|
||||
indicated value of this type.
|
||||
"""
|
||||
@@ -72,19 +92,23 @@ class Type:
|
||||
except ValueError:
|
||||
return self.null
|
||||
|
||||
def normalize(self, value):
|
||||
def normalize(self, value: Any) -> Union[T, N]:
|
||||
"""Given a value that will be assigned into a field of this
|
||||
type, normalize the value to have the appropriate type. This
|
||||
base implementation only reinterprets `None`.
|
||||
"""
|
||||
# TYPING ERROR
|
||||
if value is None:
|
||||
return self.null
|
||||
else:
|
||||
# TODO This should eventually be replaced by
|
||||
# `self.model_type(value)`
|
||||
return value
|
||||
return cast(T, value)
|
||||
|
||||
def from_sql(self, sql_value):
|
||||
def from_sql(
|
||||
self,
|
||||
sql_value: Union[None, int, float, str, bytes],
|
||||
) -> Union[T, N]:
|
||||
"""Receives the value stored in the SQL backend and return the
|
||||
value to be stored in the model.
|
||||
|
||||
@@ -99,13 +123,13 @@ class Type:
|
||||
and the method must handle these in addition.
|
||||
"""
|
||||
if isinstance(sql_value, memoryview):
|
||||
sql_value = bytes(sql_value).decode('utf-8', 'ignore')
|
||||
sql_value = bytes(sql_value).decode("utf-8", "ignore")
|
||||
if isinstance(sql_value, str):
|
||||
return self.parse(sql_value)
|
||||
else:
|
||||
return self.normalize(sql_value)
|
||||
|
||||
def to_sql(self, model_value):
|
||||
def to_sql(self, model_value: Any) -> Union[None, int, float, str, bytes]:
|
||||
"""Convert a value as stored in the model object to a value used
|
||||
by the database adapter.
|
||||
"""
|
||||
@@ -114,18 +138,23 @@ class Type:
|
||||
|
||||
# Reusable types.
|
||||
|
||||
class Default(Type):
|
||||
null = None
|
||||
|
||||
class Default(Type[str, None]):
|
||||
model_type = str
|
||||
|
||||
@property
|
||||
def null(self):
|
||||
return None
|
||||
|
||||
|
||||
class Integer(Type):
|
||||
"""A basic integer type.
|
||||
"""
|
||||
sql = 'INTEGER'
|
||||
query = query.NumericQuery
|
||||
class BaseInteger(Type[int, N]):
|
||||
"""A basic integer type."""
|
||||
|
||||
sql = "INTEGER"
|
||||
query = NumericQuery
|
||||
model_type = int
|
||||
|
||||
def normalize(self, value):
|
||||
def normalize(self, value: Any) -> Union[int, N]:
|
||||
try:
|
||||
return self.model_type(round(float(value)))
|
||||
except ValueError:
|
||||
@@ -134,91 +163,153 @@ class Integer(Type):
|
||||
return self.null
|
||||
|
||||
|
||||
class PaddedInt(Integer):
|
||||
class Integer(BaseInteger[int]):
|
||||
@property
|
||||
def null(self) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
class NullInteger(BaseInteger[None]):
|
||||
@property
|
||||
def null(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class BasePaddedInt(BaseInteger[N]):
|
||||
"""An integer field that is formatted with a given number of digits,
|
||||
padded with zeroes.
|
||||
"""
|
||||
def __init__(self, digits):
|
||||
|
||||
def __init__(self, digits: int):
|
||||
self.digits = digits
|
||||
|
||||
def format(self, value):
|
||||
return '{0:0{1}d}'.format(value or 0, self.digits)
|
||||
def format(self, value: Union[int, N]) -> str:
|
||||
return "{0:0{1}d}".format(value or 0, self.digits)
|
||||
|
||||
|
||||
class NullPaddedInt(PaddedInt):
|
||||
"""Same as `PaddedInt`, but does not normalize `None` to `0.0`.
|
||||
"""
|
||||
null = None
|
||||
class PaddedInt(BasePaddedInt[int]):
|
||||
pass
|
||||
|
||||
|
||||
class NullPaddedInt(BasePaddedInt[None]):
|
||||
"""Same as `PaddedInt`, but does not normalize `None` to `0`."""
|
||||
|
||||
@property
|
||||
def null(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class ScaledInt(Integer):
|
||||
"""An integer whose formatting operation scales the number by a
|
||||
constant and adds a suffix. Good for units with large magnitudes.
|
||||
"""
|
||||
def __init__(self, unit, suffix=''):
|
||||
|
||||
def __init__(self, unit: int, suffix: str = ""):
|
||||
self.unit = unit
|
||||
self.suffix = suffix
|
||||
|
||||
def format(self, value):
|
||||
return '{}{}'.format((value or 0) // self.unit, self.suffix)
|
||||
def format(self, value: int) -> str:
|
||||
return "{}{}".format((value or 0) // self.unit, self.suffix)
|
||||
|
||||
|
||||
class Id(Integer):
|
||||
class Id(NullInteger):
|
||||
"""An integer used as the row id or a foreign key in a SQLite table.
|
||||
This type is nullable: None values are not translated to zero.
|
||||
"""
|
||||
null = None
|
||||
|
||||
def __init__(self, primary=True):
|
||||
@property
|
||||
def null(self) -> None:
|
||||
return None
|
||||
|
||||
def __init__(self, primary: bool = True):
|
||||
if primary:
|
||||
self.sql = 'INTEGER PRIMARY KEY'
|
||||
self.sql = "INTEGER PRIMARY KEY"
|
||||
|
||||
|
||||
class Float(Type):
|
||||
class BaseFloat(Type[float, N]):
|
||||
"""A basic floating-point type. The `digits` parameter specifies how
|
||||
many decimal places to use in the human-readable representation.
|
||||
"""
|
||||
sql = 'REAL'
|
||||
query = query.NumericQuery
|
||||
|
||||
sql = "REAL"
|
||||
query: typing.Type[FieldQuery[Any]] = NumericQuery
|
||||
model_type = float
|
||||
|
||||
def __init__(self, digits=1):
|
||||
def __init__(self, digits: int = 1):
|
||||
self.digits = digits
|
||||
|
||||
def format(self, value):
|
||||
return '{0:.{1}f}'.format(value or 0, self.digits)
|
||||
def format(self, value: Union[float, N]) -> str:
|
||||
return "{0:.{1}f}".format(value or 0, self.digits)
|
||||
|
||||
|
||||
class NullFloat(Float):
|
||||
"""Same as `Float`, but does not normalize `None` to `0.0`.
|
||||
"""
|
||||
null = None
|
||||
class Float(BaseFloat[float]):
|
||||
"""Floating-point type that normalizes `None` to `0.0`."""
|
||||
|
||||
@property
|
||||
def null(self) -> float:
|
||||
return 0.0
|
||||
|
||||
|
||||
class String(Type):
|
||||
"""A Unicode string type.
|
||||
"""
|
||||
sql = 'TEXT'
|
||||
query = query.SubstringQuery
|
||||
class NullFloat(BaseFloat[None]):
|
||||
"""Same as `Float`, but does not normalize `None` to `0.0`."""
|
||||
|
||||
def normalize(self, value):
|
||||
@property
|
||||
def null(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class BaseString(Type[T, N]):
|
||||
"""A Unicode string type."""
|
||||
|
||||
sql = "TEXT"
|
||||
query = SubstringQuery
|
||||
|
||||
def normalize(self, value: Any) -> Union[T, N]:
|
||||
if value is None:
|
||||
return self.null
|
||||
else:
|
||||
return self.model_type(value)
|
||||
|
||||
|
||||
class Boolean(Type):
|
||||
"""A boolean type.
|
||||
class String(BaseString[str, Any]):
|
||||
"""A Unicode string type."""
|
||||
|
||||
model_type = str
|
||||
|
||||
|
||||
class DelimitedString(BaseString[List[str], List[str]]):
|
||||
"""A list of Unicode strings, represented in-database by a single string
|
||||
containing delimiter-separated values.
|
||||
"""
|
||||
sql = 'INTEGER'
|
||||
query = query.BooleanQuery
|
||||
|
||||
model_type = list
|
||||
|
||||
def __init__(self, delimiter: str):
|
||||
self.delimiter = delimiter
|
||||
|
||||
def format(self, value: List[str]):
|
||||
return self.delimiter.join(value)
|
||||
|
||||
def parse(self, string: str):
|
||||
if not string:
|
||||
return []
|
||||
return string.split(self.delimiter)
|
||||
|
||||
def to_sql(self, model_value: List[str]):
|
||||
return self.delimiter.join(model_value)
|
||||
|
||||
|
||||
class Boolean(Type):
|
||||
"""A boolean type."""
|
||||
|
||||
sql = "INTEGER"
|
||||
query = BooleanQuery
|
||||
model_type = bool
|
||||
|
||||
def format(self, value):
|
||||
def format(self, value: bool) -> str:
|
||||
return str(bool(value))
|
||||
|
||||
def parse(self, string):
|
||||
def parse(self, string: str) -> bool:
|
||||
return str2bool(string)
|
||||
|
||||
|
||||
@@ -231,3 +322,7 @@ FLOAT = Float()
|
||||
NULL_FLOAT = NullFloat()
|
||||
STRING = String()
|
||||
BOOLEAN = Boolean()
|
||||
SEMICOLON_SPACE_DSV = DelimitedString(delimiter="; ")
|
||||
|
||||
# Will set the proper null char in mediafile
|
||||
MULTI_VALUE_DSV = DelimitedString(delimiter="\\␀")
|
||||
|
||||
Reference in New Issue
Block a user