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