Add NullPathType and types to PathType

This commit is contained in:
Šarūnas Nejus 2025-05-12 12:19:00 +01:00
parent 1a045c9166
commit b40ce836d5
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
5 changed files with 73 additions and 68 deletions

View file

@ -48,4 +48,6 @@ f36bc497c8c8f89004f3f6879908d3f0b25123e1
# Fix formatting # Fix formatting
c490ac5810b70f3cf5fd8649669838e8fdb19f4d c490ac5810b70f3cf5fd8649669838e8fdb19f4d
# Importer restructure # Importer restructure
9147577b2b19f43ca827e9650261a86fb0450cef 9147577b2b19f43ca827e9650261a86fb0450cef
# Copy paste query, types from library to dbcore
1a045c91668c771686f4c871c84f1680af2e944b

View file

@ -40,6 +40,8 @@ else:
# To use the SQLite "blob" type, it doesn't suffice to provide a byte # To use the SQLite "blob" type, it doesn't suffice to provide a byte
# string; SQLite treats that as encoded text. Wrapping it in a # string; SQLite treats that as encoded text. Wrapping it in a
# `memoryview` tells it that we actually mean non-text data. # `memoryview` tells it that we actually mean non-text data.
# needs to be defined in here due to circular import.
# TODO: remove it from this module and define it in dbcore/types.py instead
BLOB_TYPE = memoryview BLOB_TYPE = memoryview

View file

@ -22,6 +22,7 @@ import typing
from abc import ABC from abc import ABC
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
import beets
from beets import util from beets import util
from . import query from . import query
@ -345,7 +346,7 @@ class DateType(Float):
return self.null return self.null
class PathType(Type[bytes, bytes]): class BasePathType(Type[bytes, N]):
"""A dbcore type for filesystem paths. """A dbcore type for filesystem paths.
These are represented as `bytes` objects, in keeping with These are represented as `bytes` objects, in keeping with
@ -356,27 +357,10 @@ class PathType(Type[bytes, bytes]):
query = query.PathQuery query = query.PathQuery
model_type = bytes model_type = bytes
def __init__(self, nullable=False): def parse(self, string: str) -> bytes:
"""Create a path type object. return util.normpath(string)
`nullable` controls whether the type may be missing, i.e., None. def normalize(self, value: Any) -> bytes | N:
"""
self.nullable = nullable
@property
def null(self):
if self.nullable:
return None
else:
return b""
def format(self, value):
return util.displayable_path(value)
def parse(self, string):
return util.normpath(util.bytestring_path(string))
def normalize(self, value):
if isinstance(value, str): if isinstance(value, str):
# Paths stored internally as encoded bytes. # Paths stored internally as encoded bytes.
return util.bytestring_path(value) return util.bytestring_path(value)
@ -391,12 +375,30 @@ class PathType(Type[bytes, bytes]):
def from_sql(self, sql_value): def from_sql(self, sql_value):
return self.normalize(sql_value) return self.normalize(sql_value)
def to_sql(self, value): def to_sql(self, value: bytes) -> BLOB_TYPE:
if isinstance(value, bytes): if isinstance(value, bytes):
value = BLOB_TYPE(value) value = BLOB_TYPE(value)
return value return value
class NullPathType(BasePathType[None]):
@property
def null(self) -> None:
return None
def format(self, value: bytes | None) -> str:
return util.displayable_path(value or b"")
class PathType(BasePathType[bytes]):
@property
def null(self) -> bytes:
return b""
def format(self, value: bytes) -> str:
return util.displayable_path(value or b"")
class MusicalKey(String): class MusicalKey(String):
"""String representing the musical key of a song. """String representing the musical key of a song.

View file

@ -910,7 +910,7 @@ class Album(LibModel):
_always_dirty = True _always_dirty = True
_fields = { _fields = {
"id": types.PRIMARY_ID, "id": types.PRIMARY_ID,
"artpath": types.PathType(True), "artpath": types.NullPathType(),
"added": types.DATE, "added": types.DATE,
"albumartist": types.STRING, "albumartist": types.STRING,
"albumartist_sort": types.STRING, "albumartist_sort": types.STRING,

View file

@ -1,59 +1,58 @@
import time import time
import unittest
import beets import beets
from beets.dbcore import types from beets.dbcore import types
from beets.util import normpath from beets.util import normpath
class LibraryFieldTypesTest(unittest.TestCase): def test_datetype():
"""Test format() and parse() for library-specific field types""" t = types.DATE
def test_datetype(self): # format
t = types.DATE time_format = beets.config["time_format"].as_str()
time_local = time.strftime(time_format, time.localtime(123456789))
assert time_local == t.format(123456789)
# parse
assert 123456789.0 == t.parse(time_local)
assert 123456789.0 == t.parse("123456789.0")
assert t.null == t.parse("not123456789.0")
assert t.null == t.parse("1973-11-29")
# format
time_format = beets.config["time_format"].as_str()
time_local = time.strftime(time_format, time.localtime(123456789))
assert time_local == t.format(123456789)
# parse
assert 123456789.0 == t.parse(time_local)
assert 123456789.0 == t.parse("123456789.0")
assert t.null == t.parse("not123456789.0")
assert t.null == t.parse("1973-11-29")
def test_pathtype(self): def test_pathtype():
t = types.PathType() t = types.PathType()
# format # format
assert "/tmp" == t.format("/tmp") assert "/tmp" == t.format("/tmp")
assert "/tmp/\xe4lbum" == t.format("/tmp/\u00e4lbum") assert "/tmp/\xe4lbum" == t.format("/tmp/\u00e4lbum")
# parse # parse
assert normpath(b"/tmp") == t.parse("/tmp") assert normpath(b"/tmp") == t.parse("/tmp")
assert normpath(b"/tmp/\xc3\xa4lbum") == t.parse("/tmp/\u00e4lbum/") assert normpath(b"/tmp/\xc3\xa4lbum") == t.parse("/tmp/\u00e4lbum/")
def test_musicalkey(self):
t = types.MusicalKey()
# parse def test_musicalkey():
assert "C#m" == t.parse("c#m") t = types.MusicalKey()
assert "Gm" == t.parse("g minor")
assert "Not c#m" == t.parse("not C#m")
def test_durationtype(self): # parse
t = types.DurationType() assert "C#m" == t.parse("c#m")
assert "Gm" == t.parse("g minor")
assert "Not c#m" == t.parse("not C#m")
# format
assert "1:01" == t.format(61.23) def test_durationtype():
assert "60:01" == t.format(3601.23) t = types.DurationType()
assert "0:00" == t.format(None)
# parse # format
assert 61.0 == t.parse("1:01") assert "1:01" == t.format(61.23)
assert 61.23 == t.parse("61.23") assert "60:01" == t.format(3601.23)
assert 3601.0 == t.parse("60:01") assert "0:00" == t.format(None)
assert t.null == t.parse("1:00:01") # parse
assert t.null == t.parse("not61.23") assert 61.0 == t.parse("1:01")
# config format_raw_length assert 61.23 == t.parse("61.23")
beets.config["format_raw_length"] = True assert 3601.0 == t.parse("60:01")
assert 61.23 == t.format(61.23) assert t.null == t.parse("1:00:01")
assert 3601.23 == t.format(3601.23) assert t.null == t.parse("not61.23")
# config format_raw_length
beets.config["format_raw_length"] = True
assert 61.23 == t.format(61.23)
assert 3601.23 == t.format(3601.23)