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