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