Use a class + mapper instead of dynamic declarative magic
[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 meta_category_id = Column(Integer, ForeignKey('move_meta_categories.id'), nullable=False)
720 meta_ailment_id = Column(Integer, ForeignKey('move_meta_ailments.id'), nullable=False)
721 min_hits = Column(Integer, nullable=True, index=True)
722 max_hits = Column(Integer, nullable=True, index=True)
723 min_turns = Column(Integer, nullable=True, index=True)
724 max_turns = Column(Integer, nullable=True, index=True)
725 recoil = Column(Integer, nullable=False, index=True)
726 healing = Column(Integer, nullable=False, index=True)
727 crit_rate = Column(Integer, nullable=False, index=True)
728 ailment_chance = Column(Integer, nullable=False, index=True)
729 flinch_chance = Column(Integer, nullable=False, index=True)
730 stat_chance = Column(Integer, nullable=False, index=True)
731
732 class MoveMetaAilment(TableBase, OfficiallyNamed):
733 u"""Common status ailments moves can inflict on a single Pokémon, including
734 major ailments like paralysis and minor ailments like trapping.
735 """
736 __tablename__ = 'move_meta_ailments'
737 __singlename__ = 'move_meta_ailment'
738 id = Column(Integer, primary_key=True, nullable=False)
739 identifier = Column(Unicode(24), nullable=False)
740
741 class MoveMetaCategory(TableBase):
742 u"""Very general categories that loosely group move effects."""
743 __tablename__ = 'move_meta_categories'
744 __singlename__ = 'move_meta_category'
745 id = Column(Integer, primary_key=True, nullable=False)
746 description = ProseColumn(Unicode(64), plural='descriptions', nullable=False)
747
748 class MoveMetaStatChange(TableBase):
749 u"""Stat changes moves (may) make."""
750 __tablename__ = 'move_meta_stat_changes'
751 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False)
752 stat_id = Column(Integer, ForeignKey('stats.id'), primary_key=True, nullable=False, autoincrement=False)
753 change = Column(Integer, nullable=False, index=True)
754
755 class MoveTarget(TableBase, UnofficiallyNamed):
756 u"""Targetting or "range" of a move, e.g. "Affects all opponents" or "Affects user".
757 """
758 __tablename__ = 'move_targets'
759 __singlename__ = 'move_target'
760 id = Column(Integer, primary_key=True, nullable=False,
761 info=dict(description="A numeric ID"))
762 identifier = Column(Unicode(32), nullable=False,
763 info=dict(description="An identifier", format='identifier'))
764 description = ProseColumn(Unicode(128), plural='descriptions', nullable=False,
765 info=dict(description="A description", format='plaintext'))
766
767 class Move(TableBase, OfficiallyNamed):
768 u"""A Move: technique or attack a Pokémon can learn to use
769 """
770 __tablename__ = 'moves'
771 __singlename__ = 'move'
772 id = Column(Integer, primary_key=True, nullable=False,
773 info=dict(description="A numeric ID"))
774 identifier = Column(Unicode(24), nullable=False,
775 info=dict(description="An identifier", format='identifier'))
776 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
777 info=dict(description="ID of the generation this move first appeared in"))
778 type_id = Column(Integer, ForeignKey('types.id'), nullable=False,
779 info=dict(description="ID of the move's elemental type"))
780 power = Column(SmallInteger, nullable=False,
781 info=dict(description="Base power of the move"))
782 pp = Column(SmallInteger, nullable=True,
783 info=dict(description="Base PP (Power Points) of the move, nullable if not applicable (e.g. Struggle and Shadow moves)."))
784 accuracy = Column(SmallInteger, nullable=True,
785 info=dict(description="Accuracy of the move; NULL means it never misses"))
786 priority = Column(SmallInteger, nullable=False,
787 info=dict(description="The move's priority bracket"))
788 target_id = Column(Integer, ForeignKey('move_targets.id'), nullable=False,
789 info=dict(description="ID of the target (range) of the move"))
790 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=False,
791 info=dict(description="ID of the damage class (physical/special) of the move"))
792 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=False,
793 info=dict(description="ID of the move's effect"))
794 effect_chance = Column(Integer, nullable=True,
795 info=dict(description="The chance for a secondary effect. What this is a chance of is specified by the move's effect."))
796 contest_type_id = Column(Integer, ForeignKey('contest_types.id'), nullable=True,
797 info=dict(description="ID of the move's Contest type (e.g. cool or smart)"))
798 contest_effect_id = Column(Integer, ForeignKey('contest_effects.id'), nullable=True,
799 info=dict(description="ID of the move's Contest effect"))
800 super_contest_effect_id = Column(Integer, ForeignKey('super_contest_effects.id'), nullable=True,
801 info=dict(description="ID of the move's Super Contest effect"))
802
803 class MoveChangelog(TableBase):
804 """History of changes to moves across main game versions."""
805 __tablename__ = 'move_changelog'
806 __singlename__ = 'move_changelog'
807 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False,
808 info=dict(description="ID of the move that changed"))
809 changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
810 info=dict(description="ID of the version group in which the move changed"))
811 type_id = Column(Integer, ForeignKey('types.id'), nullable=True,
812 info=dict(description="Prior type of the move, or NULL if unchanged"))
813 power = Column(SmallInteger, nullable=True,
814 info=dict(description="Prior base power of the move, or NULL if unchanged"))
815 pp = Column(SmallInteger, nullable=True,
816 info=dict(description="Prior base PP of the move, or NULL if unchanged"))
817 accuracy = Column(SmallInteger, nullable=True,
818 info=dict(description="Prior accuracy of the move, or NULL if unchanged"))
819 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=True,
820 info=dict(description="Prior ID of the effect, or NULL if unchanged"))
821 effect_chance = Column(Integer, nullable=True,
822 info=dict(description="Prior effect chance, or NULL if unchanged"))
823
824 class Nature(TableBase, OfficiallyNamed):
825 u"""A nature a Pokémon can have, such as Calm or Brave
826 """
827 __tablename__ = 'natures'
828 __singlename__ = 'nature'
829 id = Column(Integer, primary_key=True, nullable=False,
830 info=dict(description="A numeric ID"))
831 identifier = Column(Unicode(8), nullable=False,
832 info=dict(description="An identifier", format='identifier'))
833 decreased_stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
834 info=dict(description="ID of the stat that this nature decreases by 10% (if decreased_stat_id is the same, the effects cancel out)"))
835 increased_stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
836 info=dict(description="ID of the stat that this nature increases by 10% (if decreased_stat_id is the same, the effects cancel out)"))
837 hates_flavor_id = Column(Integer, ForeignKey('contest_types.id'), nullable=False,
838 info=dict(description=u"ID of the Berry flavor the Pokémon hates (if likes_flavor_id is the same, the effects cancel out)"))
839 likes_flavor_id = Column(Integer, ForeignKey('contest_types.id'), nullable=False,
840 info=dict(description=u"ID of the Berry flavor the Pokémon likes (if hates_flavor_id is the same, the effects cancel out)"))
841
842 @property
843 def is_neutral(self):
844 u"""Returns True iff this nature doesn't alter a Pokémon's stats,
845 bestow taste preferences, etc.
846 """
847 return self.increased_stat_id == self.decreased_stat_id
848
849 class NatureBattleStylePreference(TableBase):
850 u"""Battle Palace move preference
851
852 Specifies how likely a Pokémon with a specific Nature is to use a move of
853 a particular battl style in Battle Palace or Battle Tent
854 """
855 __tablename__ = 'nature_battle_style_preferences'
856 nature_id = Column(Integer, ForeignKey('natures.id'), primary_key=True, nullable=False,
857 info=dict(description=u"ID of the Pokémon's nature"))
858 move_battle_style_id = Column(Integer, ForeignKey('move_battle_styles.id'), primary_key=True, nullable=False,
859 info=dict(description="ID of the battle style"))
860 low_hp_preference = Column(Integer, nullable=False,
861 info=dict(description=u"Chance of using the move, in percent, if HP is under ½"))
862 high_hp_preference = Column(Integer, nullable=False,
863 info=dict(description=u"Chance of using the move, in percent, if HP is over ½"))
864
865 class NaturePokeathlonStat(TableBase):
866 u"""Specifies how a Nature affects a Pokéathlon stat
867 """
868 __tablename__ = 'nature_pokeathlon_stats'
869 nature_id = Column(Integer, ForeignKey('natures.id'), primary_key=True, nullable=False,
870 info=dict(description="ID of the nature"))
871 pokeathlon_stat_id = Column(Integer, ForeignKey('pokeathlon_stats.id'), primary_key=True, nullable=False,
872 info=dict(description="ID of the stat"))
873 max_change = Column(Integer, nullable=False,
874 info=dict(description="Maximum change"))
875
876 class PokeathlonStat(TableBase, OfficiallyNamed):
877 u"""A Pokéathlon stat, such as "Stamina" or "Jump".
878 """
879 __tablename__ = 'pokeathlon_stats'
880 __singlename__ = 'pokeathlon_stat'
881 id = Column(Integer, primary_key=True, nullable=False,
882 info=dict(description="A numeric ID"))
883 identifier = Column(Unicode(8), nullable=False,
884 info=dict(description="An identifier", format='identifier'))
885
886 class Pokedex(TableBase, UnofficiallyNamed):
887 u"""A collection of Pokémon species ordered in a particular way
888 """
889 __tablename__ = 'pokedexes'
890 __singlename__ = 'pokedex'
891 id = Column(Integer, primary_key=True, nullable=False,
892 info=dict(description="A numeric ID"))
893 region_id = Column(Integer, ForeignKey('regions.id'), nullable=True,
894 info=dict(description=u"ID of the region this Pokédex is used in, or None if it's global"))
895 identifier = Column(Unicode(16), nullable=False,
896 info=dict(description=u"An identifier", format='identifier'))
897 description = ProseColumn(Unicode(512), plural='descriptions', nullable=False,
898 info=dict(description=u"A longer description of the Pokédex", format='plaintext'))
899
900 class Pokemon(TableBase, OfficiallyNamed):
901 u"""A species of Pokémon. The core to this whole mess.
902 """
903 __tablename__ = 'pokemon'
904 __singlename__ = 'pokemon'
905 id = Column(Integer, primary_key=True, nullable=False,
906 info=dict(description=u"A numeric ID"))
907 identifier = Column(Unicode(20), nullable=False,
908 info=dict(description=u"An identifier", format='identifier'))
909 generation_id = Column(Integer, ForeignKey('generations.id'),
910 info=dict(description=u"ID of the generation this species first appeared in"))
911 evolution_chain_id = Column(Integer, ForeignKey('evolution_chains.id'),
912 info=dict(description=u"ID of the species' evolution chain (a.k.a. family)"))
913 height = Column(Integer, nullable=False,
914 info=dict(description=u"The height of the Pokémon, in decimeters (tenths of a meter)"))
915 weight = Column(Integer, nullable=False,
916 info=dict(description=u"The weight of the Pokémon, in tenths of a kilogram (decigrams)"))
917 species = TextColumn(Unicode(16), nullable=False, plural='species_names',
918 info=dict(description=u'The short flavor text, such as "Seed" or "Lizard"; usually affixed with the word "Pokémon"',
919 official=True, format='plaintext'))
920 color_id = Column(Integer, ForeignKey('pokemon_colors.id'), nullable=False,
921 info=dict(description=u"ID of this Pokémon's Pokédex color, as used for a gimmick search function in the games."))
922 pokemon_shape_id = Column(Integer, ForeignKey('pokemon_shapes.id'), nullable=True,
923 info=dict(description=u"ID of this Pokémon's body shape, as used for a gimmick search function in the games."))
924 habitat_id = Column(Integer, ForeignKey('pokemon_habitats.id'), nullable=True,
925 info=dict(description=u"ID of this Pokémon's habitat, as used for a gimmick search function in the games."))
926 gender_rate = Column(Integer, nullable=False,
927 info=dict(description=u"The chance of this Pokémon being female, in eighths; or -1 for genderless"))
928 capture_rate = Column(Integer, nullable=False,
929 info=dict(description=u"The base capture rate; up to 255"))
930 base_experience = Column(Integer, nullable=False,
931 info=dict(description=u"The base EXP gained when defeating this Pokémon")) # XXX: Is this correct?
932 base_happiness = Column(Integer, nullable=False,
933 info=dict(description=u"The tameness when caught by a normal ball"))
934 is_baby = Column(Boolean, nullable=False,
935 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."))
936 hatch_counter = Column(Integer, nullable=False,
937 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"))
938 has_gender_differences = Column(Boolean, nullable=False,
939 info=dict(description=u"Set iff the species exhibits enough sexual dimorphism to have separate sets of sprites in Gen IV and beyond."))
940 order = Column(Integer, nullable=False, index=True,
941 info=dict(description=u"Order for sorting. Almost national order, except families and forms are grouped together."))
942
943 ### Stuff to handle alternate Pokémon forms
944
945 @property
946 def form(self):
947 u"""Returns the Pokémon's form, using its default form as fallback."""
948
949 return self.unique_form or self.default_form
950
951 @property
952 def is_base_form(self):
953 u"""Returns True iff the Pokémon is the base form for its species,
954 e.g. Land Shaymin.
955 """
956
957 return self.unique_form is None or self.unique_form.is_default
958
959 @property
960 def form_name(self):
961 u"""Returns the Pokémon's form name if it represents a particular form
962 and that form has a name, or None otherwise.
963 """
964
965 # If self.unique_form is None, the short-circuit "and" will go ahead
966 # and return that. Otherwise, it'll return the form's name, which may
967 # also be None.
968 return self.unique_form and self.unique_form.name
969
970 @property
971 def full_name(self):
972 u"""Returns the Pokémon's name, including its form if applicable."""
973
974 if self.form_name:
975 return u'{0} {1}'.format(self.form_name, self.name)
976 else:
977 return self.name
978
979 @property
980 def normal_form(self):
981 u"""Returns the normal form for this Pokémon; i.e., this will return
982 regular Deoxys when called on any Deoxys form.
983 """
984
985 if self.unique_form:
986 return self.unique_form.form_base_pokemon
987 return self
988
989 ### Not forms!
990
991 def stat(self, stat_name):
992 u"""Returns a PokemonStat record for the given stat name (or Stat row
993 object). Uses the normal has-many machinery, so all the stats are
994 effectively cached.
995 """
996 if isinstance(stat_name, Stat):
997 stat_name = stat_name.name
998
999 for pokemon_stat in self.stats:
1000 if pokemon_stat.stat.name == stat_name:
1001 return pokemon_stat
1002
1003 raise KeyError(u'No stat named %s' % stat_name)
1004
1005 @property
1006 def better_damage_class(self):
1007 u"""Returns the MoveDamageClass that this Pokémon is best suited for,
1008 based on its attack stats.
1009
1010 If the attack stats are about equal (within 5), returns None. The
1011 value None, not the damage class called 'None'.
1012 """
1013 phys = self.stat(u'Attack')
1014 spec = self.stat(u'Special Attack')
1015
1016 diff = phys.base_stat - spec.base_stat
1017
1018 if diff > 5:
1019 return phys.stat.damage_class
1020 elif diff < -5:
1021 return spec.stat.damage_class
1022 else:
1023 return None
1024
1025 class PokemonAbility(TableBase):
1026 u"""Maps an ability to a Pokémon that can have it
1027 """
1028 __tablename__ = 'pokemon_abilities'
1029 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1030 info=dict(description=u"ID of the Pokémon"))
1031 ability_id = Column(Integer, ForeignKey('abilities.id'), nullable=False,
1032 info=dict(description=u"ID of the ability"))
1033 # XXX having both a method and a slot is kind of gross. "slot" is a
1034 # misnomer, anyway: duplicate abilities don't appear in slot 2.
1035 # Probably should replace that with "order".
1036 is_dream = Column(Boolean, nullable=False, index=True,
1037 info=dict(description=u"Whether this is a Dream World ability"))
1038 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1039 info=dict(description=u"The ability slot, i.e. 1 or 2 for gen. IV"))
1040
1041 class PokemonColor(TableBase, OfficiallyNamed):
1042 u"""The "Pokédex color" of a Pokémon species. Usually based on the Pokémon's color.
1043 """
1044 __tablename__ = 'pokemon_colors'
1045 __singlename__ = 'pokemon_color'
1046 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1047 info=dict(description=u"ID of the Pokémon"))
1048 identifier = Column(Unicode(6), nullable=False,
1049 info=dict(description=u"An identifier", format='identifier'))
1050
1051 class PokemonDexNumber(TableBase):
1052 u"""The number of a Pokémon in a particular Pokédex (e.g. Jigglypuff is #138 in Hoenn's 'dex)
1053 """
1054 __tablename__ = 'pokemon_dex_numbers'
1055 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1056 info=dict(description=u"ID of the Pokémon"))
1057 pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), primary_key=True, nullable=False, autoincrement=False,
1058 info=dict(description=u"ID of the Pokédex"))
1059 pokedex_number = Column(Integer, nullable=False,
1060 info=dict(description=u"Number of the Pokémon in that the Pokédex"))
1061
1062 class PokemonEggGroup(TableBase):
1063 u"""Maps an Egg group to a Pokémon; each Pokémon belongs to one or two egg groups
1064 """
1065 __tablename__ = 'pokemon_egg_groups'
1066 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1067 info=dict(description=u"ID of the Pokémon"))
1068 egg_group_id = Column(Integer, ForeignKey('egg_groups.id'), primary_key=True, nullable=False, autoincrement=False,
1069 info=dict(description=u"ID of the egg group"))
1070
1071 class PokemonEvolution(TableBase):
1072 u"""A required action ("trigger") and the conditions under which the trigger
1073 must occur to cause a Pokémon to evolve.
1074
1075 Any condition may be null if it does not apply for a particular Pokémon.
1076 """
1077 __tablename__ = 'pokemon_evolution'
1078 from_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False,
1079 info=dict(description=u"The ID of the pre-evolution Pokémon."))
1080 to_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1081 info=dict(description=u"The ID of the post-evolution Pokémon."))
1082 evolution_trigger_id = Column(Integer, ForeignKey('evolution_triggers.id'), nullable=False,
1083 info=dict(description=u"The ID of the evolution trigger."))
1084 trigger_item_id = Column(Integer, ForeignKey('items.id'), nullable=True,
1085 info=dict(description=u"The ID of the item that must be used on the Pokémon."))
1086 minimum_level = Column(Integer, nullable=True,
1087 info=dict(description=u"The minimum level for the Pokémon."))
1088 gender = Column(Enum('male', 'female', name='pokemon_evolution_gender'), nullable=True,
1089 info=dict(description=u"The Pokémon's required gender, or None if gender doesn't matter"))
1090 location_id = Column(Integer, ForeignKey('locations.id'), nullable=True,
1091 info=dict(description=u"The ID of the location the evolution must be triggered at."))
1092 held_item_id = Column(Integer, ForeignKey('items.id'), nullable=True,
1093 info=dict(description=u"The ID of the item the Pokémon must hold."))
1094 time_of_day = Column(Enum('day', 'night', name='pokemon_evolution_time_of_day'), nullable=True,
1095 info=dict(description=u"The required time of day."))
1096 known_move_id = Column(Integer, ForeignKey('moves.id'), nullable=True,
1097 info=dict(description=u"The ID of the move the Pokémon must know."))
1098 minimum_happiness = Column(Integer, nullable=True,
1099 info=dict(description=u"The minimum happiness value the Pokémon must have."))
1100 minimum_beauty = Column(Integer, nullable=True,
1101 info=dict(description=u"The minimum Beauty value the Pokémon must have."))
1102 relative_physical_stats = Column(Integer, nullable=True,
1103 info=dict(description=u"The required relation between the Pokémon's Attack and Defense stats, as sgn(atk-def)."))
1104 party_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=True,
1105 info=dict(description=u"The ID of the Pokémon that must be present in the party."))
1106 trade_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=True,
1107 info=dict(description=u"The ID of the Pokémon for which this Pokémon must be traded."))
1108
1109 class PokemonFlavorText(TableBase, LanguageSpecific):
1110 u"""In-game Pokédex descrption of a Pokémon.
1111 """
1112 __tablename__ = 'pokemon_flavor_text'
1113 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1114 info=dict(description=u"ID of the Pokémon"))
1115 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False,
1116 info=dict(description=u"ID of the version that has this flavor text"))
1117 flavor_text = Column(Unicode(255), nullable=False,
1118 info=dict(description=u"ID of the version that has this flavor text", official=True, format='gametext'))
1119
1120 class PokemonForm(TableBase, OfficiallyNamed):
1121 u"""An individual form of a Pokémon.
1122
1123 Pokémon that do not have separate forms are still given a single row to
1124 represent their single form.
1125 """
1126 __tablename__ = 'pokemon_forms'
1127 __singlename__ = 'pokemon_form'
1128 id = Column(Integer, primary_key=True, nullable=False,
1129 info=dict(description=u'A unique ID for this form.'))
1130 identifier = Column(Unicode(16), nullable=True,
1131 info=dict(description=u"An identifier", format='identifier'))
1132 form_base_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, autoincrement=False,
1133 info=dict(description=u'The ID of the base Pokémon for this form.'))
1134 unique_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), autoincrement=False,
1135 info=dict(description=u'The ID of a Pokémon that represents specifically this form, for Pokémon with functionally-different forms like Wormadam.'))
1136 introduced_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), autoincrement=False,
1137 info=dict(description=u'The ID of the version group in which this form first appeared.'))
1138 is_default = Column(Boolean, nullable=False,
1139 info=dict(description=u'Set for exactly one form used as the default for each species.'))
1140 order = Column(Integer, nullable=False, autoincrement=False,
1141 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.'))
1142
1143 @property
1144 def pokemon(self):
1145 u"""Returns the Pokémon for this form, using the form base as fallback.
1146 """
1147
1148 return self.unique_pokemon or self.form_base_pokemon
1149
1150 @property
1151 def full_name(self):
1152 u"""Returns the full name of this form, e.g. "Plant Cloak"."""
1153
1154 if not self.name:
1155 return None
1156 elif self.form_group and self.form_group.term:
1157 return u'{0} {1}'.format(self.name, self.form_group.term)
1158 else:
1159 return self.name
1160
1161 @property
1162 def pokemon_name(self):
1163 u"""Returns the name of this Pokémon with this form, e.g. "Plant
1164 Burmy".
1165 """
1166
1167 if self.name:
1168 return u'{0} {1}'.format(self.name, self.form_base_pokemon.name)
1169 else:
1170 return self.form_base_pokemon.name
1171
1172 class PokemonFormGroup(TableBase):
1173 u"""Information about a Pokémon's forms as a group."""
1174 __tablename__ = 'pokemon_form_groups'
1175 __singlename__ = 'pokemon_form_group'
1176 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1177 info=dict(description=u"ID of the base form Pokémon"))
1178 term = ProseColumn(Unicode(16), plural='terms', nullable=True,
1179 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'))
1180 is_battle_only = Column(Boolean, nullable=False,
1181 info=dict(description=u"Set iff the forms only change in battle"))
1182 description = ProseColumn(markdown.MarkdownColumn(1024), plural='descriptions', nullable=False,
1183 info=dict(description=u"Description of how the forms work", format='markdown'))
1184 PokemonFormGroup.id = PokemonFormGroup.pokemon_id
1185
1186 class PokemonFormPokeathlonStat(TableBase):
1187 u"""A Pokémon form's performance in one Pokéathlon stat."""
1188 __tablename__ = 'pokemon_form_pokeathlon_stats'
1189 pokemon_form_id = Column(Integer, ForeignKey('pokemon_forms.id'), primary_key=True, nullable=False, autoincrement=False,
1190 info=dict(description=u'The ID of the Pokémon form.'))
1191 pokeathlon_stat_id = Column(Integer, ForeignKey('pokeathlon_stats.id'), primary_key=True, nullable=False, autoincrement=False,
1192 info=dict(description=u'The ID of the Pokéathlon stat.'))
1193 minimum_stat = Column(Integer, nullable=False, autoincrement=False,
1194 info=dict(description=u'The minimum value for this stat for this Pokémon form.'))
1195 base_stat = Column(Integer, nullable=False, autoincrement=False,
1196 info=dict(description=u'The default value for this stat for this Pokémon form.'))
1197 maximum_stat = Column(Integer, nullable=False, autoincrement=False,
1198 info=dict(description=u'The maximum value for this stat for this Pokémon form.'))
1199
1200 class PokemonHabitat(TableBase, OfficiallyNamed):
1201 u"""The habitat of a Pokémon, as given in the FireRed/LeafGreen version Pokédex
1202 """
1203 __tablename__ = 'pokemon_habitats'
1204 __singlename__ = 'pokemon_habitat'
1205 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1206 info=dict(description=u"A numeric ID"))
1207 identifier = Column(Unicode(16), nullable=False,
1208 info=dict(description=u"An identifier", format='identifier'))
1209
1210 class PokemonInternalID(TableBase):
1211 u"""The number of a Pokémon a game uses internally
1212 """
1213 __tablename__ = 'pokemon_internal_ids'
1214 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, autoincrement=False, nullable=False,
1215 info=dict(description=u"Database ID of the Pokémon"))
1216 generation_id = Column(Integer, ForeignKey('generations.id'), primary_key=True, autoincrement=False, nullable=False,
1217 info=dict(description=u"Database ID of the generation"))
1218 internal_id = Column(Integer, nullable=False,
1219 info=dict(description=u"Internal ID the generation's games use for the Pokémon"))
1220
1221 class PokemonItem(TableBase):
1222 u"""Record of an item a Pokémon can hold in the wild
1223 """
1224 __tablename__ = 'pokemon_items'
1225 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1226 info=dict(description=u"ID of the Pokémon"))
1227 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False,
1228 info=dict(description=u"ID of the version this applies to"))
1229 item_id = Column(Integer, ForeignKey('items.id'), primary_key=True, nullable=False, autoincrement=False,
1230 info=dict(description=u"ID of the item"))
1231 rarity = Column(Integer, nullable=False,
1232 info=dict(description=u"Chance of the Pokémon holding the item, in percent"))
1233
1234 class PokemonMove(TableBase):
1235 u"""Record of a move a Pokémon can learn
1236 """
1237 __tablename__ = 'pokemon_moves'
1238 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, index=True,
1239 info=dict(description=u"ID of the Pokémon"))
1240 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False, index=True,
1241 info=dict(description=u"ID of the version group this applies to"))
1242 move_id = Column(Integer, ForeignKey('moves.id'), nullable=False, index=True,
1243 info=dict(description=u"ID of the move"))
1244 pokemon_move_method_id = Column(Integer, ForeignKey('pokemon_move_methods.id'), nullable=False, index=True,
1245 info=dict(description=u"ID of the method this move is learned by"))
1246 level = Column(Integer, nullable=True, index=True,
1247 info=dict(description=u"Level the move is learned at, if applicable"))
1248 order = Column(Integer, nullable=True,
1249 info=dict(description=u"A sort key to produce the correct ordering when all else is equal")) # XXX: This needs a better description
1250
1251 __table_args__ = (
1252 PrimaryKeyConstraint('pokemon_id', 'version_group_id', 'move_id', 'pokemon_move_method_id', 'level'),
1253 {},
1254 )
1255
1256 class PokemonMoveMethod(TableBase, UnofficiallyNamed):
1257 u"""A method a move can be learned by, such as "Level up" or "Tutor".
1258 """
1259 __tablename__ = 'pokemon_move_methods'
1260 __singlename__ = 'pokemon_move_method'
1261 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1262 info=dict(description=u"A numeric ID"))
1263 identifier = Column(Unicode(64), nullable=False,
1264 info=dict(description=u"An identifier", format='identifier'))
1265 description = ProseColumn(Unicode(255), plural='descriptions', nullable=False,
1266 info=dict(description=u"A detailed description of how the method works", format='plaintext'))
1267
1268 class PokemonShape(TableBase, UnofficiallyNamed):
1269 u"""The shape of a Pokémon's body, as used in generation IV Pokédexes.
1270 """
1271 __tablename__ = 'pokemon_shapes'
1272 __singlename__ = 'pokemon_shape'
1273 id = Column(Integer, primary_key=True, nullable=False,
1274 info=dict(description=u"A numeric ID"))
1275 identifier = Column(Unicode(24), nullable=False,
1276 info=dict(description=u"An identifier", format='identifier'))
1277 awesome_name = ProseColumn(Unicode(16), plural='awesome_names', nullable=False,
1278 info=dict(description=u"A splendiferous name of the body shape", format='plaintext'))
1279
1280 class PokemonStat(TableBase):
1281 u"""A stat value of a Pokémon
1282 """
1283 __tablename__ = 'pokemon_stats'
1284 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1285 info=dict(description=u"ID of the Pokémon"))
1286 stat_id = Column(Integer, ForeignKey('stats.id'), primary_key=True, nullable=False, autoincrement=False,
1287 info=dict(description=u"ID of the stat"))
1288 base_stat = Column(Integer, nullable=False,
1289 info=dict(description=u"The base stat"))
1290 effort = Column(Integer, nullable=False,
1291 info=dict(description=u"The effort increase in this stat gained when this Pokémon is defeated"))
1292
1293 class PokemonType(TableBase):
1294 u"""Maps a type to a Pokémon. Each Pokémon has 1 or 2 types.
1295 """
1296 __tablename__ = 'pokemon_types'
1297 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1298 info=dict(description=u"ID of the Pokémon"))
1299 type_id = Column(Integer, ForeignKey('types.id'), nullable=False,
1300 info=dict(description=u"ID of the type"))
1301 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1302 info=dict(description=u"The type's slot, 1 or 2, used to sort types if there are two of them"))
1303
1304 class Region(TableBase, OfficiallyNamed):
1305 u"""Major areas of the world: Kanto, Johto, etc.
1306 """
1307 __tablename__ = 'regions'
1308 __singlename__ = 'region'
1309 id = Column(Integer, primary_key=True, nullable=False,
1310 info=dict(description=u"A numeric ID"))
1311 identifier = Column(Unicode(16), nullable=False,
1312 info=dict(description=u"An identifier", format='identifier'))
1313
1314 class Stat(TableBase, OfficiallyNamed):
1315 u"""A Stat, such as Attack or Speed
1316 """
1317 __tablename__ = 'stats'
1318 __singlename__ = 'stat'
1319 id = Column(Integer, primary_key=True, nullable=False,
1320 info=dict(description=u"A numeric ID"))
1321 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=True,
1322 info=dict(description=u"For offensive and defensive stats, the damage this stat relates to; otherwise None (the NULL value)"))
1323 identifier = Column(Unicode(16), nullable=False,
1324 info=dict(description=u"An identifier", format='identifier'))
1325
1326 class StatHint(TableBase):
1327 u"""Flavor text for genes that appears in a Pokémon's summary. Sometimes
1328 called "characteristics".
1329 """
1330 __tablename__ = 'stat_hints'
1331 __singlename__ = 'stat_hint'
1332 id = Column(Integer, primary_key=True, nullable=False)
1333 stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False)
1334 gene_mod_5 = Column(Integer, nullable=False, index=True)
1335 text = TextColumn(Unicode(24), plural='texts', nullable=False, index=True, unique=True,
1336 info=dict(description=u"The English text displayed", official=True, format='plaintext'))
1337
1338 class SuperContestCombo(TableBase):
1339 u"""Combo of two moves in a Super Contest.
1340 """
1341 __tablename__ = 'super_contest_combos'
1342 first_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
1343 info=dict(description=u"The ID of the first move in the combo."))
1344 second_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
1345 info=dict(description=u"The ID of the second and last move."))
1346
1347 class SuperContestEffect(TableBase):
1348 u"""An effect a move can have when used in the Super Contest
1349 """
1350 __tablename__ = 'super_contest_effects'
1351 __singlename__ = 'super_contest_effect'
1352 id = Column(Integer, primary_key=True, nullable=False,
1353 info=dict(description=u"This effect's unique ID."))
1354 appeal = Column(SmallInteger, nullable=False,
1355 info=dict(description=u"The number of hearts the user gains."))
1356 flavor_text = ProseColumn(Unicode(64), plural='flavor_texts', nullable=False,
1357 info=dict(description=u"A description of the effect.", format='plaintext'))
1358
1359 class TypeEfficacy(TableBase):
1360 u"""The damage multiplier used when a move of a particular type damages a
1361 Pokémon of a particular other type.
1362 """
1363 __tablename__ = 'type_efficacy'
1364 damage_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False,
1365 info=dict(description=u"The ID of the damaging type."))
1366 target_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False,
1367 info=dict(description=u"The ID of the defending Pokémon's type."))
1368 damage_factor = Column(Integer, nullable=False,
1369 info=dict(description=u"The multiplier, as a percentage of damage inflicted."))
1370
1371 class Type(TableBase, OfficiallyNamed):
1372 u"""Any of the elemental types Pokémon and moves can have."""
1373 __tablename__ = 'types'
1374 __singlename__ = 'type'
1375 id = Column(Integer, primary_key=True, nullable=False,
1376 info=dict(description=u"A unique ID for this type."))
1377 identifier = Column(Unicode(8), nullable=False,
1378 info=dict(description=u"An identifier", format='identifier'))
1379 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
1380 info=dict(description=u"The ID of the generation this type first appeared in."))
1381 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=True,
1382 info=dict(description=u"The ID of the damage class this type's moves had before Generation IV, null if not applicable (e.g. ???)."))
1383
1384 class VersionGroup(TableBase):
1385 u"""A group of versions, containing either two paired versions (such as Red
1386 and Blue) or a single game (such as Yellow.)
1387 """
1388 __tablename__ = 'version_groups'
1389 id = Column(Integer, primary_key=True, nullable=False,
1390 info=dict(description=u"This version group's unique ID."))
1391 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
1392 info=dict(description=u"The ID of the generation the games in this group belong to."))
1393 pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), nullable=False,
1394 info=dict(description=u"The ID of the regional Pokédex used in this version group."))
1395
1396 class VersionGroupRegion(TableBase):
1397 u"""Maps a version group to a region that appears in it."""
1398 __tablename__ = 'version_group_regions'
1399 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
1400 info=dict(description=u"The ID of the version group."))
1401 region_id = Column(Integer, ForeignKey('regions.id'), primary_key=True, nullable=False,
1402 info=dict(description=u"The ID of the region."))
1403
1404 class Version(TableBase, OfficiallyNamed):
1405 u"""An individual main-series Pokémon game."""
1406 __tablename__ = 'versions'
1407 __singlename__ = 'version'
1408 id = Column(Integer, primary_key=True, nullable=False,
1409 info=dict(description=u"A unique ID for this version."))
1410 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False,
1411 info=dict(description=u"The ID of the version group this game belongs to."))
1412 identifier = Column(Unicode(32), nullable=False,
1413 info=dict(description=u'And identifier', format='identifier'))
1414
1415
1416 ### Relations down here, to avoid ordering problems
1417 Ability.changelog = relation(AbilityChangelog,
1418 order_by=AbilityChangelog.changed_in_version_group_id.desc(),
1419 backref='ability',
1420 )
1421 Ability.flavor_text = relation(AbilityFlavorText, order_by=AbilityFlavorText.version_group_id, backref='ability')
1422 Ability.generation = relation(Generation, backref='abilities')
1423 Ability.all_pokemon = relation(Pokemon,
1424 secondary=PokemonAbility.__table__,
1425 order_by=Pokemon.order,
1426 back_populates='all_abilities',
1427 )
1428 Ability.pokemon = relation(Pokemon,
1429 secondary=PokemonAbility.__table__,
1430 primaryjoin=and_(
1431 PokemonAbility.ability_id == Ability.id,
1432 PokemonAbility.is_dream == False
1433 ),
1434 order_by=Pokemon.order,
1435 back_populates='abilities',
1436 )
1437 Ability.dream_pokemon = relation(Pokemon,
1438 secondary=PokemonAbility.__table__,
1439 primaryjoin=and_(
1440 PokemonAbility.ability_id == Ability.id,
1441 PokemonAbility.is_dream == True
1442 ),
1443 order_by=Pokemon.order,
1444 back_populates='dream_ability',
1445 )
1446
1447 AbilityChangelog.changed_in = relation(VersionGroup, backref='ability_changelog')
1448
1449 AbilityFlavorText.version_group = relation(VersionGroup)
1450
1451 Berry.berry_firmness = relation(BerryFirmness, backref='berries')
1452 Berry.firmness = association_proxy('berry_firmness', 'name')
1453 Berry.flavors = relation(BerryFlavor, order_by=BerryFlavor.contest_type_id, backref='berry')
1454 Berry.natural_gift_type = relation(Type)
1455
1456 BerryFlavor.contest_type = relation(ContestType)
1457
1458 ContestCombo.first = relation(Move, primaryjoin=ContestCombo.first_move_id==Move.id,
1459 backref='contest_combo_first')
1460 ContestCombo.second = relation(Move, primaryjoin=ContestCombo.second_move_id==Move.id,
1461 backref='contest_combo_second')
1462
1463 Encounter.location_area = relation(LocationArea, backref='encounters')
1464 Encounter.pokemon = relation(Pokemon, backref='encounters')
1465 Encounter.version = relation(Version, backref='encounters')
1466 Encounter.slot = relation(EncounterSlot, backref='encounters')
1467
1468 EncounterConditionValue.condition = relation(EncounterCondition, backref='values')
1469
1470 Encounter.condition_value_map = relation(EncounterConditionValueMap, backref='encounter')
1471 Encounter.condition_values = association_proxy('condition_value_map', 'condition_value')
1472 EncounterConditionValueMap.condition_value = relation(EncounterConditionValue,
1473 backref='encounter_map')
1474
1475 EncounterSlot.terrain = relation(EncounterTerrain, backref='slots')
1476 EncounterSlot.version_group = relation(VersionGroup)
1477
1478 EvolutionChain.growth_rate = relation(GrowthRate, backref='evolution_chains')
1479 EvolutionChain.baby_trigger_item = relation(Item, backref='evolution_chains')
1480 EvolutionChain.pokemon = relation(Pokemon, order_by=Pokemon.order, back_populates='evolution_chain')
1481
1482 Experience.growth_rate = relation(GrowthRate, backref='experience_table')
1483
1484 Generation.canonical_pokedex = relation(Pokedex, backref='canonical_for_generation')
1485 Generation.versions = relation(Version, secondary=VersionGroup.__table__)
1486 Generation.main_region = relation(Region)
1487
1488 GrowthRate.max_experience_obj = relation(Experience, primaryjoin=and_(Experience.growth_rate_id == GrowthRate.id, Experience.level == 100), uselist=False)
1489 GrowthRate.max_experience = association_proxy('max_experience_obj', 'experience')
1490
1491 Item.berry = relation(Berry, uselist=False, backref='item')
1492 Item.flags = relation(ItemFlag, secondary=ItemFlagMap.__table__)
1493 Item.flavor_text = relation(ItemFlavorText, order_by=ItemFlavorText.version_group_id.asc(), backref='item')
1494 Item.fling_effect = relation(ItemFlingEffect, backref='items')
1495 Item.machines = relation(Machine, order_by=Machine.version_group_id.asc())
1496 Item.category = relation(ItemCategory)
1497 Item.pocket = association_proxy('category', 'pocket')
1498
1499 ItemCategory.items = relation(Item, order_by=Item.identifier)
1500 ItemCategory.pocket = relation(ItemPocket)
1501
1502 ItemFlavorText.version_group = relation(VersionGroup)
1503
1504 ItemInternalID.item = relation(Item, backref='internal_ids')
1505 ItemInternalID.generation = relation(Generation)
1506
1507 ItemPocket.categories = relation(ItemCategory, order_by=ItemCategory.identifier)
1508
1509 Location.region = relation(Region, backref='locations')
1510
1511 LocationArea.location = relation(Location, backref='areas')
1512
1513 LocationInternalID.location = relation(Location, backref='internal_ids')
1514 LocationInternalID.generation = relation(Generation)
1515
1516 Machine.item = relation(Item)
1517 Machine.version_group = relation(VersionGroup)
1518
1519 Move.changelog = relation(MoveChangelog,
1520 order_by=MoveChangelog.changed_in_version_group_id.desc(),
1521 backref='move',
1522 )
1523 Move.contest_effect = relation(ContestEffect, backref='moves')
1524 Move.contest_combo_next = association_proxy('contest_combo_first', 'second')
1525 Move.contest_combo_prev = association_proxy('contest_combo_second', 'first')
1526 Move.contest_type = relation(ContestType, backref='moves')
1527 Move.damage_class = relation(MoveDamageClass, backref='moves')
1528 Move.flags = association_proxy('move_flags', 'flag')
1529 Move.flavor_text = relation(MoveFlavorText, order_by=MoveFlavorText.version_group_id, backref='move')
1530 Move.generation = relation(Generation, backref='moves')
1531 Move.machines = relation(Machine, backref='move')
1532 Move.meta = relation(MoveMeta, uselist=False, backref='move')
1533 Move.meta_stat_changes = relation(MoveMetaStatChange)
1534 Move.move_effect = relation(MoveEffect, backref='moves')
1535 Move.move_flags = relation(MoveFlag, backref='move')
1536 Move.super_contest_effect = relation(SuperContestEffect, backref='moves')
1537 Move.super_contest_combo_next = association_proxy('super_contest_combo_first', 'second')
1538 Move.super_contest_combo_prev = association_proxy('super_contest_combo_second', 'first')
1539 Move.target = relation(MoveTarget, backref='moves')
1540 Move.type = relation(Type, back_populates='moves')
1541
1542 Move.effect = markdown.MoveEffectProperty('effect')
1543 Move.effects = markdown.MoveEffectsProperty('effect')
1544 Move.short_effect = markdown.MoveEffectProperty('short_effect')
1545 Move.short_effects = markdown.MoveEffectsProperty('short_effect')
1546
1547 MoveChangelog.changed_in = relation(VersionGroup, backref='move_changelog')
1548 MoveChangelog.move_effect = relation(MoveEffect, backref='move_changelog')
1549 MoveChangelog.type = relation(Type, backref='move_changelog')
1550
1551 MoveChangelog.effect = markdown.MoveEffectProperty('effect')
1552 MoveChangelog.effects = markdown.MoveEffectsProperty('effect')
1553 MoveChangelog.short_effect = markdown.MoveEffectProperty('short_effect')
1554 MoveChangelog.short_effects = markdown.MoveEffectsProperty('short_effect')
1555
1556 MoveEffect.category_map = relation(MoveEffectCategoryMap)
1557 MoveEffect.categories = association_proxy('category_map', 'category')
1558 MoveEffect.changelog = relation(MoveEffectChangelog,
1559 order_by=MoveEffectChangelog.changed_in_version_group_id.desc(),
1560 backref='move_effect',
1561 )
1562 MoveEffectCategoryMap.category = relation(MoveEffectCategory)
1563
1564 MoveEffectChangelog.changed_in = relation(VersionGroup, backref='move_effect_changelog')
1565
1566 MoveFlag.flag = relation(MoveFlagType)
1567
1568 MoveFlavorText.version_group = relation(VersionGroup)
1569
1570 MoveMeta.category = relation(MoveMetaCategory, backref='move_meta')
1571 MoveMeta.ailment = relation(MoveMetaAilment, backref='move_meta')
1572
1573 MoveMetaStatChange.stat = relation(Stat, backref='move_meta_stat_changes')
1574
1575 Nature.decreased_stat = relation(Stat, primaryjoin=Nature.decreased_stat_id==Stat.id,
1576 backref='decreasing_natures')
1577 Nature.increased_stat = relation(Stat, primaryjoin=Nature.increased_stat_id==Stat.id,
1578 backref='increasing_natures')
1579 Nature.hates_flavor = relation(ContestType, primaryjoin=Nature.hates_flavor_id==ContestType.id,
1580 backref='hating_natures')
1581 Nature.likes_flavor = relation(ContestType, primaryjoin=Nature.likes_flavor_id==ContestType.id,
1582 backref='liking_natures')
1583 Nature.battle_style_preferences = relation(NatureBattleStylePreference,
1584 order_by=NatureBattleStylePreference.move_battle_style_id,
1585 backref='nature')
1586 Nature.pokeathlon_effects = relation(NaturePokeathlonStat, order_by=NaturePokeathlonStat.pokeathlon_stat_id)
1587
1588 NatureBattleStylePreference.battle_style = relation(MoveBattleStyle, backref='nature_preferences')
1589
1590 NaturePokeathlonStat.pokeathlon_stat = relation(PokeathlonStat, backref='nature_effects')
1591
1592 Pokedex.region = relation(Region, backref='pokedexes')
1593 Pokedex.version_groups = relation(VersionGroup, order_by=VersionGroup.id, back_populates='pokedex')
1594
1595 Pokemon.all_abilities = relation(Ability,
1596 secondary=PokemonAbility.__table__,
1597 order_by=PokemonAbility.slot,
1598 )
1599 Pokemon.abilities = relation(Ability,
1600 secondary=PokemonAbility.__table__,
1601 primaryjoin=and_(
1602 Pokemon.id == PokemonAbility.pokemon_id,
1603 PokemonAbility.is_dream == False,
1604 ),
1605 order_by=PokemonAbility.slot,
1606 )
1607 Pokemon.dream_ability = relation(Ability,
1608 secondary=PokemonAbility.__table__,
1609 primaryjoin=and_(
1610 Pokemon.id == PokemonAbility.pokemon_id,
1611 PokemonAbility.is_dream == True,
1612 ),
1613 uselist=False,
1614 )
1615 Pokemon.pokemon_color = relation(PokemonColor, backref='pokemon')
1616 Pokemon.color = association_proxy('pokemon_color', 'name')
1617 Pokemon.dex_numbers = relation(PokemonDexNumber, order_by=PokemonDexNumber.pokedex_id.asc(), backref='pokemon')
1618 Pokemon.egg_groups = relation(EggGroup, secondary=PokemonEggGroup.__table__,
1619 order_by=PokemonEggGroup.egg_group_id,
1620 backref=backref('pokemon', order_by=Pokemon.order))
1621 Pokemon.evolution_chain = relation(EvolutionChain, back_populates='pokemon')
1622 Pokemon.child_pokemon = relation(Pokemon,
1623 primaryjoin=Pokemon.id==PokemonEvolution.from_pokemon_id,
1624 secondary=PokemonEvolution.__table__,
1625 secondaryjoin=PokemonEvolution.to_pokemon_id==Pokemon.id,
1626 backref=backref('parent_pokemon', uselist=False),
1627 )
1628 Pokemon.flavor_text = relation(PokemonFlavorText, order_by=PokemonFlavorText.version_id.asc(), backref='pokemon')
1629 Pokemon.forms = relation(PokemonForm, primaryjoin=Pokemon.id==PokemonForm.form_base_pokemon_id,
1630 order_by=(PokemonForm.order.asc(), PokemonForm.identifier.asc()))
1631 Pokemon.default_form = relation(PokemonForm,
1632 primaryjoin=and_(Pokemon.id==PokemonForm.form_base_pokemon_id, PokemonForm.is_default==True),
1633 uselist=False,
1634 )
1635 Pokemon.pokemon_habitat = relation(PokemonHabitat, backref='pokemon')
1636 Pokemon.habitat = association_proxy('pokemon_habitat', 'name')
1637 Pokemon.items = relation(PokemonItem, backref='pokemon')
1638 Pokemon.generation = relation(Generation, backref='pokemon')
1639 Pokemon.shape = relation(PokemonShape, backref='pokemon')
1640 Pokemon.stats = relation(PokemonStat, backref='pokemon', order_by=PokemonStat.stat_id.asc())
1641 Pokemon.types = relation(Type, secondary=PokemonType.__table__,
1642 order_by=PokemonType.slot.asc(),
1643 back_populates='pokemon')
1644
1645 PokemonDexNumber.pokedex = relation(Pokedex)
1646
1647 PokemonEvolution.from_pokemon = relation(Pokemon,
1648 primaryjoin=PokemonEvolution.from_pokemon_id==Pokemon.id,
1649 backref='child_evolutions',
1650 )
1651 PokemonEvolution.to_pokemon = relation(Pokemon,
1652 primaryjoin=PokemonEvolution.to_pokemon_id==Pokemon.id,
1653 backref=backref('parent_evolution', uselist=False),
1654 )
1655 PokemonEvolution.child_evolutions = relation(PokemonEvolution,
1656 primaryjoin=PokemonEvolution.from_pokemon_id==PokemonEvolution.to_pokemon_id,
1657 foreign_keys=[PokemonEvolution.to_pokemon_id],
1658 backref=backref('parent_evolution',
1659 remote_side=[PokemonEvolution.from_pokemon_id],
1660 uselist=False,
1661 ),
1662 )
1663 PokemonEvolution.trigger = relation(EvolutionTrigger, backref='evolutions')
1664 PokemonEvolution.trigger_item = relation(Item,
1665 primaryjoin=PokemonEvolution.trigger_item_id==Item.id,
1666 backref='triggered_evolutions',
1667 )
1668 PokemonEvolution.held_item = relation(Item,
1669 primaryjoin=PokemonEvolution.held_item_id==Item.id,
1670 backref='required_for_evolutions',
1671 )
1672 PokemonEvolution.location = relation(Location, backref='triggered_evolutions')
1673 PokemonEvolution.known_move = relation(Move, backref='triggered_evolutions')
1674 PokemonEvolution.party_pokemon = relation(Pokemon,
1675 primaryjoin=PokemonEvolution.party_pokemon_id==Pokemon.id,
1676 backref='triggered_evolutions',
1677 )
1678 PokemonEvolution.trade_pokemon = relation(Pokemon,
1679 primaryjoin=PokemonEvolution.trade_pokemon_id==Pokemon.id,
1680 )
1681
1682 PokemonFlavorText.version = relation(Version)
1683
1684 PokemonForm.form_base_pokemon = relation(Pokemon, primaryjoin=PokemonForm.form_base_pokemon_id==Pokemon.id)
1685 PokemonForm.unique_pokemon = relation(Pokemon, backref=backref('unique_form', uselist=False),
1686 primaryjoin=PokemonForm.unique_pokemon_id==Pokemon.id)
1687 PokemonForm.version_group = relation(VersionGroup)
1688 PokemonForm.form_group = association_proxy('form_base_pokemon', 'form_group')
1689 PokemonForm.pokeathlon_stats = relation(PokemonFormPokeathlonStat,
1690 order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id,
1691 backref='pokemon_form')
1692
1693 PokemonFormGroup.pokemon = relation(Pokemon, backref=backref('form_group',
1694 uselist=False))
1695
1696 PokemonFormPokeathlonStat.pokeathlon_stat = relation(PokeathlonStat)
1697
1698 PokemonItem.item = relation(Item, backref='pokemon')
1699 PokemonItem.version = relation(Version)
1700
1701 PokemonMove.pokemon = relation(Pokemon, backref='pokemon_moves')
1702 PokemonMove.version_group = relation(VersionGroup)
1703 PokemonMove.machine = relation(Machine, backref='pokemon_moves',
1704 primaryjoin=and_(Machine.version_group_id==PokemonMove.version_group_id,
1705 Machine.move_id==PokemonMove.move_id),
1706 foreign_keys=[Machine.version_group_id, Machine.move_id],
1707 uselist=False)
1708 PokemonMove.move = relation(Move, backref='pokemon_moves')
1709 PokemonMove.method = relation(PokemonMoveMethod)
1710
1711 PokemonStat.stat = relation(Stat)
1712
1713 # This is technically a has-many; Generation.main_region_id -> Region.id
1714 Region.generation = relation(Generation, uselist=False)
1715 Region.version_group_regions = relation(VersionGroupRegion, backref='region',
1716 order_by='VersionGroupRegion.version_group_id')
1717 Region.version_groups = association_proxy('version_group_regions', 'version_group')
1718
1719 Stat.damage_class = relation(MoveDamageClass, backref='stats')
1720
1721 StatHint.stat = relation(Stat, backref='hints')
1722
1723 SuperContestCombo.first = relation(Move, primaryjoin=SuperContestCombo.first_move_id==Move.id,
1724 backref='super_contest_combo_first')
1725 SuperContestCombo.second = relation(Move, primaryjoin=SuperContestCombo.second_move_id==Move.id,
1726 backref='super_contest_combo_second')
1727
1728 Type.damage_efficacies = relation(TypeEfficacy,
1729 primaryjoin=Type.id
1730 ==TypeEfficacy.damage_type_id,
1731 backref='damage_type')
1732 Type.target_efficacies = relation(TypeEfficacy,
1733 primaryjoin=Type.id
1734 ==TypeEfficacy.target_type_id,
1735 backref='target_type')
1736
1737 Type.generation = relation(Generation, backref='types')
1738 Type.damage_class = relation(MoveDamageClass, backref='types')
1739 Type.pokemon = relation(Pokemon, secondary=PokemonType.__table__,
1740 order_by=Pokemon.order,
1741 back_populates='types')
1742 Type.moves = relation(Move, back_populates='type', order_by=Move.id)
1743
1744 Version.version_group = relation(VersionGroup, back_populates='versions')
1745 Version.generation = association_proxy('version_group', 'generation')
1746
1747 VersionGroup.versions = relation(Version, order_by=Version.id, back_populates='version_group')
1748 VersionGroup.generation = relation(Generation, backref='version_groups')
1749 VersionGroup.version_group_regions = relation(VersionGroupRegion, backref='version_group')
1750 VersionGroup.regions = association_proxy('version_group_regions', 'region')
1751 VersionGroup.pokedex = relation(Pokedex, back_populates='version_groups')
1752
1753
1754 ### Add name tables
1755 for table in list(table_classes):
1756 if issubclass(table, OfficiallyNamed):
1757 cls = TextColumn
1758 info=dict(description="The name", format='plaintext', official=True)
1759 elif issubclass(table, UnofficiallyNamed):
1760 cls = ProseColumn
1761 info=dict(description="The name", format='plaintext', official=False)
1762 else:
1763 continue
1764 table.name = cls(Unicode(class_mapper(table).c.identifier.type.length),
1765 plural='names', nullable=False, info=info)
1766
1767 ### Add text/prose tables
1768
1769 def makeTextTable(object_table, name_plural, name_singular, columns, lazy):
1770 # With "Language", we'd have two language_id. So, rename one to 'lang'
1771 safe_name = object_table.__singlename__
1772 if safe_name == 'language':
1773 safe_name = 'lang'
1774
1775 tablename = object_table.__singlename__ + '_' + name_plural
1776 singlename = object_table.__singlename__ + '_' + name_singular
1777
1778 class Strings(object):
1779 __tablename__ = tablename
1780 __singlename__ = singlename
1781
1782 for name, plural, column in columns:
1783 column.name = name
1784
1785 table = Table(tablename, metadata,
1786 Column(safe_name + '_id', Integer, ForeignKey(object_table.id),
1787 primary_key=True, nullable=False),
1788 Column('language_id', Integer, ForeignKey(Language.id),
1789 primary_key=True, nullable=False),
1790 *(column for name, plural, column in columns)
1791 )
1792
1793 mapper(Strings, table,
1794 properties={
1795 "object_id": synonym(safe_name + '_id'),
1796 "language": relation(
1797 Language,
1798 primaryjoin=table.c.language_id == Language.id,
1799 ),
1800 },
1801 )
1802
1803 # The relation to the object
1804 setattr(object_table, name_plural, relation(
1805 Strings,
1806 primaryjoin=(object_table.id == Strings.object_id),
1807 backref=safe_name,
1808 collection_class=attribute_mapped_collection('language'),
1809 lazy=lazy,
1810 ))
1811 Strings.object = getattr(Strings, safe_name)
1812
1813 # Link the tables themselves, so we can get to them
1814 Strings.object_table = object_table
1815 setattr(object_table, name_singular + '_table', Strings)
1816
1817 for colname, pluralname, column in columns:
1818 # Provide a relation with all the names, and an English accessor
1819 # for backwards compatibility
1820 def scope(colname, pluralname, column):
1821 def get_strings(self):
1822 return dict(
1823 (l, getattr(t, colname))
1824 for l, t in getattr(self, name_plural).items()
1825 )
1826
1827 def get_english_string(self):
1828 try:
1829 return get_strings(self)['en']
1830 except KeyError:
1831 raise AttributeError(colname)
1832
1833 setattr(object_table, pluralname, property(get_strings))
1834 setattr(object_table, colname, property(get_english_string))
1835 scope(colname, pluralname, column)
1836
1837 if colname == 'name':
1838 object_table.name_table = Strings
1839
1840 return Strings
1841
1842 for table in list(table_classes):
1843 # Find all the language-specific columns, keeping them in the order they
1844 # were defined
1845 all_columns = []
1846 for colname in dir(table):
1847 column = getattr(table, colname)
1848 if isinstance(column, LanguageSpecificColumn):
1849 all_columns.append((colname, column))
1850 all_columns.sort(key=lambda pair: pair[1].order)
1851
1852 # Break them into text and prose columns
1853 text_columns = []
1854 prose_columns = []
1855 for colname, column in all_columns:
1856 spec = colname, column.plural, column.makeSAColumn()
1857 if isinstance(column, TextColumn):
1858 text_columns.append(spec)
1859 elif isinstance(column, ProseColumn):
1860 prose_columns.append(spec)
1861
1862 if (text_columns or prose_columns) and issubclass(table, LanguageSpecific):
1863 raise AssertionError("Language-specific table %s shouldn't have explicit language-specific columns" % table)
1864
1865 if text_columns:
1866 string_table = makeTextTable(table, 'texts', 'text', text_columns, lazy=False)
1867 if prose_columns:
1868 string_table = makeTextTable(table, 'prose', 'prose', prose_columns, lazy=True)
1869
1870 ### Add language relations
1871 for table in list(table_classes):
1872 if issubclass(table, LanguageSpecific):
1873 table.language = relation(Language, primaryjoin=table.language_id == Language.id)