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