From 053778ce07c6080cb1343c8187d346179fccd34f Mon Sep 17 00:00:00 2001 From: Richard O'Dwyer Date: Thu, 29 Aug 2013 00:41:00 +0000 Subject: [PATCH] PEP8 code style updates. Modified from original commit. Signed-off-by: Raymond Wagner --- scripts/populate_locale.py | 5 +- scripts/pytmdb3.py | 1 - setup.py | 12 +- tmdb3/cache.py | 40 +-- tmdb3/cache_engine.py | 30 ++- tmdb3/cache_file.py | 52 ++-- tmdb3/cache_null.py | 18 +- tmdb3/locales.py | 24 +- tmdb3/pager.py | 26 +- tmdb3/request.py | 59 +++-- tmdb3/tmdb_api.py | 491 +++++++++++++++++++++---------------- tmdb3/tmdb_auth.py | 35 +-- tmdb3/tmdb_exceptions.py | 90 ++++--- tmdb3/util.py | 179 ++++++++------ 14 files changed, 612 insertions(+), 450 deletions(-) diff --git a/scripts/populate_locale.py b/scripts/populate_locale.py index ff36f98..d1aee4d 100755 --- a/scripts/populate_locale.py +++ b/scripts/populate_locale.py @@ -10,12 +10,14 @@ import sys import os + def sanitize(name): name = ' '.join(name.split()) return name + fpath = os.path.join(os.getcwd(), __file__) if not __file__.startswith('/') else __file__ -fpath = os.path.join(fpath.rsplit('/',2)[0], 'tmdb3/locales.py') +fpath = os.path.join(fpath.rsplit('/', 2)[0], 'tmdb3/locales.py') fd = open(fpath, 'r') while True: @@ -52,4 +54,3 @@ def sanitize(name): continue name, _, alpha2 = [t.text if t.text else t.getchildren()[0].tail for t in row] fd.write('Country("{0}", u"{1}")\n'.format(alpha2, sanitize(name).encode('utf8'))) - diff --git a/scripts/pytmdb3.py b/scripts/pytmdb3.py index ad63cab..afce681 100755 --- a/scripts/pytmdb3.py +++ b/scripts/pytmdb3.py @@ -49,4 +49,3 @@ namespace = globals().copy() namespace.update(locals()) code.InteractiveConsole(namespace).interact(banner) - diff --git a/setup.py b/setup.py index 3294b5c..0397d25 100755 --- a/setup.py +++ b/setup.py @@ -3,9 +3,9 @@ from distutils.core import setup setup( - name='tmdb3', - version='0.6.16', - description='TheMovieDB.org APIv3 interface', - long_description="Object-oriented interface to TheMovieDB.org's v3 API.", - packages=['tmdb3'] - ) + name='tmdb3', + version='0.6.16', + description='TheMovieDB.org APIv3 interface', + long_description="Object-oriented interface to TheMovieDB.org's v3 API.", + packages=['tmdb3'] +) diff --git a/tmdb3/cache.py b/tmdb3/cache.py index 9e124ea..463d7a2 100644 --- a/tmdb3/cache.py +++ b/tmdb3/cache.py @@ -16,14 +16,18 @@ import cache_null import cache_file -class Cache( object ): + +class Cache(object): """ - This class implements a persistent cache, backed in a file specified in - the object creation. The file is protected for safe, concurrent access - by multiple instances using flock. - This cache uses JSON for speed and storage efficiency, so only simple - data types are supported. - Data is stored in a simple format {key:(expiretimestamp, data)} + This class implements a cache framework, allowing selecting of a + pluggable engine. The framework stores data in a key/value manner, + along with a lifetime, after which data will be expired and + pulled fresh next time it is requested from the cache. + + This class defines a wrapper to be used with query functions. The + wrapper will automatically cache the inputs and outputs of the + wrapped function, pulling the output from local storage for + subsequent calls with those inputs. """ def __init__(self, engine=None, *args, **kwargs): self._engine = None @@ -40,7 +44,7 @@ def _import(self, data=None): self._age = max(self._age, obj.creation) def _expire(self): - for k,v in self._data.items(): + for k, v in self._data.items(): if v.expired: del self._data[k] @@ -90,19 +94,22 @@ def __init__(self, cache, callback, func=None, inst=None): self.__doc__ = func.__doc__ def __call__(self, *args, **kwargs): - if self.func is None: # decorator is waiting to be given a function + if self.func is None: + # decorator is waiting to be given a function if len(kwargs) or (len(args) != 1): - raise TMDBCacheError('Cache.Cached decorator must be called '+\ - 'a single callable argument before it '+\ - 'be used.') + raise TMDBCacheError( + 'Cache.Cached decorator must be called a single ' + + 'callable argument before it be used.') elif args[0] is None: - raise TMDBCacheError('Cache.Cached decorator called before '+\ - 'being given a function to wrap.') + raise TMDBCacheError( + 'Cache.Cached decorator called before being given ' + + 'a function to wrap.') elif not callable(args[0]): - raise TMDBCacheError('Cache.Cached must be provided a '+\ - 'callable object.') + raise TMDBCacheError( + 'Cache.Cached must be provided a callable object.') return self.__class__(self.cache, self.callback, args[0]) elif self.inst.lifetime == 0: + # lifetime of zero means never cache return self.func(*args, **kwargs) else: key = self.callback() @@ -121,4 +128,3 @@ def __get__(self, inst, owner): func = self.func.__get__(inst, owner) callback = self.callback.__get__(inst, owner) return self.__class__(self.cache, callback, func, inst) - diff --git a/tmdb3/cache_engine.py b/tmdb3/cache_engine.py index 99ad4cd..1101955 100644 --- a/tmdb3/cache_engine.py +++ b/tmdb3/cache_engine.py @@ -10,35 +10,46 @@ import time from weakref import ref -class Engines( object ): + +class Engines(object): + """ + Static collector for engines to register against. + """ def __init__(self): self._engines = {} + def register(self, engine): self._engines[engine.__name__] = engine self._engines[engine.name] = engine + def __getitem__(self, key): return self._engines[key] + def __contains__(self, key): return self._engines.__contains__(key) + Engines = Engines() -class CacheEngineType( type ): + +class CacheEngineType(type): """ Cache Engine Metaclass that registers new engines against the cache for named selection and use. """ - def __init__(mcs, name, bases, attrs): - super(CacheEngineType, mcs).__init__(name, bases, attrs) + def __init__(cls, name, bases, attrs): + super(CacheEngineType, cls).__init__(name, bases, attrs) if name != 'CacheEngine': # skip base class - Engines.register(mcs) + Engines.register(cls) -class CacheEngine( object ): - __metaclass__ = CacheEngineType +class CacheEngine(object): + __metaclass__ = CacheEngineType name = 'unspecified' + def __init__(self, parent): self.parent = ref(parent) + def configure(self): raise RuntimeError def get(self, date): @@ -48,7 +59,8 @@ def put(self, key, value, lifetime): def expire(self, key): raise RuntimeError -class CacheObject( object ): + +class CacheObject(object): """ Cache object class, containing one stored record. """ @@ -64,7 +76,7 @@ def __len__(self): @property def expired(self): - return (self.remaining == 0) + return self.remaining == 0 @property def remaining(self): diff --git a/tmdb3/cache_file.py b/tmdb3/cache_file.py index 54a9ca4..ad1b4b1 100644 --- a/tmdb3/cache_file.py +++ b/tmdb3/cache_file.py @@ -55,11 +55,11 @@ def _donothing(*args, **kwargs): try: import fcntl - class Flock( object ): + class Flock(object): """ - Context manager to flock file for the duration the object exists. - Referenced file will be automatically unflocked as the interpreter - exits the context. + Context manager to flock file for the duration the object + exists. Referenced file will be automatically unflocked as the + interpreter exits the context. Supports an optional callback to process the error and optionally suppress it. """ @@ -70,8 +70,10 @@ def __init__(self, fileobj, operation, callback=None): self.fileobj = fileobj self.operation = operation self.callback = callback + def __enter__(self): fcntl.flock(self.fileobj, self.operation) + def __exit__(self, exc_type, exc_value, exc_tb): suppress = False if callable(self.callback): @@ -102,9 +104,11 @@ def __init__(self, fileobj, operation, callback=None): self.fileobj = fileobj self.operation = operation self.callback = callback + def __enter__(self): self.size = os.path.getsize(self.fileobj.name) msvcrt.locking(self.fileobj.fileno(), self.operation, self.size) + def __exit__(self, exc_type, exc_value, exc_tb): suppress = False if callable(self.callback): @@ -119,7 +123,7 @@ def parse_filename(filename): if filename.startswith('~'): # check for home directory return os.path.expanduser(filename) - elif (ord(filename[0]) in (range(65,91)+range(99,123))) \ + elif (ord(filename[0]) in (range(65, 91) + range(99, 123))) \ and (filename[1:3] == ':\\'): # check for absolute drive path (e.g. C:\...) return filename @@ -127,12 +131,12 @@ def parse_filename(filename): # check for absolute UNC path (e.g. \\server\...) return filename # return path with temp directory prepended - return os.path.expandvars(os.path.join('%TEMP%',filename)) + return os.path.expandvars(os.path.join('%TEMP%', filename)) -class FileCacheObject( CacheObject ): - _struct = struct.Struct('dII') # double and two ints - # timestamp, lifetime, position +class FileCacheObject(CacheObject): + _struct = struct.Struct('dII') # double and two ints + # timestamp, lifetime, position @classmethod def fromFile(cls, fd): @@ -151,7 +155,7 @@ def __init__(self, *args, **kwargs): @property def size(self): if self._size is None: - self._buff.seek(0,2) + self._buff.seek(0, 2) size = self._buff.tell() if size == 0: if (self._key is None) or (self._data is None): @@ -160,8 +164,10 @@ def size(self): self._size = self._buff.tell() self._size = size return self._size + @size.setter - def size(self, value): self._size = value + def size(self, value): + self._size = value @property def key(self): @@ -171,16 +177,20 @@ def key(self): except: pass return self._key + @key.setter - def key(self, value): self._key = value + def key(self, value): + self._key = value @property def data(self): if self._data is None: self._key, self._data = json.loads(self._buff.getvalue()) return self._data + @data.setter - def data(self, value): self._data = value + def data(self, value): + self._data = value def load(self, fd): fd.seek(self.position) @@ -200,7 +210,7 @@ def dumpdata(self, fd): class FileEngine( CacheEngine ): """Simple file-backed engine.""" name = 'file' - _struct = struct.Struct('HH') # two shorts for version and count + _struct = struct.Struct('HH') # two shorts for version and count _version = 2 def __init__(self, parent): @@ -220,7 +230,6 @@ def _init_cache(self): if self.cachefile is None: raise TMDBCacheError("No cache filename given.") - self.cachefile = parse_filename(self.cachefile) try: @@ -258,7 +267,7 @@ def get(self, date): self._init_cache() self._open('r+b') - with Flock(self.cachefd, Flock.LOCK_SH): # lock for shared access + with Flock(self.cachefd, Flock.LOCK_SH): # return any new objects in the cache return self._read(date) @@ -266,7 +275,7 @@ def put(self, key, value, lifetime): self._init_cache() self._open('r+b') - with Flock(self.cachefd, Flock.LOCK_EX): # lock for exclusive access + with Flock(self.cachefd, Flock.LOCK_EX): newobjs = self._read(self.age) newobjs.append(FileCacheObject(key, value, lifetime)) @@ -284,7 +293,8 @@ def _open(self, mode='r+b'): # already opened in requested mode, nothing to do self.cachefd.seek(0) return - except: pass # catch issue of no cachefile yet opened + except: + pass # catch issue of no cachefile yet opened self.cachefd = io.open(self.cachefile, mode) def _read(self, date): @@ -311,7 +321,7 @@ def _read(self, date): return [] # get end of file - self.cachefd.seek(0,2) + self.cachefd.seek(0, 2) position = self.cachefd.tell() newobjs = [] emptycount = 0 @@ -349,7 +359,7 @@ def _write(self, data): data = data[-1] # determine write position of data in cache - self.cachefd.seek(0,2) + self.cachefd.seek(0, 2) end = self.cachefd.tell() data.position = end @@ -388,5 +398,3 @@ def _write(self, data): def expire(self, key): pass - - diff --git a/tmdb3/cache_null.py b/tmdb3/cache_null.py index a59741c..8c360da 100644 --- a/tmdb3/cache_null.py +++ b/tmdb3/cache_null.py @@ -9,11 +9,19 @@ from cache_engine import CacheEngine -class NullEngine( CacheEngine ): + +class NullEngine(CacheEngine): """Non-caching engine for debugging.""" name = 'null' - def configure(self): pass - def get(self, date): return [] - def put(self, key, value, lifetime): return [] - def expire(self, key): pass + def configure(self): + pass + + def get(self, date): + return [] + + def put(self, key, value, lifetime): + return [] + + def expire(self, key): + pass diff --git a/tmdb3/locales.py b/tmdb3/locales.py index 97efec7..0ef0310 100644 --- a/tmdb3/locales.py +++ b/tmdb3/locales.py @@ -11,7 +11,8 @@ syslocale = None -class LocaleBase( object ): + +class LocaleBase(object): __slots__ = ['__immutable'] _stored = {} fallthrough = False @@ -24,19 +25,21 @@ def __init__(self, *keys): def __setattr__(self, key, value): if getattr(self, '__immutable', False): raise NotImplementedError(self.__class__.__name__ + - ' does not support modification.') + ' does not support modification.') super(LocaleBase, self).__setattr__(key, value) def __delattr__(self, key): if getattr(self, '__immutable', False): raise NotImplementedError(self.__class__.__name__ + - ' does not support modification.') + ' does not support modification.') super(LocaleBase, self).__delattr__(key) def __lt__(self, other): return (id(self) != id(other)) and (str(self) > str(other)) + def __gt__(self, other): return (id(self) != id(other)) and (str(self) < str(other)) + def __eq__(self, other): return (id(self) == id(other)) or (str(self) == str(other)) @@ -48,9 +51,10 @@ def getstored(cls, key): return cls._stored[key.lower()] except: raise TMDBLocaleError("'{0}' is not a known valid {1} code."\ - .format(key, cls.__name__)) + .format(key, cls.__name__)) + -class Language( LocaleBase ): +class Language(LocaleBase): __slots__ = ['ISO639_1', 'ISO639_2', 'ISO639_2B', 'englishname', 'nativename'] _stored = {} @@ -69,12 +73,13 @@ def __str__(self): def __repr__(self): return u"".format(self) -class Country( LocaleBase ): + +class Country(LocaleBase): __slots__ = ['alpha2', 'name'] _stored = {} def __init__(self, alpha2, name): - self.alpha2 = alpha2 + self.alpha2 = alpha2 self.name = name super(Country, self).__init__(alpha2) @@ -84,7 +89,8 @@ def __str__(self): def __repr__(self): return u"".format(self) -class Locale( LocaleBase ): + +class Locale(LocaleBase): __slots__ = ['language', 'country', 'encoding'] def __init__(self, language, country, encoding): @@ -120,6 +126,7 @@ def decode(self, dat): # just return unmodified and hope for the best return dat + def set_locale(language=None, country=None, fallthrough=False): global syslocale LocaleBase.fallthrough = fallthrough @@ -142,6 +149,7 @@ def set_locale(language=None, country=None, fallthrough=False): syslocale = Locale(language, country, sysenc) + def get_locale(language=-1, country=-1): """Output locale using provided attributes, or return system locale.""" global syslocale diff --git a/tmdb3/pager.py b/tmdb3/pager.py index 24f1366..ebcb9d2 100644 --- a/tmdb3/pager.py +++ b/tmdb3/pager.py @@ -8,7 +8,8 @@ from collections import Sequence, Iterator -class PagedIterator( Iterator ): + +class PagedIterator(Iterator): def __init__(self, parent): self._parent = parent self._index = -1 @@ -23,7 +24,8 @@ def next(self): raise StopIteration return self._parent[self._index] -class UnpagedData( object ): + +class UnpagedData(object): def copy(self): return self.__class__() @@ -33,10 +35,11 @@ def __mul__(self, other): def __rmul__(self, other): return (self.copy() for a in range(other)) -class PagedList( Sequence ): + +class PagedList(Sequence): """ - List-like object, with support for automatically grabbing additional - pages from a data source. + List-like object, with support for automatically grabbing + additional pages from a data source. """ _iter_class = None @@ -87,17 +90,19 @@ def _populatepage(self, page): pagestart += 1 def _getpage(self, page): - raise NotImplementedError("PagedList._getpage() must be provided "+\ + raise NotImplementedError("PagedList._getpage() must be provided " + "by subclass") -class PagedRequest( PagedList ): + +class PagedRequest(PagedList): """ - Derived PageList that provides a list-like object with automatic paging - intended for use with search requests. + Derived PageList that provides a list-like object with automatic + paging intended for use with search requests. """ def __init__(self, request, handler=None): self._request = request - if handler: self._handler = handler + if handler: + self._handler = handler super(PagedRequest, self).__init__(self._getpage(1), 20) def _getpage(self, page): @@ -109,4 +114,3 @@ def _getpage(self, page): yield None else: yield self._handler(item) - diff --git a/tmdb3/request.py b/tmdb3/request.py index d521959..2d51dcd 100644 --- a/tmdb3/request.py +++ b/tmdb3/request.py @@ -23,10 +23,11 @@ #DEBUG = True #cache = Cache(engine='null') + def set_key(key): """ - Specify the API key to use retrieving data from themoviedb.org. This - key must be set before any calls will function. + Specify the API key to use retrieving data from themoviedb.org. + This key must be set before any calls will function. """ if len(key) != 32: raise TMDBKeyInvalid("Specified API key must be 128-bit hex") @@ -36,42 +37,50 @@ def set_key(key): raise TMDBKeyInvalid("Specified API key must be 128-bit hex") Request._api_key = key + def set_cache(engine=None, *args, **kwargs): """Specify caching engine and properties.""" cache.configure(engine, *args, **kwargs) -class Request( urllib2.Request ): + +class Request(urllib2.Request): _api_key = None _base_url = "http://api.themoviedb.org/3/" @property def api_key(self): if self._api_key is None: - raise TMDBKeyMissing("API key must be specified before "+\ + raise TMDBKeyMissing("API key must be specified before " + "requests can be made") return self._api_key def __init__(self, url, **kwargs): - """Return a request object, using specified API path and arguments.""" + """ + Return a request object, using specified API path and + arguments. + """ kwargs['api_key'] = self.api_key self._url = url.lstrip('/') - self._kwargs = dict([(kwa,kwv) for kwa,kwv in kwargs.items() + self._kwargs = dict([(kwa, kwv) for kwa, kwv in kwargs.items() if kwv is not None]) locale = get_locale() kwargs = {} - for k,v in self._kwargs.items(): + for k, v in self._kwargs.items(): kwargs[k] = locale.encode(v) - url = '{0}{1}?{2}'.format(self._base_url, self._url, urlencode(kwargs)) + url = '{0}{1}?{2}'\ + .format(self._base_url, self._url, urlencode(kwargs)) urllib2.Request.__init__(self, url) self.add_header('Accept', 'application/json') - self.lifetime = 3600 # 1hr + self.lifetime = 3600 # 1hr def new(self, **kwargs): - """Create a new instance of the request, with tweaked arguments.""" + """ + Create a new instance of the request, with tweaked arguments. + """ args = dict(self._kwargs) - for k,v in kwargs.items(): + for k, v in kwargs.items(): if v is None: if k in args: del args[k] @@ -128,27 +137,27 @@ def readJSON(self): status_handlers = { 1: None, 2: TMDBRequestInvalid('Invalid service - This service does not exist.'), - 3: TMDBRequestError('Authentication Failed - You do not have '+\ + 3: TMDBRequestError('Authentication Failed - You do not have ' + 'permissions to access this service.'), - 4: TMDBRequestInvalid("Invalid format - This service doesn't exist "+\ + 4: TMDBRequestInvalid("Invalid format - This service doesn't exist " + 'in that format.'), - 5: TMDBRequestInvalid('Invalid parameters - Your request parameters '+\ + 5: TMDBRequestInvalid('Invalid parameters - Your request parameters ' + 'are incorrect.'), - 6: TMDBRequestInvalid('Invalid id - The pre-requisite id is invalid '+\ + 6: TMDBRequestInvalid('Invalid id - The pre-requisite id is invalid ' + 'or not found.'), 7: TMDBKeyInvalid('Invalid API key - You must be granted a valid key.'), - 8: TMDBRequestError('Duplicate entry - The data you tried to submit '+\ + 8: TMDBRequestError('Duplicate entry - The data you tried to submit ' + 'already exists.'), 9: TMDBOffline('This service is tempirarily offline. Try again later.'), - 10: TMDBKeyRevoked('Suspended API key - Access to your account has been '+\ - 'suspended, contact TMDB.'), - 11: TMDBError('Internal error - Something went wrong. Contact TMDb.'), - 12: None, - 13: None, - 14: TMDBRequestError('Authentication Failed.'), - 15: TMDBError('Failed'), - 16: TMDBError('Device Denied'), - 17: TMDBError('Session Denied')} + 10: TMDBKeyRevoked('Suspended API key - Access to your account has been ' + + 'suspended, contact TMDB.'), + 11: TMDBError('Internal error - Something went wrong. Contact TMDb.'), + 12: None, + 13: None, + 14: TMDBRequestError('Authentication Failed.'), + 15: TMDBError('Failed'), + 16: TMDBError('Device Denied'), + 17: TMDBError('Session Denied')} def handle_status(data, query): status = status_handlers[data.get('status_code', 1)] diff --git a/tmdb3/tmdb_api.py b/tmdb3/tmdb_api.py index 72141aa..ff0bdb6 100644 --- a/tmdb3/tmdb_api.py +++ b/tmdb3/tmdb_api.py @@ -13,8 +13,8 @@ # (http://creativecommons.org/licenses/GPL/2.0/) #----------------------- -__title__ = "tmdb_api - Simple-to-use Python interface to TMDB's API v3 "+\ - "(www.themoviedb.org)" +__title__ = ("tmdb_api - Simple-to-use Python interface to TMDB's API v3 " + + "(www.themoviedb.org)") __author__ = "Raymond Wagner" __purpose__ = """ This Python library is intended to provide a series of classes and methods @@ -22,7 +22,7 @@ Preliminary API specifications can be found at http://help.themoviedb.org/kb/api/about-3""" -__version__="v0.6.17" +__version__ = "v0.6.17" # 0.1.0 Initial development # 0.2.0 Add caching mechanism for API queries # 0.2.1 Temporary work around for broken search paging @@ -76,6 +76,7 @@ DEBUG = False + def process_date(datestr): try: return datetime.date(*[int(x) for x in datestr.split('-')]) @@ -85,34 +86,40 @@ def process_date(datestr): import traceback _,_,tb = sys.exc_info() f,l,_,_ = traceback.extract_tb(tb)[-1] - warnings.warn_explicit(('"{0}" is not a supported date format. ' - 'Please fix upstream data at http://www.themoviedb.org.')\ - .format(datestr), Warning, f, l) + warnings.warn_explicit(('"{0}" is not a supported date format. ' + + 'Please fix upstream data at ' + + 'http://www.themoviedb.org.' + ).format(datestr), Warning, f, l) return None -class Configuration( Element ): + +class Configuration(Element): images = Datapoint('images') + def _populate(self): return Request('configuration') + Configuration = Configuration() -class Account( NameRepr, Element ): + +class Account(NameRepr, Element): def _populate(self): return Request('account', session_id=self._session.sessionid) - id = Datapoint('id') - adult = Datapoint('include_adult') - country = Datapoint('iso_3166_1') - language = Datapoint('iso_639_1') - name = Datapoint('name') - username = Datapoint('username') + id = Datapoint('id') + adult = Datapoint('include_adult') + country = Datapoint('iso_3166_1') + language = Datapoint('iso_639_1') + name = Datapoint('name') + username = Datapoint('username') @property def locale(self): return get_locale(self.language, self.country) + def searchMovie(query, locale=None, adult=False, year=None): - kwargs = {'query':query, 'include_adult':adult} + kwargs = {'query': query, 'include_adult': adult} if year is not None: try: kwargs['year'] = year.year @@ -120,6 +127,7 @@ def searchMovie(query, locale=None, adult=False, year=None): kwargs['year'] = year return MovieSearchResult(Request('search/movie', **kwargs), locale=locale) + def searchMovieWithYear(query, locale=None, adult=False): year = None if (len(query) > 6) and (query[-1] == ')') and (query[-6] == '('): @@ -137,70 +145,80 @@ def searchMovieWithYear(query, locale=None, adult=False): year = None return searchMovie(query, locale, adult, year) -class MovieSearchResult( SearchRepr, PagedRequest ): + +class MovieSearchResult(SearchRepr, PagedRequest): """Stores a list of search matches.""" _name = None def __init__(self, request, locale=None): if locale is None: locale = get_locale() super(MovieSearchResult, self).__init__( - request.new(language=locale.language), - lambda x: Movie(raw=x, locale=locale)) + request.new(language=locale.language), + lambda x: Movie(raw=x, locale=locale)) + def searchPerson(query, adult=False): return PeopleSearchResult(Request('search/person', query=query, include_adult=adult)) -class PeopleSearchResult( SearchRepr, PagedRequest ): + +class PeopleSearchResult(SearchRepr, PagedRequest): """Stores a list of search matches.""" _name = None def __init__(self, request): - super(PeopleSearchResult, self).__init__(request, - lambda x: Person(raw=x)) + super(PeopleSearchResult, self).__init__( + request, lambda x: Person(raw=x)) + def searchStudio(query): return StudioSearchResult(Request('search/company', query=query)) -class StudioSearchResult( SearchRepr, PagedRequest ): + +class StudioSearchResult(SearchRepr, PagedRequest): """Stores a list of search matches.""" _name = None def __init__(self, request): - super(StudioSearchResult, self).__init__(request, - lambda x: Studio(raw=x)) + super(StudioSearchResult, self).__init__( + request, lambda x: Studio(raw=x)) + def searchList(query, adult=False): ListSearchResult(Request('search/list', query=query, include_adult=adult)) -class ListSearchResult( SearchRepr, PagedRequest ): + +class ListSearchResult(SearchRepr, PagedRequest): """Stores a list of search matches.""" _name = None def __init__(self, request): - super(ListSearchResult, self).__init__(request, - lambda x: List(raw=x)) + super(ListSearchResult, self).__init__( + request, lambda x: List(raw=x)) + def searchCollection(query, locale=None): return CollectionSearchResult(Request('search/collection', query=query), locale=locale) -class CollectionSearchResult( SearchRepr, PagedRequest ): + +class CollectionSearchResult(SearchRepr, PagedRequest): """Stores a list of search matches.""" _name=None def __init__(self, request, locale=None): if locale is None: locale = get_locale() super(CollectionSearchResult, self).__init__( - request.new(language=locale.language), - lambda x: Collection(raw=x, locale=locale)) - -class Image( Element ): - filename = Datapoint('file_path', initarg=1, - handler=lambda x: x.lstrip('/')) - aspectratio = Datapoint('aspect_ratio') - height = Datapoint('height') - width = Datapoint('width') - language = Datapoint('iso_639_1') - userrating = Datapoint('vote_average') - votes = Datapoint('vote_count') + request.new(language=locale.language), + lambda x: Collection(raw=x, locale=locale)) + + +class Image(Element): + filename = Datapoint('file_path', initarg=1, + handler=lambda x: x.lstrip('/')) + aspectratio = Datapoint('aspect_ratio') + height = Datapoint('height') + width = Datapoint('width') + language = Datapoint('iso_639_1') + userrating = Datapoint('vote_average') + votes = Datapoint('vote_count') def sizes(self): return ['original'] @@ -215,12 +233,15 @@ def geturl(self, size='original'): def __lt__(self, other): return (self.language == self._locale.language) \ and (self.language != other.language) + def __gt__(self, other): return (self.language != other.language) \ and (other.language == self._locale.language) + # direct match for comparison def __eq__(self, other): return self.filename == other.filename + # special handling for boolean to see if exists def __nonzero__(self): if len(self.filename) == 0: @@ -231,20 +252,28 @@ def __repr__(self): # BASE62 encoded filename, no need to worry about unicode return u"<{0.__class__.__name__} '{0.filename}'>".format(self) -class Backdrop( Image ): + +class Backdrop(Image): def sizes(self): return Configuration.images['backdrop_sizes'] -class Poster( Image ): + + +class Poster(Image): def sizes(self): return Configuration.images['poster_sizes'] -class Profile( Image ): + + +class Profile(Image): def sizes(self): return Configuration.images['profile_sizes'] -class Logo( Image ): + + +class Logo(Image): def sizes(self): return Configuration.images['logo_sizes'] -class AlternateTitle( Element ): + +class AlternateTitle(Element): country = Datapoint('iso_3166_1') title = Datapoint('title') @@ -252,28 +281,31 @@ class AlternateTitle( Element ): def __lt__(self, other): return (self.country == self._locale.country) \ and (self.country != other.country) + def __gt__(self, other): return (self.country != other.country) \ and (other.country == self._locale.country) + def __eq__(self, other): return self.country == other.country def __repr__(self): return u"<{0.__class__.__name__} '{0.title}' ({0.country})>"\ - .format(self).encode('utf-8') - -class Person( Element ): - id = Datapoint('id', initarg=1) - name = Datapoint('name') - biography = Datapoint('biography') - dayofbirth = Datapoint('birthday', default=None, handler=process_date) - dayofdeath = Datapoint('deathday', default=None, handler=process_date) - homepage = Datapoint('homepage') - birthplace = Datapoint('place_of_birth') - profile = Datapoint('profile_path', handler=Profile, \ - raw=False, default=None) - adult = Datapoint('adult') - aliases = Datalist('also_known_as') + .format(self).encode('utf-8') + + +class Person(Element): + id = Datapoint('id', initarg=1) + name = Datapoint('name') + biography = Datapoint('biography') + dayofbirth = Datapoint('birthday', default=None, handler=process_date) + dayofdeath = Datapoint('deathday', default=None, handler=process_date) + homepage = Datapoint('homepage') + birthplace = Datapoint('place_of_birth') + profile = Datapoint('profile_path', handler=Profile, + raw=False, default=None) + adult = Datapoint('adult') + aliases = Datalist('also_known_as') def __repr__(self): return u"<{0.__class__.__name__} '{0.name}'>"\ @@ -281,55 +313,63 @@ def __repr__(self): def _populate(self): return Request('person/{0}'.format(self.id)) + def _populate_credits(self): - return Request('person/{0}/credits'.format(self.id), \ - language=self._locale.language) + return Request('person/{0}/credits'.format(self.id), + language=self._locale.language) def _populate_images(self): return Request('person/{0}/images'.format(self.id)) - roles = Datalist('cast', handler=lambda x: ReverseCast(raw=x), \ - poller=_populate_credits) - crew = Datalist('crew', handler=lambda x: ReverseCrew(raw=x), \ - poller=_populate_credits) - profiles = Datalist('profiles', handler=Profile, poller=_populate_images) + roles = Datalist('cast', handler=lambda x: ReverseCast(raw=x), + poller=_populate_credits) + crew = Datalist('crew', handler=lambda x: ReverseCrew(raw=x), + poller=_populate_credits) + profiles = Datalist('profiles', handler=Profile, poller=_populate_images) -class Cast( Person ): - character = Datapoint('character') - order = Datapoint('order') + +class Cast(Person): + character = Datapoint('character') + order = Datapoint('order') def __repr__(self): return u"<{0.__class__.__name__} '{0.name}' as '{0.character}'>"\ - .format(self).encode('utf-8') + .format(self).encode('utf-8') + -class Crew( Person ): - job = Datapoint('job') - department = Datapoint('department') +class Crew(Person): + job = Datapoint('job') + department = Datapoint('department') def __repr__(self): return u"<{0.__class__.__name__} '{0.name}','{0.job}'>"\ - .format(self).encode('utf-8') + .format(self).encode('utf-8') + -class Keyword( Element ): +class Keyword(Element): id = Datapoint('id') name = Datapoint('name') def __repr__(self): - return u"<{0.__class__.__name__} {0.name}>".format(self).encode('utf-8') + return u"<{0.__class__.__name__} {0.name}>"\ + .format(self).encode('utf-8') -class Release( Element ): - certification = Datapoint('certification') - country = Datapoint('iso_3166_1') - releasedate = Datapoint('release_date', handler=process_date) + +class Release(Element): + certification = Datapoint('certification') + country = Datapoint('iso_3166_1') + releasedate = Datapoint('release_date', handler=process_date) def __repr__(self): return u"<{0.__class__.__name__} {0.country}, {0.releasedate}>"\ - .format(self).encode('utf-8') + .format(self).encode('utf-8') + + +class Trailer(Element): + name = Datapoint('name') + size = Datapoint('size') + source = Datapoint('source') -class Trailer( Element ): - name = Datapoint('name') - size = Datapoint('size') - source = Datapoint('source') -class YoutubeTrailer( Trailer ): +class YoutubeTrailer(Trailer): def geturl(self): return "http://www.youtube.com/watch?v={0}".format(self.source) @@ -337,8 +377,9 @@ def __repr__(self): # modified BASE64 encoding, no need to worry about unicode return u"<{0.__class__.__name__} '{0.name}'>".format(self) -class AppleTrailer( Element ): - name = Datapoint('name') + +class AppleTrailer(Element): + name = Datapoint('name') sources = Datadict('sources', handler=Trailer, attr='size') def sizes(self): @@ -347,84 +388,91 @@ def sizes(self): def geturl(self, size=None): if size is None: # sort assuming ###p format for now, take largest resolution - size = str(sorted([int(size[:-1]) for size in self.sources])[-1])+'p' + size = str(sorted( + [int(size[:-1]) for size in self.sources] + )[-1]) + 'p' return self.sources[size].source def __repr__(self): return u"<{0.__class__.__name__} '{0.name}'>".format(self) -class Translation( Element ): - name = Datapoint('name') - language = Datapoint('iso_639_1') - englishname = Datapoint('english_name') + +class Translation(Element): + name = Datapoint('name') + language = Datapoint('iso_639_1') + englishname = Datapoint('english_name') def __repr__(self): return u"<{0.__class__.__name__} '{0.name}' ({0.language})>"\ - .format(self).encode('utf-8') + .format(self).encode('utf-8') -class Genre( NameRepr, Element ): - id = Datapoint('id') - name = Datapoint('name') + +class Genre(NameRepr, Element): + id = Datapoint('id') + name = Datapoint('name') def _populate_movies(self): return Request('genre/{0}/movies'.format(self.id), \ - language=self._locale.language) + language=self._locale.language) @property def movies(self): if 'movies' not in self._data: search = MovieSearchResult(self._populate_movies(), \ - locale=self._locale) + locale=self._locale) search._name = "{0.name} Movies".format(self) self._data['movies'] = search return self._data['movies'] @classmethod def getAll(cls, locale=None): - class GenreList( Element ): + class GenreList(Element): genres = Datalist('genres', handler=Genre) + def _populate(self): return Request('genre/list', language=self._locale.language) return GenreList(locale=locale).genres -class Studio( NameRepr, Element ): - id = Datapoint('id', initarg=1) - name = Datapoint('name') - description = Datapoint('description') - headquarters = Datapoint('headquarters') - logo = Datapoint('logo_path', handler=Logo, \ - raw=False, default=None) +class Studio(NameRepr, Element): + id = Datapoint('id', initarg=1) + name = Datapoint('name') + description = Datapoint('description') + headquarters = Datapoint('headquarters') + logo = Datapoint('logo_path', handler=Logo, raw=False, default=None) # FIXME: manage not-yet-defined handlers in a way that will propogate # locale information properly - parent = Datapoint('parent_company', \ - handler=lambda x: Studio(raw=x)) + parent = Datapoint('parent_company', handler=lambda x: Studio(raw=x)) def _populate(self): return Request('company/{0}'.format(self.id)) + def _populate_movies(self): - return Request('company/{0}/movies'.format(self.id), \ - language=self._locale.language) + return Request('company/{0}/movies'.format(self.id), + language=self._locale.language) # FIXME: add a cleaner way of adding types with no additional processing @property def movies(self): if 'movies' not in self._data: - search = MovieSearchResult(self._populate_movies(), \ - locale=self._locale) + search = MovieSearchResult(self._populate_movies(), + locale=self._locale) search._name = "{0.name} Movies".format(self) self._data['movies'] = search return self._data['movies'] -class Country( NameRepr, Element ): - code = Datapoint('iso_3166_1') - name = Datapoint('name') -class Language( NameRepr, Element ): - code = Datapoint('iso_639_1') - name = Datapoint('name') +class Country(NameRepr, Element): + code = Datapoint('iso_3166_1') + name = Datapoint('name') -class Movie( Element ): + +class Language(NameRepr, Element): + code = Datapoint('iso_639_1') + name = Datapoint('name') + + +class Movie(Element): @classmethod def latest(cls): req = Request('latest/movie') @@ -462,7 +510,7 @@ def favorites(cls, session=None): account = Account(session=session) res = MovieSearchResult( Request('account/{0}/favorite_movies'.format(account.id), - session_id=session.sessionid)) + session_id=session.sessionid)) res._name = "Favorites" return res @@ -473,7 +521,7 @@ def ratedmovies(cls, session=None): account = Account(session=session) res = MovieSearchResult( Request('account/{0}/rated_movies'.format(account.id), - session_id=session.sessionid)) + session_id=session.sessionid)) res._name = "Movies You Rated" return res @@ -484,7 +532,7 @@ def watchlist(cls, session=None): account = Account(session=session) res = MovieSearchResult( Request('account/{0}/movie_watchlist'.format(account.id), - session_id=session.sessionid)) + session_id=session.sessionid)) res._name = "Movies You're Watching" return res @@ -503,104 +551,113 @@ def fromIMDB(cls, imdbid, locale=None): movie._populate() return movie - id = Datapoint('id', initarg=1) - title = Datapoint('title') - originaltitle = Datapoint('original_title') - tagline = Datapoint('tagline') - overview = Datapoint('overview') - runtime = Datapoint('runtime') - budget = Datapoint('budget') - revenue = Datapoint('revenue') - releasedate = Datapoint('release_date', handler=process_date) - homepage = Datapoint('homepage') - imdb = Datapoint('imdb_id') - - backdrop = Datapoint('backdrop_path', handler=Backdrop, \ - raw=False, default=None) - poster = Datapoint('poster_path', handler=Poster, \ - raw=False, default=None) - - popularity = Datapoint('popularity') - userrating = Datapoint('vote_average') - votes = Datapoint('vote_count') - - adult = Datapoint('adult') - collection = Datapoint('belongs_to_collection', handler=lambda x: \ + id = Datapoint('id', initarg=1) + title = Datapoint('title') + originaltitle = Datapoint('original_title') + tagline = Datapoint('tagline') + overview = Datapoint('overview') + runtime = Datapoint('runtime') + budget = Datapoint('budget') + revenue = Datapoint('revenue') + releasedate = Datapoint('release_date', handler=process_date) + homepage = Datapoint('homepage') + imdb = Datapoint('imdb_id') + + backdrop = Datapoint('backdrop_path', handler=Backdrop, + raw=False, default=None) + poster = Datapoint('poster_path', handler=Poster, + raw=False, default=None) + + popularity = Datapoint('popularity') + userrating = Datapoint('vote_average') + votes = Datapoint('vote_count') + + adult = Datapoint('adult') + collection = Datapoint('belongs_to_collection', handler=lambda x: \ Collection(raw=x)) - genres = Datalist('genres', handler=Genre) - studios = Datalist('production_companies', handler=Studio) - countries = Datalist('production_countries', handler=Country) - languages = Datalist('spoken_languages', handler=Language) + genres = Datalist('genres', handler=Genre) + studios = Datalist('production_companies', handler=Studio) + countries = Datalist('production_countries', handler=Country) + languages = Datalist('spoken_languages', handler=Language) def _populate(self): return Request('movie/{0}'.format(self.id), \ - language=self._locale.language) + language=self._locale.language) + def _populate_titles(self): kwargs = {} if not self._locale.fallthrough: kwargs['country'] = self._locale.country - return Request('movie/{0}/alternative_titles'.format(self.id), **kwargs) + return Request('movie/{0}/alternative_titles'.format(self.id), + **kwargs) + def _populate_cast(self): return Request('movie/{0}/casts'.format(self.id)) + def _populate_images(self): kwargs = {} if not self._locale.fallthrough: kwargs['language'] = self._locale.language return Request('movie/{0}/images'.format(self.id), **kwargs) + def _populate_keywords(self): return Request('movie/{0}/keywords'.format(self.id)) + def _populate_releases(self): return Request('movie/{0}/releases'.format(self.id)) + def _populate_trailers(self): - return Request('movie/{0}/trailers'.format(self.id), \ + return Request('movie/{0}/trailers'.format(self.id), language=self._locale.language) + def _populate_translations(self): return Request('movie/{0}/translations'.format(self.id)) alternate_titles = Datalist('titles', handler=AlternateTitle, \ - poller=_populate_titles, sort=True) - cast = Datalist('cast', handler=Cast, \ - poller=_populate_cast, sort='order') - crew = Datalist('crew', handler=Crew, poller=_populate_cast) - backdrops = Datalist('backdrops', handler=Backdrop, \ - poller=_populate_images, sort=True) - posters = Datalist('posters', handler=Poster, \ - poller=_populate_images, sort=True) - keywords = Datalist('keywords', handler=Keyword, \ - poller=_populate_keywords) - releases = Datadict('countries', handler=Release, \ - poller=_populate_releases, attr='country') - youtube_trailers = Datalist('youtube', handler=YoutubeTrailer, \ - poller=_populate_trailers) - apple_trailers = Datalist('quicktime', handler=AppleTrailer, \ - poller=_populate_trailers) - translations = Datalist('translations', handler=Translation, \ - poller=_populate_translations) + poller=_populate_titles, sort=True) + cast = Datalist('cast', handler=Cast, + poller=_populate_cast, sort='order') + crew = Datalist('crew', handler=Crew, poller=_populate_cast) + backdrops = Datalist('backdrops', handler=Backdrop, + poller=_populate_images, sort=True) + posters = Datalist('posters', handler=Poster, + poller=_populate_images, sort=True) + keywords = Datalist('keywords', handler=Keyword, + poller=_populate_keywords) + releases = Datadict('countries', handler=Release, + poller=_populate_releases, attr='country') + youtube_trailers = Datalist('youtube', handler=YoutubeTrailer, + poller=_populate_trailers) + apple_trailers = Datalist('quicktime', handler=AppleTrailer, + poller=_populate_trailers) + translations = Datalist('translations', handler=Translation, + poller=_populate_translations) def setFavorite(self, value): - req = Request('account/{0}/favorite'.format(\ - Account(session=self._session).id), - session_id=self._session.sessionid) - req.add_data({'movie_id':self.id, 'favorite':str(bool(value)).lower()}) + req = Request('account/{0}/favorite'.format( + Account(session=self._session).id), + session_id=self._session.sessionid) + req.add_data({'movie_id': self.id, + 'favorite': str(bool(value)).lower()}) req.lifetime = 0 req.readJSON() def setRating(self, value): if not (0 <= value <= 10): raise TMDBError("Ratings must be between '0' and '10'.") - req = Request('movie/{0}/rating'.format(self.id), \ - session_id=self._session.sessionid) + req = Request('movie/{0}/rating'.format(self.id), + session_id=self._session.sessionid) req.lifetime = 0 req.add_data({'value':value}) req.readJSON() def setWatchlist(self, value): - req = Request('account/{0}/movie_watchlist'.format(\ - Account(session=self._session).id), - session_id=self._session.sessionid) + req = Request('account/{0}/movie_watchlist'.format( + Account(session=self._session).id), + session_id=self._session.sessionid) req.lifetime = 0 - req.add_data({'movie_id':self.id, - 'movie_watchlist':str(bool(value)).lower()}) + req.add_data({'movie_id': self.id, + 'movie_watchlist': str(bool(value)).lower()}) req.readJSON() def getSimilar(self): @@ -608,9 +665,9 @@ def getSimilar(self): @property def similar(self): - res = MovieSearchResult(Request('movie/{0}/similar_movies'\ - .format(self.id)), - locale=self._locale) + res = MovieSearchResult(Request( + 'movie/{0}/similar_movies'.format(self.id)), + locale=self._locale) res._name = 'Similar to {0}'.format(self._printable_name()) return res @@ -632,61 +689,61 @@ def _printable_name(self): return s def __repr__(self): - return u"<{0} {1}>".format(self.__class__.__name__,\ + return u"<{0} {1}>".format(self.__class__.__name__, self._printable_name()).encode('utf-8') + class ReverseCast( Movie ): character = Datapoint('character') def __repr__(self): - return u"<{0.__class__.__name__} '{0.character}' on {1}>"\ - .format(self, self._printable_name()).encode('utf-8') + return (u"<{0.__class__.__name__} '{0.character}' on {1}>" + .format(self, self._printable_name()).encode('utf-8')) + class ReverseCrew( Movie ): - department = Datapoint('department') - job = Datapoint('job') + department = Datapoint('department') + job = Datapoint('job') def __repr__(self): - return u"<{0.__class__.__name__} '{0.job}' for {1}>"\ - .format(self, self._printable_name()).encode('utf-8') + return (u"<{0.__class__.__name__} '{0.job}' for {1}>" + .format(self, self._printable_name()).encode('utf-8')) -class Collection( NameRepr, Element ): - id = Datapoint('id', initarg=1) - name = Datapoint('name') + +class Collection(NameRepr, Element): + id = Datapoint('id', initarg=1) + name = Datapoint('name') backdrop = Datapoint('backdrop_path', handler=Backdrop, \ - raw=False, default=None) - poster = Datapoint('poster_path', handler=Poster, \ - raw=False, default=None) - members = Datalist('parts', handler=Movie) + raw=False, default=None) + poster = Datapoint('poster_path', handler=Poster, raw=False, default=None) + members = Datalist('parts', handler=Movie) overview = Datapoint('overview') def _populate(self): - return Request('collection/{0}'.format(self.id), \ - language=self._locale.language) + return Request('collection/{0}'.format(self.id), + language=self._locale.language) + def _populate_images(self): kwargs = {} if not self._locale.fallthrough: kwargs['language'] = self._locale.language return Request('collection/{0}/images'.format(self.id), **kwargs) - backdrops = Datalist('backdrops', handler=Backdrop, \ - poller=_populate_images, sort=True) - posters = Datalist('posters', handler=Poster, \ - poller=_populate_images, sort=True) + backdrops = Datalist('backdrops', handler=Backdrop, + poller=_populate_images, sort=True) + posters = Datalist('posters', handler=Poster, + poller=_populate_images, sort=True) -class List( NameRepr, Element ): - id = Datapoint('id', initarg=1) - name = Datapoint('name') - author = Datapoint('created_by') +class List(NameRepr, Element): + id = Datapoint('id', initarg=1) + name = Datapoint('name') + author = Datapoint('created_by') description = Datapoint('description') - favorites = Datapoint('favorite_count') - language = Datapoint('iso_639_1') - count = Datapoint('item_count') - poster = Datapoint('poster_path', handler=Poster, \ - raw=False, default=None) - - members = Datalist('items', handler=Movie) + favorites = Datapoint('favorite_count') + language = Datapoint('iso_639_1') + count = Datapoint('item_count') + poster = Datapoint('poster_path', handler=Poster, raw=False, default=None) + members = Datalist('items', handler=Movie) def _populate(self): return Request('list/{0}'.format(self.id)) - diff --git a/tmdb3/tmdb_auth.py b/tmdb3/tmdb_auth.py index 8583b99..b447b5a 100644 --- a/tmdb3/tmdb_auth.py +++ b/tmdb3/tmdb_auth.py @@ -11,7 +11,7 @@ from datetime import datetime as _pydatetime, \ tzinfo as _pytzinfo import re -class datetime( _pydatetime ): +class datetime(_pydatetime): """Customized datetime class with ISO format parsing.""" _reiso = re.compile('(?P[0-9]{4})' '-(?P[0-9]{1,2})' @@ -27,21 +27,27 @@ class datetime( _pydatetime ): '(?P[0-9]{2})?' ')?') - class _tzinfo( _pytzinfo): + class _tzinfo(_pytzinfo): def __init__(self, direc='+', hr=0, min=0): if direc == '-': hr = -1*int(hr) self._offset = timedelta(hours=int(hr), minutes=int(min)) - def utcoffset(self, dt): return self._offset - def tzname(self, dt): return '' - def dst(self, dt): return timedelta(0) + + def utcoffset(self, dt): + return self._offset + + def tzname(self, dt): + return '' + + def dst(self, dt): + return timedelta(0) @classmethod def fromIso(cls, isotime, sep='T'): match = cls._reiso.match(isotime) if match is None: - raise TypeError("time data '%s' does not match ISO 8601 format" \ - % isotime) + raise TypeError("time data '%s' does not match ISO 8601 format" + % isotime) dt = [int(a) for a in match.groups()[:5]] if match.group('sec') is not None: @@ -52,9 +58,9 @@ def fromIso(cls, isotime, sep='T'): if match.group('tz') == 'Z': tz = cls._tzinfo() elif match.group('tzmin'): - tz = cls._tzinfo(*match.group('tzdirec','tzhour','tzmin')) + tz = cls._tzinfo(*match.group('tzdirec', 'tzhour', 'tzmin')) else: - tz = cls._tzinfo(*match.group('tzdirec','tzhour')) + tz = cls._tzinfo(*match.group('tzdirec', 'tzhour')) dt.append(0) dt.append(tz) return cls(*dt) @@ -64,10 +70,12 @@ def fromIso(cls, isotime, sep='T'): syssession = None + def set_session(sessionid): global syssession syssession = Session(sessionid) + def get_session(sessionid=None): global syssession if sessionid: @@ -77,8 +85,8 @@ def get_session(sessionid=None): else: return Session.new() -class Session( object ): +class Session(object): @classmethod def new(cls): return cls(None) @@ -91,9 +99,9 @@ def sessionid(self): if self._sessionid is None: if self._authtoken is None: raise TMDBError("No Auth Token to produce Session for") - # TODO: check authtokenexpiration against current time - req = Request('authentication/session/new', \ - request_token=self._authtoken) + # TODO: check authtoken expiration against current time + req = Request('authentication/session/new', + request_token=self._authtoken) req.lifetime = 0 dat = req.readJSON() if not dat['success']: @@ -128,4 +136,3 @@ def authtoken(self): @property def callbackurl(self): return "http://www.themoviedb.org/authenticate/"+self._authtoken - diff --git a/tmdb3/tmdb_exceptions.py b/tmdb3/tmdb_exceptions.py index 35e0364..f85fbcf 100644 --- a/tmdb3/tmdb_exceptions.py +++ b/tmdb3/tmdb_exceptions.py @@ -6,23 +6,24 @@ # Author: Raymond Wagner #----------------------- -class TMDBError( Exception ): - Error = 0 - KeyError = 10 - KeyMissing = 20 - KeyInvalid = 30 - KeyRevoked = 40 - RequestError = 50 - RequestInvalid = 51 - PagingIssue = 60 - CacheError = 70 - CacheReadError = 71 - CacheWriteError = 72 - CacheDirectoryError = 73 - ImageSizeError = 80 - HTTPError = 90 - Offline = 100 - LocaleError = 110 + +class TMDBError(Exception): + Error = 0 + KeyError = 10 + KeyMissing = 20 + KeyInvalid = 30 + KeyRevoked = 40 + RequestError = 50 + RequestInvalid = 51 + PagingIssue = 60 + CacheError = 70 + CacheReadError = 71 + CacheWriteError = 72 + CacheDirectoryError = 73 + ImageSizeError = 80 + HTTPError = 90 + Offline = 100 + LocaleError = 110 def __init__(self, msg=None, errno=0): self.errno = errno @@ -30,60 +31,77 @@ def __init__(self, msg=None, errno=0): self.errno = getattr(self, 'TMDB'+self.__class__.__name__, errno) self.args = (msg,) -class TMDBKeyError( TMDBError ): + +class TMDBKeyError(TMDBError): pass -class TMDBKeyMissing( TMDBKeyError ): + +class TMDBKeyMissing(TMDBKeyError): pass -class TMDBKeyInvalid( TMDBKeyError ): + +class TMDBKeyInvalid(TMDBKeyError): pass -class TMDBKeyRevoked( TMDBKeyInvalid ): + +class TMDBKeyRevoked(TMDBKeyInvalid): pass -class TMDBRequestError( TMDBError ): + +class TMDBRequestError(TMDBError): pass -class TMDBRequestInvalid( TMDBRequestError ): + +class TMDBRequestInvalid(TMDBRequestError): pass -class TMDBPagingIssue( TMDBRequestError ): + +class TMDBPagingIssue(TMDBRequestError): pass -class TMDBCacheError( TMDBRequestError ): + +class TMDBCacheError(TMDBRequestError): pass -class TMDBCacheReadError( TMDBCacheError ): + +class TMDBCacheReadError(TMDBCacheError): def __init__(self, filename): super(TMDBCacheReadError, self).__init__( - "User does not have permission to access cache file: {0}.".format(filename)) + "User does not have permission to access cache file: {0}."\ + .format(filename)) self.filename = filename -class TMDBCacheWriteError( TMDBCacheError ): + +class TMDBCacheWriteError(TMDBCacheError): def __init__(self, filename): super(TMDBCacheWriteError, self).__init__( - "User does not have permission to write cache file: {0}.".format(filename)) + "User does not have permission to write cache file: {0}."\ + .format(filename)) self.filename = filename -class TMDBCacheDirectoryError( TMDBCacheError ): + +class TMDBCacheDirectoryError(TMDBCacheError): def __init__(self, filename): super(TMDBCacheDirectoryError, self).__init__( - "Directory containing cache file does not exist: {0}.".format(filename)) + "Directory containing cache file does not exist: {0}."\ + .format(filename)) self.filename = filename -class TMDBImageSizeError( TMDBError ): + +class TMDBImageSizeError(TMDBError ): pass -class TMDBHTTPError( TMDBError ): + +class TMDBHTTPError(TMDBError): def __init__(self, err): self.httperrno = err.code self.response = err.fp.read() super(TMDBHTTPError, self).__init__(str(err)) -class TMDBOffline( TMDBError ): - pass -class TMDBLocaleError( TMDBError ): +class TMDBOffline(TMDBError): pass + +class TMDBLocaleError(TMDBError): + pass diff --git a/tmdb3/util.py b/tmdb3/util.py index bba9fcc..2ff73d1 100644 --- a/tmdb3/util.py +++ b/tmdb3/util.py @@ -10,13 +10,15 @@ from locales import get_locale from tmdb_auth import get_session -class NameRepr( object ): + +class NameRepr(object): """Mixin for __repr__ methods using 'name' attribute.""" def __repr__(self): return u"<{0.__class__.__name__} '{0.name}'>"\ - .format(self).encode('utf-8') + .format(self).encode('utf-8') + -class SearchRepr( object ): +class SearchRepr(object): """ Mixin for __repr__ methods for classes with '_name' and '_request' attributes. @@ -25,10 +27,11 @@ def __repr__(self): name = self._name if self._name else self._request._kwargs['query'] return u"".format(name).encode('utf-8') -class Poller( object ): + +class Poller(object): """ - Wrapper for an optional callable to populate an Element derived class - with raw data, or data from a Request. + Wrapper for an optional callable to populate an Element derived + class with raw data, or data from a Request. """ def __init__(self, func, lookup, inst=None): self.func = func @@ -60,7 +63,7 @@ def __call__(self): if not callable(self.func): raise RuntimeError('Poller object called without a source function') req = self.func() - if (('language' in req._kwargs) or ('country' in req._kwargs)) \ + if ('language' in req._kwargs) or ('country' in req._kwargs) \ and self.inst._locale.fallthrough: # request specifies a locale filter, and fallthrough is enabled # run a first pass with specified filter @@ -79,7 +82,7 @@ def __call__(self): def apply(self, data, set_nones=True): # apply data directly, bypassing callable function unfilled = False - for k,v in self.lookup.items(): + for k, v in self.lookup.items(): if (k in data) and \ ((data[k] is not None) if callable(self.func) else True): # argument received data, populate it @@ -100,7 +103,8 @@ def apply(self, data, set_nones=True): unfilled = True return unfilled -class Data( object ): + +class Data(object): """ Basic response definition class This maps to a single key in a JSON dictionary received from the API @@ -108,24 +112,29 @@ class Data( object ): def __init__(self, field, initarg=None, handler=None, poller=None, raw=True, default=u'', lang=False): """ - This defines how the dictionary value is to be processed by the poller - field -- defines the dictionary key that filters what data this uses - initarg -- (optional) specifies that this field must be supplied - when creating a new instance of the Element class this - definition is mapped to. Takes an integer for the order - it should be used in the input arguments - handler -- (optional) callable used to process the received value - before being stored in the Element object. - poller -- (optional) callable to be used if data is requested and - this value has not yet been defined. the callable should - return a dictionary of data from a JSON query. many - definitions may share a single poller, which will be - and the data used to populate all referenced definitions - based off their defined field - raw -- (optional) if the specified handler is an Element class, - the data will be passed into it using the 'raw' keyword - attribute. setting this to false will force the data to - instead be passed in as the first argument + This defines how the dictionary value is to be processed by the + poller + field -- defines the dictionary key that filters what data + this uses + initarg -- (optional) specifies that this field must be + supplied when creating a new instance of the Element + class this definition is mapped to. Takes an integer + for the order it should be used in the input + arguments + handler -- (optional) callable used to process the received + value before being stored in the Element object. + poller -- (optional) callable to be used if data is requested + and this value has not yet been defined. the + callable should return a dictionary of data from a + JSON query. many definitions may share a single + poller, which will be and the data used to populate + all referenced definitions based off their defined + field + raw -- (optional) if the specified handler is an Element + class, the data will be passed into it using the + 'raw' keyword attribute. setting this to false + will force the data to instead be passed in as the + first argument """ self.field = field self.initarg = initarg @@ -162,37 +171,44 @@ def sethandler(self, handler): else: self.handler = lambda x: handler(x) -class Datapoint( Data ): + +class Datapoint(Data): pass -class Datalist( Data ): + +class Datalist(Data): """ Response definition class for list data This maps to a key in a JSON dictionary storing a list of data """ def __init__(self, field, handler=None, poller=None, sort=None, raw=True): """ - This defines how the dictionary value is to be processed by the poller - field -- defines the dictionary key that filters what data this uses - handler -- (optional) callable used to process the received value - before being stored in the Element object. - poller -- (optional) callable to be used if data is requested and - this value has not yet been defined. the callable should - return a dictionary of data from a JSON query. many - definitions may share a single poller, which will be - and the data used to populate all referenced definitions - based off their defined field - sort -- (optional) name of attribute in resultant data to be used - to sort the list after processing. this effectively - a handler be defined to process the data into something - that has attributes - raw -- (optional) if the specified handler is an Element class, - the data will be passed into it using the 'raw' keyword - attribute. setting this to false will force the data to - instead be passed in as the first argument + This defines how the dictionary value is to be processed by the + poller + field -- defines the dictionary key that filters what data + this uses + handler -- (optional) callable used to process the received + value before being stored in the Element object. + poller -- (optional) callable to be used if data is requested + and this value has not yet been defined. the + callable should return a dictionary of data from a + JSON query. many definitions may share a single + poller, which will be and the data used to populate + all referenced definitions based off their defined + field + sort -- (optional) name of attribute in resultant data to be + used to sort the list after processing. this + effectively requires a handler be defined to process + the data into something that has attributes + raw -- (optional) if the specified handler is an Element + class, the data will be passed into it using the + 'raw' keyword attribute. setting this to false will + force the data to instead be passed in as the first + argument """ super(Datalist, self).__init__(field, None, handler, poller, raw) self.sort = sort + def __set__(self, inst, value): data = [] if value: @@ -209,7 +225,8 @@ def __set__(self, inst, value): data.sort(key=lambda x: getattr(x, self.sort)) inst._data[self.field] = data -class Datadict( Data ): + +class Datadict(Data): """ Response definition class for dictionary data This maps to a key in a JSON dictionary storing a dictionary of data @@ -217,26 +234,31 @@ class Datadict( Data ): def __init__(self, field, handler=None, poller=None, raw=True, key=None, attr=None): """ - This defines how the dictionary value is to be processed by the poller - field -- defines the dictionary key that filters what data this uses - handler -- (optional) callable used to process the received value - before being stored in the Element object. - poller -- (optional) callable to be used if data is requested and - this value has not yet been defined. the callable should - return a dictionary of data from a JSON query. many - definitions may share a single poller, which will be - and the data used to populate all referenced definitions - based off their defined field - key -- (optional) name of key in resultant data to be used as - the key in the stored dictionary. if this is not the - field name from the source data is used instead - attr -- (optional) name of attribute in resultant data to be used + This defines how the dictionary value is to be processed by the + poller + field -- defines the dictionary key that filters what data + this uses + handler -- (optional) callable used to process the received + value before being stored in the Element object. + poller -- (optional) callable to be used if data is requested + and this value has not yet been defined. the + callable should return a dictionary of data from a + JSON query. many definitions may share a single + poller, which will be and the data used to populate + all referenced definitions based off their defined + field + key -- (optional) name of key in resultant data to be used as the key in the stored dictionary. if this is not the field name from the source data is used instead - raw -- (optional) if the specified handler is an Element class, - the data will be passed into it using the 'raw' keyword - attribute. setting this to false will force the data to - instead be passed in as the first argument + attr -- (optional) name of attribute in resultant data to be + used as the key in the stored dictionary. if this is + not the field name from the source data is used + instead + raw -- (optional) if the specified handler is an Element + class, the data will be passed into it using the + 'raw' keyword attribute. setting this to false will + force the data to instead be passed in as the first + argument """ if key and attr: raise TypeError("`key` and `attr` cannot both be defined") @@ -246,8 +268,9 @@ def __init__(self, field, handler=None, poller=None, raw=True, elif attr: self.getkey = lambda x: getattr(x, attr) else: - raise TypeError("Datadict requires `key` or `attr` be defined "+\ + raise TypeError("Datadict requires `key` or `attr` be defined " + "for populating the dictionary") + def __set__(self, inst, value): data = {} if value: @@ -286,7 +309,7 @@ def __new__(mcs, name, bases, attrs): # extract copies of each defined Poller function # from parent classes pollers[k] = attr.func - for k,attr in attrs.items(): + for k, attr in attrs.items(): if isinstance(attr, Data): data[k] = attr if '_populate' in attrs: @@ -295,9 +318,9 @@ def __new__(mcs, name, bases, attrs): # process all defined Data attribues, testing for use as an initial # argument, and building a list of what Pollers are used to populate # which Data points - pollermap = dict([(k,[]) for k in pollers]) + pollermap = dict([(k, []) for k in pollers]) initargs = [] - for k,v in data.items(): + for k, v in data.items(): v.name = k if v.initarg: initargs.append(v) @@ -313,7 +336,7 @@ def __new__(mcs, name, bases, attrs): # wrap each used poller function with a Poller class, and push into # the new class attributes - for k,v in pollermap.items(): + for k, v in pollermap.items(): if len(v) == 0: continue lookup = dict([(attr.field, attr.name) for attr in v]) @@ -326,8 +349,8 @@ def __new__(mcs, name, bases, attrs): attrs[attr.name] = attr # build sorted list of arguments used for intialization - attrs['_InitArgs'] = tuple([a.name for a in \ - sorted(initargs, key=lambda x: x.initarg)]) + attrs['_InitArgs'] = tuple( + [a.name for a in sorted(initargs, key=lambda x: x.initarg)]) return type.__new__(mcs, name, bases, attrs) def __call__(cls, *args, **kwargs): @@ -346,21 +369,23 @@ def __call__(cls, *args, **kwargs): if 'raw' in kwargs: # if 'raw' keyword is supplied, create populate object manually if len(args) != 0: - raise TypeError('__init__() takes exactly 2 arguments (1 given)') + raise TypeError( + '__init__() takes exactly 2 arguments (1 given)') obj._populate.apply(kwargs['raw'], False) else: # if not, the number of input arguments must exactly match that # defined by the Data definitions if len(args) != len(cls._InitArgs): - raise TypeError('__init__() takes exactly {0} arguments ({1} given)'\ + raise TypeError( + '__init__() takes exactly {0} arguments ({1} given)'\ .format(len(cls._InitArgs)+1, len(args)+1)) - for a,v in zip(cls._InitArgs, args): + for a, v in zip(cls._InitArgs, args): setattr(obj, a, v) obj.__init__() return obj + class Element( object ): __metaclass__ = ElementType _lang = 'en' -