Update media accessors wrt repo split

All accessors now take a `root` arg, the root of the media tree.
Alternatively `root` can be a custom MediaFile subclass, which should allow
neat tricks like:
- Checking some kind of manifest to prevent stat() calls
- Custom properties of the file objects (e.g. for HTML <img> tags)
- Downloading the media on demand

Tests assume media is at pokedex/data/media, skip otherwise.
This commit is contained in:
Petr Viktorin 2011-04-19 14:44:47 +03:00
parent 134f5a00ff
commit ab2baaa759
2 changed files with 96 additions and 50 deletions

View file

@ -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

View file

@ -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 '<Pokedex file %s>' % 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):