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