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