Permalink
Browse files

Cleanup, more development, still WIP.

- Created separate package `orochi`
- Split up api and client
- Started creating a client using the cmd module
- Converted api functions to a wrapper class
  • Loading branch information...
1 parent 64dddf9 commit 179a63e146f0ee669dd040d3b1d2b835b226afb7 @dbrgn committed Apr 4, 2013
Showing with 212 additions and 84 deletions.
  1. +10 −1 README.rst
  2. +0 −83 client.py
  3. 0 orochi/__init__.py
  4. +132 −0 orochi/api.py
  5. +70 −0 orochi/client.py
View
@@ -16,14 +16,23 @@ Python).
*Image courtesy of Gustavo Araujo*
+
Usage
-----
::
$ pip install -r requirements.txt
$ export EIGHTTRACKS_API_KEY='your_api_key'
- $ python client.py
+ $ python -m orochi.client
+
+
+Coding Guidelines
+-----------------
+
+PEP8 via `flake8 <https://pypi.python.org/pypi/flake8>`_ with max-line-width set
+to 99 and E126-E128 ignored.
+
License
-------
View
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import print_function, division, absolute_import, unicode_literals
-
-import os
-import time
-from string import Template
-
-import requests
-import mpylayer
-
-
-# Helper functions
-
-def env(key):
- try:
- return os.environ[key]
- except KeyError:
- print('Please set the {key} environment variable.'.format(key=key))
- sys.exit(-1)
-
-
-# Configuration
-
-HEADERS = {
- 'X-Api-Key': env('EIGHTTRACKS_API_KEY'),
- 'X-Api-Version': 2,
- 'Accept': 'application/json',
-}
-
-BASE_URL = 'http://8tracks.com/'
-
-
-# Search mixes
-
-search = raw_input('Search keywords: ')
-params = {
- 'q': search,
- 'sort': 'hot',
- 'per_page': 20,
-}
-r = requests.get(BASE_URL + 'mixes.json', params=params, headers=HEADERS)
-data_mixes = r.json()
-assert data_mixes['errors'] is None
-
-print('Found the following mixes:')
-mix_info_tpl = Template(' $id) $name ($trackcount tracks, ${hours}h ${minutes}m)')
-for i, mix in enumerate(data_mixes['mixes'], 1):
- hours = mix['duration'] // 60 // 60
- minutes = (mix['duration'] // 60) % 60
- mix_info = mix_info_tpl.substitute(id=i, name=mix['name'],
- trackcount=mix['tracks_count'], hours=hours, minutes=minutes)
- print(mix_info)
-
-
-# Choose mix
-
-mix_id = int(raw_input('Please choose a mix: '))
-mix = data_mixes['mixes'][mix_id - 1]
-
-
-# Obtain a play token
-
-r = requests.get(BASE_URL + 'sets/new.json', headers=HEADERS)
-data_token = r.json()
-assert data_token['errors'] is None
-
-play_token = data_token['play_token']
-
-
-# Get song
-
-params = {'mix_id': mix['id']}
-r = requests.get(BASE_URL + 'sets/{token}/play.json'.format(token=play_token), params=params, headers=HEADERS)
-data_track = r.json()
-track = data_track['set']['track']
-
-print('Now playing "{track[name]}" by "{track[performer]}"...'.format(track=track))
-mp = mpylayer.MPlayerControl()
-mp.loadfile(track['url'])
-time.sleep(1)
-mp.pause() # Start playback
-time.sleep(1)
-time.sleep(mp.length - 1)
View
No changes.
View
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+from __future__ import print_function, division, absolute_import, unicode_literals
+
+import os
+import sys
+import time
+
+import requests
+import mpylayer
+
+
+def env(key):
+ try:
+ return os.environ[key]
+ except KeyError:
+ print('Please set the {key} environment variable.'.format(key=key))
+ sys.exit(-1)
+
+
+class APIError(RuntimeError):
+ """Raised when the API returns error messages."""
+ pass
+
+
+class EightTracksAPI(object):
+
+ def __init__(self):
+ self.base_url = 'https://8tracks.com/'
+ self.s = requests.Session()
+ self.s.headers.update({
+ 'X-Api-Key': env('EIGHTTRACKS_API_KEY'),
+ 'X-Api-Version': 2,
+ 'Accept': 'application/json',
+ })
+
+ def _get(self, resource, params={}, **kwargs):
+ """Do a GET request to the specified API resource.
+
+ After the query is sent, the HTTP status is verified (a non-200 status
+ raises an exception). Then the JSON data is unpacked and verified for
+ errors.
+
+ Args:
+ resource:
+ The resource that is appended to the base url (without any GET
+ parameters).
+ params:
+ The GET parameters dictionary. Default: {}.
+ **kwargs:
+ Any other keyword arguments that should be passed directly to
+ requests.
+
+ Returns:
+ The JSON response data.
+
+ Raises:
+ requests.exceptions.HTTPError:
+ Raised if the request fails or if it returns a non-200 status
+ code.
+ simplejson.decoder.JSONDecodeError:
+ Raised if JSON decoding fails.
+ APIError:
+ Raised when the API returns an error. The first argument is the
+ error message, the second argument is the entire JSON response.
+
+ """
+ r = self.s.get(self.base_url + resource, params=params, **kwargs)
+ r.raise_for_status()
+ data = r.json()
+ if 'errors' in data and data['errors'] is not None:
+ raise APIError(data['errors'], data)
+ return data
+
+ def search_mix(self, query, sort='hot', page=1, per_page=20):
+ """Search for a mix.
+
+ Args:
+ query:
+ The search term to search for.
+ sort:
+ The sort order. Possible values: recent, popular, hot.
+ Default: 'hot'.
+ page:
+ Which result page to return, if more than ``per_page`` are
+ found.
+ per_page:
+ How many mixes to return per page. Default: 20.
+
+ Returns:
+ The list of matching mixes.
+
+ """
+ data = self._get('mixes.json', {
+ 'q': query,
+ 'sort': sort,
+ 'per_page': per_page,
+ })
+ return data['mixes']
+
+
+
+#
+#
+## Choose mix
+#
+#mix_id = int(raw_input('Please choose a mix: '))
+#mix = data_mixes['mixes'][mix_id - 1]
+#
+#
+## Obtain a play token
+#
+#r = requests.get(BASE_URL + 'sets/new.json', headers=HEADERS)
+#data_token = r.json()
+#assert data_token['errors'] is None
+#
+#play_token = data_token['play_token']
+#
+#
+## Get song
+#
+#params = {'mix_id': mix['id']}
+#r = requests.get(BASE_URL + 'sets/{token}/play.json'.format(token=play_token), params=params, headers=HEADERS)
+#data_track = r.json()
+#track = data_track['set']['track']
+#
+#print('Now playing "{track[name]}" by "{track[performer]}"...'.format(track=track))
+#mp = mpylayer.MPlayerControl()
+#mp.loadfile(track['url'])
+#time.sleep(1)
+#mp.pause() # Start playback
+#time.sleep(1)
+#time.sleep(mp.length - 1)
View
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+from __future__ import print_function, division, absolute_import, unicode_literals
+
+import os
+import cmd
+from string import Template
+from textwrap import TextWrapper
+
+from .api import EightTracksAPI
+
+
+class CmdExitMixin(object):
+ """A mixin for a Cmd instance that provides the exit and quit command."""
+
+ def do_exit(self, s):
+ return True
+
+ def help_exit(self):
+ print('Exit the interpreter.')
+ print('You can also use the Ctrl-D shortcut.')
+
+ do_EOF = do_exit
+ help_EOF = help_exit
+ do_quit = do_exit
+ help_quit = help_exit
+
+
+class Client(CmdExitMixin, cmd.Cmd, object):
+
+ # Setup / configuration
+
+ def preloop(self):
+ print('Hello')
+ self.api = EightTracksAPI()
+ return super(Client, self).preloop()
+
+ def precmd(self, line):
+ self.console_width = int(os.popen('stty size', 'r').read().split()[1])
+ return super(Client, self).precmd(line)
+
+ def postloop(self):
+ print('Goodbye')
+ return super(Client, self).postloop()
+
+ def emptyline(self):
+ """Don't repeat last command on empty line."""
+ pass
+
+ # Actual commands
+
+ def do_search(self, s):
+ print('Results for "{}":'.format(s))
+ mixes = self.api.search_mix(s)
+ wrapper = TextWrapper(width=self.console_width - 5, subsequent_indent=(' ' * 5))
+ mix_info_tpl = Template('$name ($trackcount tracks, ${hours}h ${minutes}m)')
+ for i, mix in enumerate(mixes, 1):
+ prefix = ' {0})'.format(i).ljust(5)
+ hours = mix['duration'] // 60 // 60
+ minutes = (mix['duration'] // 60) % 60
+ mix_info = mix_info_tpl.substitute(name=mix['name'],
+ trackcount=mix['tracks_count'], hours=hours, minutes=minutes)
+ print(prefix + wrapper.fill(mix_info))
+
+ def help_search(self):
+ print('Search for a mix.')
+
+
+if __name__ == '__main__':
+ client = Client()
+ client.cmdloop()

0 comments on commit 179a63e

Please sign in to comment.