diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08f0b0e..44f4019 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,13 +10,17 @@ on: jobs: sonarcloud: runs-on: ubuntu-latest + strategy: + matrix: + python: [3.6, 3.7, 3.8, 3.9, "3.10"] + beets: [1.4.9, 1.5.0, 1.6.0] steps: - uses: actions/checkout@v2 with: # Disabling shallow clone is recommended for improving relevancy of reporting fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v2 - name: Install dependencies @@ -24,7 +28,9 @@ jobs: python -m pip install --upgrade pip setuptools wheel pip install poetry==1.2.0a2 poetry install --sync + pip install beets==${{ matrix.beets }} - name: Pytest + continue-on-error: true run: | poetry run pytest - name: Coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 0087cd0..479ceba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,67 @@ +## [0.12.0] 2022-02-10 + +### Added + +- `album`: following MusicBrainz [title format specification], strings **EP** and **LP** are from now on kept in place in album names. +- `catalognum`: To find the catalog number, we have previously been looking at the release title and pointers such as **Catalogue Number:** within the release description. + + In addition to the above, we now apply a generic search pattern across the rest of the text, including media title, media description and the rest of the release description. + + For those interested, at a high level the pattern used in the search looks like below + + ```perl + ( + [A-Z .]+\d{3} # HANDS D300 + | [A-z ][ ]0\d{2,3} # Persephonic Sirens 012 + | [A-Z-]{2,}\d+ # RIV4 + | [A-Z]+[A-Z.$-]+\d{2,} # USE202, HEY-101, LI$025 + | [A-Z.]{2,}[ ]\d{1,3} # OBS.CUR 9 + | \w+[A-z]0\d+ # 1ØPILLS018, fa036 + | [a-z]+(cd|lp)\d+ # ostgutlp45 + | [A-z]+\d+-\d+ # P90-003 + ) + ( # optionally followed by + [ ]?[A-Z] # IBM001V + | [.][0-9]+ # ISMVA002.1 + | -?[A-Z]+ # PLUS8024CD + )? + ``` + +- `albumtype`: similar to the `catalognum`, the descriptions are searched for **EP** and **LP** strings presence to find out the `albumtype`. + +- `track`: Support for tracks that do not use dash (`-`) but some other character to separate pieces of information in track names. For example, consider the following [tracklist]: + + ``` + A1 | WHITESHADOWHURTS x TOXICSPIKEBACK | Arcadia + A2 | WHITESHADOWHURTS | Corrupted Entity + A3 | WHITESHADOWHURTS | Colosseo + B1 | TOXICSPIKEBACK | Eclipse + B2 | TOXICSPIKEBACK | Eclipse [DJ LINT's Tribe Mix] + B3 | WHITESHADOWHURTS | Corrupted Entity [OAT.M's Oldschool Mix] + ``` + + `beetcamp` now finds that `|` is being used as the delimiter and parses values for `track_alt`, `artist` and `title` accordingly. + +### Updated + +- singleton: `album` and `albumartist` fields are not anymore populated. +- `catalognum`: artists like **PROCESS 404** are not assumed to be catalogue numbers anymore. +- `track_alt`: allow non-capital letters, like **a1** to be parsed and convert them to capitals. +- `albumartist`: use **Various Artists** (or equivalent) when a release includes more than four different artists. Until now we've only done so for compilations. +- `genre`: genres are now sorted alphabetically + +### Fixed + +- Support for `beets<1.5` has been broken since `0.11.0`, - it should now work fine. However, fields such as `comments` and `lyrics` are not available, and album-like metadata like `catalognum` is not available for singletons. Thanks **@zane-schaffer** for reporting this issue (Closes #22). +- `singleton`: `catalognum`, if found, is now reliably removed from the title. +- `track.title`: `-` delimiter is handled more appropriately when it is found in the song title. +- `albumartist`: for the Various Artists releases, the plugin will now use the globally configured `va_name` field instead of hard-coding _Various Artists_. +- `artist`: Recent bandcamp updates of the JSON data removed artists names from most of compilations, therefore we are again having a peek at the raw HTML data to fetch the data from there. + +[tracklist]: https://scumcllctv.bandcamp.com/album/scum002-arcadia +[title format specification]: https://beta.musicbrainz.org/doc/Style/Titles +[0.12.0]: https://github.com/snejus/beetcamp/releases/tag/0.12.0 + ## [0.11.0] 2021-11-12 ### Added @@ -62,6 +126,7 @@ data - this is now handled gracefully. [musicbrainz genres]: https://beta.musicbrainz.org/genres +[0.11.0]: https://github.com/snejus/beetcamp/releases/tag/0.11.0 ## [0.10.1] 2021-09-13 @@ -70,7 +135,7 @@ - Fixed #18 by handling cases when a track duration is not given. - Fixed #19 where artist names like **SUNN O)))** would get incorrectly mistreated by the album name cleanup logic due to multiple consecutive parentheses. The fix involved - adding some rules around it: they are now deduped _only if_ + adding some rules around it: they are now deduplicated _only if_ - they are preceded with a space - or they enclose remix / edit info and are the last characters in the album name @@ -154,7 +219,7 @@ Thanks @arogl for reporting each of the above! - `label`: some releases embed the `recordLabel` field into the json data - it now gets prioritized over the publisher name when it is available. - `track.title`: clean up `*digital only*` properly. Previously we did not account for - asterixes + asterisks ### Fixed @@ -180,7 +245,7 @@ Thanks @arogl for reporting each of the above! ### Added -- Added a github action to run ci for `master` and `dev` branches. For now it's just a minimal +- Added a github action to run CI for `master` and `dev` branches. For now it's just a minimal configuration and will probably get updated soon. ## [0.9.1] 2021-06-04 @@ -194,19 +259,19 @@ Thanks @arogl for reporting each of the above! been fixed regarding the MusicBrainz description: release composed of the same title and multiple remixes is a single. - Use `ep` only if _EP_ is mentioned either in the album name or the disc title. -- `album.catalognum`: Make the _DISCTITLE_ uppercase before looking for the catalogue +- `album.catalognum`: Make the _DISCTITLE_ uppercase before looking for the catalog number. - `album.media`: Exclude anything that contains _bundle_ in their names. These usually contain additional releases that we do not need. - `track.title`: Clean `- DIGITAL ONLY` (and similar) when it's preceded by a dash and not - enclosed by parens or square brackets. + enclosed by parentheses or square brackets. - `track.track_alt`: Having witnessed a very creative track title **E7-E5**, limit the `track_alt` field number to the range **0-6**. -- Committed a JSON testcase which was supposed to be part of `0.9.0`. +- Committed a JSON test case which was supposed to be part of `0.9.0`. ### Added -- Extend `url2json` with `--tracklist-for-tests` to ease adding new testcases. +- Extend `url2json` with `--tracklist-for-tests` to ease adding new test cases. ## [0.9.0] 2021-06-01 @@ -219,12 +284,12 @@ Thanks @arogl for reporting each of the above! ### Added - The `comments` field now includes the media description and credits. -- The description is searched for artist and album names in addition to the catalogue +- The description is searched for artist and album names in addition to the catalog number. ### Updated -- All testcases are now pretty JSON files - this should bring more transparency around +- All test cases are now pretty JSON files - this should bring more transparency around the adjustments that Bandcamp make in the future (once they get updated). The `url2json` tool has `-u` flag that updates them automatically. @@ -248,7 +313,7 @@ Thanks @arogl for reporting each of the above! - Parsing / logic: - - Token `feat.` is now recognised as a valid member of the `artist` field. + - Token `feat.` is now recognized as a valid member of the `artist` field. - `free download`, `[EP|LP]`, `(EP|LP)`, `E.P.`, `LP` are now cleaned from the album name. - Updated `albumtype` logic: in some `compilation` cases track artists would go missing and get set to _Various Artists_ - instead it now defaults to the original @@ -271,7 +336,7 @@ Thanks @arogl for reporting each of the above! ### Added -- Release description is now checked for the catalogue number. +- Release description is now checked for the catalog number. - Added a test based on parsing _the JSON output_ directly without having to parse the entire HTML. Bandcamp have been moving away from HTML luckily, so let's hope the trend continues. @@ -305,7 +370,7 @@ Thanks @arogl for reporting each of the above! - Artist name (unless it's a singleton track) - Label name - - Catalogue number + - Catalog number - Strings - **Various Artists** - **limited edition** @@ -328,7 +393,7 @@ Thanks @arogl for reporting each of the above! - Added _recommended_ installation method in the readme. - Added tox tests for `beets < 1.5` and `beets > 1.5` for python versions from 3.6 up to 3.9. -- Sped up reimporting bandcamp items by checking whether the URL is already available +- Sped up re-importing bandcamp items by checking whether the URL is already available before searching. - Parsing: If track's name includes _bandcamp digital (bonus|only) etc._, **bandcamp** part gets removed as well. @@ -391,7 +456,7 @@ Thanks @arogl for reporting each of the above! ### Updated -- Catalogue number parser now requires at least two digits to find a good match. +- Catalog number parser now requires at least two digits to find a good match. ## [0.5.5] 2021-01-30 @@ -419,8 +484,8 @@ Thanks @arogl for reporting each of the above! - Handle a sold-out release where the track listing isn't available, which would otherwise cause a KeyError. -- Catalogue number parser should now forget that cassette types like **C30** or **C90** - could be valid catalogue numbers. +- Catalog number parser should now forget that cassette types like **C30** or **C90** + could be valid catalog numbers. ### Updated @@ -430,8 +495,7 @@ Thanks @arogl for reporting each of the above! ### Fixed -- For data that is parsed directly from the html, ampersands are now correctly - unescaped. +- For data that is parsed directly from the html, ampersands are now correctly unescaped. ## [0.5.2] 2021-01-18 @@ -487,7 +551,7 @@ Thanks @arogl for reporting each of the above! ### Fixed -- `catalognum` parser used to parse `Vol.30` or `Christmas 2020` as catalogue +- `catalognum` parser used to parse `Vol.30` or `Christmas 2020` as catalog number - these are now excluded. It's likely that additional patterns will come up later. @@ -507,5 +571,5 @@ Thanks @arogl for reporting each of the above! - The pipeline now uses generators, therefore the plug-in searches until it finds a good fit and won't continue further (same as the musicbrainz autotagger) -- Extended the parsing functionality with data like catalogue number, label, +- Extended the parsing functionality with data like catalog number, label, country etc. The full list is given in the readme. diff --git a/beetsplug/bandcamp/__init__.py b/beetsplug/bandcamp/__init__.py index 08ae940..31cca65 100644 --- a/beetsplug/bandcamp/__init__.py +++ b/beetsplug/bandcamp/__init__.py @@ -135,7 +135,7 @@ def guru(self, url: str, html: Optional[str] = None) -> Optional[Metaguru]: if not html: html = self._get(url) if html: - self._gurucache[url] = Metaguru(html, self.config.flatten()) + self._gurucache[url] = Metaguru.from_html(html, self.config.flatten()) return self._gurucache.get(url) def loaded(self) -> None: @@ -212,7 +212,7 @@ def track_for_id(self, track_id: str) -> Optional[TrackInfo]: def handle(self, guru: Metaguru, attr: str, _id: str) -> Any: try: return getattr(guru, attr) - except (KeyError, ValueError, AttributeError): + except (KeyError, ValueError, AttributeError, IndexError): self._info("Failed obtaining {}", _id) return None except Exception: # pylint: disable=broad-except diff --git a/beetsplug/bandcamp/_metaguru.py b/beetsplug/bandcamp/_metaguru.py index c5481d8..1b75c54 100644 --- a/beetsplug/bandcamp/_metaguru.py +++ b/beetsplug/bandcamp/_metaguru.py @@ -3,12 +3,25 @@ import json import operator as op import re +from collections import Counter, defaultdict from datetime import date, datetime from functools import partial -from math import floor -from typing import Any, Dict, Iterable, List, Optional, Pattern, Set, Tuple +from html import unescape +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Pattern, + Set, + Tuple, + Union, +) from unicodedata import normalize +from beets import config as beets_config from beets.autotag.hooks import AlbumInfo, TrackInfo from cached_property import cached_property from pkg_resources import get_distribution, parse_version @@ -38,32 +51,59 @@ } VA = "Various Artists" -_catalognum = r"(\b[A-Za-z]([^-.\s\d]|[.-][^0-9])+(([-.]?|[A-Z]\s)\d+|\s\d{2,})[A-Z]?(?:[.-]?\d|CD)?\b)" # noqa -_catalognum_header = r"(?:Catalog(?:ue)?(?: (?:Number|N[or]))?|Cat N[or])\.?:" +_catalognum = r"""(?(?i:[1-5]|single|double|triple))(LP)? ?x? ?((7|10|12)" )?Vinyl' - ), + "remix_or_ft": re.compile(r" [\[\(].*(?i:mix|edit|f(ea)?t\.).*"), + "track_alt": re.compile(r"([ABCDEFGH]{1,3}[0-6])\W+", re.I), + "vinyl_name": re.compile(r"[1-5](?= ?(xLP|LP|x))|single|double|triple", re.I), } def urlify(pretty_string: str) -> str: """Transform a string into bandcamp url.""" - name = pretty_string.lower().replace("'", "") + name = pretty_string.lower().replace("'", "").replace(".", "") return re.sub("--+", "-", re.sub(r"\W", "-", name, flags=re.ASCII)).strip("-") @@ -71,171 +111,183 @@ class Helpers: @staticmethod def get_vinyl_count(name: str) -> int: conv = {"single": 1, "double": 2, "triple": 3} - match = re.search(PATTERNS["vinyl_name"], name) - if not match: - return 1 - count: str = match.groupdict()["count"] - return int(count) if count.isdigit() else conv[count.lower()] + for match in PATTERNS["vinyl_name"].finditer(name): + count = match.group() + return int(count) if count.isdigit() else conv[count.lower()] + return 1 @staticmethod - def clean_digital_only_track(name: str) -> Tuple[str, bool]: - """Return cleaned title and whether this track is digital-only.""" + def clear_digi_only(name: str) -> str: + """Return the track title which is clear from digi-only artifacts.""" clean_name = name for pat in PATTERNS["digital"]: # type: ignore clean_name = pat.sub("", clean_name) - if clean_name != name: - return clean_name, True - return clean_name, False + return clean_name @staticmethod - def parse_track_name(name: str, catalognum: str = "") -> Dict[str, Optional[str]]: - track: Dict[str, Optional[str]] = { - a: None for a in ["track_alt", "artist", "title", "main_title"] - } - - # remove catalognum if given - if catalognum: - name = name.replace(catalognum, "").strip(", ") - - # remove leading numerical index if found - name = re.sub(r"^[01]?[0-9][. ]\s?(?=[A-Z])", "", name).strip(", ") + def parse_track_name(name: str, delim: str = "-") -> Dict[str, str]: + track: Dict[str, str] = defaultdict(str) # match track alt and remove it from the name - match = PATTERNS["track_alt"].match(name) - if match: - track_alt = match.expand(r"\1") - track["track_alt"] = track_alt - name = name.replace(track_alt, "") - - # do not strip a period from the end since it could end with an abbrev - name = name.lstrip(".") - # in most cases that's the delimiter between the artist and the title - parts = re.split(r"\s?-\s|\s-\s?", name.strip(",- ")) - - # title is always given - track["title"] = parts.pop(-1) - track["main_title"] = PATTERNS["remix_or_ft"].sub("", track["title"]) - - # whatever is left must be the artist - if len(parts): - track["artist"] = ", ".join(parts).strip(",. ") + def get_trackalt(name: str) -> Tuple[str, str]: + match = PATTERNS["track_alt"].match(name) + track_alt = "" + if match: + track_alt = match.expand(r"\1").upper() + # do not strip a period from the end since it may end with an abbrev + name = name.replace(match.group(), "") + return name, track_alt + + name, track_alt = get_trackalt(name) + parts = name.split(f" {delim} ") + if len(parts) == 1: + parts = re.split(fr" [{delim}]|[{delim}] ", name) + parts = list(map(lambda x: x.strip(" -"), parts)) + + title = parts.pop(-1) + artist = ", ".join(sorted(set(parts))) + artist = re.sub(r" \(.*mix.*", "", artist).strip(",") + artist = re.sub(r"[(](f(ea)?t.*)[)]", r"\1", artist) + if not track_alt: + title, track_alt = get_trackalt(title) + track.update(title=title, artist=artist) + if track_alt: + track.update(track_alt=track_alt) + track.update(main_title=PATTERNS["remix_or_ft"].sub("", title)) return track @staticmethod - def get_track_artist(parsed_artist, raw_track, albumartist): - # type: (Optional[str], JSONDict, str) -> str - """Return the first of the following options, if found: - 1. Parsed artist from the track title - 2. Artist specified by Bandcamp (official) - 3. Albumartist (worst case scenario) - """ - official_artist = raw_track.get("byArtist", {}).get("name", "") - return parsed_artist or official_artist or albumartist - - @staticmethod - def parse_catalognum(album: str, disctitle: str, description: str, label: str) -> str: - """Try finding the catalogue number in the following sequence: - 1. Check description for a formal catalogue number - 2. Check album name for [CATALOGNUM] or (CATALOGNUM) - 3. Check whether label name is followed by numbers - 4. Check album name and disctitle using more flexible rules. - """ - label_excl: Tuple[Optional[Pattern], Optional[str]] = (None, None) + def parse_catalognum(album, disctitle, description, label, **kwargs): + # type: (str, str, str, str, Any) -> str + """Try getting the catalog number looking at text from various fields.""" + cases = [ + (CATNUM_PAT["with_header"], description), + (CATNUM_PAT["anywhere"], disctitle), + (CATNUM_PAT["anywhere"], album), + (CATNUM_PAT["start_or_end"], description), + (CATNUM_PAT["anywhere"], description), + ] if label: - escaped = re.escape(label) - label_excl = (re.compile(rf"({escaped}\s?[0-9]+)"), album) - - for pattern, source in [ - (PATTERNS["desc_catalognum"], description), - (PATTERNS["quick_catalognum"], album), - label_excl, - (PATTERNS["catalognum"], album), - (PATTERNS["catalognum"], disctitle), - ]: - if not pattern: - continue + # if label name is followed by digits, it may form a cat number + esc = re.escape(label) + cases.insert(0, (re.compile(fr"(?i:({esc} ?[A-Z]?\d+[A-Z]?))"), album)) - match = pattern.search(PATTERNS["catalognum_excl"].sub("", source)) - if match: - return match.groups()[0] - return "" + def find(pat: Pattern, string: str) -> str: + try: + return pat.search(string).groups()[0].strip() # type: ignore + except (IndexError, AttributeError): + return "" + + ignored = set(map(str.casefold, kwargs.get("artists") or []) or [None, ""]) + + def not_ignored(option: str) -> bool: + return bool(option) and option.casefold() not in ignored + + try: + return next(filter(not_ignored, it.starmap(find, cases))) + except StopIteration: + return "" @staticmethod def get_duration(source: JSONDict) -> int: - prop = [ - x.get("value") or 0 - for x in source.get("additionalProperty", []) - if x.get("name") == "duration_secs" - ] - if len(prop) == 1: - return floor(prop[0]) - return 0 + try: + h, m, s = map(int, re.findall(r"[0-9]+", source["duration"])) + return h * 3600 + m * 60 + s + except KeyError: + return 0 @staticmethod def clean_name(name: str, *args: str, remove_extra: bool = False) -> str: - """Return the clean album name. - If it ends up cleaning the name entirely, then return the first `args` member - if given (catalognum or label). If not given, return the original name. - + """Return clean album name / track title. If `remove_extra`, remove info from within the parentheses (usually remix info). """ - # catalognum, album, albumartist - for arg in args: - arg = re.escape(arg) - name = re.sub(rf"{arg}((?=[^'])|$)", "", name) - - # redundant spaces, duoble quotes, parentheses - for pat, repl in [ - (r"\s\s+", " "), - (r"\(\s+|(- )?\(+", "("), - # Remove duplicate closing parens if they follow a space - # or enclose mix/edit info and are at the end + replacements: List[Tuple[str, Union[str, Callable]]] = [ + (r" +", " "), # multiple spaces + (r"\( +|(- )?\(+", "("), # rubbish that precedes opening parenthesis (r" \)+|(?<=(?i:.mix|edit))\)+$", ")"), - (r'"', ""), - ]: - name = re.sub(pat, repl, name) - - # redundant information about 'remixes from xyz' + ('"', ""), # double quote anywhere in the string + # spaces around dash in remixer names within parens + (r"(\([^)]+) - ([^(]+\))", r"\1-\2"), + (r"[\[(][A-Z]+[0-9]+[\])]", ""), + # uppercase EP and LP, and remove surrounding parens / brackets + (r"(\S*(\b(?i:[EL]P)\b)\S*)", lambda x: x.expand(r"\2").upper()), + ] + for pat, repl in replacements: + name = re.sub(pat, repl, name).strip() + for arg in filter(op.truth, args): + esc = re.escape(arg) + name = re.sub(fr"[^'\])\w]*(?i:{esc})[^'(\[\w]*", " ", name).strip() if remove_extra: + # redundant information about 'remixes from xyz' name = PATTERNS["clean_incl"].sub("", name) + name = PATTERNS["clean_title"].sub("", name).strip(" -|/") + return re.sub("^'([^']+)'$", r"\1", name) - # always removed - exclude = [ - "limited edition", - "various artists", - "various artist", - "free download", - "free dl", - "free)", - "vinyl", - "e.p.", - "ep", - "lp", - "va", - ] - # handle special chars - excl = "|".join(map(re.escape, exclude)) + @staticmethod + def clean_ep_lp_name(album: str, artists: List[str]) -> str: + """Parse album name - which precedes 'LP' or 'EP' in the release title. + Attempt to remove artist names from the parsed string: + * If we're only left with 'EP', it means that the album name is made up of those + artists - in that case we keep them. + * Otherwise, we will end up cleaning a release title such as 'Artist Album EP', + where the artist is not clearly separated from the album name. + """ + match = re.search(r"[^-|]+[EL]P", album) + if not match: + return "" + album_with_artists = match.group().strip() + clean_album = Helpers.clean_name(album_with_artists, *artists) + return album_with_artists if len(clean_album) == 2 else clean_album - rubbish = fr"(?i:\b({excl})(\b|$))" - empty_parens = r"\(\)|\[\]" - default = next(iter([*args, name])) + @staticmethod + def clean_track_names(names: List[str], catalognum: str = "") -> List[str]: + """Remove catalogue number and leading numerical index if they are found.""" + if catalognum: + names = list(map(lambda x: Helpers.clean_name(x, catalognum), names)) + + len_tot = len(names) + if len_tot > 1 and sum(map(lambda x: int(x[0].isdigit()), names)) > len_tot / 2: + pat = re.compile(r"^\d+\W+") + names = list(map(lambda x: pat.sub("", x), names)) + return names + + @staticmethod + def track_delimiter(names: List[str]) -> str: + """Return the track parts delimiter that is in effect in the current release. + In some (unusual) situations track parts are delimited by a pipe character + instead of dash. + + This checks every track looking for the first character (see the regex for + exclusions) that splits it. The character that split the most and + at least half of the tracklist is the character we need. + """ - def clean(patstr: str, text: str) -> str: - return re.sub(patstr, "", text) + def get_delim(string: str) -> str: + match = re.search(r" ([^\w&()+ ]) ", string) + return match.expand(r"\1") if match else "-" - return clean(empty_parens, clean(rubbish, name)).strip("/-|([ ") or default + most_common = Counter(map(get_delim, names)).most_common(1) + if not most_common: + return "" + delim, count = most_common.pop() + return delim if (len(names) == 1 or count > len(names) / 2) else "-" @staticmethod def get_genre(keywords: Iterable[str], config: JSONDict) -> Iterable[str]: - """Verify each keyword against the list of MusicBrainz genres and return - a comma-delimited list of valid ones, where validity depends on the mode: - * classical: valid only if the entire keyword is found in the MB genres list - * progressive: above + if each of the words is a valid MB genre since it is - effectively a subgenre. - * psychedelic: above + if the last word is a valid MB genre - - If a keyword is part of another keyword (genre within a sub-genre), exclude it. - For example, + """Return a comma-delimited list of valid genres, using MB genres for reference. + + Verify each keyword's (potential genre) validity w.r.t. the configured `mode`: + * classical: valid only if the _entire keyword_ matches a MB genre in the list + * progressive: either above or if each of the words matches MB genre - since it + is effectively a subgenre. + * psychedelic: either one of the above or if the last word is a valid MB genre. + This allows to be flexible regarding the variety of potential genres while + keeping away from spammy ones. + + Once we have the list of keywords that make it through the mode filters, + an additional filter is executed: + * if a keyword is _part of another keyword_ (genre within a sub-genre), + the more generic option gets excluded, for example, >>> get_genre(['house', 'garage house', 'glitch'], "classical") 'garage house, glitch' """ @@ -257,7 +309,7 @@ def valid_for_mode(kw: str) -> bool: return valid_mb_genre(kw) or valid_mb_genre(list(words)[-1]) # expand badly delimited keywords - split_kw = partial(re.split, r"[.] | #") + split_kw = partial(re.split, r"[.] | #| - ") for kw in it.chain(*map(split_kw, keywords)): # remove full stops and hashes and ensure the expected form of 'and' kw = re.sub("[.#]", "", str(kw)).replace("&", "and") @@ -299,34 +351,29 @@ def is_bundle(fmt: JSONDict) -> bool: class Metaguru(Helpers): - html: str meta: JSONDict config: JSONDict - - _media: Dict[str, str] _singleton = False - excluded_fields: Set[str] = set() + va_name: str = VA - def __init__(self, html, config=None) -> None: - self.html = html + def __init__(self, meta: JSONDict, config: Optional[JSONDict] = None) -> None: + self.meta = meta self.config = config or {} - self.meta = {} - self.excluded_fields.update(set(self.config.get("exclude_extra_fields") or [])) - match = re.search(PATTERNS["meta"], html) - if match: - self.meta = json.loads(match.group()) + self.va_name = beets_config["va_name"].as_str() or self.va_name - self._media = self.meta.get("albumRelease", [{}])[0] + @classmethod + def from_html(cls, html: str, config: JSONDict = None) -> "Metaguru": try: - media_index = self._get_media_reference(self.meta) - except (KeyError, AttributeError): - pass - else: - # if preference is given and the format is available, use it - for preference in (self.config.get("preferred_media") or "").split(","): - if preference in media_index: - self._media = media_index[preference] - break + meta = json.loads(re.search(PATTERNS["meta"], html).group()) # type: ignore + meta["tracks"] = list(map(unescape, re.findall(r"^[0-9]+[.] .*", html, re.M))) + except AttributeError as exc: + raise AttributeError("Could not find release metadata JSON") from exc + + return cls(meta, config) + + @cached_property + def excluded_fields(self) -> Set[str]: + return set(self.config.get("excluded_fields") or []) @cached_property def comments(self) -> str: @@ -334,23 +381,35 @@ def comments(self) -> str: the configured separator string. """ parts = [self.meta.get("description")] - if self.media_name != DIGI_MEDIA: - parts.append(self._media.get("description")) + media_desc = self.media.get("description") + if media_desc and not media_desc.startswith("Includes high-quality"): + parts.append(media_desc) parts.append(self.meta.get("creditText")) sep = self.config["comments_separator"] return sep.join(filter(op.truth, parts)).replace("\r", "") + @cached_property + def all_media_comments(self) -> str: + get_desc = op.methodcaller("get", "description", "") + return "\n".join( + [ + # self.comments, + *map(get_desc, self.meta.get("albumRelease", {})), + self.comments, + ] + ) + @cached_property def album_name(self) -> str: - match = re.search(r"Title:([^\n]+)", self.comments) + match = re.search(r"Title:([^\n]+)", self.all_media_comments) if match: return match.groups()[0].strip() return self.meta["name"] @cached_property def label(self) -> str: - match = re.search(r"Label:([^/,\n]+)", self.comments) + match = re.search(r"Label:([^/,\n]+)", self.all_media_comments) if match: return match.groups()[0].strip() @@ -371,17 +430,21 @@ def artist_id(self) -> str: return self.meta["publisher"]["@id"] @cached_property - def bandcamp_albumartist(self) -> str: - match = re.search(r"Artist:([^\n]+)", self.comments) + def raw_albumartist(self) -> str: + match = re.search(r"Artists?:([^\n]+)", self.all_media_comments) if match: return str(match.groups()[0].strip()) + return self.meta["byArtist"]["name"] - albumartist = self.meta["byArtist"]["name"].replace("various", VA) - album = self.album_name + @cached_property + def bandcamp_albumartist(self) -> str: + """Return the official release albumartist. + It is correct in half of the cases. In others, we usually find the label name. + """ + albumartist = self.raw_albumartist if self.label == albumartist: - albumartist = ( - self.parse_track_name(album, self.catalognum).get("artist") or albumartist - ) + album = self.album_name + albumartist = self.parse_track_name(album).get("artist") or albumartist return re.sub(r"(?i:, ft.*remix.*)", "", albumartist) @@ -408,15 +471,30 @@ def albumstatus(self) -> str: reldate = self.release_date return "Official" if reldate and reldate <= date.today() else "Promotional" + @cached_property + def media(self) -> JSONDict: + media = self.meta.get("albumRelease", [{}])[0] + try: + media_index = self._get_media_reference(self.meta) + except (KeyError, AttributeError): + pass + else: + # if preference is given and the format is available, use it + for preference in (self.config.get("preferred_media") or "").split(","): + if preference in media_index: + media = media_index[preference] + break + return media + @cached_property def media_name(self) -> str: """Return the human-readable version of the media format.""" - return MEDIA_MAP.get(self._media.get("musicReleaseFormat", ""), DIGI_MEDIA) + return MEDIA_MAP.get(self.media.get("musicReleaseFormat", ""), DIGI_MEDIA) @cached_property def disctitle(self) -> str: """Return medium's disc title if found.""" - return "" if self.media_name == DIGI_MEDIA else self._media.get("name", "") + return "" if self.media_name == DIGI_MEDIA else self.media.get("name", "") @cached_property def mediums(self) -> int: @@ -425,7 +503,11 @@ def mediums(self) -> int: @cached_property def catalognum(self) -> str: return self.parse_catalognum( - self.album_name, self.disctitle, self.comments, self.label + self.album_name, + self.disctitle, + self.all_media_comments, + self.label, + artists=self.raw_artists, ) @cached_property @@ -444,90 +526,107 @@ def country(self) -> str: @cached_property def tracks(self) -> List[JSONDict]: """Parse relevant details from the tracks' JSON.""" + try: + raw_tracks = self.meta["track"]["itemListElement"] + except KeyError: + raw_tracks = [{"item": self.meta, "position": 1}] if self._singleton: - raw_tracks = [{"item": self.meta}] + names = [raw_tracks[0].get("item").get("name") or ""] else: - raw_tracks = self.meta["track"].get("itemListElement", []) - - albumartist = self.bandcamp_albumartist - catalognum = self.catalognum + names = self.track_names + delim = self.track_delimiter(names) + names = self.clean_track_names(names, self.catalognum) tracks = [] - for raw_track in raw_tracks: - raw_item = raw_track["item"] - index = raw_track.get("position") or 1 - name, digital_only = self.clean_digital_only_track(raw_item["name"]) - name = self.clean_name(name, *filter(op.truth, [self.catalognum, self.label])) - track = dict( - digital_only=digital_only, - index=index, - medium_index=index, - track_id=raw_item.get("@id"), - length=self.get_duration(raw_item) or None, - **self.parse_track_name(name, catalognum), - ) - track["artist"] = self.get_track_artist( - track["artist"], raw_item, albumartist # type: ignore + for item, position in map(op.itemgetter("item", "position"), raw_tracks): + initial_name = names[position - 1] + name = self.clear_digi_only(initial_name) + track: JSONDict = defaultdict( + str, + digi_only=name != initial_name, + index=position or 1, + medium_index=position or 1, + track_id=item.get("@id"), + length=self.get_duration(item) or None, + **self.parse_track_name(self.clean_name(name), delim), ) - lyrics = raw_item.get("recordingOf", {}).get("lyrics", {}).get("text") + track["artist"] = track["artist"] or self.bandcamp_albumartist + lyrics = item.get("recordingOf", {}).get("lyrics", {}).get("text") if lyrics: track["lyrics"] = lyrics.replace("\r", "") - else: - track["lyrics"] = None - tracks.append(track) return tracks @cached_property def track_artists(self) -> Set[str]: - artists = {(t.get("artist") or "") for t in self.tracks} - artists.discard("") + artists = {(t.get("artist") or "") for t in self.tracks} - {""} return artists @cached_property - def is_single(self) -> bool: - return self._singleton or len(set(t.get("main_title") for t in self.tracks)) == 1 + def unique_artists(self): + pat = re.compile(r" (?:[x+]|vs|f(?:ea)?t\.?) |, ") + split = map(lambda x: pat.split(x), self.track_artists) + artists = set(it.chain(*split)) + for artist in list(artists): + if " X " in artist and artist.upper() == artist: + artists.discard(artist) + artists.update(artist.split(" X ")) + continue + + subartists = artist.split(" & ") + if len(subartists) > 1 and any(map(lambda x: x in artists, subartists)): + artists.discard(artist) + artists.update(subartists) + return artists @cached_property - def is_lp(self) -> bool: - return "LP" in self.album_name or "LP" in self.disctitle + def track_names(self) -> List[str]: + raw_tracks = self.meta.get("tracks") or [] + return list(map(lambda x: x.split(". ", maxsplit=1)[1], raw_tracks)) + + @cached_property + def raw_artists(self) -> Set[str]: + def only_artist(name: str) -> str: + return re.sub(r" - .*", "", PATTERNS["track_alt"].sub("", name)) + + artists = set(map(only_artist, self.track_names)) + artists.update(self.meta["byArtist"]["name"].split(", ")) + return artists @cached_property - def is_ep(self) -> bool: - return "EP" in self.album_name or "EP" in self.disctitle + def is_single(self) -> bool: + return self._singleton or len(set(t.get("main_title") for t in self.tracks)) == 1 @cached_property def is_va(self) -> bool: - return ( - VA.casefold() in self.album_name.casefold() - or len(self.track_artists) == len(self.tracks) - or ( - len(self.track_artists) > 1 - and not {*self.bandcamp_albumartist.split(", ")}.issubset( - self.track_artists - ) - and len(self.tracks) >= 4 - ) + track_artists = self.track_artists + track_count = len(self.tracks) + unique = set(map(lambda x: re.sub(r" ?[&,x].*", "", x).lower(), track_artists)) + return VA.casefold() in self.album_name.casefold() or ( + len(unique) > min(4, track_count - 2) and track_count >= 4 ) @cached_property def albumartist(self) -> str: - """Handle various artists and albums that have a single artist.""" - if self.albumtype == "compilation": - return VA - tartists = self.track_artists - if len(tartists) == 1: - first_tartist = tartists.copy().pop() - if first_tartist != self.label: - return first_tartist - return self.bandcamp_albumartist + """Take into account the release contents and return the actual albumartist. + * 'Various Artists' (or `va_name` configuration option) for a compilation release + * If every track has the same author, treat it as the albumartist + """ + if self.va: + return self.va_name + + return ", ".join(sorted(self.unique_artists)) @cached_property def albumtype(self) -> str: - if self.is_lp: + text = "\n".join([self.album_name, self.disctitle, self.comments]) + lp_count = text.count(" LP") + ep_count = text.count(" EP") + if lp_count >= ep_count and lp_count: return "album" - if self.is_ep: + if ep_count > lp_count: return "ep" + if self.is_single: return "single" if self.is_va: @@ -536,7 +635,7 @@ def albumtype(self) -> str: @cached_property def va(self) -> bool: - return self.albumtype == "compilation" + return len(self.unique_artists) > 4 @cached_property def style(self) -> Optional[str]: @@ -564,18 +663,43 @@ def genre(self) -> Optional[str]: if genre_cfg["maximum"]: genres = it.islice(genres, genre_cfg["maximum"]) - return ", ".join(genres) or None + return ", ".join(sorted(genres)) or None + + @cached_property + def parsed_album_name(self) -> str: + match = re.search(r"[:-] ?([\w ]+) [EL]P", self.all_media_comments) + if match: + return match.expand(r"\1") + return self.catalognum @cached_property def clean_album_name(self) -> str: - args = [self.catalognum] if self.catalognum else [] - if not self.albumtype == "compilation": - args.append(self.label) + album = self.album_name + if " EP" in album or " LP" in album: + # EP and LP strings also get normalised during the cleanup, + # which is important for the next step + no_catlabel = self.clean_name(self.album_name, self.catalognum, self.label) + return self.clean_ep_lp_name(no_catlabel, self.albumartist.split(", ")) + + args = [self.catalognum] if not self._singleton: + args.append(self.bandcamp_albumartist) args.append(self.albumartist) - return self.clean_name(self.album_name, *args, remove_extra=True) + args.extend(self.albumartist.split(", ")) + # leave label name in place for compilations + if self.albumtype == "compilation": + # it could have been added as an albumartist already + for _ in range(args.count(self.label)): + args.remove(self.label) + else: + args.append(self.label) - @property + album = self.clean_name(self.album_name, *args, remove_extra=True) + if album: + return album + return self.parsed_album_name + + @cached_property def _common(self) -> JSONDict: return dict( data_source=DATA_SOURCE, @@ -586,13 +710,13 @@ def _common(self) -> JSONDict: def get_fields(self, fields: Iterable[str], src: object = None) -> JSONDict: """Return a mapping between unexcluded fields and their values.""" - fields = set(fields) - self.excluded_fields + fields = list(set(fields) - self.excluded_fields) if len(fields) == 1: field = fields.pop() return {field: getattr(self, field)} return dict(zip(fields, iter(op.attrgetter(*fields)(src or self)))) - @property + @cached_property def _common_album(self) -> JSONDict: common_data: JSONDict = dict(album=self.clean_album_name) fields = [ @@ -601,10 +725,9 @@ def _common_album(self) -> JSONDict: "albumtype", "albumstatus", "country", - "style", - "genre", - "comments", ] + if NEW_BEETS: + fields.extend(["genre", "style", "comments"]) common_data.update(self.get_fields(fields)) reldate = self.release_date if reldate: @@ -613,43 +736,43 @@ def _common_album(self) -> JSONDict: return common_data def _trackinfo(self, track: JSONDict, **kwargs: Any) -> TrackInfo: - track.pop("digital_only") - track.pop("main_title") - track_info = TrackInfo( - **self._common, + track.pop("digi_only", None) + track.pop("main_title", None) + if not NEW_BEETS: + track.pop("lyrics", None) + + data = dict( **track, + **self._common, disctitle=self.disctitle or None, medium=1, **kwargs, ) - for field in set(track_info.keys()) & self.excluded_fields: - track_info[field] = None + for field in set(data.keys()) & self.excluded_fields: + data.pop(field) - return track_info + return TrackInfo(**data) - @property + @cached_property def singleton(self) -> TrackInfo: self._singleton = True - kwargs: JSONDict = {} + track: TrackInfo = self._trackinfo(self.tracks[0]) if NEW_BEETS: - kwargs.update(**self._common_album, albumartist=self.bandcamp_albumartist) - - track = self.tracks[0].copy() - track.update(self.parse_track_name(self.album_name)) - if not track.get("artist"): - track["artist"] = self.bandcamp_albumartist - if NEW_BEETS and "-" not in kwargs.get("album", ""): - kwargs["album"] = "{} - {}".format(track["artist"], track["title"]) - - return self._trackinfo(track, medium_total=1, **kwargs) + track.update(self._common_album) + track.pop("album", None) + if not track.artist: + track.artist = self.bandcamp_albumartist + track.index = track.medium_index = track.medium_total = 1 + track.track_id = track.data_url + return track - @property + @cached_property def album(self) -> AlbumInfo: """Return album for the appropriate release format.""" tracks: Iterable[JSONDict] = self.tracks include_digi = self.config.get("include_digital_only_tracks") if not include_digi and self.media_name != DIGI_MEDIA: - tracks = it.filterfalse(op.itemgetter("digital_only"), tracks) + tracks = it.filterfalse(op.itemgetter("digi_only"), tracks) tracks = list(map(op.methodcaller("copy"), tracks)) @@ -662,5 +785,6 @@ def album(self) -> AlbumInfo: mediums=self.mediums, tracks=list(map(get_trackinfo, tracks)), ) - album_info.update(self.get_fields(["va"])) + for key, val in self.get_fields(["va"]).items(): + setattr(album_info, key, val) return album_info diff --git a/poetry.lock b/poetry.lock index e0d5bbb..5eb0cb0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "astroid" -version = "2.8.4" +version = "2.9.0" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -8,7 +8,7 @@ python-versions = "~=3.6" [package.dependencies] lazy-object-proxy = ">=1.4.0" -typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} +typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} wrapt = ">=1.11,<1.14" @@ -22,32 +22,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] - -[[package]] -name = "backports.entry-points-selectable" -version = "1.1.1" -description = "Compatibility shim providing selectable entry points for older implementations" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "bandit" @@ -65,9 +50,9 @@ stevedore = ">=1.20.0" [[package]] name = "beets" -version = "1.5.0" +version = "1.6.0" description = "music tagger and library organizer" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -78,7 +63,6 @@ mediafile = ">=0.2.0" munkres = ">=1.0.0" musicbrainzngs = ">=0.4" pyyaml = "*" -six = ">=1.9" unidecode = "*" [package.extras] @@ -86,16 +70,15 @@ absubmit = ["requests"] beatport = ["requests-oauthlib (>=0.6.1)"] bpd = ["pygobject"] chroma = ["pyacoustid"] -discogs = ["python3-discogs-client"] +discogs = ["python3-discogs-client (>=2.3.10)"] embedart = ["pillow"] embyupdate = ["requests"] fetchart = ["requests", "pillow"] -gmusic = ["gmusicapi"] -import = ["rarfile"] +import = ["rarfile", "py7zr"] kodiupdate = ["requests"] lastgenre = ["pylast"] lastimport = ["pylast"] -lint = ["flake8", "flake8-coding", "flake8-docstrings", "flake8-future-import", "pep8-naming"] +lint = ["flake8", "flake8-docstrings", "pep8-naming"] lyrics = ["requests", "beautifulsoup4", "langdetect"] metasync = ["dbus-python"] mpdstats = ["python-mpd2 (>=0.4.2)"] @@ -126,7 +109,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.7" +version = "2.0.11" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -139,7 +122,7 @@ unicode_backport = ["unicodedata2"] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -147,7 +130,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "commonmark" version = "0.9.1" description = "Python parser for the CommonMark Markdown spec" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -156,9 +139,9 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "confuse" -version = "1.6.0" +version = "1.7.0" description = "Painless YAML configuration." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" @@ -170,7 +153,7 @@ test = ["pathlib"] [[package]] name = "coverage" -version = "6.1.2" +version = "6.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -186,13 +169,13 @@ toml = ["tomli"] name = "dataclasses" version = "0.8" description = "A backport of the dataclasses module for Python 3.6" -category = "dev" +category = "main" optional = false python-versions = ">=3.6, <3.7" [[package]] name = "distlib" -version = "0.3.3" +version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false @@ -200,7 +183,7 @@ python-versions = "*" [[package]] name = "filelock" -version = "3.3.2" +version = "3.4.1" description = "A platform independent file lock." category = "dev" optional = false @@ -336,17 +319,17 @@ colors = ["colorama (>=0.4.3,<0.5.0)"] name = "jellyfish" version = "0.8.9" description = "a library for doing approximate and phonetic matching of strings." -category = "dev" +category = "main" optional = false python-versions = ">3.5" [[package]] name = "lazy-object-proxy" -version = "1.6.0" +version = "1.7.1" description = "A fast and thorough lazy object proxy." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -358,9 +341,9 @@ python-versions = "*" [[package]] name = "mediafile" -version = "0.8.1" +version = "0.9.0" description = "Handles low-level interfacing for files' tags. Wraps Mutagen to" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -375,7 +358,7 @@ test = ["tox"] name = "munkres" version = "1.1.4" description = "Munkres (Hungarian) algorithm for the Assignment Problem" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -383,7 +366,7 @@ python-versions = "*" name = "musicbrainzngs" version = "0.7.1" description = "Python bindings for the MusicBrainz NGS and the Cover Art Archive webservices" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -391,27 +374,27 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "mutagen" version = "1.45.1" description = "read and write audio tags for many formats" -category = "dev" +category = "main" optional = false python-versions = ">=3.5, <4" [[package]] name = "mypy" -version = "0.910" +version = "0.931" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.7.4" +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] [[package]] name = "mypy-extensions" @@ -423,18 +406,18 @@ python-versions = "*" [[package]] name = "packaging" -version = "21.2" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2,<3" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pbr" -version = "5.7.0" +version = "5.8.1" description = "Python Build Reasonableness" category = "dev" optional = false @@ -467,6 +450,14 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pprintpp" +version = "0.4.0" +description = "A drop-in replacement for pprint that's actually pretty" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "py" version = "1.11.0" @@ -501,40 +492,43 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.10.0" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.11.1" +version = "2.12.0" description = "python code static checker" category = "dev" optional = false python-versions = "~=3.6" [package.dependencies] -astroid = ">=2.8.0,<2.9" +astroid = ">=2.9.0,<2.10" colorama = {version = "*", markers = "sys_platform == \"win32\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.7" platformdirs = ">=2.2.0" -toml = ">=0.7.1" +toml = ">=0.9.2" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.7" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "6.2.5" +version = "7.0.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -549,10 +543,23 @@ iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" py = ">=1.8.2" -toml = "*" +tomli = ">=1.0.0" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-clarity" +version = "1.0.1" +description = "A plugin providing an alternative, colourful diff output for failing assertions." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pprintpp = ">=0.4.0" +pytest = ">=3.5.0" +rich = ">=8.0.0" [[package]] name = "pytest-cov" @@ -582,7 +589,7 @@ pytest = ">=3.2.5" [[package]] name = "pytest-randomly" -version = "3.10.2" +version = "3.10.3" description = "Pytest plugin to randomly order tests and control random.seed." category = "dev" optional = false @@ -592,17 +599,28 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} pytest = "*" +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" [[package]] name = "requests" -version = "2.26.0" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false @@ -622,7 +640,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] name = "rich" version = "10.11.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "dev" +category = "main" optional = false python-versions = ">=3.6,<4.0" @@ -640,7 +658,7 @@ jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" @@ -674,7 +692,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.2" +version = "1.2.3" description = "A lil' TOML parser" category = "dev" optional = false @@ -682,7 +700,7 @@ python-versions = ">=3.6" [[package]] name = "tox" -version = "3.24.4" +version = "3.24.5" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false @@ -701,27 +719,38 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] [[package]] name = "typed-ast" -version = "1.4.3" +version = "1.5.2" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false +python-versions = ">=3.6" + +[[package]] +name = "types-python-dateutil" +version = "2.8.9" +description = "Typing stubs for python-dateutil" +category = "dev" +optional = false python-versions = "*" [[package]] name = "types-requests" -version = "2.26.0" +version = "2.27.8" description = "Typing stubs for requests" category = "dev" optional = false python-versions = "*" +[package.dependencies] +types-urllib3 = "<1.27" + [[package]] name = "types-setuptools" -version = "57.4.2" +version = "57.4.9" description = "Typing stubs for setuptools" category = "dev" optional = false @@ -735,11 +764,19 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "types-urllib3" +version = "1.26.9" +description = "Typing stubs for urllib3" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "typing-extensions" version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -747,13 +784,13 @@ python-versions = "*" name = "unidecode" version = "1.3.2" description = "ASCII transliterations of Unicode text" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" [[package]] name = "urllib3" -version = "1.26.7" +version = "1.26.8" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -766,14 +803,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.10.0" +version = "20.13.1" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -808,31 +844,27 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = ">=3.6,<3.10" -content-hash = "a39217e83ad33049b20c074677beb2dd5f2ca0f99baf85977317f9ad9e1c74ef" +content-hash = "076567c0484cf0c3bbf6f6d2e75db54a06fce79e95cdf206fd6e26128683b4ee" [metadata.files] astroid = [ - {file = "astroid-2.8.4-py3-none-any.whl", hash = "sha256:0755c998e7117078dcb7d0bda621391dd2a85da48052d948c7411ab187325346"}, - {file = "astroid-2.8.4.tar.gz", hash = "sha256:1e83a69fd51b013ebf5912d26b9338d6643a55fec2f20c787792680610eed4a2"}, + {file = "astroid-2.9.0-py3-none-any.whl", hash = "sha256:776ca0b748b4ad69c00bfe0fff38fa2d21c338e12c84aa9715ee0d473c422778"}, + {file = "astroid-2.9.0.tar.gz", hash = "sha256:5939cf55de24b92bda00345d4d0659d01b3c7dafb5055165c330bc7c568ba273"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -"backports.entry-points-selectable" = [ - {file = "backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl", hash = "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b"}, - {file = "backports.entry_points_selectable-1.1.1.tar.gz", hash = "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] bandit = [ {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, {file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"}, ] beets = [ - {file = "beets-1.5.0.tar.gz", hash = "sha256:887f7bbac0fc14c49469e50d406fd216f914a27acf3818c6503c223f9825342b"}, + {file = "beets-1.6.0.tar.gz", hash = "sha256:aa6fb734e44afc9b039c0abd0edd4c7706df00d4eb4aae7afa9ff4b6bb15525d"}, ] cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, @@ -843,8 +875,8 @@ certifi = [ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, - {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, + {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, + {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -855,69 +887,69 @@ commonmark = [ {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] confuse = [ - {file = "confuse-1.6.0-py2.py3-none-any.whl", hash = "sha256:82137fae8aab189901f16ed9155268e34ec8673f13db0ad331ab670528ceb999"}, - {file = "confuse-1.6.0.tar.gz", hash = "sha256:d60104c0b2a708041ac27487fa6ee69bd37d910549659c7ba92f52cbe2ced4dc"}, + {file = "confuse-1.7.0-py2.py3-none-any.whl", hash = "sha256:f002a733b3a4c16f73a094fcca3c80715b66e0ec08000983dc755267e2bd069f"}, + {file = "confuse-1.7.0.tar.gz", hash = "sha256:c9fe8474516a62397f8e52fcf89547bb2f2737b1a4a6f6dec11a286f0b3a7401"}, ] coverage = [ - {file = "coverage-6.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:675adb3b3380967806b3cbb9c5b00ceb29b1c472692100a338730c1d3e59c8b9"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95a58336aa111af54baa451c33266a8774780242cab3704b7698d5e514840758"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d0a595a781f8e186580ff8e3352dd4953b1944289bec7705377c80c7e36c4d6c"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d3c5f49ce6af61154060640ad3b3281dbc46e2e0ef2fe78414d7f8a324f0b649"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:310c40bed6b626fd1f463e5a83dba19a61c4eb74e1ac0d07d454ebbdf9047e9d"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a4d48e42e17d3de212f9af44f81ab73b9378a4b2b8413fd708d0d9023f2bbde4"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ffa545230ca2ad921ad066bf8fd627e7be43716b6e0fcf8e32af1b8188ccb0ab"}, - {file = "coverage-6.1.2-cp310-cp310-win32.whl", hash = "sha256:cd2d11a59afa5001ff28073ceca24ae4c506da4355aba30d1e7dd2bd0d2206dc"}, - {file = "coverage-6.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:96129e41405887a53a9cc564f960d7f853cc63d178f3a182fdd302e4cab2745b"}, - {file = "coverage-6.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1de9c6f5039ee2b1860b7bad2c7bc3651fbeb9368e4c4d93e98a76358cdcb052"}, - {file = "coverage-6.1.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:80cb70264e9a1d04b519cdba3cd0dc42847bf8e982a4d55c769b9b0ee7cdce1e"}, - {file = "coverage-6.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba6125d4e55c0b8e913dad27b22722eac7abdcb1f3eab1bd090eee9105660266"}, - {file = "coverage-6.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8492d37acdc07a6eac6489f6c1954026f2260a85a4c2bb1e343fe3d35f5ee21a"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66af99c7f7b64d050d37e795baadf515b4561124f25aae6e1baa482438ecc388"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ebcc03e1acef4ff44f37f3c61df478d6e469a573aa688e5a162f85d7e4c3860d"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d44a8136eebbf544ad91fef5bd2b20ef0c9b459c65a833c923d9aa4546b204"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c18725f3cffe96732ef96f3de1939d81215fd6d7d64900dcc4acfe514ea4fcbf"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c8e9c4bcaaaa932be581b3d8b88b677489975f845f7714efc8cce77568b6711c"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:06d009e8a29483cbc0520665bc46035ffe9ae0e7484a49f9782c2a716e37d0a0"}, - {file = "coverage-6.1.2-cp36-cp36m-win32.whl", hash = "sha256:e5432d9c329b11c27be45ee5f62cf20a33065d482c8dec1941d6670622a6fb8f"}, - {file = "coverage-6.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:82fdcb64bf08aa5db881db061d96db102c77397a570fbc112e21c48a4d9cb31b"}, - {file = "coverage-6.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:94f558f8555e79c48c422045f252ef41eb43becdd945e9c775b45ebfc0cbd78f"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046647b96969fda1ae0605f61288635209dd69dcd27ba3ec0bf5148bc157f954"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cc799916b618ec9fd00135e576424165691fec4f70d7dc12cfaef09268a2478c"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62646d98cf0381ffda301a816d6ac6c35fc97aa81b09c4c52d66a15c4bef9d7c"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:27a3df08a855522dfef8b8635f58bab81341b2fb5f447819bc252da3aa4cf44c"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:610c0ba11da8de3a753dc4b1f71894f9f9debfdde6559599f303286e70aeb0c2"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:35b246ae3a2c042dc8f410c94bcb9754b18179cdb81ff9477a9089dbc9ecc186"}, - {file = "coverage-6.1.2-cp37-cp37m-win32.whl", hash = "sha256:0cde7d9fe2fb55ff68ebe7fb319ef188e9b88e0a3d1c9c5db7dd829cd93d2193"}, - {file = "coverage-6.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:958ac66272ff20e63d818627216e3d7412fdf68a2d25787b89a5c6f1eb7fdd93"}, - {file = "coverage-6.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a300b39c3d5905686c75a369d2a66e68fd01472ea42e16b38c948bd02b29e5bd"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d3855d5d26292539861f5ced2ed042fc2aa33a12f80e487053aed3bcb6ced13"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:586d38dfc7da4a87f5816b203ff06dd7c1bb5b16211ccaa0e9788a8da2b93696"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a34fccb45f7b2d890183a263578d60a392a1a218fdc12f5bce1477a6a68d4373"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bc1ee1318f703bc6c971da700d74466e9b86e0c443eb85983fb2a1bd20447263"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3f546f48d5d80a90a266769aa613bc0719cb3e9c2ef3529d53f463996dd15a9d"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd92ece726055e80d4e3f01fff3b91f54b18c9c357c48fcf6119e87e2461a091"}, - {file = "coverage-6.1.2-cp38-cp38-win32.whl", hash = "sha256:24ed38ec86754c4d5a706fbd5b52b057c3df87901a8610d7e5642a08ec07087e"}, - {file = "coverage-6.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:97ef6e9119bd39d60ef7b9cd5deea2b34869c9f0b9777450a7e3759c1ab09b9b"}, - {file = "coverage-6.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e5a8c947a2a89c56655ecbb789458a3a8e3b0cbf4c04250331df8f647b3de59"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a39590d1e6acf6a3c435c5d233f72f5d43b585f5be834cff1f21fec4afda225"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9d2c2e3ce7b8cc932a2f918186964bd44de8c84e2f9ef72dc616f5bb8be22e71"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3348865798c077c695cae00da0924136bb5cc501f236cfd6b6d9f7a3c94e0ec4"}, - {file = "coverage-6.1.2-cp39-cp39-win32.whl", hash = "sha256:fae3fe111670e51f1ebbc475823899524e3459ea2db2cb88279bbfb2a0b8a3de"}, - {file = "coverage-6.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:af45eea024c0e3a25462fade161afab4f0d9d9e0d5a5d53e86149f74f0a35ecc"}, - {file = "coverage-6.1.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:eab14fdd410500dae50fd14ccc332e65543e7b39f6fc076fe90603a0e5d2f929"}, - {file = "coverage-6.1.2.tar.gz", hash = "sha256:d9a635114b88c0ab462e0355472d00a180a5fbfd8511e7f18e4ac32652e7d972"}, + {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, + {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, + {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, + {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, + {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, + {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, + {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, + {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, + {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, + {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, + {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, + {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, + {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, + {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, + {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, + {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, + {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, + {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, + {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, + {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] dataclasses = [ {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, ] distlib = [ - {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, - {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] filelock = [ - {file = "filelock-3.3.2-py3-none-any.whl", hash = "sha256:bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b"}, - {file = "filelock-3.3.2.tar.gz", hash = "sha256:7afc856f74fa7006a289fd10fa840e1eebd8bbff6bffb69c26c54a0512ea8cf8"}, + {file = "filelock-3.4.1-py3-none-any.whl", hash = "sha256:a4bc51381e01502a30e9f06dd4fa19a1712eab852b6fb0f84fd7cce0793d8ca3"}, + {file = "filelock-3.4.1.tar.gz", hash = "sha256:0f12f552b42b5bf60dba233710bf71337d35494fc8bdd4fd6d9f6d082ad45e06"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, @@ -977,36 +1009,51 @@ jellyfish = [ {file = "jellyfish-0.8.9.tar.gz", hash = "sha256:90d25e8f5971ebbcf56f216ff5bb65d6466572b78908c88c47ab588d4ea436c2"}, ] lazy-object-proxy = [ - {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, + {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, + {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mediafile = [ - {file = "mediafile-0.8.1-py3-none-any.whl", hash = "sha256:e013980e8f33bd22bf5f2fa4733b75bc02e0e9f7eedce2aec305accee6e0707b"}, - {file = "mediafile-0.8.1.tar.gz", hash = "sha256:878ccc378b77f2d6c175abea135ea25631f28c722e01e1a051924d962ebea165"}, + {file = "mediafile-0.9.0-py3-none-any.whl", hash = "sha256:ce8c7968f2b8646d7c3eeb509a7b1d0d1cfe3e127a268f099f438e73330167f3"}, + {file = "mediafile-0.9.0.tar.gz", hash = "sha256:93ccef3fbb7d4554a0e7689d41236cd5686a2f2f17493098622b8344cf83df9a"}, ] munkres = [ {file = "munkres-1.1.4-py2.py3-none-any.whl", hash = "sha256:6b01867d4a8480d865aea2326e4b8f7c46431e9e55b4a2e32d989307d7bced2a"}, @@ -1021,41 +1068,38 @@ mutagen = [ {file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"}, ] mypy = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, - {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pbr = [ - {file = "pbr-5.7.0-py2.py3-none-any.whl", hash = "sha256:60002958e459b195e8dbe61bf22bcf344eedf1b4e03a321a5414feb15566100c"}, - {file = "pbr-5.7.0.tar.gz", hash = "sha256:4651ca1445e80f2781827305de3d76b3ce53195f2227762684eb08f17bc473b7"}, + {file = "pbr-5.8.1-py2.py3-none-any.whl", hash = "sha256:27108648368782d07bbf1cb468ad2e2eeef29086affd14087a6d04b7de8af4ec"}, + {file = "pbr-5.8.1.tar.gz", hash = "sha256:66bc5a34912f408bb3925bf21231cb6f59206267b7f63f3503ef865c1a292e25"}, ] platformdirs = [ {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, @@ -1065,6 +1109,10 @@ pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] +pprintpp = [ + {file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"}, + {file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"}, +] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -1081,20 +1129,23 @@ pyflakes = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pylint = [ - {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"}, - {file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"}, + {file = "pylint-2.12.0-py3-none-any.whl", hash = "sha256:ba00afcb1550bc217bbcb0eb76c10cb8335f7417a3323bdd980c29fb5b59f8d2"}, + {file = "pylint-2.12.0.tar.gz", hash = "sha256:245c87e5da54c35b623c21b35debf87d93b18bf9e0229515cc172d0b83d627cd"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-7.0.0-py3-none-any.whl", hash = "sha256:42901e6bd4bd4a0e533358a86e848427a49005a3256f657c5c8f8dd35ef137a9"}, + {file = "pytest-7.0.0.tar.gz", hash = "sha256:dad48ffda394e5ad9aa3b7d7ddf339ed502e5e365b1350e0af65f4a602344b11"}, +] +pytest-clarity = [ + {file = "pytest-clarity-1.0.1.tar.gz", hash = "sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772"}, ] pytest-cov = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, @@ -1105,8 +1156,12 @@ pytest-lazy-fixture = [ {file = "pytest_lazy_fixture-0.6.3-py3-none-any.whl", hash = "sha256:e0b379f38299ff27a653f03eaa69b08a6fd4484e46fd1c9907d984b9f9daeda6"}, ] pytest-randomly = [ - {file = "pytest-randomly-3.10.2.tar.gz", hash = "sha256:2c0a332c4b124e372e2473803bcc91ec87797664f4955afef2b844c0021662b1"}, - {file = "pytest_randomly-3.10.2-py3-none-any.whl", hash = "sha256:cbd5c50b7c41491c202c71a3df33a75619d610a4f5c34aa2bd02ac30fce7cd43"}, + {file = "pytest-randomly-3.10.3.tar.gz", hash = "sha256:22154cdcff7ba44e0599596490e6b75278ca973a33812ea6a54bf14d0b042ef1"}, + {file = "pytest_randomly-3.10.3-py3-none-any.whl", hash = "sha256:b05a7a45f54cae2b5095752c6a10cb559df84448421b0420ae492dd2fb1727ef"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, @@ -1144,8 +1199,8 @@ pyyaml = [ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rich = [ {file = "rich-10.11.0-py3-none-any.whl", hash = "sha256:44bb3f9553d00b3c8938abf89828df870322b9ba43caf3b12bb7758debdc6dec"}, @@ -1168,57 +1223,59 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, - {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, ] tox = [ - {file = "tox-3.24.4-py2.py3-none-any.whl", hash = "sha256:5e274227a53dc9ef856767c21867377ba395992549f02ce55eb549f9fb9a8d10"}, - {file = "tox-3.24.4.tar.gz", hash = "sha256:c30b57fa2477f1fb7c36aa1d83292d5c2336cd0018119e1b1c17340e2c2708ca"}, + {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, + {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, ] typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, + {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, + {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, + {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, + {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, + {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, + {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, + {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, + {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, +] +types-python-dateutil = [ + {file = "types-python-dateutil-2.8.9.tar.gz", hash = "sha256:90f95a6b6d4faba359287f17a2cae511ccc9d4abc89b01969bdac1185815c05d"}, + {file = "types_python_dateutil-2.8.9-py3-none-any.whl", hash = "sha256:d60db7f5d40ce85ce54e7fb14e4157daf33e24f5a4bfb5f44ee7a5b790dfabd0"}, ] types-requests = [ - {file = "types-requests-2.26.0.tar.gz", hash = "sha256:df5ec8c34b413a42ebb38e4f96bdeb68090b875bdfcc5138dc82989c95445883"}, - {file = "types_requests-2.26.0-py3-none-any.whl", hash = "sha256:809b5dcd3c408ac39d11d593835b6aff32420b3e7ddb79c7f3e823330f040466"}, + {file = "types-requests-2.27.8.tar.gz", hash = "sha256:c2f4e4754d07ca0a88fd8a89bbc6c8a9f90fb441f9c9b572fd5c484f04817486"}, + {file = "types_requests-2.27.8-py3-none-any.whl", hash = "sha256:8ec9f5f84adc6f579f53943312c28a84e87dc70201b54f7c4fbc7d22ecfa8a3e"}, ] types-setuptools = [ - {file = "types-setuptools-57.4.2.tar.gz", hash = "sha256:5499a0f429281d1a3aa9494c79b6599ab356dfe6d393825426bc749e48ea1bf8"}, - {file = "types_setuptools-57.4.2-py3-none-any.whl", hash = "sha256:9c96aab47fdcf066fef83160b2b9ddbfab3d2c8fdc89053579d0b306837bf22a"}, + {file = "types-setuptools-57.4.9.tar.gz", hash = "sha256:536ef74744f8e1e4be4fc719887f886e74e4cf3c792b4a06984320be4df450b5"}, + {file = "types_setuptools-57.4.9-py3-none-any.whl", hash = "sha256:948dc6863373750e2cd0b223a84f1fb608414cde5e55cf38ea657b93aeb411d2"}, ] types-six = [ {file = "types-six-0.1.9.tar.gz", hash = "sha256:860d4ce7e24d14e7441a1118428964ccd2d100c654ef69e7ee1d137f9aaa82d6"}, {file = "types_six-0.1.9-py2.py3-none-any.whl", hash = "sha256:659efd70c88c750d33f83857bbe83c4686e437dcdd542816825ba64f01a999f3"}, ] +types-urllib3 = [ + {file = "types-urllib3-1.26.9.tar.gz", hash = "sha256:abd2d4857837482b1834b4817f0587678dcc531dbc9abe4cde4da28cef3f522c"}, + {file = "types_urllib3-1.26.9-py3-none-any.whl", hash = "sha256:4a54f6274ab1c80968115634a55fb9341a699492b95e32104a7c513db9fe02e9"}, +] typing-extensions = [ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, @@ -1229,12 +1286,12 @@ unidecode = [ {file = "Unidecode-1.3.2.tar.gz", hash = "sha256:669898c1528912bcf07f9819dc60df18d057f7528271e31f8ec28cc88ef27504"}, ] urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ - {file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"}, - {file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"}, + {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"}, + {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"}, ] wrapt = [ {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, diff --git a/pyproject.toml b/pyproject.toml index 05325a9..6f508aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "beetcamp" -version = "0.11.0" +version = "0.12.0" description = "Bandcamp autotagger source for beets (http://beets.io)." authors = ["Šarūnas Nejus "] readme = "README.md" @@ -17,9 +17,11 @@ python = ">=3.6,<3.10" requests = "*" cached-property = "^1.5.2" pycountry = "^20.7.3" +python-dateutil = "^2.8.2" +beets = "*" +rich = "*" [tool.poetry.dev-dependencies] -beets = "*" dataclasses = { version = "*", python = "<3.7" } flake8 = ">=3.8.4" flake8-bandit = ">=2.0.0" @@ -29,11 +31,12 @@ pytest = ">=6.2" pytest-cov = ">=2.10.1" pytest-randomly = "*" pytest-lazy-fixture = ">=0.6.3" -rich = "^10.9.0" tox = ">=3.21.1" types-setuptools = "^57.0.0" types-requests = "^2.25.0" types-six = "^0.1.7" +types-python-dateutil = "^2.8.2" +pytest-clarity = "^1.0.1" [tool.poetry.scripts] beetcamp = "beetsplug.bandcamp:main" diff --git a/setup.cfg b/setup.cfg index e931f67..ab53d16 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,10 +13,11 @@ deps = pytest pytest-cov pytest-randomly + pytest-clarity pytest-lazy-fixture rich commands = - pytest -m parsing + pytest -m 'parsing or jsons' # [testenv:py3{6,7,8,9}-beets15] # install_command = pip install --pre -e "git+https://github.com/beetbox/beets#egg=beets" {packages} @@ -24,18 +25,25 @@ commands = [tool:pytest] cache_dir = /tmp/pytest_cache log_cli = true +log_cli_level = DEBUG addopts = -vv + -k "not lib" + --ignore-glob='*test_lib*' + --diff-symbols + --log-level=DEBUG --junit-xml=.reports/pytest-tests.xml - --code-highlight=yes + --code-highlight=no --strict-config --cov=beetsplug.bandcamp --cov-report=xml:.reports/coverage.xml + --cov-report=html:.reports/html markers = need_connection: end-to-end tests that require internet connection jsons: tests that compare parsed releases with json fixtures parsing: parsing tests + lib: library tests testpaths = beetsplug @@ -100,6 +108,7 @@ persistent = no known-third-party = beets, beetsplug +py-version = 3.6 [pylint.MESSAGES] disable = missing-function-docstring, diff --git a/tests/conftest.py b/tests/conftest.py index 4b60d43..1a5599e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,7 +22,7 @@ class ReleaseInfo: def track_data(self, **kwargs) -> TrackInfo: kget = kwargs.get track_url = kget("track_id", f"{self.artist_id}/track/{kget('title_id')}") - return dict( + data = dict( title=kget("title"), track_id=track_url, artist=kget("artist"), @@ -37,8 +37,10 @@ def track_data(self, **kwargs) -> TrackInfo: medium_index=kget("index"), medium_total=kget("medium_total"), disctitle=self.disctitle, - lyrics=kget("lyrics"), ) + if NEW_BEETS and "lyrics" in kwargs: + data["lyrics"] = kget("lyrics") + return data def set_singleton(self, artist: str, title: str, length: int, **kwargs) -> None: data = self.track_data( @@ -64,10 +66,12 @@ def set_albuminfo(self, tracks, **kwargs): "alt", "lyrics", ] + if not NEW_BEETS: + fields.pop(-1) iter_tracks = [ zip(fields, (len(tracks), idx, *track)) for idx, track in enumerate(tracks, 1) ] - self.albuminfo = AlbumInfo( + data = dict( album=kwargs["album"], album_id=self.album_id, artist=kwargs["albumartist"], @@ -86,9 +90,10 @@ def set_albuminfo(self, tracks, **kwargs): media=self.media, data_source=DATA_SOURCE, tracks=[TrackInfo(**self.track_data(**dict(t))) for t in iter_tracks], - genre=kwargs.get("genre"), - style=kwargs.get("style"), ) + if NEW_BEETS: + data.update(genre=kwargs.get("genre"), style=kwargs.get("style")) + self.albuminfo = AlbumInfo(**data) @pytest.fixture @@ -104,12 +109,10 @@ def single_track_release() -> ReleaseInfo: artist="Matriark", title="Arangel", length=421, - album="Matriark - Arangel", - albumartist="Matriark", albumstatus="Official", label="Megatech Industries", albumtype="single", - catalognum="", + catalognum="mt004", year=2020, month=11, day=9, @@ -131,10 +134,8 @@ def single_only_track_name() -> ReleaseInfo: ) info.set_singleton( artist="GUTKEIN", - title="OENERA", + title="oenera", length=355, - album="GUTKEIN - OENERA", - albumartist="GUTKEIN", albumstatus="Official", label="GUTKEIN", albumtype="single", @@ -143,7 +144,7 @@ def single_only_track_name() -> ReleaseInfo: month=1, day=10, country="RU", - genre="techno, trance", + genre="trance", style="electronic", ) return info @@ -277,7 +278,7 @@ def album_with_track_alt() -> ReleaseInfo: ] info.set_albuminfo( tracks, - album="Common Assault", + album="Common Assault EP", albumartist=artist, albumtype="ep", catalognum="FLD001", @@ -422,7 +423,8 @@ def artist_mess() -> ReleaseInfo: disctitle=None, ) # fmt: off - albumartist = "Psykovsky & Friends" + albumartist = "Various Artists" + psykovsky = "Psykovsky & Friends" lyrics = '''Little lark all alone @@ -476,14 +478,14 @@ def artist_mess() -> ReleaseInfo: ("so-we-sailed-till-we-found", "Psykovsky & Spiral", "So We Sailed Till We Found", 454, None), # noqa ("doors-of-perception", "Psykovsky & Kasatka", "Doors Of Perception", 473, None), # noqa ("variant-on-the-right", "Psykovsky & Spiral & Seeasound", "Variant On The Right", 736, None), # noqa - ("call-of-beauty", albumartist, "Call Of Beauty", 769, None), + ("call-of-beauty", psykovsky, "Call Of Beauty", 769, None), ("many-many-krishnas", "Psykovsky & Orestis & Jobaba", "Many Many Krishnas", 729, None), # noqa ("prem-i-um", "Psykovsky & Kashyyyk & Arcek", "Prem I Um", 409, None), ("now-here-nowhere", "Psykovsky & Arcek", "Now Here Nowhere", 557, None), ("holy-black-little-lark", "Psykovsky & Maleficium & Seeasound", "Holy Black / Little Lark", 1087, None, lyrics), # noqa - ("worlds-of-wisdom", albumartist, "Worlds Of Wisdom", 408, None), - ("pc-transmission", albumartist, "PC Transmission", 561, None), - ("rs-lightmusic", albumartist, "RS Lightmusic", 411, None), + ("worlds-of-wisdom", psykovsky, "Worlds Of Wisdom", 408, None), + ("pc-transmission", psykovsky, "PC Transmission", 561, None), + ("rs-lightmusic", psykovsky, "RS Lightmusic", 411, None), ("ksolntsu", "Psykovsky & Quip Tone Beatz & Flish", "Ksolntsu", 555, None), ("dadme-albricios-hijos-deva", "Birds Of Praise", "Dadme albricios hijos d'Eva", 623, None), # noqa ] @@ -496,7 +498,7 @@ def artist_mess() -> ReleaseInfo: catalognum="", label="Psykovsky", release_date=date(2015, 2, 12), - va=False, + va=True, country="NU", mediums=1, genre="experimental, psytrance", @@ -546,7 +548,7 @@ def ep() -> ReleaseInfo: info.set_albuminfo( tracks, album="Kickdown Vienna", - albumartist="jeånne, DJ DISRESPECT", + albumartist="DJ DISRESPECT, jeånne", albumtype="album", catalognum="fa010", label="falling apart", @@ -566,8 +568,8 @@ def description_meta() -> ReleaseInfo: info = ReleaseInfo( artist_id="https://diffusereality.bandcamp.com", album_id="https://diffusereality.bandcamp.com/album/francois-dillinger-icosahedrone-lp", # noqa - media="CD", - disctitle="Francois Dillinger - Icosahedrone [LP]", + media=DIGI_MEDIA, + disctitle=None, ) tracks = [ ("count-to-infinity", albumartist, "Count To Infinity", 376, None), @@ -592,7 +594,7 @@ def description_meta() -> ReleaseInfo: va=False, country="PT", mediums=1, - genre="ambient, electro, rave, techno, trance", + genre="ambient, electro, minimal, rave, techno", style="electronic", ) return info @@ -674,7 +676,7 @@ def remix_artists() -> ReleaseInfo: ] info.set_albuminfo( tracks, - album="Unseen", + album="Unseen EP", albumartist=albumartist, albumtype="ep", catalognum="", diff --git a/tests/json/album.json b/tests/json/album.json index a6b3564..0ce7721 100644 --- a/tests/json/album.json +++ b/tests/json/album.json @@ -245,16 +245,37 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "a" + } + ], "name": "Sub-labels", "url": "https://ute-rec.bandcamp.com/artists" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "Releases", "url": "https://ute-rec.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "Merch", "url": "https://ute-rec.bandcamp.com/merch" } @@ -274,25 +295,10 @@ "name": "track_id", "value": 2780084730 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 504.014 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -312,25 +318,10 @@ "name": "track_id", "value": 4132926702 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 487.471 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -350,25 +341,10 @@ "name": "track_id", "value": 2058726302 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 431.483 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -388,25 +364,10 @@ "name": "track_id", "value": 3564730108 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 421.233 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", diff --git a/tests/json/album_with_track_alt.json b/tests/json/album_with_track_alt.json index 9939188..c02805e 100644 --- a/tests/json/album_with_track_alt.json +++ b/tests/json/album_with_track_alt.json @@ -256,21 +256,49 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "a" + } + ], "name": "artists", "url": "https://foldrecords.bandcamp.com/artists" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://foldrecords.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "merch", "url": "https://foldrecords.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://foldrecords.bandcamp.com/community" } @@ -290,25 +318,10 @@ "name": "track_id", "value": 2847020791 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 357.372 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -328,25 +341,10 @@ "name": "track_id", "value": 1382985841 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 351.961 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -366,25 +364,10 @@ "name": "track_id", "value": 2410206971 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 20.8 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -404,25 +387,10 @@ "name": "track_id", "value": 2674898874 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 315.224 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", @@ -442,25 +410,10 @@ "name": "track_id", "value": 276309811 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 365.373 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 5 } ], "copyrightNotice": "All Rights Reserved", @@ -480,25 +433,10 @@ "name": "track_id", "value": 1813148828 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 20 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 6 } ], "copyrightNotice": "All Rights Reserved", diff --git a/tests/json/artist_mess.json b/tests/json/artist_mess.json index 2f59a23..01e51d0 100644 --- a/tests/json/artist_mess.json +++ b/tests/json/artist_mess.json @@ -190,11 +190,25 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://psykovsky.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://psykovsky.bandcamp.com/community" } @@ -214,31 +228,12 @@ "name": "track_id", "value": 3936209160 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 518.488 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Orestis" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H08M38S", "name": "Ela Na Pame" @@ -256,31 +251,12 @@ "name": "track_id", "value": 1697462790 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 673.161 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Luuli" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H11M13S", "name": "Stone Sea" @@ -298,31 +274,12 @@ "name": "track_id", "value": 100558700 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 454.736 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Spiral" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H07M34S", "name": "So We Sailed Till We Found" @@ -340,31 +297,12 @@ "name": "track_id", "value": 64137153 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 473.863 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Kasatka" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H07M53S", "name": "Doors Of Perception" @@ -382,31 +320,12 @@ "name": "track_id", "value": 2945940491 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 736.543 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 5 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Spiral & Seeasound" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H12M16S", "name": "Variant On The Right" @@ -424,25 +343,10 @@ "name": "track_id", "value": 249961523 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 769.609 - }, { "@type": "PropertyValue", "name": "license_name", "value": "attribution_non_commercial_share_alike" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 6 } ], "copyrightNotice": "Attribution Non Commercial Share Alike", @@ -462,31 +366,12 @@ "name": "track_id", "value": 1039297523 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 729.883 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 7 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Orestis & Jobaba" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H12M09S", "name": "Many Many Krishnas" @@ -504,31 +389,12 @@ "name": "track_id", "value": 2584951270 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 409.205 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 8 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Kashyyyk & Arcek" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H06M49S", "name": "Prem I Um" @@ -546,31 +412,12 @@ "name": "track_id", "value": 1788081328 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 557.82 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 9 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Arcek" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H09M17S", "name": "Now Here Nowhere" @@ -588,31 +435,12 @@ "name": "track_id", "value": 3407782947 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 1087.81 - }, { "@type": "PropertyValue", "name": "license_name", "value": "attribution_non_commercial_share_alike" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 10 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Maleficium & Seeasound" - }, "copyrightNotice": "Attribution Non Commercial Share Alike", "duration": "P00H18M07S", "name": "Holy Black / Little Lark", @@ -637,25 +465,10 @@ "name": "track_id", "value": 386177510 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 408.473 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 11 } ], "copyrightNotice": "All Rights Reserved", @@ -675,25 +488,10 @@ "name": "track_id", "value": 1401461526 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 561.487 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 12 } ], "copyrightNotice": "All Rights Reserved", @@ -713,25 +511,10 @@ "name": "track_id", "value": 3234798938 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 411.157 - }, { "@type": "PropertyValue", "name": "license_name", "value": "attribution_non_commercial_share_alike" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 13 } ], "copyrightNotice": "Attribution Non Commercial Share Alike", @@ -751,31 +534,12 @@ "name": "track_id", "value": 1634779854 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 555.756 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 14 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Psykovsky & Quip Tone Beatz & Flish" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H09M15S", "name": "Ksolntsu" @@ -793,31 +557,12 @@ "name": "track_id", "value": 1223215058 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 623.999 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 15 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Birds Of Praise" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H10M23S", "name": "Dadme albricios hijos d'Eva" diff --git a/tests/json/compilation.json b/tests/json/compilation.json index f1f29e6..cf30b5c 100644 --- a/tests/json/compilation.json +++ b/tests/json/compilation.json @@ -13,11 +13,6 @@ "name": "featured_track_num", "value": 13 }, - { - "@type": "PropertyValue", - "name": "has_discounts", - "value": true - }, { "@type": "PropertyValue", "name": "license_name", @@ -66,7 +61,7 @@ "name": "ISMVA003.3" }, { - "@id": "https://ismusberlin.bandcamp.com/album/ismva0033#b42222766", + "@id": "https://ismusberlin.bandcamp.com/album/ismva0033#b45760447", "@type": [ "MusicRelease", "Product" @@ -75,7 +70,7 @@ { "@type": "PropertyValue", "name": "item_id", - "value": 42222766 + "value": 45760447 }, { "@type": "PropertyValue", @@ -95,31 +90,26 @@ { "@type": "PropertyValue", "name": "art_id", - "value": 378822374 + "value": 2588751690 }, { "@type": "PropertyValue", "name": "is_bfd", "value": true - }, - { - "@type": "PropertyValue", - "name": "type_id", - "value": "d" } ], "image": [ - "https://f4.bcbits.com/img/a0378822374_10.jpg" + "https://f4.bcbits.com/img/a2588751690_10.jpg" ], "musicReleaseFormat": "DigitalFormat", - "name": "full digital discography (16 releases)", + "name": "full digital discography (18 releases)", "offers": { "@type": "Offer", "additionalProperty": [ { "@type": "PropertyValue", "name": "bundle_size", - "value": 16 + "value": 18 }, { "@type": "PropertyValue", @@ -138,7 +128,7 @@ "priceSpecification": { "minPrice": 29.25 }, - "url": "https://ismusberlin.bandcamp.com/album/ismva0033#b42222766-buy" + "url": "https://ismusberlin.bandcamp.com/album/ismva0033#b45760447-buy" } }, { @@ -277,16 +267,37 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://ismusberlin.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "merch", "url": "https://ismusberlin.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://ismusberlin.bandcamp.com/community" } @@ -306,25 +317,10 @@ "name": "track_id", "value": 2573934244 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 414 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -344,25 +340,10 @@ "name": "track_id", "value": 2486903435 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 361.558 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -382,25 +363,10 @@ "name": "track_id", "value": 283685132 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 313.6 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -420,25 +386,10 @@ "name": "track_id", "value": 2033411417 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 388 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", @@ -458,25 +409,10 @@ "name": "track_id", "value": 2012833122 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 330 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 5 } ], "copyrightNotice": "All Rights Reserved", @@ -496,25 +432,10 @@ "name": "track_id", "value": 650634565 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 426 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 6 } ], "copyrightNotice": "All Rights Reserved", @@ -534,25 +455,10 @@ "name": "track_id", "value": 1306777172 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 414.247 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 7 } ], "copyrightNotice": "All Rights Reserved", @@ -572,25 +478,10 @@ "name": "track_id", "value": 571350648 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 390 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 8 } ], "copyrightNotice": "All Rights Reserved", @@ -610,25 +501,10 @@ "name": "track_id", "value": 3160434405 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 370.75 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 9 } ], "copyrightNotice": "All Rights Reserved", @@ -648,25 +524,10 @@ "name": "track_id", "value": 2384179591 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 374.75 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 10 } ], "copyrightNotice": "All Rights Reserved", @@ -686,25 +547,10 @@ "name": "track_id", "value": 3204960381 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 344.022 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 11 } ], "copyrightNotice": "All Rights Reserved", @@ -724,25 +570,10 @@ "name": "track_id", "value": 2339272420 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 465 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 12 } ], "copyrightNotice": "All Rights Reserved", @@ -762,25 +593,10 @@ "name": "track_id", "value": 359530111 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 408.649 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 13 } ], "copyrightNotice": "All Rights Reserved", diff --git a/tests/json/description_meta.json b/tests/json/description_meta.json index d465035..e2faaac 100644 --- a/tests/json/description_meta.json +++ b/tests/json/description_meta.json @@ -85,54 +85,40 @@ { "@type": "PropertyValue", "name": "type_name", - "value": "Compact Disc (CD)" + "value": "USB Flash Drive" }, { "@type": "PropertyValue", "name": "image_ids", "value": [ - 23625297 + 23625297, + 26182438 ] }, - { - "@type": "PropertyValue", - "name": "is_music_merch", - "value": true - }, { "@type": "PropertyValue", "name": "type_id", - "value": 1 + "value": 5 } ], - "description": "Artist: Francois Dillinger\nTitle: Icosahedrone\nFormat: Vinyl, 12\" & CD\nLabel: Diffuse Reality\nCatalogue: DREA 005\n\nVinyl → bit.ly/3cUflzb\nVinyl orders include two Diffuse Reality stickers and digital downloads of the full release.\nwww.diffusereality.net", + "description": "Artist: Francois Dillinger\nTitle: Icosahedrone\nFormat: Vinyl, 12\" & CD\nLabel: Diffuse Reality\nCatalogue: DREA 005\n\nVinyl → bit.ly/3cUflzb\nVinyl orders include two Diffuse Reality stickers and digital downloads of the full release.\n\n誠 Makoto - Honesty, absolute sincerity.\nFSC certified Bamboo wood USB flash drive.\nThis means that the wood from which this USB memory is made comes from sustainable forests and is respectful with the environment.\n4GB capacity.\nBlack screen printing. \nwww.diffusereality.net", "image": [ - "https://f4.bcbits.com/img/0023625297_10.jpg" + "https://f4.bcbits.com/img/0023625297_10.jpg", + "https://f4.bcbits.com/img/0026182438_10.jpg" ], - "musicReleaseFormat": "CDFormat", "name": "Francois Dillinger - Icosahedrone [LP]", "offers": { "@type": "Offer", "additionalProperty": [ - { - "@type": "PropertyValue", - "name": "download_type", - "value": "a" - }, { "@type": "PropertyValue", "name": "fulfillment_days", "value": 15 }, - { - "@type": "PropertyValue", - "name": "includes_digital_download", - "value": true - }, { "@type": "PropertyValue", "name": "quantity_available", - "value": 7 + "value": 6 }, { "@type": "PropertyValue", @@ -150,7 +136,7 @@ } }, { - "@id": "https://diffusereality.bandcamp.com/album/francois-dillinger-icosahedrone-lp#b43435753", + "@id": "https://diffusereality.bandcamp.com/album/francois-dillinger-icosahedrone-lp#b45762127", "@type": [ "MusicRelease", "Product" @@ -159,7 +145,7 @@ { "@type": "PropertyValue", "name": "item_id", - "value": 43435753 + "value": 45762127 }, { "@type": "PropertyValue", @@ -179,36 +165,31 @@ { "@type": "PropertyValue", "name": "art_id", - "value": 3627156854 + "value": 2590020634 }, { "@type": "PropertyValue", "name": "is_bfd", "value": true - }, - { - "@type": "PropertyValue", - "name": "type_id", - "value": "d" } ], "image": [ - "https://f4.bcbits.com/img/a3627156854_10.jpg" + "https://f4.bcbits.com/img/a2590020634_10.jpg" ], "musicReleaseFormat": "DigitalFormat", - "name": "full digital discography (378 releases)", + "name": "full digital discography (483 releases)", "offers": { "@type": "Offer", "additionalProperty": [ { "@type": "PropertyValue", "name": "bundle_size", - "value": 378 + "value": 483 }, { "@type": "PropertyValue", "name": "discount", - "value": 0.7 + "value": 0.8 }, { "@type": "PropertyValue", @@ -217,12 +198,12 @@ } ], "availability": "OnlineOnly", - "price": 886.97, + "price": 769.36, "priceCurrency": "EUR", "priceSpecification": { - "minPrice": 886.97 + "minPrice": 769.36 }, - "url": "https://diffusereality.bandcamp.com/album/francois-dillinger-icosahedrone-lp#b43435753-buy" + "url": "https://diffusereality.bandcamp.com/album/francois-dillinger-icosahedrone-lp#b45762127-buy" } }, { @@ -273,16 +254,16 @@ "name": "Diffuse Reality Records" }, "copyrightNotice": "All Rights Reserved", - "dateModified": "14 May 2021 10:34:44 GMT", + "dateModified": "22 Sep 2021 09:00:19 GMT", "datePublished": "05 May 2021 00:00:00 GMT", "image": "https://f4.bcbits.com/img/a0890773906_10.jpg", "keywords": [ "Electronic", "ambient", "electro", + "minimal", "rave", "techno", - "trance", "Portugal" ], "name": "Francois Dillinger - Icosahedrone [LP]", @@ -319,47 +300,73 @@ { "@type": "PropertyValue", "name": "image_height", - "value": 3000 + "value": 1400 }, { "@type": "PropertyValue", "name": "image_id", - "value": 23801099 + "value": 27598543 }, { "@type": "PropertyValue", "name": "image_width", - "value": 3000 + "value": 1400 } ], - "description": "From Buenos Aires, based in Barcelona.", + "description": "Independent Record Label & Party Promoter founded in 2013. Barcelona based.\nArts Festival: Teorema\nSublabel: Periphylla\n۩ Send a private message to visit our Record shop in Barcelona. \n> https://bit.ly/3j2ZxNU", "foundingLocation": { "@type": "Place", "name": "Portugal" }, "genre": "https://bandcamp.com/tag/electronic", - "image": "https://f4.bcbits.com/img/0023801099_10.jpg", + "image": "https://f4.bcbits.com/img/0027598543_10.jpg", "mainEntityOfPage": [ { "@type": "WebPage", "name": "diffusereality.net", "url": "http://www.diffusereality.net/" + }, + { + "@type": "WebPage", + "name": "linktr.ee", + "url": "https://linktr.ee/diffusereality" } ], "name": "Diffuse Reality Records", "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "MUSIC", "url": "https://diffusereality.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "VINYL / MERCH", "url": "https://diffusereality.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "Community", "url": "https://diffusereality.bandcamp.com/community" } @@ -379,25 +386,10 @@ "name": "track_id", "value": 3885956131 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 376 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -417,25 +409,10 @@ "name": "track_id", "value": 2952764834 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 347.317 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -455,25 +432,10 @@ "name": "track_id", "value": 4283494502 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 320 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -493,25 +455,10 @@ "name": "track_id", "value": 4099471097 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 341.28 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", @@ -531,25 +478,10 @@ "name": "track_id", "value": 3534669991 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 335.988 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 5 } ], "copyrightNotice": "All Rights Reserved", @@ -569,25 +501,10 @@ "name": "track_id", "value": 3254609518 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 378 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 6 } ], "copyrightNotice": "All Rights Reserved", @@ -607,25 +524,10 @@ "name": "track_id", "value": 643361449 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 432 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 7 } ], "copyrightNotice": "All Rights Reserved", @@ -645,25 +547,10 @@ "name": "track_id", "value": 938275914 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 353.143 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 8 } ], "copyrightNotice": "All Rights Reserved", @@ -683,25 +570,10 @@ "name": "track_id", "value": 1106482452 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 320 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 9 } ], "copyrightNotice": "All Rights Reserved", @@ -721,25 +593,10 @@ "name": "track_id", "value": 1336260361 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 442 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 10 } ], "copyrightNotice": "All Rights Reserved", diff --git a/tests/json/edge_cases.json b/tests/json/edge_cases.json index 6cce36f..b597fb9 100644 --- a/tests/json/edge_cases.json +++ b/tests/json/edge_cases.json @@ -13,11 +13,6 @@ "name": "featured_track_num", "value": 1 }, - { - "@type": "PropertyValue", - "name": "has_discounts", - "value": true - }, { "@type": "PropertyValue", "name": "license_name", @@ -66,7 +61,7 @@ "name": "NYH244 Less Weird Parallel Universe - Nearly 20 Years of Erikoisdance" }, { - "@id": "https://newyorkhaunted.bandcamp.com/album/nyh244-less-weird-parallel-universe-nearly-20-years-of-erikoisdance#b43334244", + "@id": "https://newyorkhaunted.bandcamp.com/album/nyh244-less-weird-parallel-universe-nearly-20-years-of-erikoisdance#b45659762", "@type": [ "MusicRelease", "Product" @@ -75,7 +70,7 @@ { "@type": "PropertyValue", "name": "item_id", - "value": 43334244 + "value": 45659762 }, { "@type": "PropertyValue", @@ -95,31 +90,26 @@ { "@type": "PropertyValue", "name": "art_id", - "value": 2614409477 + "value": 3309405927 }, { "@type": "PropertyValue", "name": "is_bfd", "value": true - }, - { - "@type": "PropertyValue", - "name": "type_id", - "value": "d" } ], "image": [ - "https://f4.bcbits.com/img/a2614409477_10.jpg" + "https://f4.bcbits.com/img/a3309405927_10.jpg" ], "musicReleaseFormat": "DigitalFormat", - "name": "full digital discography (289 releases)", + "name": "full digital discography (306 releases)", "offers": { "@type": "Offer", "additionalProperty": [ { "@type": "PropertyValue", "name": "bundle_size", - "value": 289 + "value": 306 }, { "@type": "PropertyValue", @@ -133,12 +123,12 @@ } ], "availability": "OnlineOnly", - "price": 87.1, + "price": 122.5, "priceCurrency": "EUR", "priceSpecification": { - "minPrice": 87.1 + "minPrice": 122.5 }, - "url": "https://newyorkhaunted.bandcamp.com/album/nyh244-less-weird-parallel-universe-nearly-20-years-of-erikoisdance#b43334244-buy" + "url": "https://newyorkhaunted.bandcamp.com/album/nyh244-less-weird-parallel-universe-nearly-20-years-of-erikoisdance#b45659762-buy" } }, { @@ -280,7 +270,7 @@ "value": 590 } ], - "description": "NYH is the label of Dutch producer Drvg Cvltvre. Experimental house and techno. \n\nimagery created by @youtubeartifact\na bot by @dontsave\nand kandinsky.io", + "description": "NYH is the label of Dutch producer Drvg Cvltvre. Experimental house and techno. \n\nimagery created by @youtubeartifact\na bot by @dontsave, kandinsky.io and Dream by Wombo.", "foundingLocation": { "@type": "Place", "name": "Tilburg, Netherlands" @@ -291,16 +281,37 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://newyorkhaunted.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "merch", "url": "https://newyorkhaunted.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://newyorkhaunted.bandcamp.com/community" } @@ -320,25 +331,10 @@ "name": "track_id", "value": 1765925550 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 205.633 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -358,25 +354,10 @@ "name": "track_id", "value": 1998398008 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 224.52 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -396,25 +377,10 @@ "name": "track_id", "value": 1251598717 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 134.609 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -434,25 +400,10 @@ "name": "track_id", "value": 4010114980 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 280.361 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", @@ -472,25 +423,10 @@ "name": "track_id", "value": 210486065 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 435.838 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 5 } ], "copyrightNotice": "All Rights Reserved", @@ -510,25 +446,10 @@ "name": "track_id", "value": 3216207004 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 92.3574 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 6 } ], "copyrightNotice": "All Rights Reserved", @@ -548,25 +469,10 @@ "name": "track_id", "value": 1137318928 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 595.497 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 7 } ], "copyrightNotice": "All Rights Reserved", @@ -586,25 +492,10 @@ "name": "track_id", "value": 2953877677 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 162.856 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 8 } ], "copyrightNotice": "All Rights Reserved", @@ -624,25 +515,10 @@ "name": "track_id", "value": 1089671829 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 215.407 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 9 } ], "copyrightNotice": "All Rights Reserved", @@ -662,25 +538,10 @@ "name": "track_id", "value": 2239748918 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 213.424 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 10 } ], "copyrightNotice": "All Rights Reserved", @@ -700,25 +561,10 @@ "name": "track_id", "value": 2465973750 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 162.433 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 11 } ], "copyrightNotice": "All Rights Reserved", @@ -738,25 +584,10 @@ "name": "track_id", "value": 2408056440 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 251.347 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 12 } ], "copyrightNotice": "All Rights Reserved", @@ -776,25 +607,10 @@ "name": "track_id", "value": 139973535 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 178.252 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 13 } ], "copyrightNotice": "All Rights Reserved", @@ -814,25 +630,10 @@ "name": "track_id", "value": 820494330 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 376.958 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 14 } ], "copyrightNotice": "All Rights Reserved", @@ -852,25 +653,10 @@ "name": "track_id", "value": 2607172093 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 358.398 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 15 } ], "copyrightNotice": "All Rights Reserved", @@ -890,25 +676,10 @@ "name": "track_id", "value": 799719279 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 248.483 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 16 } ], "copyrightNotice": "All Rights Reserved", @@ -928,25 +699,10 @@ "name": "track_id", "value": 1776876399 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 287.696 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 17 } ], "copyrightNotice": "All Rights Reserved", @@ -966,25 +722,10 @@ "name": "track_id", "value": 2765402059 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 315.228 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 18 } ], "copyrightNotice": "All Rights Reserved", diff --git a/tests/json/ep.json b/tests/json/ep.json index 6b21668..962bb3a 100644 --- a/tests/json/ep.json +++ b/tests/json/ep.json @@ -134,7 +134,7 @@ { "@type": "PropertyValue", "name": "quantity_available", - "value": 97 + "value": 93 } ], "availability": "InStock", @@ -238,7 +238,7 @@ { "@type": "WebPage", "name": "Instagram", - "url": "https://www.instagram.com/fallingapart030/" + "url": "https://www.instagram.com/fallingapartrecords/" }, { "@type": "WebPage", @@ -248,23 +248,44 @@ { "@type": "WebPage", "name": "SoundCloud", - "url": "https://soundcloud.com/fallingapart030" + "url": "https://soundcloud.com/fallingapartrecords" } ], "name": "falling apart", "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://fallingapart.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "merch", "url": "https://fallingapart.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://fallingapart.bandcamp.com/community" } @@ -284,25 +305,10 @@ "name": "track_id", "value": 1568631558 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 385.8 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -322,25 +328,10 @@ "name": "track_id", "value": 1365259276 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 333.5 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -360,25 +351,10 @@ "name": "track_id", "value": 2270629014 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 315.203 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -398,25 +374,10 @@ "name": "track_id", "value": 1411519787 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 333.703 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", diff --git a/tests/json/remix_artists.json b/tests/json/remix_artists.json index 81b53ee..24f2d07 100644 --- a/tests/json/remix_artists.json +++ b/tests/json/remix_artists.json @@ -61,7 +61,7 @@ "name": "UNREALNUMBERS - Unseen EP (Varya Karpova & Lacchesi Remixes)" }, { - "@id": "https://maisoncloserecords.bandcamp.com/album/unrealnumbers-unseen-ep-varya-karpova-lacchesi-remixes#b42347570", + "@id": "https://maisoncloserecords.bandcamp.com/album/unrealnumbers-unseen-ep-varya-karpova-lacchesi-remixes#b44109687", "@type": [ "MusicRelease", "Product" @@ -70,7 +70,7 @@ { "@type": "PropertyValue", "name": "item_id", - "value": 42347570 + "value": 44109687 }, { "@type": "PropertyValue", @@ -90,31 +90,26 @@ { "@type": "PropertyValue", "name": "art_id", - "value": 587506457 + "value": 3034521613 }, { "@type": "PropertyValue", "name": "is_bfd", "value": true - }, - { - "@type": "PropertyValue", - "name": "type_id", - "value": "d" } ], "image": [ - "https://f4.bcbits.com/img/a0587506457_10.jpg" + "https://f4.bcbits.com/img/a3034521613_10.jpg" ], "musicReleaseFormat": "DigitalFormat", - "name": "full digital discography (8 releases)", + "name": "full digital discography (9 releases)", "offers": { "@type": "Offer", "additionalProperty": [ { "@type": "PropertyValue", "name": "bundle_size", - "value": 8 + "value": 9 }, { "@type": "PropertyValue", @@ -128,12 +123,12 @@ } ], "availability": "OnlineOnly", - "price": 42.6, + "price": 49.8, "priceCurrency": "EUR", "priceSpecification": { - "minPrice": 42.6 + "minPrice": 49.8 }, - "url": "https://maisoncloserecords.bandcamp.com/album/unrealnumbers-unseen-ep-varya-karpova-lacchesi-remixes#b42347570-buy" + "url": "https://maisoncloserecords.bandcamp.com/album/unrealnumbers-unseen-ep-varya-karpova-lacchesi-remixes#b44109687-buy" } }, { @@ -231,16 +226,37 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://maisoncloserecords.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "merch", "url": "https://maisoncloserecords.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://maisoncloserecords.bandcamp.com/community" } @@ -260,25 +276,10 @@ "name": "track_id", "value": 3030790431 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 383.662 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -298,25 +299,10 @@ "name": "track_id", "value": 987420966 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 352.448 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -336,25 +322,10 @@ "name": "track_id", "value": 3850977436 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 341.408 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -374,25 +345,10 @@ "name": "track_id", "value": 628289803 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 340.699 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", @@ -412,31 +368,12 @@ "name": "track_id", "value": 3533046617 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 362.774 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 5 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Varya Karpova" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H06M02S", "name": "UNREALNUMBERS - Unseen (Varya Karpova Remix)" @@ -454,31 +391,12 @@ "name": "track_id", "value": 359910911 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 350.897 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 6 } ], - "byArtist": { - "@type": "MusicGroup", - "name": "Lacchesi" - }, "copyrightNotice": "All Rights Reserved", "duration": "P00H05M50S", "name": "UNREALNUMBERS - MK4 (Lacchesi Remix)" diff --git a/tests/json/single_only_track_name.json b/tests/json/single_only_track_name.json index 20bd40c..4df991a 100644 --- a/tests/json/single_only_track_name.json +++ b/tests/json/single_only_track_name.json @@ -11,22 +11,12 @@ { "@type": "PropertyValue", "name": "art_id", - "value": 732802335 - }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 355.064 + "value": 1255022292 }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true } ], "byArtist": { @@ -35,10 +25,10 @@ "name": "GUTKEIN" }, "copyrightNotice": "All Rights Reserved", - "dateModified": "10 Jan 2021 19:56:09 GMT", - "datePublished": "10 Jan 2021 19:56:09 GMT", + "dateModified": "09 Nov 2021 05:15:25 GMT", + "datePublished": "10 Jan 2021 00:00:00 GMT", "duration": "P00H05M55S", - "image": "https://f4.bcbits.com/img/a0732802335_10.jpg", + "image": "https://f4.bcbits.com/img/a1255022292_10.jpg", "inAlbum": { "@type": "MusicAlbum", "albumRelease": [ @@ -72,15 +62,15 @@ { "@type": "PropertyValue", "name": "art_id", - "value": 732802335 + "value": 1255022292 } ], "description": "Includes high-quality download in MP3, FLAC and more. Paying supporters also get unlimited streaming via the free Bandcamp app.", "image": [ - "https://f4.bcbits.com/img/a0732802335_10.jpg" + "https://f4.bcbits.com/img/a1255022292_10.jpg" ], "musicReleaseFormat": "DigitalFormat", - "name": "OENERA", + "name": "oenera", "offers": { "@type": "Offer", "additionalProperty": [ @@ -106,17 +96,17 @@ } ], "albumReleaseType": "SingleRelease", - "name": "OENERA", + "name": "oenera", "numTracks": 1 }, "keywords": [ "Electronic", - "KETA", - "techno", + "keta techno", + "meta", "trance", "Russia" ], - "name": "OENERA", + "name": "oenera", "publisher": { "@id": "https://gutkeinforu.bandcamp.com", "@type": "MusicGroup", @@ -152,7 +142,7 @@ "value": 749 } ], - "description": "have u ever seen a label located at the very tip of a k-hall?", + "description": "support a tiny acquarium on the tip of a K-hall ~)", "foundingLocation": { "@type": "Place", "name": "Russia" @@ -175,8 +165,39 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://gutkeinforu.bandcamp.com/music" + }, + { + "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], + "name": "merch", + "url": "https://gutkeinforu.bandcamp.com/merch" + }, + { + "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "v" + } + ], + "name": "video", + "url": "https://gutkeinforu.bandcamp.com/video" } ] } diff --git a/tests/json/single_track_release.json b/tests/json/single_track_release.json index 503c311..15a335b 100644 --- a/tests/json/single_track_release.json +++ b/tests/json/single_track_release.json @@ -13,20 +13,10 @@ "name": "art_id", "value": 919724444 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 421.078 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true } ], "byArtist": { @@ -147,21 +137,49 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "a" + } + ], "name": "artists", "url": "https://mega-tech.bandcamp.com/artists" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://mega-tech.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "merch", "url": "https://mega-tech.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://mega-tech.bandcamp.com/community" } diff --git a/tests/json/single_with_remixes.json b/tests/json/single_with_remixes.json index b7a6d94..4dcc29d 100644 --- a/tests/json/single_with_remixes.json +++ b/tests/json/single_with_remixes.json @@ -66,7 +66,7 @@ { "@type": "PropertyValue", "name": "url_suffix", - "value": "from=btl" + "value": "?from=btl" } ], "name": "Kulør" @@ -149,7 +149,7 @@ { "@type": "PropertyValue", "name": "quantity_available", - "value": 32 + "value": 25 } ], "availability": "InStock", @@ -261,16 +261,37 @@ "subjectOf": [ { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "m" + } + ], "name": "music", "url": "https://reececox.bandcamp.com/music" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "p" + } + ], "name": "merch", "url": "https://reececox.bandcamp.com/merch" }, { "@type": "WebPage", + "additionalProperty": [ + { + "@type": "PropertyValue", + "name": "nav_type", + "value": "c" + } + ], "name": "community", "url": "https://reececox.bandcamp.com/community" } @@ -290,25 +311,10 @@ "name": "track_id", "value": 4095985028 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 295.095 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 1 } ], "copyrightNotice": "All Rights Reserved", @@ -328,25 +334,10 @@ "name": "track_id", "value": 2433903511 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 521.279 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 2 } ], "copyrightNotice": "All Rights Reserved", @@ -366,25 +357,10 @@ "name": "track_id", "value": 3231912467 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 339.92 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 3 } ], "copyrightNotice": "All Rights Reserved", @@ -404,25 +380,10 @@ "name": "track_id", "value": 1217738624 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 400.483 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 4 } ], "copyrightNotice": "All Rights Reserved", @@ -442,25 +403,10 @@ "name": "track_id", "value": 1473625405 }, - { - "@type": "PropertyValue", - "name": "duration_secs", - "value": 265.714 - }, { "@type": "PropertyValue", "name": "license_name", "value": "all_rights_reserved" - }, - { - "@type": "PropertyValue", - "name": "streaming", - "value": true - }, - { - "@type": "PropertyValue", - "name": "tracknum", - "value": 5 } ], "copyrightNotice": "All Rights Reserved", diff --git a/tests/test_genre.py b/tests/test_genre.py index 5ae09f2..117b784 100644 --- a/tests/test_genre.py +++ b/tests/test_genre.py @@ -1,10 +1,12 @@ import pytest + from beetsplug.bandcamp._metaguru import Metaguru +pytestmark = pytest.mark.parsing + def test_style(beets_config): - guru = Metaguru("", beets_config) - guru.meta = {"publisher": {"genre": "bandcamp.com/tag/folk"}} + guru = Metaguru({"publisher": {"genre": "bandcamp.com/tag/folk"}}, beets_config) assert guru.style == "folk" @@ -13,7 +15,7 @@ def test_style(beets_config): [ ([], None), (["crazy music"], None), - (["ambient. techno. industrial"], "ambient, techno, industrial"), + (["ambient. techno. industrial"], "ambient, industrial, techno"), (["Drum & Bass"], "drum and bass"), (["Techno."], "techno"), (["E.B.M"], "ebm"), @@ -28,8 +30,7 @@ def test_style(beets_config): def test_genre_variations(keywords, expected, beets_config): beets_config["genre"]["mode"] = "psychedelic" beets_config["genre"]["always_include"] = ["^hard", "core$"] - guru = Metaguru("", beets_config) - guru.meta = {"keywords": keywords} + guru = Metaguru({"keywords": keywords}, beets_config) assert guru.genre == expected @@ -85,8 +86,8 @@ def test_genre(keywords, mode, mode_result, beets_config): @pytest.mark.parametrize( ("capitalize", "maximum", "expected"), [ - (True, 0, "Folk, House, Grime, Trance"), - (True, 3, "Folk, House, Grime"), + (True, 0, "Folk, Grime, House, Trance"), + (True, 3, "Folk, Grime, House"), (False, 2, "folk, house"), ], ) @@ -97,8 +98,7 @@ def test_beets_config(capitalize, maximum, expected, beets_config): } beets_config["genre"]["capitalize"] = capitalize beets_config["genre"]["maximum"] = maximum - guru = Metaguru("", beets_config) - guru.meta = meta + guru = Metaguru(meta, beets_config) assert guru.style == ("Dubstep" if capitalize else "dubstep") assert guru.genre == expected diff --git a/tests/test_jsons.py b/tests/test_jsons.py index 40169ef..cdc55c5 100644 --- a/tests/test_jsons.py +++ b/tests/test_jsons.py @@ -1,15 +1,18 @@ import re +from operator import itemgetter import pytest -from beetsplug.bandcamp._metaguru import NEW_BEETS, Metaguru from pytest_lazyfixture import lazy_fixture +from beetsplug.bandcamp._metaguru import NEW_BEETS, Metaguru + pytestmark = pytest.mark.jsons @pytest.fixture(name="release") def _release(request): """Read the json data and make it span a single line - same like it's found in htmls. + Prepend JSON data with a multiline track list. Fixture names map to the testfiles (minus the extension). """ info = request.param @@ -21,7 +24,19 @@ def _release(request): if filename: with open(filename) as file: - return re.sub(r"\n *", "", file.read()), info + json = re.sub(r"\n *", "", file.read()) + + if info.singleton: + return json, info + + tracklist = [] + for track in info.albuminfo.tracks: + tracklist.append( + f"{track['index']}. " + + (f"{track['track_alt']}. " if track["track_alt"] else "") + + f"{track['artist']} - {track['title']}" + ) + return "\n".join([*tracklist, json]), info def check(actual, expected) -> None: @@ -38,9 +53,12 @@ def check(actual, expected) -> None: ) def test_parse_single_track_release(release, beets_config): html, expected = release - guru = Metaguru(html, beets_config) + print(html) + actual = Metaguru.from_html(html, beets_config).singleton + if hasattr(actual, "comments"): + actual.pop("comments") - check(guru.singleton, expected.singleton) + check(actual, expected.singleton) @pytest.mark.parametrize( @@ -65,10 +83,12 @@ def test_parse_single_track_release(release, beets_config): def test_parse_various_types(release, beets_config): html, expected_release = release beets_config["preferred_media"] = expected_release.media - guru = Metaguru(html, beets_config) + guru = Metaguru.from_html(html, beets_config) actual_album = guru.album expected_album = expected_release.albuminfo + if hasattr(actual_album, "comments"): + actual_album.pop("comments") assert hasattr(actual_album, "tracks") assert len(actual_album.tracks) == len(expected_album.tracks) diff --git a/tests/test_lib.py b/tests/test_lib.py new file mode 100644 index 0000000..cf64d1c --- /dev/null +++ b/tests/test_lib.py @@ -0,0 +1,148 @@ +import json +import os +import re +from collections import Counter, defaultdict, namedtuple +from functools import partial +from itertools import groupby +from html import unescape + +import pytest +from beetsplug.bandcamp import BandcampPlugin +from beetsplug.bandcamp._metaguru import Metaguru +from rich.columns import Columns +from rich.traceback import install + +from rich_tables.utils import border_panel, make_console, make_difftext, new_table, wrap + +pytestmark = pytest.mark.lib + +target_dir = "dev" +compare_against = "4b86dcc" +if not os.path.exists(target_dir): + os.makedirs(target_dir) +install(show_locals=True, extra_lines=8, width=int(os.environ.get("COLUMNS", 150))) +console = make_console(stderr=True) + +testfiles = list(filter(lambda x: x.endswith("json"), os.listdir("jsons"))) + + +@pytest.fixture(params=testfiles) +def file(request): + return request.param + + +Oldnew = namedtuple("Oldnew", ["old", "new", "diff"]) +oldnew = defaultdict(list) + + +@pytest.fixture(scope="session") +def _report(): + yield + cols = [] + for field in set(oldnew.keys()) - {"comments", "genre"}: + field_diffs = sorted(oldnew[field], key=lambda x: x.new) + if not field_diffs: + continue + tab = new_table() + for new, all_old in groupby(field_diffs, lambda x: x.new): + tab.add_row( + " | ".join( + map( + lambda x: (f"{x[1]} x " if x[1] > 1 else "") + + wrap(x[0], "b s red"), + Counter(map(lambda x: x.old, all_old)).items(), + ) + ), + wrap(new, "b green"), + ) + cols.append(border_panel(tab, title=field)) + + console.print("") + console.print(Columns(cols, expand=True)) + + stats_table = new_table("field", "#", border_style="white") + for field, count in sorted(stats_map.items(), key=lambda x: x[1], reverse=True): + stats_table.add_row(field, str(count)) + if stats_table.rows: + stats_table.add_row("total", str(len(testfiles))) + console.print(stats_table) + + +stats_map = defaultdict(lambda: 0) + + +@pytest.fixture(scope="module") +def config(request): + yield BandcampPlugin().config.flatten() + + +def do_key(table, key: str, before, after) -> None: + before = re.sub(r"^\s|\s$", "", str(before or "")) + after = re.sub(r"^\s|\s$", "", str(after or "")) + + if (before or after) and (before != after): + difftext = "" + stats_map[key] += 1 + if key == "genre": + before_set, after_set = set(before.split(", ")), set(after.split(", ")) + common, gone, added_new = ( + before_set & after_set, + before_set - after_set, + after_set - before_set, + ) + diffparts = list(map(partial(wrap, tag="b #111111"), sorted(common))) + if gone: + gone = list(map(partial(wrap, tag="b strike red"), gone)) + diffparts.extend(gone) + if added_new: + added_new = list(map(partial(wrap, tag="b green"), added_new)) + diffparts.extend(added_new) + if diffparts: + difftext = " | ".join(diffparts) + else: + difftext = make_difftext(before, after) + if difftext: + oldnew[key].append(Oldnew(before, after, difftext)) + table.add_row(wrap(key, "b"), difftext) + + +def compare(old, new) -> bool: + for entity in old, new: + entity["albumartist"] = entity.pop("artist", "") + + every_new = [new, *(new.get("tracks") or [])] + every_old = [old, *(old.get("tracks") or [])] + album_name = new.get("album") + album_id = wrap(new.get("album_id"), "dim") + keys_excl = {"bandcamp_artist_id", "bandcamp_album_id", "art_url_id", "art_url"} + keys_excl.update(("tracks", "comments", "length")) + table = new_table() + for new, old in zip(every_new, every_old): + title = " - ".join([new.get("artist") or "", new.get("title") or ""]) + title = wrap(album_name or title, "b") + for key in sorted(set(new.keys()).union(set(old.keys())) - keys_excl): + do_key(table, key, str(old.get(key, "")), str(new.get(key, ""))) + + if table.rows: + console.print("") + console.print(border_panel(table, title=title, subtitle=album_id)) + pytest.fail(pytrace=False) + + +@pytest.mark.usefixtures("_report") +def test_file(file, config): + meta_file = os.path.join("jsons", file) + tracks_file = os.path.join("jsons", file.replace(".json", ".tracks")) + meta = open(meta_file).read() + "\n" + unescape(unescape(open(tracks_file).read())) + guru = Metaguru.from_html(meta, config) + + if "_track_" in file: + new = guru.singleton + else: + new = guru.album + + target = os.path.join(target_dir, file) + json.dump(new, open(target, "w"), indent=2) + + old = json.load(open(os.path.join(compare_against, file))) + compare(old, new) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 75e16d0..4525c37 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,14 +1,15 @@ """Module for tests related to parsing.""" -import json from datetime import date +from operator import itemgetter import pytest -from beetsplug.bandcamp._metaguru import Helpers, Metaguru, urlify from rich.console import Console from rich.panel import Panel from rich.table import Table from rich.text import Text +from beetsplug.bandcamp._metaguru import Metaguru, urlify + pytestmark = pytest.mark.parsing console = Console(force_terminal=True, force_interactive=True) @@ -16,8 +17,6 @@ def print_result(case, expected, result): - console.width = 150 - table = Table("result", *expected.keys(), show_header=True, border_style="black") expectedrow = [] resultrow = [] @@ -54,7 +53,7 @@ def test_comments(descr, disctitle, creds, expected): dateModified="doesntmatter", ) config = {"preferred_media": "Vinyl", "comments_separator": "\n"} - guru = Metaguru(json.dumps(meta), config) + guru = Metaguru(meta, config) assert guru.comments == expected, vars(guru) @@ -86,122 +85,128 @@ def test_convert_title(title, expected): @pytest.mark.parametrize( - ("inputs", "expected"), + ("name", "expected"), [ - (("Title",), (None, None, "Title", "Title")), - (("Artist - Title",), (None, "Artist", "Title", "Title")), - (("A1. Artist - Title",), ("A1", "Artist", "Title", "Title")), - (("A1- Artist - Title",), ("A1", "Artist", "Title", "Title")), - (("A1.- Artist - Title",), ("A1", "Artist", "Title", "Title")), - (("A1 - Title",), ("A1", None, "Title", "Title")), - (("B2 - Artist - Title",), ("B2", "Artist", "Title", "Title")), - (("1. Artist - Title",), (None, "Artist", "Title", "Title")), - (("1.Artist - Title",), (None, "Artist", "Title", "Title")), - (("A2. Two Spaces",), ("A2", None, "Two Spaces", "Two Spaces")), - (("D1 No Punct",), ("D1", None, "No Punct", "No Punct")), - ( - ("DJ BEVERLY HILL$ - Raw Steeze",), - (None, "DJ BEVERLY HILL$", "Raw Steeze", "Raw Steeze"), - ), - ( - ("LI$INGLE010 - cyberflex - LEVEL X", "LI$INGLE010"), - (None, "cyberflex", "LEVEL X", "LEVEL X"), - ), - (("Fifty-Third ft. SYH",), (None, None, "Fifty-Third ft. SYH", "Fifty-Third")), - ( - ("I'll Become Pure N-R-G",), - (None, None, "I'll Become Pure N-R-G", "I'll Become Pure N-R-G"), - ), - (("&$%@#!",), (None, None, "&$%@#!", "&$%@#!")), - (("24 Hours",), (None, None, "24 Hours", "24 Hours")), - ( - ("Some tune (Someone's Remix)",), - (None, None, "Some tune (Someone's Remix)", "Some tune"), - ), - ( - ("19.85 - Colapso Inevitable",), - (None, "19.85", "Colapso Inevitable", "Colapso Inevitable"), - ), - ( - ("19.85 - Colapso Inevitable (FREE)",), - (None, "19.85", "Colapso Inevitable", "Colapso Inevitable"), - ), - (("E7-E5",), (None, None, "E7-E5", "E7-E5")), + ("Title", ("", "", "Title", "Title")), + ("Artist - Title", ("", "Artist", "Title", "Title")), + ("A1. Artist - Title", ("A1", "Artist", "Title", "Title")), + ("A1- Artist - Title", ("A1", "Artist", "Title", "Title")), + ("A1.- Artist - Title", ("A1", "Artist", "Title", "Title")), + ("A1 - Title", ("A1", "", "Title", "Title")), + ("B2 - Artist - Title", ("B2", "Artist", "Title", "Title")), + ("A2. Two Spaces", ("A2", "", "Two Spaces", "Two Spaces")), + ("a2.non caps - Title", ("A2", "non caps", "Title", "Title")), + ("D1 No Punct", ("D1", "", "No Punct", "No Punct")), ( - ("Lacchesi - UNREALNUMBERS - MK4 (Lacchesi Remix)",), - (None, "Lacchesi, UNREALNUMBERS", "MK4 (Lacchesi Remix)", "MK4"), + "DJ BEVERLY HILL$ - Raw Steeze", + ("", "DJ BEVERLY HILL$", "Raw Steeze", "Raw Steeze"), ), + ("&$%@#!", ("", "", "&$%@#!", "&$%@#!")), + ("24 Hours", ("", "", "24 Hours", "24 Hours")), ( - ("UNREALNUMBERS -Karaburan",), - (None, "UNREALNUMBERS", "Karaburan", "Karaburan"), + "Some tune (Someone's Remix)", + ("", "", "Some tune (Someone's Remix)", "Some tune"), ), + ("19.85 - Colapso (FREE)", ("", "19.85", "Colapso", "Colapso")), + ("E7-E5", ("", "", "E7-E5", "E7-E5")), ( - ("Ellie Goulding- Starry Eyed ( ROWDIBOÏ EDIT))",), - (None, "Ellie Goulding", "Starry Eyed (ROWDIBOÏ EDIT)", "Starry Eyed"), + "Lacchesi - UNREALNUMBERS - MK4 (Lacchesi Remix)", + ("", "Lacchesi, UNREALNUMBERS", "MK4 (Lacchesi Remix)", "MK4"), ), + ("UNREALNUMBERS -Karaburan", ("", "UNREALNUMBERS", "Karaburan", "Karaburan")), ( - ("Space Jam - (RZVX EDIT)",), - (None, None, "Space Jam (RZVX EDIT)", "Space Jam"), - ), - (("¯\\_(ツ)_/¯",), (None, None, "¯\\_(ツ)_/¯", "¯\\_(ツ)_/¯")), - (("VIENNA (WARM UP MIX",), (None, None, "VIENNA (WARM UP MIX", "VIENNA")), - ( - ("MOD-R - ARE YOU RECEIVING ME",), - (None, "MOD-R", "ARE YOU RECEIVING ME", "ARE YOU RECEIVING ME"), + "Ellie Goulding- Eyed ( ROWDIBOÏ EDIT))", + ("", "Ellie Goulding", "Eyed (ROWDIBOÏ EDIT)", "Eyed"), ), + ("Space Jam - (RZVX EDIT)", ("", "", "Space Jam (RZVX EDIT)", "Space Jam")), + ("¯\\_(ツ)_/¯", ("", "", "¯\\_(ツ)_/¯", "¯\\_(ツ)_/¯")), + ("VIENNA (WARM UP MIX", ("", "", "VIENNA (WARM UP MIX", "VIENNA")), + ("MOD-R - ARE YOU", ("", "MOD-R", "ARE YOU", "ARE YOU")), + ("K - The Lightning", ("", "K", "The Lightning", "The Lightning")), + ("MEAN-E - PLANETARY", ("", "MEAN-E", "PLANETARY", "PLANETARY")), + ("f-theme", ("", "", "f-theme", "f-theme")), + ("Mr. Free - The 4th Room", ("", "Mr. Free", "The 4th Room", "The 4th Room")), + ("O)))Bow 1", ("", "", "O)))Bow 1", "O)))Bow 1")), + ("H.E.L.L.O.", ("", "", "H.E.L.L.O.", "H.E.L.L.O.")), + ("Erik Burka - Pigeon [MNRM003]", ("", "Erik Burka", "Pigeon", "Pigeon")), + ("Artist - Title [ONE001]", ("", "Artist", "Title", "Title")), + ("Artist + Other - Title", ("", "Artist + Other", "Title", "Title")), + ("Artist (feat. Other) - Title", ("", "Artist feat. Other", "Title", "Title")), + ("Artist (some remix) - Title", ("", "Artist", "Title", "Title")), + ], +) +def test_parse_track_name(name, expected, beets_config): + track = {"item": {"@id": "album_url", "name": name}, "position": 1} + meta = { + "track": {"itemListElement": [track]}, + "name": "album", + "publisher": {"name": "some label"}, + "byArtist": {"name": ""}, + "tracks": [f"1. {name}"], + } + fields = "track_alt", "artist", "title", "main_title" + expected = dict(zip(fields, expected)) + + guru = Metaguru(meta, beets_config) + result_track = guru.tracks[0] + result = dict(zip(fields, itemgetter(*fields)(result_track))) + assert result == expected, print_result(name, expected, result) + + +@pytest.mark.parametrize( + ("names", "catalognum", "expected"), + [ ( - ("K - The Lightning Princess",), - (None, "K", "The Lightning Princess", "The Lightning Princess"), + ["LI$INGLE010 - cyberflex - LEVEL X"], + "LI$INGLE010", + ["cyberflex - LEVEL X"], ), ( - ("MEAN-E - PLANETARY NEBULAE",), - (None, "MEAN-E", "PLANETARY NEBULAE", "PLANETARY NEBULAE"), + ["1. Artist - Title", "2. Artist - Title"], + "", + ["Artist - Title", "Artist - Title"], ), - (("f-theme",), (None, None, "f-theme", "f-theme")), ( - ("NYH244 04 Chris Angel - Mind Freak", "NYH244"), - (None, "Chris Angel", "Mind Freak", "Mind Freak"), + ["9 Artist - Title", "Artist - Title"], + "", + ["9 Artist - Title", "Artist - Title"], ), ( - ("Mr. Free - The 4th Room",), - (None, "Mr. Free", "The 4th Room", "The 4th Room"), + ["NYH244 04 Artist - Title", "NYH244 05 Artist - Title"], + "NYH244", + ["Artist - Title", "Artist - Title"], ), - (("O)))Bow 1",), (None, None, "O)))Bow 1", "O)))Bow 1")), - (("H.E.L.L.O.",), (None, None, "H.E.L.L.O.", "H.E.L.L.O.")), ], ) -def test_parse_track_name(inputs, expected): - expected_track = dict(zip(("track_alt", "artist", "title", "main_title"), expected)) - result = Metaguru.parse_track_name(Metaguru.clean_name(*inputs)) - assert expected_track == result, print_result(inputs[0], expected_track, result) +def test_clean_track_names(names, catalognum, expected): + assert Metaguru.clean_track_names(names, catalognum) == expected @pytest.mark.parametrize( - ("parsed", "official", "albumartist", "expected"), + ("album", "artists", "expected"), [ - (None, "", "AlbumA", "AlbumA"), - ("", "", "Artist1, Artist2", "Artist1, Artist2"), - ("Parsed", "", "AlbumA", "Parsed"), - ("Parsed", "Official", "AlbumA", "Parsed"), - (None, "Official", "AlbumA", "Official"), + ("Album EP", [], "Album EP"), + ("Artist Album EP", ["Artist"], "Album EP"), + ("Artist EP", ["Artist"], "Artist EP"), + ("Album Artist EP", ["Artist"], "Album EP"), + ("CAT001 - Artist Album EP", ["Artist"], "Album EP"), ], ) -def test_get_track_artist(parsed, official, albumartist, expected): - item = {"byArtist": {"name": official}} if official else {} - assert Metaguru.get_track_artist(parsed, item, albumartist) == expected +def test_clean_ep_lp_name(album, artists, expected): + assert Metaguru.clean_ep_lp_name(album, artists) == expected @pytest.mark.parametrize( ("artists", "expected"), [(["4.44.444.8", "4.44.444.8"], {"4.44.444.8"})] ) def test_track_artists(artists, expected): - guru = Metaguru("") + guru = Metaguru({}) guru.tracks = [{"artist": a} for a in artists] assert guru.track_artists == expected @pytest.mark.parametrize( - ("name", "expected_digital_only", "expected_name"), + ("name", "expected_digi_only", "expected_name"), [ ("Artist - Track [Digital Bonus]", True, "Artist - Track"), ("DIGI 11. Track", True, "Track"), @@ -220,10 +225,10 @@ def test_track_artists(artists, expected): ("TROPICOFRIO - DIGITAL DRIVER", False, "TROPICOFRIO - DIGITAL DRIVER"), ], ) -def test_check_digital_only(name, expected_digital_only, expected_name): - actual_name, actual_digi_only = Metaguru.clean_digital_only_track(name) +def test_check_digi_only(name, expected_digi_only, expected_name): + actual_name = Metaguru.clear_digi_only(name) assert actual_name == expected_name - assert actual_digi_only == expected_digital_only + assert (actual_name != name) == expected_digi_only @pytest.mark.parametrize( @@ -248,8 +253,7 @@ def test_check_digital_only(name, expected_digital_only, expected_name): ], ) def test_parse_country(name, expected): - guru = Metaguru("") - guru.meta = {"publisher": {"foundingLocation": {"name": name}}} + guru = Metaguru({"publisher": {"foundingLocation": {"name": name}}}) assert guru.country == expected @@ -258,9 +262,8 @@ def test_parse_country(name, expected): [ ("Tracker-229 [PRH-002]", "", "", "", "PRH-002"), ("[PRH-002] Tracker-229", "", "", "", "PRH-002"), - ("Tracker-229 PRH-002", "", "", "", "Tracker-229"), ("ISMVA003.2", "", "", "", "ISMVA003.2"), - ("UTC003-CD", "", "", "", "UTC003"), + ("UTC003-CD", "", "", "", "UTC003-CD"), ("UTC-003", "", "", "", "UTC-003"), ("EP [SINDEX008]", "", "", "", "SINDEX008"), ("2 x Vinyl LP - MTY003", "", "", "", "MTY003"), @@ -275,22 +278,13 @@ def test_parse_country(name, expected): ("Emotion 1 - Kulør 008", "Emotion 1 Vinyl", "", "Kulør", "Kulør 008"), ("zz333HZ with remixes from Le Chocolat Noir", "", "", "", ""), ("UTC-003", "", "Catalogue Number: TE0029", "", "TE0029"), - ("UTC-003", "", "Catalogue Nr: TE0029", "", "TE0029"), - ("UTC-003", "", "Catalogue No.: TE0029", "", "TE0029"), - ("UTC-003", "", "Catalogue: CTU-300", "", "CTU-300"), - ("UTC-003", "", "Cat No: TE0029", "", "TE0029"), - ("UTC-003", "", "Cat Nr.: TE0029", "", "TE0029"), - ("UTC-003", "", "Catalogue:CTU-300", "", "CTU-300"), - ("Emotional Shutdown", "", "Catalog: SCTR007", "", "SCTR007"), ("", "LP | ostgutlp31", "", "", "ostgutlp31"), ("Album VA001", "", "", "", ""), ("Album MVA001", "", "", "", "MVA001"), - ("Album [ROAD4]", "", "", "", "ROAD4"), ("Need For Lead (ISM001)", "", "", "", "ISM001"), ("OBS.CUR 2 Depths", "", "", "", "OBS.CUR 2"), ("VINYL 12", "", "", "", ""), ("Triple 12", "", "", "", ""), - ("", "o-ton 113", "", "", "o-ton 113"), ("IBM001V", "", "", "", "IBM001V"), ("fa010", "", "", "", "fa010"), ("", 'EP 12"', "", "", ""), @@ -298,10 +292,33 @@ def test_parse_country(name, expected): ("Counterspell [HMX005]", "", "", "", "HMX005"), ("3: Flight Of The Behemoth", "", "", "SUNN O)))", ""), ("[CAT001]", "", "", "\\m/ records", "CAT001"), + ("", "", "On INS004, ", "", "INS004"), + ("Addax EP - WU55", "", "", "", "WU55"), + ("BAD001", "Life Without Friction (SSPB008)", "", "", "SSPB008"), + ("", "TS G5000 hello hello t-shirt.", "", "", ""), + ("GOOD GOOD001", "", "", "", "GOOD GOOD001"), + ("BAd GOOD001", "", "", "", "GOOD001"), + ("bad GOOD001", "", "", "bad GOOD", "bad GOOD001"), + ("MNQ 049 Void Vision - Sour (2019 repress)", "", "", "", "MNQ 049"), + ("P90-003", "", "", "", "P90-003"), ], ) -def test_parse_catalognum(album, disctitle, description, label, expected): - assert Metaguru.parse_catalognum(album, disctitle, description, label) == expected +def test_parse_catalognum(album, disctitle, description, label, expected, beets_config): + meta = { + "name": album, + "description": description, + "publisher": {"name": label}, + "byArtist": {"name": ""}, + "albumRelease": [ + { + "name": disctitle, + "musicReleaseFormat": "VinylFormat", + "description": "", + }, + ], + } + + assert Metaguru(meta, beets_config).catalognum == expected @pytest.mark.parametrize( @@ -310,29 +327,29 @@ def test_parse_catalognum(album, disctitle, description, label, expected): ("Album - Various Artists", [], "Album"), ("Various Artists - Album", [], "Album"), ("Various Artists Album", [], "Album"), - ("Album EP", [], "Album"), - ("Album [EP]", [], "Album"), - ("Album (EP)", [], "Album"), - ("Album E.P.", [], "Album"), - ("Album LP", [], "Album"), - ("Album [LP]", [], "Album"), - ("Album (LP)", [], "Album"), - ("[Label] Album EP", ["Label"], "Album"), - ("Artist - Album EP", ["Artist"], "Album"), + ("Album EP", [], "Album EP"), + ("Album [EP]", [], "Album EP"), + ("Album (EP)", [], "Album EP"), + ("Album E.P.", [], "Album E.P."), + ("Album LP", [], "Album LP"), + ("Album [LP]", [], "Album LP"), + ("Album (LP)", [], "Album LP"), + ("[Label] Album EP", ["Label"], "Album EP"), + ("Artist - Album EP", ["Artist"], "Album EP"), ("Label | Album", ["Label"], "Album"), - ("Tweaker-229 [PRH-002]", ["PRH-002", "Tweaker-229"], "PRH-002"), + ("Tweaker-229 [PRH-002]", ["PRH-002", "Tweaker-229"], ""), ("Album (limited edition)", [], "Album"), ("Album - VARIOUS ARTISTS", [], "Album"), ("Drepa Mann", [], "Drepa Mann"), ("Some ft. Some ONE - Album", ["Some ft. Some ONE"], "Album"), ("Some feat. Some ONE - Album", ["Some feat. Some ONE"], "Album"), - ("Healing Noise (EP) (Free Download)", [], "Healing Noise"), - ("[MCVA003] - VARIOUS ARTISTS", ["MCVA003"], "MCVA003"), + ("Healing Noise (EP) (Free Download)", [], "Healing Noise EP"), + ("[MCVA003] - VARIOUS ARTISTS", ["MCVA003"], ""), ("Drepa Mann [Vinyl]", [], "Drepa Mann"), ("Drepa Mann [Vinyl]", [], "Drepa Mann"), ("The Castle [BLCKLPS009] Incl. Remix", ["BLCKLPS009"], "The Castle"), - ("The Castle [BLCKLPS009] Incl. Remix", [], "The Castle [BLCKLPS009]"), - ('Anetha - "Ophiuchus EP"', ["Anetha"], "Ophiuchus"), + ("The Castle [BLCKLPS009] Incl. Remix", [], "The Castle"), + ('Anetha - "Ophiuchus EP"', ["Anetha"], "Ophiuchus EP"), ("Album (FREE DL)", [], "Album"), ("Devils Kiss VA", [], "Devils Kiss"), ("Devils Kiss VA001", [], "Devils Kiss VA001"), @@ -341,14 +358,14 @@ def test_parse_catalognum(album, disctitle, description, label, expected): ["EDLX.051", "Dax J"], "Illusions Of Power", ), - ("WEAPONS 001 - VARIOUS ARTISTS", ["WEAPONS 001"], "WEAPONS 001"), + ("WEAPONS 001 - VARIOUS ARTISTS", ["WEAPONS 001"], ""), ("Diva Hello", [], "Diva Hello"), - ("RR009 - Various Artist", ["RR009"], "RR009"), + ("RR009 - Various Artist", ["RR009"], ""), ("Diva (Incl. some sort of Remixes)", [], "Diva"), ("HWEP010 - MEZZ - COLOR OF WAR", ["HWEP010", "MEZZ"], "COLOR OF WAR"), ("O)))Bow 1", [], "O)))Bow 1"), - ("hi'Hello", ["hi"], "hi'Hello"), - ("hi]Hello", ["hi"], "]Hello"), + ("hi'Hello", ["hi"], "'Hello"), + ("Blood Moon †INVI VA006†", ["INVI VA006"], "Blood Moon"), ], ) def test_clean_name(name, extras, expected): @@ -362,13 +379,12 @@ def test_bundles_get_excluded(): {"name": "Vinyl", "musicReleaseFormat": "VinylFormat"}, ] } - assert set(Helpers._get_media_reference(meta)) == {"Vinyl"} + assert set(Metaguru._get_media_reference(meta)) == {"Vinyl"} @pytest.mark.parametrize( ("date", "expected"), [("08 Dec 2020 00:00:00 GMT", date(2020, 12, 8)), (None, None)] ) def test_handles_missing_publish_date(date, expected): - guru = Metaguru("") - guru.meta = {"datePublished": date} + guru = Metaguru({"datePublished": date}) assert guru.release_date == expected