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