I had previously tested the `munkres` -> `lapjv` replacement
extensively, so I was today surprised to find that nothing gets matched
correctly when I tried importing some new tracks.
On the other hand I now remember making a small adjustment in the logic
to make autotagging tests pass which is when I introduced a bug: I did
not realize that `lapjv` returns index '-1' for each unmatched item.
This issue did not get caught by tests because this 'unmatched' item
index '-1' anecdotally ended up pointing to the last (expected) item in
the test making it pass.
This commit adjusts the aforementioned test to catch this issue and
fixes the logic to correctly identify unmatched tracks.
This utilises regex substitution in the substitute plugin. The previous
approach only used regex to match the pattern, then replaced it with a
static string. This change allows more complex substitutions, where the
output depends on the input.
### Example use case
Say we want to keep only the first artist of a multi-artist credit, as
in the following list:
```
Neil Young & Crazy Horse -> Neil Young
Michael Hurley, The Holy Modal Rounders, Jeffrey Frederick & The Clamtones -> Michael Hurley
James Yorkston and the Athletes -> James Yorkston
````
This would previously have required three separate rules, one for each
resulting artist. By using a regex substitution, we can get the desired
behaviour in a single rule:
```yaml
substitute:
^(.*?)(,| &| and).*: \1
```
(Capture the text until the first `,` ` &` or ` and`, then use that
capture group as the output)
### Notes
I've kept the previous behaviour of only applying the first matching
rule, but I'm not 100% sure it's the ideal approach.
I can imagine both cases where you want to apply several rules in
sequence and cases where you want to stop after the first match.
This PR refactors the test codebase by removing redundant functions and
simplifying item and album creation. Key changes include:
- Removed redundant `_item_ident` index tracker from `_common.py`.
- Removed `album` function from `_common.py` replacing it with direct
`library.Album` invocations.
- Removed `generate_album_info` and `generate_track_info` functions,
replacing them directly with `TrackInfo` and `AlbumInfo`.
- Updated `setup.cfg` to exclude test helper files from coverage
reports.
- Adjusted the tests regarding the changes, and simplified
`test_mbsync.py`.
These functions were used to generate mock data for tests but have been
replaced with direct instantiation of AlbumInfo and TrackInfo objects.
This change simplifies the test code and removes unnecessary helper
functions.
- Refactored Tekstowo backend to fetch lyrics directly from song pages.
- Added `encode` method to convert artist and title to their URL format,
where non-alphanumeric characters are replaced with underscores.
- Removed the now redundant search functionality and associated tests.
- Simplified `extract_lyrics` method to directly parse lyrics without
any checks.
Since the `for_artist` keyword has been removed from
`ftintitle.contains_feat`, the unit tests need to be updated.
This includes the deletion of the test cases that test the
`for_artist=True` delimiters.
The previous version of the `plugins.feat_tokens` regular expression
only matched "feat. X" parts if preceded by a space. This caused missed
detections in the `ftintitle.contains_feat` function.
This commit adds unit tests for the updated regex that also matches
"feat. X" parts within parentheses and brackets
The variable in `test_ordered_enum` was flagged for naming issues, and
I noticed that `OrderedEnum` is essentially `enum.IntEnum`.
I guess `OrderedEnum` exists because it was created before
`enum.IntEnum` was made available in Python 3.4. We do not need it
anymore though, so it's now gone.
* Replace `noqa` comments in `assert...` method definitions with
a configuration option to ignore these names.
* Use the `__all__` variable to specify importable items from the
module, replacing `*` imports and `noqa` comments for unused imports.
* Address issues with poorly named variables and methods by renaming
them appropriately.
- Fix imports
- Fix pytest issues
- Do not assign lambda as variable
- Use isinstance instead of type to check type
- Rename ambiguously named variables
- Name custom errors with Error suffix
See my comment under #5406 for context
> The build on win32 is failing to install reflink because it's [only
supported until Python
3.7](https://gitlab.com/rubdos/pyreflink/-/blob/master/setup.py?ref_type=heads).
>
> I will address this in a separate PR and rebase this one accordingly
once the fix is merged.
>
> Note: this issue popped up now because I added a new requests-mock
dependency which invalidated cached dependencies.
As noted by 5bf4e3d92f, MusicBrainz
external IDs (`*_album_id`) were only saved for items and not albums.
This commit addresses that by copying `AlbumInfo` fields to the `Album`,
i.e. what's saved in the DB.
This is similar to how `TrackInfo` fields are copied to `Item` instances
except the copying is done at a different time since we only get an
`Album` much later in the import flow.
A constant `preload_plugin` is used to disable loading the plugin in the
`setUp` initialisation, allowing the plugin to be loaded manually by the
tests.
Also added a cleanup instruction to remove listeners from configured
plugins, and removed this logic from several tests.
A couple of `ConfigTest` would previously fail locally since they
somehow depended on the local environment to work fine. This commmit
decouples them from any environment by setting up patching of the
environment variables properly.
Fixes#5229, is part of #5361 and relates to #5285.
I have to admit thsi was a fairly tough task - I initially assumed that
the problem lies
with how the tests are setup, and that we're probably missing some
`teardown_beets` calls
here and there.
Unfortunately, it was not so simple. I came across several issues that
gave rise to
leftover temporary files:
1. `fetchart`, `artresizer` and `play` handling of temporary files.
These plugins created
isolated temporary files outside of the directories that tests clean up.
You will find
I added a couple of functions (namely `get_module_tempdir`) that force
these plugins to
create files in directories determined by their module names. This way
we can clean up
after them using the new `CleanupModulesMixin`.
2. Tests that ran temporary directories setup twice, running
`_common.TestCase.setUp` and
`test.helper.TestHelper.setup_beets`. Both of these ran `self.temp_dir =
mkdtemp()`,
therefore the directories created by the initial setup persisted since
those have been
overridden and thus unreachable in the teardown. Here, I removed the
`setUp` calls, see
- `test/plugins/test_embedart.py`
- `test/test_importer.py`
- and `test/test_plugins.py` where `setup_beets` was called twice
3. `test/test_config_command.py` attempted to manage the temporary
directory by itself,
where I found that `tearDown` failed to remove the directory for four
tests. Could not
figure out the cause, and found that delegating this task to
`TestHelper` fixed the
issue.
4. Mediafile fixture removal depended on calling
`remove_mediafile_fixtures` method, which
`test/plugins/test_zero.py` failed to do. I made the fixtures to be
created within the
same `temp_dir` directory that gets removed in the teardown, so now they
are taken care
of automatically.
In summary, see the test modules that left files behind:
```
Temp files created by test/__init__.py
Temp files created by test/plugins/__init__.py
Temp files created by test/plugins/lyrics_download_samples.py
Temp files created by test/plugins/test_acousticbrainz.py
Temp files created by test/plugins/test_advancedrewrite.py
Temp files created by test/plugins/test_albumtypes.py
Temp files created by test/plugins/test_art.py
/tmp/tmp11nicahe.jpg
/tmp/tmp1bjmodum.png
/tmp/tmped7nhls4.jpg
/tmp/tmpflnzr9wz.jpg
/tmp/tmpjngkauqs.png
/tmp/tmpkzy9mn6t.jpg
/tmp/tmpph_wmuea.jpg
/tmp/tmps6gk58i_.jpg
/tmp/tmpz2eji_o4.jpg
Temp files created by test/plugins/test_aura.py
Temp files created by test/plugins/test_bareasc.py
/tmp/tmphl3kzhug
/tmp/tmpnh2q6v02
/tmp/tmpppw5qrhz
Temp files created by test/plugins/test_beatport.py
Temp files created by test/plugins/test_bucket.py
Temp files created by test/plugins/test_convert.py
Temp files created by test/plugins/test_discogs.py
Temp files created by test/plugins/test_edit.py
Temp files created by test/plugins/test_embedart.py
/tmp/tmp1ayvqzhx
/tmp/tmp58k6mdfx.jpg
/tmp/tmp64c2lqiv
/tmp/tmp6nar4kr5
/tmp/tmp6u0d5dex
/tmp/tmpacoq7w_f
/tmp/tmpajnr_sxr
/tmp/tmpasj16beh
/tmp/tmpboyaixb5
/tmp/tmpcrmcyt5r
/tmp/tmpdomje5g3
/tmp/tmplu3o6t6g
/tmp/tmpns_xvkns
/tmp/tmpo87o1h6o.jpg
/tmp/tmpqem39h_j
/tmp/tmprlzm18pb
/tmp/tmpt22v4u6x
/tmp/tmptp3rxdgv
Temp files created by test/plugins/test_embyupdate.py
Temp files created by test/plugins/test_export.py
Temp files created by test/plugins/test_fetchart.py
Temp files created by test/plugins/test_filefilter.py
Temp files created by test/plugins/test_ftintitle.py
Temp files created by test/plugins/test_hook.py
Temp files created by test/plugins/test_ihate.py
Temp files created by test/plugins/test_importadded.py
Temp files created by test/plugins/test_importfeeds.py
Temp files created by test/plugins/test_info.py
Temp files created by test/plugins/test_ipfs.py
Temp files created by test/plugins/test_keyfinder.py
Temp files created by test/plugins/test_lastgenre.py
Temp files created by test/plugins/test_limit.py
Temp files created by test/plugins/test_lyrics.py
Temp files created by test/plugins/test_mbsubmit.py
Temp files created by test/plugins/test_mbsync.py
Temp files created by test/plugins/test_mpdstats.py
Temp files created by test/plugins/test_parentwork.py
Temp files created by test/plugins/test_permissions.py
Temp files created by test/plugins/test_player.py
Temp files created by test/plugins/test_playlist.py
Temp files created by test/plugins/test_play.py
/tmp/tmp6ohknmve.m3u
/tmp/tmp8rw2z_j4.m3u
/tmp/tmp9vi27ypx.m3u
/tmp/tmpa_s66jh8.m3u
/tmp/tmpb7h3cn3n.m3u
/tmp/tmpexbmqvry.m3u
/tmp/tmpinbqrt80.m3u
/tmp/tmpql02hax5.m3u
/tmp/tmpvbdzprsf.m3u
/tmp/tmpzipim36x.m3u
Temp files created by test/plugins/test_plexupdate.py
Temp files created by test/plugins/test_plugin_mediafield.py
Temp files created by test/plugins/test_random.py
Temp files created by test/plugins/test_replaygain.py
Temp files created by test/plugins/test_smartplaylist.py
Temp files created by test/plugins/test_spotify.py
Temp files created by test/plugins/test_subsonicupdate.py
Temp files created by test/plugins/test_the.py
Temp files created by test/plugins/test_thumbnails.py
Temp files created by test/plugins/test_types_plugin.py
Temp files created by test/plugins/test_web.py
Temp files created by test/plugins/test_zero.py
/tmp/tmp3ub9xmzy
Temp files created by test/rsrc/beetsplug/test.py
Temp files created by test/rsrc/convert_stub.py
Temp files created by test/testall.py
Temp files created by test/test_art_resize.py
/tmp/tmp3p7p60ih.jpg
/tmp/tmp8exclgit.jpg
/tmp/tmpkrrjsitl.jpg
/tmp/tmpw6n8ee8e.jpg
/tmp/tmpygws_0aw.jpg
Temp files created by test/test_autotag.py
Temp files created by test/test_config_command.py
/tmp/tmp333f0r2j
/tmp/tmphr356z5r
/tmp/tmporp4rag2
/tmp/tmpy7sjqdsw
Temp files created by test/test_datequery.py
Temp files created by test/test_dbcore.py
Temp files created by test/test_files.py
Temp files created by test/test_hidden.py
Temp files created by test/test_importer.py
/tmp/tmp0m363gfb
/tmp/tmp2n3i13mc
/tmp/tmpxk3v304s
Temp files created by test/test_library.py
Temp files created by test/test_logging.py
Temp files created by test/test_m3ufile.py
Temp files created by test/test_mb.py
Temp files created by test/test_metasync.py
Temp files created by test/test_pipeline.py
Temp files created by test/test_plugins.py
/tmp/tmp6pxhx67u
/tmp/tmpb8pqi9ui
/tmp/tmpcx_658g7
/tmp/tmp_giqb9jz
/tmp/tmpgm9xk94_
/tmp/tmpk60l6bt3
/tmp/tmpqoj4la68
/tmp/tmptcdu20rp
/tmp/tmpvr7k5shn
/tmp/tmpwnfnzs91
Temp files created by test/test_query.py
Temp files created by test/test_sort.py
Temp files created by test/test_template.py
Temp files created by test/test_ui_commands.py
/tmp/tmpns2u94w6
Temp files created by test/test_ui_importer.py
Temp files created by test/test_ui_init.py
Temp files created by test/test_ui.py
Temp files created by test/test_util.py
Temp files created by test/test_vfs.py
```
And that's what we have right now:
```
Temp files created by test/__init__.py
Temp files created by test/plugins/__init__.py
Temp files created by test/plugins/lyrics_download_samples.py
Temp files created by test/plugins/test_acousticbrainz.py
Temp files created by test/plugins/test_advancedrewrite.py
Temp files created by test/plugins/test_albumtypes.py
Temp files created by test/plugins/test_art.py
Temp files created by test/plugins/test_aura.py
Temp files created by test/plugins/test_bareasc.py
Temp files created by test/plugins/test_beatport.py
Temp files created by test/plugins/test_bucket.py
Temp files created by test/plugins/test_convert.py
Temp files created by test/plugins/test_discogs.py
Temp files created by test/plugins/test_edit.py
Temp files created by test/plugins/test_embedart.py
Temp files created by test/plugins/test_embyupdate.py
Temp files created by test/plugins/test_export.py
Temp files created by test/plugins/test_fetchart.py
Temp files created by test/plugins/test_filefilter.py
Temp files created by test/plugins/test_ftintitle.py
Temp files created by test/plugins/test_hook.py
Temp files created by test/plugins/test_ihate.py
Temp files created by test/plugins/test_importadded.py
Temp files created by test/plugins/test_importfeeds.py
Temp files created by test/plugins/test_info.py
Temp files created by test/plugins/test_ipfs.py
Temp files created by test/plugins/test_keyfinder.py
Temp files created by test/plugins/test_lastgenre.py
Temp files created by test/plugins/test_limit.py
Temp files created by test/plugins/test_lyrics.py
Temp files created by test/plugins/test_mbsubmit.py
Temp files created by test/plugins/test_mbsync.py
Temp files created by test/plugins/test_mpdstats.py
Temp files created by test/plugins/test_parentwork.py
Temp files created by test/plugins/test_permissions.py
Temp files created by test/plugins/test_player.py
Temp files created by test/plugins/test_playlist.py
Temp files created by test/plugins/test_play.py
Temp files created by test/plugins/test_plexupdate.py
Temp files created by test/plugins/test_plugin_mediafield.py
Temp files created by test/plugins/test_random.py
Temp files created by test/plugins/test_replaygain.py
Temp files created by test/plugins/test_smartplaylist.py
Temp files created by test/plugins/test_spotify.py
Temp files created by test/plugins/test_subsonicupdate.py
Temp files created by test/plugins/test_the.py
Temp files created by test/plugins/test_thumbnails.py
Temp files created by test/plugins/test_types_plugin.py
Temp files created by test/plugins/test_web.py
Temp files created by test/plugins/test_zero.py
Temp files created by test/rsrc/beetsplug/test.py
Temp files created by test/rsrc/convert_stub.py
Temp files created by test/testall.py
Temp files created by test/test_art_resize.py
Temp files created by test/test_autotag.py
Temp files created by test/test_config_command.py
Temp files created by test/test_datequery.py
Temp files created by test/test_dbcore.py
Temp files created by test/test_files.py
Temp files created by test/test_hidden.py
Temp files created by test/test_importer.py
Temp files created by test/test_library.py
Temp files created by test/test_logging.py
Temp files created by test/test_m3ufile.py
Temp files created by test/test_mb.py
Temp files created by test/test_metasync.py
Temp files created by test/test_pipeline.py
Temp files created by test/test_plugins.py
Temp files created by test/test_query.py
Temp files created by test/test_sort.py
Temp files created by test/test_template.py
Temp files created by test/test_ui_commands.py
Temp files created by test/test_ui_importer.py
Temp files created by test/test_ui_init.py
Temp files created by test/test_ui.py
Temp files created by test/test_util.py
Temp files created by test/test_vfs.py
```
Note that the command which provides the output is now available through
`poe`.
Due to some weird race conditions the temporary directories were not
getting torn down for four of the tests. I failed to figure out why, and
I found that using TestHelper to manage the temporary directory somehow
fixes this.
This way they get automatically removed with removal of temp_dir.
After this change `test/plugins/test_zero.py` does not any more leave
any mediafield fixtures hanging around.
And move the definition to a shared module.
The problem was that EmbedartCliTest ran `_common.TestCase.setUp`
method which initialised temporary directory for the tests AND ran
`helper.TestHelper.setup_beets` method which initialised another set of
temporary directories. This meant that the first set of directories
could not be tracked down for the cleanup.
In order to include the table name for fields in this query, use the
`field_query` method.
Since `AnyFieldQuery` is just an `OrQuery` under the hood, remove it and
construct `OrQuery` explicitly instead.
Unify query creation logic from
- queryparse.py:construct_query_part,
- Model.field_query,
- DefaultTemplateFunctions._tmpl_unique
to a single implementation under `LibModel.field_query` class method.
This method should be used for query resolution for model (flex)fields.
Allow filtering item attributes in album queries and vice versa by
merging `flex_attrs` from Album and Item together as `all_flex_attrs`.
This field is only used for filtering and is discarded after.
For a flexible attribute query, replace the `col_name` property with
a function call that extracts that attribute from the `field_attrs`
field introduced in the earlier commit.
Additionally, for boolean, numeric and date queries CAST the value to
NUMERIC SQLite affinity to ensure that our queries like 'flex:1..5' and
'flex:true' continue working fine.
This removes the concept of 'slow query', since every query for any
field now has an SQL clause.
In #4746 I was making a small adjustment in beetsplug/aura.py and found
that the module wasn't tested. So this PR adds some high-level tests to
act a safeguard for any future adjustments.
The docs say:
> The `auto` option uses reflinks when possible and falls back to plain
> copying when necessary.
I've been using this option for a while, and recently discovered that
despite the option, copying fails between two BTRFS filesystems with:
Error: OS/filesystem does not support reflinks. during link of paths /mnt/fs1/file, /mnt/fs2/file
I tracked this down to how the configuration is handled in the importer.
Remove 'NamedQuery' since it is not being used by any queries any more.
This simplifies query parsing logic in 'queryparse.py'. We know that
this logic can only receive 'FieldQuery' thus I adjusted types and
removed the logic that handles other cases.
Effectively, this means that the query parsing logic does not any more
care whether the query is named by the corresponding DB field. Instead,
queries like 'SingletonQuery' and 'PlaylistQuery' are responsible for
translating 'singleton' and 'playlist' to the underlying DB filters.
When unix tools make use of an external editor, they typically check the
environment variable VISUAL and fall back to EDITOR. This commit adds the
additional check for VISUAL to the existing EDITOR check (where VISUAL is
preferred over EDITOR).
Allow generating extm3u playlists so that they contain additional item fields such as the `id`.
The feature is required by the mgoltzsche/beets-webm3u plugin (M3U server) to transform playlists using a request based item URI template which may require additional fields such as the `id`, e.g. `beets:library:track;$id`.
- generic method to check if operation was performed
- add test of deinterlace operation
- add test for multiple operations performed if required (fails on master)
External Python packages interfacing beets may want to use an in-memory
beets library instance for testing beets-related code.
The `TestHelper` class is very helpful for this purpose.
Previously `TestHelper` was located in the `test/` directory.
Now it is part of `beets` itself (`beets.test.helper.TestHelper`) and
can be easily imported.
The import path mangling is not relevant (anymore?) for the two
ways of running tests:
* `python3 test/testall.py` (see CONTRIBUTING.rst):
The `testall.py` script already adds the project path to `sys.path`.
* `tox -e py-cov`: this command is supposed to be run from the project
path. Thus, the current directory is already the first of location
in `sys.path`.
The previous mangling of the import path while loading a module could
lead to unwanted side-effects hidden in an unexpected location.
Instead, import path mangling should take place in the script being
called by the user (here: `testall.py`).
Beets web API already allows remote players to access audio files but it doesn't provide a way to expose the playlists defined using the smartplaylist plugin.
Now the smartplaylist plugin provides an option to generate ID-based item URIs/URLs instead of paths.
Once playlists are generated this way, they can be served using a regular HTTP server such as nginx.
To provide sufficient flexibility for various ways of integrating beets remotely (e.g. beets API, beets API with context path, AURA API, mopidy resource URI, etc), the new option has been defined as a template with an `$id` placeholder (assuming each remote integration requires a different path schema but they all rely on using the beets item `id` as identifier/path segment).
To prevent local path-related plugin configuration from leaking into a HTTP URL-based playlist generation (invoked with CLI option in addition to the local playlists generated into another directory), setting the new option makes the plugin ignore the other path-related options `prefix`, `relative_to`, `forward_slash` and `urlencode`.
Usage examples:
* `beet splupdate --uri-format 'http://beets:8337/item/$id/file'` (for beets web API)
* `beet splupdate --uri-format 'http://beets:8337/aura/tracks/$id/audio'` (for AURA API)
(While it was already possible to generate playlists containing HTTP URLs previously using the `prefix` option, it did not allow to generate ID-based URLs pointing to the beets web API but required to expose the audio files using a web server directly and refer to them using their file system `$path`.)
Relates to #5037
The boolean flags `--extm3u` and `--no-extm3u` are replaced with a string option `--output=m3u|m3u8`.
This reduces the amount of options and allows to evolve the CLI to support more playlist output formats in the future (e.g. JSON) without polluting the CLI at that point.
This assertion was silently a no-op for years, and it apparently fails.
So since we were not even testing it before, it is at least no worse to
just remove it.
AFAICT, `mock.has_calls` *never* existed. It only started emitting a
warning recently. And for some reason I only see this crop up on
Windows? Truly mysterious.
Improve the split_into_lines regex and whitespace handling
so that spaces are handled and colored text can be wrapped
Create a new test suite for the color splitting function as
it was previously introducing rogue escape characters when
splitting colorized words.
- Allow user to change UI colors in config file.
- "Change Representation" class allows Albums and Track
matches to reuse similar formatting code
- Functions to split text into lines for printing
- Tests for the new UI to check wrapping functions
Adds the following fields with id3v2.4 multi-valued tag support to autotag:
- artists, artists_sort, artists_credit
- albumartists, albumartists_sort, albumartists_credit
- mb_artistids, mb_albumartistids
MusicBrainz support to populate + write the above multi-valued tags by default. Can be toggled to use id3v2.3 or id3v2.4 tags via the existing beets configuration option `id3v23`.
Big thanks to @JOJ0, @OxygenCobalt, @arsaboo for testing + @sampsyo for the initial code review .
by using store(inherit=False) for the creation of a new "ipfs album" as well as
when test_ipfs creates album+items to compare with.
Or put differently: Make ipfs and test_ipfs keep the old store() behaviour for
which the plugin initially was built for.
by adding files which are not completely silent, thus hitting a different
code path in some calculations
The sample files were generated using
> sox -n whitenoise.flac synth 00:00:02 whitenoise
> ffmpeg -i whitenoise.flac whitenoise.opus
> ffmpeg -i whitenoise.flac whitenoise.mp3
this replaces assertions of the form
self.assertTrue(os.path.exists(syspath(path)))
by
self.assertExists(path)
which includes the syspath conversion and is much easier to read.
Occurences where located using
git grep -E 'assert(True|False).*(isdir|isfile|exist)'
these are mostly in the tests, which didn't cause issues since the
affected directories usually have nice ASCII paths. For consistency, it
is nicer to always invoke syspath. That also avoids deprecation warnings
for the bytestring interfaces on Python <= 3.5. The bytestring
interfaces were undeprecated with PEP 529 in Python 3.6, such that we
didn't observe any actual failures.
- Add NamedQuery abstract class to be able to express the expectation
that a query should be such a query (and have a specific constructor
signature) in construct_query_part
- slightly (and probably completely irrelevantly) improve Query.__hash__
- also, sprinkle some ABC/abstractmethod around to clarify things
A small change, as discussed in #4798, to avoid depending on the reflink
library to run *all* tests. The library seems to be unmaintained and it
may be annoying to install.
and shorten second and third test a little by providing -y cli flag. Enough to
test with interactive input once. Move all 3 tests to the very bottom of the
test class.
This slightly speeds up the queries and there's a nice side-effect where
`singleton:1` and `singleton:0` now work fine!
This is ultimately building towards replacing as many python-only
queries with SQL equivalents.
- M3UFile.read() method reads in rb mode.
- M3UFile.read() method handles removal of (platform specific) line endings.
- Playlist contents and EXTM3U header is handled as bytes.
- Fix test_playlist*read* tests to encode playlist UTF-8 assert strings to
bytes using bytestring_path() before comparision.
- Fixture playlist_windows.m3u8 is now actually Windows formatted (\r\n + BOM)
Make sure we stay with the beets standard of handling everything internally as
bytes.
- M3UFile.write() method writes in wb mode.
- Playlist contents and EXTM3U header is handled as bytes.
- item.destination() gives us unicode string paths, we tranlate to bytes
using util.bytestring_path().
- Fix test_playlist*write* tests to encode UTF-8 assert strings as bytes using
bytestring_path() before comparision.
Add test_playlist_write_and_read_unicode_windows: Writes 2 media file
paths containing unicode characters, reads them in using M3UFile class
again and tests if the contents is correct.
- Add and Exception class called EmptyPlaylistError ought to be raised
when playlists without files are loaded or saved.
- Add a test for it in test_m3ufile
- Fix media_files vs. media_list attribute name.
- We introduce a new submodule of beets.util named id_extractors.
- Parts of the ID extraction utilites required by metadata source plugins
should live there.
- Also this enables future usage of those utilities from the "outside" of
metadata source plugins.
- Move Discogs ID extractor to the new module and change test_discogs to use
the new location.
- Add spotify_id_regex variable to the new module.
- samefile exists on all platforms for recent python
- don't rely on monkey-patching os/os.path and on specifics on the
implementation: as a result of doing so, the tests start failing in
obscure ways as soon as the implementation (and its usage of
os.path.exists and os.path.samefile) is changed
- move tests for case_sensitive to test_util.py, since this is not
really the concern of PathQueryTest
- removes part of the tests, since the tests that patch os.path.samefile
and os.path.exists are super brittle since they test the
implementation rather than the functionality of case_sensitive().
This is a prepartory step for actually changing the implementation,
which would otherwise break the tests in a confusing way...
This was a helper for situations when Python 2 and 3 APIs returned bytes
and unicode, respectively. In these situation, we should nowadays know
which of the two we receive, so there's no need to wrap & hide the
`bytes.decode()` anymore (when it is still required).
Detailed justification:
beets/ui/__init__.py:
- command line options are always parsed to str
beets/ui/commands.py:
- confuse's config.dump always returns str
- open(...) defaults to text mode, read()ing str
beetsplug/keyfinder.py:
- ...
beetsplug/web/__init__.py:
- internally, paths are always bytestrings
- additionally, I took the liberty to slighlty re-arrange the code: it
makes sense to split off the basename first, since we're only
interested in the unicode conversion of that part.
test/helper.py:
- capture_stdout() gives a StringIO, which yields str
test/test_ui.py:
- self.io, from _common.TestCase, ultimately contains a
_common.DummyOut, which appears to be dealing with str (cf.
DummyOut.get)
used to work due to inconsistent mediafile implementation, but with
https://github.com/beetbox/mediafile/pull/64 (in mediafile >= 0.11.0)
list fields are None if non-existent, not the empty list
On Windows, converting command-line arguments (hopefully!!!) only needs
to deal with valid strings from the OS. So it is not really relevant to
test with non-UTF-8, non-surrogate bytes.
Unidecode 1.3.5 (a yanked PyPI version) changed the behavior of
Unidecode for some specific characters:
> Remove trailing space in replacements for vulgar fractions.
As luck would have it, our tests used the 1/2 character specifically to
test the behavior when these characters decoded to contain slashes. We
now pin a sufficiently recent version of Unidecode and adapt the tests
to match the new behavior.
Quoth the responses documentation:
> querystring is matched by default
Not sure how recent this is, unfortunately---but probably 0.17.0, since
that's the version where `match_querystring` was deprecated.
Makes the dispatch to the chosen backend simpler in the thumbnails
plugin. Given that ArtResizer is not only about resizing art anymore,
these methods fit there quite nicely.
- Adds a configuration that, when enabled, will append the style to genre
- Rationale is to have more verbose genres in genre tag of players that only support genre
This didn't cause any issues since we only use the shared instance
anyway, but logically it doesn't make a lot of sense for the backends
always using ArtResizer.shared (which they should be oblivious of).
Uses a custom assertion to have more detailed output (which should help
with getting an idea what the difference between expected and actual
lyrics is), and use `subTest` to clearly associate test failure to a
backend.
In addition, this strip parenthesis from the lyrics' words. That helps
with the currently failing Tekstowo test, but doesn't entirely fix it.
Requires Python 3.4 for subTest.
Test more combinations of tags that might initially be present and
expected tags. The R128 codepath and the case of having the wrong type
of tags wasn't really tested before.
Another incorrect py2 -> py3 translation. Since python 3 attached the
traceback to the exception, this should preserve the traceback without
needing to resort to sys.exc_info
This is an incorrect translation of a python 2 reraise to python 3.
With python 3, however, we can just rely on exception chaining to get
the traceback, so get rid of the complicated re-raising entirely, with
the additional benefit that the exception from the tear-down is also
shown.
When the delete_originals was set, beets would print the following, regardless
of the presence of the quiet parameter:
convert: Removing original file /path/to/file.ext
This commit ensures that the log is only printed when quiet is not present.
Unit test may fails when path to temprorary library contains `.`; to
garantue that bug wasn't here, it forces to use one more `.` inside path.
Fixes: https://github.com/beetbox/beets/issues/4151