ab5e6b81ead5be32373057c509989eb0efa3b1fe
[zzz-pokedex.git] / pokedex / util / get.py
1 """Provides simple functions for common queries
2
3 These include identifier- and name-based lookup, filtering out base forms
4 of pokemon, ordering by name, and getting canonical "pokedex" lists (i.e.
5 ordered and without cruft like alternate pokemon forms or Shadow moves)
6 """
7
8 from sqlalchemy.orm import aliased
9
10 from pokedex.db import tables
11
12 ### Getters
13
14 def get(session, table, identifier=None, name=None, id=None,
15 form_identifier=None, form_name=None, language=None, is_pokemon=None):
16 """Get one object from the database.
17
18 session: The session to use (from pokedex.db.connect())
19 table: The table to select from (such as pokedex.db.tables.Move)
20
21 identifier: Identifier of the object
22 name: The name of the object
23 id: The ID number of the object
24 form_identifier: For pokemon, identifier of the form
25 form_name: For pokemon, name of the form
26
27 language: A Language to use for name and form_name
28 is_pokemon: If true, specifies that the table should be treated as a
29 pokemon table (handling forms specially). If None and table is the
30 (unaliased) Pokemon, it is set to True. Otherwise, the pokemon forms
31 aren't handled.
32
33 All conditions must match, so it's not a good idea to specify more than one
34 of identifier/name/id at once.
35
36 If zero or more than one objects matching the criteria are found, the
37 appropriate SQLAlchemy exception is raised.
38 Exception: for pokemon, selects the form base unless form_* is given.
39 """
40
41 if is_pokemon is None:
42 is_pokemon = (table is tables.Pokemon)
43
44 query = session.query(table)
45
46 if identifier is not None:
47 query = query.filter_by(identifier=identifier)
48
49 if name is not None:
50 query = filter_name(query, table, name, language)
51
52 if id is not None:
53 query = query.filter_by(id=id)
54
55 if form_identifier is not None or form_name is not None:
56 if is_pokemon:
57 query = query.join(table.unique_form)
58 if form_identifier is not None:
59 query = query.filter(tables.PokemonForm.identifier ==
60 form_identifier)
61 if form_name is not None:
62 query = filter_name(query, table, form_name, language)
63 else:
64 raise ValueError(
65 "form_identifier and form_name only make sense for pokemon")
66 elif is_pokemon:
67 query = filter_base_forms(query)
68
69 return query.one()
70
71 ### Helpers
72
73 def filter_name(query, table, name, language):
74 """Filter a query by name, return the resulting query
75
76 query: The query to filter
77 table: The table of named objects
78 name: The name to look for. May be a tuple of alternatives.
79 language: The language for "name", or None for the session default
80 """
81 if language is None:
82 query = query.filter(table.name == name)
83 else:
84 names_table = table.names_table
85 query = query.join(names_table)
86 query = query.filter(names_table.foreign_id == table.id)
87 query = query.filter(names_table.local_language_id == language.id)
88 if isinstance(name, tuple):
89 query = query.filter(names_table.name in name)
90 else:
91 query = query.filter(names_table.name == name)
92 return query
93
94 def filter_base_forms(query):
95 """Filter only base forms of pokemon, and return the resulting query
96 """
97 query = query.filter(tables.Pokemon.forms.any())
98 return query
99
100 def order_by_name(query, table, language=None, *extra_languages):
101 """Order a query by name.
102
103 query: The query to order
104 table: Table of the named objects
105 language: The language to order names by. If None, use the
106 connection default.
107 extra_languages: Extra languages to order by, should the translations for
108 `language` be incomplete (or ambiguous).
109
110 Uses the identifier as a fallback ordering.
111 """
112 if language is None:
113 query = query.outerjoin(table.names_local)
114 query = query.order_by(table.names_table.name)
115 else:
116 extra_languages = (language, ) + extra_languages
117 for language in extra_languages:
118 names_table = aliased(table.names_table)
119 query = query.outerjoin(names_table)
120 query = query.filter(names_table.foreign_id == table.id)
121 query = query.filter(names_table.local_language_id == language.id)
122 query = query.order_by(names_table.name)
123 query = query.order_by(table.identifier)
124 return query
125
126 _name = object()
127 def get_all(session, table, order=_name):
128 """Shortcut to get an ordered query from table.
129
130 session: The session to use
131 table: The table to select from
132 order: A clause to order by, or None for no ordering.
133 The default is to order by name; this can also be specified explicitly
134 with the table's name property (e.g. tables.Pokemon.name). Be aware
135 that the query's order_by will not order by name this way.
136 """
137 query = session.query(table)
138 if order is table.name or order is _name:
139 query = order_by_name(query, table)
140 elif order is not None:
141 query = query.order_by(order)
142 return query
143
144 ### Shortcuts
145
146 def pokemon(session, order=tables.Pokemon.id):
147 """Return a query for all base form pokemon, ordered by id by default
148
149 See get_all for the session and order arguments (but note the default for
150 pokemon is to order by id).
151 """
152 query = get_all(session, tables.Pokemon, order=order)
153 query = query.filter(tables.Pokemon.forms.any())
154 return query
155
156 def moves(session, order=_name):
157 """Return a query for moves in the mainline games (i.e. no Shadow moves)
158
159 See get_all for the session and order arguments.
160 """
161 return get_all(session, tables.Move, order=order).filter(tables.Move.id < 10000)
162
163 def types(session, order=_name):
164 """Return a query for sane types (i.e. not ???, Shadow)
165
166 See get_all for the session and order arguments.
167 """
168 return get_all(session, tables.Type, order=order).filter(tables.Type.id < 10000)
169
170 def items(session, order=_name):
171 """Return a query for items
172
173 See get_all for the session and order arguments.
174 """
175 return get_all(session, tables.Item, order=order)