Merge branch 'encukou-util'

This commit is contained in:
Eevee 2011-04-17 22:34:26 -07:00
commit 47c4235cdd
8 changed files with 279 additions and 10 deletions

View file

@ -1,4 +1,7 @@
"""Functions missing from Python 2.5"""
"""Things missing from older versions of Python
Currently these are functions missing from Python 2.5.
"""
try:
from itertools import permutations

View file

@ -48,7 +48,7 @@ def create_translation_table(_table_name, foreign_class, relation_name,
are rows in the created tables.
- `(relation_name)_local`, a relation to the row in the new table that
matches the current default language.
- `(relation_name)_class`, the class created by this function.
- `(relation_name)_table`, the class created by this function.
Note that these are distinct relations. Even though the former necessarily
includes the latter, SQLAlchemy doesn't treat them as linked; loading one

View file

@ -1099,12 +1099,6 @@ class Pokemon(TableBase):
### Stuff to handle alternate Pokémon forms
@property
def form(self):
u"""Returns the Pokémon's form, using its default form as fallback."""
return self.unique_form or self.default_form
@property
def is_base_form(self):
u"""Returns True iff the Pokémon is the base form for its species,
@ -2021,6 +2015,14 @@ Pokemon.types = relation(Type,
innerjoin=True,
order_by=PokemonType.slot.asc(),
backref=backref('pokemon', order_by=Pokemon.order))
Pokemon.form = relation(PokemonForm,
primaryjoin=or_(
PokemonForm.unique_pokemon_id==Pokemon.id,
and_(PokemonForm.unique_pokemon_id==None,
PokemonForm.form_base_pokemon_id==Pokemon.id,
PokemonForm.is_default==True)
),
uselist=False)
PokemonDexNumber.pokedex = relation(Pokedex,
innerjoin=True, lazy='joined')

123
pokedex/db/util.py Normal file
View file

@ -0,0 +1,123 @@
"""Helpers for common ways to work with pokedex queries
These include identifier- and name-based lookup, filtering out base forms
of pokemon, and filtering/ordering by name.
"""
from sqlalchemy.orm import aliased
from pokedex.db import tables
### Getter
def get(session, table, identifier=None, name=None, id=None,
form_identifier=None, form_name=None, language=None, is_pokemon=None):
"""Get one object from the database.
session: The session to use (from pokedex.db.connect())
table: The table to select from (such as pokedex.db.tables.Move)
identifier: Identifier of the object
name: The name of the object
id: The ID number of the object
form_identifier: For pokemon, identifier of the form
form_name: For pokemon, name of the form
language: A Language to use for name and form_name
is_pokemon: If true, specifies that the table should be treated as a
pokemon table (handling forms specially). If None and table is the
(unaliased) Pokemon, it is set to True. Otherwise, the pokemon forms
aren't handled.
All conditions must match, so it's not a good idea to specify more than one
of identifier/name/id at once.
If zero or more than one objects matching the criteria are found, the
appropriate SQLAlchemy exception is raised.
Exception: for pokemon, selects the form base unless form_* is given.
"""
if is_pokemon is None:
is_pokemon = (table is tables.Pokemon)
query = session.query(table)
if identifier is not None:
query = query.filter_by(identifier=identifier)
if name is not None:
query = filter_name(query, table, name, language)
if id is not None:
query = query.filter_by(id=id)
if form_identifier is not None or form_name is not None:
if is_pokemon:
query = query.join(table.unique_form)
if form_identifier is not None:
query = query.filter(tables.PokemonForm.identifier ==
form_identifier)
if form_name is not None:
query = filter_name(query, table, form_name, language)
else:
raise ValueError(
"form_identifier and form_name only make sense for pokemon")
elif is_pokemon:
query = filter_base_forms(query)
return query.one()
### Helpers
def filter_name(query, table, name, language):
"""Filter a query by name, return the resulting query
query: The query to filter
table: The table of named objects
name: The name to look for. May be a tuple of alternatives.
language: The language for "name", or None for the session default
"""
if language is None:
query = query.filter(table.name == name)
else:
names_table = table.names_table
query = query.join(names_table)
query = query.filter(names_table.foreign_id == table.id)
query = query.filter(names_table.local_language_id == language.id)
if isinstance(name, tuple):
query = query.filter(names_table.name in name)
else:
query = query.filter(names_table.name == name)
return query
def filter_base_forms(query):
"""Filter only base forms of pokemon, and return the resulting query
"""
query = query.filter(tables.Pokemon.forms.any())
return query
def order_by_name(query, table, language=None, *extra_languages):
"""Order a query by name.
query: The query to order
table: Table of the named objects
language: The language to order names by. If None, use the
connection default.
extra_languages: Extra languages to order by, should the translations for
`language` be incomplete (or ambiguous).
Uses the identifier as a fallback ordering.
"""
if language is None:
query = query.outerjoin(table.names_local)
query = query.order_by(table.names_table.name)
else:
extra_languages = (language, ) + extra_languages
for language in extra_languages:
names_table = aliased(table.names_table)
query = query.outerjoin(names_table)
query = query.filter(names_table.foreign_id == table.id)
query = query.filter(names_table.local_language_id == language.id)
query = query.order_by(names_table.name)
query = query.order_by(table.identifier)
return query

View file

@ -14,7 +14,7 @@ from whoosh.qparser import QueryParser
import whoosh.scoring
import whoosh.spelling
from pokedex.util import namedtuple
from pokedex.compatibility import namedtuple
from pokedex.db import connect
import pokedex.db.tables as tables

View file

@ -12,7 +12,7 @@ import struct
from pokedex.db import tables
from pokedex.formulae import calculated_hp, calculated_stat
from pokedex.util import namedtuple, permutations
from pokedex.compatibility import namedtuple, permutations
from pokedex.struct._pokemon_struct import pokemon_struct
def pokemon_prng(seed):

View file

@ -0,0 +1,75 @@
# encoding: utf8
from nose.tools import *
import unittest
from pokedex.db import connect, tables, util
from pokedex.util import simple
session = connect()
def test_get_item_identifier():
item = util.get(session, tables.Item, identifier='master-ball')
assert item.name == 'Master Ball'
def test_get_item_name():
item = util.get(session, tables.Item, name='Awakening')
assert item.name == 'Awakening'
def test_get_english_by_identifier():
language = util.get(session, tables.Language, 'en')
assert language.name == 'English'
def test_get_pokemon_baseform_identifier():
for identifier in 'burmy shaymin unown cresselia'.split():
poke = util.get(session, tables.Pokemon, identifier=identifier)
assert poke.identifier == identifier
assert poke.is_base_form
def test_get_pokemon_baseform_name():
for name in 'Burmy Shaymin Unown Cresselia'.split():
poke = util.get(session, tables.Pokemon, name=name)
assert poke.name == name
assert poke.is_base_form
def test_get_pokemon_baseform_name_explicit_language():
french = util.get(session, tables.Language, 'fr')
for name in 'Cheniti Shaymin Zarbi Cresselia'.split():
poke = util.get(session, tables.Pokemon, name=name, language=french)
assert poke.name_map[french] == name, poke.name_map[french]
assert poke.is_base_form
def test_get_pokemon_other_form_identifier():
for ii in 'wormadam/trash shaymin/sky shaymin/land'.split():
pokemon_identifier, form_identifier = ii.split('/')
poke = util.get(session, tables.Pokemon, identifier=pokemon_identifier, form_identifier=form_identifier)
assert poke.identifier == pokemon_identifier
if poke.form.unique_pokemon_id:
assert poke.form.identifier == form_identifier
def test_types_french_order():
french = util.get(session, tables.Language, 'fr')
types = session.query(tables.Type).filter(tables.Type.id < 10000)
types = list(util.order_by_name(types, tables.Type, language=french))
assert types[0].name_map[french] == 'Acier', types[0].name_map[french]
assert types[-1].name_map[french] == 'Vol', types[-1].name_map[french]
def test_simple_pokemon():
pokemon = simple.pokemon(session)
assert pokemon[0].identifier == 'bulbasaur'
assert pokemon[-1].identifier == 'genesect'
def test_simple_types():
types = simple.types(session)
assert types[0].identifier == 'bug'
assert types[-1].identifier == 'water'
def test_simple_moves():
moves = simple.moves(session)
assert moves[0].identifier == 'absorb'
assert moves[-1].identifier == 'zen-headbutt'
def test_simple_items():
items = simple.items(session)
assert items[0].identifier == 'ability-urge'
assert items[-1].identifier == 'zoom-lens'

66
pokedex/util/simple.py Normal file
View file

@ -0,0 +1,66 @@
"""Simple lists of things for simple scripts
If you want to get a pokemon list, and you don't want it to include three
Wormadams and a whole bunch of Rotoms because of how the database is
structured, this module is for you.
The returned queries basically contain what a pokedex would show you.
You should make no other assumptions about them.
If you need to make assumptions, feel free to use these functions as examples
of what to watch out for.
"""
from pokedex.db import tables
from pokedex.db.util import filter_base_forms, order_by_name
def pokemon(session):
"""Get a "sane" list of pokemon
WARNING: The result of this function is not very well defined.
If you want something specific, build that specific query yourself.
Currently, all base forms are returned, in evolution-preserving order
"""
query = session.query(tables.Pokemon)
query = query.order_by(tables.Pokemon.order)
query = filter_base_forms(query)
return query
def moves(session):
"""Get a "sane" list of moves
WARNING: The result of this function is not very well defined.
If you want something specific, build that specific query yourself.
Currently, moves from mainline games are returned, sored by name
"""
query = session.query(tables.Move)
query = order_by_name(query, tables.Move)
query = query.filter(tables.Move.id < 10000)
return query
def types(session):
"""Get a "sane" list of types
WARNING: The result of this function is not very well defined.
If you want something specific, build that specific query yourself.
Currently, generation V types are returned, sored by name
"""
query = session.query(tables.Type)
query = order_by_name(query, tables.Type)
query = query.filter(tables.Type.id < 10000)
return query
def items(session):
"""Get a "sane" list of items
WARNING: The result of this function is not very well defined.
If you want something specific, build that specific query yourself.
Currently, items are sored by name
"""
query = session.query(tables.Item)
query = order_by_name(query, tables.Item)
return query