Sigh! Remove support for strings as keys; use Language objects.
[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 class Location(TableBase):
574 u"""A place in the Pokémon world
575 """
576 __tablename__ = 'locations'
577 __singlename__ = 'location'
578 id = Column(Integer, primary_key=True, nullable=False,
579 info=dict(description="A numeric ID"))
580 region_id = Column(Integer, ForeignKey('regions.id'),
581 info=dict(description="ID of the region this location is in"))
582 identifier = Column(Unicode(64), nullable=False,
583 info=dict(description="An identifier", format='identifier'))
584 name = TextColumn(Unicode(64), nullable=False, index=True, plural='names',
585 info=dict(description="The name", format='plaintext', official=True))
586
587 class LocationArea(TableBase):
588 u"""A sub-area of a location
589 """
590 __tablename__ = 'location_areas'
591 __singlename__ = 'location_area'
592 id = Column(Integer, primary_key=True, nullable=False,
593 info=dict(description="A numeric ID"))
594 location_id = Column(Integer, ForeignKey('locations.id'), nullable=False,
595 info=dict(description="ID of the location this area is part of"))
596 internal_id = Column(Integer, nullable=False,
597 info=dict(description="ID the games ude for this area"))
598 identifier = Column(Unicode(64), nullable=True,
599 info=dict(description="An identifier", format='identifier'))
600 name = ProseColumn(Unicode(64), nullable=False, index=True, plural='names',
601 info=dict(description="The name", format='plaintext', official=False))
602
603 class LocationAreaEncounterRate(TableBase):
604 # XXX: What's this exactly? Someone add the docstring & revise the descriptions
605 __tablename__ = 'location_area_encounter_rates'
606 location_area_id = Column(Integer, ForeignKey('location_areas.id'), primary_key=True, nullable=False, autoincrement=False,
607 info=dict(description="ID of the area"))
608 encounter_terrain_id = Column(Integer, ForeignKey('encounter_terrain.id'), primary_key=True, nullable=False, autoincrement=False,
609 info=dict(description="ID of the terrain"))
610 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, autoincrement=False,
611 info=dict(description="ID of the version"))
612 rate = Column(Integer, nullable=True,
613 info=dict(description="The encounter rate")) # units?
614
615 class LocationInternalID(TableBase):
616 u"""IDs the games use internally for locations
617 """
618 __tablename__ = 'location_internal_ids'
619 location_id = Column(Integer, ForeignKey('locations.id'), nullable=False, primary_key=True,
620 info=dict(description="Database ID of the locaion"))
621 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False, primary_key=True,
622 info=dict(description="ID of the generation this entry to"))
623 internal_id = Column(Integer, nullable=False,
624 info=dict(description="Internal game ID of the location"))
625
626 class Machine(TableBase):
627 u"""A TM or HM; numbered item that can teach a move to a Pokémon
628 """
629 __tablename__ = 'machines'
630 machine_number = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
631 info=dict(description="Number of the machine for TMs, or 100 + the munber for HMs"))
632 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False,
633 info=dict(description="Versions this entry applies to"))
634 item_id = Column(Integer, ForeignKey('items.id'), nullable=False,
635 info=dict(description="ID of the corresponding Item"))
636 move_id = Column(Integer, ForeignKey('moves.id'), nullable=False,
637 info=dict(description="ID of the taught move"))
638
639 @property
640 def is_hm(self):
641 u"""True if this machine is a HM, False if it's a TM
642 """
643 return self.machine_number >= 100
644
645 class MoveBattleStyle(TableBase):
646 u"""A battle style of a move""" # XXX: Explain better
647 __tablename__ = 'move_battle_styles'
648 __singlename__ = 'move_battle_style'
649 id = Column(Integer, primary_key=True, nullable=False,
650 info=dict(description="A numeric ID"))
651 identifier = Column(Unicode(8), nullable=False,
652 info=dict(description="An identifier", format='identifier'))
653 name = ProseColumn(Unicode(8), nullable=False, index=True, plural='names',
654 info=dict(description="The name", format='plaintext', official=False))
655
656 class MoveEffectCategory(TableBase):
657 u"""Category of a move effect
658 """
659 __tablename__ = 'move_effect_categories'
660 __singlename__ = 'move_effect_category'
661 id = Column(Integer, primary_key=True, nullable=False,
662 info=dict(description="A numeric ID"))
663 identifier = Column(Unicode(64), nullable=False,
664 info=dict(description="An identifier", format='identifier'))
665 can_affect_user = Column(Boolean, nullable=False,
666 info=dict(description="Set if the user can be affected"))
667 name = ProseColumn(Unicode(64), nullable=False, index=True, plural='names',
668 info=dict(description="The name", format='plaintext', official=False))
669
670 class MoveEffectCategoryMap(TableBase):
671 u"""Maps a move effect category to a move effect
672 """
673 __tablename__ = 'move_effect_category_map'
674 move_effect_id = Column(Integer, ForeignKey('move_effects.id'), primary_key=True, nullable=False,
675 info=dict(description="ID of the move effect"))
676 move_effect_category_id = Column(Integer, ForeignKey('move_effect_categories.id'), primary_key=True, nullable=False,
677 info=dict(description="ID of the category"))
678 affects_user = Column(Boolean, primary_key=True, nullable=False,
679 info=dict(description="Set if the user is affected"))
680
681 class MoveDamageClass(TableBase):
682 u"""Any of the damage classes moves can have, i.e. physical, special, or non-damaging.
683 """
684 __tablename__ = 'move_damage_classes'
685 __singlename__ = 'move_damage_class'
686 id = Column(Integer, primary_key=True, nullable=False,
687 info=dict(description="A numeric ID"))
688 identifier = Column(Unicode(16), nullable=False,
689 info=dict(description="An identifier", format='identifier'))
690 description = ProseColumn(Unicode(64), plural='descriptions', nullable=False,
691 info=dict(description="A description of the class", format='plaintext'))
692 name = ProseColumn(Unicode(16), nullable=False, index=True, plural='names',
693 info=dict(description="The name", format='plaintext', official=False))
694
695 class MoveEffect(TableBase):
696 u"""An effect of a move
697 """
698 __tablename__ = 'move_effects'
699 __singlename__ = 'move_effect'
700 id = Column(Integer, primary_key=True, nullable=False,
701 info=dict(description="A numeric ID"))
702 short_effect = ProseColumn(Unicode(256), plural='short_effects', nullable=False,
703 info=dict(description="A short summary of the effect", format='plaintext'))
704 effect = ProseColumn(Unicode(5120), plural='effects', nullable=False,
705 info=dict(description="A detailed description of the effect", format='plaintext'))
706
707 class MoveEffectChangelog(TableBase):
708 """History of changes to move effects across main game versions."""
709 __tablename__ = 'move_effect_changelog'
710 __singlename__ = 'move_effect_changelog'
711 id = Column(Integer, primary_key=True, nullable=False,
712 info=dict(description="A numeric ID"))
713 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=False,
714 info=dict(description="The ID of the effect that changed"))
715 changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False,
716 info=dict(description="The ID of the version group in which the effect changed"))
717 effect = ProseColumn(markdown.MarkdownColumn(512), plural='effects', nullable=False,
718 info=dict(description="A description of the old behavior", format='markdown'))
719
720 __table_args__ = (
721 UniqueConstraint(effect_id, changed_in_version_group_id),
722 {},
723 )
724
725 class MoveFlag(TableBase):
726 u"""Maps a move flag to a move
727 """
728 # XXX: Other flags have a ___Flag class for the actual flag and ___FlagMap for the map,
729 # these, somewhat confusingly, have MoveFlagType and MoveFlag
730 __tablename__ = 'move_flags'
731 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
732 info=dict(description="ID of the move"))
733 move_flag_type_id = Column(Integer, ForeignKey('move_flag_types.id'), primary_key=True, nullable=False, autoincrement=False,
734 info=dict(description="ID of the flag"))
735
736 class MoveFlagType(TableBase):
737 u"""A Move attribute such as "snatchable" or "contact".
738 """
739 __tablename__ = 'move_flag_types'
740 __singlename__ = 'move_flag_type'
741 id = Column(Integer, primary_key=True, nullable=False,
742 info=dict(description="A numeric ID"))
743 identifier = Column(Unicode(32), nullable=False,
744 info=dict(description="A short identifier for the flag", format='identifier'))
745 description = ProseColumn(markdown.MarkdownColumn(128), plural='descriptions', nullable=False,
746 info=dict(description="A short description of the flag", format='markdown'))
747 name = ProseColumn(Unicode(32), nullable=False, index=True, plural='names',
748 info=dict(description="The name", format='plaintext', official=False))
749
750 class MoveFlavorText(TableBase, LanguageSpecific):
751 u"""In-game description of a move
752 """
753 __tablename__ = 'move_flavor_text'
754 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
755 info=dict(description="ID of the move"))
756 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False,
757 info=dict(description="ID of the version group this text appears in"))
758 flavor_text = Column(Unicode(255), nullable=False,
759 info=dict(description="The flavor text", official=True, format='gametext'))
760
761 class MoveMeta(TableBase):
762 u"""Metadata for move effects, sorta-kinda ripped straight from the game"""
763 __tablename__ = 'move_meta'
764 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
765 info=dict(description="A numeric ID"))
766 meta_category_id = Column(Integer, ForeignKey('move_meta_categories.id'), nullable=False,
767 info=dict(description="ID of the move category"))
768 meta_ailment_id = Column(Integer, ForeignKey('move_meta_ailments.id'), nullable=False,
769 info=dict(description="ID of the caused ailment"))
770 min_hits = Column(Integer, nullable=True, index=True,
771 info=dict(description="Minimum number of hits per use"))
772 max_hits = Column(Integer, nullable=True, index=True,
773 info=dict(description="Maximum number of hits per use"))
774 min_turns = Column(Integer, nullable=True, index=True,
775 info=dict(description="Minimum number of turns the user is forced to use the move"))
776 max_turns = Column(Integer, nullable=True, index=True,
777 info=dict(description="Maximum number of turns the user is forced to use the move"))
778 recoil = Column(Integer, nullable=False, index=True,
779 info=dict(description="Recoil damage, in percent of damage done"))
780 healing = Column(Integer, nullable=False, index=True,
781 info=dict(description="Healing, in percent of user's max HP"))
782 crit_rate = Column(Integer, nullable=False, index=True,
783 info=dict(description="Critical hit rate bonus"))
784 ailment_chance = Column(Integer, nullable=False, index=True,
785 info=dict(description="Chance to cause an ailment, in percent"))
786 flinch_chance = Column(Integer, nullable=False, index=True,
787 info=dict(description="Chance to cause flinching, in percent"))
788 stat_chance = Column(Integer, nullable=False, index=True,
789 info=dict(description="Chance to cause a stat change, in percent"))
790
791 class MoveMetaAilment(TableBase):
792 u"""Common status ailments moves can inflict on a single Pokémon, including
793 major ailments like paralysis and minor ailments like trapping.
794 """
795 __tablename__ = 'move_meta_ailments'
796 __singlename__ = 'move_meta_ailment'
797 id = Column(Integer, primary_key=True, nullable=False,
798 info=dict(description="A numeric ID"))
799 identifier = Column(Unicode(24), nullable=False,
800 info=dict(description="An identifier", format='identifier'))
801 name = TextColumn(Unicode(24), nullable=False, index=True, plural='names',
802 info=dict(description="The name", format='plaintext', official=True))
803
804 class MoveMetaCategory(TableBase):
805 u"""Very general categories that loosely group move effects."""
806 __tablename__ = 'move_meta_categories'
807 __singlename__ = 'move_meta_category'
808 id = Column(Integer, primary_key=True, nullable=False,
809 info=dict(description="A numeric ID"))
810 description = ProseColumn(Unicode(64), plural='descriptions', nullable=False,
811 info=dict(description="A description of the category"))
812
813 class MoveMetaStatChange(TableBase):
814 u"""Stat changes moves (may) make."""
815 __tablename__ = 'move_meta_stat_changes'
816 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
817 info=dict(description="ID of the move"))
818 stat_id = Column(Integer, ForeignKey('stats.id'), primary_key=True, nullable=False, autoincrement=False,
819 info=dict(description="ID of the stat"))
820 change = Column(Integer, nullable=False, index=True,
821 info=dict(description="Amount of increase/decrease, in stages"))
822
823 class MoveTarget(TableBase):
824 u"""Targetting or "range" of a move, e.g. "Affects all opponents" or "Affects user".
825 """
826 __tablename__ = 'move_targets'
827 __singlename__ = 'move_target'
828 id = Column(Integer, primary_key=True, nullable=False,
829 info=dict(description="A numeric ID"))
830 identifier = Column(Unicode(32), nullable=False,
831 info=dict(description="An identifier", format='identifier'))
832 description = ProseColumn(Unicode(128), plural='descriptions', nullable=False,
833 info=dict(description="A description", format='plaintext'))
834 name = ProseColumn(Unicode(32), nullable=False, index=True, plural='names',
835 info=dict(description="The name", format='plaintext', official=False))
836
837 class Move(TableBase):
838 u"""A Move: technique or attack a Pokémon can learn to use
839 """
840 __tablename__ = 'moves'
841 __singlename__ = 'move'
842 id = Column(Integer, primary_key=True, nullable=False,
843 info=dict(description="A numeric ID"))
844 identifier = Column(Unicode(24), nullable=False,
845 info=dict(description="An identifier", format='identifier'))
846 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
847 info=dict(description="ID of the generation this move first appeared in"))
848 type_id = Column(Integer, ForeignKey('types.id'), nullable=False,
849 info=dict(description="ID of the move's elemental type"))
850 power = Column(SmallInteger, nullable=False,
851 info=dict(description="Base power of the move"))
852 pp = Column(SmallInteger, nullable=True,
853 info=dict(description="Base PP (Power Points) of the move, nullable if not applicable (e.g. Struggle and Shadow moves)."))
854 accuracy = Column(SmallInteger, nullable=True,
855 info=dict(description="Accuracy of the move; NULL means it never misses"))
856 priority = Column(SmallInteger, nullable=False,
857 info=dict(description="The move's priority bracket"))
858 target_id = Column(Integer, ForeignKey('move_targets.id'), nullable=False,
859 info=dict(description="ID of the target (range) of the move"))
860 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=False,
861 info=dict(description="ID of the damage class (physical/special) of the move"))
862 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=False,
863 info=dict(description="ID of the move's effect"))
864 effect_chance = Column(Integer, nullable=True,
865 info=dict(description="The chance for a secondary effect. What this is a chance of is specified by the move's effect."))
866 contest_type_id = Column(Integer, ForeignKey('contest_types.id'), nullable=True,
867 info=dict(description="ID of the move's Contest type (e.g. cool or smart)"))
868 contest_effect_id = Column(Integer, ForeignKey('contest_effects.id'), nullable=True,
869 info=dict(description="ID of the move's Contest effect"))
870 super_contest_effect_id = Column(Integer, ForeignKey('super_contest_effects.id'), nullable=True,
871 info=dict(description="ID of the move's Super Contest effect"))
872 name = TextColumn(Unicode(24), nullable=False, index=True, plural='names',
873 info=dict(description="The name", format='plaintext', official=True))
874
875 class MoveChangelog(TableBase):
876 """History of changes to moves across main game versions."""
877 __tablename__ = 'move_changelog'
878 __singlename__ = 'move_changelog'
879 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False,
880 info=dict(description="ID of the move that changed"))
881 changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
882 info=dict(description="ID of the version group in which the move changed"))
883 type_id = Column(Integer, ForeignKey('types.id'), nullable=True,
884 info=dict(description="Prior type of the move, or NULL if unchanged"))
885 power = Column(SmallInteger, nullable=True,
886 info=dict(description="Prior base power of the move, or NULL if unchanged"))
887 pp = Column(SmallInteger, nullable=True,
888 info=dict(description="Prior base PP of the move, or NULL if unchanged"))
889 accuracy = Column(SmallInteger, nullable=True,
890 info=dict(description="Prior accuracy of the move, or NULL if unchanged"))
891 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=True,
892 info=dict(description="Prior ID of the effect, or NULL if unchanged"))
893 effect_chance = Column(Integer, nullable=True,
894 info=dict(description="Prior effect chance, or NULL if unchanged"))
895
896 class Nature(TableBase):
897 u"""A nature a Pokémon can have, such as Calm or Brave
898 """
899 __tablename__ = 'natures'
900 __singlename__ = 'nature'
901 id = Column(Integer, primary_key=True, nullable=False,
902 info=dict(description="A numeric ID"))
903 identifier = Column(Unicode(8), nullable=False,
904 info=dict(description="An identifier", format='identifier'))
905 decreased_stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
906 info=dict(description="ID of the stat that this nature decreases by 10% (if decreased_stat_id is the same, the effects cancel out)"))
907 increased_stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
908 info=dict(description="ID of the stat that this nature increases by 10% (if decreased_stat_id is the same, the effects cancel out)"))
909 hates_flavor_id = Column(Integer, ForeignKey('contest_types.id'), nullable=False,
910 info=dict(description=u"ID of the Berry flavor the Pokémon hates (if likes_flavor_id is the same, the effects cancel out)"))
911 likes_flavor_id = Column(Integer, ForeignKey('contest_types.id'), nullable=False,
912 info=dict(description=u"ID of the Berry flavor the Pokémon likes (if hates_flavor_id is the same, the effects cancel out)"))
913 name = TextColumn(Unicode(8), nullable=False, index=True, plural='names',
914 info=dict(description="The name", format='plaintext', official=True))
915
916 @property
917 def is_neutral(self):
918 u"""Returns True iff this nature doesn't alter a Pokémon's stats,
919 bestow taste preferences, etc.
920 """
921 return self.increased_stat_id == self.decreased_stat_id
922
923 class NatureBattleStylePreference(TableBase):
924 u"""Battle Palace move preference
925
926 Specifies how likely a Pokémon with a specific Nature is to use a move of
927 a particular battl style in Battle Palace or Battle Tent
928 """
929 __tablename__ = 'nature_battle_style_preferences'
930 nature_id = Column(Integer, ForeignKey('natures.id'), primary_key=True, nullable=False,
931 info=dict(description=u"ID of the Pokémon's nature"))
932 move_battle_style_id = Column(Integer, ForeignKey('move_battle_styles.id'), primary_key=True, nullable=False,
933 info=dict(description="ID of the battle style"))
934 low_hp_preference = Column(Integer, nullable=False,
935 info=dict(description=u"Chance of using the move, in percent, if HP is under ½"))
936 high_hp_preference = Column(Integer, nullable=False,
937 info=dict(description=u"Chance of using the move, in percent, if HP is over ½"))
938
939 class NaturePokeathlonStat(TableBase):
940 u"""Specifies how a Nature affects a Pokéathlon stat
941 """
942 __tablename__ = 'nature_pokeathlon_stats'
943 nature_id = Column(Integer, ForeignKey('natures.id'), primary_key=True, nullable=False,
944 info=dict(description="ID of the nature"))
945 pokeathlon_stat_id = Column(Integer, ForeignKey('pokeathlon_stats.id'), primary_key=True, nullable=False,
946 info=dict(description="ID of the stat"))
947 max_change = Column(Integer, nullable=False,
948 info=dict(description="Maximum change"))
949
950 class PokeathlonStat(TableBase):
951 u"""A Pokéathlon stat, such as "Stamina" or "Jump".
952 """
953 __tablename__ = 'pokeathlon_stats'
954 __singlename__ = 'pokeathlon_stat'
955 id = Column(Integer, primary_key=True, nullable=False,
956 info=dict(description="A numeric ID"))
957 identifier = Column(Unicode(8), nullable=False,
958 info=dict(description="An identifier", format='identifier'))
959 name = TextColumn(Unicode(8), nullable=False, index=True, plural='names',
960 info=dict(description="The name", format='plaintext', official=True))
961
962 class Pokedex(TableBase):
963 u"""A collection of Pokémon species ordered in a particular way
964 """
965 __tablename__ = 'pokedexes'
966 __singlename__ = 'pokedex'
967 id = Column(Integer, primary_key=True, nullable=False,
968 info=dict(description="A numeric ID"))
969 region_id = Column(Integer, ForeignKey('regions.id'), nullable=True,
970 info=dict(description=u"ID of the region this Pokédex is used in, or None if it's global"))
971 identifier = Column(Unicode(16), nullable=False,
972 info=dict(description=u"An identifier", format='identifier'))
973 description = ProseColumn(Unicode(512), plural='descriptions', nullable=False,
974 info=dict(description=u"A longer description of the Pokédex", format='plaintext'))
975 name = ProseColumn(Unicode(16), nullable=False, index=True, plural='names',
976 info=dict(description="The name", format='plaintext', official=False))
977
978 class Pokemon(TableBase):
979 u"""A species of Pokémon. The core to this whole mess.
980 """
981 __tablename__ = 'pokemon'
982 __singlename__ = 'pokemon'
983 id = Column(Integer, primary_key=True, nullable=False,
984 info=dict(description=u"A numeric ID"))
985 identifier = Column(Unicode(20), nullable=False,
986 info=dict(description=u"An identifier", format='identifier'))
987 generation_id = Column(Integer, ForeignKey('generations.id'),
988 info=dict(description=u"ID of the generation this species first appeared in"))
989 evolution_chain_id = Column(Integer, ForeignKey('evolution_chains.id'),
990 info=dict(description=u"ID of the species' evolution chain (a.k.a. family)"))
991 height = Column(Integer, nullable=False,
992 info=dict(description=u"The height of the Pokémon, in decimeters (tenths of a meter)"))
993 weight = Column(Integer, nullable=False,
994 info=dict(description=u"The weight of the Pokémon, in tenths of a kilogram (decigrams)"))
995 species = TextColumn(Unicode(16), nullable=False, plural='species_names',
996 info=dict(description=u'The short flavor text, such as "Seed" or "Lizard"; usually affixed with the word "Pokémon"',
997 official=True, format='plaintext'))
998 color_id = Column(Integer, ForeignKey('pokemon_colors.id'), nullable=False,
999 info=dict(description=u"ID of this Pokémon's Pokédex color, as used for a gimmick search function in the games."))
1000 pokemon_shape_id = Column(Integer, ForeignKey('pokemon_shapes.id'), nullable=True,
1001 info=dict(description=u"ID of this Pokémon's body shape, as used for a gimmick search function in the games."))
1002 habitat_id = Column(Integer, ForeignKey('pokemon_habitats.id'), nullable=True,
1003 info=dict(description=u"ID of this Pokémon's habitat, as used for a gimmick search function in the games."))
1004 gender_rate = Column(Integer, nullable=False,
1005 info=dict(description=u"The chance of this Pokémon being female, in eighths; or -1 for genderless"))
1006 capture_rate = Column(Integer, nullable=False,
1007 info=dict(description=u"The base capture rate; up to 255"))
1008 base_experience = Column(Integer, nullable=False,
1009 info=dict(description=u"The base EXP gained when defeating this Pokémon")) # XXX: Is this correct?
1010 base_happiness = Column(Integer, nullable=False,
1011 info=dict(description=u"The tameness when caught by a normal ball"))
1012 is_baby = Column(Boolean, nullable=False,
1013 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."))
1014 hatch_counter = Column(Integer, nullable=False,
1015 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"))
1016 has_gender_differences = Column(Boolean, nullable=False,
1017 info=dict(description=u"Set iff the species exhibits enough sexual dimorphism to have separate sets of sprites in Gen IV and beyond."))
1018 order = Column(Integer, nullable=False, index=True,
1019 info=dict(description=u"Order for sorting. Almost national order, except families and forms are grouped together."))
1020 name = TextColumn(Unicode(20), nullable=False, index=True, plural='names',
1021 info=dict(description="The name", format='plaintext', official=True))
1022
1023 ### Stuff to handle alternate Pokémon forms
1024
1025 @property
1026 def form(self):
1027 u"""Returns the Pokémon's form, using its default form as fallback."""
1028
1029 return self.unique_form or self.default_form
1030
1031 @property
1032 def is_base_form(self):
1033 u"""Returns True iff the Pokémon is the base form for its species,
1034 e.g. Land Shaymin.
1035 """
1036
1037 return self.unique_form is None or self.unique_form.is_default
1038
1039 @property
1040 def form_name(self):
1041 u"""Returns the Pokémon's form name if it represents a particular form
1042 and that form has a name, or None otherwise.
1043 """
1044
1045 # If self.unique_form is None, the short-circuit "and" will go ahead
1046 # and return that. Otherwise, it'll return the form's name, which may
1047 # also be None.
1048 return self.unique_form and self.unique_form.name
1049
1050 @property
1051 def full_name(self):
1052 u"""Returns the Pokémon's name, including its form if applicable."""
1053
1054 if self.form_name:
1055 return u'{0} {1}'.format(self.form_name, self.name)
1056 else:
1057 return self.name
1058
1059 @property
1060 def normal_form(self):
1061 u"""Returns the normal form for this Pokémon; i.e., this will return
1062 regular Deoxys when called on any Deoxys form.
1063 """
1064
1065 if self.unique_form:
1066 return self.unique_form.form_base_pokemon
1067 return self
1068
1069 ### Not forms!
1070
1071 def stat(self, stat_name):
1072 u"""Returns a PokemonStat record for the given stat name (or Stat row
1073 object). Uses the normal has-many machinery, so all the stats are
1074 effectively cached.
1075 """
1076 if isinstance(stat_name, Stat):
1077 stat_name = stat_name.name
1078
1079 for pokemon_stat in self.stats:
1080 if pokemon_stat.stat.name == stat_name:
1081 return pokemon_stat
1082
1083 raise KeyError(u'No stat named %s' % stat_name)
1084
1085 @property
1086 def better_damage_class(self):
1087 u"""Returns the MoveDamageClass that this Pokémon is best suited for,
1088 based on its attack stats.
1089
1090 If the attack stats are about equal (within 5), returns None. The
1091 value None, not the damage class called 'None'.
1092 """
1093 phys = self.stat(u'Attack')
1094 spec = self.stat(u'Special Attack')
1095
1096 diff = phys.base_stat - spec.base_stat
1097
1098 if diff > 5:
1099 return phys.stat.damage_class
1100 elif diff < -5:
1101 return spec.stat.damage_class
1102 else:
1103 return None
1104
1105 class PokemonAbility(TableBase):
1106 u"""Maps an ability to a Pokémon that can have it
1107 """
1108 __tablename__ = 'pokemon_abilities'
1109 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1110 info=dict(description=u"ID of the Pokémon"))
1111 ability_id = Column(Integer, ForeignKey('abilities.id'), nullable=False,
1112 info=dict(description=u"ID of the ability"))
1113 # XXX having both a method and a slot is kind of gross. "slot" is a
1114 # misnomer, anyway: duplicate abilities don't appear in slot 2.
1115 # Probably should replace that with "order".
1116 is_dream = Column(Boolean, nullable=False, index=True,
1117 info=dict(description=u"Whether this is a Dream World ability"))
1118 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1119 info=dict(description=u"The ability slot, i.e. 1 or 2 for gen. IV"))
1120
1121 class PokemonColor(TableBase):
1122 u"""The "Pokédex color" of a Pokémon species. Usually based on the Pokémon's color.
1123 """
1124 __tablename__ = 'pokemon_colors'
1125 __singlename__ = 'pokemon_color'
1126 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1127 info=dict(description=u"ID of the Pokémon"))
1128 identifier = Column(Unicode(6), nullable=False,
1129 info=dict(description=u"An identifier", format='identifier'))
1130 name = TextColumn(Unicode(6), nullable=False, index=True, plural='names',
1131 info=dict(description="The name", format='plaintext', official=True))
1132
1133 class PokemonDexNumber(TableBase):
1134 u"""The number of a Pokémon in a particular Pokédex (e.g. Jigglypuff is #138 in Hoenn's 'dex)
1135 """
1136 __tablename__ = 'pokemon_dex_numbers'
1137 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1138 info=dict(description=u"ID of the Pokémon"))
1139 pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), primary_key=True, nullable=False, autoincrement=False,
1140 info=dict(description=u"ID of the Pokédex"))
1141 pokedex_number = Column(Integer, nullable=False,
1142 info=dict(description=u"Number of the Pokémon in that the Pokédex"))
1143
1144 class PokemonEggGroup(TableBase):
1145 u"""Maps an Egg group to a Pokémon; each Pokémon belongs to one or two egg groups
1146 """
1147 __tablename__ = 'pokemon_egg_groups'
1148 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1149 info=dict(description=u"ID of the Pokémon"))
1150 egg_group_id = Column(Integer, ForeignKey('egg_groups.id'), primary_key=True, nullable=False, autoincrement=False,
1151 info=dict(description=u"ID of the egg group"))
1152
1153 class PokemonEvolution(TableBase):
1154 u"""A required action ("trigger") and the conditions under which the trigger
1155 must occur to cause a Pokémon to evolve.
1156
1157 Any condition may be null if it does not apply for a particular Pokémon.
1158 """
1159 __tablename__ = 'pokemon_evolution'
1160 from_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False,
1161 info=dict(description=u"The ID of the pre-evolution Pokémon."))
1162 to_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1163 info=dict(description=u"The ID of the post-evolution Pokémon."))
1164 evolution_trigger_id = Column(Integer, ForeignKey('evolution_triggers.id'), nullable=False,
1165 info=dict(description=u"The ID of the evolution trigger."))
1166 trigger_item_id = Column(Integer, ForeignKey('items.id'), nullable=True,
1167 info=dict(description=u"The ID of the item that must be used on the Pokémon."))
1168 minimum_level = Column(Integer, nullable=True,
1169 info=dict(description=u"The minimum level for the Pokémon."))
1170 gender = Column(Enum('male', 'female', name='pokemon_evolution_gender'), nullable=True,
1171 info=dict(description=u"The Pokémon's required gender, or None if gender doesn't matter"))
1172 location_id = Column(Integer, ForeignKey('locations.id'), nullable=True,
1173 info=dict(description=u"The ID of the location the evolution must be triggered at."))
1174 held_item_id = Column(Integer, ForeignKey('items.id'), nullable=True,
1175 info=dict(description=u"The ID of the item the Pokémon must hold."))
1176 time_of_day = Column(Enum('day', 'night', name='pokemon_evolution_time_of_day'), nullable=True,
1177 info=dict(description=u"The required time of day."))
1178 known_move_id = Column(Integer, ForeignKey('moves.id'), nullable=True,
1179 info=dict(description=u"The ID of the move the Pokémon must know."))
1180 minimum_happiness = Column(Integer, nullable=True,
1181 info=dict(description=u"The minimum happiness value the Pokémon must have."))
1182 minimum_beauty = Column(Integer, nullable=True,
1183 info=dict(description=u"The minimum Beauty value the Pokémon must have."))
1184 relative_physical_stats = Column(Integer, nullable=True,
1185 info=dict(description=u"The required relation between the Pokémon's Attack and Defense stats, as sgn(atk-def)."))
1186 party_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=True,
1187 info=dict(description=u"The ID of the Pokémon that must be present in the party."))
1188 trade_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=True,
1189 info=dict(description=u"The ID of the Pokémon for which this Pokémon must be traded."))
1190
1191 class PokemonFlavorText(TableBase, LanguageSpecific):
1192 u"""In-game Pokédex descrption of a Pokémon.
1193 """
1194 __tablename__ = 'pokemon_flavor_text'
1195 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1196 info=dict(description=u"ID of the Pokémon"))
1197 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False,
1198 info=dict(description=u"ID of the version that has this flavor text"))
1199 flavor_text = Column(Unicode(255), nullable=False,
1200 info=dict(description=u"ID of the version that has this flavor text", official=True, format='gametext'))
1201
1202 class PokemonForm(TableBase):
1203 u"""An individual form of a Pokémon.
1204
1205 Pokémon that do not have separate forms are still given a single row to
1206 represent their single form.
1207 """
1208 __tablename__ = 'pokemon_forms'
1209 __singlename__ = 'pokemon_form'
1210 id = Column(Integer, primary_key=True, nullable=False,
1211 info=dict(description=u'A unique ID for this form.'))
1212 identifier = Column(Unicode(16), nullable=True,
1213 info=dict(description=u"An identifier", format='identifier'))
1214 form_base_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, autoincrement=False,
1215 info=dict(description=u'The ID of the base Pokémon for this form.'))
1216 unique_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), autoincrement=False,
1217 info=dict(description=u'The ID of a Pokémon that represents specifically this form, for Pokémon with functionally-different forms like Wormadam.'))
1218 introduced_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), autoincrement=False,
1219 info=dict(description=u'The ID of the version group in which this form first appeared.'))
1220 is_default = Column(Boolean, nullable=False,
1221 info=dict(description=u'Set for exactly one form used as the default for each species.'))
1222 order = Column(Integer, nullable=False, autoincrement=False,
1223 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.'))
1224 name = TextColumn(Unicode(16), nullable=False, index=True, plural='names',
1225 info=dict(description="The name", format='plaintext', official=True))
1226
1227 @property
1228 def pokemon(self):
1229 u"""Returns the Pokémon for this form, using the form base as fallback.
1230 """
1231
1232 return self.unique_pokemon or self.form_base_pokemon
1233
1234 @property
1235 def full_name(self):
1236 u"""Returns the full name of this form, e.g. "Plant Cloak"."""
1237
1238 if not self.name:
1239 return None
1240 elif self.form_group and self.form_group.term:
1241 return u'{0} {1}'.format(self.name, self.form_group.term)
1242 else:
1243 return self.name
1244
1245 @property
1246 def pokemon_name(self):
1247 u"""Returns the name of this Pokémon with this form, e.g. "Plant
1248 Burmy".
1249 """
1250
1251 if self.name:
1252 return u'{0} {1}'.format(self.name, self.form_base_pokemon.name)
1253 else:
1254 return self.form_base_pokemon.name
1255
1256 class PokemonFormGroup(TableBase):
1257 u"""Information about a Pokémon's forms as a group."""
1258 __tablename__ = 'pokemon_form_groups'
1259 __singlename__ = 'pokemon_form_group'
1260 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1261 info=dict(description=u"ID of the base form Pokémon"))
1262 term = ProseColumn(Unicode(16), plural='terms', nullable=True,
1263 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'))
1264 is_battle_only = Column(Boolean, nullable=False,
1265 info=dict(description=u"Set iff the forms only change in battle"))
1266 description = ProseColumn(markdown.MarkdownColumn(1024), plural='descriptions', nullable=False,
1267 info=dict(description=u"Description of how the forms work", format='markdown'))
1268 PokemonFormGroup.id = PokemonFormGroup.pokemon_id
1269
1270 class PokemonFormPokeathlonStat(TableBase):
1271 u"""A Pokémon form's performance in one Pokéathlon stat."""
1272 __tablename__ = 'pokemon_form_pokeathlon_stats'
1273 pokemon_form_id = Column(Integer, ForeignKey('pokemon_forms.id'), primary_key=True, nullable=False, autoincrement=False,
1274 info=dict(description=u'The ID of the Pokémon form.'))
1275 pokeathlon_stat_id = Column(Integer, ForeignKey('pokeathlon_stats.id'), primary_key=True, nullable=False, autoincrement=False,
1276 info=dict(description=u'The ID of the Pokéathlon stat.'))
1277 minimum_stat = Column(Integer, nullable=False, autoincrement=False,
1278 info=dict(description=u'The minimum value for this stat for this Pokémon form.'))
1279 base_stat = Column(Integer, nullable=False, autoincrement=False,
1280 info=dict(description=u'The default value for this stat for this Pokémon form.'))
1281 maximum_stat = Column(Integer, nullable=False, autoincrement=False,
1282 info=dict(description=u'The maximum value for this stat for this Pokémon form.'))
1283
1284 class PokemonHabitat(TableBase):
1285 u"""The habitat of a Pokémon, as given in the FireRed/LeafGreen version Pokédex
1286 """
1287 __tablename__ = 'pokemon_habitats'
1288 __singlename__ = 'pokemon_habitat'
1289 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1290 info=dict(description=u"A numeric ID"))
1291 identifier = Column(Unicode(16), nullable=False,
1292 info=dict(description=u"An identifier", format='identifier'))
1293 name = TextColumn(Unicode(16), nullable=False, index=True, plural='names',
1294 info=dict(description="The name", format='plaintext', official=True))
1295
1296 class PokemonInternalID(TableBase):
1297 u"""The number of a Pokémon a game uses internally
1298 """
1299 __tablename__ = 'pokemon_internal_ids'
1300 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, autoincrement=False, nullable=False,
1301 info=dict(description=u"Database ID of the Pokémon"))
1302 generation_id = Column(Integer, ForeignKey('generations.id'), primary_key=True, autoincrement=False, nullable=False,
1303 info=dict(description=u"Database ID of the generation"))
1304 internal_id = Column(Integer, nullable=False,
1305 info=dict(description=u"Internal ID the generation's games use for the Pokémon"))
1306
1307 class PokemonItem(TableBase):
1308 u"""Record of an item a Pokémon can hold in the wild
1309 """
1310 __tablename__ = 'pokemon_items'
1311 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1312 info=dict(description=u"ID of the Pokémon"))
1313 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False,
1314 info=dict(description=u"ID of the version this applies to"))
1315 item_id = Column(Integer, ForeignKey('items.id'), primary_key=True, nullable=False, autoincrement=False,
1316 info=dict(description=u"ID of the item"))
1317 rarity = Column(Integer, nullable=False,
1318 info=dict(description=u"Chance of the Pokémon holding the item, in percent"))
1319
1320 class PokemonMove(TableBase):
1321 u"""Record of a move a Pokémon can learn
1322 """
1323 __tablename__ = 'pokemon_moves'
1324 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, index=True,
1325 info=dict(description=u"ID of the Pokémon"))
1326 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False, index=True,
1327 info=dict(description=u"ID of the version group this applies to"))
1328 move_id = Column(Integer, ForeignKey('moves.id'), nullable=False, index=True,
1329 info=dict(description=u"ID of the move"))
1330 pokemon_move_method_id = Column(Integer, ForeignKey('pokemon_move_methods.id'), nullable=False, index=True,
1331 info=dict(description=u"ID of the method this move is learned by"))
1332 level = Column(Integer, nullable=True, index=True,
1333 info=dict(description=u"Level the move is learned at, if applicable"))
1334 order = Column(Integer, nullable=True,
1335 info=dict(description=u"A sort key to produce the correct ordering when all else is equal")) # XXX: This needs a better description
1336
1337 __table_args__ = (
1338 PrimaryKeyConstraint('pokemon_id', 'version_group_id', 'move_id', 'pokemon_move_method_id', 'level'),
1339 {},
1340 )
1341
1342 class PokemonMoveMethod(TableBase):
1343 u"""A method a move can be learned by, such as "Level up" or "Tutor".
1344 """
1345 __tablename__ = 'pokemon_move_methods'
1346 __singlename__ = 'pokemon_move_method'
1347 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1348 info=dict(description=u"A numeric ID"))
1349 identifier = Column(Unicode(64), nullable=False,
1350 info=dict(description=u"An identifier", format='identifier'))
1351 description = ProseColumn(Unicode(255), plural='descriptions', nullable=False,
1352 info=dict(description=u"A detailed description of how the method works", format='plaintext'))
1353 name = ProseColumn(Unicode(64), nullable=False, index=True, plural='names',
1354 info=dict(description="The name", format='plaintext', official=False))
1355
1356 class PokemonShape(TableBase):
1357 u"""The shape of a Pokémon's body, as used in generation IV Pokédexes.
1358 """
1359 __tablename__ = 'pokemon_shapes'
1360 __singlename__ = 'pokemon_shape'
1361 id = Column(Integer, primary_key=True, nullable=False,
1362 info=dict(description=u"A numeric ID"))
1363 identifier = Column(Unicode(24), nullable=False,
1364 info=dict(description=u"An identifier", format='identifier'))
1365 awesome_name = ProseColumn(Unicode(16), plural='awesome_names', nullable=False,
1366 info=dict(description=u"A splendiferous name of the body shape", format='plaintext'))
1367 name = ProseColumn(Unicode(24), nullable=False, index=True, plural='names',
1368 info=dict(description="The name", format='plaintext', official=False))
1369
1370 class PokemonStat(TableBase):
1371 u"""A stat value of a Pokémon
1372 """
1373 __tablename__ = 'pokemon_stats'
1374 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1375 info=dict(description=u"ID of the Pokémon"))
1376 stat_id = Column(Integer, ForeignKey('stats.id'), primary_key=True, nullable=False, autoincrement=False,
1377 info=dict(description=u"ID of the stat"))
1378 base_stat = Column(Integer, nullable=False,
1379 info=dict(description=u"The base stat"))
1380 effort = Column(Integer, nullable=False,
1381 info=dict(description=u"The effort increase in this stat gained when this Pokémon is defeated"))
1382
1383 class PokemonType(TableBase):
1384 u"""Maps a type to a Pokémon. Each Pokémon has 1 or 2 types.
1385 """
1386 __tablename__ = 'pokemon_types'
1387 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False,
1388 info=dict(description=u"ID of the Pokémon"))
1389 type_id = Column(Integer, ForeignKey('types.id'), nullable=False,
1390 info=dict(description=u"ID of the type"))
1391 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False,
1392 info=dict(description=u"The type's slot, 1 or 2, used to sort types if there are two of them"))
1393
1394 class Region(TableBase):
1395 u"""Major areas of the world: Kanto, Johto, etc.
1396 """
1397 __tablename__ = 'regions'
1398 __singlename__ = 'region'
1399 id = Column(Integer, primary_key=True, nullable=False,
1400 info=dict(description=u"A numeric ID"))
1401 identifier = Column(Unicode(16), nullable=False,
1402 info=dict(description=u"An identifier", format='identifier'))
1403 name = TextColumn(Unicode(16), nullable=False, index=True, plural='names',
1404 info=dict(description="The name", format='plaintext', official=True))
1405
1406 class Stat(TableBase):
1407 u"""A Stat, such as Attack or Speed
1408 """
1409 __tablename__ = 'stats'
1410 __singlename__ = 'stat'
1411 id = Column(Integer, primary_key=True, nullable=False,
1412 info=dict(description=u"A numeric ID"))
1413 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=True,
1414 info=dict(description=u"For offensive and defensive stats, the damage this stat relates to; otherwise None (the NULL value)"))
1415 identifier = Column(Unicode(16), nullable=False,
1416 info=dict(description=u"An identifier", format='identifier'))
1417 name = TextColumn(Unicode(16), nullable=False, index=True, plural='names',
1418 info=dict(description="The name", format='plaintext', official=True))
1419
1420 class StatHint(TableBase):
1421 u"""Flavor text for genes that appears in a Pokémon's summary. Sometimes
1422 called "characteristics".
1423 """
1424 __tablename__ = 'stat_hints'
1425 __singlename__ = 'stat_hint'
1426 id = Column(Integer, primary_key=True, nullable=False,
1427 info=dict(description=u"A numeric ID"))
1428 stat_id = Column(Integer, ForeignKey('stats.id'), nullable=False,
1429 info=dict(description=u"ID of the highest stat"))
1430 gene_mod_5 = Column(Integer, nullable=False, index=True,
1431 info=dict(description=u"Value of the highest stat modulo 5"))
1432 message = TextColumn(Unicode(24), plural='messages', nullable=False, index=True, unique=True,
1433 info=dict(description=u"The text displayed", official=True, format='plaintext'))
1434
1435 class SuperContestCombo(TableBase):
1436 u"""Combo of two moves in a Super Contest.
1437 """
1438 __tablename__ = 'super_contest_combos'
1439 first_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
1440 info=dict(description=u"The ID of the first move in the combo."))
1441 second_move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False,
1442 info=dict(description=u"The ID of the second and last move."))
1443
1444 class SuperContestEffect(TableBase):
1445 u"""An effect a move can have when used in the Super Contest
1446 """
1447 __tablename__ = 'super_contest_effects'
1448 __singlename__ = 'super_contest_effect'
1449 id = Column(Integer, primary_key=True, nullable=False,
1450 info=dict(description=u"This effect's unique ID."))
1451 appeal = Column(SmallInteger, nullable=False,
1452 info=dict(description=u"The number of hearts the user gains."))
1453 flavor_text = ProseColumn(Unicode(64), plural='flavor_texts', nullable=False,
1454 info=dict(description=u"A description of the effect.", format='plaintext'))
1455
1456 class TypeEfficacy(TableBase):
1457 u"""The damage multiplier used when a move of a particular type damages a
1458 Pokémon of a particular other type.
1459 """
1460 __tablename__ = 'type_efficacy'
1461 damage_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False,
1462 info=dict(description=u"The ID of the damaging type."))
1463 target_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False,
1464 info=dict(description=u"The ID of the defending Pokémon's type."))
1465 damage_factor = Column(Integer, nullable=False,
1466 info=dict(description=u"The multiplier, as a percentage of damage inflicted."))
1467
1468 class Type(TableBase):
1469 u"""Any of the elemental types Pokémon and moves can have."""
1470 __tablename__ = 'types'
1471 __singlename__ = 'type'
1472 id = Column(Integer, primary_key=True, nullable=False,
1473 info=dict(description=u"A unique ID for this type."))
1474 identifier = Column(Unicode(12), nullable=False,
1475 info=dict(description=u"An identifier", format='identifier'))
1476 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
1477 info=dict(description=u"The ID of the generation this type first appeared in."))
1478 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=True,
1479 info=dict(description=u"The ID of the damage class this type's moves had before Generation IV, null if not applicable (e.g. ???)."))
1480 name = TextColumn(Unicode(12), nullable=False, index=True, plural='names',
1481 info=dict(description="The name", format='plaintext', official=True))
1482
1483 class VersionGroup(TableBase):
1484 u"""A group of versions, containing either two paired versions (such as Red
1485 and Blue) or a single game (such as Yellow.)
1486 """
1487 __tablename__ = 'version_groups'
1488 id = Column(Integer, primary_key=True, nullable=False,
1489 info=dict(description=u"This version group's unique ID."))
1490 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
1491 info=dict(description=u"The ID of the generation the games in this group belong to."))
1492 pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), nullable=False,
1493 info=dict(description=u"The ID of the regional Pokédex used in this version group."))
1494
1495 class VersionGroupRegion(TableBase):
1496 u"""Maps a version group to a region that appears in it."""
1497 __tablename__ = 'version_group_regions'
1498 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
1499 info=dict(description=u"The ID of the version group."))
1500 region_id = Column(Integer, ForeignKey('regions.id'), primary_key=True, nullable=False,
1501 info=dict(description=u"The ID of the region."))
1502
1503 class Version(TableBase):
1504 u"""An individual main-series Pokémon game."""
1505 __tablename__ = 'versions'
1506 __singlename__ = 'version'
1507 id = Column(Integer, primary_key=True, nullable=False,
1508 info=dict(description=u"A unique ID for this version."))
1509 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False,
1510 info=dict(description=u"The ID of the version group this game belongs to."))
1511 identifier = Column(Unicode(32), nullable=False,
1512 info=dict(description=u'And identifier', format='identifier'))
1513 name = TextColumn(Unicode(32), nullable=False, index=True, plural='names',
1514 info=dict(description="The name", format='plaintext', official=True))
1515
1516
1517 ### Relations down here, to avoid ordering problems
1518 Ability.changelog = relation(AbilityChangelog,
1519 order_by=AbilityChangelog.changed_in_version_group_id.desc(),
1520 backref='ability',
1521 )
1522 Ability.flavor_text = relation(AbilityFlavorText, order_by=AbilityFlavorText.version_group_id, backref='ability')
1523 Ability.generation = relation(Generation, backref='abilities')
1524 Ability.all_pokemon = relation(Pokemon,
1525 secondary=PokemonAbility.__table__,
1526 order_by=Pokemon.order,
1527 back_populates='all_abilities',
1528 )
1529 Ability.pokemon = relation(Pokemon,
1530 secondary=PokemonAbility.__table__,
1531 primaryjoin=and_(
1532 PokemonAbility.ability_id == Ability.id,
1533 PokemonAbility.is_dream == False
1534 ),
1535 order_by=Pokemon.order,
1536 back_populates='abilities',
1537 )
1538 Ability.dream_pokemon = relation(Pokemon,
1539 secondary=PokemonAbility.__table__,
1540 primaryjoin=and_(
1541 PokemonAbility.ability_id == Ability.id,
1542 PokemonAbility.is_dream == True
1543 ),
1544 order_by=Pokemon.order,
1545 back_populates='dream_ability',
1546 )
1547
1548 AbilityChangelog.changed_in = relation(VersionGroup, backref='ability_changelog')
1549
1550 AbilityFlavorText.version_group = relation(VersionGroup)
1551
1552 Berry.berry_firmness = relation(BerryFirmness, backref='berries')
1553 Berry.firmness = association_proxy('berry_firmness', 'name')
1554 Berry.flavors = relation(BerryFlavor, order_by=BerryFlavor.contest_type_id, backref='berry')
1555 Berry.natural_gift_type = relation(Type)
1556
1557 BerryFlavor.contest_type = relation(ContestType)
1558
1559 ContestCombo.first = relation(Move, primaryjoin=ContestCombo.first_move_id==Move.id,
1560 backref='contest_combo_first')
1561 ContestCombo.second = relation(Move, primaryjoin=ContestCombo.second_move_id==Move.id,
1562 backref='contest_combo_second')
1563
1564 Encounter.location_area = relation(LocationArea, backref='encounters')
1565 Encounter.pokemon = relation(Pokemon, backref='encounters')
1566 Encounter.version = relation(Version, backref='encounters')
1567 Encounter.slot = relation(EncounterSlot, backref='encounters')
1568
1569 EncounterConditionValue.condition = relation(EncounterCondition, backref='values')
1570
1571 Encounter.condition_value_map = relation(EncounterConditionValueMap, backref='encounter')
1572 Encounter.condition_values = association_proxy('condition_value_map', 'condition_value')
1573 EncounterConditionValueMap.condition_value = relation(EncounterConditionValue,
1574 backref='encounter_map')
1575
1576 EncounterSlot.terrain = relation(EncounterTerrain, backref='slots')
1577 EncounterSlot.version_group = relation(VersionGroup)
1578
1579 EvolutionChain.growth_rate = relation(GrowthRate, backref='evolution_chains')
1580 EvolutionChain.baby_trigger_item = relation(Item, backref='evolution_chains')
1581 EvolutionChain.pokemon = relation(Pokemon, order_by=Pokemon.order, back_populates='evolution_chain')
1582
1583 Experience.growth_rate = relation(GrowthRate, backref='experience_table')
1584
1585 Generation.canonical_pokedex = relation(Pokedex, backref='canonical_for_generation')
1586 Generation.versions = relation(Version, secondary=VersionGroup.__table__)
1587 Generation.main_region = relation(Region)
1588
1589 GrowthRate.max_experience_obj = relation(Experience, primaryjoin=and_(Experience.growth_rate_id == GrowthRate.id, Experience.level == 100), uselist=False)
1590 GrowthRate.max_experience = association_proxy('max_experience_obj', 'experience')
1591
1592 Item.berry = relation(Berry, uselist=False, backref='item')
1593 Item.flags = relation(ItemFlag, secondary=ItemFlagMap.__table__)
1594 Item.flavor_text = relation(ItemFlavorText, order_by=ItemFlavorText.version_group_id.asc(), backref='item')
1595 Item.fling_effect = relation(ItemFlingEffect, backref='items')
1596 Item.machines = relation(Machine, order_by=Machine.version_group_id.asc())
1597 Item.category = relation(ItemCategory)
1598 Item.pocket = association_proxy('category', 'pocket')
1599
1600 ItemCategory.items = relation(Item, order_by=Item.identifier)
1601 ItemCategory.pocket = relation(ItemPocket)
1602
1603 ItemFlavorText.version_group = relation(VersionGroup)
1604
1605 ItemInternalID.item = relation(Item, backref='internal_ids')
1606 ItemInternalID.generation = relation(Generation)
1607
1608 ItemPocket.categories = relation(ItemCategory, order_by=ItemCategory.identifier)
1609
1610 Location.region = relation(Region, backref='locations')
1611
1612 LocationArea.location = relation(Location, backref='areas')
1613
1614 LocationInternalID.location = relation(Location, backref='internal_ids')
1615 LocationInternalID.generation = relation(Generation)
1616
1617 Machine.item = relation(Item)
1618 Machine.version_group = relation(VersionGroup)
1619
1620 Move.changelog = relation(MoveChangelog,
1621 order_by=MoveChangelog.changed_in_version_group_id.desc(),
1622 backref='move',
1623 )
1624 Move.contest_effect = relation(ContestEffect, backref='moves')
1625 Move.contest_combo_next = association_proxy('contest_combo_first', 'second')
1626 Move.contest_combo_prev = association_proxy('contest_combo_second', 'first')
1627 Move.contest_type = relation(ContestType, backref='moves')
1628 Move.damage_class = relation(MoveDamageClass, backref='moves')
1629 Move.flags = association_proxy('move_flags', 'flag')
1630 Move.flavor_text = relation(MoveFlavorText, order_by=MoveFlavorText.version_group_id, backref='move')
1631 Move.generation = relation(Generation, backref='moves')
1632 Move.machines = relation(Machine, backref='move')
1633 Move.meta = relation(MoveMeta, uselist=False, backref='move')
1634 Move.meta_stat_changes = relation(MoveMetaStatChange)
1635 Move.move_effect = relation(MoveEffect, backref='moves')
1636 Move.move_flags = relation(MoveFlag, backref='move')
1637 Move.super_contest_effect = relation(SuperContestEffect, backref='moves')
1638 Move.super_contest_combo_next = association_proxy('super_contest_combo_first', 'second')
1639 Move.super_contest_combo_prev = association_proxy('super_contest_combo_second', 'first')
1640 Move.target = relation(MoveTarget, backref='moves')
1641 Move.type = relation(Type, back_populates='moves')
1642
1643 MoveChangelog.changed_in = relation(VersionGroup, backref='move_changelog')
1644 MoveChangelog.move_effect = relation(MoveEffect, backref='move_changelog')
1645 MoveChangelog.type = relation(Type, backref='move_changelog')
1646
1647 MoveEffect.category_map = relation(MoveEffectCategoryMap)
1648 MoveEffect.categories = association_proxy('category_map', 'category')
1649 MoveEffect.changelog = relation(MoveEffectChangelog,
1650 order_by=MoveEffectChangelog.changed_in_version_group_id.desc(),
1651 backref='move_effect',
1652 )
1653 MoveEffectCategoryMap.category = relation(MoveEffectCategory)
1654
1655 MoveEffectChangelog.changed_in = relation(VersionGroup, backref='move_effect_changelog')
1656
1657 MoveFlag.flag = relation(MoveFlagType)
1658
1659 MoveFlavorText.version_group = relation(VersionGroup)
1660
1661 MoveMeta.category = relation(MoveMetaCategory, backref='move_meta')
1662 MoveMeta.ailment = relation(MoveMetaAilment, backref='move_meta')
1663
1664 MoveMetaStatChange.stat = relation(Stat, backref='move_meta_stat_changes')
1665
1666 Nature.decreased_stat = relation(Stat, primaryjoin=Nature.decreased_stat_id==Stat.id,
1667 backref='decreasing_natures')
1668 Nature.increased_stat = relation(Stat, primaryjoin=Nature.increased_stat_id==Stat.id,
1669 backref='increasing_natures')
1670 Nature.hates_flavor = relation(ContestType, primaryjoin=Nature.hates_flavor_id==ContestType.id,
1671 backref='hating_natures')
1672 Nature.likes_flavor = relation(ContestType, primaryjoin=Nature.likes_flavor_id==ContestType.id,
1673 backref='liking_natures')
1674 Nature.battle_style_preferences = relation(NatureBattleStylePreference,
1675 order_by=NatureBattleStylePreference.move_battle_style_id,
1676 backref='nature')
1677 Nature.pokeathlon_effects = relation(NaturePokeathlonStat, order_by=NaturePokeathlonStat.pokeathlon_stat_id)
1678
1679 NatureBattleStylePreference.battle_style = relation(MoveBattleStyle, backref='nature_preferences')
1680
1681 NaturePokeathlonStat.pokeathlon_stat = relation(PokeathlonStat, backref='nature_effects')
1682
1683 Pokedex.region = relation(Region, backref='pokedexes')
1684 Pokedex.version_groups = relation(VersionGroup, order_by=VersionGroup.id, back_populates='pokedex')
1685
1686 Pokemon.all_abilities = relation(Ability,
1687 secondary=PokemonAbility.__table__,
1688 order_by=PokemonAbility.slot,
1689 )
1690 Pokemon.abilities = relation(Ability,
1691 secondary=PokemonAbility.__table__,
1692 primaryjoin=and_(
1693 Pokemon.id == PokemonAbility.pokemon_id,
1694 PokemonAbility.is_dream == False,
1695 ),
1696 order_by=PokemonAbility.slot,
1697 )
1698 Pokemon.dream_ability = relation(Ability,
1699 secondary=PokemonAbility.__table__,
1700 primaryjoin=and_(
1701 Pokemon.id == PokemonAbility.pokemon_id,
1702 PokemonAbility.is_dream == True,
1703 ),
1704 uselist=False,
1705 )
1706 Pokemon.pokemon_color = relation(PokemonColor, backref='pokemon')
1707 Pokemon.color = association_proxy('pokemon_color', 'name')
1708 Pokemon.dex_numbers = relation(PokemonDexNumber, order_by=PokemonDexNumber.pokedex_id.asc(), backref='pokemon')
1709 Pokemon.egg_groups = relation(EggGroup, secondary=PokemonEggGroup.__table__,
1710 order_by=PokemonEggGroup.egg_group_id,
1711 backref=backref('pokemon', order_by=Pokemon.order))
1712 Pokemon.evolution_chain = relation(EvolutionChain, back_populates='pokemon')
1713 Pokemon.child_pokemon = relation(Pokemon,
1714 primaryjoin=Pokemon.id==PokemonEvolution.from_pokemon_id,
1715 secondary=PokemonEvolution.__table__,
1716 secondaryjoin=PokemonEvolution.to_pokemon_id==Pokemon.id,
1717 backref=backref('parent_pokemon', uselist=False),
1718 )
1719 Pokemon.flavor_text = relation(PokemonFlavorText, order_by=PokemonFlavorText.version_id.asc(), backref='pokemon')
1720 Pokemon.forms = relation(PokemonForm, primaryjoin=Pokemon.id==PokemonForm.form_base_pokemon_id,
1721 order_by=(PokemonForm.order.asc(), PokemonForm.identifier.asc()))
1722 Pokemon.default_form = relation(PokemonForm,
1723 primaryjoin=and_(Pokemon.id==PokemonForm.form_base_pokemon_id, PokemonForm.is_default==True),
1724 uselist=False,
1725 )
1726 Pokemon.pokemon_habitat = relation(PokemonHabitat, backref='pokemon')
1727 Pokemon.habitat = association_proxy('pokemon_habitat', 'name')
1728 Pokemon.items = relation(PokemonItem, backref='pokemon')
1729 Pokemon.generation = relation(Generation, backref='pokemon')
1730 Pokemon.shape = relation(PokemonShape, backref='pokemon')
1731 Pokemon.stats = relation(PokemonStat, backref='pokemon', order_by=PokemonStat.stat_id.asc())
1732 Pokemon.types = relation(Type, secondary=PokemonType.__table__,
1733 order_by=PokemonType.slot.asc(),
1734 back_populates='pokemon')
1735
1736 PokemonDexNumber.pokedex = relation(Pokedex)
1737
1738 PokemonEvolution.from_pokemon = relation(Pokemon,
1739 primaryjoin=PokemonEvolution.from_pokemon_id==Pokemon.id,
1740 backref='child_evolutions',
1741 )
1742 PokemonEvolution.to_pokemon = relation(Pokemon,
1743 primaryjoin=PokemonEvolution.to_pokemon_id==Pokemon.id,
1744 backref=backref('parent_evolution', uselist=False),
1745 )
1746 PokemonEvolution.child_evolutions = relation(PokemonEvolution,
1747 primaryjoin=PokemonEvolution.from_pokemon_id==PokemonEvolution.to_pokemon_id,
1748 foreign_keys=[PokemonEvolution.to_pokemon_id],
1749 backref=backref('parent_evolution',
1750 remote_side=[PokemonEvolution.from_pokemon_id],
1751 uselist=False,
1752 ),
1753 )
1754 PokemonEvolution.trigger = relation(EvolutionTrigger, backref='evolutions')
1755 PokemonEvolution.trigger_item = relation(Item,
1756 primaryjoin=PokemonEvolution.trigger_item_id==Item.id,
1757 backref='triggered_evolutions',
1758 )
1759 PokemonEvolution.held_item = relation(Item,
1760 primaryjoin=PokemonEvolution.held_item_id==Item.id,
1761 backref='required_for_evolutions',
1762 )
1763 PokemonEvolution.location = relation(Location, backref='triggered_evolutions')
1764 PokemonEvolution.known_move = relation(Move, backref='triggered_evolutions')
1765 PokemonEvolution.party_pokemon = relation(Pokemon,
1766 primaryjoin=PokemonEvolution.party_pokemon_id==Pokemon.id,
1767 backref='triggered_evolutions',
1768 )
1769 PokemonEvolution.trade_pokemon = relation(Pokemon,
1770 primaryjoin=PokemonEvolution.trade_pokemon_id==Pokemon.id,
1771 )
1772
1773 PokemonFlavorText.version = relation(Version)
1774
1775 PokemonForm.form_base_pokemon = relation(Pokemon, primaryjoin=PokemonForm.form_base_pokemon_id==Pokemon.id)
1776 PokemonForm.unique_pokemon = relation(Pokemon, backref=backref('unique_form', uselist=False),
1777 primaryjoin=PokemonForm.unique_pokemon_id==Pokemon.id)
1778 PokemonForm.version_group = relation(VersionGroup)
1779 PokemonForm.form_group = association_proxy('form_base_pokemon', 'form_group')
1780 PokemonForm.pokeathlon_stats = relation(PokemonFormPokeathlonStat,
1781 order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id,
1782 backref='pokemon_form')
1783
1784 PokemonFormGroup.pokemon = relation(Pokemon, backref=backref('form_group',
1785 uselist=False))
1786
1787 PokemonFormPokeathlonStat.pokeathlon_stat = relation(PokeathlonStat)
1788
1789 PokemonItem.item = relation(Item, backref='pokemon')
1790 PokemonItem.version = relation(Version)
1791
1792 PokemonMove.pokemon = relation(Pokemon, backref='pokemon_moves')
1793 PokemonMove.version_group = relation(VersionGroup)
1794 PokemonMove.machine = relation(Machine, backref='pokemon_moves',
1795 primaryjoin=and_(Machine.version_group_id==PokemonMove.version_group_id,
1796 Machine.move_id==PokemonMove.move_id),
1797 foreign_keys=[Machine.version_group_id, Machine.move_id],
1798 uselist=False)
1799 PokemonMove.move = relation(Move, backref='pokemon_moves')
1800 PokemonMove.method = relation(PokemonMoveMethod)
1801
1802 PokemonStat.stat = relation(Stat)
1803
1804 # This is technically a has-many; Generation.main_region_id -> Region.id
1805 Region.generation = relation(Generation, uselist=False)
1806 Region.version_group_regions = relation(VersionGroupRegion, backref='region',
1807 order_by='VersionGroupRegion.version_group_id')
1808 Region.version_groups = association_proxy('version_group_regions', 'version_group')
1809
1810 Stat.damage_class = relation(MoveDamageClass, backref='stats')
1811
1812 StatHint.stat = relation(Stat, backref='hints')
1813
1814 SuperContestCombo.first = relation(Move, primaryjoin=SuperContestCombo.first_move_id==Move.id,
1815 backref='super_contest_combo_first')
1816 SuperContestCombo.second = relation(Move, primaryjoin=SuperContestCombo.second_move_id==Move.id,
1817 backref='super_contest_combo_second')
1818
1819 Type.damage_efficacies = relation(TypeEfficacy,
1820 primaryjoin=Type.id
1821 ==TypeEfficacy.damage_type_id,
1822 backref='damage_type')
1823 Type.target_efficacies = relation(TypeEfficacy,
1824 primaryjoin=Type.id
1825 ==TypeEfficacy.target_type_id,
1826 backref='target_type')
1827
1828 Type.generation = relation(Generation, backref='types')
1829 Type.damage_class = relation(MoveDamageClass, backref='types')
1830 Type.pokemon = relation(Pokemon, secondary=PokemonType.__table__,
1831 order_by=Pokemon.order,
1832 back_populates='types')
1833 Type.moves = relation(Move, back_populates='type', order_by=Move.id)
1834
1835 Version.version_group = relation(VersionGroup, back_populates='versions')
1836 Version.generation = association_proxy('version_group', 'generation')
1837
1838 VersionGroup.versions = relation(Version, order_by=Version.id, back_populates='version_group')
1839 VersionGroup.generation = relation(Generation, backref='version_groups')
1840 VersionGroup.version_group_regions = relation(VersionGroupRegion, backref='version_group')
1841 VersionGroup.regions = association_proxy('version_group_regions', 'region')
1842 VersionGroup.pokedex = relation(Pokedex, back_populates='version_groups')
1843
1844
1845 ### Add text/prose tables
1846
1847 default_lang = u'en'
1848
1849 def create_translation_table(_table_name, foreign_class,
1850 _language_class=Language, **kwargs):
1851 """Creates a table that represents some kind of data attached to the given
1852 foreign class, but translated across several languages. Returns the new
1853 table's mapped class.
1854 TODO give it a __table__ or __init__?
1855
1856 `foreign_class` must have a `__singlename__`, currently only used to create
1857 the name of the foreign key column.
1858 TODO remove this requirement
1859
1860 Also supports the notion of a default language, which is attached to the
1861 session. This is English by default, for historical and practical reasons.
1862
1863 Usage looks like this:
1864
1865 class Foo(Base): ...
1866
1867 create_translation_table('foo_bars', Foo,
1868 name = Column(...),
1869 )
1870
1871 # Now you can do the following:
1872 foo.name
1873 foo.name_map['en']
1874 foo.foo_bars['en']
1875
1876 foo.name_map['en'] = "new name"
1877 del foo.name_map['en']
1878
1879 q.options(joinedload(Foo.default_translation))
1880 q.options(joinedload(Foo.foo_bars))
1881
1882 In the above example, the following attributes are added to Foo:
1883
1884 - `foo_bars`, a relation to the new table. It uses a dict-based collection
1885 class, where the keys are language identifiers and the values are rows in
1886 the created tables.
1887 - `foo_bars_local`, a relation to the row in the new table that matches the
1888 current default language.
1889
1890 Note that these are distinct relations. Even though the former necessarily
1891 includes the latter, SQLAlchemy doesn't treat them as linked; loading one
1892 will not load the other. Modifying both within the same transaction has
1893 undefined behavior.
1894
1895 For each column provided, the following additional attributes are added to
1896 Foo:
1897
1898 - `(column)_map`, an association proxy onto `foo_bars`.
1899 - `(column)`, an association proxy onto `foo_bars_local`.
1900
1901 Pardon the naming disparity, but the grammar suffers otherwise.
1902
1903 Modifying these directly is not likely to be a good idea.
1904 """
1905 # n.b.: _language_class only exists for the sake of tests, which sometimes
1906 # want to create tables entirely separate from the pokedex metadata
1907
1908 foreign_key_name = foreign_class.__singlename__ + '_id'
1909 # A foreign key "language_id" will clash with the language_id we naturally
1910 # put in every table. Rename it something else
1911 if foreign_key_name == 'language_id':
1912 # TODO change language_id below instead and rename this
1913 foreign_key_name = 'lang_id'
1914
1915 Translations = type(_table_name, (object,), {
1916 '_language_identifier': association_proxy('language', 'identifier'),
1917 })
1918
1919 # Create the table object
1920 table = Table(_table_name, foreign_class.__table__.metadata,
1921 Column(foreign_key_name, Integer, ForeignKey(foreign_class.id),
1922 primary_key=True, nullable=False),
1923 Column('language_id', Integer, ForeignKey(_language_class.id),
1924 primary_key=True, nullable=False),
1925 )
1926
1927 # Add ye columns
1928 # Column objects have a _creation_order attribute in ascending order; use
1929 # this to get the (unordered) kwargs sorted correctly
1930 kwitems = kwargs.items()
1931 kwitems.sort(key=lambda kv: kv[1]._creation_order)
1932 for name, column in kwitems:
1933 column.name = name
1934 table.append_column(column)
1935
1936 # Construct ye mapper
1937 mapper(Translations, table, properties={
1938 # TODO change to foreign_id
1939 'object_id': synonym(foreign_key_name),
1940 # TODO change this as appropriate
1941 'language': relation(_language_class,
1942 primaryjoin=table.c.language_id == _language_class.id,
1943 lazy='joined',
1944 innerjoin=True),
1945 # TODO does this need to join to the original table?
1946 })
1947
1948 # Add full-table relations to the original class
1949 # Class.foo_bars
1950 setattr(foreign_class, _table_name, relation(Translations,
1951 primaryjoin=foreign_class.id == Translations.object_id,
1952 collection_class=attribute_mapped_collection('language'),
1953 # TODO
1954 lazy='select',
1955 ))
1956 # Class.foo_bars_local
1957 # This is a bit clever; it uses bindparam() to make the join clause
1958 # modifiable on the fly. db sessions know the current language identifier
1959 # populates the bindparam.
1960 local_relation_name = _table_name + '_local'
1961 setattr(foreign_class, local_relation_name, relation(Translations,
1962 primaryjoin=and_(
1963 foreign_class.id == Translations.object_id,
1964 Translations._language_identifier ==
1965 bindparam('_default_language', required=True),
1966 ),
1967 uselist=False,
1968 # TODO MORESO HERE
1969 lazy='select',
1970 ))
1971
1972 # Add per-column proxies to the original class
1973 for name, column in kwitems:
1974 # Class.(column) -- accessor for the default language's value
1975 setattr(foreign_class, name,
1976 association_proxy(local_relation_name, name))
1977
1978 # Class.(column)_map -- accessor for the language dict
1979 # Need a custom creator since Translations doesn't have an init, and
1980 # these are passed as *args anyway
1981 def creator(language, value):
1982 row = Translations()
1983 row.language = language
1984 setattr(row, name, value)
1985 return row
1986 setattr(foreign_class, name + '_map',
1987 association_proxy(_table_name, name, creator=creator))
1988
1989 # Done
1990 return Translations
1991
1992 def makeTextTable(foreign_table_class, table_suffix_plural, table_suffix_singular, columns, lazy, Language=Language):
1993 # With "Language", we'd have two language_id. So, rename one to 'lang'
1994 foreign_key_name = foreign_table_class.__singlename__
1995 if foreign_key_name == 'language':
1996 foreign_key_name = 'lang'
1997
1998 table_name = foreign_table_class.__singlename__ + '_' + table_suffix_plural
1999
2000 class TranslatedStringsTable(object):
2001 __tablename__ = table_name
2002 _attrname = table_suffix_plural
2003 _language_identifier = association_proxy('language', 'identifier')
2004
2005 for column_name, column_name_plural, column in columns:
2006 column.name = column_name
2007 if not column.nullable:
2008 # A Python side default value, so that the strings can be set
2009 # one by one without the DB complaining about missing values
2010 column.default = ColumnDefault(u'')
2011
2012 table = Table(table_name, foreign_table_class.__table__.metadata,
2013 Column(foreign_key_name + '_id', Integer, ForeignKey(foreign_table_class.id),
2014 primary_key=True, nullable=False),
2015 Column('language_id', Integer, ForeignKey(Language.id),
2016 primary_key=True, index=True, nullable=False),
2017 *(column for name, plural, column in columns)
2018 )
2019
2020 mapper(TranslatedStringsTable, table,
2021 properties={
2022 "object_id": synonym(foreign_key_name + '_id'),
2023 "language": relation(Language,
2024 primaryjoin=table.c.language_id == Language.id,
2025 ),
2026 foreign_key_name: relation(foreign_table_class,
2027 primaryjoin=(foreign_table_class.id == table.c[foreign_key_name + "_id"]),
2028 backref=backref(table_suffix_plural,
2029 collection_class=attribute_mapped_collection('_language_identifier'),
2030 lazy=lazy,
2031 ),
2032 ),
2033 },
2034 )
2035
2036 # The relation to the object
2037 TranslatedStringsTable.object = getattr(TranslatedStringsTable, foreign_key_name)
2038
2039 # Link the tables themselves, so we can get them if needed
2040 TranslatedStringsTable.foreign_table_class = foreign_table_class
2041 setattr(foreign_table_class, table_suffix_singular + '_table', TranslatedStringsTable)
2042
2043 for column_name, column_name_plural, column in columns:
2044 # Provide a property with all the names, and an English accessor
2045 # for backwards compatibility
2046 def text_string_creator(language_code, string):
2047 row = TranslatedStringsTable()
2048 row._language_identifier = language_code
2049 setattr(row, column_name, string)
2050 return row
2051
2052 setattr(foreign_table_class, column_name_plural,
2053 association_proxy(table_suffix_plural, column_name, creator=text_string_creator))
2054 setattr(foreign_table_class, column_name, DefaultLangProperty(column_name_plural))
2055
2056 if column_name == 'name':
2057 foreign_table_class.name_table = TranslatedStringsTable
2058
2059 compile_mappers()
2060 return TranslatedStringsTable
2061
2062 class DefaultLangProperty(object):
2063 def __init__(self, column_name):
2064 self.column_name = column_name
2065
2066 def __get__(self, instance, cls):
2067 if instance:
2068 return getattr(instance, self.column_name)[default_lang]
2069 else:
2070 # TODO I think this is kind of broken
2071 return getattr(cls, self.column_name)[default_lang]
2072
2073 def __set__(self, instance, value):
2074 getattr(instance, self.colname)[default_lang] = value
2075
2076 def __delete__(self, instance):
2077 del getattr(instance, self.colname)[default_lang]
2078
2079 for table in list(table_classes):
2080 # Find all the language-specific columns, keeping them in the order they
2081 # were defined
2082 all_columns = []
2083 for colname in dir(table):
2084 column = getattr(table, colname)
2085 if isinstance(column, LanguageSpecificColumn):
2086 all_columns.append((colname, column))
2087 delattr(table, colname)
2088 all_columns.sort(key=lambda pair: pair[1].order)
2089
2090 # Break them into text and prose columns
2091 text_columns = []
2092 prose_columns = []
2093 for colname, column in all_columns:
2094 spec = colname, column.plural, column.makeSAColumn()
2095 if isinstance(column, TextColumn):
2096 text_columns.append(spec)
2097 elif isinstance(column, ProseColumn):
2098 prose_columns.append(spec)
2099
2100 if (text_columns or prose_columns) and issubclass(table, LanguageSpecific):
2101 raise AssertionError("Language-specific table %s shouldn't have explicit language-specific columns" % table)
2102
2103 if text_columns:
2104 string_table = makeTextTable(table, 'texts', 'text', text_columns, lazy=False)
2105 if prose_columns:
2106 string_table = makeTextTable(table, 'prose', 'prose', prose_columns, lazy='select')
2107
2108 ### Add language relations
2109 for table in list(table_classes):
2110 if issubclass(table, LanguageSpecific):
2111 table.language = relation(Language, primaryjoin=table.language_id == Language.id)
2112
2113 Move.effect = DefaultLangProperty('effects')
2114 Move.effects = markdown.MoveEffectsProperty('effect')
2115 Move.short_effect = DefaultLangProperty('short_effects')
2116 Move.short_effects = markdown.MoveEffectsProperty('short_effect')
2117
2118 MoveChangelog.effect = DefaultLangProperty('effects')
2119 MoveChangelog.effects = markdown.MoveEffectsProperty('effect')
2120 MoveChangelog.short_effect = DefaultLangProperty('short_effects')
2121 MoveChangelog.short_effects = markdown.MoveEffectsProperty('short_effect')