Skip to content

Commit 05eb8e7

Browse files
authored
Merge branch 'master' into pyup-pin-mypy-0.521
2 parents d83c2c4 + 748e63a commit 05eb8e7

11 files changed

Lines changed: 323 additions & 33 deletions

File tree

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ python:
3232
- 3.4
3333
- 2.7
3434
script: tox
35+
36+
after_success:
37+

README.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,18 @@ LyricsMaster
1414
:alt: Documentation Status
1515

1616
.. image:: https://pyup.io/repos/github/SekouD/lyricsmaster/shield.svg
17-
:target: https://pyup.io/repos/github/SekouD/lyricsmaster/
18-
:alt: Updates
17+
:target: https://pyup.io/repos/github/SekouD/lyricsmaster/
18+
:alt: Updates
19+
:alt: Coverage Status
1920

21+
.. image:: https://codecov.io/gh/SekouD/lyricsmaster/branch/master/graph/badge.svg
22+
:target: https://codecov.io/gh/SekouD/lyricsmaster
2023

21-
LyricsMaster is a library for analyzing lyrics using various Machine Learning algorithms. It includes utilities for downloading lyrics from multiple lyrics providers.
24+
LyricsMaster is a library for downloading lyrics from multiple lyrics providers. The following Lyrics Providers are supported:
25+
26+
- Lyric Wikia
27+
- The Original Hip-Hop (Rap) Lyrics Archive - OHHLA.com
28+
- and more to come soon.
2229

2330

2431
* Free software: MIT license

lyricsmaster/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55
__author__ = """SekouD"""
66
__email__ = 'sekoud.python@gmail.com'
7-
__version__ = '0.1.9'
7+
__version__ = '1.0.2'

lyricsmaster/lyricsmaster.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,65 @@
11
# -*- coding: utf-8 -*-
22

33
"""Main module."""
4+
5+
class Song:
6+
def __init__(self, title, album, author, lyrics):
7+
self.title = title
8+
self.album = album
9+
self.author = author
10+
self.lyrics = lyrics
11+
12+
def __repr__(self):
13+
return self.__class__.__name__ + " Object: " + self.title
14+
15+
16+
class Album:
17+
def __init__(self, title, author, songs):
18+
self.idx = 0
19+
self.title = title
20+
self.author = author
21+
self.songs = songs
22+
23+
def __repr__(self):
24+
return self.__class__.__name__ + " Object: " + self.title
25+
26+
def __len__(self):
27+
return len(self.songs)
28+
29+
def __iter__(self):
30+
return self
31+
32+
def __next__(self):
33+
self.idx += 1
34+
try:
35+
return self.songs[self.idx - 1]
36+
except IndexError:
37+
self.idx = 0
38+
raise StopIteration
39+
40+
next = __next__
41+
42+
class Discography:
43+
def __init__(self, author, albums):
44+
self.idx = 0
45+
self.author = author
46+
self.albums = albums
47+
48+
def __repr__(self):
49+
return self.__class__.__name__ + " Object: " + self.author
50+
51+
def __len__(self):
52+
return len(self.albums)
53+
54+
def __iter__(self):
55+
return self
56+
57+
def __next__(self):
58+
self.idx += 1
59+
try:
60+
return self.albums[self.idx - 1]
61+
except IndexError:
62+
self.idx = 0
63+
raise StopIteration
64+
65+
next = __next__

lyricsmaster/lyricsprovider.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Lyrics Providers."""
4+
from .lyricsmaster import Song, Album, Discography
5+
import requests
6+
import bs4
7+
from bs4 import BeautifulSoup
8+
9+
10+
class LyricsProvider:
11+
def get_page(self, url):
12+
try:
13+
req = requests.get(url)
14+
except:
15+
req = None
16+
print('Unable to download url ' + url)
17+
return req
18+
19+
def get_lyrics(self, author):
20+
pass
21+
22+
def get_artist_page(self, author):
23+
pass
24+
25+
def get_album_page(self, author, album):
26+
pass
27+
28+
def get_lyrics_page(self, author, album, title):
29+
pass
30+
31+
def extract_lyrics(self, song):
32+
pass
33+
34+
35+
class LyricWiki(LyricsProvider):
36+
base_url = 'http://lyrics.wikia.com'
37+
38+
def clean_string(self, text):
39+
text = text.replace('#', 'Number_').replace('[', '(').replace(']', ')').replace('{', '(').replace('}', ')')
40+
text = text.replace(' ', '_')
41+
return text
42+
43+
def get_artist_page(self, author):
44+
author = self.clean_string(author)
45+
url = self.base_url + '/wiki/' + author
46+
artist_page = BeautifulSoup(self.get_page(url).text, 'lxml')
47+
return artist_page
48+
49+
def get_album_page(self, author, album):
50+
author = self.clean_string(author)
51+
album = self.clean_string(album)
52+
url = self.base_url + '/wiki/' + author + ':' + album
53+
album_page = BeautifulSoup(self.get_page(url).text, 'lxml')
54+
return album_page
55+
56+
def get_lyrics_page(self, url):
57+
lyrics_page = BeautifulSoup(self.get_page(url).text, 'lxml')
58+
return lyrics_page
59+
60+
def extract_lyrics(self, song):
61+
lyric_box = song.find("div", {'class': 'lyricbox'})
62+
lyrics = lyric_box.text
63+
return lyrics
64+
65+
def get_songs(self, album):
66+
parent_node = album.parent
67+
while parent_node.name != 'ol':
68+
parent_node = parent_node.next_sibling
69+
song_links = parent_node.find_all('li')
70+
return song_links
71+
72+
def create_song(self, link, author, album_title):
73+
link = link.find('a')
74+
song_title = link.attrs['title']
75+
if '(page does not exist' in song_title:
76+
return None
77+
lyrics_page = self.get_lyrics_page(self.base_url + link.attrs['href'])
78+
lyrics = self.extract_lyrics(lyrics_page)
79+
song = Song(song_title, album_title, author, lyrics)
80+
return song
81+
82+
def get_lyrics(self, author):
83+
artist_page = self.get_artist_page(author)
84+
albums = [tag for tag in artist_page.find_all("span", {'class': 'mw-headline'}) if
85+
tag.attrs['id'] not in ('Additional_information', 'External_links')]
86+
album_objects = []
87+
for elmt in albums:
88+
album_title = elmt.text
89+
song_links = self.get_songs(elmt)
90+
songs = [self.create_song(link, author, album_title) for link in song_links]
91+
album = Album(album_title, author, songs)
92+
album_objects.append(album)
93+
discography = Discography(author, album_objects)
94+
return discography
95+
96+
97+
98+
99+
100+
101+
class RapProvider(LyricsProvider):
102+
base_url = "http://ohhla.com/all{0}.html"
103+
pass
104+
105+
if __name__ == "__main__":
106+
test = LyricWiki()
107+
artist = test.get_artist_page('2Pac')
108+
album = test.get_album_page('2Pac', 'Me Against The World (1995)')
109+
lyrics_page = test.get_lyrics_page('http://lyrics.wikia.com/wiki/2Pac:Young_Black_Male')
110+
lyrics = test.extract_lyrics(lyrics_page)
111+
test_wikia = test.get_lyrics('Reggie Watts')
112+
pass

requirements.txt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
scikit-learn>=0.18
2-
git+https://github.com/clips/pattern.git@development
3-
pandas==0.20.3
4-
h5py>=2.7
5-
matplotlib>=2
1+
beautifulsoup4==4.6.0
2+
requests

requirements_dev.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ Sphinx==1.6.3
99
cryptography==2.0.3
1010
PyYAML==3.12
1111
mypy==0.521
12+
pytest==3.2.2
13+
hypothesis==3.28.1
14+
beautifulsoup4==4.6.0
15+
requests
16+

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.1.9
2+
current_version = 1.0.2
33
commit = True
44
tag = True
55

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
setup(
2828
name='lyricsmaster',
29-
version='0.1.9',
29+
version='1.0.2',
3030
description="LyricsMaster is a library for analyzing lyrics using various Machine Learning algorithms. It includes utilities for downloading lyrics from multiple lyrics providers.",
3131
long_description=readme + '\n\n' + history,
3232
author="SekouD",

tests/test_lyricsmaster.py

Lines changed: 116 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,129 @@
44
"""Tests for `lyricsmaster` package."""
55

66

7-
import unittest
7+
from collections import Iterable
8+
import pytest
89
from click.testing import CliRunner
910

11+
from bs4 import BeautifulSoup, Tag
12+
1013
from lyricsmaster import lyricsmaster
1114
from lyricsmaster import cli
15+
from lyricsmaster import lyricsprovider
16+
17+
try:
18+
basestring
19+
except NameError:
20+
basestring = str
21+
22+
@pytest.fixture(scope="module")
23+
def songs():
24+
songs = [lyricsmaster.Song('Bad Love', 'Bad news is coming','Luther Alison', None),
25+
lyricsmaster.Song('Ragged and dirty', 'Bad news is coming', 'Luther Alison', None),
26+
lyricsmaster.Song('Red rooster', 'Bad news is coming', 'Luther Alison', None),
27+
lyricsmaster.Song('Life is bitch', 'Bad news is coming', 'Luther Alison', None)]
28+
return songs
29+
30+
class TestSongs:
31+
"""Tests for Song Class."""
32+
song = lyricsmaster.Song('Bad Love', 'Bad news is coming','Luther Alison', None)
33+
34+
def test_song(self):
35+
assert self.song.__repr__() == 'Song Object: Bad Love'
36+
37+
38+
class TestAlbums:
39+
"""Tests for Album Class."""
40+
41+
songs = songs()
42+
album = lyricsmaster.Album('Bad news is coming', 'Luther Alison', songs)
43+
44+
def test_album(self):
45+
assert self.album.idx == 0
46+
assert self.album.title == 'Bad news is coming'
47+
assert self.album.author == 'Luther Alison'
48+
assert self.album.__repr__() == 'Album Object: Bad news is coming'
49+
50+
def test_album_isiter(self):
51+
assert len(self.album) == 4
52+
assert [elmt for elmt in self.album] == self.songs
53+
54+
55+
class TestDiscography:
56+
"""Tests for Album Class."""
57+
58+
albums = [lyricsmaster.Album('Bad news is coming', 'Luther Alison', songs),
59+
lyricsmaster.Album('Bad news is coming', 'Luther Alison', songs)]
60+
discography = lyricsmaster.Discography('Luther Allison', albums)
61+
62+
def test_discography(self):
63+
assert self.discography.__repr__() == 'Discography Object: Luther Allison'
64+
65+
def test_discography_isiter(self):
66+
assert len(self.discography) == 2
67+
assert [elmt for elmt in self.discography] == self.albums
68+
69+
class TestLyricWiki:
70+
provider = lyricsprovider.LyricWiki()
71+
author = 'Reggie Watts'
72+
73+
def test_clean_string(self):
74+
assert self.provider.clean_string('Reggie Watts {(#5)}') == 'Reggie_Watts_((Number_5))'
75+
76+
def test_get_artist_page(self):
77+
page = self.provider.get_artist_page(self.author)
78+
assert isinstance(page, BeautifulSoup)
79+
80+
def test_get_album_page(self):
81+
page = self.provider.get_album_page('Reggie Watts', 'Simplified (2004)')
82+
assert isinstance(page, BeautifulSoup)
83+
84+
def test_get_lyrics_page(self):
85+
page = self.provider.get_lyrics_page('http://lyrics.wikia.com/wiki/Reggie_Watts:Your_Name')
86+
assert isinstance(page, BeautifulSoup)
87+
88+
def test_extract_lyrics(self):
89+
page = self.provider.get_lyrics_page('http://lyrics.wikia.com/wiki/Reggie_Watts:Your_Name')
90+
lyrics = self.provider.extract_lyrics(page)
91+
assert isinstance(lyrics, basestring)
92+
assert 'I recall the day' in lyrics
93+
assert "And I hope you'll stay." in lyrics
1294

95+
def test_get_songs(self):
96+
author_page = self.provider.get_artist_page(self.author)
97+
album = [tag for tag in author_page.find_all("span", {'class': 'mw-headline'}) if
98+
tag.attrs['id'] not in ('Additional_information', 'External_links')][0]
99+
song_links = self.provider.get_songs(album)
100+
for link in song_links:
101+
assert isinstance(link, Tag)
13102

14-
class TestLyricsmaster(unittest.TestCase):
15-
"""Tests for `lyricsmaster` package."""
103+
def test_create_song(self):
104+
author_page = self.provider.get_artist_page(self.author)
105+
album = [tag for tag in author_page.find_all("span", {'class': 'mw-headline'}) if
106+
tag.attrs['id'] not in ('Additional_information', 'External_links')][0]
107+
song_links = self.provider.get_songs(album)
108+
fail_song = self.provider.create_song(song_links[0], self.author, "Simplified (2004)")
109+
assert fail_song is None
110+
good_song = self.provider.create_song(song_links[9], self.author, "Simplified (2004)")
111+
assert isinstance(good_song, lyricsmaster.Song)
112+
assert good_song.title == 'Reggie Watts:Your Name'
113+
assert good_song.album == "Simplified (2004)"
114+
assert good_song.author == self.author
115+
assert 'I recall the day' in good_song.lyrics
116+
assert "And I hope you'll stay." in good_song.lyrics
16117

17-
def setUp(self):
18-
"""Set up test fixtures, if any."""
118+
def test_get_lyrics(self):
119+
discography = self.provider.get_lyrics(self.author)
120+
assert isinstance(discography, lyricsmaster.Discography)
19121

20-
def tearDown(self):
21-
"""Tear down test fixtures, if any."""
22122

23-
def test_000_something(self):
24-
"""Test something."""
25123

26-
def test_command_line_interface(self):
27-
"""Test the CLI."""
28-
runner = CliRunner()
29-
result = runner.invoke(cli.main)
30-
assert result.exit_code == 0
31-
assert 'lyricsmaster.cli.main' in result.output
32-
help_result = runner.invoke(cli.main, ['--help'])
33-
assert help_result.exit_code == 0
34-
assert '--help Show this message and exit.' in help_result.output
124+
def test_command_line_interface():
125+
"""Test the CLI."""
126+
runner = CliRunner()
127+
result = runner.invoke(cli.main)
128+
assert result.exit_code == 0
129+
assert 'lyricsmaster.cli.main' in result.output
130+
help_result = runner.invoke(cli.main, ['--help'])
131+
assert help_result.exit_code == 0
132+
assert '--help Show this message and exit.' in help_result.output

0 commit comments

Comments
 (0)