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