diff options
-rw-r--r-- | .gitignore | 57 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | anidb/__init__.py | 139 | ||||
-rw-r--r-- | setup.py | 36 |
4 files changed, 235 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6797c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Created by http://www.gitignore.io + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ec932e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# anidb.py + +AniDB API wrapper
\ No newline at end of file diff --git a/anidb/__init__.py b/anidb/__init__.py new file mode 100644 index 0000000..29e9b78 --- /dev/null +++ b/anidb/__init__.py @@ -0,0 +1,139 @@ +import requests +from bs4 import BeautifulSoup + +from datetime import datetime + +BASE_URL = "http://api.anidb.net:9001/httpapi" +SEARCH_URL = "http://anisearch.outrance.pl/" +PROTOCOL_VERSION = 1 + +try: + str = unicode +except: + pass + # str = str + +class AniDB: + def __init__(self, client_id, client_ver=0): + self.client_id = client_id + self.client_ver = client_ver + self._cache = {} + + def _request(self, datapage, params={}, cache=True): + params.update({ + 'client': self.client_id, + 'clientver': self.client_ver, + 'protover': PROTOCOL_VERSION, + 'request': datapage + }) + r = requests.get(BASE_URL, params=params) + r.raise_for_status() + return r + + # Anime http://wiki.anidb.net/w/HTTP_API_Definition#Access + + def search(self, q): + """ + Search for `aid`s by anime title using service provided by eloyard. + http://anisearch.outrance.pl/doc.html + """ + r = requests.get(SEARCH_URL, params={ + 'task': "search", + 'query': q, + }) + r.raise_for_status() + results = [] + animetitles = BeautifulSoup(r.text, 'xml').animetitles + for anime in animetitles.find_all('anime'): + results.append(Anime({ + 'id': int(anime['id']), + 'title': str(anime.find('title', attrs={'type': "main"}).string) + }, partial=True, updater=lambda: self.get(anime['id']))) + + return results + + def get(self, id): + """ + Allows retrieval of non-file or episode related information for a specific anime by AID (AniDB anime id). + http://wiki.anidb.net/w/HTTP_API_Definition#Anime + """ + id = int(id) # why? + + r = self._request("anime", {'aid': id}) + soup = BeautifulSoup(r.text, 'xml') + if soup.error is not None: + raise Exception(soup.error.string) + + anime = soup.anime + titles = anime.titles + + a = Anime({ + 'id': id, + 'type': str(anime.type.string), + 'episodecount': int(anime.episodecount.string), + 'startdate': datetime(*list(map(int, anime.startdate.string.split("-")))), + 'enddate': datetime(*list(map(int, anime.enddate.string.split("-")))), + 'titles': [( + str(title.string), + title['type'] if 'type' in title else "unknown" + ) for title in anime.find_all('title')], + 'title': str(titles.find('title', attrs={'type': "main"}).string), + 'relatedanime': [], + 'url': str(anime.url.string), + 'creators': [], + 'description': str(anime.description.string), + 'ratings': SmartDict({ + 'permanent': float(anime.ratings.permanent.string), + 'temporary': float(anime.ratings.temporary.string), + 'review': float(anime.ratings.review.string) + }), + 'picture': "http://img7.anidb.net/pics/anime/" + str(anime.picture.string), + 'categories': [], + 'tags': [], + 'characters': [], + 'episodes': [], + }) + + self._cache[id] = a + + return a + +class SmartDict(dict): + def __init__(self, *a, **kw): + super(SmartDict, self).__init__(**kw) + for x in a: + try: + self.update(x) + except TypeError: + self.update(x.__dict__) + self.__dict__ = self + +class Anime: + def __init__(self, data={}, partial=False, **kw): + self._data = data + self._partial = partial + if partial: + self._updater = kw['updater'] + + def _update(self): + if not self._partial: + raise Exception("Attempted to update a ready object") + else: + self._data = self._updater()._data + self._partial = False + + def __getattr__(self, name): + if name in self._data: + return self._data[name] + else: + if self._partial: + self._update() + if name in self._data: + return self._data[name] + else: + raise AttributeError("no attribute called '%s'" % name) + else: + raise AttributeError("no attribute called '%s'" % name) + + def __repr__(self): + return u'<Anime %i "%s">' % (self.id, self.title)
\ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a75b0f8 --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +import os +import sys + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +if sys.argv[-1] == 'publish': + os.system('python setup.py sdist upload') + sys.exit() + +setup( + name="anidb", + version="0.0.1", + description="AniDB API wrapper", + long_description="", + url="https://github.com/iamale/anidb.py", + author="Ale", + author_email="ale@incrowd.ws", + packages=['anidb'], + package_dir={'anidb': 'anidb'}, + install_requires=["requests==2.3.0", "beautifulsoup4==4.3.2"], + license='MIT', + classifiers=( + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7' + ), +) |