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 os
import re import re
from functools import wraps
from nose.tools import * from nose.tools import *
from nose.plugins.skip import SkipTest 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./]*$') 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(): def test_totodile():
"""Totodile's female sprite -- same as male""" """Totodile's female sprite -- same as male"""
totodile = session.query(tables.Pokemon).filter_by(identifier=u'totodile').one() 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) assert accessor.sprite() == accessor.sprite(female=True)
@if_available
def test_chimecho(): def test_chimecho():
"""Chimecho's Platinum female backsprite -- diffeent from male""" """Chimecho's Platinum female backsprite -- diffeent from male"""
chimecho = session.query(tables.Pokemon).filter_by(identifier=u'chimecho').one() 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) male = accessor.sprite('platinum', back=True, frame=2)
female = accessor.sprite('platinum', back=True, female=True, frame=2) female = accessor.sprite('platinum', back=True, female=True, frame=2)
assert male != female assert male != female
@if_available
def test_venonat(): def test_venonat():
"""Venonat's shiny Yellow sprite -- same as non-shiny""" """Venonat's shiny Yellow sprite -- same as non-shiny"""
venonat = session.query(tables.Pokemon).filter_by(identifier=u'venonat').one() 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) assert accessor.sprite('yellow') == accessor.sprite('yellow', shiny=True)
@if_available
def test_arceus_icon(): def test_arceus_icon():
"""Arceus fire-form icon -- same as base icon""" """Arceus fire-form icon -- same as base icon"""
arceus = session.query(tables.Pokemon).filter_by(identifier=u'arceus').one() 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_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() assert accessor.icon() == fire_accessor.icon()
@if_available
@raises(ValueError) @raises(ValueError)
def test_strict_castform(): def test_strict_castform():
"""Castform rainy form overworld with strict -- unavailable""" """Castform rainy form overworld with strict -- unavailable"""
castform = session.query(tables.Pokemon).filter_by(identifier=u'castform').first() 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 = [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) rainy_castform.overworld('up', strict=True)
@if_available
@raises(ValueError) @raises(ValueError)
def test_strict_exeggcute(): def test_strict_exeggcute():
"""Exeggcutes's female backsprite, with strict -- unavailable""" """Exeggcutes's female backsprite, with strict -- unavailable"""
exeggcute = session.query(tables.Pokemon).filter_by(identifier=u'exeggcute').one() 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) accessor.sprite(female=True, strict=True)
@ -73,6 +93,7 @@ def get_all_filenames():
all_filenames = set() all_filenames = set()
for dirpath, dirnames, filenames in os.walk(basedir): for dirpath, dirnames, filenames in os.walk(basedir):
dirnames[:] = [dirname for dirname in dirnames if dirname != '.git']
for filename in filenames: for filename in filenames:
path = os.path.join(dirpath, filename) path = os.path.join(dirpath, filename)
assert path_re.match(path), path assert path_re.match(path), path
@ -101,6 +122,7 @@ def hit(filenames, method, *args, **kwargs):
pass pass
return True return True
@if_available
def check_get_everything(): def check_get_everything():
""" """
For every the accessor method, loop over the Cartesian products of all For every the accessor method, loop over the Cartesian products of all
@ -121,23 +143,23 @@ def check_get_everything():
# Some small stuff first # Some small stuff first
for damage_class in session.query(tables.MoveDamageClass).all(): 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(): 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(): 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(): for item_pocket in session.query(tables.ItemPocket).all():
assert hit(filenames, media.ItemPocketMedia(item_pocket).icon) assert hit(filenames, media.ItemPocketMedia(root, item_pocket).icon)
assert hit(filenames, media.ItemPocketMedia(item_pocket).icon, selected=True) assert hit(filenames, media.ItemPocketMedia(root, item_pocket).icon, selected=True)
for contest_type in session.query(tables.ContestType).all(): 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(): 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 # Items
versions_for_items = [ versions_for_items = [
@ -146,7 +168,7 @@ def check_get_everything():
] ]
for item in session.query(tables.Item).all(): 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 assert hit(filenames, accessor.berry_image) or not item.berry
for rotation in (0, 90, 180, 270): for rotation in (0, 90, 180, 270):
assert hit(filenames, accessor.underground, rotation=rotation) or ( 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 color in 'red green blue pale prism'.split():
for big in (True, False): 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) assert hit(filenames, accessor.underground)
for rock_type in 'i ii o o-big s t z'.split(): 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): for rotation in (0, 90, 180, 270):
success = hit(filenames, accessor.underground, rotation=rotation) success = hit(filenames, accessor.underground, rotation=rotation)
assert success or rotation assert success or rotation
@ -170,19 +192,17 @@ def check_get_everything():
# Pokemon! # Pokemon!
accessors = [] accessors = []
accessors.append(media.UnknownPokemonMedia()) accessors.append(media.UnknownPokemonMedia(root))
accessors.append(media.EggMedia()) accessors.append(media.EggMedia(root))
manaphy = session.query(tables.Pokemon).filter_by(identifier=u'manaphy').one() manaphy = session.query(tables.Pokemon).filter_by(identifier=u'manaphy').one()
accessors.append(media.EggMedia(manaphy)) accessors.append(media.EggMedia(root, manaphy))
accessors.append(media.SubstituteMedia()) accessors.append(media.SubstituteMedia(root))
print 'Loading pokemon'
for form in session.query(tables.PokemonForm).filter(tables.PokemonForm.identifier != '').all(): 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(): for pokemon in session.query(tables.Pokemon).all():
accessors.append(media.PokemonMedia(pokemon)) accessors.append(media.PokemonMedia(root, pokemon))
for accessor in accessors: for accessor in accessors:
assert hit(filenames, accessor.footprint) or not accessor.form assert hit(filenames, accessor.footprint) or not accessor.form

View file

@ -1,7 +1,12 @@
"""Media accessors """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 Their various methods take a number of arguments specifying exactly which
file you want (such as the female sprite, backsprite, etc.). file you want (such as the female sprite, backsprite, etc.).
ValueError is raised when the specified file cannot be found. 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 os
import pkg_resources from functools import partial
class MediaFile(object): class MediaFile(object):
"""Represents a file: picture, sound, etc. """Represents a file: picture, sound, etc.
Attributes: 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 path: Absolute path to the file
exists: True if the file exists exists: True if the file exists
media_available: false if no media is available at the given root.
open(): Open the file open(): Open the file
""" """
def __init__(self, *path_elements): def __init__(self, root, *path_elements):
self.path_elements = path_elements self.path_elements = path_elements
self._dexpath = '/'.join(('data', 'media') + path_elements) self.root = root
@property @property
def relative_path(self): def relative_path(self):
@ -49,7 +57,7 @@ class MediaFile(object):
@property @property
def path(self): def path(self):
return pkg_resources.resource_filename('pokedex', self._dexpath) return os.path.join(self.root, *self.path_elements)
def open(self): def open(self):
"""Open this file for reading, in the appropriate mode (i.e. binary) """Open this file for reading, in the appropriate mode (i.e. binary)
@ -58,7 +66,11 @@ class MediaFile(object):
@property @property
def exists(self): 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): def __eq__(self, other):
return self.path == other.path return self.path == other.path
@ -70,15 +82,25 @@ class MediaFile(object):
return '<Pokedex file %s>' % self.relative_path return '<Pokedex file %s>' % self.relative_path
class BaseMedia(object): 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, def from_path_elements(self, path_elements, basename, extension,
surely_exists=False): surely_exists=False):
filename = basename + extension filename = basename + extension
path_elements = [self.toplevel_dir] + path_elements + [filename] path_elements = [self.toplevel_dir] + path_elements + [filename]
mfile = MediaFile(*path_elements) mfile = self.file_class(*path_elements)
if surely_exists or mfile.exists: if surely_exists or mfile.exists:
return mfile return mfile
else: else:
raise ValueError('File %s not found' % mfile.relative_path) raise ValueError('File %s not found' % mfile.path)
class _BasePokemonMedia(BaseMedia): class _BasePokemonMedia(BaseMedia):
toplevel_dir = 'pokemon' toplevel_dir = 'pokemon'
@ -104,8 +126,8 @@ class _BasePokemonMedia(BaseMedia):
'black-white': (5, set('back shiny female'.split())), 'black-white': (5, set('back shiny female'.split())),
} }
def __init__(self, pokemon_id, form_postfix=None): def __init__(self, root, pokemon_id, form_postfix=None):
BaseMedia.__init__(self) BaseMedia.__init__(self, root)
self.pokemon_id = str(pokemon_id) self.pokemon_id = str(pokemon_id)
self.form_postfix = form_postfix self.form_postfix = form_postfix
@ -316,13 +338,13 @@ class _BasePokemonMedia(BaseMedia):
class PokemonFormMedia(_BasePokemonMedia): class PokemonFormMedia(_BasePokemonMedia):
"""Media related to a Pokemon form """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 pokemon_id = pokemon_form.form_base_pokemon_id
if pokemon_form.identifier: if pokemon_form.identifier:
form_postfix = '-' + pokemon_form.identifier form_postfix = '-' + pokemon_form.identifier
else: else:
form_postfix = None form_postfix = None
_BasePokemonMedia.__init__(self, pokemon_id, form_postfix) _BasePokemonMedia.__init__(self, root, pokemon_id, form_postfix)
self.form = pokemon_form self.form = pokemon_form
pokemon = pokemon_form.form_base_pokemon pokemon = pokemon_form.form_base_pokemon
self.has_gender_differences = pokemon.has_gender_differences self.has_gender_differences = pokemon.has_gender_differences
@ -331,8 +353,8 @@ class PokemonFormMedia(_BasePokemonMedia):
class PokemonMedia(_BasePokemonMedia): class PokemonMedia(_BasePokemonMedia):
"""Media related to a Pokemon """Media related to a Pokemon
""" """
def __init__(self, pokemon): def __init__(self, root, pokemon):
_BasePokemonMedia.__init__(self, pokemon.id) _BasePokemonMedia.__init__(self, root, pokemon.id)
self.form = pokemon.default_form self.form = pokemon.default_form
self.has_gender_differences = (pokemon.has_gender_differences) self.has_gender_differences = (pokemon.has_gender_differences)
self.introduced_in = pokemon.generation_id self.introduced_in = pokemon.generation_id
@ -342,8 +364,8 @@ class UnknownPokemonMedia(_BasePokemonMedia):
Note that not a lot of files are available for it. Note that not a lot of files are available for it.
""" """
def __init__(self): def __init__(self, root):
_BasePokemonMedia.__init__(self, '0') _BasePokemonMedia.__init__(self, root, '0')
class EggMedia(_BasePokemonMedia): class EggMedia(_BasePokemonMedia):
"""Media related to a pokemon egg """Media related to a pokemon egg
@ -352,20 +374,20 @@ class EggMedia(_BasePokemonMedia):
Give a Manaphy as `pokemon` to get the Manaphy egg. 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': if pokemon and pokemon.identifier == 'manaphy':
postfix = '-manaphy' postfix = '-manaphy'
else: else:
postfix = None postfix = None
_BasePokemonMedia.__init__(self, 'egg', postfix) _BasePokemonMedia.__init__(self, root, 'egg', postfix)
class SubstituteMedia(_BasePokemonMedia): class SubstituteMedia(_BasePokemonMedia):
"""Media related to the Substitute sprite """Media related to the Substitute sprite
Note that not a lot of files are available for Substitute. Note that not a lot of files are available for Substitute.
""" """
def __init__(self): def __init__(self, root):
_BasePokemonMedia.__init__(self, 'substitute') _BasePokemonMedia.__init__(self, root, 'substitute')
class _BaseItemMedia(BaseMedia): class _BaseItemMedia(BaseMedia):
toplevel_dir = 'items' toplevel_dir = 'items'
@ -383,7 +405,8 @@ class _BaseItemMedia(BaseMedia):
class ItemMedia(_BaseItemMedia): class ItemMedia(_BaseItemMedia):
"""Media related to an item """Media related to an item
""" """
def __init__(self, item): def __init__(self, root, item):
_BaseItemMedia.__init__(self, root)
self.item = item self.item = item
self.identifier = item.identifier 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 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 self.identifier = 'rock-%s' % rock_type
class UndergroundSphereMedia(_BaseItemMedia): class UndergroundSphereMedia(_BaseItemMedia):
@ -467,13 +491,15 @@ class UndergroundSphereMedia(_BaseItemMedia):
color can be one of: red, blue, green, pale, prism 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 self.identifier = '%s-sphere' % color
if big: if big:
self.identifier += '-big' self.identifier += '-big'
class _SimpleIconMedia(BaseMedia): class _SimpleIconMedia(BaseMedia):
def __init__(self, thing): def __init__(self, root, thing):
BaseMedia.__init__(self, root)
self.identifier = thing.identifier self.identifier = thing.identifier
def icon(self): def icon(self):