mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
PEP8
--HG-- extra : convert_revision : svn%3A41726ec3-264d-0410-9c23-a9f1637257cc/trunk%40133
This commit is contained in:
parent
e52f0aaaf1
commit
1cdf13ea8d
4 changed files with 107 additions and 78 deletions
102
beets/library.py
102
beets/library.py
|
|
@ -1,7 +1,11 @@
|
|||
import sqlite3, os, sys, operator, re, shutil
|
||||
from beets.mediafile import MediaFile, FileTypeError
|
||||
import sqlite3
|
||||
import os
|
||||
import operator
|
||||
import re
|
||||
import shutil
|
||||
from string import Template
|
||||
import logging
|
||||
from beets.mediafile import MediaFile, FileTypeError
|
||||
|
||||
# Fields in the "items" table; all the metadata available for items in the
|
||||
# library. These are used directly in SQL; they are vulnerable to injection if
|
||||
|
|
@ -46,7 +50,6 @@ library_options = {
|
|||
}
|
||||
|
||||
# Logger.
|
||||
|
||||
log = logging.getLogger('beets')
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.addHandler(logging.StreamHandler())
|
||||
|
|
@ -64,12 +67,14 @@ class InvalidFieldError(Exception):
|
|||
|
||||
def _normpath(path):
|
||||
"""Provide the canonical form of the path suitable for storing in the
|
||||
database."""
|
||||
database.
|
||||
"""
|
||||
return os.path.normpath(os.path.abspath(os.path.expanduser(path)))
|
||||
|
||||
def _ancestry(path):
|
||||
"""Return a list consisting of path's parent directory, its grandparent,
|
||||
and so on. For instance, _ancestry('/a/b/c') == ['/', '/a', '/a/b']."""
|
||||
and so on. For instance, _ancestry('/a/b/c') == ['/', '/a', '/a/b'].
|
||||
"""
|
||||
out = []
|
||||
while path and path != '/':
|
||||
path = os.path.dirname(path)
|
||||
|
|
@ -80,7 +85,8 @@ def _ancestry(path):
|
|||
def _walk_files(path):
|
||||
"""Like os.walk, but only yields the files in the directory tree. The full
|
||||
pathnames to the files (under path) are given. Also, if path is a file,
|
||||
_walk_files just yields that."""
|
||||
_walk_files just yields that.
|
||||
"""
|
||||
if os.path.isfile(path):
|
||||
yield path
|
||||
else:
|
||||
|
|
@ -124,8 +130,8 @@ class Item(object):
|
|||
def __getattr__(self, key):
|
||||
"""If key is an item attribute (i.e., a column in the database),
|
||||
returns the record entry for that key. Otherwise, performs an ordinary
|
||||
getattr."""
|
||||
|
||||
getattr.
|
||||
"""
|
||||
if key in item_keys:
|
||||
return self.record[key]
|
||||
else:
|
||||
|
|
@ -137,8 +143,8 @@ class Item(object):
|
|||
attribute in the database or in the file's tags, one must call store()
|
||||
or write().
|
||||
|
||||
Otherwise, performs an ordinary setattr."""
|
||||
|
||||
Otherwise, performs an ordinary setattr.
|
||||
"""
|
||||
if key in item_keys:
|
||||
if (not (key in self.record)) or (self.record[key] != value):
|
||||
# don't dirty if value unchanged
|
||||
|
|
@ -152,8 +158,8 @@ class Item(object):
|
|||
|
||||
def load(self, load_id=None):
|
||||
"""Refresh the item's metadata from the library database. If fetch_id
|
||||
is not specified, use the current item's id."""
|
||||
|
||||
is not specified, use the current item's id.
|
||||
"""
|
||||
if not self.library:
|
||||
raise LibraryError('no library to load from')
|
||||
|
||||
|
|
@ -169,8 +175,8 @@ class Item(object):
|
|||
def store(self, store_id=None, store_all=False):
|
||||
"""Save the item's metadata into the library database. If store_id is
|
||||
specified, use it instead of the item's current id. If store_all is
|
||||
true, save the entire record instead of just the dirty fields."""
|
||||
|
||||
true, save the entire record instead of just the dirty fields.
|
||||
"""
|
||||
if not self.library:
|
||||
raise LibraryError('no library to store to')
|
||||
|
||||
|
|
@ -201,8 +207,8 @@ class Item(object):
|
|||
def add(self, library=None):
|
||||
"""Add the item as a new object to the library database. The id field
|
||||
will be updated; the new id is returned. If library is specified, set
|
||||
the item's library before adding."""
|
||||
|
||||
the item's library before adding.
|
||||
"""
|
||||
if library:
|
||||
self.library = library
|
||||
if not self.library:
|
||||
|
|
@ -228,8 +234,8 @@ class Item(object):
|
|||
return new_id
|
||||
|
||||
def remove(self):
|
||||
"""Removes the item from the database (leaving the file on disk)."""
|
||||
|
||||
"""Removes the item from the database (leaving the file on disk).
|
||||
"""
|
||||
self.library.conn.execute('delete from items where id=?',
|
||||
(self.id,) )
|
||||
|
||||
|
|
@ -238,8 +244,8 @@ class Item(object):
|
|||
|
||||
def read(self, read_path=None):
|
||||
"""Read the metadata from the associated file. If read_path is
|
||||
specified, read metadata from that file instead."""
|
||||
|
||||
specified, read metadata from that file instead.
|
||||
"""
|
||||
if read_path is None:
|
||||
read_path = self.path
|
||||
f = MediaFile(read_path)
|
||||
|
|
@ -249,7 +255,8 @@ class Item(object):
|
|||
self.path = read_path
|
||||
|
||||
def write(self):
|
||||
"""Writes the item's metadata to the associated file."""
|
||||
"""Writes the item's metadata to the associated file.
|
||||
"""
|
||||
f = MediaFile(self.path)
|
||||
for key in metadata_rw_keys:
|
||||
setattr(f, key, getattr(self, key))
|
||||
|
|
@ -260,8 +267,8 @@ class Item(object):
|
|||
|
||||
def destination(self):
|
||||
"""Returns the path within the library directory designated for this
|
||||
item (i.e., where the file ought to be)."""
|
||||
|
||||
item (i.e., where the file ought to be).
|
||||
"""
|
||||
libpath = self.library.options['directory']
|
||||
subpath_tmpl = Template(self.library.options['path_format'])
|
||||
|
||||
|
|
@ -302,8 +309,8 @@ class Item(object):
|
|||
moving/copying fails.
|
||||
|
||||
Note that one should almost certainly call store() and library.save()
|
||||
after this method in order to keep on-disk data consistent."""
|
||||
|
||||
after this method in order to keep on-disk data consistent.
|
||||
"""
|
||||
dest = self.destination()
|
||||
|
||||
# Create necessary ancestry for the move. Like os.renames but only
|
||||
|
|
@ -326,8 +333,8 @@ class Item(object):
|
|||
Also calls remove(), deleting the appropriate row from the database.
|
||||
|
||||
As with move(), library.save() should almost certainly be called after
|
||||
invoking this (although store() should not)."""
|
||||
|
||||
invoking this (although store() should not).
|
||||
"""
|
||||
os.unlink(self.path)
|
||||
self.remove()
|
||||
|
||||
|
|
@ -335,7 +342,8 @@ class Item(object):
|
|||
def from_path(cls, path, library=None):
|
||||
"""Creates a new item from the media file at the specified path. Sets
|
||||
the item's library (but does not add the item) if library is
|
||||
specified."""
|
||||
specified.
|
||||
"""
|
||||
i = cls({})
|
||||
i.read(path)
|
||||
i.library = library
|
||||
|
|
@ -353,19 +361,22 @@ class Query(object):
|
|||
def clause(self):
|
||||
"""Returns (clause, subvals) where clause is a valid sqlite WHERE
|
||||
clause implementing the query and subvals is a list of items to be
|
||||
substituted for ?s in the clause."""
|
||||
substituted for ?s in the clause.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def statement(self, columns='*'):
|
||||
"""Returns (query, subvals) where clause is a sqlite SELECT statement
|
||||
to enact this query and subvals is a list of values to substitute in
|
||||
for ?s in the query."""
|
||||
for ?s in the query.
|
||||
"""
|
||||
clause, subvals = self.clause()
|
||||
return ('select ' + columns + ' from items where ' + clause, subvals)
|
||||
|
||||
def execute(self, library):
|
||||
"""Runs the query in the specified library, returning a
|
||||
ResultIterator."""
|
||||
ResultIterator.
|
||||
"""
|
||||
c = library.conn.cursor()
|
||||
c.execute(*self.statement())
|
||||
return ResultIterator(c, library)
|
||||
|
|
@ -399,7 +410,8 @@ class SubstringQuery(FieldQuery):
|
|||
|
||||
class CollectionQuery(Query):
|
||||
"""An abstract query class that aggregates other queries. Can be indexed
|
||||
like a list to access the sub-queries."""
|
||||
like a list to access the sub-queries.
|
||||
"""
|
||||
|
||||
def __init__(self, subqueries = ()):
|
||||
self.subqueries = subqueries
|
||||
|
|
@ -412,7 +424,8 @@ class CollectionQuery(Query):
|
|||
|
||||
def clause_with_joiner(self, joiner):
|
||||
"""Returns a clause created by joining together the clauses of all
|
||||
subqueries with the string joiner (padded by spaces)."""
|
||||
subqueries with the string joiner (padded by spaces).
|
||||
"""
|
||||
clause_parts = []
|
||||
subvals = []
|
||||
for subq in self.subqueries:
|
||||
|
|
@ -425,7 +438,8 @@ class CollectionQuery(Query):
|
|||
@classmethod
|
||||
def from_dict(cls, matches):
|
||||
"""Construct a query from a dictionary, matches, whose keys are item
|
||||
field names and whose values are substring patterns."""
|
||||
field names and whose values are substring patterns.
|
||||
"""
|
||||
subqueries = []
|
||||
for key, pattern in matches.iteritems():
|
||||
subqueries.append(SubstringQuery(key, pattern))
|
||||
|
|
@ -489,7 +503,8 @@ class AnySubstringQuery(CollectionQuery):
|
|||
|
||||
class MutableCollectionQuery(CollectionQuery):
|
||||
"""A collection query whose subqueries may be modified after the query is
|
||||
initialized."""
|
||||
initialized.
|
||||
"""
|
||||
def __setitem__(self, key, value): self.subqueries[key] = value
|
||||
def __delitem__(self, key): del self.subqueries[key]
|
||||
|
||||
|
|
@ -514,7 +529,8 @@ class ResultIterator(object):
|
|||
|
||||
def count(self):
|
||||
"""Returns the number of matched rows and invalidates the
|
||||
iterator."""
|
||||
iterator.
|
||||
"""
|
||||
# Apparently, there is no good way to get the number of rows
|
||||
# returned by an sqlite SELECT.
|
||||
num = 0
|
||||
|
|
@ -547,7 +563,7 @@ class Library(object):
|
|||
self._setup()
|
||||
|
||||
def _setup(self):
|
||||
"Set up the schema of the library file."
|
||||
"""Set up the schema of the library file."""
|
||||
|
||||
# options (library data) table
|
||||
setup_sql = """
|
||||
|
|
@ -558,7 +574,7 @@ class Library(object):
|
|||
|
||||
# items (things in the library) table
|
||||
setup_sql += 'create table if not exists items ('
|
||||
setup_sql += ', '.join(map(' '.join, item_fields))
|
||||
setup_sql += ', '.join([' '.join(f) for f in item_fields])
|
||||
setup_sql += ');'
|
||||
|
||||
self.conn.executescript(setup_sql)
|
||||
|
|
@ -603,7 +619,8 @@ class Library(object):
|
|||
def add(self, path, copy=False):
|
||||
"""Add a file to the library or recursively search a directory and add
|
||||
all its contents. If copy is True, copy files to their destination in
|
||||
the library directory while adding."""
|
||||
the library directory while adding.
|
||||
"""
|
||||
|
||||
for f in _walk_files(path):
|
||||
try:
|
||||
|
|
@ -616,7 +633,8 @@ class Library(object):
|
|||
|
||||
def get(self, query=None):
|
||||
"""Returns a ResultIterator to the items matching query, which may be
|
||||
None (match the entire library), a Query object, or a query string."""
|
||||
None (match the entire library), a Query object, or a query string.
|
||||
"""
|
||||
if query is None:
|
||||
query = TrueQuery()
|
||||
elif isinstance(query, str) or isinstance(query, unicode):
|
||||
|
|
@ -626,5 +644,7 @@ class Library(object):
|
|||
return query.execute(self)
|
||||
|
||||
def save(self):
|
||||
"""Writes the library to disk (completing a sqlite transaction)."""
|
||||
"""Writes the library to disk (completing a sqlite transaction).
|
||||
"""
|
||||
self.conn.commit()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,17 @@ automatically detect file types and provide a unified interface for a useful
|
|||
subset of music files' tags.
|
||||
|
||||
Usage:
|
||||
>>> f = MediaFile('Lucy.mp3')
|
||||
>>> f.title
|
||||
u'Lucy in the Sky with Diamonds'
|
||||
>>> f.artist = 'The Beatles'
|
||||
>>> f.save()
|
||||
|
||||
>>> f = MediaFile('Lucy.mp3')
|
||||
>>> f.title
|
||||
u'Lucy in the Sky with Diamonds'
|
||||
>>> f.artist = 'The Beatles'
|
||||
>>> f.save()
|
||||
|
||||
A field will always return a reasonable value of the correct type, even if no
|
||||
tag is present. If no value is available, the value will be false (e.g., zero
|
||||
or the empty string)."""
|
||||
or the empty string).
|
||||
"""
|
||||
|
||||
import mutagen
|
||||
import datetime
|
||||
|
|
@ -42,7 +44,8 @@ packing = Enumeration('SLASHED', # pair delimited by /
|
|||
|
||||
class StorageStyle(object):
|
||||
"""Parameterizes the storage behavior of a single field for a certain tag
|
||||
format."""
|
||||
format.
|
||||
"""
|
||||
def __init__(self,
|
||||
# The Mutagen key used to access the data for this field.
|
||||
key,
|
||||
|
|
@ -73,14 +76,16 @@ class StorageStyle(object):
|
|||
|
||||
class Packed(object):
|
||||
"""Makes a packed list of values subscriptable. To access the packed output
|
||||
after making changes, use packed_thing.items."""
|
||||
after making changes, use packed_thing.items.
|
||||
"""
|
||||
|
||||
def __init__(self, items, packstyle, none_val=0, out_type=int):
|
||||
"""Create a Packed object for subscripting the packed values in items.
|
||||
The items are packed using packstyle, which is a value from the
|
||||
packing enum. none_val is returned from a request when no suitable
|
||||
value is found in the items. Vales are converted to out_type before
|
||||
they are returned."""
|
||||
they are returned.
|
||||
"""
|
||||
self.items = items
|
||||
self.packstyle = packstyle
|
||||
self.none_val = none_val
|
||||
|
|
@ -160,7 +165,8 @@ class MediaField(object):
|
|||
"""A descriptor providing access to a particular (abstract) metadata
|
||||
field. out_type is the type that users of MediaFile should see and can
|
||||
be unicode, int, or bool. id3, mp4, and flac are StorageStyle instances
|
||||
parameterizing the field's storage for each type."""
|
||||
parameterizing the field's storage for each type.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
# The field's semantic (exterior) type.
|
||||
|
|
@ -180,7 +186,8 @@ class MediaField(object):
|
|||
def _fetchdata(self, obj):
|
||||
"""Get the value associated with this descriptor's key (and id3_desc if
|
||||
present) from the mutagen tag dict. Unwraps from a list if
|
||||
necessary."""
|
||||
necessary.
|
||||
"""
|
||||
style = self._style(obj)
|
||||
|
||||
try:
|
||||
|
|
@ -210,7 +217,8 @@ class MediaField(object):
|
|||
|
||||
def _storedata(self, obj, val):
|
||||
"""Store val for this descriptor's key in the tag dictionary. Store it
|
||||
as a single-item list if necessary. Uses id3_desc if present."""
|
||||
as a single-item list if necessary. Uses id3_desc if present.
|
||||
"""
|
||||
style = self._style(obj)
|
||||
|
||||
# wrap as a list if necessary
|
||||
|
|
@ -244,7 +252,8 @@ class MediaField(object):
|
|||
def _style(self, obj): return self.styles[obj.type]
|
||||
|
||||
def __get__(self, obj, owner):
|
||||
"""Retrieve the value of this metadata field."""
|
||||
"""Retrieve the value of this metadata field.
|
||||
"""
|
||||
style = self._style(obj)
|
||||
out = self._fetchdata(obj)
|
||||
|
||||
|
|
@ -274,7 +283,8 @@ class MediaField(object):
|
|||
return out
|
||||
|
||||
def __set__(self, obj, val):
|
||||
"""Set the value of this metadata field."""
|
||||
"""Set the value of this metadata field.
|
||||
"""
|
||||
style = self._style(obj)
|
||||
|
||||
if style.packing:
|
||||
|
|
@ -317,27 +327,25 @@ class MediaField(object):
|
|||
self._storedata(obj, out)
|
||||
|
||||
class CompositeDateField(object):
|
||||
"""
|
||||
A MediaFile field for conveniently accessing the year, month, and day fields
|
||||
as a datetime.date object. Allows both getting and setting of the component
|
||||
fields.
|
||||
"""A MediaFile field for conveniently accessing the year, month, and day
|
||||
fields as a datetime.date object. Allows both getting and setting of the
|
||||
component fields.
|
||||
"""
|
||||
def __init__(self, year_field, month_field, day_field):
|
||||
"""
|
||||
Create a new date field from the indicated MediaFields for the component
|
||||
values.
|
||||
"""Create a new date field from the indicated MediaFields for the
|
||||
component values.
|
||||
"""
|
||||
self.year_field = year_field
|
||||
self.month_field = month_field
|
||||
self.day_field = day_field
|
||||
|
||||
def __get__(self, obj, owner):
|
||||
"""
|
||||
Return a datetime.date object whose components indicating the smallest
|
||||
valid date whose components are at least as large as the three component
|
||||
fields (that is, if year == 1999, month == 0, and day == 0, then
|
||||
date == datetime.date(1999, 1, 1)). If the components indicate an
|
||||
invalid date (e.g., if month == 47), datetime.date.min is returned.
|
||||
"""Return a datetime.date object whose components indicating the
|
||||
smallest valid date whose components are at least as large as the
|
||||
three component fields (that is, if year == 1999, month == 0, and
|
||||
day == 0, then date == datetime.date(1999, 1, 1)). If the components
|
||||
indicate an invalid date (e.g., if month == 47), datetime.date.min is
|
||||
returned.
|
||||
"""
|
||||
try:
|
||||
return datetime.date(max(self.year_field.__get__(obj, owner),
|
||||
|
|
@ -349,8 +357,7 @@ class CompositeDateField(object):
|
|||
return datetime.date.min
|
||||
|
||||
def __set__(self, obj, val):
|
||||
"""
|
||||
Set the year, month, and day fields to match the components of the
|
||||
"""Set the year, month, and day fields to match the components of the
|
||||
provided datetime.date object.
|
||||
"""
|
||||
self.year_field.__set__(obj, val.year)
|
||||
|
|
@ -361,7 +368,8 @@ class CompositeDateField(object):
|
|||
|
||||
class MediaFile(object):
|
||||
"""Represents a multimedia file on disk and provides access to its
|
||||
metadata."""
|
||||
metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, path):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -627,7 +627,7 @@ class BGServer(Server):
|
|||
"""
|
||||
|
||||
def __init__(self, library, host='127.0.0.1', port=DEFAULT_PORT):
|
||||
import gstplayer
|
||||
import beets.player.gstplayer
|
||||
super(BGServer, self).__init__(host, port)
|
||||
self.lib = library
|
||||
self.player = gstplayer.GstPlayer(self.play_finished)
|
||||
|
|
|
|||
|
|
@ -87,9 +87,9 @@ def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
|
|||
if readfield=='date' and field in ('year', 'month', 'day'):
|
||||
try:
|
||||
correct = datetime.date(
|
||||
self.value if field=='year' else correct.year,
|
||||
self.value if field=='month' else correct.month,
|
||||
self.value if field=='day' else correct.day
|
||||
self.value if field=='year' else correct.year,
|
||||
self.value if field=='month' else correct.month,
|
||||
self.value if field=='day' else correct.day
|
||||
)
|
||||
except ValueError:
|
||||
correct = datetime.date.min
|
||||
|
|
@ -98,9 +98,10 @@ def MakeWritingTest(path, correct_dict, field, testsuffix='_test'):
|
|||
correct = getattr(self.value, readfield)
|
||||
|
||||
self.assertEqual(got, correct,
|
||||
readfield + ' changed when it should not have (expected'
|
||||
' ' + repr(correct) + ', got ' + repr(got) + ') when '
|
||||
'modifying ' + field + ' in ' + os.path.basename(path))
|
||||
readfield + ' changed when it should not have'
|
||||
' (expected ' + repr(correct) + ', got ' + \
|
||||
repr(got) + ') when modifying ' + field + ' in ' + \
|
||||
os.path.basename(path))
|
||||
|
||||
def tearDown(self):
|
||||
os.remove(self.tpath)
|
||||
|
|
|
|||
Loading…
Reference in a new issue