From 15510b61879ef96a5ac233c394204d906a0b00df Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 23 Jan 2014 19:36:18 -0800 Subject: [PATCH] new dbcore.types module Now Type is proper, full-on class so we don't have to fit everything in a lambda. --- beets/dbcore/__init__.py | 3 +- beets/dbcore/db.py | 7 +- beets/dbcore/types.py | 83 ++++++++++++++++ beets/library.py | 207 ++++++++++++++++++--------------------- 4 files changed, 181 insertions(+), 119 deletions(-) create mode 100644 beets/dbcore/types.py diff --git a/beets/dbcore/__init__.py b/beets/dbcore/__init__.py index dc342cb8c..41d1c1256 100644 --- a/beets/dbcore/__init__.py +++ b/beets/dbcore/__init__.py @@ -15,5 +15,6 @@ """DBCore is an abstract database package that forms the basis for beets' Library. """ -from .db import Type, Model, Database +from .db import Model, Database from .query import Query, FieldQuery, MatchQuery, AndQuery +from .types import Type diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 75f0298d5..11db94d47 100644 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -16,7 +16,7 @@ """ import time import os -from collections import defaultdict, namedtuple +from collections import defaultdict import threading import sqlite3 import contextlib @@ -27,10 +27,7 @@ from .query import MatchQuery -# Abstract base for model classes and their field types. - - -Type = namedtuple('Type', 'sql query format') +# Abstract base for model classes. class Model(object): diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py new file mode 100644 index 000000000..506271e31 --- /dev/null +++ b/beets/dbcore/types.py @@ -0,0 +1,83 @@ +# This file is part of beets. +# Copyright 2014, Adrian Sampson. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Representation of type information for DBCore model fields. +""" +from . import query + + +class Type(object): + """An object encapsulating the type of a model field. Includes + information about how to store the value in the database, query, + format, and parse a given field. + """ + def __init__(self, sql, query, format_func=None): + """Create a type. `sql` is the SQLite column type for the value. + `query` is the `Query` subclass to be used when querying the + field. `format_func` is a function that transforms values of + this type to a human-readable Unicode string. If `format_func` + is not provided, the subclass must override `format` to provide + the functionality. + """ + self.sql = sql + self.query = query + self.format_func = format_func + + def format(self, value): + """Given a value of this type, produce a Unicode string + representing the value. This is used in template evaluation. + """ + return self.format_func(value) + + +# Common singleton types. + +ID_TYPE = Type('INTEGER PRIMARY KEY', query.NumericQuery, unicode) +INT_TYPE = Type('INTEGER', query.NumericQuery, + lambda n: unicode(n or 0)) +FLOAT_TYPE = Type('REAL', query.NumericQuery, + lambda n: u'{0:.1f}'.format(n or 0.0)) +STRING_TYPE = Type('TEXT', query.SubstringQuery, + lambda s: s or u'') +BOOL_TYPE = Type('INTEGER', query.BooleanQuery, + lambda b: unicode(bool(b))) + + + +# Parameterized types. + + +class PaddedInt(Type): + """An integer field that is formatted with a given number of digits, + padded with zeroes. + """ + def __init__(self, digits): + self.digits = digits + super(PaddedInt, self).__init__('INTEGER', query.NumericQuery) + + def format(self, value): + return u'{0:0{1}d}'.format(value or 0, self.digits) + + +class ScaledInt(Type): + """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=u''): + self.unit = unit + self.suffix = suffix + super(ScaledInt, self).__init__('INTEGER', query.NumericQuery) + + def format(self, value): + return u'{0}{1}'.format((value or 0) // self.unit, self.suffix) diff --git a/beets/library.py b/beets/library.py index 13a173d51..a99bcba0b 100644 --- a/beets/library.py +++ b/beets/library.py @@ -29,9 +29,8 @@ from beets import util from beets.util import bytestring_path, syspath, normpath, samefile from beets.util.functemplate import Template from beets import dbcore -from beets.dbcore import Type +from beets.dbcore import types import beets -from datetime import datetime @@ -78,31 +77,13 @@ class SingletonQuery(dbcore.Query): # Model field lists. # Common types used in field definitions. -def _str_or_empty(s): - if not s: - return u'' - return unicode(s) -ID_TYPE = Type('INTEGER PRIMARY KEY', dbcore.query.NumericQuery, _str_or_empty) -INT_TYPE = Type('INTEGER', dbcore.query.NumericQuery, _str_or_empty) -FLOAT_TYPE = Type('REAL', dbcore.query.NumericQuery, - lambda n: u'{0:.1f}'.format(n or 0.0)) -DATE_TYPE = Type( +DATE_TYPE = types.Type( 'REAL', dbcore.query.NumericQuery, lambda n: time.strftime(beets.config['time_format'].get(unicode), time.localtime(n or 0)) ) -STRING_TYPE = Type('TEXT', dbcore.query.SubstringQuery, _str_or_empty) -BOOL_TYPE = Type('INTEGER', dbcore.query.BooleanQuery, _str_or_empty) -PATH_TYPE = Type('BLOB', PathQuery, util.displayable_path) - -def _padded_int(digits): - return Type('INTEGER', dbcore.query.NumericQuery, - lambda n: u'{0:0{1}d}'.format(n or 0, digits)) - -def _scaled_int(suffix=u'', unit=1000): - return Type('INTEGER', dbcore.query.NumericQuery, - lambda n: u'{0}{1}'.format((n or 0) // unit, suffix)) +PATH_TYPE = types.Type('BLOB', PathQuery, util.displayable_path) # Fields in the "items" database table; all the metadata available for # items in the library. These are used directly in SQL; they are @@ -113,67 +94,67 @@ def _scaled_int(suffix=u'', unit=1000): # - Is the field writable? # - Does the field reflect an attribute of a MediaFile? ITEM_FIELDS = [ - ('id', ID_TYPE, False, False), - ('path', PATH_TYPE, False, False), - ('album_id', INT_TYPE, False, False), + ('id', types.ID_TYPE, False, False), + ('path', PATH_TYPE, False, False), + ('album_id', types.INT_TYPE, False, False), - ('title', STRING_TYPE, True, True), - ('artist', STRING_TYPE, True, True), - ('artist_sort', STRING_TYPE, True, True), - ('artist_credit', STRING_TYPE, True, True), - ('album', STRING_TYPE, True, True), - ('albumartist', STRING_TYPE, True, True), - ('albumartist_sort', STRING_TYPE, True, True), - ('albumartist_credit', STRING_TYPE, True, True), - ('genre', STRING_TYPE, True, True), - ('composer', STRING_TYPE, True, True), - ('grouping', STRING_TYPE, True, True), - ('year', _padded_int(4), True, True), - ('month', _padded_int(2), True, True), - ('day', _padded_int(2), True, True), - ('track', _padded_int(2), True, True), - ('tracktotal', _padded_int(2), True, True), - ('disc', _padded_int(2), True, True), - ('disctotal', _padded_int(2), True, True), - ('lyrics', STRING_TYPE, True, True), - ('comments', STRING_TYPE, True, True), - ('bpm', INT_TYPE, True, True), - ('comp', BOOL_TYPE, True, True), - ('mb_trackid', STRING_TYPE, True, True), - ('mb_albumid', STRING_TYPE, True, True), - ('mb_artistid', STRING_TYPE, True, True), - ('mb_albumartistid', STRING_TYPE, True, True), - ('albumtype', STRING_TYPE, True, True), - ('label', STRING_TYPE, True, True), - ('acoustid_fingerprint', STRING_TYPE, True, True), - ('acoustid_id', STRING_TYPE, True, True), - ('mb_releasegroupid', STRING_TYPE, True, True), - ('asin', STRING_TYPE, True, True), - ('catalognum', STRING_TYPE, True, True), - ('script', STRING_TYPE, True, True), - ('language', STRING_TYPE, True, True), - ('country', STRING_TYPE, True, True), - ('albumstatus', STRING_TYPE, True, True), - ('media', STRING_TYPE, True, True), - ('albumdisambig', STRING_TYPE, True, True), - ('disctitle', STRING_TYPE, True, True), - ('encoder', STRING_TYPE, True, True), - ('rg_track_gain', FLOAT_TYPE, True, True), - ('rg_track_peak', FLOAT_TYPE, True, True), - ('rg_album_gain', FLOAT_TYPE, True, True), - ('rg_album_peak', FLOAT_TYPE, True, True), - ('original_year', _padded_int(4), True, True), - ('original_month', _padded_int(2), True, True), - ('original_day', _padded_int(2), True, True), + ('title', types.STRING_TYPE, True, True), + ('artist', types.STRING_TYPE, True, True), + ('artist_sort', types.STRING_TYPE, True, True), + ('artist_credit', types.STRING_TYPE, True, True), + ('album', types.STRING_TYPE, True, True), + ('albumartist', types.STRING_TYPE, True, True), + ('albumartist_sort', types.STRING_TYPE, True, True), + ('albumartist_credit', types.STRING_TYPE, True, True), + ('genre', types.STRING_TYPE, True, True), + ('composer', types.STRING_TYPE, True, True), + ('grouping', types.STRING_TYPE, True, True), + ('year', types.PaddedInt(4), True, True), + ('month', types.PaddedInt(2), True, True), + ('day', types.PaddedInt(2), True, True), + ('track', types.PaddedInt(2), True, True), + ('tracktotal', types.PaddedInt(2), True, True), + ('disc', types.PaddedInt(2), True, True), + ('disctotal', types.PaddedInt(2), True, True), + ('lyrics', types.STRING_TYPE, True, True), + ('comments', types.STRING_TYPE, True, True), + ('bpm', types.INT_TYPE, True, True), + ('comp', types.BOOL_TYPE, True, True), + ('mb_trackid', types.STRING_TYPE, True, True), + ('mb_albumid', types.STRING_TYPE, True, True), + ('mb_artistid', types.STRING_TYPE, True, True), + ('mb_albumartistid', types.STRING_TYPE, True, True), + ('albumtype', types.STRING_TYPE, True, True), + ('label', types.STRING_TYPE, True, True), + ('acoustid_fingerprint', types.STRING_TYPE, True, True), + ('acoustid_id', types.STRING_TYPE, True, True), + ('mb_releasegroupid', types.STRING_TYPE, True, True), + ('asin', types.STRING_TYPE, True, True), + ('catalognum', types.STRING_TYPE, True, True), + ('script', types.STRING_TYPE, True, True), + ('language', types.STRING_TYPE, True, True), + ('country', types.STRING_TYPE, True, True), + ('albumstatus', types.STRING_TYPE, True, True), + ('media', types.STRING_TYPE, True, True), + ('albumdisambig', types.STRING_TYPE, True, True), + ('disctitle', types.STRING_TYPE, True, True), + ('encoder', types.STRING_TYPE, True, True), + ('rg_track_gain', types.FLOAT_TYPE, True, True), + ('rg_track_peak', types.FLOAT_TYPE, True, True), + ('rg_album_gain', types.FLOAT_TYPE, True, True), + ('rg_album_peak', types.FLOAT_TYPE, True, True), + ('original_year', types.PaddedInt(4), True, True), + ('original_month', types.PaddedInt(2), True, True), + ('original_day', types.PaddedInt(2), True, True), - ('length', FLOAT_TYPE, False, True), - ('bitrate', _scaled_int(u'kbps'), False, True), - ('format', STRING_TYPE, False, True), - ('samplerate', _scaled_int(u'kHz'), False, True), - ('bitdepth', INT_TYPE, False, True), - ('channels', INT_TYPE, False, True), - ('mtime', DATE_TYPE, False, False), - ('added', DATE_TYPE, False, False), + ('length', types.FLOAT_TYPE, False, True), + ('bitrate', types.ScaledInt(1000, u'kbps'), False, True), + ('format', types.STRING_TYPE, False, True), + ('samplerate', types.ScaledInt(1000, u'kHz'), False, True), + ('bitdepth', types.INT_TYPE, False, True), + ('channels', types.INT_TYPE, False, True), + ('mtime', DATE_TYPE, False, False), + ('added', DATE_TYPE, False, False), ] ITEM_KEYS_WRITABLE = [f[0] for f in ITEM_FIELDS if f[3] and f[2]] ITEM_KEYS_META = [f[0] for f in ITEM_FIELDS if f[3]] @@ -183,39 +164,39 @@ ITEM_KEYS = [f[0] for f in ITEM_FIELDS] # The third entry in each tuple indicates whether the field reflects an # identically-named field in the items table. ALBUM_FIELDS = [ - ('id', ID_TYPE, False), - ('artpath', PATH_TYPE, False), - ('added', DATE_TYPE, True), + ('id', types.ID_TYPE, False), + ('artpath', PATH_TYPE, False), + ('added', DATE_TYPE, True), - ('albumartist', STRING_TYPE, True), - ('albumartist_sort', STRING_TYPE, True), - ('albumartist_credit', STRING_TYPE, True), - ('album', STRING_TYPE, True), - ('genre', STRING_TYPE, True), - ('year', _padded_int(4), True), - ('month', _padded_int(2), True), - ('day', _padded_int(2), True), - ('tracktotal', _padded_int(2), True), - ('disctotal', _padded_int(2), True), - ('comp', BOOL_TYPE, True), - ('mb_albumid', STRING_TYPE, True), - ('mb_albumartistid', STRING_TYPE, True), - ('albumtype', STRING_TYPE, True), - ('label', STRING_TYPE, True), - ('mb_releasegroupid', STRING_TYPE, True), - ('asin', STRING_TYPE, True), - ('catalognum', STRING_TYPE, True), - ('script', STRING_TYPE, True), - ('language', STRING_TYPE, True), - ('country', STRING_TYPE, True), - ('albumstatus', STRING_TYPE, True), - ('media', STRING_TYPE, True), - ('albumdisambig', STRING_TYPE, True), - ('rg_album_gain', FLOAT_TYPE, True), - ('rg_album_peak', FLOAT_TYPE, True), - ('original_year', _padded_int(4), True), - ('original_month', _padded_int(2), True), - ('original_day', _padded_int(2), True), + ('albumartist', types.STRING_TYPE, True), + ('albumartist_sort', types.STRING_TYPE, True), + ('albumartist_credit', types.STRING_TYPE, True), + ('album', types.STRING_TYPE, True), + ('genre', types.STRING_TYPE, True), + ('year', types.PaddedInt(4), True), + ('month', types.PaddedInt(2), True), + ('day', types.PaddedInt(2), True), + ('tracktotal', types.PaddedInt(2), True), + ('disctotal', types.PaddedInt(2), True), + ('comp', types.BOOL_TYPE, True), + ('mb_albumid', types.STRING_TYPE, True), + ('mb_albumartistid', types.STRING_TYPE, True), + ('albumtype', types.STRING_TYPE, True), + ('label', types.STRING_TYPE, True), + ('mb_releasegroupid', types.STRING_TYPE, True), + ('asin', types.STRING_TYPE, True), + ('catalognum', types.STRING_TYPE, True), + ('script', types.STRING_TYPE, True), + ('language', types.STRING_TYPE, True), + ('country', types.STRING_TYPE, True), + ('albumstatus', types.STRING_TYPE, True), + ('media', types.STRING_TYPE, True), + ('albumdisambig', types.STRING_TYPE, True), + ('rg_album_gain', types.FLOAT_TYPE, True), + ('rg_album_peak', types.FLOAT_TYPE, True), + ('original_year', types.PaddedInt(4), True), + ('original_month', types.PaddedInt(2), True), + ('original_day', types.PaddedInt(2), True), ] ALBUM_KEYS = [f[0] for f in ALBUM_FIELDS] ALBUM_KEYS_ITEM = [f[0] for f in ALBUM_FIELDS if f[2]]