Improve docstrings

Signed-off-by: JoeLametta <JoeLametta@users.noreply.github.com>
This commit is contained in:
JoeLametta
2019-03-20 21:12:56 +00:00
parent 3b269e7a3b
commit e56c636fd3
26 changed files with 583 additions and 488 deletions

View File

@@ -43,8 +43,7 @@ class EntryNotFound(Exception):
class _AccurateRipResponse:
"""
An AccurateRip response contains a collection of metadata identifying a
particular digital audio compact disc.
An AR resp. contains a collection of metadata identifying a specific disc.
For disc level metadata it contains the track count, two internal disc
IDs, and the CDDB disc ID.
@@ -55,9 +54,12 @@ class _AccurateRipResponse:
The response is stored as a packed binary structure.
"""
def __init__(self, data):
"""
The checksums and confidences arrays are indexed by relative track
Init _AccurateRipResponse.
Checksums and confidences arrays are indexed by relative track
position, so track 1 will have array index 0, track 2 will have array
index 1, and so forth. HTOA and other hidden tracks are not included.
"""
@@ -98,12 +100,14 @@ def _split_responses(raw_entry):
def calculate_checksums(track_paths):
"""
Return ARv1 and ARv2 checksums as two arrays of character strings in a
dictionary: {'v1': ['deadbeef', ...], 'v2': [...]}
Return None instead of checksum string for unchecksummable tracks.
Calculate AccurateRip checksums for the given tracks.
HTOA checksums are not included in the database and are not calculated.
:returns: ARv1 and ARv2 checksums as two arrays of character strings in a
dictionary: ``{'v1': ['deadbeef', ...], 'v2': [...]}``
or None instead of checksum string for unchecksummable tracks.
:rtype: dict(string, list()) or None
"""
track_count = len(track_paths)
v1_checksums = []
@@ -152,9 +156,10 @@ def _save_entry(raw_entry, path):
def get_db_entry(path):
"""
Retrieve cached AccurateRip disc entry as array of _AccurateRipResponses.
Downloads entry from accuraterip.com on cache fault.
`path' is in the format of the output of table.accuraterip_path().
``path`` is in the format of the output of ``table.accuraterip_path()``.
"""
cached_path = join(_CACHE_DIR, path)
if exists(cached_path):
@@ -183,11 +188,11 @@ def _assign_checksums_and_confidences(tracks, checksums, responses):
def _match_responses(tracks, responses):
"""
Match and save track accuraterip response checksums against
all non-hidden tracks.
Match and save track AR response checksums against all non-hidden tracks.
Returns True if every track has a match for every entry for either
AccurateRip version.
:returns: True if every track has a match for every entry for either
AccurateRip version, False otherwise.
:rtype: bool
"""
for r in responses:
for i, track in enumerate(tracks):
@@ -211,7 +216,8 @@ def _match_responses(tracks, responses):
def verify_result(result, responses, checksums):
"""
Verify track AccurateRip checksums against database responses.
Stores track checksums and database values on result.
Store track checksums and database values on result.
"""
if not (result and responses and checksums):
return False
@@ -226,9 +232,7 @@ def verify_result(result, responses, checksums):
def print_report(result):
"""
Print AccurateRip verification results.
"""
"""Print AccurateRip verification results."""
for _, track in enumerate(result.tracks):
status = 'rip NOT accurate'
conf = '(not found)'

View File

@@ -45,6 +45,7 @@ class Persister:
def __init__(self, path=None, default=None):
"""
If path is not given, the object will not be persisted.
This allows code to transparently deal with both persisted and
non-persisted objects, since the persist method will just end up
doing nothing.
@@ -56,8 +57,7 @@ class Persister:
def persist(self, obj=None):
"""
Persist the given object, if we have a persistence path and the
object changed.
Persist the given obj if we have a persist. path and the obj changed.
If object is not given, re-persist our object, always.
If object is given, only persist if it was changed.
@@ -115,9 +115,7 @@ class Persister:
class PersistedCache:
"""
I wrap a directory of persisted objects.
"""
"""Wrap a directory of persisted objects."""
path = None
@@ -129,9 +127,7 @@ class PersistedCache:
return os.path.join(self.path, '%s.pickle' % key)
def get(self, key):
"""
Returns the persister for the given key.
"""
"""Return the persister for the given key."""
persister = Persister(self._getPath(key))
if persister.object:
if hasattr(persister.object, 'instanceVersion'):
@@ -154,8 +150,9 @@ class ResultCache:
def getRipResult(self, cddbdiscid, create=True):
"""
Retrieve the persistable RipResult either from our cache (from a
previous, possibly aborted rip), or return a new one.
Get the persistable RipResult either from our cache or ret. a new one.
The cached RipResult may come from an aborted rip.
:rtype: :any:`Persistable` for :any:`result.RipResult`
"""
@@ -183,9 +180,8 @@ class ResultCache:
class TableCache:
"""
I read and write entries to and from the cache of tables.
Read and write entries to and from the cache of tables.
If no path is specified, the cache will write to the current cache
directory and read from all possible cache directories (to allow for

View File

@@ -39,14 +39,14 @@ BYTES_PER_FRAME = SAMPLES_PER_FRAME * 4
class EjectError(SystemError):
"""
Possibly ejects the drive in command.main.
"""
"""Possibly eject the drive in command.main."""
def __init__(self, device, *args):
"""
args is a tuple used by BaseException.__str__
device is the device path to eject
Init EjectError.
:param args: a tuple used by ``BaseException.__str__``
:param device: device path to eject
"""
self.args = args
self.device = device
@@ -54,13 +54,12 @@ class EjectError(SystemError):
def msfToFrames(msf):
"""
Converts a string value in MM:SS:FF to frames.
Convert a string value in MM:SS:FF to frames.
:param msf: the MM:SS:FF value to convert
:type msf: str
:rtype: int
:type msf: str
:returns: number of frames
:rtype: int
"""
if ':' not in msf:
return int(msf)
@@ -97,21 +96,19 @@ def framesToHMSF(frames):
def formatTime(seconds, fractional=3):
"""
Nicely format time in a human-readable format, like
HH:MM:SS.mmm
Nicely format time in a human-readable format, like HH:MM:SS.mmm.
If fractional is zero, no seconds will be shown.
If it is greater than 0, we will show seconds and fractions of seconds.
As a side consequence, there is no way to show seconds without fractions.
:param seconds: the time in seconds to format.
:type seconds: int or float
:param seconds: the time in seconds to format
:type seconds: int or float
:param fractional: how many digits to show for the fractional part of
seconds.
:type fractional: int
seconds
:type fractional: int
:returns: a nicely formatted time string
:rtype: string
:returns: a nicely formatted time string.
"""
chunks = []
@@ -149,16 +146,13 @@ class EmptyError(Exception):
class MissingFrames(Exception):
"""
Less frames decoded than expected.
"""
"""Less frames decoded than expected."""
pass
def truncate_filename(path):
"""
Truncate filename to the max. len. allowed by the path's filesystem
"""
"""Truncate filename to the max. len. allowed by the path's filesystem."""
p, f = os.path.split(os.path.normpath(path))
f, e = os.path.splitext(f)
# Get the filename length limit in bytes
@@ -172,7 +166,8 @@ def truncate_filename(path):
def shrinkPath(path):
"""
Shrink a full path to a shorter version.
Used to handle ENAMETOOLONG
Used to handle ``ENAMETOOLONG``.
"""
parts = list(os.path.split(path))
length = len(parts[-1])
@@ -204,14 +199,15 @@ def shrinkPath(path):
def getRealPath(refPath, filePath):
"""
Translate a .cue or .toc's FILE argument to an existing path.
Does Windows path translation.
Will look for the given file name, but with .flac and .wav as extensions.
:param refPath: path to the file from which the track is referenced;
for example, path to the .cue file in the same directory
:type refPath: str
:type filePath: str
:param refPath: path to the file from which the track is referenced;
for example, path to the .cue file in the same directory
:type refPath: str
:type filePath: str
"""
assert isinstance(filePath, str), "%r is not str" % filePath
@@ -258,10 +254,9 @@ def getRealPath(refPath, filePath):
def getRelativePath(targetPath, collectionPath):
"""
Get a relative path from the directory of collectionPath to
targetPath.
Get a relative path from the directory of collectionPath to targetPath.
Used to determine the path to use in .cue/.m3u files
Used to determine the path to use in .cue/.m3u files.
"""
logger.debug('getRelativePath: target %r, collection %r',
targetPath, collectionPath)
@@ -280,9 +275,7 @@ def getRelativePath(targetPath, collectionPath):
def validate_template(template, kind):
"""
Raise exception if disc/track template includes invalid variables
"""
"""Raise exception if disc/track template includes invalid variables."""
if kind == 'disc':
matches = re.findall(r'%[^ARSXdrxy]', template)
elif kind == 'track':
@@ -294,20 +287,22 @@ def validate_template(template, kind):
class VersionGetter:
"""
I get the version of a program by looking for it in command output
according to a regexp.
Get the version of a program.
It is extracted by looking for it in command output according to a RegEX.
"""
def __init__(self, dependency, args, regexp, expander):
"""
:param dependency: name of the dependency providing the program
:param args: the arguments to invoke to show the version
:type args: list of str
:param regexp: the regular expression to get the version
:param expander: the expansion string for the version using the
regexp group dict
"""
Init VersionGetter.
:param dependency: name of the dependency providing the program
:param args: the arguments to invoke to show the version
:type args: list(str)
:param regexp: the regular expression to get the version
:param expander: the expansion string for the version using the
regexp group dict
"""
self._dep = dependency
self._args = args
self._regexp = regexp

View File

@@ -96,9 +96,7 @@ class Config:
self.write()
def getReadOffset(self, vendor, model, release):
"""
Get a read offset for the given drive.
"""
"""Get a read offset for the given drive."""
section = self._findDriveSection(vendor, model, release)
try:

View File

@@ -18,9 +18,7 @@
# You should have received a copy of the GNU General Public License
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
"""
Handles communication with the MusicBrainz server using NGS.
"""
"""Handle communication with the MusicBrainz server using NGS."""
from urllib.error import HTTPError
import whipper
@@ -62,16 +60,19 @@ class TrackMetadata:
class DiscMetadata:
"""
:param artist: artist(s) name
:param sortName: release artist sort name
:param release: earliest release date, in YYYY-MM-DD
:type release: str
:param title: title of the disc (with disambiguation)
:param releaseTitle: title of the release (without disambiguation)
:type tracks: list of :any:`TrackMetadata`
:param countries: MusicBrainz release countries
:type countries: list or None
Represent the disc metadata.
:cvar artist: artist(s) name
:cvar sortName: release artist sort name
:cvar release: earliest release date, in YYYY-MM-DD
:vartype release: str
:cvar title: title of the disc (with disambiguation)
:cvar releaseTitle: title of the release (without disambiguation)
:vartype tracks: list of :any:`TrackMetadata`
:cvar countries: MusicBrainz release countries
:vartype countries: list or None
"""
artist = None
sortName = None
title = None
@@ -123,10 +124,7 @@ def _record(record, which, name, what):
class _Credit(list):
"""
I am a representation of an artist-credit in MusicBrainz for a disc
or track.
"""
"""Represent an artist-credit in MusicBrainz for a disc or track."""
def joiner(self, attributeGetter, joinString=None):
res = []
@@ -217,10 +215,11 @@ def _getPerformers(recording):
def _getMetadata(release, discid=None, country=None):
"""
:type release: dict
Get disc metadata based upon the provided release id.
:param release: a release dict as returned in the value for key release
from get_release_by_id
:type release: dict
:rtype: DiscMetadata or None
"""
logger.debug('getMetadata for release id %r', release['id'])
@@ -378,14 +377,16 @@ def getReleaseMetadata(release_id, discid=None, country=None, record=False):
def musicbrainz(discid, country=None, record=False):
"""
Based on a MusicBrainz disc id, get a list of DiscMetadata objects
for the given disc id.
Get a list of DiscMetadata objects for the given MusicBrainz disc id.
Example disc id: Mj48G109whzEmAbPBoGvd4KyCS4-
:type discid: str
Example disc id: ``Mj48G109whzEmAbPBoGvd4KyCS4-``
:type discid: str
:rtype: list of :any:`DiscMetadata`
:param country: country name used to filter releases by provenance
:type country: str
:param record: whether to record to disc as a JSON serialization
:type record: bool
"""
logger.debug('looking up results for discid %r', discid)

View File

@@ -22,16 +22,20 @@ import re
class PathFilter:
"""
I filter path components for safe storage on file systems.
"""
"""Filter path components for safe storage on file systems."""
def __init__(self, slashes=True, quotes=True, fat=True, special=False):
"""
Init PathFilter.
:param slashes: whether to convert slashes to dashes
:param quotes: whether to normalize quotes
:param fat: whether to strip characters illegal on FAT filesystems
:type slashes: bool
:param quotes: whether to normalize quotes
:type quotes: bool
:param fat: whether to strip characters illegal on FAT filesystems
:type fat: bool
:param special: whether to strip special characters
:type special: bool
"""
self._slashes = slashes
self._quotes = quotes

View File

@@ -18,9 +18,7 @@
# You should have received a copy of the GNU General Public License
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
"""
Common functionality and class for all programs using whipper.
"""
"""Common functionality and class for all programs using whipper."""
import musicbrainzngs
import re
@@ -47,10 +45,10 @@ class Program:
I maintain program state and functionality.
:vartype metadata: mbngs.DiscMetadata
:cvar result: the rip's result
:vartype result: result.RipResult
:vartype outdir: str
:vartype config: whipper.common.config.Config
:cvar result: the rip's result
:vartype result: result.RipResult
:vartype outdir: str
:vartype config: whipper.common.config.Config
"""
cuePath = None
@@ -61,7 +59,9 @@ class Program:
def __init__(self, config, record=False):
"""
:param record: whether to record results of API calls for playback.
Init Program.
:param record: whether to record results of API calls for playback
"""
self._record = record
self._cache = cache.ResultCache()
@@ -89,7 +89,9 @@ class Program:
os.chdir(workingDirectory)
def getFastToc(self, runner, device):
"""Retrieve the normal TOC table from the drive.
"""
Retrieve the normal TOC table from the drive.
Also warn about buggy cdrdao versions.
"""
from pkg_resources import parse_version as V
@@ -150,8 +152,9 @@ class Program:
def getRipResult(self, cddbdiscid):
"""
Retrieve the persistable RipResult either from our cache (from a
previous, possibly aborted rip), or return a new one.
Get the persistable RipResult either from our cache or ret. a new one.
The cached RipResult may come from an aborted rip.
:rtype: result.RipResult
"""
@@ -176,28 +179,31 @@ class Program:
def getPath(self, outdir, template, mbdiscid, metadata, track_number=None):
"""
Return disc or track path relative to outdir according to
template. Track paths do not include extension.
Return disc or track path relative to outdir according to template.
Track paths do not include extension.
Tracks are named according to the track template, filling in
the variables and adding the file extension. Variables
exclusive to the track template are:
- %t: track number
- %a: track artist
- %n: track title
- %s: track sort name
* ``%t``: track number
* ``%a``: track artist
* ``%n``: track title
* ``%s``: track sort name
Disc files (.cue, .log, .m3u) are named according to the disc
template, filling in the variables and adding the file
extension. Variables for both disc and track template are:
- %A: release artist
- %S: release artist sort name
- %d: disc title
- %y: release year
- %r: release type, lowercase
- %R: release type, normal case
- %x: audio extension, lowercase
- %X: audio extension, uppercase
* ``%A``: release artist
* ``%S``: release artist sort name
* ``%d``: disc title
* ``%y``: release year
* ``%r``: release type, lowercase
* ``%R``: release type, normal case
* ``%x``: audio extension, lowercase
* ``%X``: audio extension, uppercase
"""
assert isinstance(outdir, str), "%r is not str" % outdir
assert isinstance(template, str), "%r is not str" % template
@@ -247,8 +253,9 @@ class Program:
@staticmethod
def getCDDB(cddbdiscid):
"""
:param cddbdiscid: list of id, tracks, offsets, seconds
Fetch basic metadata from freedb's CDDB.
:param cddbdiscid: list of id, tracks, offsets, seconds
:rtype: str
"""
# FIXME: convert to nonblocking?
@@ -272,7 +279,20 @@ class Program:
def getMusicBrainz(self, ittoc, mbdiscid, release=None, country=None,
prompt=False):
"""
:type ittoc: whipper.image.table.Table
Fetch MusicBrainz's metadata for the given MusicBrainz disc id.
:param ittoc: disc TOC
:type ittoc: whipper.image.table.Table
:param mbdiscid: MusicBrainz DiscID
:type mbdiscid: str
:param release: MusicBrainz release id to match to
(if there are multiple)
:type release: str or None
:param country: country name used to filter releases by provenance
:type country: str or None
:param prompt: whether to prompt if there are multiple
matching releases
:type prompt: bool
"""
# look up disc on MusicBrainz
print('Disc duration: %s, %d audio tracks' % (
@@ -393,9 +413,10 @@ class Program:
"""
Based on the metadata, get a dict of tags for the given track.
:param number: track number (0 for HTOA)
:type number: int
:param number: track number (0 for HTOA)
:type number: int
:param mbdiscid: MusicBrainz DiscID
:type mbdiscid: str
:rtype: dict
"""
trackArtist = 'Unknown Artist'
@@ -469,6 +490,7 @@ class Program:
Check if we have hidden track one audio.
:returns: tuple of (start, stop), or None
:rtype: tuple(int, int) or None
"""
track = self.result.table.tracks[0]
try:
@@ -531,11 +553,26 @@ class Program:
def ripTrack(self, runner, trackResult, offset, device, taglist,
overread, what=None, coverArtPath=None):
"""
Rip and store a track of the disc.
Ripping the track may change the track's filename as stored in
trackResult.
:param trackResult: the object to store information in.
:type trackResult: result.TrackResult
:param runner: synchronous track rip task
:type runner: task.SyncRunner
:param trackResult: the object to store information in
:type trackResult: result.TrackResult
:param offset: ripping offset, in CD frames
:type offset: int
:param device: path to the hardware disc drive
:type device: str
:param taglist: dictionary of tags for the given track
:type taglist: dict
:param overread: whether to force overreading into the
lead-out portion of the disc
:type overread: bool
:param what: a string representing what's being read; e.g. Track
:type what: str or None
"""
if trackResult.number == 0:
start, stop = self.getHTOA()
@@ -581,7 +618,8 @@ class Program:
def verifyImage(self, runner, table):
"""
verify table against accuraterip and cue_path track lengths
Verify table against AccurateRip and cue_path track lengths.
Verify our image against the given AccurateRip responses.
Needs an initialized self.result.

View File

@@ -35,14 +35,13 @@ class Operator:
self._resuming = False
def addOperation(self, operation):
"""
Add an operation.
"""
"""Add an operation."""
self._todo.append(operation)
def load(self):
"""
Load state from the given state path using the given key.
Verifies the state.
"""
todo = os.path.join(self._statePath, self._key + '.todo')
@@ -67,9 +66,7 @@ class Operator:
self._resuming = True
def save(self):
"""
Saves the state to the given state path using the given key.
"""
"""Save the state to the given state path using the given key."""
# only save todo first time
todo = os.path.join(self._statePath, self._key + '.todo')
if not os.path.exists(todo):
@@ -88,9 +85,7 @@ class Operator:
handle.write('%s %s\n' % (name, data))
def start(self):
"""
Execute the operations
"""
"""Execute the operations."""
def __next__(self):
operation = self._todo[len(self._done)]
@@ -110,10 +105,10 @@ class FileRenamer(Operator):
"""
Add a rename operation.
:param source: source filename
:type source: str
:param source: source filename
:type source: str
:param destination: destination filename
:type destination: str
:type destination: str
"""
@@ -122,27 +117,27 @@ class Operation:
def verify(self):
"""
Check if the operation will succeed in the current conditions.
Consider this a pre-flight check.
Consider this a pre-flight check.
Does not eliminate the need to handle errors as they happen.
"""
def do(self):
"""
Perform the operation.
"""
"""Perform the operation."""
pass
def redo(self):
"""
Perform the operation, without knowing if it already has been
(partly) performed.
Perform the operation.
Perform it without knowing if it already has been (partly) performed.
"""
self.do()
def serialize(self):
"""
Serialize the operation.
The return value should bu usable with :any:`deserialize`
:rtype: str
@@ -152,7 +147,7 @@ class Operation:
"""
Deserialize the operation with the given operation data.
:type data: str
:type data: str
"""
raise NotImplementedError
deserialize = classmethod(deserialize)

View File

@@ -25,9 +25,7 @@ class LoggableMultiSeparateTask(task.MultiSeparateTask):
class PopenTask(task.Task):
"""
I am a task that runs a command using Popen.
"""
"""Task that runs a command using Popen."""
logCategory = 'PopenTask'
bufsize = 1024
@@ -117,31 +115,21 @@ class PopenTask(task.Task):
# self.stop()
def readbytesout(self, bytes_stdout):
"""
Called when bytes have been read from stdout.
"""
"""Call when bytes have been read from stdout."""
pass
def readbyteserr(self, bytes_stderr):
"""
Called when bytes have been read from stderr.
"""
"""Call when bytes have been read from stderr."""
pass
def done(self):
"""
Called when the command completed successfully.
"""
"""Call when the command completed successfully."""
pass
def failed(self):
"""
Called when the command failed.
"""
"""Call when the command failed."""
pass
def commandMissing(self):
"""
Called when the command is missing.
"""
"""Call when the command is missing."""
pass