diff --git a/pokedex/tests/test_media.py b/pokedex/tests/test_media.py index 81b5714..ddbc778 100644 --- a/pokedex/tests/test_media.py +++ b/pokedex/tests/test_media.py @@ -8,6 +8,7 @@ This, of course, takes a lot of time to run. import os import re +from functools import wraps from nose.tools import * from nose.plugins.skip import SkipTest @@ -22,47 +23,66 @@ basedir = pkg_resources.resource_filename('pokedex', 'data/media') path_re = re.compile('^[-a-z0-9./]*$') +root = pkg_resources.resource_filename('pokedex', 'data/media') + +media_available = media.BaseMedia(root).available + +def if_available(func): + @wraps(func) + def if_available_wrapper(*args, **kwargs): + if not media_available: + raise SkipTest('Media not available at %s' % root) + else: + func(*args, **kwargs) + return if_available_wrapper + +@if_available def test_totodile(): """Totodile's female sprite -- same as male""" totodile = session.query(tables.Pokemon).filter_by(identifier=u'totodile').one() - accessor = media.PokemonMedia(totodile) + accessor = media.PokemonMedia(root, totodile) assert accessor.sprite() == accessor.sprite(female=True) +@if_available def test_chimecho(): """Chimecho's Platinum female backsprite -- diffeent from male""" chimecho = session.query(tables.Pokemon).filter_by(identifier=u'chimecho').one() - accessor = media.PokemonMedia(chimecho) + accessor = media.PokemonMedia(root, chimecho) male = accessor.sprite('platinum', back=True, frame=2) female = accessor.sprite('platinum', back=True, female=True, frame=2) assert male != female +@if_available def test_venonat(): """Venonat's shiny Yellow sprite -- same as non-shiny""" venonat = session.query(tables.Pokemon).filter_by(identifier=u'venonat').one() - accessor = media.PokemonMedia(venonat) + accessor = media.PokemonMedia(root, venonat) assert accessor.sprite('yellow') == accessor.sprite('yellow', shiny=True) +@if_available def test_arceus_icon(): """Arceus fire-form icon -- same as base icon""" arceus = session.query(tables.Pokemon).filter_by(identifier=u'arceus').one() - accessor = media.PokemonMedia(arceus) + accessor = media.PokemonMedia(root, arceus) fire_arceus = [f for f in arceus.forms if f.identifier == 'fire'][0] - fire_accessor = media.PokemonFormMedia(fire_arceus) + fire_accessor = media.PokemonFormMedia(root, fire_arceus) assert accessor.icon() == fire_accessor.icon() +@if_available @raises(ValueError) def test_strict_castform(): """Castform rainy form overworld with strict -- unavailable""" castform = session.query(tables.Pokemon).filter_by(identifier=u'castform').first() rainy_castform = [f for f in castform.forms if f.identifier == 'rainy'][0] - rainy_castform = media.PokemonFormMedia(rainy_castform) + rainy_castform = media.PokemonFormMedia(root, rainy_castform) rainy_castform.overworld('up', strict=True) +@if_available @raises(ValueError) def test_strict_exeggcute(): """Exeggcutes's female backsprite, with strict -- unavailable""" exeggcute = session.query(tables.Pokemon).filter_by(identifier=u'exeggcute').one() - accessor = media.PokemonMedia(exeggcute) + accessor = media.PokemonMedia(root, exeggcute) accessor.sprite(female=True, strict=True) @@ -73,6 +93,7 @@ def get_all_filenames(): all_filenames = set() for dirpath, dirnames, filenames in os.walk(basedir): + dirnames[:] = [dirname for dirname in dirnames if dirname != '.git'] for filename in filenames: path = os.path.join(dirpath, filename) assert path_re.match(path), path @@ -101,6 +122,7 @@ def hit(filenames, method, *args, **kwargs): pass return True +@if_available def check_get_everything(): """ For every the accessor method, loop over the Cartesian products of all @@ -121,23 +143,23 @@ def check_get_everything(): # Some small stuff first for damage_class in session.query(tables.MoveDamageClass).all(): - assert hit(filenames, media.DamageClassMedia(damage_class).icon) + assert hit(filenames, media.DamageClassMedia(root, damage_class).icon) for habitat in session.query(tables.PokemonHabitat).all(): - assert hit(filenames, media.HabitatMedia(habitat).icon) + assert hit(filenames, media.HabitatMedia(root, habitat).icon) for shape in session.query(tables.PokemonShape).all(): - assert hit(filenames, media.ShapeMedia(shape).icon) + assert hit(filenames, media.ShapeMedia(root, shape).icon) for item_pocket in session.query(tables.ItemPocket).all(): - assert hit(filenames, media.ItemPocketMedia(item_pocket).icon) - assert hit(filenames, media.ItemPocketMedia(item_pocket).icon, selected=True) + assert hit(filenames, media.ItemPocketMedia(root, item_pocket).icon) + assert hit(filenames, media.ItemPocketMedia(root, item_pocket).icon, selected=True) for contest_type in session.query(tables.ContestType).all(): - assert hit(filenames, media.ContestTypeMedia(contest_type).icon) + assert hit(filenames, media.ContestTypeMedia(root, contest_type).icon) for elemental_type in session.query(tables.Type).all(): - assert hit(filenames, media.TypeMedia(elemental_type).icon) + assert hit(filenames, media.TypeMedia(root, elemental_type).icon) # Items versions_for_items = [ @@ -146,7 +168,7 @@ def check_get_everything(): ] for item in session.query(tables.Item).all(): - accessor = media.ItemMedia(item) + accessor = media.ItemMedia(root, item) assert hit(filenames, accessor.berry_image) or not item.berry for rotation in (0, 90, 180, 270): assert hit(filenames, accessor.underground, rotation=rotation) or ( @@ -158,11 +180,11 @@ def check_get_everything(): for color in 'red green blue pale prism'.split(): for big in (True, False): - accessor = media.UndergroundSphereMedia(color=color, big=big) + accessor = media.UndergroundSphereMedia(root, color=color, big=big) assert hit(filenames, accessor.underground) for rock_type in 'i ii o o-big s t z'.split(): - accessor = media.UndergroundRockMedia(rock_type) + accessor = media.UndergroundRockMedia(root, rock_type) for rotation in (0, 90, 180, 270): success = hit(filenames, accessor.underground, rotation=rotation) assert success or rotation @@ -170,19 +192,17 @@ def check_get_everything(): # Pokemon! accessors = [] - accessors.append(media.UnknownPokemonMedia()) - accessors.append(media.EggMedia()) + accessors.append(media.UnknownPokemonMedia(root)) + accessors.append(media.EggMedia(root)) manaphy = session.query(tables.Pokemon).filter_by(identifier=u'manaphy').one() - accessors.append(media.EggMedia(manaphy)) - accessors.append(media.SubstituteMedia()) - - print 'Loading pokemon' + accessors.append(media.EggMedia(root, manaphy)) + accessors.append(media.SubstituteMedia(root)) for form in session.query(tables.PokemonForm).filter(tables.PokemonForm.identifier != '').all(): - accessors.append(media.PokemonFormMedia(form)) + accessors.append(media.PokemonFormMedia(root, form)) for pokemon in session.query(tables.Pokemon).all(): - accessors.append(media.PokemonMedia(pokemon)) + accessors.append(media.PokemonMedia(root, pokemon)) for accessor in accessors: assert hit(filenames, accessor.footprint) or not accessor.form diff --git a/pokedex/util/media.py b/pokedex/util/media.py index 9faf19f..98964b3 100644 --- a/pokedex/util/media.py +++ b/pokedex/util/media.py @@ -1,7 +1,12 @@ """Media accessors -Most media accessor __init__s take an ORM object from the pokedex package. +All media accessor __init__s take a `root` argument, which should be a path +to the root of the media directory. +Alternatively, `root` can be a custom MediaFile subclass. + +Most __init__s take an ORM object as a second argument. + Their various methods take a number of arguments specifying exactly which file you want (such as the female sprite, backsprite, etc.). ValueError is raised when the specified file cannot be found. @@ -26,22 +31,25 @@ All images are in the PNG format, except animations (GIF). All sounds are OGGs. """ import os -import pkg_resources +from functools import partial class MediaFile(object): """Represents a file: picture, sound, etc. Attributes: - relative_path: Filesystem path relative to the media directory + path_elements: List of directory/file names that make up relative_path + relative_path: Filesystem path relative to the root path: Absolute path to the file exists: True if the file exists + media_available: false if no media is available at the given root. + open(): Open the file """ - def __init__(self, *path_elements): + def __init__(self, root, *path_elements): self.path_elements = path_elements - self._dexpath = '/'.join(('data', 'media') + path_elements) + self.root = root @property def relative_path(self): @@ -49,7 +57,7 @@ class MediaFile(object): @property def path(self): - return pkg_resources.resource_filename('pokedex', self._dexpath) + return os.path.join(self.root, *self.path_elements) def open(self): """Open this file for reading, in the appropriate mode (i.e. binary) @@ -58,7 +66,11 @@ class MediaFile(object): @property def exists(self): - return pkg_resources.resource_exists('pokedex', self._dexpath) + return os.path.exists(self.path) + + @property + def media_available(self): + return os.path.isdir(self.root) def __eq__(self, other): return self.path == other.path @@ -70,15 +82,25 @@ class MediaFile(object): return '' % self.relative_path class BaseMedia(object): + def __init__(self, root): + if isinstance(root, basestring): + self.file_class = partial(MediaFile, root) + else: + self.file_class = root + + @property + def available(self): + return self.file_class().media_available + def from_path_elements(self, path_elements, basename, extension, surely_exists=False): filename = basename + extension path_elements = [self.toplevel_dir] + path_elements + [filename] - mfile = MediaFile(*path_elements) + mfile = self.file_class(*path_elements) if surely_exists or mfile.exists: return mfile else: - raise ValueError('File %s not found' % mfile.relative_path) + raise ValueError('File %s not found' % mfile.path) class _BasePokemonMedia(BaseMedia): toplevel_dir = 'pokemon' @@ -104,8 +126,8 @@ class _BasePokemonMedia(BaseMedia): 'black-white': (5, set('back shiny female'.split())), } - def __init__(self, pokemon_id, form_postfix=None): - BaseMedia.__init__(self) + def __init__(self, root, pokemon_id, form_postfix=None): + BaseMedia.__init__(self, root) self.pokemon_id = str(pokemon_id) self.form_postfix = form_postfix @@ -316,13 +338,13 @@ class _BasePokemonMedia(BaseMedia): class PokemonFormMedia(_BasePokemonMedia): """Media related to a Pokemon form """ - def __init__(self, pokemon_form): + def __init__(self, root, pokemon_form): pokemon_id = pokemon_form.form_base_pokemon_id if pokemon_form.identifier: form_postfix = '-' + pokemon_form.identifier else: form_postfix = None - _BasePokemonMedia.__init__(self, pokemon_id, form_postfix) + _BasePokemonMedia.__init__(self, root, pokemon_id, form_postfix) self.form = pokemon_form pokemon = pokemon_form.form_base_pokemon self.has_gender_differences = pokemon.has_gender_differences @@ -331,8 +353,8 @@ class PokemonFormMedia(_BasePokemonMedia): class PokemonMedia(_BasePokemonMedia): """Media related to a Pokemon """ - def __init__(self, pokemon): - _BasePokemonMedia.__init__(self, pokemon.id) + def __init__(self, root, pokemon): + _BasePokemonMedia.__init__(self, root, pokemon.id) self.form = pokemon.default_form self.has_gender_differences = (pokemon.has_gender_differences) self.introduced_in = pokemon.generation_id @@ -342,8 +364,8 @@ class UnknownPokemonMedia(_BasePokemonMedia): Note that not a lot of files are available for it. """ - def __init__(self): - _BasePokemonMedia.__init__(self, '0') + def __init__(self, root): + _BasePokemonMedia.__init__(self, root, '0') class EggMedia(_BasePokemonMedia): """Media related to a pokemon egg @@ -352,20 +374,20 @@ class EggMedia(_BasePokemonMedia): Give a Manaphy as `pokemon` to get the Manaphy egg. """ - def __init__(self, pokemon=None): + def __init__(self, root, pokemon=None): if pokemon and pokemon.identifier == 'manaphy': postfix = '-manaphy' else: postfix = None - _BasePokemonMedia.__init__(self, 'egg', postfix) + _BasePokemonMedia.__init__(self, root, 'egg', postfix) class SubstituteMedia(_BasePokemonMedia): """Media related to the Substitute sprite Note that not a lot of files are available for Substitute. """ - def __init__(self): - _BasePokemonMedia.__init__(self, 'substitute') + def __init__(self, root): + _BasePokemonMedia.__init__(self, root, 'substitute') class _BaseItemMedia(BaseMedia): toplevel_dir = 'items' @@ -383,7 +405,8 @@ class _BaseItemMedia(BaseMedia): class ItemMedia(_BaseItemMedia): """Media related to an item """ - def __init__(self, item): + def __init__(self, root, item): + _BaseItemMedia.__init__(self, root) self.item = item self.identifier = item.identifier @@ -459,7 +482,8 @@ class UndergroundRockMedia(_BaseItemMedia): rock_type can be one of: i, ii, o, o-big, s, t, z """ - def __init__(self, rock_type): + def __init__(self, root, rock_type): + _BaseItemMedia.__init__(self, root) self.identifier = 'rock-%s' % rock_type class UndergroundSphereMedia(_BaseItemMedia): @@ -467,13 +491,15 @@ class UndergroundSphereMedia(_BaseItemMedia): color can be one of: red, blue, green, pale, prism """ - def __init__(self, color, big=False): + def __init__(self, root, color, big=False): + _BaseItemMedia.__init__(self, root) self.identifier = '%s-sphere' % color if big: self.identifier += '-big' class _SimpleIconMedia(BaseMedia): - def __init__(self, thing): + def __init__(self, root, thing): + BaseMedia.__init__(self, root) self.identifier = thing.identifier def icon(self):