Support filtering by strings (Pokemon.name, Pokemon.names['fr'], etc.)
[zzz-pokedex.git] / pokedex / db / tables.py
1 # encoding: utf8
2
3 u"""The Pokédex schema
4
5 Columns have a info dictionary with these keys:
6 - description: The description of the column
7 - official: True if the values appear in games or official material; False if
8 they are fan-created or fan-written. This flag is currently only set for
9 official text columns.
10 - markup: The format of a text column. Can be one of:
11 - plaintext: Normal Unicode text (widely used in names)
12 - markdown: Veekun's Markdown flavor (generally used in effect descriptions)
13 - gametext: Transcription of in-game text that strives to be both
14 human-readable and represent the original text exactly.
15 - identifier: A fan-made identifier in the [-_a-z0-9]* format. Not intended
16 for translation.
17 - latex: A formula in LaTeX syntax.
18
19 A localizable text column is visible as two properties:
20 The plural-name property (e.g. Pokemon.names) is a language-to-name dictionary:
21 bulbasaur.names['en'] == "Bulbasaur" and bulbasaur.names['de'] == "Bisasam".
22 You can use Pokemon.names['en'] to filter a query.
23 The singular-name property returns the name in the default language, English.
24 For example bulbasaur.name == "Bulbasaur"
25 Setting pokedex.db.tables.default_lang changes the default language.
26 """
27 # XXX: Check if "gametext" is set correctly everywhere
28
29 import operator
30
31 from sqlalchemy import Column, ForeignKey, MetaData, PrimaryKeyConstraint, Table
32 from sqlalchemy.ext.declarative import (
33 declarative_base, declared_attr, DeclarativeMeta,
34 )
35 from sqlalchemy.ext.associationproxy import association_proxy
36 from sqlalchemy.orm import (
37 backref, eagerload_all, relation, class_mapper, synonym, mapper,
38 )
39 from sqlalchemy.orm.session import Session
40 from sqlalchemy.orm.collections import attribute_mapped_collection
41 from sqlalchemy.ext.associationproxy import association_proxy
42 from sqlalchemy.sql import and_
43 from sqlalchemy.sql.expression import ColumnOperators
44 from sqlalchemy.types import *
45 from inspect import isclass
46
47 from pokedex.db import markdown
48
49 # A list of all table classes will live in table_classes
50 table_classes = []
51
52 class TableMetaclass(DeclarativeMeta):
53 def __init__(cls, name, bases, attrs):
54 super(TableMetaclass, cls).__init__(name, bases, attrs)
55 if hasattr(cls, '__tablename__'):
56 table_classes.append(cls)
57
58 metadata = MetaData()
59 TableBase = declarative_base(metadata=metadata, metaclass=TableMetaclass)
60
61 ### Helper classes
62 class Named(object):
63 """Mixin for objects that have names"""
64 def __unicode__(self):
65 try:
66 return '<%s: %s>' % (type(self).__name__, self.identifier)
67 except AttributeError:
68 return '<%s>' % type(self).__name__
69
70 def __str__(self):
71 return unicode(self).encode('utf-8')
72
73 def __repr__(self):
74 return str(self)
75
76 class OfficiallyNamed(Named):
77 """Mixin for stuff with official names"""
78
79 class UnofficiallyNamed(Named):
80 """Mixin for stuff with unofficial names"""
81
82 class LanguageSpecific(object):
83 """Mixin for prose and text tables"""
84 @declared_attr
85 def language_id(cls):
86 return Column(Integer, ForeignKey('languages.id'), primary_key=True, nullable=False,
87 info=dict(description="The language"))
88
89 class LanguageSpecificColumn(object):
90 """A column that will not appear in the table it's defined in, but in a related one"""
91 _ordering = [1]
92 def __init__(self, *args, **kwargs):
93 self.args = args
94 self.plural = kwargs.pop('plural')
95 self.kwargs = kwargs
96 self.order = self._ordering[0]
97 self._ordering[0] += 1
98
99 def makeSAColumn(self):
100 return Column(*self.args, **self.kwargs)
101
102 class ProseColumn(LanguageSpecificColumn):
103 """A column that will appear in the corresponding _prose table"""
104
105 class TextColumn(LanguageSpecificColumn):
106 """A column that will appear in the corresponding _text table"""
107
108
109 ### The actual tables
110
111 class Ability(TableBase, OfficiallyNamed):
112 u"""An ability a Pokémon can have, such as Static or Pressure.
113 """
114 __tablename__ = 'abilities'
115 __singlename__ = 'ability'
116 id = Column(Integer, primary_key=True, nullable=False,
117 info=dict(description="This ability's unique ID; matches the games' internal ID"))
118 identifier = Column(Unicode(24), nullable=False,
119 info=dict(description="An identifier", format='identifier'))
120 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
121 info=dict(description="The ID of the generation this ability was introduced in", detail=True))
122 effect = ProseColumn(markdown.MarkdownColumn(5120), plural='effects', nullable=False,
123 info=dict(description="A detailed description of this ability's effect", format='markdown'))
124 short_effect = ProseColumn(markdown.MarkdownColumn(255), plural='short_effects', nullable=False,
125 info=dict(description="A short summary of this ability's effect", format='markdown'))
126
127 class AbilityChangelog(TableBase):
128 """History of changes to abilities across main game versions."""
129 __tablename__ = 'ability_changelog'
130 __singlename__ = 'ability_changelog'
131 id = Column(Integer, primary_key=True, nullable=False,
132 info=dict(description="This change's unique ID"))
133 ability_id = Column(Integer, ForeignKey('abilities.id'), nullable=False,
134 info=dict(description="The ID of the ability that changed"))
135 changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False,
136 info=dict(description="The ID of the version group in which the ability changed"))
137 effect = ProseColumn(markdown.MarkdownColumn(255), plural='effects', nullable=False,
138 info=dict(description="A description of the old behavior", format='markdown'))
139
140 class AbilityFlavorText(TableBase, LanguageSpecific):
141 u"""In-game flavor text of an ability
142 """
143 __tablename__ = 'ability_flavor_text'
144 ability_id = Column(Integer, ForeignKey('abilities.id'), primary_key=True, nullable=False, autoincrement=False,
145 info=dict(description="The ID of the ability"))
146 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False,
147 info=dict(description="The ID of the version group this flavor text is taken from"))
148 flavor_text = Column(Unicode(64), nullable=False,
149 info=dict(description="The actual flavor text", official=True, format='gametext'))
150
151 class Berry(TableBase):
152 u"""A Berry, consumable item that grows on trees
153
154 For data common to all items, such as the name, see the corresponding item entry.
155 """
156 __tablename__ = 'berries'
157 id = Column(Integer, primary_key=True, nullable=False,
158 info=dict(description="This Berry's in-game number"))
159 item_id = Column(Integer, ForeignKey('items.id'), nullable=False,
160 info=dict(description="The ID of the item that represents this Berry"))
161 firmness_id = Column(Integer, ForeignKey('berry_firmness.id'), nullable=False,
162 info=dict(description="The ID of this Berry's firmness category"))
163 natural_gift_power = Column(Integer, nullable=True,
164 info=dict(description="Natural Gift's power when used with this Berry"))
165 natural_gift_type_id = Column(Integer, ForeignKey('types.id'), nullable=True,
166 info=dict(description="The ID of the Type that Natural Gift has when used with this Berry"))
167 size = Column(Integer, nullable=False,
168 info=dict(description=u"The size of this Berry, in millimeters"))
169 max_harvest = Column(Integer, nullable=False,
170 info=dict(description="The maximum number of these berries that can grow on one tree in Generation IV"))
171 growth_time = Column(Integer, nullable=False,
172 info=dict(description="Time it takes the tree to grow one stage, in hours. Berry trees go through four of these growth stages before they can be picked."))
173 soil_dryness = Column(Integer, nullable=False,
174 info=dict(description="The speed at which this Berry dries out the soil as it grows. A higher rate means the soil dries more quickly."))
175 smoothness = Column(Integer, nullable=False,
176 info=dict(description="The smoothness of this Berry, used in making Pokéblocks or Poffins"))
177
178 class BerryFirmness(TableBase, OfficiallyNamed):
179 u"""A Berry firmness, such as "hard" or "very soft".
180 """
181 __tablename__ = 'berry_firmness'
182 __singlename__ = 'berry_firmness'
183 id = Column(Integer, primary_key=True, nullable=False,
184 info=dict(description="A unique ID for this firmness"))
185 identifier = Column(Unicode(10), nullable=False,
186 info=dict(description="An identifier", format='identifier'))
187
188 class BerryFlavor(TableBase):
189 u"""A Berry flavor level.
190 """
191 __tablename__ = 'berry_flavors'
192 berry_id = Column(Integer, ForeignKey('berries.id'), primary_key=True, nullable=False, autoincrement=False,
193 info=dict(description="The ID of the berry"))
194 contest_type_id = Column(Integer, ForeignKey('contest_types.id'), primary_key=True, nullable=False, autoincrement=False,
195 info=dict(description="The ID of the flavor"))
196 flavor = Column(Integer, nullable=False,
197 info=dict(description="The level of the flavor in the berry"))
198
199 class ContestCombo(TableBase):
200 u"""Combo of two moves in a Contest.
201 """
202 __tablename__ = 'contest_combos'
203 first_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
204 info=dict(description="The ID of the first move in the combo"))
205 second_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
206 info=dict(description="The ID of the second and final move in the combo"))
207
208 class ContestEffect(TableBase):
209 u"""Effect of a move when used in a Contest.
210 """
211 __tablename__ = 'contest_effects'
212 __singlename__ = 'contest_effect'
213 id = Column(Integer, primary_key=True, nullable=False,
214 info=dict(description="A unique ID for this effect"))
215 appeal = Column(SmallInteger, nullable=False,
216 info=dict(description="The base number of hearts the user of this move gets"))
217 jam = Column(SmallInteger, nullable=False,
218 info=dict(description="The base number of hearts the user's opponent loses"))
219 flavor_text = ProseColumn(Unicode(64), plural='flavor_texts', nullable=False,
220 info=dict(description="The in-game description of this effect", official=True, format='gametext'))
221 effect = ProseColumn(Unicode(255), plural='effects', nullable=False,
222 info=dict(description="A detailed description of the effect", format='plaintext'))
223
224 class ContestType(TableBase, OfficiallyNamed):
225 u"""A Contest type, such as "cool" or "smart", and their associated Berry flavors and Pokéblock colors.
226 """
227 __tablename__ = 'contest_types'
228 __singlename__ = 'contest_type'
229 id = Column(Integer, primary_key=True, nullable=False,
230 info=dict(description="A unique ID for this Contest type"))
231 identifier = Column(Unicode(6), nullable=False,
232 info=dict(description="An identifier", format='identifier'))
233 flavor = TextColumn(Unicode(6), nullable=False, plural='flavors',
234 info=dict(description="The name of the corresponding Berry flavor", official=True, format='plaintext'))
235 color = TextColumn(Unicode(6), nullable=False, plural='colors',
236 info=dict(description=u"The name of the corresponding Pokéblock color", official=True, format='plaintext'))
237
238 class EggGroup(TableBase, UnofficiallyNamed):
239 u"""An Egg group. Usually, two Pokémon can breed if they share an Egg Group.
240
241 (exceptions are the Ditto and No Eggs groups)
242 """
243 __tablename__ = 'egg_groups'
244 __singlename__ = 'egg_group'
245 id = Column(Integer, primary_key=True, nullable=False,
246 info=dict(description="A unique ID for this group"))
247 identifier = Column(Unicode(16), nullable=False,
248 info=dict(description=u"An identifier.", format='identifier'))
249
250 class Encounter(TableBase):
251 u"""Encounters with wild Pokémon.
252
253 Bear with me, here.
254
255 Within a given area in a given game, encounters are differentiated by the
256 "slot" they are in and the state of the game world.
257
258 What the player is doing to get an encounter, such as surfing or walking
259 through tall grass, is called terrain. Each terrain has its own set of
260 encounter slots.
261
262 Within a terrain, slots are defined primarily by rarity. Each slot can
263 also be affected by world conditions; for example, the 20% slot for walking
264 in tall grass is affected by whether a swarm is in effect in that area.
265 "Is there a swarm?" is a condition; "there is a swarm" and "there is not a
266 swarm" are the possible values of this condition.
267
268 A slot (20% walking in grass) and any appropriate world conditions (no
269 swarm) are thus enough to define a specific encounter.
270
271 Well, okay, almost: each slot actually appears twice.
272 """
273
274 __tablename__ = 'encounters'
275 id = Column(Integer, primary_key=True, nullable=False,
276 info=dict(description="A unique ID for this encounter"))
277 version_id = Column(Integer, ForeignKey('versions.id'), nullable=False, autoincrement=False,
278 info=dict(description="The ID of the version this applies to"))
279 location_area_id = Column(Integer, ForeignKey('location_areas.id'), nullable=False, autoincrement=False,
280 info=dict(description="The ID of the location of this encounter"))
281 encounter_slot_id = Column(Integer, ForeignKey('encounter_slots.id'), nullable=False, autoincrement=False,
282 info=dict(description="The ID of the encounter slot, which determines terrain and rarity"))
283 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, autoincrement=False,
284 info=dict(description=u"The ID of the encountered Pokémon"))
285 min_level = Column(Integer, nullable=False, autoincrement=False,
286 info=dict(description=u"The minimum level of the encountered Pokémon"))
287 max_level = Column(Integer, nullable=False, autoincrement=False,
288 info=dict(description=u"The maxmum level of the encountered Pokémon"))
289
290 class EncounterCondition(TableBase, UnofficiallyNamed):
291 u"""A conditions in the game world that affects Pokémon encounters, such as time of day.
292 """
293
294 __tablename__ = 'encounter_conditions'
295 __singlename__ = 'encounter_condition'
296 id = Column(Integer, primary_key=True, nullable=False,
297 info=dict(description="A unique ID for this condition"))
298 identifier = Column(Unicode(64), nullable=False,
299 info=dict(description="An identifier", format='identifier'))
300
301 class EncounterConditionValue(TableBase, UnofficiallyNamed):
302 u"""A possible state for a condition; for example, the state of 'swarm' could be 'swarm' or 'no swarm'.
303 """
304
305 __tablename__ = 'encounter_condition_values'
306 __singlename__ = 'encounter_condition_value'
307 id = Column(Integer, primary_key=True, nullable=False,
308 info=dict(description="A numeric ID"))
309 encounter_condition_id = Column(Integer, ForeignKey('encounter_conditions.id'), primary_key=False, nullable=False, autoincrement=False,
310 info=dict(description="The ID of the encounter condition this is a value of"))
311 identifier = Column(Unicode(64), nullable=False,
312 info=dict(description="An identifier", format='identifier'))
313 is_default = Column(Boolean, nullable=False,
314 info=dict(description='Set if this value is the default state for the condition'))
315
316 class EncounterConditionValueMap(TableBase):
317 u"""Maps encounters to the specific conditions under which they occur.
318 """
319 __tablename__ = 'encounter_condition_value_map'
320 encounter_id = Column(Integer, ForeignKey('encounters.id'), primary_key=True, nullable=False, autoincrement=False,
321 info=dict(description="The ID of the encounter"))
322 encounter_condition_value_id = Column(Integer, ForeignKey('encounter_condition_values.id'), primary_key=True, nullable=False, autoincrement=False,
323 info=dict(description="The ID of the encounter condition value"))
324
325 class EncounterTerrain(TableBase, UnofficiallyNamed):
326 u"""A way the player can enter a wild encounter, e.g., surfing, fishing, or walking through tall grass.
327 """
328
329 __tablename__ = 'encounter_terrain'
330 __singlename__ = __tablename__
331 id = Column(Integer, primary_key=True, nullable=False,
332 info=dict(description="A unique ID for the terrain"))
333 identifier = Column(Unicode(64), nullable=False,
334 info=dict(description="An identifier", format='identifier'))
335
336 class EncounterSlot(TableBase):
337 u"""An abstract "slot" within a terrain, associated with both some set of conditions and a rarity.
338
339 Note that there are two encounters per slot, so the rarities will only add
340 up to 50.
341 """
342
343 __tablename__ = 'encounter_slots'
344 id = Column(Integer, primary_key=True, nullable=False,
345 info=dict(description="A unique ID for this slot"))
346 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False, autoincrement=False,
347 info=dict(description="The ID of the version group this slot is in"))
348 encounter_terrain_id = Column(Integer, ForeignKey('encounter_terrain.id'), primary_key=False, nullable=False, autoincrement=False,
349 info=dict(description="The ID of the terrain"))
350 slot = Column(Integer, nullable=True,
351 info=dict(description="This slot's order for the location and terrain"))
352 rarity = Column(Integer, nullable=False,
353 info=dict(description="The chance of the encounter as a percentage"))
354
355 class EvolutionChain(TableBase):
356 u"""A family of Pokémon that are linked by evolution
357 """
358 __tablename__ = 'evolution_chains'
359 id = Column(Integer, primary_key=True, nullable=False,
360 info=dict(description="A numeric ID"))
361 growth_rate_id = Column(Integer, ForeignKey('growth_rates.id'), nullable=False,
362 info=dict(description="ID of the growth rate for this family"))
363 baby_trigger_item_id = Column(Integer, ForeignKey('items.id'), nullable=True,
364 info=dict(description="Item that a parent must hold while breeding to produce a baby"))
365
366 class EvolutionTrigger(TableBase, UnofficiallyNamed):
367 u"""An evolution type, such as "level" or "trade".
368 """
369 __tablename__ = 'evolution_triggers'
370 __singlename__ = 'evolution_trigger'
371 id = Column(Integer, primary_key=True, nullable=False,
372 info=dict(description="A numeric ID"))
373 identifier = Column(Unicode(16), nullable=False,
374 info=dict(description="An identifier", format='identifier'))
375
376 class Experience(TableBase):
377 u"""EXP needed for a certain level with a certain growth rate
378 """
379 __tablename__ = 'experience'
380 growth_rate_id = Column(Integer, ForeignKey('growth_rates.id'), primary_key=True, nullable=False,
381 info=dict(description="ID of the growth rate"))
382 level = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
383 info=dict(description="The level"))
384 experience = Column(Integer, nullable=False,
385 info=dict(description="The number of EXP points needed to get to that level"))
386
387 class Generation(TableBase, OfficiallyNamed):
388 u"""A Generation of the Pokémon franchise
389 """
390 __tablename__ = 'generations'
391 __singlename__ = 'generation'
392 id = Column(Integer, primary_key=True, nullable=False,
393 info=dict(description="A numeric ID"))
394 main_region_id = Column(Integer, ForeignKey('regions.id'),
395 info=dict(description="ID of the region this generation's main games take place in"))
396 canonical_pokedex_id = Column(Integer, ForeignKey('pokedexes.id'),
397 info=dict(description=u"ID of the Pokédex this generation's main games use by default"))
398 identifier = Column(Unicode(16), nullable=False,
399 info=dict(description=u'An identifier', format='identifier'))
400
401 class GrowthRate(TableBase, UnofficiallyNamed):
402 u"""Growth rate of a Pokémon, i.e. the EXP → level function.
403 """
404 __tablename__ = 'growth_rates'
405 __singlename__ = 'growth_rate'
406 id = Column(Integer, primary_key=True, nullable=False,
407 info=dict(description="A numeric ID"))
408 identifier = Column(Unicode(20), nullable=False,
409 info=dict(description="An identifier", format='identifier'))
410 formula = Column(Unicode(500), nullable=False,
411 info=dict(description="The formula", format='latex'))
412
413 class Item(TableBase, OfficiallyNamed):
414 u"""An Item from the games, like "Poké Ball" or "Bicycle".
415 """
416 __tablename__ = 'items'
417 __singlename__ = 'item'
418 id = Column(Integer, primary_key=True, nullable=False,
419 info=dict(description="A numeric ID"))
420 identifier = Column(Unicode(20), nullable=False,
421 info=dict(description="An identifier", format='identifier'))
422 category_id = Column(Integer, ForeignKey('item_categories.id'), nullable=False,
423 info=dict(description="ID of a category this item belongs to"))
424 cost = Column(Integer, nullable=False,
425 info=dict(description=u"Cost of the item when bought. Items sell for half this price."))
426 fling_power = Column(Integer, nullable=True,
427 info=dict(description=u"Power of the move Fling when used with this item."))
428 fling_effect_id = Column(Integer, ForeignKey('item_fling_effects.id'), nullable=True,
429 info=dict(description=u"ID of the fling-effect of the move Fling when used with this item. Note that these are different from move effects."))
430 short_effect = ProseColumn(Unicode(256), plural='short_effects', nullable=False,
431 info=dict(description="A short summary of the effect", format='plaintext'))
432 effect = ProseColumn(markdown.MarkdownColumn(5120), plural='effects', nullable=False,
433 info=dict(description=u"Detailed description of the item's effect.", format='markdown'))
434
435 @property
436 def appears_underground(self):
437 u"""True if the item appears underground, as specified by the appropriate flag
438 """
439 return any(flag.identifier == u'underground' for flag in self.flags)
440
441 class ItemCategory(TableBase, UnofficiallyNamed):
442 u"""An item category
443 """
444 # XXX: This is fanon, right?
445 __tablename__ = 'item_categories'
446 __singlename__ = 'item_category'
447 id = Column(Integer, primary_key=True, nullable=False,
448 info=dict(description="A numeric ID"))
449 pocket_id = Column(Integer, ForeignKey('item_pockets.id'), nullable=False,
450 info=dict(description="ID of the pocket these items go to"))
451 identifier = Column(Unicode(16), nullable=False,
452 info=dict(description="An identifier", format='identifier'))
453
454 class ItemFlag(TableBase, UnofficiallyNamed):
455 u"""An item attribute such as "consumable" or "holdable".
456 """
457 __tablename__ = 'item_flags'
458 __singlename__ = 'item_flag'
459 id = Column(Integer, primary_key=True, nullable=False,
460 info=dict(description="A numeric ID"))
461 identifier = Column(Unicode(24), nullable=False,
462 info=dict(description="Identifier of the flag", format='identifier'))
463 description = ProseColumn(Unicode(64), plural='descriptions', nullable=False,
464 info=dict(description="Short description of the flag", format='plaintext'))
465
466 class ItemFlagMap(TableBase):
467 u"""Maps an item flag to its item.
468 """
469 __tablename__ = 'item_flag_map'
470 item_id = Column(Integer, ForeignKey('items.id'), primary_key=True, autoincrement=False, nullable=False,
471 info=dict(description="The ID of the item"))
472 item_flag_id = Column(Integer, ForeignKey('item_flags.id'), primary_key=True, autoincrement=False, nullable=False,
473 info=dict(description="The ID of the item flag"))
474
475 class ItemFlavorText(TableBase, LanguageSpecific):
476 u"""An in-game description of an item
477 """
478 __tablename__ = 'item_flavor_text'
479 __singlename__ = 'item_flavor_text'
480 item_id = Column(Integer, ForeignKey('items.id'), primary_key=True, autoincrement=False, nullable=False,
481 info=dict(description="The ID of the item"))
482 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, autoincrement=False, nullable=False,
483 info=dict(description="ID of the version group that sports this text"))
484 flavor_text = Column(Unicode(255), nullable=False,
485 info=dict(description="The flavor text itself", official=True, format='gametext'))
486
487 class ItemFlingEffect(TableBase):
488 u"""An effect of the move Fling when used with a specific item
489 """
490 __tablename__ = 'item_fling_effects'
491 __singlename__ = 'item_fling_effect'
492 id = Column(Integer, primary_key=True, nullable=False,
493 info=dict(description="A numeric ID"))
494 effect = ProseColumn(Unicode(255), plural='effects', nullable=False,
495 info=dict(description="Description of the effect", format='plaintext'))
496
497 class ItemInternalID(TableBase):
498 u"""The internal ID number a game uses for an item
499 """
500 __tablename__ = 'item_internal_ids'
501 item_id = Column(Integer, ForeignKey('items.id'), primary_key=True, autoincrement=False, nullable=False,
502 info=dict(description="The database ID of the item"))
503 generation_id = Column(Integer, ForeignKey('generations.id'), primary_key=True, autoincrement=False, nullable=False,
504 info=dict(description="ID of the generation of games"))
505 internal_id = Column(Integer, nullable=False,
506 info=dict(description="Internal ID of the item in the generation"))
507
508 class ItemPocket(TableBase, OfficiallyNamed):
509 u"""A pocket that categorizes items
510 """
511 __tablename__ = 'item_pockets'
512 __singlename__ = 'item_pocket'
513 id = Column(Integer, primary_key=True, nullable=False,
514 info=dict(description="A numeric ID"))
515 identifier = Column(Unicode(16), nullable=False,
516 info=dict(description="An identifier of this pocket", format='identifier'))
517
518 class Language(TableBase, OfficiallyNamed):
519 u"""A language the Pokémon games have been transleted into
520 """
521 __tablename__ = 'languages'
522 __singlename__ = 'language'
523 id = Column(Integer, primary_key=True, nullable=False,
524 info=dict(description="A numeric ID"))
525 iso639 = Column(Unicode(2), nullable=False,
526 info=dict(description="The two-letter code of the country where this language is spoken. Note that it is not unique.", format='identifier'))
527 iso3166 = Column(Unicode(2), nullable=False,
528 info=dict(description="The two-letter code of the language. Note that it is not unique.", format='identifier'))
529 identifier = Column(Unicode(16), nullable=False,
530 info=dict(description="An identifier", format='identifier'))
531 official = Column(Boolean, nullable=False, index=True,
532 info=dict(description=u"True iff games are produced in the language."))
533 order = Column(Integer, nullable=True,
534 info=dict(description=u"Order for sorting in foreign name lists."))
535
536 # Languages compare equal to its identifier, so a dictionary of
537 # translations, with a Language as the key, can be indexed by the identifier
538 def __eq__(self, other):
539 try:
540 return (
541 self is other or
542 self.identifier == other or
543 self.identifier == other.identifier
544 )
545 except AttributeError:
546 return NotImplemented
547
548 def __ne__(self, other):
549 return not (self == other)
550
551 def __hash__(self):
552 return hash(self.identifier)
553
554 class Location(TableBase, OfficiallyNamed):
555 u"""A place in the Pokémon world
556 """
557 __tablename__ = 'locations'
558 __singlename__ = 'location'
559 id = Column(Integer, primary_key=True, nullable=False,
560 info=dict(description="A numeric ID"))
561 region_id = Column(Integer, ForeignKey('regions.id'),
562 info=dict(description="ID of the region this location is in"))
563 identifier = Column(Unicode(64), nullable=False,
564 info=dict(description="An identifier", format='identifier'))
565
566 class LocationArea(TableBase, UnofficiallyNamed):
567 u"""A sub-area of a location
568 """
569 __tablename__ = 'location_areas'
570 __singlename__ = 'location_area'
571 id = Column(Integer, primary_key=True, nullable=False,
572 info=dict(description="A numeric ID"))
573 location_id = Column(Integer, ForeignKey('locations.id'), nullable=False,
574 info=dict(description="ID of the location this area is part of"))
575 internal_id = Column(Integer, nullable=False,
576 info=dict(description="ID the games ude for this area"))
577 identifier = Column(Unicode(64), nullable=True,
578 info=dict(description="An identifier", format='identifier'))
579
580 class LocationAreaEncounterRate(TableBase):
581 # XXX: What's this exactly? Someone add the docstring & revise the descriptions
582 __tablename__ = 'location_area_encounter_rates'
583 location_area_id = Column(Integer, ForeignKey('location_areas.id'), primary_key=True, nullable=False, autoincrement=False,
584 info=dict(description="ID of the area"))
585 encounter_terrain_id = Column(Integer, ForeignKey('encounter_terrain.id'), primary_key=True, nullable=False, autoincrement=False,
586 info=dict(description="ID of the terrain"))
587 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, autoincrement=False,
588 info=dict(description="ID of the version"))
589 rate = Column(Integer, nullable=True,
590 info=dict(description="The encounter rate")) # units?
591
592 class LocationInternalID(TableBase):
593 u"""IDs the games use internally for locations
594 """
595 __tablename__ = 'location_internal_ids'
596 location_id = Column(Integer, ForeignKey('locations.id'), nullable=False, primary_key=True,
597 info=dict(description="Database ID of the locaion"))
598 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False, primary_key=True,
599 info=dict(description="ID of the generation this entry to"))
600 internal_id = Column(Integer, nullable=False,
601 info=dict(description="Internal game ID of the location"))
602
603 class Machine(TableBase):
604 u"""A TM or HM; numbered item that can teach a move to a Pokémon
605 """
606 __tablename__ = 'machines'
607 machine_number = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
608 info=dict(description="Number of the machine for TMs, or 100 + the munber for HMs"))
609 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False,
610 info=dict(description="Versions this entry applies to"))
611 item_id = Column(Integer, ForeignKey('items.id'), nullable=False,
612 info=dict(description="ID of the corresponding Item"))
613 move_id = Column(Integer, ForeignKey('moves.id'), nullable=False,
614 info=dict(description="ID of the taught move"))
615
616 @property
617 def is_hm(self):
618 u"""True if this machine is a HM, False if it's a TM
619 """
620 return self.machine_number >= 100
621
622 class MoveBattleStyle(TableBase, UnofficiallyNamed):
623 u"""A battle style of a move""" # XXX: Explain better
624 __tablename__ = 'move_battle_styles'
625 __singlename__ = 'move_battle_style'
626 id = Column(Integer, primary_key=True, nullable=False,
627 info=dict(description="A numeric ID"))
628 identifier = Column(Unicode(8), nullable=False,
629 info=dict(description="An identifier", format='identifier'))
630
631 class MoveEffectCategory(TableBase, UnofficiallyNamed):
632 u"""Category of a move effect
633 """
634 __tablename__ = 'move_effect_categories'
635 __singlename__ = 'move_effect_category'
636 id = Column(Integer, primary_key=True, nullable=False,
637 info=dict(description="A numeric ID"))
638 identifier = Column(Unicode(64), nullable=False,
639 info=dict(description="An identifier", format='identifier'))
640 can_affect_user = Column(Boolean, nullable=False,
641 info=dict(description="Set if the user can be affected"))
642
643 class MoveEffectCategoryMap(TableBase):
644 u"""Maps a move effect category to a move effect
645 """
646 __tablename__ = 'move_effect_category_map'
647 move_effect_id = Column(Integer, ForeignKey('move_effects.id'), primary_key=True, nullable=False,
648 info=dict(description="ID of the move effect"))
649 move_effect_category_id = Column(Integer, ForeignKey('move_effect_categories.id'), primary_key=True, nullable=False,
650 info=dict(description="ID of the category"))
651 affects_user = Column(Boolean, primary_key=True, nullable=False,
652 info=dict(description="Set if the user is affected"))
653
654 class MoveDamageClass(TableBase, UnofficiallyNamed):
655 u"""Any of the damage classes moves can have, i.e. physical, special, or non-damaging.
656 """
657 __tablename__ = 'move_damage_classes'
658 __singlename__ = 'move_damage_class'
659 id = Column(Integer, primary_key=True, nullable=False,
660 info=dict(description="A numeric ID"))
661 identifier = Column(Unicode(16), nullable=False,
662 info=dict(description="An identifier", format='identifier'))
663 description = ProseColumn(Unicode(64), plural='descriptions', nullable=False,
664 info=dict(description="A description of the class", format='plaintext'))
665
666 class MoveEffect(TableBase):
667 u"""An effect of a move
668 """
669 __tablename__ = 'move_effects'
670 __singlename__ = 'move_effect'
671 id = Column(Integer, primary_key=True, nullable=False,
672 info=dict(description="A numeric ID"))
673 short_effect = ProseColumn(Unicode(256), plural='short_effects', nullable=False,
674 info=dict(description="A short summary of the effect", format='plaintext'))
675 effect = ProseColumn(Unicode(5120), plural='effects', nullable=False,
676 info=dict(description="A detailed description of the effect", format='plaintext'))
677
678 class MoveEffectChangelog(TableBase):
679 """History of changes to move effects across main game versions."""
680 __tablename__ = 'move_effect_changelog'
681 __singlename__ = 'move_effect_changelog'
682 id = Column(Integer, primary_key=True, nullable=False,
683 info=dict(description="A numeric ID"))
684 effect_id = Column(Integer, ForeignKey('move_effects.id'), primary_key=True, nullable=False,
685 info=dict(description="The ID of the effect that changed"))
686 changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
687 info=dict(description="The ID of the version group in which the effect changed"))
688 effect = ProseColumn(markdown.MarkdownColumn(512), plural='effects', nullable=False,
689 info=dict(description="A description of the old behavior", format='markdown'))
690
691 class MoveFlag(TableBase):
692 u"""Maps a move flag to a move
693 """
694 # XXX: Other flags have a ___Flag class for the actual flag and ___FlagMap for the map,
695 # these, somewhat confusingly, have MoveFlagType and MoveFlag
696 __tablename__ = 'move_flags'
697 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
698 info=dict(description="ID of the move"))
699 move_flag_type_id = Column(Integer, ForeignKey('move_flag_types.id'), primary_key=True, nullable=False, autoincrement=False,
700 info=dict(description="ID of the flag"))
701
702 class MoveFlagType(TableBase, UnofficiallyNamed):
703 u"""A Move attribute such as "snatchable" or "contact".
704 """
705 __tablename__ = 'move_flag_types'
706 __singlename__ = 'move_flag_type'
707 id = Column(Integer, primary_key=True, nullable=False,
708 info=dict(description="A numeric ID"))
709 identifier = Column(Unicode(32), nullable=False,
710 info=dict(description="A short identifier for the flag", format='identifier'))
711 description = ProseColumn(markdown.MarkdownColumn(128), plural='descriptions', nullable=False,
712 info=dict(description="A short description of the flag", format='markdown'))
713
714 class MoveFlavorText(TableBase, LanguageSpecific):
715 u"""In-game description of a move
716 """
717 __tablename__ = 'move_flavor_text'
718 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
719 info=dict(description="ID of the move"))
720 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False,
721 info=dict(description="ID of the version group this text appears in"))
722 flavor_text = Column(Unicode(255), nullable=False,
723 info=dict(description="The flavor text", official=True, format='gametext'))
724
725 class MoveMeta(TableBase):
726 u"""Metadata for move effects, sorta-kinda ripped straight from the game"""
727 __tablename__ = 'move_meta'
728 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
729 info=dict(description="A numeric ID"))
730 meta_category_id = Column(Integer, ForeignKey('move_meta_categories.id'), nullable=False,
731 info=dict(description="ID of the move category"))
732 meta_ailment_id = Column(Integer, ForeignKey('move_meta_ailments.id'), nullable=False,
733 info=dict(description="ID of the caused ailment"))
734 min_hits = Column(Integer, nullable=True, index=True,
735 info=dict(description="Minimum number of hits per use"))
736 max_hits = Column(Integer, nullable=True, index=True,
737 info=dict(description="Maximum number of hits per use"))
738 min_turns = Column(Integer, nullable=True, index=True,
739 info=dict(description="Minimum number of turns the user is forced to use the move"))
740 max_turns = Column(Integer, nullable=True, index=True,
741 info=dict(description="Maximum number of turns the user is forced to use the move"))
742 recoil = Column(Integer, nullable=False, index=True,
743 info=dict(description="Recoil damage, in percent of damage done"))
744 healing = Column(Integer, nullable=False, index=True,
745 info=dict(description="Healing, in percent of user's max HP"))
746 crit_rate = Column(Integer, nullable=False, index=True,
747 info=dict(description="Critical hit rate bonus"))
748 ailment_chance = Column(Integer, nullable=False, index=True,
749 info=dict(description="Chance to cause an ailment, in percent"))
750 flinch_chance = Column(Integer, nullable=False, index=True,
751 info=dict(description="Chance to cause flinching, in percent"))
752 stat_chance = Column(Integer, nullable=False, index=True,
753 info=dict(description="Chance to cause a stat change, in percent"))
754
755 class MoveMetaAilment(TableBase, OfficiallyNamed):
756 u"""Common status ailments moves can inflict on a single Pokémon, including
757 major ailments like paralysis and minor ailments like trapping.
758 """
759 __tablename__ = 'move_meta_ailments'
760 __singlename__ = 'move_meta_ailment'
761 id = Column(Integer, primary_key=True, nullable=False,
762 info=dict(description="A numeric ID"))
763 identifier = Column(Unicode(24), nullable=False,
764 info=dict(description="An identifier", format='identifier'))
765
766 class MoveMetaCategory(TableBase):
767 u"""Very general categories that loosely group move effects."""
768 __tablename__ = 'move_meta_categories'
769 __singlename__ = 'move_meta_category'
770 id = Column(Integer, primary_key=True, nullable=False,
771 info=dict(description="A numeric ID"))
772 description = ProseColumn(Unicode(64), plural='descriptions', nullable=False,
773 info=dict(description="A description of the category"))
774
775 class MoveMetaStatChange(TableBase):
776 u"""Stat changes moves (may) make."""
777 __tablename__ = 'move_meta_stat_changes'
778 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
779 info=dict(description="ID of the move"))
780 stat_id = Column(Integer, ForeignKey('stats.id'), primary_key=True, nullable=False, autoincrement=False,
781 info=dict(description="ID of the stat"))
782 change = Column(Integer, nullable=False, index=True,
783 info=dict(description="Amount of increase/decrease, in stages"))
784
785 class MoveTarget(TableBase, UnofficiallyNamed):
786 u"""Targetting or "range" of a move, e.g. "Affects all opponents" or "Affects user".
787 """
788 __tablename__ = 'move_targets'
789 __singlename__ = 'move_target'
790 id = Column(Integer, primary_key=True, nullable=False,
791 info=dict(description="A numeric ID"))
792 identifier = Column(Unicode(32), nullable=False,
793 info=dict(description="An identifier", format='identifier'))
794 description = ProseColumn(Unicode(128), plural='descriptions', nullable=False,
795 info=dict(description="A description", format='plaintext'))
796
797 class Move(TableBase, OfficiallyNamed):
798 u"""A Move: technique or attack a Pokémon can learn to use
799 """
800 __tablename__ = 'moves'
801 __singlename__ = 'move'
802 id = Column(Integer, primary_key=True, nullable=False,
803 info=dict(description="A numeric ID"))
804 identifier = Column(Unicode(24), nullable=False,
805 info=dict(description="An identifier", format='identifier'))
806 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
807 info=dict(description="ID of the generation this move first appeared in"))
808 type_id = Column(Integer, ForeignKey('types.id'), nullable=False,
809 info=dict(description="ID of the move's elemental type"))
810 power = Column(SmallInteger, nullable=False,
811 info=dict(description="Base power of the move"))
812 pp = Column(SmallInteger, nullable=True,
813 info=dict(description="Base PP (Power Points) of the move, nullable if not applicable (e.g. Struggle and Shadow moves)."))
814 accuracy = Column(SmallInteger, nullable=True,
815 info=dict(description="Accuracy of the move; NULL means it never misses"))
816 priority = Column(SmallInteger, nullable=False,
817 info=dict(description="The move's priority bracket"))
818 target_id = Column(Integer, ForeignKey('move_targets.id'), nullable=False,
819 info=dict(description="ID of the target (range) of the move"))
820 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=False,
821 info=dict(description="ID of the damage class (physical/special) of the move"))
822 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=False,
823 info=dict(description="ID of the move's effect"))
824 effect_chance = Column(Integer, nullable=True,
825 info=dict(description="The chance for a secondary effect. What this is a chance of is specified by the move's effect."))
826 contest_type_id = Column(Integer, ForeignKey('contest_types.id'), nullable=True,
827 info=dict(description="ID of the move's Contest type (e.g. cool or smart)"))
828 contest_effect_id = Column(Integer, ForeignKey('contest_effects.id'), nullable=True,
829 info=dict(description="ID of the move's Contest effect"))
830 super_contest_effect_id = Column(Integer, ForeignKey('super_contest_effects.id'), nullable=True,
831 info=dict(description="ID of the move's Super Contest effect"))
832
833 class MoveChangelog(TableBase):
834 """History of changes to moves across main game versions."""
835 __tablename__ = 'move_changelog'
836 __singlename__ = 'move_changelog'
837 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False,
838 info=dict(description="ID of the move that changed"))
839 changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
840 info=dict(description="ID of the version group in which the move changed"))
841 type_id = Column(Integer, ForeignKey('types.id'), nullable=True,
842 info=dict(description="Prior type of the move, or NULL if unchanged"))
843 power = Column(SmallInteger, nullable=True,
844 info=dict(description="Prior base power of the move, or NULL if unchanged"))
845 pp = Column(SmallInteger, nullable=True,
846 info=dict(description="Prior base PP of the move, or NULL if unchanged"))
847 accuracy = Column(SmallInteger, nullable=True,
848 info=dict(description="Prior accuracy of the move, or NULL if unchanged"))
849 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=True,
850 info=dict(description="Prior ID of the effect, or NULL if unchanged"))
851 effect_chance = Column(Integer, nullable=True,
852 info=dict(description="Prior effect chance, or NULL if unchanged"))
853
854 class Nature(TableBase, OfficiallyNamed):
855 u"""A nature a Pokémon can have, such as Calm or Brave
856 """
857 __tablename__ = 'natures'
858 __singlename__ = 'nature'
859 id = Column(Integer, primary_key=True, nullable=False,
860 info=dict(description="A numeric ID"))
861 identifier = Column(Unicode(8), nullable=False,
862 info=dict(description="An identifier", format='identifier'))
863 decreased_stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
864 info=dict(description="ID of the stat that this nature decreases by 10% (if decreased_stat_id is the same, the effects cancel out)"))
865 increased_stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
866 info=dict(description="ID of the stat that this nature increases by 10% (if decreased_stat_id is the same, the effects cancel out)"))
867 hates_flavor_id = Column(Integer, ForeignKey('contest_types.id'), nullable=False,
868 info=dict(description=u"ID of the Berry flavor the Pokémon hates (if likes_flavor_id is the same, the effects cancel out)"))
869 likes_flavor_id = Column(Integer, ForeignKey('contest_types.id'), nullable=False,
870 info=dict(description=u"ID of the Berry flavor the Pokémon likes (if hates_flavor_id is the same, the effects cancel out)"))
871
872 @property
873 def is_neutral(self):
874 u"""Returns True iff this nature doesn't alter a Pokémon's stats,
875 bestow taste preferences, etc.
876 """
877 return self.increased_stat_id == self.decreased_stat_id
878
879 class NatureBattleStylePreference(TableBase):
880 u"""Battle Palace move preference
881
882 Specifies how likely a Pokémon with a specific Nature is to use a move of
883 a particular battl style in Battle Palace or Battle Tent
884 """
885 __tablename__ = 'nature_battle_style_preferences'
886 nature_id = Column(Integer, ForeignKey('natures.id'), primary_key=True, nullable=False,
887 info=dict(description=u"ID of the Pokémon's nature"))
888 move_battle_style_id = Column(Integer, ForeignKey('move_battle_styles.id'), primary_key=True, nullable=False,
889 info=dict(description="ID of the battle style"))
890 low_hp_preference = Column(Integer, nullable=False,
891 info=dict(description=u"Chance of using the move, in percent, if HP is under ½"))
892 high_hp_preference = Column(Integer, nullable=False,
893 info=dict(description=u"Chance of using the move, in percent, if HP is over ½"))
894
895 class NaturePokeathlonStat(TableBase):
896 u"""Specifies how a Nature affects a Pokéathlon stat
897 """
898 __tablename__ = 'nature_pokeathlon_stats'
899 nature_id = Column(Integer, ForeignKey('natures.id'), primary_key=True, nullable=False,
900 info=dict(description="ID of the nature"))
901 pokeathlon_stat_id = Column(Integer, ForeignKey('pokeathlon_stats.id'), primary_key=True, nullable=False,
902 info=dict(description="ID of the stat"))
903 max_change = Column(Integer, nullable=False,
904 info=dict(description="Maximum change"))
905
906 class PokeathlonStat(TableBase, OfficiallyNamed):
907 u"""A Pokéathlon stat, such as "Stamina" or "Jump".
908 """
909 __tablename__ = 'pokeathlon_stats'
910 __singlename__ = 'pokeathlon_stat'
911 id = Column(Integer, primary_key=True, nullable=False,
912 info=dict(description="A numeric ID"))
913 identifier = Column(Unicode(8), nullable=False,
914 info=dict(description="An identifier", format='identifier'))
915
916 class Pokedex(TableBase, UnofficiallyNamed):
917 u"""A collection of Pokémon species ordered in a particular way
918 """
919 __tablename__ = 'pokedexes'
920 __singlename__ = 'pokedex'
921 id = Column(Integer, primary_key=True, nullable=False,
922 info=dict(description="A numeric ID"))
923 region_id = Column(Integer, ForeignKey('regions.id'), nullable=True,
924 info=dict(description=u"ID of the region this Pokédex is used in, or None if it's global"))
925 identifier = Column(Unicode(16), nullable=False,
926 info=dict(description=u"An identifier", format='identifier'))
927 description = ProseColumn(Unicode(512), plural='descriptions', nullable=False,
928 info=dict(description=u"A longer description of the Pokédex", format='plaintext'))
929
930 class Pokemon(TableBase, OfficiallyNamed):
931 u"""A species of Pokémon. The core to this whole mess.
932 """
933 __tablename__ = 'pokemon'
934 __singlename__ = 'pokemon'
935 id = Column(Integer, primary_key=True, nullable=False,
936 info=dict(description=u"A numeric ID"))
937 identifier = Column(Unicode(20), nullable=False,
938 info=dict(description=u"An identifier", format='identifier'))
939 generation_id = Column(Integer, ForeignKey('generations.id'),
940 info=dict(description=u"ID of the generation this species first appeared in"))
941 evolution_chain_id = Column(Integer, ForeignKey('evolution_chains.id'),
942 info=dict(description=u"ID of the species' evolution chain (a.k.a. family)"))
943 height = Column(Integer, nullable=False,
944 info=dict(description=u"The height of the Pokémon, in decimeters (tenths of a meter)"))
945 weight = Column(Integer, nullable=False,
946 info=dict(description=u"The weight of the Pokémon, in tenths of a kilogram (decigrams)"))
947 species = TextColumn(Unicode(16), nullable=False, plural='species_names',
948 info=dict(description=u'The short flavor text, such as "Seed" or "Lizard"; usually affixed with the word "Pokémon"',
949 official=True, format='plaintext'))
950 color_id = Column(Integer, ForeignKey('pokemon_colors.id'), nullable=False,
951 info=dict(description=u"ID of this Pokémon's Pokédex color, as used for a gimmick search function in the games."))
952 pokemon_shape_id = Column(Integer, ForeignKey('pokemon_shapes.id'), nullable=True,
953 info=dict(description=u"ID of this Pokémon's body shape, as used for a gimmick search function in the games."))
954 habitat_id = Column(Integer, ForeignKey('pokemon_habitats.id'), nullable=True,
955 info=dict(description=u"ID of this Pokémon's habitat, as used for a gimmick search function in the games."))
956 gender_rate = Column(Integer, nullable=False,
957 info=dict(description=u"The chance of this Pokémon being female, in eighths; or -1 for genderless"))
958 capture_rate = Column(Integer, nullable=False,
959 info=dict(description=u"The base capture rate; up to 255"))
960 base_experience = Column(Integer, nullable=False,
961 info=dict(description=u"The base EXP gained when defeating this Pokémon")) # XXX: Is this correct?
962 base_happiness = Column(Integer, nullable=False,
963 info=dict(description=u"The tameness when caught by a normal ball"))
964 is_baby = Column(Boolean, nullable=False,
965 info=dict(description=u"True iff the Pokémon is a baby, i.e. a lowest-stage Pokémon that cannot breed but whose evolved form can."))
966 hatch_counter = Column(Integer, nullable=False,
967 info=dict(description=u"Initial hatch counter: one must walk 255 × (hatch_counter + 1) steps before this Pokémon's egg hatches, unless utilizing bonuses like Flame Body's"))
968 has_gender_differences = Column(Boolean, nullable=False,
969 info=dict(description=u"Set iff the species exhibits enough sexual dimorphism to have separate sets of sprites in Gen IV and beyond."))
970 order = Column(Integer, nullable=False, index=True,
971 info=dict(description=u"Order for sorting. Almost national order, except families and forms are grouped together."))
972
973 ### Stuff to handle alternate Pokémon forms
974
975 @property
976 def form(self):
977 u"""Returns the Pokémon's form, using its default form as fallback."""
978
979 return self.unique_form or self.default_form
980
981 @property
982 def is_base_form(self):
983 u"""Returns True iff the Pokémon is the base form for its species,
984 e.g. Land Shaymin.
985 """
986
987 return self.unique_form is None or self.unique_form.is_default
988
989 @property
990 def form_name(self):
991 u"""Returns the Pokémon's form name if it represents a particular form
992 and that form has a name, or None otherwise.
993 """
994
995 # If self.unique_form is None, the short-circuit "and" will go ahead
996 # and return that. Otherwise, it'll return the form's name, which may
997 # also be None.
998 return self.unique_form and self.unique_form.name
999
1000 @property
1001 def full_name(self):
1002 u"""Returns the Pokémon's name, including its form if applicable."""
1003
1004 if self.form_name:
1005 return u'{0} {1}'.format(self.form_name, self.name)
1006 else:
1007 return self.name
1008
1009 @property
1010 def normal_form(self):
1011 u"""Returns the normal form for this Pokémon; i.e., this will return
1012 regular Deoxys when called on any Deoxys form.
1013 """
1014
1015 if self.unique_form:
1016 return self.unique_form.form_base_pokemon
1017 return self
1018
1019 ### Not forms!
1020
1021 def stat(self, stat_name):
1022 u"""Returns a PokemonStat record for the given stat name (or Stat row
1023 object). Uses the normal has-many machinery, so all the stats are
1024 effectively cached.
1025 """
1026 if isinstance(stat_name, Stat):
1027 stat_name = stat_name.name
1028
1029 for pokemon_stat in self.stats:
1030 if pokemon_stat.stat.name == stat_name:
1031 return pokemon_stat
1032
1033 raise KeyError(u'No stat named %s' % stat_name)
1034
1035 @property
1036 def better_damage_class(self):
1037 u"""Returns the MoveDamageClass that this Pokémon is best suited for,
1038 based on its attack stats.
1039
1040 If the attack stats are about equal (within 5), returns None. The
1041 value None, not the damage class called 'None'.
1042 """
1043 phys = self.stat(u'Attack')
1044 spec = self.stat(u'Special Attack')
1045
1046 diff = phys.base_stat - spec.base_stat
1047
1048 if diff > 5:
1049 return phys.stat.damage_class
1050 elif diff < -5:
1051 return spec.stat.damage_class
1052 else:
1053 return None
1054
1055 class PokemonAbility(TableBase):
1056 u"""Maps an ability to a Pokémon that can have it
1057 """
1058 __tablename__ = 'pokemon_abilities'
1059 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1060 info=dict(description=u"ID of the Pokémon"))
1061 ability_id = Column(Integer, ForeignKey('abilities.id'), nullable=False,
1062 info=dict(description=u"ID of the ability"))
1063 # XXX having both a method and a slot is kind of gross. "slot" is a
1064 # misnomer, anyway: duplicate abilities don't appear in slot 2.
1065 # Probably should replace that with "order".
1066 is_dream = Column(Boolean, nullable=False, index=True,
1067 info=dict(description=u"Whether this is a Dream World ability"))
1068 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1069 info=dict(description=u"The ability slot, i.e. 1 or 2 for gen. IV"))
1070
1071 class PokemonColor(TableBase, OfficiallyNamed):
1072 u"""The "Pokédex color" of a Pokémon species. Usually based on the Pokémon's color.
1073 """
1074 __tablename__ = 'pokemon_colors'
1075 __singlename__ = 'pokemon_color'
1076 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1077 info=dict(description=u"ID of the Pokémon"))
1078 identifier = Column(Unicode(6), nullable=False,
1079 info=dict(description=u"An identifier", format='identifier'))
1080
1081 class PokemonDexNumber(TableBase):
1082 u"""The number of a Pokémon in a particular Pokédex (e.g. Jigglypuff is #138 in Hoenn's 'dex)
1083 """
1084 __tablename__ = 'pokemon_dex_numbers'
1085 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1086 info=dict(description=u"ID of the Pokémon"))
1087 pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), primary_key=True, nullable=False, autoincrement=False,
1088 info=dict(description=u"ID of the Pokédex"))
1089 pokedex_number = Column(Integer, nullable=False,
1090 info=dict(description=u"Number of the Pokémon in that the Pokédex"))
1091
1092 class PokemonEggGroup(TableBase):
1093 u"""Maps an Egg group to a Pokémon; each Pokémon belongs to one or two egg groups
1094 """
1095 __tablename__ = 'pokemon_egg_groups'
1096 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1097 info=dict(description=u"ID of the Pokémon"))
1098 egg_group_id = Column(Integer, ForeignKey('egg_groups.id'), primary_key=True, nullable=False, autoincrement=False,
1099 info=dict(description=u"ID of the egg group"))
1100
1101 class PokemonEvolution(TableBase):
1102 u"""A required action ("trigger") and the conditions under which the trigger
1103 must occur to cause a Pokémon to evolve.
1104
1105 Any condition may be null if it does not apply for a particular Pokémon.
1106 """
1107 __tablename__ = 'pokemon_evolution'
1108 from_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False,
1109 info=dict(description=u"The ID of the pre-evolution Pokémon."))
1110 to_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1111 info=dict(description=u"The ID of the post-evolution Pokémon."))
1112 evolution_trigger_id = Column(Integer, ForeignKey('evolution_triggers.id'), nullable=False,
1113 info=dict(description=u"The ID of the evolution trigger."))
1114 trigger_item_id = Column(Integer, ForeignKey('items.id'), nullable=True,
1115 info=dict(description=u"The ID of the item that must be used on the Pokémon."))
1116 minimum_level = Column(Integer, nullable=True,
1117 info=dict(description=u"The minimum level for the Pokémon."))
1118 gender = Column(Enum('male', 'female', name='pokemon_evolution_gender'), nullable=True,
1119 info=dict(description=u"The Pokémon's required gender, or None if gender doesn't matter"))
1120 location_id = Column(Integer, ForeignKey('locations.id'), nullable=True,
1121 info=dict(description=u"The ID of the location the evolution must be triggered at."))
1122 held_item_id = Column(Integer, ForeignKey('items.id'), nullable=True,
1123 info=dict(description=u"The ID of the item the Pokémon must hold."))
1124 time_of_day = Column(Enum('day', 'night', name='pokemon_evolution_time_of_day'), nullable=True,
1125 info=dict(description=u"The required time of day."))
1126 known_move_id = Column(Integer, ForeignKey('moves.id'), nullable=True,
1127 info=dict(description=u"The ID of the move the Pokémon must know."))
1128 minimum_happiness = Column(Integer, nullable=True,
1129 info=dict(description=u"The minimum happiness value the Pokémon must have."))
1130 minimum_beauty = Column(Integer, nullable=True,
1131 info=dict(description=u"The minimum Beauty value the Pokémon must have."))
1132 relative_physical_stats = Column(Integer, nullable=True,
1133 info=dict(description=u"The required relation between the Pokémon's Attack and Defense stats, as sgn(atk-def)."))
1134 party_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=True,
1135 info=dict(description=u"The ID of the Pokémon that must be present in the party."))
1136 trade_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=True,
1137 info=dict(description=u"The ID of the Pokémon for which this Pokémon must be traded."))
1138
1139 class PokemonFlavorText(TableBase, LanguageSpecific):
1140 u"""In-game Pokédex descrption of a Pokémon.
1141 """
1142 __tablename__ = 'pokemon_flavor_text'
1143 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1144 info=dict(description=u"ID of the Pokémon"))
1145 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False,
1146 info=dict(description=u"ID of the version that has this flavor text"))
1147 flavor_text = Column(Unicode(255), nullable=False,
1148 info=dict(description=u"ID of the version that has this flavor text", official=True, format='gametext'))
1149
1150 class PokemonForm(TableBase, OfficiallyNamed):
1151 u"""An individual form of a Pokémon.
1152
1153 Pokémon that do not have separate forms are still given a single row to
1154 represent their single form.
1155 """
1156 __tablename__ = 'pokemon_forms'
1157 __singlename__ = 'pokemon_form'
1158 id = Column(Integer, primary_key=True, nullable=False,
1159 info=dict(description=u'A unique ID for this form.'))
1160 identifier = Column(Unicode(16), nullable=True,
1161 info=dict(description=u"An identifier", format='identifier'))
1162 form_base_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, autoincrement=False,
1163 info=dict(description=u'The ID of the base Pokémon for this form.'))
1164 unique_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), autoincrement=False,
1165 info=dict(description=u'The ID of a Pokémon that represents specifically this form, for Pokémon with functionally-different forms like Wormadam.'))
1166 introduced_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), autoincrement=False,
1167 info=dict(description=u'The ID of the version group in which this form first appeared.'))
1168 is_default = Column(Boolean, nullable=False,
1169 info=dict(description=u'Set for exactly one form used as the default for each species.'))
1170 order = Column(Integer, nullable=False, autoincrement=False,
1171 info=dict(description=u'The order in which forms should be sorted. Multiple forms may have equal order, in which case they should fall back on sorting by name.'))
1172
1173 @property
1174 def pokemon(self):
1175 u"""Returns the Pokémon for this form, using the form base as fallback.
1176 """
1177
1178 return self.unique_pokemon or self.form_base_pokemon
1179
1180 @property
1181 def full_name(self):
1182 u"""Returns the full name of this form, e.g. "Plant Cloak"."""
1183
1184 if not self.name:
1185 return None
1186 elif self.form_group and self.form_group.term:
1187 return u'{0} {1}'.format(self.name, self.form_group.term)
1188 else:
1189 return self.name
1190
1191 @property
1192 def pokemon_name(self):
1193 u"""Returns the name of this Pokémon with this form, e.g. "Plant
1194 Burmy".
1195 """
1196
1197 if self.name:
1198 return u'{0} {1}'.format(self.name, self.form_base_pokemon.name)
1199 else:
1200 return self.form_base_pokemon.name
1201
1202 class PokemonFormGroup(TableBase):
1203 u"""Information about a Pokémon's forms as a group."""
1204 __tablename__ = 'pokemon_form_groups'
1205 __singlename__ = 'pokemon_form_group'
1206 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1207 info=dict(description=u"ID of the base form Pokémon"))
1208 term = ProseColumn(Unicode(16), plural='terms', nullable=True,
1209 info=dict(description=u"The term for this Pokémon's forms, e.g. \"Cloak\" for Burmy or \"Forme\" for Deoxys.", official=True, format='plaintext'))
1210 is_battle_only = Column(Boolean, nullable=False,
1211 info=dict(description=u"Set iff the forms only change in battle"))
1212 description = ProseColumn(markdown.MarkdownColumn(1024), plural='descriptions', nullable=False,
1213 info=dict(description=u"Description of how the forms work", format='markdown'))
1214 PokemonFormGroup.id = PokemonFormGroup.pokemon_id
1215
1216 class PokemonFormPokeathlonStat(TableBase):
1217 u"""A Pokémon form's performance in one Pokéathlon stat."""
1218 __tablename__ = 'pokemon_form_pokeathlon_stats'
1219 pokemon_form_id = Column(Integer, ForeignKey('pokemon_forms.id'), primary_key=True, nullable=False, autoincrement=False,
1220 info=dict(description=u'The ID of the Pokémon form.'))
1221 pokeathlon_stat_id = Column(Integer, ForeignKey('pokeathlon_stats.id'), primary_key=True, nullable=False, autoincrement=False,
1222 info=dict(description=u'The ID of the Pokéathlon stat.'))
1223 minimum_stat = Column(Integer, nullable=False, autoincrement=False,
1224 info=dict(description=u'The minimum value for this stat for this Pokémon form.'))
1225 base_stat = Column(Integer, nullable=False, autoincrement=False,
1226 info=dict(description=u'The default value for this stat for this Pokémon form.'))
1227 maximum_stat = Column(Integer, nullable=False, autoincrement=False,
1228 info=dict(description=u'The maximum value for this stat for this Pokémon form.'))
1229
1230 class PokemonHabitat(TableBase, OfficiallyNamed):
1231 u"""The habitat of a Pokémon, as given in the FireRed/LeafGreen version Pokédex
1232 """
1233 __tablename__ = 'pokemon_habitats'
1234 __singlename__ = 'pokemon_habitat'
1235 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1236 info=dict(description=u"A numeric ID"))
1237 identifier = Column(Unicode(16), nullable=False,
1238 info=dict(description=u"An identifier", format='identifier'))
1239
1240 class PokemonInternalID(TableBase):
1241 u"""The number of a Pokémon a game uses internally
1242 """
1243 __tablename__ = 'pokemon_internal_ids'
1244 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, autoincrement=False, nullable=False,
1245 info=dict(description=u"Database ID of the Pokémon"))
1246 generation_id = Column(Integer, ForeignKey('generations.id'), primary_key=True, autoincrement=False, nullable=False,
1247 info=dict(description=u"Database ID of the generation"))
1248 internal_id = Column(Integer, nullable=False,
1249 info=dict(description=u"Internal ID the generation's games use for the Pokémon"))
1250
1251 class PokemonItem(TableBase):
1252 u"""Record of an item a Pokémon can hold in the wild
1253 """
1254 __tablename__ = 'pokemon_items'
1255 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1256 info=dict(description=u"ID of the Pokémon"))
1257 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False,
1258 info=dict(description=u"ID of the version this applies to"))
1259 item_id = Column(Integer, ForeignKey('items.id'), primary_key=True, nullable=False, autoincrement=False,
1260 info=dict(description=u"ID of the item"))
1261 rarity = Column(Integer, nullable=False,
1262 info=dict(description=u"Chance of the Pokémon holding the item, in percent"))
1263
1264 class PokemonMove(TableBase):
1265 u"""Record of a move a Pokémon can learn
1266 """
1267 __tablename__ = 'pokemon_moves'
1268 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, index=True,
1269 info=dict(description=u"ID of the Pokémon"))
1270 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False, index=True,
1271 info=dict(description=u"ID of the version group this applies to"))
1272 move_id = Column(Integer, ForeignKey('moves.id'), nullable=False, index=True,
1273 info=dict(description=u"ID of the move"))
1274 pokemon_move_method_id = Column(Integer, ForeignKey('pokemon_move_methods.id'), nullable=False, index=True,
1275 info=dict(description=u"ID of the method this move is learned by"))
1276 level = Column(Integer, nullable=True, index=True,
1277 info=dict(description=u"Level the move is learned at, if applicable"))
1278 order = Column(Integer, nullable=True,
1279 info=dict(description=u"A sort key to produce the correct ordering when all else is equal")) # XXX: This needs a better description
1280
1281 __table_args__ = (
1282 PrimaryKeyConstraint('pokemon_id', 'version_group_id', 'move_id', 'pokemon_move_method_id', 'level'),
1283 {},
1284 )
1285
1286 class PokemonMoveMethod(TableBase, UnofficiallyNamed):
1287 u"""A method a move can be learned by, such as "Level up" or "Tutor".
1288 """
1289 __tablename__ = 'pokemon_move_methods'
1290 __singlename__ = 'pokemon_move_method'
1291 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1292 info=dict(description=u"A numeric ID"))
1293 identifier = Column(Unicode(64), nullable=False,
1294 info=dict(description=u"An identifier", format='identifier'))
1295 description = ProseColumn(Unicode(255), plural='descriptions', nullable=False,
1296 info=dict(description=u"A detailed description of how the method works", format='plaintext'))
1297
1298 class PokemonShape(TableBase, UnofficiallyNamed):
1299 u"""The shape of a Pokémon's body, as used in generation IV Pokédexes.
1300 """
1301 __tablename__ = 'pokemon_shapes'
1302 __singlename__ = 'pokemon_shape'
1303 id = Column(Integer, primary_key=True, nullable=False,
1304 info=dict(description=u"A numeric ID"))
1305 identifier = Column(Unicode(24), nullable=False,
1306 info=dict(description=u"An identifier", format='identifier'))
1307 awesome_name = ProseColumn(Unicode(16), plural='awesome_names', nullable=False,
1308 info=dict(description=u"A splendiferous name of the body shape", format='plaintext'))
1309
1310 class PokemonStat(TableBase):
1311 u"""A stat value of a Pokémon
1312 """
1313 __tablename__ = 'pokemon_stats'
1314 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1315 info=dict(description=u"ID of the Pokémon"))
1316 stat_id = Column(Integer, ForeignKey('stats.id'), primary_key=True, nullable=False, autoincrement=False,
1317 info=dict(description=u"ID of the stat"))
1318 base_stat = Column(Integer, nullable=False,
1319 info=dict(description=u"The base stat"))
1320 effort = Column(Integer, nullable=False,
1321 info=dict(description=u"The effort increase in this stat gained when this Pokémon is defeated"))
1322
1323 class PokemonType(TableBase):
1324 u"""Maps a type to a Pokémon. Each Pokémon has 1 or 2 types.
1325 """
1326 __tablename__ = 'pokemon_types'
1327 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1328 info=dict(description=u"ID of the Pokémon"))
1329 type_id = Column(Integer, ForeignKey('types.id'), nullable=False,
1330 info=dict(description=u"ID of the type"))
1331 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1332 info=dict(description=u"The type's slot, 1 or 2, used to sort types if there are two of them"))
1333
1334 class Region(TableBase, OfficiallyNamed):
1335 u"""Major areas of the world: Kanto, Johto, etc.
1336 """
1337 __tablename__ = 'regions'
1338 __singlename__ = 'region'
1339 id = Column(Integer, primary_key=True, nullable=False,
1340 info=dict(description=u"A numeric ID"))
1341 identifier = Column(Unicode(16), nullable=False,
1342 info=dict(description=u"An identifier", format='identifier'))
1343
1344 class Stat(TableBase, OfficiallyNamed):
1345 u"""A Stat, such as Attack or Speed
1346 """
1347 __tablename__ = 'stats'
1348 __singlename__ = 'stat'
1349 id = Column(Integer, primary_key=True, nullable=False,
1350 info=dict(description=u"A numeric ID"))
1351 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=True,
1352 info=dict(description=u"For offensive and defensive stats, the damage this stat relates to; otherwise None (the NULL value)"))
1353 identifier = Column(Unicode(16), nullable=False,
1354 info=dict(description=u"An identifier", format='identifier'))
1355
1356 class StatHint(TableBase):
1357 u"""Flavor text for genes that appears in a Pokémon's summary. Sometimes
1358 called "characteristics".
1359 """
1360 __tablename__ = 'stat_hints'
1361 __singlename__ = 'stat_hint'
1362 id = Column(Integer, primary_key=True, nullable=False,
1363 info=dict(description=u"A numeric ID"))
1364 stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
1365 info=dict(description=u"ID of the highest stat"))
1366 gene_mod_5 = Column(Integer, nullable=False, index=True,
1367 info=dict(description=u"Value of the highest stat modulo 5"))
1368 text = TextColumn(Unicode(24), plural='texts', nullable=False, index=True, unique=True,
1369 info=dict(description=u"The text displayed", official=True, format='plaintext'))
1370
1371 class SuperContestCombo(TableBase):
1372 u"""Combo of two moves in a Super Contest.
1373 """
1374 __tablename__ = 'super_contest_combos'
1375 first_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
1376 info=dict(description=u"The ID of the first move in the combo."))
1377 second_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
1378 info=dict(description=u"The ID of the second and last move."))
1379
1380 class SuperContestEffect(TableBase):
1381 u"""An effect a move can have when used in the Super Contest
1382 """
1383 __tablename__ = 'super_contest_effects'
1384 __singlename__ = 'super_contest_effect'
1385 id = Column(Integer, primary_key=True, nullable=False,
1386 info=dict(description=u"This effect's unique ID."))
1387 appeal = Column(SmallInteger, nullable=False,
1388 info=dict(description=u"The number of hearts the user gains."))
1389 flavor_text = ProseColumn(Unicode(64), plural='flavor_texts', nullable=False,
1390 info=dict(description=u"A description of the effect.", format='plaintext'))
1391
1392 class TypeEfficacy(TableBase):
1393 u"""The damage multiplier used when a move of a particular type damages a
1394 Pokémon of a particular other type.
1395 """
1396 __tablename__ = 'type_efficacy'
1397 damage_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False,
1398 info=dict(description=u"The ID of the damaging type."))
1399 target_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False,
1400 info=dict(description=u"The ID of the defending Pokémon's type."))
1401 damage_factor = Column(Integer, nullable=False,
1402 info=dict(description=u"The multiplier, as a percentage of damage inflicted."))
1403
1404 class Type(TableBase, OfficiallyNamed):
1405 u"""Any of the elemental types Pokémon and moves can have."""
1406 __tablename__ = 'types'
1407 __singlename__ = 'type'
1408 id = Column(Integer, primary_key=True, nullable=False,
1409 info=dict(description=u"A unique ID for this type."))
1410 identifier = Column(Unicode(8), nullable=False,
1411 info=dict(description=u"An identifier", format='identifier'))
1412 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
1413 info=dict(description=u"The ID of the generation this type first appeared in."))
1414 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=True,
1415 info=dict(description=u"The ID of the damage class this type's moves had before Generation IV, null if not applicable (e.g. ???)."))
1416
1417 class VersionGroup(TableBase):
1418 u"""A group of versions, containing either two paired versions (such as Red
1419 and Blue) or a single game (such as Yellow.)
1420 """
1421 __tablename__ = 'version_groups'
1422 id = Column(Integer, primary_key=True, nullable=False,
1423 info=dict(description=u"This version group's unique ID."))
1424 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
1425 info=dict(description=u"The ID of the generation the games in this group belong to."))
1426 pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), nullable=False,
1427 info=dict(description=u"The ID of the regional Pokédex used in this version group."))
1428
1429 class VersionGroupRegion(TableBase):
1430 u"""Maps a version group to a region that appears in it."""
1431 __tablename__ = 'version_group_regions'
1432 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
1433 info=dict(description=u"The ID of the version group."))
1434 region_id = Column(Integer, ForeignKey('regions.id'), primary_key=True, nullable=False,
1435 info=dict(description=u"The ID of the region."))
1436
1437 class Version(TableBase, OfficiallyNamed):
1438 u"""An individual main-series Pokémon game."""
1439 __tablename__ = 'versions'
1440 __singlename__ = 'version'
1441 id = Column(Integer, primary_key=True, nullable=False,
1442 info=dict(description=u"A unique ID for this version."))
1443 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False,
1444 info=dict(description=u"The ID of the version group this game belongs to."))
1445 identifier = Column(Unicode(32), nullable=False,
1446 info=dict(description=u'And identifier', format='identifier'))
1447
1448
1449 ### Relations down here, to avoid ordering problems
1450 Ability.changelog = relation(AbilityChangelog,
1451 order_by=AbilityChangelog.changed_in_version_group_id.desc(),
1452 backref='ability',
1453 )
1454 Ability.flavor_text = relation(AbilityFlavorText, order_by=AbilityFlavorText.version_group_id, backref='ability')
1455 Ability.generation = relation(Generation, backref='abilities')
1456 Ability.all_pokemon = relation(Pokemon,
1457 secondary=PokemonAbility.__table__,
1458 order_by=Pokemon.order,
1459 back_populates='all_abilities',
1460 )
1461 Ability.pokemon = relation(Pokemon,
1462 secondary=PokemonAbility.__table__,
1463 primaryjoin=and_(
1464 PokemonAbility.ability_id == Ability.id,
1465 PokemonAbility.is_dream == False
1466 ),
1467 order_by=Pokemon.order,
1468 back_populates='abilities',
1469 )
1470 Ability.dream_pokemon = relation(Pokemon,
1471 secondary=PokemonAbility.__table__,
1472 primaryjoin=and_(
1473 PokemonAbility.ability_id == Ability.id,
1474 PokemonAbility.is_dream == True
1475 ),
1476 order_by=Pokemon.order,
1477 back_populates='dream_ability',
1478 )
1479
1480 AbilityChangelog.changed_in = relation(VersionGroup, backref='ability_changelog')
1481
1482 AbilityFlavorText.version_group = relation(VersionGroup)
1483
1484 Berry.berry_firmness = relation(BerryFirmness, backref='berries')
1485 Berry.firmness = association_proxy('berry_firmness', 'name')
1486 Berry.flavors = relation(BerryFlavor, order_by=BerryFlavor.contest_type_id, backref='berry')
1487 Berry.natural_gift_type = relation(Type)
1488
1489 BerryFlavor.contest_type = relation(ContestType)
1490
1491 ContestCombo.first = relation(Move, primaryjoin=ContestCombo.first_move_id==Move.id,
1492 backref='contest_combo_first')
1493 ContestCombo.second = relation(Move, primaryjoin=ContestCombo.second_move_id==Move.id,
1494 backref='contest_combo_second')
1495
1496 Encounter.location_area = relation(LocationArea, backref='encounters')
1497 Encounter.pokemon = relation(Pokemon, backref='encounters')
1498 Encounter.version = relation(Version, backref='encounters')
1499 Encounter.slot = relation(EncounterSlot, backref='encounters')
1500
1501 EncounterConditionValue.condition = relation(EncounterCondition, backref='values')
1502
1503 Encounter.condition_value_map = relation(EncounterConditionValueMap, backref='encounter')
1504 Encounter.condition_values = association_proxy('condition_value_map', 'condition_value')
1505 EncounterConditionValueMap.condition_value = relation(EncounterConditionValue,
1506 backref='encounter_map')
1507
1508 EncounterSlot.terrain = relation(EncounterTerrain, backref='slots')
1509 EncounterSlot.version_group = relation(VersionGroup)
1510
1511 EvolutionChain.growth_rate = relation(GrowthRate, backref='evolution_chains')
1512 EvolutionChain.baby_trigger_item = relation(Item, backref='evolution_chains')
1513 EvolutionChain.pokemon = relation(Pokemon, order_by=Pokemon.order, back_populates='evolution_chain')
1514
1515 Experience.growth_rate = relation(GrowthRate, backref='experience_table')
1516
1517 Generation.canonical_pokedex = relation(Pokedex, backref='canonical_for_generation')
1518 Generation.versions = relation(Version, secondary=VersionGroup.__table__)
1519 Generation.main_region = relation(Region)
1520
1521 GrowthRate.max_experience_obj = relation(Experience, primaryjoin=and_(Experience.growth_rate_id == GrowthRate.id, Experience.level == 100), uselist=False)
1522 GrowthRate.max_experience = association_proxy('max_experience_obj', 'experience')
1523
1524 Item.berry = relation(Berry, uselist=False, backref='item')
1525 Item.flags = relation(ItemFlag, secondary=ItemFlagMap.__table__)
1526 Item.flavor_text = relation(ItemFlavorText, order_by=ItemFlavorText.version_group_id.asc(), backref='item')
1527 Item.fling_effect = relation(ItemFlingEffect, backref='items')
1528 Item.machines = relation(Machine, order_by=Machine.version_group_id.asc())
1529 Item.category = relation(ItemCategory)
1530 Item.pocket = association_proxy('category', 'pocket')
1531
1532 ItemCategory.items = relation(Item, order_by=Item.identifier)
1533 ItemCategory.pocket = relation(ItemPocket)
1534
1535 ItemFlavorText.version_group = relation(VersionGroup)
1536
1537 ItemInternalID.item = relation(Item, backref='internal_ids')
1538 ItemInternalID.generation = relation(Generation)
1539
1540 ItemPocket.categories = relation(ItemCategory, order_by=ItemCategory.identifier)
1541
1542 Location.region = relation(Region, backref='locations')
1543
1544 LocationArea.location = relation(Location, backref='areas')
1545
1546 LocationInternalID.location = relation(Location, backref='internal_ids')
1547 LocationInternalID.generation = relation(Generation)
1548
1549 Machine.item = relation(Item)
1550 Machine.version_group = relation(VersionGroup)
1551
1552 Move.changelog = relation(MoveChangelog,
1553 order_by=MoveChangelog.changed_in_version_group_id.desc(),
1554 backref='move',
1555 )
1556 Move.contest_effect = relation(ContestEffect, backref='moves')
1557 Move.contest_combo_next = association_proxy('contest_combo_first', 'second')
1558 Move.contest_combo_prev = association_proxy('contest_combo_second', 'first')
1559 Move.contest_type = relation(ContestType, backref='moves')
1560 Move.damage_class = relation(MoveDamageClass, backref='moves')
1561 Move.flags = association_proxy('move_flags', 'flag')
1562 Move.flavor_text = relation(MoveFlavorText, order_by=MoveFlavorText.version_group_id, backref='move')
1563 Move.generation = relation(Generation, backref='moves')
1564 Move.machines = relation(Machine, backref='move')
1565 Move.meta = relation(MoveMeta, uselist=False, backref='move')
1566 Move.meta_stat_changes = relation(MoveMetaStatChange)
1567 Move.move_effect = relation(MoveEffect, backref='moves')
1568 Move.move_flags = relation(MoveFlag, backref='move')
1569 Move.super_contest_effect = relation(SuperContestEffect, backref='moves')
1570 Move.super_contest_combo_next = association_proxy('super_contest_combo_first', 'second')
1571 Move.super_contest_combo_prev = association_proxy('super_contest_combo_second', 'first')
1572 Move.target = relation(MoveTarget, backref='moves')
1573 Move.type = relation(Type, back_populates='moves')
1574
1575 Move.effect = markdown.MoveEffectProperty('effect')
1576 Move.effects = markdown.MoveEffectsProperty('effect')
1577 Move.short_effect = markdown.MoveEffectProperty('short_effect')
1578 Move.short_effects = markdown.MoveEffectsProperty('short_effect')
1579
1580 MoveChangelog.changed_in = relation(VersionGroup, backref='move_changelog')
1581 MoveChangelog.move_effect = relation(MoveEffect, backref='move_changelog')
1582 MoveChangelog.type = relation(Type, backref='move_changelog')
1583
1584 MoveChangelog.effect = markdown.MoveEffectProperty('effect')
1585 MoveChangelog.effects = markdown.MoveEffectsProperty('effect')
1586 MoveChangelog.short_effect = markdown.MoveEffectProperty('short_effect')
1587 MoveChangelog.short_effects = markdown.MoveEffectsProperty('short_effect')
1588
1589 MoveEffect.category_map = relation(MoveEffectCategoryMap)
1590 MoveEffect.categories = association_proxy('category_map', 'category')
1591 MoveEffect.changelog = relation(MoveEffectChangelog,
1592 order_by=MoveEffectChangelog.changed_in_version_group_id.desc(),
1593 backref='move_effect',
1594 )
1595 MoveEffectCategoryMap.category = relation(MoveEffectCategory)
1596
1597 MoveEffectChangelog.changed_in = relation(VersionGroup, backref='move_effect_changelog')
1598
1599 MoveFlag.flag = relation(MoveFlagType)
1600
1601 MoveFlavorText.version_group = relation(VersionGroup)
1602
1603 MoveMeta.category = relation(MoveMetaCategory, backref='move_meta')
1604 MoveMeta.ailment = relation(MoveMetaAilment, backref='move_meta')
1605
1606 MoveMetaStatChange.stat = relation(Stat, backref='move_meta_stat_changes')
1607
1608 Nature.decreased_stat = relation(Stat, primaryjoin=Nature.decreased_stat_id==Stat.id,
1609 backref='decreasing_natures')
1610 Nature.increased_stat = relation(Stat, primaryjoin=Nature.increased_stat_id==Stat.id,
1611 backref='increasing_natures')
1612 Nature.hates_flavor = relation(ContestType, primaryjoin=Nature.hates_flavor_id==ContestType.id,
1613 backref='hating_natures')
1614 Nature.likes_flavor = relation(ContestType, primaryjoin=Nature.likes_flavor_id==ContestType.id,
1615 backref='liking_natures')
1616 Nature.battle_style_preferences = relation(NatureBattleStylePreference,
1617 order_by=NatureBattleStylePreference.move_battle_style_id,
1618 backref='nature')
1619 Nature.pokeathlon_effects = relation(NaturePokeathlonStat, order_by=NaturePokeathlonStat.pokeathlon_stat_id)
1620
1621 NatureBattleStylePreference.battle_style = relation(MoveBattleStyle, backref='nature_preferences')
1622
1623 NaturePokeathlonStat.pokeathlon_stat = relation(PokeathlonStat, backref='nature_effects')
1624
1625 Pokedex.region = relation(Region, backref='pokedexes')
1626 Pokedex.version_groups = relation(VersionGroup, order_by=VersionGroup.id, back_populates='pokedex')
1627
1628 Pokemon.all_abilities = relation(Ability,
1629 secondary=PokemonAbility.__table__,
1630 order_by=PokemonAbility.slot,
1631 )
1632 Pokemon.abilities = relation(Ability,
1633 secondary=PokemonAbility.__table__,
1634 primaryjoin=and_(
1635 Pokemon.id == PokemonAbility.pokemon_id,
1636 PokemonAbility.is_dream == False,
1637 ),
1638 order_by=PokemonAbility.slot,
1639 )
1640 Pokemon.dream_ability = relation(Ability,
1641 secondary=PokemonAbility.__table__,
1642 primaryjoin=and_(
1643 Pokemon.id == PokemonAbility.pokemon_id,
1644 PokemonAbility.is_dream == True,
1645 ),
1646 uselist=False,
1647 )
1648 Pokemon.pokemon_color = relation(PokemonColor, backref='pokemon')
1649 Pokemon.color = association_proxy('pokemon_color', 'name')
1650 Pokemon.dex_numbers = relation(PokemonDexNumber, order_by=PokemonDexNumber.pokedex_id.asc(), backref='pokemon')
1651 Pokemon.egg_groups = relation(EggGroup, secondary=PokemonEggGroup.__table__,
1652 order_by=PokemonEggGroup.egg_group_id,
1653 backref=backref('pokemon', order_by=Pokemon.order))
1654 Pokemon.evolution_chain = relation(EvolutionChain, back_populates='pokemon')
1655 Pokemon.child_pokemon = relation(Pokemon,
1656 primaryjoin=Pokemon.id==PokemonEvolution.from_pokemon_id,
1657 secondary=PokemonEvolution.__table__,
1658 secondaryjoin=PokemonEvolution.to_pokemon_id==Pokemon.id,
1659 backref=backref('parent_pokemon', uselist=False),
1660 )
1661 Pokemon.flavor_text = relation(PokemonFlavorText, order_by=PokemonFlavorText.version_id.asc(), backref='pokemon')
1662 Pokemon.forms = relation(PokemonForm, primaryjoin=Pokemon.id==PokemonForm.form_base_pokemon_id,
1663 order_by=(PokemonForm.order.asc(), PokemonForm.identifier.asc()))
1664 Pokemon.default_form = relation(PokemonForm,
1665 primaryjoin=and_(Pokemon.id==PokemonForm.form_base_pokemon_id, PokemonForm.is_default==True),
1666 uselist=False,
1667 )
1668 Pokemon.pokemon_habitat = relation(PokemonHabitat, backref='pokemon')
1669 Pokemon.habitat = association_proxy('pokemon_habitat', 'name')
1670 Pokemon.items = relation(PokemonItem, backref='pokemon')
1671 Pokemon.generation = relation(Generation, backref='pokemon')
1672 Pokemon.shape = relation(PokemonShape, backref='pokemon')
1673 Pokemon.stats = relation(PokemonStat, backref='pokemon', order_by=PokemonStat.stat_id.asc())
1674 Pokemon.types = relation(Type, secondary=PokemonType.__table__,
1675 order_by=PokemonType.slot.asc(),
1676 back_populates='pokemon')
1677
1678 PokemonDexNumber.pokedex = relation(Pokedex)
1679
1680 PokemonEvolution.from_pokemon = relation(Pokemon,
1681 primaryjoin=PokemonEvolution.from_pokemon_id==Pokemon.id,
1682 backref='child_evolutions',
1683 )
1684 PokemonEvolution.to_pokemon = relation(Pokemon,
1685 primaryjoin=PokemonEvolution.to_pokemon_id==Pokemon.id,
1686 backref=backref('parent_evolution', uselist=False),
1687 )
1688 PokemonEvolution.child_evolutions = relation(PokemonEvolution,
1689 primaryjoin=PokemonEvolution.from_pokemon_id==PokemonEvolution.to_pokemon_id,
1690 foreign_keys=[PokemonEvolution.to_pokemon_id],
1691 backref=backref('parent_evolution',
1692 remote_side=[PokemonEvolution.from_pokemon_id],
1693 uselist=False,
1694 ),
1695 )
1696 PokemonEvolution.trigger = relation(EvolutionTrigger, backref='evolutions')
1697 PokemonEvolution.trigger_item = relation(Item,
1698 primaryjoin=PokemonEvolution.trigger_item_id==Item.id,
1699 backref='triggered_evolutions',
1700 )
1701 PokemonEvolution.held_item = relation(Item,
1702 primaryjoin=PokemonEvolution.held_item_id==Item.id,
1703 backref='required_for_evolutions',
1704 )
1705 PokemonEvolution.location = relation(Location, backref='triggered_evolutions')
1706 PokemonEvolution.known_move = relation(Move, backref='triggered_evolutions')
1707 PokemonEvolution.party_pokemon = relation(Pokemon,
1708 primaryjoin=PokemonEvolution.party_pokemon_id==Pokemon.id,
1709 backref='triggered_evolutions',
1710 )
1711 PokemonEvolution.trade_pokemon = relation(Pokemon,
1712 primaryjoin=PokemonEvolution.trade_pokemon_id==Pokemon.id,
1713 )
1714
1715 PokemonFlavorText.version = relation(Version)
1716
1717 PokemonForm.form_base_pokemon = relation(Pokemon, primaryjoin=PokemonForm.form_base_pokemon_id==Pokemon.id)
1718 PokemonForm.unique_pokemon = relation(Pokemon, backref=backref('unique_form', uselist=False),
1719 primaryjoin=PokemonForm.unique_pokemon_id==Pokemon.id)
1720 PokemonForm.version_group = relation(VersionGroup)
1721 PokemonForm.form_group = association_proxy('form_base_pokemon', 'form_group')
1722 PokemonForm.pokeathlon_stats = relation(PokemonFormPokeathlonStat,
1723 order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id,
1724 backref='pokemon_form')
1725
1726 PokemonFormGroup.pokemon = relation(Pokemon, backref=backref('form_group',
1727 uselist=False))
1728
1729 PokemonFormPokeathlonStat.pokeathlon_stat = relation(PokeathlonStat)
1730
1731 PokemonItem.item = relation(Item, backref='pokemon')
1732 PokemonItem.version = relation(Version)
1733
1734 PokemonMove.pokemon = relation(Pokemon, backref='pokemon_moves')
1735 PokemonMove.version_group = relation(VersionGroup)
1736 PokemonMove.machine = relation(Machine, backref='pokemon_moves',
1737 primaryjoin=and_(Machine.version_group_id==PokemonMove.version_group_id,
1738 Machine.move_id==PokemonMove.move_id),
1739 foreign_keys=[Machine.version_group_id, Machine.move_id],
1740 uselist=False)
1741 PokemonMove.move = relation(Move, backref='pokemon_moves')
1742 PokemonMove.method = relation(PokemonMoveMethod)
1743
1744 PokemonStat.stat = relation(Stat)
1745
1746 # This is technically a has-many; Generation.main_region_id -> Region.id
1747 Region.generation = relation(Generation, uselist=False)
1748 Region.version_group_regions = relation(VersionGroupRegion, backref='region',
1749 order_by='VersionGroupRegion.version_group_id')
1750 Region.version_groups = association_proxy('version_group_regions', 'version_group')
1751
1752 Stat.damage_class = relation(MoveDamageClass, backref='stats')
1753
1754 StatHint.stat = relation(Stat, backref='hints')
1755
1756 SuperContestCombo.first = relation(Move, primaryjoin=SuperContestCombo.first_move_id==Move.id,
1757 backref='super_contest_combo_first')
1758 SuperContestCombo.second = relation(Move, primaryjoin=SuperContestCombo.second_move_id==Move.id,
1759 backref='super_contest_combo_second')
1760
1761 Type.damage_efficacies = relation(TypeEfficacy,
1762 primaryjoin=Type.id
1763 ==TypeEfficacy.damage_type_id,
1764 backref='damage_type')
1765 Type.target_efficacies = relation(TypeEfficacy,
1766 primaryjoin=Type.id
1767 ==TypeEfficacy.target_type_id,
1768 backref='target_type')
1769
1770 Type.generation = relation(Generation, backref='types')
1771 Type.damage_class = relation(MoveDamageClass, backref='types')
1772 Type.pokemon = relation(Pokemon, secondary=PokemonType.__table__,
1773 order_by=Pokemon.order,
1774 back_populates='types')
1775 Type.moves = relation(Move, back_populates='type', order_by=Move.id)
1776
1777 Version.version_group = relation(VersionGroup, back_populates='versions')
1778 Version.generation = association_proxy('version_group', 'generation')
1779
1780 VersionGroup.versions = relation(Version, order_by=Version.id, back_populates='version_group')
1781 VersionGroup.generation = relation(Generation, backref='version_groups')
1782 VersionGroup.version_group_regions = relation(VersionGroupRegion, backref='version_group')
1783 VersionGroup.regions = association_proxy('version_group_regions', 'region')
1784 VersionGroup.pokedex = relation(Pokedex, back_populates='version_groups')
1785
1786
1787 ### Add name tables
1788 for table in list(table_classes):
1789 if issubclass(table, OfficiallyNamed):
1790 cls = TextColumn
1791 info=dict(description="The name", format='plaintext', official=True)
1792 elif issubclass(table, UnofficiallyNamed):
1793 cls = ProseColumn
1794 info=dict(description="The name", format='plaintext', official=False)
1795 else:
1796 continue
1797 table.name = cls(Unicode(class_mapper(table).c.identifier.type.length),
1798 plural='names', nullable=False, info=info)
1799
1800 ### Add text/prose tables
1801
1802 default_lang = u'en'
1803
1804 def makeTextTable(object_table, name_plural, name_singular, columns, lazy):
1805 # With "Language", we'd have two language_id. So, rename one to 'lang'
1806 safe_name = object_table.__singlename__
1807 if safe_name == 'language':
1808 safe_name = 'lang'
1809
1810 tablename = object_table.__singlename__ + '_' + name_plural
1811 singlename = object_table.__singlename__ + '_' + name_singular
1812
1813 class Strings(object):
1814 __tablename__ = tablename
1815 __singlename__ = singlename
1816 _attrname = name_plural
1817 _language_identifier = association_proxy('language', 'identifier')
1818
1819 for name, plural, column in columns:
1820 column.name = name
1821
1822 table = Table(tablename, metadata,
1823 Column(safe_name + '_id', Integer, ForeignKey(object_table.id),
1824 primary_key=True, nullable=False),
1825 Column('language_id', Integer, ForeignKey(Language.id),
1826 primary_key=True, nullable=False),
1827 *(column for name, plural, column in columns)
1828 )
1829
1830 mapper(Strings, table,
1831 properties={
1832 "object_id": synonym(safe_name + '_id'),
1833 "language": relation(
1834 Language,
1835 primaryjoin=table.c.language_id == Language.id,
1836 ),
1837 },
1838 )
1839
1840 # The relation to the object
1841 setattr(object_table, name_plural, relation(
1842 Strings,
1843 primaryjoin=(object_table.id == Strings.object_id),
1844 backref=safe_name,
1845 collection_class=attribute_mapped_collection('language'),
1846 lazy=lazy,
1847 ))
1848 Strings.object = getattr(Strings, safe_name)
1849
1850 # Link the tables themselves, so we can get them if needed
1851 Strings.object_table = object_table
1852 setattr(object_table, name_singular + '_table', Strings)
1853
1854 for colname, pluralname, column in columns:
1855 # Provide a property with all the names, and an English accessor
1856 # for backwards compatibility
1857 setattr(object_table, pluralname, StringProperty(
1858 object_table, Strings, colname,
1859 ))
1860 setattr(object_table, colname, DefaultLangProperty(pluralname))
1861
1862 if colname == 'name':
1863 object_table.name_table = Strings
1864
1865 return Strings
1866
1867 class StringProperty(object):
1868 def __init__(self, cls, stringclass, colname):
1869 self.cls = cls
1870 self.colname = colname
1871 self.stringclass = stringclass
1872
1873 def __get__(self, instance, cls):
1874 if instance:
1875 return dict(
1876 (l, getattr(t, self.colname))
1877 for l, t
1878 in getattr(instance, self.stringclass._attrname).items()
1879 )
1880 else:
1881 return self
1882
1883 def __getitem__(self, lang):
1884 return StringExpression(self, lang)
1885
1886 def __str__(self):
1887 return '<StringDict %s.%s>' % (self.cls, self.colname)
1888
1889 class StringExpression(ColumnOperators):
1890 def __init__(self, prop, lang):
1891 self.prop = prop
1892 self.column = getattr(prop.stringclass, prop.colname)
1893 self.lang_column = prop.stringclass._language_identifier
1894 if isinstance(lang, basestring):
1895 self.lang = lang
1896 else:
1897 self.lang = lang.identifier
1898
1899 def operate(self, op, *values, **kwargs):
1900 return getattr(self.prop.cls, self.prop.stringclass._attrname).any(and_(
1901 self.lang_column == self.lang,
1902 op(self.column, *values, **kwargs),
1903 ))
1904
1905 class DefaultLangProperty(object):
1906 def __init__(self, colname):
1907 self.colname = colname
1908
1909 def __get__(self, instance, cls):
1910 if instance:
1911 return getattr(instance, self.colname)[default_lang]
1912 else:
1913 return getattr(cls, self.colname)[default_lang]
1914
1915 for table in list(table_classes):
1916 # Find all the language-specific columns, keeping them in the order they
1917 # were defined
1918 all_columns = []
1919 for colname in dir(table):
1920 column = getattr(table, colname)
1921 if isinstance(column, LanguageSpecificColumn):
1922 all_columns.append((colname, column))
1923 all_columns.sort(key=lambda pair: pair[1].order)
1924
1925 # Break them into text and prose columns
1926 text_columns = []
1927 prose_columns = []
1928 for colname, column in all_columns:
1929 spec = colname, column.plural, column.makeSAColumn()
1930 if isinstance(column, TextColumn):
1931 text_columns.append(spec)
1932 elif isinstance(column, ProseColumn):
1933 prose_columns.append(spec)
1934
1935 if (text_columns or prose_columns) and issubclass(table, LanguageSpecific):
1936 raise AssertionError("Language-specific table %s shouldn't have explicit language-specific columns" % table)
1937
1938 if text_columns:
1939 string_table = makeTextTable(table, 'texts', 'text', text_columns, lazy=False)
1940 if prose_columns:
1941 string_table = makeTextTable(table, 'prose', 'prose', prose_columns, lazy=True)
1942
1943 ### Add language relations
1944 for table in list(table_classes):
1945 if issubclass(table, LanguageSpecific):
1946 table.language = relation(Language, primaryjoin=table.language_id == Language.id)