7 Commits

Author SHA1 Message Date
629ea88388 Merge branch 'master' into new_docs 2020-03-13 02:47:31 +00:00
05922fc9a7 updating builder docstring 2020-03-13 02:46:30 +00:00
c7cabe8d63 adding new doctype 2020-03-12 23:58:31 +00:00
5b24952e1e Merge branch 'develop' 2020-03-12 21:30:27 +00:00
511986a131 updating cli.py 2020-03-12 21:30:12 +00:00
21472e8100 adding mkdocs 2020-03-12 01:27:47 +00:00
4e0091f80f latest 2020-03-11 20:44:46 +00:00
29 changed files with 905 additions and 386 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1,3 +1,8 @@
{ {
"python.pythonPath": "/Users/dtomlinson/.virtualenvs/musicbrainzapi/bin/python" "python.pythonPath": "/Users/dtomlinson/.virtualenvs/musicbrainzapi/bin/python",
"restructuredtext.confPath": "${workspaceFolder}/docs/source",
"restructuredtext.linter.executablePath": "/Users/dtomlinson/.virtualenvs/utility-doc8/bin/doc8",
"files.trimTrailingWhitespace": true,
"restructuredtext.languageServer.trace.server": "messages",
"editor.fontSize": 13,
} }

1
docs/about.md Normal file
View File

@@ -0,0 +1 @@
# Some about page

17
docs/index.md Normal file
View File

@@ -0,0 +1,17 @@
# Welcome to MkDocs
For full documentation visit [mkdocs.org](https://www.mkdocs.org).
## Commands
* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs -h` - Print help message and exit.
## Project layout
mkdocs.yml # The configuration file.
docs/
index.md # The documentation homepage.
... # Other markdown pages, images and other files.

20
docs/python.md Normal file
View File

@@ -0,0 +1,20 @@
# Some python
## Some test python
!!! warning "Important warning"
This isn't yet complete.
```python
def _generate_word_cloud(self) -> None:
"""Generates a word cloud
"""
self.wc = WordCloud(
max_words=150,
width=1500,
height=1500,
mask=self.char_mask,
random_state=1,
).generate_from_frequencies(self.freq)
return self
```

83
docs/test.md Normal file
View File

@@ -0,0 +1,83 @@
# musicbrainzapi.wordcloud package
Wordcloud from lyrics.
### class musicbrainzapi.wordcloud.LyricsWordcloud(pillow_img: PIL.PngImagePlugin.PngImageFile, all_albums_lyrics_count: Lyrics.all_albums_lyrics_count)
Bases: `object`
Create a word cloud from Lyrics.
* **Variables**
* **all_albums_lyrics_count** (*list*) List of all albums + track lyrics counted by each word
* **char_mask** (*np.array*) numpy array containing data for the word cloud image
* **freq** (*collections.Counter*) Counter object containing counts for all words across all tracks
* **lyrics_list** (*list*) List of all words from all lyrics across all tracks.
* **pillow_img** (*PIL.PngImagePlugin.PngImageFile*) pillow image of the word cloud base
* **wc** (*wordcloud.WordCloud*) WordCloud object
#### \__init__(pillow_img: PIL.PngImagePlugin.PngImageFile, all_albums_lyrics_count: Lyrics.all_albums_lyrics_count)
Create a worcloud object.
* **Parameters**
* **pillow_img** (*PIL.PngImagePlugin.PngImageFile*) pillow image of the word cloud base
* **all_albums_lyrics_count** (*Lyrics.all_albums_lyrics_count*) List of all albums + track lyrics counted by each word
#### classmethod use_microphone(all_albums_lyrics_count: Lyrics.all_albums_lyrics_count)
Class method to instantiate with a microphone base image.
* **Parameters**
**all_albums_lyrics_count** (*Lyrics.all_albums_lyrics_count*) List of all albums + track lyrics counted by each word
#### static generate_grey_colours(\*args, \*\*kwargs)
Static method to generate a random grey colour.
#### _get_lyrics_list()
Gets all words from lyrics in a single list + cleans them.
#### _get_frequencies()
Get frequencies of words from a list.
#### _get_char_mask()
Gets a numpy array for the image file.
#### _generate_word_cloud()
Generates a word cloud
#### _generate_plot()
Plots the wordcloud and sets matplotlib options.
#### create_word_cloud()
Creates a word cloud

5
docs/testdoc.md Normal file
View File

@@ -0,0 +1,5 @@
# Test documentation using mkdocstrings
## Reference
::: musicbrainzapi.api.lyrics.builder

39
mkdocs.yml Normal file
View File

@@ -0,0 +1,39 @@
site_name: Musicbrainzapi
nav:
- Welcome:
- Home: index.md
- About: about.md
- API:
- API: test.md
- Code:
- python.md
- testdoc.md
# theme: material
theme:
name: "material"
palette:
primary: "yellow"
accent: "red"
feature:
tabs: true
markdown_extensions:
- admonition
- codehilite:
guess_lang: true
- toc:
permalink: true
plugins:
- search
- mkdocstrings
repo_name: "dtomlinson91/musicbrainzapi"
repo_url: "https://github.com/dtomlinson91/musicbrainzapi-cv-airelogic"
extra:
social:
- type: "github"
link: "https://github.com/dtomlinson91/musicbrainzapi"

821
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,20 +20,18 @@ click = "^7.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^5.2" pytest = "^5.2"
python-language-server = "^0.31.8"
Rope = "^0.16.0"
Pyflakes = "^2.1.1"
McCabe = "^0.6.1"
pycodestyle = "^2.5.0"
pydocstyle = "^5.0.2"
autopep8 = "^1.5"
YAPF = "^0.29.0"
pudb = "^2019.2" pudb = "^2019.2"
pyls-black = "^0.4.4"
sphinx = "^2.4.4" sphinx = "^2.4.4"
sphinx_rtd_theme = "^0.4.3" sphinx_rtd_theme = "^0.4.3"
sphinx-click = "^2.3.1" sphinx-click = "^2.3.1"
coverage = "^5.0.3" prospector = "^1.2.0"
pylint = "^2.4.4"
pydoc-markdown = {git = "https://github.com/NiklasRosenstein/pydoc-markdown.git", rev = "develop"}
mkdocs = "^1.1"
mkdocs-material = "^4.6.3"
pymdown-extensions = "^6.3"
mkdocstrings = "^0.8.0"
beeprint = "^2.4.10"
[tool.poetry.plugins."console_scripts"] [tool.poetry.plugins."console_scripts"]
"musicbrainzapi" = "musicbrainzapi.cli.cli:cli" "musicbrainzapi" = "musicbrainzapi.cli.cli:cli"

View File

@@ -1,53 +1,49 @@
from __future__ import annotations from __future__ import annotations
from collections import Counter
import html import html
import json import json
import math import math
import string import string
from typing import Union, Dict from collections import Counter
from typing import Dict, Union
import addict import addict
import click import click
import musicbrainzngs import musicbrainzngs
import numpy as np import numpy as np
import requests import requests
from beeprint import pp
from musicbrainzapi.api.lyrics.concrete_builder import LyricsConcreteBuilder
from musicbrainzapi.api.lyrics import Lyrics
from musicbrainzapi.api import authenticate from musicbrainzapi.api import authenticate
from musicbrainzapi.api.lyrics import Lyrics
from musicbrainzapi.api.lyrics.concrete_builder import LyricsConcreteBuilder
class LyricsBuilder(LyricsConcreteBuilder): class LyricsBuilder(LyricsConcreteBuilder):
"""docstring for LyricsBuilder """
This interface will build a Lyrics object.
Attributes !!! info "Attributes"
---------- - musicbrainz_artists (addict.Dict): A dict response from the Musicbrainz api for all artists.
album_statistics : addict.Dict - release_group_ids (addict.Dict): : A dict response from the Musicbrainz api for all artists.
Dictionary containing album statistics - all_albums (list): : A dict response from the Musicbrainz api for all artists.
all_albums : list - total_track_count (list): : A dict response from the Musicbrainz api for all artists.
List of all albums + track titles - all_albums_lyrics_url (addict.Dict): : A dict response from the Musicbrainz api for all artists.
all_albums_lyrics : list - all_albums_lyrics (addict.Dict): : A dict response from the Musicbrainz api for all artists.
List of all albums + track lyrics - all_albums_lyrics_count (addict.Dict): : A dict response from the Musicbrainz api for all artists.
all_albums_lyrics_count : list - all_albums_lyrics_sum (addict.Dict): : A dict response from the Musicbrainz api for all artists.
List of all albums + track lyrics counted by each word - album_statistics (addict.Dict): : A dict response from the Musicbrainz api for all artists.
all_albums_lyrics_sum : list - album_statistics (addict.Dict): : A dict response from the Musicbrainz api for all artists.
List of all albums + track lyrics counted and summed up. - year_statistics (addict.Dict): : A dict response from the Musicbrainz api for all artists.
all_albums_lyrics_url : list - year_statistics (addict.Dict): : A dict response from the Musicbrainz api for all artists.
List of all albums + link to lyrics api for each track.
musicbrainz_artists : addict.Dict Example:
Dictionary of response from Musicbrainzapi A test example.
release_group_ids : addict.Dict
Dictionary of Musicbrainz release-group ids
total_track_count : int
Total number of tracks across all albums
year_statistics : addict.Dict
Dictionary containing album statistics
""" """
@property @property
def product(self) -> Lyrics: def product(self) -> Lyrics:
product = self._product return self._product
return product
@property @property
def artist(self) -> str: def artist(self) -> str:
@@ -95,17 +91,12 @@ class LyricsBuilder(LyricsConcreteBuilder):
def construct_lyrics_url(artist: str, song: str) -> str: def construct_lyrics_url(artist: str, song: str) -> str:
"""Builds the URL for the lyrics api. """Builds the URL for the lyrics api.
Parameters Args:
---------- artist (str): Your chosen artist.
artist : str song (str): A song to find a lyrics url for.
Artist
song : str
Track title
Returns Returns:
------- str: The url of the lyrics api for chosen song.
str
URL for lyrics from the lyrics api.
""" """
lyrics_api_base = 'https://api.lyrics.ovh/v1' lyrics_api_base = 'https://api.lyrics.ovh/v1'
@@ -167,7 +158,7 @@ class LyricsBuilder(LyricsConcreteBuilder):
Dict[str, int] Dict[str, int]
Dictionary of statistic and value. Dictionary of statistic and value.
""" """
if len(nums) == 0: if not nums:
return return
avg = math.ceil(np.mean(nums)) avg = math.ceil(np.mean(nums))
median = math.ceil(np.median(nums)) median = math.ceil(np.median(nums))
@@ -179,22 +170,22 @@ class LyricsBuilder(LyricsConcreteBuilder):
p_75 = math.ceil(np.percentile(nums, 75)) p_75 = math.ceil(np.percentile(nums, 75))
p_90 = math.ceil(np.percentile(nums, 90)) p_90 = math.ceil(np.percentile(nums, 90))
count = len(nums) count = len(nums)
_d = addict.Dict( return addict.Dict(
('avg', avg), ('avg', avg),
('median', median), ('median', median),
('std', std), ('std', std),
('max', _max), ('max', _max),
('min', _min), ('min', _min),
('p_10', p_10), ('p_10', p_10),
('p_25', p_25), ('p_25', p_25),
('p_75', p_75), ('p_75', p_75),
('p_90', p_90), ('p_90', p_90),
('count', count), ('count', count),
) )
return _d
def __init__(self) -> None: def __init__(self) -> None:
"""Create a builder instance to build a Lyrics object.""" """Create a `LyricsBuilder`.
"""
self.reset() self.reset()
def reset(self) -> None: def reset(self) -> None:
@@ -518,3 +509,6 @@ class LyricsBuilder(LyricsConcreteBuilder):
self.year_statistics = addict.Dict( self.year_statistics = addict.Dict(
**self.year_statistics, **addict.Dict((year, _d)) **self.year_statistics, **addict.Dict((year, _d))
) )
if __name__ == "__main__":
pp(LyricsBuilder)

View File

@@ -6,55 +6,64 @@ import click
from musicbrainzapi.__version__ import __version__ from musicbrainzapi.__version__ import __version__
from musicbrainzapi.__header__ import __header__ from musicbrainzapi.__header__ import __header__
CONTEXT_SETTINGS = dict(auto_envvar_prefix='COMPLEX') # pylint:disable=invalid-name
CONTEXT_SETTINGS = dict(auto_envvar_prefix="COMPLEX")
class Environment(object): class Environment:
"""Environment class to house shared parameters between all subcommands."""
def __init__(self): def __init__(self):
self.verbose = False self.verbose = False
self.home = os.getcwd() self.home = os.getcwd()
pass_environment = click.make_pass_decorator(Environment, ensure=True) pass_environment = click.make_pass_decorator(
Environment, ensure=True
)
cmd_folder = os.path.abspath( cmd_folder = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'commands') os.path.join(os.path.dirname(__file__), "commands")
) )
class ComplexCLI(click.MultiCommand): class ComplexCLI(click.MultiCommand):
"""Access and run subcommands."""
def list_commands(self, ctx): def list_commands(self, ctx):
rv = [] """List all subcommands."""
for filename in os.listdir(cmd_folder): rv = [
if filename.endswith('.py') and filename.startswith('cmd_'): filename[4:-3]
rv.append(filename[4:-3]) for filename in os.listdir(cmd_folder)
if filename.endswith(".py") and filename.startswith("cmd_")
]
rv.sort() rv.sort()
return rv return rv
def get_command(self, ctx, cmd_name): def get_command(self, ctx, cmd_name):
mod = import_module(f'musicbrainzapi.cli.commands.cmd_{cmd_name}') """Get chosen subcummands."""
mod = import_module(f"musicbrainzapi.cli.commands.cmd_{cmd_name}")
return getattr(mod, cmd_name) return getattr(mod, cmd_name)
@click.command(cls=ComplexCLI, context_settings=CONTEXT_SETTINGS) @click.command(cls=ComplexCLI, context_settings=CONTEXT_SETTINGS)
@click.option( @click.option(
'-p', "-p",
'--path', "--path",
type=click.Path( type=click.Path(exists=True, file_okay=False, resolve_path=True, writable=True),
exists=True, file_okay=False, resolve_path=True, writable=True help="Local path to save any output files.",
), default=os.getcwd(),
help='Local path to save any output files.',
default=os.getcwd()
) )
# @click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode.') @click.option("-v", "--verbose", is_flag=True, help="Enables verbose mode.")
@click.version_option( @click.version_option(
version=__version__, version=__version__,
prog_name=__header__, prog_name=__header__,
message=f'{__header__} version {__version__} 🎤', message=f"{__header__} version {__version__} 🎤",
) )
@pass_environment @pass_environment
def cli(ctx, path): def cli(ctx, verbose, path):
"""Base command for the musicbrainzapi program.""" """Display base command for the musicbrainzapi program."""
# ctx.verbose = verbose ctx.verbose = verbose
if path is not None: if path is not None:
click.echo(f'Path set to {os.path.expanduser(path)}') click.echo(f"Path set to {os.path.expanduser(path)}")
ctx.path = os.path.expanduser(path) ctx.path = os.path.expanduser(path)

View File

@@ -20,59 +20,57 @@ if typing.TYPE_CHECKING:
import PIL.PngImagePlugin.PngImageFile import PIL.PngImagePlugin.PngImageFile
# pylint:disable=line-too-long
class LyricsWordcloud: class LyricsWordcloud:
"""Create a word cloud from Lyrics. """
Create a Wordcloud from Lyrics.
The docstring continues here.
It should contain:
- something
- something else
Args:
pillow_img (PIL.PngImagePlugin.PngImageFile): pillow image of the word
cloud base
all_albums_lyrics_count (dict): A dictionary containing the lyrics from
a whole album.
!!! Attributes
- `pillow_img` (pillow): A pillow image.
Anything else can go here.
Example:
Here is how you can use it
Attributes
----------
all_albums_lyrics_count : list
List of all albums + track lyrics counted by each word
char_mask : np.array
numpy array containing data for the word cloud image
freq : collections.Counter
Counter object containing counts for all words across all tracks
lyrics_list : list
List of all words from all lyrics across all tracks.
pillow_img : PIL.PngImagePlugin.PngImageFile
pillow image of the word cloud base
wc : wordcloud.WordCloud
WordCloud object
""" """
def __init__( def __init__(
self, self,
pillow_img: 'PIL.PngImagePlugin.PngImageFile', pillow_img: "PIL.PngImagePlugin.PngImageFile",
all_albums_lyrics_count: 'Lyrics.all_albums_lyrics_count', all_albums_lyrics_count: "Lyrics.all_albums_lyrics_count",
): ):
"""
Create a worcloud object.
Parameters
----------
pillow_img : PIL.PngImagePlugin.PngImageFile
pillow image of the word cloud base
all_albums_lyrics_count : Lyrics.all_albums_lyrics_count
List of all albums + track lyrics counted by each word
"""
self.pillow_img = pillow_img self.pillow_img = pillow_img
self.all_albums_lyrics_count = all_albums_lyrics_count self.all_albums_lyrics_count = all_albums_lyrics_count
self.test = []
@classmethod @classmethod
def use_microphone( def use_microphone(
cls, all_albums_lyrics_count: 'Lyrics.all_albums_lyrics_count', cls, all_albums_lyrics_count: "Lyrics.all_albums_lyrics_count",
) -> LyricsWordcloud: ) -> LyricsWordcloud:
""" """Create a LyricsWordcloud using a microphone as a base image.
Class method to instantiate with a microphone base image.
Parameters Args:
---------- all_albums_lyrics_count (dict): A dictionary containing the lyrics from a whole album.
all_albums_lyrics_count : Lyrics.all_albums_lyrics_count Returns:
List of all albums + track lyrics counted by each word LyricsWordcloud: Instance of itself with a micrphone image loaded in.
""" """
mic_resource = resources.path( mic_resource = resources.path("musicbrainzapi.wordcloud.resources", "mic.png")
'musicbrainzapi.wordcloud.resources', 'mic.png'
)
with mic_resource as m: with mic_resource as m:
mic_img = Image.open(m) mic_img = Image.open(m)
@@ -80,15 +78,20 @@ class LyricsWordcloud:
@staticmethod @staticmethod
def generate_grey_colours( def generate_grey_colours(
# word: str, # word: str,
# font_size: str, # font_size: str,
# random_state: typing.Union[None, bool] = None, # random_state: typing.Union[None, bool] = None,
*args, *args,
**kwargs, **kwargs,
) -> str: ) -> str:
"""Static method to generate a random grey colour.""" """Static method to return a random grey color.
colour = f'hsl(0, 0%, {random.randint(60, 100)}%)'
return colour Returns:
str: A random grey colour in `hsl` form.
Can be any grey colour.
"""
return f"hsl(0, 0%, {random.randint(60, 100)}%)"
def _get_lyrics_list(self) -> None: def _get_lyrics_list(self) -> None:
"""Gets all words from lyrics in a single list + cleans them. """Gets all words from lyrics in a single list + cleans them.
@@ -101,12 +104,8 @@ class LyricsWordcloud:
for word in track: for word in track:
for _ in range(1, word[1]): for _ in range(1, word[1]):
cleaned = word[0] cleaned = word[0]
cleaned = re.sub( cleaned = re.sub(r"[\(\[].*?[\)\]]", " ", cleaned)
r'[\(\[].*?[\)\]]', ' ', cleaned cleaned = re.sub(r"[^a-zA-Z0-9\s]", "", cleaned)
)
cleaned = re.sub(
r'[^a-zA-Z0-9\s]', '', cleaned
)
cleaned = cleaned.lower() cleaned = cleaned.lower()
if cleaned in STOPWORDS: if cleaned in STOPWORDS:
continue continue
@@ -129,11 +128,7 @@ class LyricsWordcloud:
"""Generates a word cloud """Generates a word cloud
""" """
self.wc = WordCloud( self.wc = WordCloud(
max_words=150, max_words=150, width=1500, height=1500, mask=self.char_mask, random_state=1,
width=1500,
height=1500,
mask=self.char_mask,
random_state=1,
).generate_from_frequencies(self.freq) ).generate_from_frequencies(self.freq)
return self return self
@@ -141,18 +136,16 @@ class LyricsWordcloud:
"""Plots the wordcloud and sets matplotlib options. """Plots the wordcloud and sets matplotlib options.
""" """
plt.imshow( plt.imshow(
self.wc.recolor( self.wc.recolor(color_func=self.generate_grey_colours, random_state=3),
color_func=self.generate_grey_colours, random_state=3 interpolation="bilinear",
),
interpolation='bilinear',
) )
plt.axis('off') plt.axis("off")
return self return self
# def show_word_cloud(self): # def show_word_cloud(self):
# """Shows the word cloud. # """Shows the word cloud.
# """ # """
# plt.show() # plt.show()
def create_word_cloud(self) -> None: def create_word_cloud(self) -> None:
"""Creates a word cloud """Creates a word cloud

Binary file not shown.