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