mirror of
https://github.com/veekun/pokedex.git
synced 2024-08-20 18:16:34 +00:00
Merge branch 'encukou-util'
This commit is contained in:
commit
47c4235cdd
8 changed files with 279 additions and 10 deletions
|
@ -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:
|
try:
|
||||||
from itertools import permutations
|
from itertools import permutations
|
|
@ -48,7 +48,7 @@ def create_translation_table(_table_name, foreign_class, relation_name,
|
||||||
are rows in the created tables.
|
are rows in the created tables.
|
||||||
- `(relation_name)_local`, a relation to the row in the new table that
|
- `(relation_name)_local`, a relation to the row in the new table that
|
||||||
matches the current default language.
|
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
|
Note that these are distinct relations. Even though the former necessarily
|
||||||
includes the latter, SQLAlchemy doesn't treat them as linked; loading one
|
includes the latter, SQLAlchemy doesn't treat them as linked; loading one
|
||||||
|
|
|
@ -1099,12 +1099,6 @@ class Pokemon(TableBase):
|
||||||
|
|
||||||
### Stuff to handle alternate Pokémon forms
|
### 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
|
@property
|
||||||
def is_base_form(self):
|
def is_base_form(self):
|
||||||
u"""Returns True iff the Pokémon is the base form for its species,
|
u"""Returns True iff the Pokémon is the base form for its species,
|
||||||
|
@ -2021,6 +2015,14 @@ Pokemon.types = relation(Type,
|
||||||
innerjoin=True,
|
innerjoin=True,
|
||||||
order_by=PokemonType.slot.asc(),
|
order_by=PokemonType.slot.asc(),
|
||||||
backref=backref('pokemon', order_by=Pokemon.order))
|
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,
|
PokemonDexNumber.pokedex = relation(Pokedex,
|
||||||
innerjoin=True, lazy='joined')
|
innerjoin=True, lazy='joined')
|
||||||
|
|
123
pokedex/db/util.py
Normal file
123
pokedex/db/util.py
Normal 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
|
|
@ -14,7 +14,7 @@ from whoosh.qparser import QueryParser
|
||||||
import whoosh.scoring
|
import whoosh.scoring
|
||||||
import whoosh.spelling
|
import whoosh.spelling
|
||||||
|
|
||||||
from pokedex.util import namedtuple
|
from pokedex.compatibility import namedtuple
|
||||||
|
|
||||||
from pokedex.db import connect
|
from pokedex.db import connect
|
||||||
import pokedex.db.tables as tables
|
import pokedex.db.tables as tables
|
||||||
|
|
|
@ -12,7 +12,7 @@ import struct
|
||||||
|
|
||||||
from pokedex.db import tables
|
from pokedex.db import tables
|
||||||
from pokedex.formulae import calculated_hp, calculated_stat
|
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
|
from pokedex.struct._pokemon_struct import pokemon_struct
|
||||||
|
|
||||||
def pokemon_prng(seed):
|
def pokemon_prng(seed):
|
||||||
|
|
75
pokedex/tests/test_util.py
Normal file
75
pokedex/tests/test_util.py
Normal 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
66
pokedex/util/simple.py
Normal 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
|
Loading…
Add table
Reference in a new issue