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