Merge branch 'schema-sanity'

This commit is contained in:
Eevee 2011-03-28 19:14:24 -07:00
commit 4daa6ab0c3
59 changed files with 787 additions and 547 deletions

View file

@ -1,4 +1,4 @@
ability_changelog_id,language_id,effect
ability_changelog_id,local_language_id,effect
1,9,Has no effect in battle.
2,9,Does not prevent regular KOs from full [HP]{mechanic}.
3,9,Has no overworld effect.

1 ability_changelog_id language_id local_language_id effect
2 1 9 Has no effect in battle.
3 2 9 Does not prevent regular KOs from full [HP]{mechanic}.
4 3 9 Has no overworld effect.

View file

@ -1,4 +1,4 @@
ability_id,language_id,name
ability_id,local_language_id,name
1,1,あくしゅう
1,5,Puanteur
1,6,Duftnote
1 ability_id language_id local_language_id name
2 1 1 あくしゅう
3 1 5 Puanteur
4 1 6 Duftnote

View file

@ -1,4 +1,4 @@
ability_id,language_id,effect,short_effect
ability_id,local_language_id,effect,short_effect
1,9,"This Pokémon's moves have approximately a 10% chance to make the target [flinch]{mechanic}.
This ability does not stack with a held [King's Rock]{item}.

1 ability_id language_id local_language_id effect short_effect
2 1 9 This Pokémon's moves have approximately a 10% chance to make the target [flinch]{mechanic}. This ability does not stack with a held [King's Rock]{item}. Overworld: The wild encounter rate is halved while this Pokémon is in the party. Has a chance of making the opponent [flinch]{mechanic} when attacking.
3 2 9 The [weather]{mechanic} changes to [rain]{mechanic} when this Pokémon enters battle and does not end unless replaced by another weather condition. If multiple Pokémon with this ability, [Drought]{ability}, [Sand Stream]{ability}, or [Snow Warning]{ability} are sent out at the same time, the abilities will activate in order of [Speed]{mechanic}, respecting [Trick Room]{move}. Each ability's weather will cancel the previous weather, and only the weather summoned by the slowest of the Pokémon will stay. Summons [rain]{mechanic} that lasts indefinitely upon entering battle.
4 3 9 This Pokémon's [Speed]{mechanic} rises one [stage]{mechanic:stat modifier} after each turn. Raises [Speed]{mechanic} one [stage]{mechanic:stat modifier} after each turn.

View file

@ -1,4 +1,4 @@
berry_firmness_id,language_id,name
berry_firmness_id,local_language_id,name
1,9,Very Soft
2,9,Soft
3,9,Hard
1 berry_firmness_id language_id local_language_id name
2 1 9 9 Very Soft
3 2 9 9 Soft
4 3 9 9 Hard

View file

@ -1,4 +1,4 @@
contest_effect_id,language_id,flavor_text,effect
contest_effect_id,local_language_id,flavor_text,effect
1,9,A highly appealing move.,Gives a high number of appeal points wth no other effects.
2,9,Affected by how well the appeal in front goes.,"If the Pokémon that appealed before the user earned less than three appeal points, user earns six; if three, user earns three; if more than three, user earns none."
3,9,"After this move, the user is more easily startled.","If the user is jammed this turn after using this move, it will receive twice as many jam points."

1 contest_effect_id language_id local_language_id flavor_text effect
2 1 9 A highly appealing move. Gives a high number of appeal points wth no other effects.
3 2 9 Affected by how well the appeal in front goes. If the Pokémon that appealed before the user earned less than three appeal points, user earns six; if three, user earns three; if more than three, user earns none.
4 3 9 After this move, the user is more easily startled. If the user is jammed this turn after using this move, it will receive twice as many jam points.

View file

@ -1,4 +1,4 @@
contest_type_id,language_id,name,flavor,color
contest_type_id,local_language_id,name,flavor,color
1,9,Cool,Spicy,Red
2,9,Beauty,Dry,Blue
3,9,Cute,Sweet,Pink
1 contest_type_id language_id local_language_id name flavor color
2 1 9 9 Cool Spicy Red
3 2 9 9 Beauty Dry Blue
4 3 9 9 Cute Sweet Pink

View file

@ -1,4 +1,4 @@
egg_group_id,language_id,name
egg_group_id,local_language_id,name
1,9,Monster
2,9,Water 1
3,9,Bug

1 egg_group_id language_id local_language_id name
2 1 9 Monster
3 2 9 Water 1
4 3 9 Bug

View file

@ -1,4 +1,4 @@
encounter_condition_id,language_id,name
encounter_condition_id,local_language_id,name
1,9,Swarm
2,9,Time of day
3,9,PokeRadar

1 encounter_condition_id language_id local_language_id name
2 1 9 9 Swarm
3 2 9 9 Time of day
4 3 9 9 PokeRadar

View file

@ -1,4 +1,4 @@
encounter_condition_value_id,language_id,name
encounter_condition_value_id,local_language_id,name
1,9,During a swarm
2,9,Not during a swarm
3,9,In the morning

1 encounter_condition_value_id language_id local_language_id name
2 1 9 During a swarm
3 2 9 Not during a swarm
4 3 9 In the morning

View file

@ -1,4 +1,4 @@
encounter_terrain_id,language_id,name
encounter_terrain_id,local_language_id,name
1,9,Walking in tall grass or a cave
2,9,Fishing with an Old Rod
3,9,Fishing with a Good Rod

1 encounter_terrain_id language_id local_language_id name
2 1 9 9 Walking in tall grass or a cave
3 2 9 9 Fishing with an Old Rod
4 3 9 9 Fishing with a Good Rod

View file

@ -1,4 +1,4 @@
evolution_trigger_id,language_id,name
evolution_trigger_id,local_language_id,name
1,9,Level_up
2,9,Trade
3,9,Use_item

1 evolution_trigger_id language_id local_language_id name
2 1 9 9 Level_up
3 2 9 9 Trade
4 3 9 9 Use_item

View file

@ -1,4 +1,4 @@
generation_id,language_id,name
generation_id,local_language_id,name
1,9,Generation I
2,9,Generation II
3,9,Generation III
1 generation_id language_id local_language_id name
2 1 9 9 Generation I
3 2 9 9 Generation II
4 3 9 9 Generation III

View file

@ -1,4 +1,4 @@
growth_rate_id,language_id,name
growth_rate_id,local_language_id,name
1,9,slow
2,9,medium
3,9,fast

1 growth_rate_id language_id local_language_id name
2 1 9 9 slow
3 2 9 9 medium
4 3 9 9 fast

View file

@ -1,4 +1,4 @@
item_category_id,language_id,name
item_category_id,local_language_id,name
1,9,Stat boosts
2,9,Effort drop
3,9,Medicine

1 item_category_id language_id local_language_id name
2 1 9 Stat boosts
3 2 9 Effort drop
4 3 9 Medicine

View file

@ -1,4 +1,4 @@
item_flag_id,language_id,name,description
item_flag_id,local_language_id,name,description
1,9,Countable,Has a count in the bag
2,9,Consumable,Consumed when used
3,9,Usable_overworld,Usable outside battle

1 item_flag_id language_id local_language_id name description
2 1 9 Countable Has a count in the bag
3 2 9 Consumable Consumed when used
4 3 9 Usable_overworld Usable outside battle

View file

@ -1,4 +1,4 @@
item_fling_effect_id,language_id,effect
item_fling_effect_id,local_language_id,effect
1,9,Badly poisons the target.
2,9,Burns the target.
3,9,Immediately activates the berry's effect on the target.

1 item_fling_effect_id language_id local_language_id effect
2 1 9 9 Badly poisons the target.
3 2 9 9 Burns the target.
4 3 9 9 Immediately activates the berry's effect on the target.

View file

@ -1,4 +1,4 @@
item_id,generation_id,internal_id
item_id,generation_id,game_index
1,3,1
1,4,1
1,5,1
1 item_id generation_id internal_id game_index
2 1 3 1
3 1 4 1
4 1 5 1

View file

@ -1,4 +1,4 @@
item_id,language_id,name
item_id,local_language_id,name
1,1,マスターボール
1,5,Master Ball
1,6,Meisterball
1 item_id language_id local_language_id name
2 1 1 マスターボール
3 1 5 Master Ball
4 1 6 Meisterball

View file

@ -1,4 +1,4 @@
item_pocket_id,language_id,name
item_pocket_id,local_language_id,name
1,9,Items
2,9,Medicine
3,9,Poké Balls
1 item_pocket_id language_id local_language_id name
2 1 9 Items
3 2 9 Medicine
4 3 9 Poké Balls

View file

@ -1,4 +1,4 @@
item_id,language_id,short_effect,effect
item_id,local_language_id,short_effect,effect
1,9,,In battle: Captures one Pokémon without fail. Has a capture rate of 255.
2,9,,In battle: Attempts to capture one Pokémon. Has a capture rate of 2.
3,9,,In battle: Attempts to capture one Pokémon. Has a capture rate of 1.5.

1 item_id language_id local_language_id short_effect effect
2 1 9 In battle: Captures one Pokémon without fail. Has a capture rate of 255.
3 2 9 In battle: Attempts to capture one Pokémon. Has a capture rate of 2.
4 3 9 In battle: Attempts to capture one Pokémon. Has a capture rate of 1.5.

View file

@ -1,4 +1,4 @@
lang_id,language_id,name
language_id,local_language_id,name
1,9,Japanese
2,9,Official Roomaji
3,9,Korean
1 lang_id language_id local_language_id name
2 1 9 1 9 Japanese
3 2 9 2 9 Official Roomaji
4 3 9 3 9 Korean

View file

@ -1,4 +1,4 @@
location_area_id,language_id,name
location_area_id,local_language_id,name
1,9,
2,9,
3,9,

1 location_area_id language_id local_language_id name
2 1 9
3 2 9
4 3 9

View file

@ -1,4 +1,4 @@
id,location_id,internal_id,identifier
id,location_id,game_index,identifier
1,1,1,
2,2,2,
3,3,3,

1 id location_id internal_id game_index identifier
2 1 1 1
3 2 2 2
4 3 3 3

View file

@ -1,4 +1,4 @@
location_id,generation_id,internal_id
location_id,generation_id,game_index
1,4,7
2,4,9
3,4,11
1 location_id generation_id internal_id game_index
2 1 4 7
3 2 4 9
4 3 4 11

View file

@ -1,4 +1,4 @@
location_id,language_id,name
location_id,local_language_id,name
1,9,Canalave City
2,9,Eterna City
3,9,Pastoria City
1 location_id language_id local_language_id name
2 1 9 Canalave City
3 2 9 Eterna City
4 3 9 Pastoria City

View file

@ -1,4 +1,4 @@
move_battle_style_id,language_id,name
move_battle_style_id,local_language_id,name
1,9,Attack
2,9,Defense
3,9,Support

1 move_battle_style_id language_id local_language_id name
2 1 9 9 Attack
3 2 9 9 Defense
4 3 9 9 Support

View file

@ -1,4 +1,4 @@
move_damage_class_id,language_id,name,description
move_damage_class_id,local_language_id,name,description
1,9,non-damaging,No damage
2,9,physical,"Physical damage, controlled by Attack and Defense"
3,9,special,"Special damage, controlled by Special Attack and Special Defense"

1 move_damage_class_id language_id local_language_id name description
2 1 9 9 non-damaging No damage
3 2 9 9 physical Physical damage, controlled by Attack and Defense
4 3 9 9 special Special damage, controlled by Special Attack and Special Defense

View file

@ -1,4 +1,4 @@
move_effect_category_id,language_id,name
move_effect_category_id,local_language_id,name
1,9,Regular damage
2,9,Power varies
3,9,Special damage

1 move_effect_category_id language_id local_language_id name
2 1 9 Regular damage
3 2 9 Power varies
4 3 9 Special damage

View file

@ -1,4 +1,4 @@
move_effect_changelog_id,language_id,effect
move_effect_changelog_id,local_language_id,effect
1,9,"Halves the target's [Defense]{mechanic} for damage calculation, which is similar to doubling the attack's [power]{mechanic}."
2,9,Hits Pokémon under the effects of [Dig]{move} and [Fly]{move}.
3,9,Does nothing in trainer battles.

1 move_effect_changelog_id language_id local_language_id effect
2 1 9 Halves the target's [Defense]{mechanic} for damage calculation, which is similar to doubling the attack's [power]{mechanic}.
3 2 9 Hits Pokémon under the effects of [Dig]{move} and [Fly]{move}.
4 3 9 Does nothing in trainer battles.

View file

@ -1,4 +1,4 @@
move_effect_id,language_id,short_effect,effect
move_effect_id,local_language_id,short_effect,effect
1,9,Inflicts regular damage with no additional effect.,Inflicts [regular damage]{mechanic}.
2,9,Puts the target to sleep.,Puts the target to [sleep]{mechanic}.
3,9,Has a $effect_chance% chance to poison the target.,Inflicts [regular damage]{mechanic}. Has a $effect_chance% chance to [poison]{mechanic} the target.

1 move_effect_id language_id local_language_id short_effect effect
2 1 9 Inflicts regular damage with no additional effect. Inflicts [regular damage]{mechanic}.
3 2 9 Puts the target to sleep. Puts the target to [sleep]{mechanic}.
4 3 9 Has a $effect_chance% chance to poison the target. Inflicts [regular damage]{mechanic}. Has a $effect_chance% chance to [poison]{mechanic} the target.

View file

@ -1,4 +1,4 @@
move_flag_type_id,language_id,name,description
move_flag_type_id,local_language_id,name,description
1,9,Makes contact,"User touches the target. This triggers some abilities (e.g., [Static]{ability}) and items (e.g., [Sticky Barb]{item})."
2,9,Has a charging turn,This move has a charging turn that can be skipped with a [Power Herb]{item}.
3,9,Must recharge,"The turn after this move is used, the Pokémon's action is skipped so it can recharge."

1 move_flag_type_id language_id local_language_id name description
2 1 9 Makes contact User touches the target. This triggers some abilities (e.g., [Static]{ability}) and items (e.g., [Sticky Barb]{item}).
3 2 9 Has a charging turn This move has a charging turn that can be skipped with a [Power Herb]{item}.
4 3 9 Must recharge The turn after this move is used, the Pokémon's action is skipped so it can recharge.

View file

@ -1,4 +1,4 @@
move_meta_ailment_id,language_id,name
move_meta_ailment_id,local_language_id,name
-1,9,????
0,9,none
1,9,Paralysis
1 move_meta_ailment_id language_id local_language_id name
2 -1 9 ????
3 0 9 none
4 1 9 Paralysis

View file

@ -1,4 +1,4 @@
move_meta_category_id,language_id,description
move_meta_category_id,local_language_id,description
0,9,Inflicts damage
1,9,No damage; inflicts status ailment
2,9,No damage; lowers target's stats or raises user's stats

1 move_meta_category_id language_id local_language_id description
2 0 9 Inflicts damage
3 1 9 No damage; inflicts status ailment
4 2 9 No damage; lowers target's stats or raises user's stats

View file

@ -1,4 +1,4 @@
move_id,language_id,name
move_id,local_language_id,name
1,1,はたく
1,5,Écras'Face
1,6,Pfund
1 move_id language_id local_language_id name
2 1 1 はたく
3 1 5 Écras'Face
4 1 6 Pfund

View file

@ -1,4 +1,4 @@
move_target_id,language_id,name,description
move_target_id,local_language_id,name,description
1,9,Specific move,One specific move. How this move is chosen depends upon on the move being used.
2,9,Selected Pokémon,"One other Pokémon on the field, selected by the trainer. Stolen moves reuse the same target."
3,9,Ally,The user's ally (if any).

1 move_target_id language_id local_language_id name description
2 1 9 Specific move One specific move. How this move is chosen depends upon on the move being used.
3 2 9 Selected Pokémon One other Pokémon on the field, selected by the trainer. Stolen moves reuse the same target.
4 3 9 Ally The user's ally (if any).

View file

@ -1,4 +1,4 @@
nature_id,language_id,name
nature_id,local_language_id,name
1,1,がんばりや
1,5,Hardi
1,6,Robust
1 nature_id language_id local_language_id name
2 1 1 がんばりや
3 1 5 Hardi
4 1 6 Robust

View file

@ -1,4 +1,4 @@
pokeathlon_stat_id,language_id,name
pokeathlon_stat_id,local_language_id,name
1,9,Speed
2,9,Power
3,9,Skill
1 pokeathlon_stat_id language_id local_language_id name
2 1 9 9 Speed
3 2 9 9 Power
4 3 9 9 Skill

View file

@ -1,4 +1,4 @@
pokedex_id,language_id,name,description
pokedex_id,local_language_id,name,description
1,9,National,Entire National dex
2,9,Kanto,Red/Blue/Yellow Kanto dex
3,9,Original Johto,"Gold/Silver/Crystal Johto dex—called the ""New"" Pokédex in-game"

1 pokedex_id language_id local_language_id name description
2 1 9 National Entire National dex
3 2 9 Kanto Red/Blue/Yellow Kanto dex
4 3 9 Original Johto Gold/Silver/Crystal Johto dex—called the "New" Pokédex in-game

View file

@ -1,4 +1,4 @@
pokemon_color_id,language_id,name
pokemon_color_id,local_language_id,name
1,9,Black
2,9,Blue
3,9,Brown
1 pokemon_color_id language_id local_language_id name
2 1 9 Black
3 2 9 Blue
4 3 9 Brown

View file

@ -1,4 +1,4 @@
pokemon_form_group_id,language_id,term,description
pokemon_form_group_id,local_language_id,term,description
172,9,,"Spiky-eared Pichu can only be received by taking the shiny Pichu from an official promotion to [Celebi]{pokemon}'s shrine in [Ilex Forest]{location}. Spiky-eared Pichu is always female, cannot evolve, and cannot be taken into the Wi-Fi Club or the Union Room, but is otherwise a normal Pichu."
201,9,,Forms only affect appearance. A form is determined at random before a wild encounter and cannot be changed.
351,9,Form,"Form changes along with type to match the [weather]{mechanic} in battle, due to [Forecast]{ability}. Castform is always in its normal form outside of battle, regardless of weather."

1 pokemon_form_group_id language_id local_language_id term description
2 172 9 Spiky-eared Pichu can only be received by taking the shiny Pichu from an official promotion to [Celebi]{pokemon}'s shrine in [Ilex Forest]{location}. Spiky-eared Pichu is always female, cannot evolve, and cannot be taken into the Wi-Fi Club or the Union Room, but is otherwise a normal Pichu.
3 201 9 Forms only affect appearance. A form is determined at random before a wild encounter and cannot be changed.
4 351 9 Form Form changes along with type to match the [weather]{mechanic} in battle, due to [Forecast]{ability}. Castform is always in its normal form outside of battle, regardless of weather.

View file

@ -1,4 +1,4 @@
pokemon_form_id,language_id,name
pokemon_form_id,local_language_id,name
1,9,
2,9,
3,9,
1 pokemon_form_id language_id local_language_id name
2 1 9
3 2 9
4 3 9

View file

@ -1,4 +1,4 @@
pokemon_id,generation_id,internal_id
pokemon_id,generation_id,game_index
1,1,153
1,2,1
1,3,1
1 pokemon_id generation_id internal_id game_index
2 1 1 153
3 1 2 1
4 1 3 1

View file

@ -1,4 +1,4 @@
pokemon_habitat_id,language_id,name
pokemon_habitat_id,local_language_id,name
1,9,cave
2,9,forest
3,9,grassland
1 pokemon_habitat_id language_id local_language_id name
2 1 9 cave
3 2 9 forest
4 3 9 grassland

View file

@ -1,4 +1,4 @@
pokemon_move_method_id,language_id,name,description
pokemon_move_method_id,local_language_id,name,description
1,9,Level up,Learned when a Pokémon reaches a certain level.
2,9,Egg,"Appears on a newly-hatched Pokémon, if the father had the same move."
3,9,Tutor,Can be taught at any time by an NPC.

1 pokemon_move_method_id language_id local_language_id name description
2 1 9 Level up Learned when a Pokémon reaches a certain level.
3 2 9 Egg Appears on a newly-hatched Pokémon, if the father had the same move.
4 3 9 Tutor Can be taught at any time by an NPC.

View file

@ -1,4 +1,4 @@
pokemon_id,language_id,name,species
pokemon_id,local_language_id,name,species
1,1,フシギダネ,
1,2,Fushigidane,
1,3,이상해씨,
1 pokemon_id language_id local_language_id name species
2 1 1 フシギダネ
3 1 2 Fushigidane
4 1 3 이상해씨

View file

@ -1,4 +1,4 @@
pokemon_shape_id,language_id,name,awesome_name
pokemon_shape_id,local_language_id,name,awesome_name
1,9,Ball,Pomaceous
2,9,Squiggle,Caudal
3,9,Fish,Ichthyic

1 pokemon_shape_id language_id local_language_id name awesome_name
2 1 9 Ball Pomaceous
3 2 9 Squiggle Caudal
4 3 9 Fish Ichthyic

View file

@ -1,4 +1,4 @@
region_id,language_id,name
region_id,local_language_id,name
1,9,Kanto
2,9,Johto
3,9,Hoenn
1 region_id language_id local_language_id name
2 1 9 9 Kanto
3 2 9 9 Johto
4 3 9 9 Hoenn

View file

@ -1,4 +1,4 @@
stat_hint_id,language_id,message
stat_hint_id,local_language_id,message
1,9,Loves to eat
2,9,Proud of its power
3,9,Sturdy body
1 stat_hint_id language_id local_language_id message
2 1 9 Loves to eat
3 2 9 Proud of its power
4 3 9 Sturdy body

View file

@ -1,4 +1,4 @@
stat_id,language_id,name
stat_id,local_language_id,name
1,9,HP
2,9,Attack
3,9,Defense
1 stat_id language_id local_language_id name
2 1 9 HP
3 2 9 Attack
4 3 9 Defense

View file

@ -1,4 +1,4 @@
super_contest_effect_id,language_id,flavor_text
super_contest_effect_id,local_language_id,flavor_text
1,9,Enables the user to perform first in the next turn.
2,9,Enables the user to perform last in the next turn.
4,9,Earn +2 if the Judge's Voltage goes up.

1 super_contest_effect_id language_id local_language_id flavor_text
2 1 9 Enables the user to perform first in the next turn.
3 2 9 Enables the user to perform last in the next turn.
4 4 9 Earn +2 if the Judge's Voltage goes up.

View file

@ -1,4 +1,4 @@
type_id,language_id,name
type_id,local_language_id,name
1,1,ノーマル
1,5,Normal
1,6,Normal
1 type_id language_id local_language_id name
2 1 1 ノーマル
3 1 5 Normal
4 1 6 Normal

View file

@ -1,4 +1,4 @@
version_id,language_id,name
version_id,local_language_id,name
1,9,Red
2,9,Blue
3,9,Yellow
1 version_id language_id local_language_id name
2 1 9 Red
3 2 9 Blue
4 3 9 Yellow

View file

@ -2,6 +2,7 @@ from sqlalchemy import MetaData, Table, engine_from_config, orm
from ..defaults import get_default_db_uri
from .tables import metadata
from .multilang import MultilangSession
def connect(uri=None, session_args={}, engine_args={}, engine_prefix=''):
@ -40,7 +41,7 @@ def connect(uri=None, session_args={}, engine_args={}, engine_prefix=''):
all_session_args = dict(autoflush=True, autocommit=False, bind=engine)
all_session_args.update(session_args)
sm = orm.sessionmaker(**all_session_args)
sm = orm.sessionmaker(class_=MultilangSession, **all_session_args)
session = orm.scoped_session(sm)
return session

View file

@ -56,34 +56,25 @@ class MarkdownString(object):
"""
return self.source_text
def _markdownify_effect_text(move, effect_text):
effect_text = effect_text.replace(
u'$effect_chance',
unicode(move.effect_chance),
)
class _MoveEffects(object):
def __init__(self, effect_column, move):
self.effect_column = effect_column
self.move = move
return MarkdownString(effect_text)
def __contains__(self, lang):
return lang in self.move.move_effect.prose
def __getitem__(self, lang):
try:
effect_text = getattr(self.move.move_effect.prose[lang], self.effect_column)
except AttributeError:
return None
effect_text = effect_text.replace(
u'$effect_chance',
unicode(self.move.effect_chance),
)
return MarkdownString(effect_text)
class MoveEffectsProperty(object):
class MoveEffectProperty(object):
"""Property that wraps move effects. Used like this:
MoveClass.effects = MoveEffectProperty('effect')
MoveClass.effect = MoveEffectProperty('effect')
some_move.effects[lang] # returns a MarkdownString
some_move.effects[lang].as_html # returns a chunk of HTML
some_move.effect # returns a MarkdownString
some_move.effect.as_html # returns a chunk of HTML
This class attempts to detect if the wrapped property is a dict-based
association proxy, and will act like such a dict if so. Don't rely on it
for querying, of course.
This class also performs simple substitution on the effect, replacing
`$effect_chance` with the move's actual effect chance.
@ -92,8 +83,17 @@ class MoveEffectsProperty(object):
def __init__(self, effect_column):
self.effect_column = effect_column
def __get__(self, move, move_class):
return _MoveEffects(self.effect_column, move)
def __get__(self, obj, cls):
prop = getattr(obj.move_effect, self.effect_column)
if isinstance(prop, dict):
# Looks like a dict proxy; markdownify everyone
newdict = dict(prop)
for key in newdict:
newdict[key] = _markdownify_effect_text(obj, newdict[key])
return newdict
# Otherwise, scalar prop. Boring
return _markdownify_effect_text(obj, prop)
class MarkdownColumn(sqlalchemy.types.TypeDecorator):
"""Generic SQLAlchemy column type for Markdown text.

164
pokedex/db/multilang.py Normal file
View file

@ -0,0 +1,164 @@
from functools import partial
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import aliased, compile_mappers, mapper, relationship, synonym
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.orm.session import Session, object_session
from sqlalchemy.schema import Column, ForeignKey, Table
from sqlalchemy.sql.expression import and_, bindparam, select
from sqlalchemy.types import Integer
def create_translation_table(_table_name, foreign_class, relation_name,
language_class, **kwargs):
"""Creates a table that represents some kind of data attached to the given
foreign class, but translated across several languages. Returns the new
table's mapped class. It won't be declarative, but it will have a
`__table__` attribute so you can retrieve the Table object.
`foreign_class` must have a `__singlename__`, currently only used to create
the name of the foreign key column.
Also supports the notion of a default language, which is attached to the
session. This is English by default, for historical and practical reasons.
Usage looks like this:
class Foo(Base): ...
create_translation_table('foo_bars', Foo, 'bars',
name = Column(...),
)
# Now you can do the following:
foo.name
foo.name_map['en']
foo.foo_bars['en']
foo.name_map['en'] = "new name"
del foo.name_map['en']
q.options(joinedload(Foo.bars_local))
q.options(joinedload(Foo.bars))
The following properties are added to the passed class:
- `(relation_name)`, a relation to the new table. It uses a dict-based
collection class, where the keys are language identifiers and the values
are rows in the created tables.
- `(relation_name)_local`, a relation to the row in the new table that
matches the current default language.
- `(relation_name)_class`, the class created by this function.
Note that these are distinct relations. Even though the former necessarily
includes the latter, SQLAlchemy doesn't treat them as linked; loading one
will not load the other. Modifying both within the same transaction has
undefined behavior.
For each column provided, the following additional attributes are added to
Foo:
- `(column)_map`, an association proxy onto `foo_bars`.
- `(column)`, an association proxy onto `foo_bars_local`.
Pardon the naming disparity, but the grammar suffers otherwise.
Modifying these directly is not likely to be a good idea.
"""
# n.b.: language_class only exists for the sake of tests, which sometimes
# want to create tables entirely separate from the pokedex metadata
foreign_key_name = foreign_class.__singlename__ + '_id'
Translations = type(_table_name, (object,), {
'_language_identifier': association_proxy('local_language', 'identifier'),
})
# Create the table object
table = Table(_table_name, foreign_class.__table__.metadata,
Column(foreign_key_name, Integer, ForeignKey(foreign_class.id),
primary_key=True, nullable=False),
Column('local_language_id', Integer, ForeignKey(language_class.id),
primary_key=True, nullable=False),
)
Translations.__table__ = table
# Add ye columns
# Column objects have a _creation_order attribute in ascending order; use
# this to get the (unordered) kwargs sorted correctly
kwitems = kwargs.items()
kwitems.sort(key=lambda kv: kv[1]._creation_order)
for name, column in kwitems:
column.name = name
table.append_column(column)
# Construct ye mapper
mapper(Translations, table, properties={
'foreign_id': synonym(foreign_key_name),
'local_language': relationship(language_class,
primaryjoin=table.c.local_language_id == language_class.id,
lazy='joined',
innerjoin=True),
})
# Add full-table relations to the original class
# Foo.bars_table
setattr(foreign_class, relation_name + '_table', Translations)
# Foo.bars
setattr(foreign_class, relation_name, relationship(Translations,
primaryjoin=foreign_class.id == Translations.foreign_id,
collection_class=attribute_mapped_collection('local_language'),
# TODO
lazy='select',
))
# Foo.bars_local
# This is a bit clever; it uses bindparam() to make the join clause
# modifiable on the fly. db sessions know the current language identifier
# populates the bindparam. The manual alias and join are (a) to make the
# condition nice (sqla prefers an EXISTS) and to make the columns play nice
# when foreign_class == language_class.
local_relation_name = relation_name + '_local'
language_class_a = aliased(language_class)
setattr(foreign_class, local_relation_name, relationship(Translations,
primaryjoin=and_(
foreign_class.id == Translations.foreign_id,
Translations.local_language_id == select(
[language_class_a.id],
language_class_a.identifier ==
bindparam('_default_language', required=True),
),
),
uselist=False,
# TODO MORESO HERE
lazy='select',
))
# Add per-column proxies to the original class
for name, column in kwitems:
# Class.(column) -- accessor for the default language's value
setattr(foreign_class, name,
association_proxy(local_relation_name, name))
# Class.(column)_map -- accessor for the language dict
# Need a custom creator since Translations doesn't have an init, and
# these are passed as *args anyway
def creator(language, value):
row = Translations()
row.local_language = language
setattr(row, name, value)
return row
setattr(foreign_class, name + '_map',
association_proxy(relation_name, name, creator=creator))
# Done
return Translations
class MultilangSession(Session):
"""A tiny Session subclass that adds support for a default language."""
default_language = 'en'
def execute(self, clause, params=None, *args, **kwargs):
if not params:
params = {}
params.setdefault('_default_language', self.default_language)
return super(MultilangSession, self).execute(
clause, params, *args, **kwargs)

File diff suppressed because it is too large Load diff

View file

@ -225,7 +225,7 @@ class PokedexLookup(object):
# Some things also have other languages' names
# XXX other language form names..?
seen = set()
for language, name in getattr(row, 'names', []).items():
for language, name in getattr(row, 'name_map', {}).items():
if name in seen:
# Don't add the name again as a different
# language; no point and it makes spell results

View file

@ -128,8 +128,8 @@ class SaveFilePokemon(object):
self._held_item = None
if st.held_item_id:
self._held_item = session.query(tables.ItemInternalID) \
.filter_by(generation_id = 4, internal_id = st.held_item_id).one().item
self._held_item = session.query(tables.ItemGameIndex) \
.filter_by(generation_id = 4, game_index = st.held_item_id).one().item
self._stats = []
for pokemon_stat in self._pokemon.stats:
@ -173,19 +173,19 @@ class SaveFilePokemon(object):
pokeball_id = st.hgss_pokeball - 17 + 492
else:
pokeball_id = st.dppt_pokeball
self._pokeball = session.query(tables.ItemInternalID) \
.filter_by(generation_id = 4, internal_id = pokeball_id).one().item
self._pokeball = session.query(tables.ItemGameIndex) \
.filter_by(generation_id = 4, game_index = pokeball_id).one().item
egg_loc_id = st.pt_egg_location_id or st.dp_egg_location_id
met_loc_id = st.pt_met_location_id or st.dp_met_location_id
self._egg_location = None
if egg_loc_id:
self._egg_location = session.query(tables.LocationInternalID) \
.filter_by(generation_id = 4, internal_id = egg_loc_id).one().location
self._egg_location = session.query(tables.LocationGameIndex) \
.filter_by(generation_id = 4, game_index = egg_loc_id).one().location
self._met_location = session.query(tables.LocationInternalID) \
.filter_by(generation_id = 4, internal_id = met_loc_id).one().location
self._met_location = session.query(tables.LocationGameIndex) \
.filter_by(generation_id = 4, game_index = met_loc_id).one().location
@property
def species(self):

View file

@ -1,9 +1,13 @@
# encoding: utf8
from nose.tools import *
import unittest
from sqlalchemy.orm import class_mapper
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import class_mapper, joinedload, sessionmaker
from sqlalchemy.orm.session import Session
from sqlalchemy.ext.declarative import declarative_base
from pokedex.db import tables, markdown
from pokedex.db.multilang import create_translation_table
def test_variable_names():
"""We want pokedex.db.tables to export tables using the class name"""
@ -22,6 +26,126 @@ def test_variable_names():
for table in tables.table_classes:
assert getattr(tables, table.__name__) is table
def test_i18n_table_creation():
"""Creates and manipulates a magical i18n table, completely independent of
the existing schema and data. Makes sure that the expected behavior of the
various proxies and columns works.
"""
Base = declarative_base()
engine = create_engine("sqlite:///:memory:", echo=True)
Base.metadata.bind = engine
# Need this for the foreign keys to work!
class Language(Base):
__tablename__ = 'languages'
id = Column(Integer, primary_key=True, nullable=False)
identifier = Column(String(2), nullable=False, unique=True)
class Foo(Base):
__tablename__ = 'foos'
__singlename__ = 'foo'
id = Column(Integer, primary_key=True, nullable=False)
FooText = create_translation_table('foo_text', Foo,
language_class=Language,
name = Column(String(100)),
)
class FauxSession(Session):
def execute(self, clause, params=None, *args, **kwargs):
if not params:
params = {}
params.setdefault('_default_language', 'en')
return super(FauxSession, self).execute(clause, params, *args, **kwargs)
# OK, create all the tables and gimme a session
Base.metadata.create_all()
sess = sessionmaker(engine, class_=FauxSession)()
# Create some languages and foos to bind together
lang_en = Language(identifier='en')
sess.add(lang_en)
lang_jp = Language(identifier='jp')
sess.add(lang_jp)
lang_ru = Language(identifier='ru')
sess.add(lang_ru)
foo = Foo()
sess.add(foo)
# Commit so the above get primary keys filled in
sess.commit()
# Give our foo some names, as directly as possible
foo_text = FooText()
foo_text.object_id = foo.id
foo_text.language_id = lang_en.id
foo_text.name = 'english'
sess.add(foo_text)
foo_text = FooText()
foo_text.object_id = foo.id
foo_text.language_id = lang_jp.id
foo_text.name = 'nihongo'
sess.add(foo_text)
# Commit! This will expire all of the above.
sess.commit()
### Test 1: re-fetch foo and check its attributes
foo = sess.query(Foo).params(_default_language='en').one()
# Dictionary of language identifiers => names
assert foo.name_map[lang_en] == 'english'
assert foo.name_map[lang_jp] == 'nihongo'
# Default language, currently English
assert foo.name == 'english'
sess.expire_all()
### Test 2: querying by default language name should work
foo = sess.query(Foo).filter_by(name='english').one()
assert foo.name == 'english'
sess.expire_all()
### Test 3: joinedload on the default name should appear to work
# THIS SHOULD WORK SOMEDAY
# .options(joinedload(Foo.name)) \
foo = sess.query(Foo) \
.options(joinedload(Foo.foo_text_local)) \
.one()
assert foo.name == 'english'
sess.expire_all()
### Test 4: joinedload on all the names should appear to work
# THIS SHOULD ALSO WORK SOMEDAY
# .options(joinedload(Foo.name_map)) \
foo = sess.query(Foo) \
.options(joinedload(Foo.foo_text)) \
.one()
assert foo.name_map[lang_en] == 'english'
assert foo.name_map[lang_jp] == 'nihongo'
sess.expire_all()
### Test 5: Mutating the dict collection should work
foo = sess.query(Foo).one()
foo.name_map[lang_en] = 'different english'
foo.name_map[lang_ru] = 'new russian'
sess.commit()
assert foo.name_map[lang_en] == 'different english'
assert foo.name_map[lang_ru] == 'new russian'
def test_texts():
"""Check DB schema for integrity of text columns & translations.