Merge pull request #4830 from wisp3rwind/pr_add_syspath_1

Always use syspath conversions (#3690 split up, part 1)
This commit is contained in:
Adrian Sampson 2023-06-26 11:02:36 -07:00 committed by GitHub
commit fb93d9eda0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 220 additions and 176 deletions

View file

@ -29,6 +29,7 @@ import beets.library # noqa: E402
from beets import importer, logging # noqa: E402
from beets.ui import commands # noqa: E402
from beets import util # noqa: E402
from beets.util import bytestring_path, syspath # noqa: E402
import beets # noqa: E402
# Make sure the development versions of the plugins are used
@ -140,11 +141,11 @@ class Assertions:
"""A mixin with additional unit test assertions."""
def assertExists(self, path): # noqa
self.assertTrue(os.path.exists(util.syspath(path)),
self.assertTrue(os.path.exists(syspath(path)),
f'file does not exist: {path!r}')
def assertNotExists(self, path): # noqa
self.assertFalse(os.path.exists(util.syspath(path)),
self.assertFalse(os.path.exists(syspath(path)),
f'file exists: {path!r}')
def assert_equal_path(self, a, b):
@ -186,8 +187,8 @@ class TestCase(unittest.TestCase, Assertions):
self.io = DummyIO()
def tearDown(self):
if os.path.isdir(self.temp_dir):
shutil.rmtree(self.temp_dir)
if os.path.isdir(syspath(self.temp_dir)):
shutil.rmtree(syspath(self.temp_dir))
if self._old_home is None:
del os.environ['HOME']
else:
@ -325,7 +326,7 @@ class DummyIO:
# Utility.
def touch(path):
open(path, 'a').close()
open(syspath(path), 'a').close()
class Bag:
@ -351,16 +352,13 @@ class TempDirMixin:
"""Create a temporary directory and assign it into `self.temp_dir`.
Call `remove_temp_dir` later to delete it.
"""
path = tempfile.mkdtemp()
if not isinstance(path, bytes):
path = path.encode('utf8')
self.temp_dir = path
self.temp_dir = bytestring_path(tempfile.mkdtemp())
def remove_temp_dir(self):
"""Delete the temporary directory created by `create_temp_dir`.
"""
if os.path.isdir(self.temp_dir):
shutil.rmtree(self.temp_dir)
if os.path.isdir(syspath(self.temp_dir)):
shutil.rmtree(syspath(self.temp_dir))
# Platform mocking.

View file

@ -49,7 +49,7 @@ from beets import importer
from beets.autotag.hooks import AlbumInfo, TrackInfo
from mediafile import MediaFile, Image
from beets import util
from beets.util import MoveOperation
from beets.util import MoveOperation, syspath, bytestring_path
# TODO Move AutotagMock here
from test import _common
@ -181,7 +181,7 @@ class TestHelper:
self.config['threaded'] = False
self.libdir = os.path.join(self.temp_dir, b'libdir')
os.mkdir(self.libdir)
os.mkdir(syspath(self.libdir))
self.config['directory'] = util.py3_path(self.libdir)
if disk:
@ -242,17 +242,17 @@ class TestHelper:
`self.temp_dir` and creates a `ImportSessionFixture` for this path.
"""
import_dir = os.path.join(self.temp_dir, b'import')
if not os.path.isdir(import_dir):
os.mkdir(import_dir)
if not os.path.isdir(syspath(import_dir)):
os.mkdir(syspath(import_dir))
album_no = 0
while album_count:
album = util.bytestring_path(f'album {album_no}')
album_dir = os.path.join(import_dir, album)
if os.path.exists(album_dir):
if os.path.exists(syspath(album_dir)):
album_no += 1
continue
os.mkdir(album_dir)
os.mkdir(syspath(album_dir))
album_count -= 1
track_no = 0
@ -262,11 +262,11 @@ class TestHelper:
src = os.path.join(_common.RSRC, b'full.mp3')
title_file = util.bytestring_path(f'{title}.mp3')
dest = os.path.join(album_dir, title_file)
if os.path.exists(dest):
if os.path.exists(syspath(dest)):
track_no += 1
continue
album_item_count -= 1
shutil.copy(src, dest)
shutil.copy(syspath(src), syspath(dest))
mediafile = MediaFile(dest)
mediafile.update({
'artist': 'artist',
@ -405,8 +405,9 @@ class TestHelper:
"""
src = os.path.join(_common.RSRC, util.bytestring_path('full.' + ext))
handle, path = mkstemp()
path = bytestring_path(path)
os.close(handle)
shutil.copyfile(src, path)
shutil.copyfile(syspath(src), syspath(path))
if images:
mediafile = MediaFile(path)
@ -428,7 +429,7 @@ class TestHelper:
def remove_mediafile_fixtures(self):
if hasattr(self, '_mediafile_fixtures'):
for path in self._mediafile_fixtures:
os.remove(path)
os.remove(syspath(path))
def _get_item_count(self):
if not hasattr(self, '__item_count'):
@ -467,7 +468,7 @@ class TestHelper:
def remove_temp_dir(self):
"""Delete the temporary directory created by `create_temp_dir`.
"""
shutil.rmtree(self.temp_dir)
shutil.rmtree(syspath(self.temp_dir))
def touch(self, path, dir=None, content=''):
"""Create a file at `path` with given content.
@ -483,10 +484,10 @@ class TestHelper:
path = os.path.join(self.temp_dir, path)
parent = os.path.dirname(path)
if not os.path.isdir(parent):
os.makedirs(util.syspath(parent))
if not os.path.isdir(syspath(parent)):
os.makedirs(syspath(parent))
with open(util.syspath(path), 'a+') as f:
with open(syspath(path), 'a+') as f:
f.write(content)
return path

View file

@ -31,6 +31,7 @@ from beets import library
from beets import importer
from beets import logging
from beets import util
from beets.util import syspath
from beets.util.artresizer import ArtResizer
import confuse
@ -197,7 +198,7 @@ class FSArtTest(UseThePlugin):
def setUp(self):
super().setUp()
self.dpath = os.path.join(self.temp_dir, b'arttest')
os.mkdir(self.dpath)
os.mkdir(syspath(self.dpath))
self.source = fetchart.FileSystem(logger, self.plugin.config)
self.settings = Settings(cautious=False,
@ -251,7 +252,7 @@ class CombinedTest(FetchImageHelper, UseThePlugin, CAAHelper):
def setUp(self):
super().setUp()
self.dpath = os.path.join(self.temp_dir, b'arttest')
os.mkdir(self.dpath)
os.mkdir(syspath(self.dpath))
def test_main_interface_returns_amazon_art(self):
self.mock_response(self.AMAZON_URL)
@ -641,10 +642,13 @@ class ArtImporterTest(UseThePlugin):
# Test library.
self.libpath = os.path.join(self.temp_dir, b'tmplib.blb')
self.libdir = os.path.join(self.temp_dir, b'tmplib')
os.mkdir(self.libdir)
os.mkdir(os.path.join(self.libdir, b'album'))
os.mkdir(syspath(self.libdir))
os.mkdir(syspath(os.path.join(self.libdir, b'album')))
itempath = os.path.join(self.libdir, b'album', b'test.mp3')
shutil.copyfile(os.path.join(_common.RSRC, b'full.mp3'), itempath)
shutil.copyfile(
syspath(os.path.join(_common.RSRC, b'full.mp3')),
syspath(itempath),
)
self.lib = library.Library(self.libpath)
self.i = _common.item()
self.i.path = itempath
@ -716,7 +720,7 @@ class ArtImporterTest(UseThePlugin):
def test_do_not_delete_original_if_already_in_place(self):
artdest = os.path.join(os.path.dirname(self.i.path), b'cover.jpg')
shutil.copyfile(self.art_file, artdest)
shutil.copyfile(syspath(self.art_file), syspath(artdest))
self.afa_response = fetchart.Candidate(logger, path=artdest)
self._fetch_art(True)

View file

@ -25,6 +25,7 @@ from test.helper import control_stdin, capture_log
from mediafile import MediaFile
from beets import util
from beets.util import bytestring_path, displayable_path, syspath
def shell_quote(text):
@ -53,15 +54,15 @@ class TestHelper(helper.TestHelper):
"""
display_tag = tag
tag = tag.encode('utf-8')
self.assertTrue(os.path.isfile(path),
self.assertTrue(os.path.isfile(syspath(path)),
'{} is not a file'.format(
util.displayable_path(path)))
displayable_path(path)))
with open(path, 'rb') as f:
f.seek(-len(display_tag), os.SEEK_END)
self.assertEqual(f.read(), tag,
'{} is not tagged with {}'
.format(
util.displayable_path(path),
displayable_path(path),
display_tag))
def assertNoFileTag(self, path, tag): # noqa
@ -70,15 +71,15 @@ class TestHelper(helper.TestHelper):
"""
display_tag = tag
tag = tag.encode('utf-8')
self.assertTrue(os.path.isfile(path),
self.assertTrue(os.path.isfile(syspath(path)),
'{} is not a file'.format(
util.displayable_path(path)))
displayable_path(path)))
with open(path, 'rb') as f:
f.seek(-len(tag), os.SEEK_END)
self.assertNotEqual(f.read(), tag,
'{} is unexpectedly tagged with {}'
.format(
util.displayable_path(path),
displayable_path(path),
display_tag))
@ -117,7 +118,7 @@ class ImportConvertTest(unittest.TestCase, TestHelper):
item = self.lib.items().get()
self.assertIsNotNone(item)
self.assertTrue(os.path.isfile(item.path))
self.assertTrue(os.path.isfile(syspath(item.path)))
def test_delete_originals(self):
self.config['convert']['delete_originals'] = True
@ -166,7 +167,7 @@ class ConvertCliTest(unittest.TestCase, TestHelper, ConvertCommand):
self.item = self.album.items()[0]
self.load_plugins('convert')
self.convert_dest = util.bytestring_path(
self.convert_dest = bytestring_path(
os.path.join(self.temp_dir, b'convert_dest')
)
self.config['convert'] = {
@ -202,7 +203,7 @@ class ConvertCliTest(unittest.TestCase, TestHelper, ConvertCommand):
with control_stdin('n'):
self.run_convert()
converted = os.path.join(self.convert_dest, b'converted.mp3')
self.assertFalse(os.path.isfile(converted))
self.assertFalse(os.path.isfile(syspath(converted)))
def test_convert_keep_new(self):
self.assertEqual(os.path.splitext(self.item.path)[1], b'.ogg')
@ -243,7 +244,7 @@ class ConvertCliTest(unittest.TestCase, TestHelper, ConvertCommand):
def test_pretend(self):
self.run_convert('--pretend')
converted = os.path.join(self.convert_dest, b'converted.mp3')
self.assertFalse(os.path.exists(converted))
self.assertFalse(os.path.exists(syspath(converted)))
def test_empty_query(self):
with capture_log('beets.convert') as logs:

View file

@ -25,7 +25,7 @@ from test.test_art_resize import DummyIMBackend
from mediafile import MediaFile
from beets import config, logging, ui
from beets.util import syspath, displayable_path
from beets.util import bytestring_path, displayable_path, syspath
from beets.util.artresizer import ArtResizer
from beets import art
from test.test_art import FetchImageHelper
@ -110,6 +110,7 @@ class EmbedartCliTest(TestHelper, FetchImageHelper):
logging.getLogger('beets.embedart').setLevel(logging.DEBUG)
handle, tmp_path = tempfile.mkstemp()
tmp_path = bytestring_path(tmp_path)
os.write(handle, self.image_data)
os.close(handle)
@ -119,9 +120,11 @@ class EmbedartCliTest(TestHelper, FetchImageHelper):
config['embedart']['remove_art_file'] = True
self.run_command('embedart', '-y')
if os.path.isfile(tmp_path):
os.remove(tmp_path)
self.fail(f'Artwork file {tmp_path} was not deleted')
if os.path.isfile(syspath(tmp_path)):
os.remove(syspath(tmp_path))
self.fail('Artwork file {} was not deleted'.format(
displayable_path(tmp_path)
))
def test_art_file_missing(self):
self.add_album_fixture()
@ -134,13 +137,14 @@ class EmbedartCliTest(TestHelper, FetchImageHelper):
logging.getLogger('beets.embedart').setLevel(logging.DEBUG)
handle, tmp_path = tempfile.mkstemp()
tmp_path = bytestring_path(tmp_path)
os.write(handle, b'I am not an image.')
os.close(handle)
try:
self.run_command('embedart', '-y', '-f', tmp_path)
finally:
os.remove(tmp_path)
os.remove(syspath(tmp_path))
mediafile = MediaFile(syspath(album.items()[0].path))
self.assertFalse(mediafile.images) # No image added.

View file

@ -25,7 +25,7 @@ from test.helper import capture_log
from test.test_importer import ImportHelper
from beets import config
from mediafile import MediaFile
from beets.util import displayable_path, bytestring_path
from beets.util import displayable_path, bytestring_path, syspath
from beetsplug.filefilter import FileFilterPlugin
@ -42,7 +42,7 @@ class FileFilterPluginTest(unittest.TestCase, ImportHelper):
def __copy_file(self, dest_path, metadata):
# Copy files
resource_path = os.path.join(_common.RSRC, b'full.mp3')
shutil.copy(resource_path, dest_path)
shutil.copy(syspath(resource_path), syspath(dest_path))
medium = MediaFile(dest_path)
# Set metadata
for attr in metadata:
@ -51,14 +51,14 @@ class FileFilterPluginTest(unittest.TestCase, ImportHelper):
def __create_import_dir(self, count):
self.import_dir = os.path.join(self.temp_dir, b'testsrcdir')
if os.path.isdir(self.import_dir):
shutil.rmtree(self.import_dir)
if os.path.isdir(syspath(self.import_dir)):
shutil.rmtree(syspath(self.import_dir))
self.artist_path = os.path.join(self.import_dir, b'artist')
self.album_path = os.path.join(self.artist_path, b'album')
self.misc_path = os.path.join(self.import_dir, b'misc')
os.makedirs(self.album_path)
os.makedirs(self.misc_path)
os.makedirs(syspath(self.album_path))
os.makedirs(syspath(self.misc_path))
metadata = {
'artist': 'Tag Artist',

View file

@ -25,7 +25,7 @@ from test import _common
from test._common import item, touch
import beets.library
from beets import util
from beets.util import MoveOperation
from beets.util import MoveOperation, bytestring_path, syspath
class MoveTest(_common.TestCase):
@ -34,7 +34,10 @@ class MoveTest(_common.TestCase):
# make a temporary file
self.path = join(self.temp_dir, b'temp.mp3')
shutil.copy(join(_common.RSRC, b'full.mp3'), self.path)
shutil.copy(
syspath(join(_common.RSRC, b'full.mp3')),
syspath(self.path),
)
# add it to a temporary library
self.lib = beets.library.Library(':memory:')
@ -43,7 +46,7 @@ class MoveTest(_common.TestCase):
# set up the destination
self.libdir = join(self.temp_dir, b'testlibdir')
os.mkdir(self.libdir)
os.mkdir(syspath(self.libdir))
self.lib.directory = self.libdir
self.lib.path_formats = [('default',
join('$artist', '$album', '$title'))]
@ -139,20 +142,20 @@ class MoveTest(_common.TestCase):
def test_read_only_file_copied_writable(self):
# Make the source file read-only.
os.chmod(self.path, 0o444)
os.chmod(syspath(self.path), 0o444)
try:
self.i.move(operation=MoveOperation.COPY)
self.assertTrue(os.access(self.i.path, os.W_OK))
self.assertTrue(os.access(syspath(self.i.path), os.W_OK))
finally:
# Make everything writable so it can be cleaned up.
os.chmod(self.path, 0o777)
os.chmod(self.i.path, 0o777)
os.chmod(syspath(self.path), 0o777)
os.chmod(syspath(self.i.path), 0o777)
def test_move_avoids_collision_with_existing_file(self):
# Make a conflicting file at the destination.
dest = self.i.destination()
os.makedirs(os.path.dirname(dest))
os.makedirs(syspath(os.path.dirname(dest)))
touch(dest)
self.i.move()
@ -164,8 +167,11 @@ class MoveTest(_common.TestCase):
def test_link_arrives(self):
self.i.move(operation=MoveOperation.LINK)
self.assertExists(self.dest)
self.assertTrue(os.path.islink(self.dest))
self.assertEqual(os.readlink(self.dest), self.path)
self.assertTrue(os.path.islink(syspath(self.dest)))
self.assertEqual(
bytestring_path(os.readlink(syspath(self.dest))),
self.path,
)
@unittest.skipUnless(_common.HAVE_SYMLINK, "need symlinks")
def test_link_does_not_depart(self):
@ -181,8 +187,8 @@ class MoveTest(_common.TestCase):
def test_hardlink_arrives(self):
self.i.move(operation=MoveOperation.HARDLINK)
self.assertExists(self.dest)
s1 = os.stat(self.path)
s2 = os.stat(self.dest)
s1 = os.stat(syspath(self.path))
s2 = os.stat(syspath(self.dest))
self.assertTrue(
(s1[stat.ST_INO], s1[stat.ST_DEV]) ==
(s2[stat.ST_INO], s2[stat.ST_DEV])
@ -271,8 +277,8 @@ class AlbumFileTest(_common.TestCase):
self.ai.store()
self.i.load()
self.assertFalse(os.path.exists(oldpath))
self.assertTrue(os.path.exists(self.i.path))
self.assertFalse(os.path.exists(syspath(oldpath)))
self.assertTrue(os.path.exists(syspath(self.i.path)))
def test_albuminfo_move_copies_file(self):
oldpath = self.i.path
@ -281,8 +287,8 @@ class AlbumFileTest(_common.TestCase):
self.ai.store()
self.i.load()
self.assertTrue(os.path.exists(oldpath))
self.assertTrue(os.path.exists(self.i.path))
self.assertTrue(os.path.exists(syspath(oldpath)))
self.assertTrue(os.path.exists(syspath(self.i.path)))
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_albuminfo_move_reflinks_file(self):
@ -326,21 +332,21 @@ class ArtFileTest(_common.TestCase):
self.otherdir = os.path.join(self.temp_dir, b'testotherdir')
def test_art_deleted_when_items_deleted(self):
self.assertTrue(os.path.exists(self.art))
self.assertTrue(os.path.exists(syspath(self.art)))
self.ai.remove(True)
self.assertFalse(os.path.exists(self.art))
self.assertFalse(os.path.exists(syspath(self.art)))
def test_art_moves_with_album(self):
self.assertTrue(os.path.exists(self.art))
self.assertTrue(os.path.exists(syspath(self.art)))
oldpath = self.i.path
self.ai.album = 'newAlbum'
self.ai.move()
self.i.load()
self.assertNotEqual(self.i.path, oldpath)
self.assertFalse(os.path.exists(self.art))
self.assertFalse(os.path.exists(syspath(self.art)))
newart = self.lib.get_album(self.i).art_destination(self.art)
self.assertTrue(os.path.exists(newart))
self.assertTrue(os.path.exists(syspath(newart)))
def test_art_moves_with_album_to_custom_dir(self):
# Move the album to another directory.
@ -355,7 +361,7 @@ class ArtFileTest(_common.TestCase):
self.assertTrue(b'testotherdir' in newart)
def test_setart_copies_image(self):
os.remove(self.art)
util.remove(self.art)
newart = os.path.join(self.libdir, b'newart.jpg')
touch(newart)
@ -367,10 +373,10 @@ class ArtFileTest(_common.TestCase):
self.assertEqual(ai.artpath, None)
ai.set_art(newart)
self.assertTrue(os.path.exists(ai.artpath))
self.assertTrue(os.path.exists(syspath(ai.artpath)))
def test_setart_to_existing_art_works(self):
os.remove(self.art)
util.remove(self.art)
# Original art.
newart = os.path.join(self.libdir, b'newart.jpg')
@ -384,7 +390,7 @@ class ArtFileTest(_common.TestCase):
# Set the art again.
ai.set_art(ai.artpath)
self.assertTrue(os.path.exists(ai.artpath))
self.assertTrue(os.path.exists(syspath(ai.artpath)))
def test_setart_to_existing_but_unset_art_works(self):
newart = os.path.join(self.libdir, b'newart.jpg')
@ -397,11 +403,11 @@ class ArtFileTest(_common.TestCase):
# Copy the art to the destination.
artdest = ai.art_destination(newart)
shutil.copy(newart, artdest)
shutil.copy(syspath(newart), syspath(artdest))
# Set the art again.
ai.set_art(artdest)
self.assertTrue(os.path.exists(ai.artpath))
self.assertTrue(os.path.exists(syspath(ai.artpath)))
def test_setart_to_conflicting_file_gets_new_path(self):
newart = os.path.join(self.libdir, b'newart.jpg')
@ -423,11 +429,11 @@ class ArtFileTest(_common.TestCase):
os.path.dirname(ai.artpath))
def test_setart_sets_permissions(self):
os.remove(self.art)
util.remove(self.art)
newart = os.path.join(self.libdir, b'newart.jpg')
touch(newart)
os.chmod(newart, 0o400) # read-only
os.chmod(syspath(newart), 0o400) # read-only
try:
i2 = item()
@ -437,14 +443,14 @@ class ArtFileTest(_common.TestCase):
i2.move(operation=MoveOperation.COPY)
ai.set_art(newart)
mode = stat.S_IMODE(os.stat(ai.artpath).st_mode)
mode = stat.S_IMODE(os.stat(syspath(ai.artpath)).st_mode)
self.assertTrue(mode & stat.S_IRGRP)
self.assertTrue(os.access(ai.artpath, os.W_OK))
self.assertTrue(os.access(syspath(ai.artpath), os.W_OK))
finally:
# Make everything writable so it can be cleaned up.
os.chmod(newart, 0o777)
os.chmod(ai.artpath, 0o777)
os.chmod(syspath(newart), 0o777)
os.chmod(syspath(ai.artpath), 0o777)
def test_move_last_file_moves_albumart(self):
oldartpath = self.lib.albums()[0].artpath
@ -609,9 +615,9 @@ class PruneTest(_common.TestCase):
super().setUp()
self.base = os.path.join(self.temp_dir, b'testdir')
os.mkdir(self.base)
os.mkdir(syspath(self.base))
self.sub = os.path.join(self.base, b'subdir')
os.mkdir(self.sub)
os.mkdir(syspath(self.sub))
def test_prune_existent_directory(self):
util.prune_dirs(self.sub, self.base)
@ -629,10 +635,10 @@ class WalkTest(_common.TestCase):
super().setUp()
self.base = os.path.join(self.temp_dir, b'testdir')
os.mkdir(self.base)
os.mkdir(syspath(self.base))
touch(os.path.join(self.base, b'y'))
touch(os.path.join(self.base, b'x'))
os.mkdir(os.path.join(self.base, b'd'))
os.mkdir(syspath(os.path.join(self.base, b'd')))
touch(os.path.join(self.base, b'd', b'z'))
def test_sorted_files(self):
@ -669,7 +675,7 @@ class UniquePathTest(_common.TestCase):
super().setUp()
self.base = os.path.join(self.temp_dir, b'testdir')
os.mkdir(self.base)
os.mkdir(syspath(self.base))
touch(os.path.join(self.base, b'x.mp3'))
touch(os.path.join(self.base, b'x.1.mp3'))
touch(os.path.join(self.base, b'x.2.mp3'))
@ -696,16 +702,16 @@ class MkDirAllTest(_common.TestCase):
def test_parent_exists(self):
path = os.path.join(self.temp_dir, b'foo', b'bar', b'baz', b'qux.mp3')
util.mkdirall(path)
self.assertTrue(os.path.isdir(
os.path.join(self.temp_dir, b'foo', b'bar', b'baz')
))
self.assertTrue(os.path.isdir(syspath(
os.path.join(self.temp_dir, b'foo', b'bar', b'baz'),
)))
def test_child_does_not_exist(self):
path = os.path.join(self.temp_dir, b'foo', b'bar', b'baz', b'qux.mp3')
util.mkdirall(path)
self.assertTrue(not os.path.exists(
os.path.join(self.temp_dir, b'foo', b'bar', b'baz', b'qux.mp3')
))
self.assertTrue(not os.path.exists(syspath(
os.path.join(self.temp_dir, b'foo', b'bar', b'baz', b'qux.mp3'),
)))
def suite():

View file

@ -20,7 +20,7 @@ import unittest
from test.test_importer import ImportHelper, AutotagStub
from beets import importer
from beets import util
from beets.util import displayable_path, syspath
from beetsplug.importadded import ImportAddedPlugin
_listeners = ImportAddedPlugin.listeners
@ -37,7 +37,7 @@ def preserve_plugin_listeners():
def modify_mtimes(paths, offset=-60000):
for i, path in enumerate(paths, start=1):
mstat = os.stat(path)
os.utime(path, (mstat.st_atime, mstat.st_mtime + offset * i))
os.utime(syspath(path), (mstat.st_atime, mstat.st_mtime + offset * i))
class ImportAddedTest(unittest.TestCase, ImportHelper):
@ -71,7 +71,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper):
if m.title.replace('Tag', 'Applied') == item.title:
return m
raise AssertionError("No MediaFile found for Item " +
util.displayable_path(item.path))
displayable_path(item.path))
def assertEqualTimes(self, first, second, msg=None): # noqa
"""For comparing file modification times at a sufficient precision"""
@ -126,7 +126,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper):
for item_path, added_after in items_added_after.items():
self.assertEqualTimes(items_added_before[item_path], added_after,
"reimport modified Item.added for " +
util.displayable_path(item_path))
displayable_path(item_path))
def test_import_singletons_with_added_dates(self):
self.config['import']['singletons'] = True
@ -164,7 +164,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper):
for item_path, added_after in items_added_after.items():
self.assertEqualTimes(items_added_before[item_path], added_after,
"reimport modified Item.added for " +
util.displayable_path(item_path))
displayable_path(item_path))
def suite():

View file

@ -29,7 +29,6 @@ from unittest.mock import patch, Mock
import unittest
from test import _common
from beets.util import displayable_path, bytestring_path, py3_path
from test.helper import TestHelper, has_program, capture_log
from test.helper import ImportSessionFixture
from beets import importer
@ -40,6 +39,7 @@ from beets.autotag import AlbumInfo, TrackInfo, AlbumMatch
from beets import config
from beets import logging
from beets import util
from beets.util import displayable_path, bytestring_path, py3_path, syspath
class AutotagStub:
@ -173,11 +173,11 @@ class ImportHelper(TestHelper):
:param count: Number of files to create
"""
self.import_dir = os.path.join(self.temp_dir, b'testsrcdir')
if os.path.isdir(self.import_dir):
shutil.rmtree(self.import_dir)
if os.path.isdir(syspath(self.import_dir)):
shutil.rmtree(syspath(self.import_dir))
album_path = os.path.join(self.import_dir, b'the_album')
os.makedirs(album_path)
os.makedirs(syspath(album_path))
resource_path = os.path.join(_common.RSRC, b'full.mp3')
@ -196,7 +196,7 @@ class ImportHelper(TestHelper):
album_path,
bytestring_path('track_%d.mp3' % (i + 1))
)
shutil.copy(resource_path, medium_path)
shutil.copy(syspath(resource_path), syspath(medium_path))
medium = MediaFile(medium_path)
# Set metadata
@ -241,7 +241,7 @@ class ImportHelper(TestHelper):
self.assertNotExists(os.path.join(self.libdir, *segments))
def assert_lib_dir_empty(self):
self.assertEqual(len(os.listdir(self.libdir)), 0)
self.assertEqual(len(os.listdir(syspath(self.libdir))), 0)
@_common.slow_test()
@ -293,8 +293,7 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper):
self.assertNotExists(os.path.join(self.import_dir, b'the_album'))
def test_import_with_move_prunes_with_extra_clutter(self):
f = open(os.path.join(self.import_dir, b'the_album', b'alog.log'), 'w')
f.close()
self.touch(os.path.join(self.import_dir, b'the_album', b'alog.log'))
config['clutter'] = ['*.log']
config['import']['move'] = True
@ -350,9 +349,9 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper):
util.bytestring_path(f'{mediafile.title}.mp3')
)
self.assertExists(filename)
self.assertTrue(os.path.islink(filename))
self.assertTrue(os.path.islink(syspath(filename)))
self.assert_equal_path(
util.bytestring_path(os.readlink(filename)),
util.bytestring_path(os.readlink(syspath(filename))),
mediafile.path
)
@ -367,8 +366,8 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper):
util.bytestring_path(f'{mediafile.title}.mp3')
)
self.assertExists(filename)
s1 = os.stat(mediafile.path)
s2 = os.stat(filename)
s1 = os.stat(syspath(mediafile.path))
s2 = os.stat(syspath(filename))
self.assertTrue(
(s1[stat.ST_INO], s1[stat.ST_DEV]) ==
(s2[stat.ST_INO], s2[stat.ST_DEV])
@ -377,9 +376,10 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper):
def create_archive(session):
(handle, path) = mkstemp(dir=py3_path(session.temp_dir))
path = bytestring_path(path)
os.close(handle)
archive = ZipFile(py3_path(path), mode='w')
archive.write(os.path.join(_common.RSRC, b'full.mp3'),
archive.write(syspath(os.path.join(_common.RSRC, b'full.mp3')),
'full.mp3')
archive.close()
path = bytestring_path(path)
@ -433,10 +433,11 @@ class ImportZipTest(unittest.TestCase, ImportHelper):
class ImportTarTest(ImportZipTest):
def create_archive(self):
(handle, path) = mkstemp(dir=self.temp_dir)
(handle, path) = mkstemp(dir=syspath(self.temp_dir))
path = bytestring_path(path)
os.close(handle)
archive = TarFile(py3_path(path), mode='w')
archive.add(os.path.join(_common.RSRC, b'full.mp3'),
archive.add(syspath(os.path.join(_common.RSRC, b'full.mp3')),
'full.mp3')
archive.close()
return path
@ -534,7 +535,7 @@ class ImportSingletonTest(_common.TestCase, ImportHelper):
resource_path = os.path.join(_common.RSRC, b'empty.mp3')
single_path = os.path.join(self.import_dir, b'track_2.mp3')
shutil.copy(resource_path, single_path)
util.copy(resource_path, single_path)
import_files = [
os.path.join(self.import_dir, b'the_album'),
single_path
@ -696,8 +697,9 @@ class ImportTest(_common.TestCase, ImportHelper):
self.assertEqual(self.lib.items().get(), None)
def test_skip_non_album_dirs(self):
self.assertTrue(os.path.isdir(
os.path.join(self.import_dir, b'the_album')))
self.assertTrue(os.path.isdir(syspath(
os.path.join(self.import_dir, b'the_album'),
)))
self.touch(b'cruft', dir=self.import_dir)
self.importer.add_choice(importer.action.APPLY)
self.importer.run()
@ -1543,7 +1545,10 @@ class IncrementalImportTest(unittest.TestCase, TestHelper):
def _mkmp3(path):
shutil.copyfile(os.path.join(_common.RSRC, b'min.mp3'), path)
shutil.copyfile(
syspath(os.path.join(_common.RSRC, b'min.mp3')),
syspath(path),
)
class AlbumsInDirTest(_common.TestCase):
@ -1552,13 +1557,13 @@ class AlbumsInDirTest(_common.TestCase):
# create a directory structure for testing
self.base = os.path.abspath(os.path.join(self.temp_dir, b'tempdir'))
os.mkdir(self.base)
os.mkdir(syspath(self.base))
os.mkdir(os.path.join(self.base, b'album1'))
os.mkdir(os.path.join(self.base, b'album2'))
os.mkdir(os.path.join(self.base, b'more'))
os.mkdir(os.path.join(self.base, b'more', b'album3'))
os.mkdir(os.path.join(self.base, b'more', b'album4'))
os.mkdir(syspath(os.path.join(self.base, b'album1')))
os.mkdir(syspath(os.path.join(self.base, b'album2')))
os.mkdir(syspath(os.path.join(self.base, b'more')))
os.mkdir(syspath(os.path.join(self.base, b'more', b'album3')))
os.mkdir(syspath(os.path.join(self.base, b'more', b'album4')))
_mkmp3(os.path.join(self.base, b'album1', b'album1song1.mp3'))
_mkmp3(os.path.join(self.base, b'album1', b'album1song2.mp3'))
@ -1597,7 +1602,7 @@ class MultiDiscAlbumsInDirTest(_common.TestCase):
otherwise, we use Unicode names.
"""
self.base = os.path.abspath(os.path.join(self.temp_dir, b'tempdir'))
os.mkdir(self.base)
os.mkdir(syspath(self.base))
name = b'CAT' if ascii else util.bytestring_path('C\xc1T')
name_alt_case = b'CAt' if ascii else util.bytestring_path('C\xc1t')
@ -1644,7 +1649,7 @@ class MultiDiscAlbumsInDirTest(_common.TestCase):
self.files = [self._normalize_path(p) for p in self.files]
for path in self.dirs:
os.mkdir(util.syspath(path))
os.mkdir(syspath(path))
if files:
for path in self.files:
_mkmp3(util.syspath(path))
@ -1839,7 +1844,7 @@ class ImportPretendTest(_common.TestCase, ImportHelper):
self._create_import_dir(1)
resource_path = os.path.join(_common.RSRC, b'empty.mp3')
single_path = os.path.join(self.import_dir, b'track_2.mp3')
shutil.copy(resource_path, single_path)
shutil.copy(syspath(resource_path), syspath(single_path))
self.import_paths = [
os.path.join(self.import_dir, b'the_album'),
single_path
@ -1852,7 +1857,7 @@ class ImportPretendTest(_common.TestCase, ImportHelper):
def __create_empty_import_dir(self):
path = os.path.join(self.temp_dir, b'empty')
os.makedirs(path)
os.makedirs(syspath(path))
self.empty_path = path
def __run(self, import_paths, singletons=True):

View file

@ -41,7 +41,7 @@ class InfoTest(unittest.TestCase, TestHelper):
mediafile.save()
out = self.run_with_output('info', path)
self.assertIn(path, out)
self.assertIn(displayable_path(path), out)
self.assertIn('albumartist: AAA', out)
self.assertIn('disctitle: DDD', out)
self.assertIn('genres: a; b; c', out)

View file

@ -1169,7 +1169,10 @@ class MtimeTest(_common.TestCase):
def setUp(self):
super().setUp()
self.ipath = os.path.join(self.temp_dir, b'testfile.mp3')
shutil.copy(os.path.join(_common.RSRC, b'full.mp3'), self.ipath)
shutil.copy(
syspath(os.path.join(_common.RSRC, b'full.mp3')),
syspath(self.ipath),
)
self.i = beets.library.Item.from_path(self.ipath)
self.lib = beets.library.Library(':memory:')
self.lib.add(self.i)

View file

@ -23,7 +23,7 @@ from test import _common
from beets.library import Item
import mediafile
from beets.plugins import BeetsPlugin
from beets.util import bytestring_path
from beets.util import bytestring_path, syspath
field_extension = mediafile.MediaField(
@ -40,7 +40,7 @@ class ExtendedFieldTestMixin(_common.TestCase):
name = bytestring_path(name + '.' + extension)
src = os.path.join(_common.RSRC, name)
target = os.path.join(self.temp_dir, name)
shutil.copy(src, target)
shutil.copy(syspath(src), syspath(target))
return mediafile.MediaFile(target)
def test_extended_field_write(self):

View file

@ -180,7 +180,7 @@ class EventsTest(unittest.TestCase, ImportHelper, TestHelper):
def __copy_file(self, dest_path, metadata):
# Copy files
resource_path = os.path.join(RSRC, b'full.mp3')
shutil.copy(resource_path, dest_path)
shutil.copy(syspath(resource_path), syspath(dest_path))
medium = MediaFile(dest_path)
# Set metadata
for attr in metadata:
@ -189,8 +189,8 @@ class EventsTest(unittest.TestCase, ImportHelper, TestHelper):
def __create_import_dir(self, count):
self.import_dir = os.path.join(self.temp_dir, b'testsrcdir')
if os.path.isdir(self.import_dir):
shutil.rmtree(self.import_dir)
if os.path.isdir(syspath(self.import_dir)):
shutil.rmtree(syspath(self.import_dir))
self.album_path = os.path.join(self.import_dir, b'album')
os.makedirs(self.album_path)

View file

@ -31,6 +31,7 @@ from beets.dbcore.query import (NoneQuery, ParsingError,
InvalidQueryArgumentValueError)
from beets.library import Library, Item
from beets import util
from beets.util import syspath
# Because the absolute path begins with something like C:, we
# can't disambiguate it from an ordinary query.
@ -662,7 +663,7 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin):
# Temporarily change directory so relative paths work.
cur_dir = os.getcwd()
try:
os.chdir(self.temp_dir)
os.chdir(syspath(self.temp_dir))
self.assertTrue(is_path_query('foo/'))
self.assertTrue(is_path_query('foo/bar'))
self.assertTrue(is_path_query('foo/bar:tagada'))

View file

@ -167,7 +167,7 @@ class SmartPlaylistTest(unittest.TestCase):
try:
spl.update_playlists(lib)
except Exception:
rmtree(dir)
rmtree(syspath(dir))
raise
lib.items.assert_called_once_with(q, None)
@ -177,7 +177,7 @@ class SmartPlaylistTest(unittest.TestCase):
self.assertTrue(path.exists(m3u_filepath))
with open(syspath(m3u_filepath), 'rb') as f:
content = f.read()
rmtree(dir)
rmtree(syspath(dir))
self.assertEqual(content, b'/tagada.mp3\n')
@ -207,14 +207,14 @@ class SmartPlaylistCLITest(unittest.TestCase, TestHelper):
self.run_with_output('splupdate', 'my_playlist')
m3u_path = path.join(self.temp_dir, b'my_playlist.m3u')
self.assertTrue(path.exists(m3u_path))
with open(m3u_path, 'rb') as f:
with open(syspath(m3u_path), 'rb') as f:
self.assertEqual(f.read(), self.item.path + b"\n")
remove(m3u_path)
remove(syspath(m3u_path))
self.run_with_output('splupdate', 'my_playlist.m3u')
with open(m3u_path, 'rb') as f:
with open(syspath(m3u_path), 'rb') as f:
self.assertEqual(f.read(), self.item.path + b"\n")
remove(m3u_path)
remove(syspath(m3u_path))
self.run_with_output('splupdate')
for name in (b'my_playlist.m3u', b'all.m3u'):

View file

@ -21,7 +21,7 @@ import unittest
from test.helper import TestHelper
from beets.util import bytestring_path
from beets.util import bytestring_path, syspath
from beetsplug.thumbnails import (ThumbnailsPlugin, NORMAL_DIR, LARGE_DIR,
PathlibURI, GioURI)
@ -51,6 +51,7 @@ class ThumbnailsTest(unittest.TestCase, TestHelper):
b"/path/to/thumbnail",
metadata,
)
# FIXME: Plugin should use syspath
mock_stat.assert_called_once_with(album.artpath)
@patch('beetsplug.thumbnails.os')
@ -68,13 +69,16 @@ class ThumbnailsTest(unittest.TestCase, TestHelper):
mock_artresizer.shared.can_write_metadata = True
def exists(path):
# FIXME: Plugin should use syspath
if path == NORMAL_DIR:
return False
# FIXME: Plugin should use syspath
if path == LARGE_DIR:
return True
raise ValueError(f"unexpected path {path!r}")
mock_os.path.exists = exists
plugin = ThumbnailsPlugin()
# FIXME: Plugin should use syspath
mock_os.makedirs.assert_called_once_with(NORMAL_DIR)
self.assertTrue(plugin._check_local_ok())
@ -109,6 +113,7 @@ class ThumbnailsTest(unittest.TestCase, TestHelper):
thumbnail_dir = os.path.normpath(b"/thumbnail/dir")
md5_file = os.path.join(thumbnail_dir, b"md5")
path_to_art = os.path.normpath(b"/path/to/art")
path_to_resized_art = os.path.normpath(b'/path/to/resized/artwork')
mock_os.path.join = os.path.join # don't mock that function
plugin = ThumbnailsPlugin()
@ -120,34 +125,43 @@ class ThumbnailsTest(unittest.TestCase, TestHelper):
mock_os.path.exists.return_value = False
def os_stat(target):
# FIXME: Plugin should use syspath
if target == md5_file:
return Mock(st_mtime=1)
# FIXME: Plugin should use syspath
elif target == path_to_art:
return Mock(st_mtime=2)
else:
raise ValueError(f"invalid target {target}")
mock_os.stat.side_effect = os_stat
mock_resize = mock_artresizer.shared.resize
mock_resize.return_value = path_to_resized_art
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
# FIXME: Plugin should use syspath
mock_os.path.exists.assert_called_once_with(md5_file)
mock_os.stat.has_calls([call(md5_file), call(path_to_art)],
mock_os.stat.has_calls([call(syspath(md5_file)),
call(syspath(path_to_art))],
any_order=True)
resize = mock_artresizer.shared.resize
resize.assert_called_once_with(12345, path_to_art, md5_file)
plugin.add_tags.assert_called_once_with(album, resize.return_value)
mock_shutils.move.assert_called_once_with(resize.return_value,
mock_resize.assert_called_once_with(12345, path_to_art, md5_file)
plugin.add_tags.assert_called_once_with(album, path_to_resized_art)
# FIXME: Plugin should use syspath
mock_shutils.move.assert_called_once_with(path_to_resized_art,
md5_file)
# now test with recent thumbnail & with force
mock_os.path.exists.return_value = True
plugin.force = False
resize.reset_mock()
mock_resize.reset_mock()
def os_stat(target):
# FIXME: Plugin should use syspath
if target == md5_file:
return Mock(st_mtime=3)
# FIXME: Plugin should use syspath
elif target == path_to_art:
return Mock(st_mtime=2)
else:
@ -155,12 +169,12 @@ class ThumbnailsTest(unittest.TestCase, TestHelper):
mock_os.stat.side_effect = os_stat
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
self.assertEqual(resize.call_count, 0)
self.assertEqual(mock_resize.call_count, 0)
# and with force
plugin.config['force'] = True
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
resize.assert_called_once_with(12345, path_to_art, md5_file)
mock_resize.assert_called_once_with(12345, path_to_art, md5_file)
@patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
def test_make_dolphin_cover_thumbnail(self, _):
@ -184,7 +198,7 @@ class ThumbnailsTest(unittest.TestCase, TestHelper):
[b"[Desktop Entry]", b"Icon=./cover.jpg"]
)
rmtree(tmp)
rmtree(syspath(tmp))
@patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
@patch('beetsplug.thumbnails.ArtResizer')

View file

@ -114,7 +114,7 @@ class RemoveTest(_common.TestCase, TestHelper):
self.io.install()
self.libdir = os.path.join(self.temp_dir, b'testlibdir')
os.mkdir(self.libdir)
os.mkdir(syspath(self.libdir))
# Copy a file into the library.
self.lib = library.Library(':memory:', self.libdir)
@ -128,26 +128,26 @@ class RemoveTest(_common.TestCase, TestHelper):
commands.remove_items(self.lib, '', False, False, False)
items = self.lib.items()
self.assertEqual(len(list(items)), 0)
self.assertTrue(os.path.exists(self.i.path))
self.assertTrue(os.path.exists(syspath(self.i.path)))
def test_remove_items_with_delete(self):
self.io.addinput('y')
commands.remove_items(self.lib, '', False, True, False)
items = self.lib.items()
self.assertEqual(len(list(items)), 0)
self.assertFalse(os.path.exists(self.i.path))
self.assertFalse(os.path.exists(syspath(self.i.path)))
def test_remove_items_with_force_no_delete(self):
commands.remove_items(self.lib, '', False, False, True)
items = self.lib.items()
self.assertEqual(len(list(items)), 0)
self.assertTrue(os.path.exists(self.i.path))
self.assertTrue(os.path.exists(syspath(self.i.path)))
def test_remove_items_with_force_delete(self):
commands.remove_items(self.lib, '', False, True, True)
items = self.lib.items()
self.assertEqual(len(list(items)), 0)
self.assertFalse(os.path.exists(self.i.path))
self.assertFalse(os.path.exists(syspath(self.i.path)))
def test_remove_items_select_with_delete(self):
i2 = library.Item.from_path(self.item_path)
@ -458,10 +458,13 @@ class MoveTest(_common.TestCase):
self.io.install()
self.libdir = os.path.join(self.temp_dir, b'testlibdir')
os.mkdir(self.libdir)
os.mkdir(syspath(self.libdir))
self.itempath = os.path.join(self.libdir, b'srcfile')
shutil.copy(os.path.join(_common.RSRC, b'full.mp3'), self.itempath)
shutil.copy(
syspath(os.path.join(_common.RSRC, b'full.mp3')),
syspath(self.itempath),
)
# Add a file to the library but don't copy it in yet.
self.lib = library.Library(':memory:', self.libdir)
@ -573,7 +576,7 @@ class UpdateTest(_common.TestCase):
_common.touch(artfile)
self.album.set_art(artfile)
self.album.store()
os.remove(artfile)
util.remove(artfile)
def _update(self, query=(), album=False, move=False, reset_mtime=True,
fields=None):
@ -586,23 +589,23 @@ class UpdateTest(_common.TestCase):
def test_delete_removes_item(self):
self.assertTrue(list(self.lib.items()))
os.remove(self.i.path)
os.remove(self.i2.path)
util.remove(self.i.path)
util.remove(self.i2.path)
self._update()
self.assertFalse(list(self.lib.items()))
def test_delete_removes_album(self):
self.assertTrue(self.lib.albums())
os.remove(self.i.path)
os.remove(self.i2.path)
util.remove(self.i.path)
util.remove(self.i2.path)
self._update()
self.assertFalse(self.lib.albums())
def test_delete_removes_album_art(self):
artpath = self.album.artpath
self.assertExists(artpath)
os.remove(self.i.path)
os.remove(self.i2.path)
util.remove(self.i.path)
util.remove(self.i2.path)
self._update()
self.assertNotExists(artpath)
@ -694,7 +697,7 @@ class UpdateTest(_common.TestCase):
mf.save()
# Make in-memory mtime match on-disk mtime.
self.i.mtime = os.path.getmtime(self.i.path)
self.i.mtime = os.path.getmtime(syspath(self.i.path))
self.i.store()
self._update(reset_mtime=False)
@ -837,20 +840,20 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions):
self.user_config_dir = os.path.join(
self.temp_dir, b'.config', b'beets'
)
os.makedirs(self.user_config_dir)
os.makedirs(syspath(self.user_config_dir))
self.user_config_path = os.path.join(self.user_config_dir,
b'config.yaml')
# Custom BEETSDIR
self.beetsdir = os.path.join(self.temp_dir, b'beetsdir')
os.makedirs(self.beetsdir)
os.makedirs(syspath(self.beetsdir))
self._reset_config()
self.load_plugins()
def tearDown(self):
commands.default_commands.pop()
os.chdir(self._orig_cwd)
os.chdir(syspath(self._orig_cwd))
if self._old_home is not None:
os.environ['HOME'] = self._old_home
if self._old_appdata is None:
@ -1034,7 +1037,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions):
def test_command_line_option_relative_to_working_dir(self):
config.read()
os.chdir(self.temp_dir)
os.chdir(syspath(self.temp_dir))
self.run_command('--library', 'foo.db', 'test', lib=None)
self.assert_equal_path(config['library'].as_filename(),
os.path.join(os.getcwd(), 'foo.db'))
@ -1308,7 +1311,7 @@ class CompletionTest(_common.TestCase, TestHelper):
# Load bash_completion library.
for path in commands.BASH_COMPLETION_PATHS:
if os.path.exists(util.syspath(path)):
if os.path.exists(syspath(path)):
bash_completion = path
break
else:

View file

@ -25,6 +25,7 @@ from test import _common
from beets import library
from beets import ui
from beets.ui import commands
from beets.util import syspath
class QueryTest(_common.TestCase):
@ -32,7 +33,7 @@ class QueryTest(_common.TestCase):
super().setUp()
self.libdir = os.path.join(self.temp_dir, b'testlibdir')
os.mkdir(self.libdir)
os.mkdir(syspath(self.libdir))
# Add a file to the library but don't copy it in yet.
self.lib = library.Library(':memory:', self.libdir)
@ -42,7 +43,10 @@ class QueryTest(_common.TestCase):
def add_item(self, filename=b'srcfile', templatefile=b'full.mp3'):
itempath = os.path.join(self.libdir, filename)
shutil.copy(os.path.join(_common.RSRC, templatefile), itempath)
shutil.copy(
syspath(os.path.join(_common.RSRC, templatefile)),
syspath(itempath),
)
item = library.Item.from_path(itempath)
self.lib.add(item)
return item, itempath