mirror of
https://github.com/veekun/pokedex.git
synced 2024-08-20 18:16:34 +00:00
Merge branch 'schema-sanity'
This commit is contained in:
commit
4daa6ab0c3
59 changed files with 787 additions and 547 deletions
|
@ -1,4 +1,4 @@
|
||||||
ability_changelog_id,language_id,effect
|
ability_changelog_id,local_language_id,effect
|
||||||
1,9,Has no effect in battle.
|
1,9,Has no effect in battle.
|
||||||
2,9,Does not prevent regular KOs from full [HP]{mechanic}.
|
2,9,Does not prevent regular KOs from full [HP]{mechanic}.
|
||||||
3,9,Has no overworld effect.
|
3,9,Has no overworld effect.
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
ability_id,language_id,name
|
ability_id,local_language_id,name
|
||||||
1,1,あくしゅう
|
1,1,あくしゅう
|
||||||
1,5,Puanteur
|
1,5,Puanteur
|
||||||
1,6,Duftnote
|
1,6,Duftnote
|
|
|
@ -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}.
|
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}.
|
This ability does not stack with a held [King's Rock]{item}.
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
berry_firmness_id,language_id,name
|
berry_firmness_id,local_language_id,name
|
||||||
1,9,Very Soft
|
1,9,Very Soft
|
||||||
2,9,Soft
|
2,9,Soft
|
||||||
3,9,Hard
|
3,9,Hard
|
|
|
@ -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.
|
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."
|
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."
|
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,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
|
1,9,Cool,Spicy,Red
|
||||||
2,9,Beauty,Dry,Blue
|
2,9,Beauty,Dry,Blue
|
||||||
3,9,Cute,Sweet,Pink
|
3,9,Cute,Sweet,Pink
|
|
|
@ -1,4 +1,4 @@
|
||||||
egg_group_id,language_id,name
|
egg_group_id,local_language_id,name
|
||||||
1,9,Monster
|
1,9,Monster
|
||||||
2,9,Water 1
|
2,9,Water 1
|
||||||
3,9,Bug
|
3,9,Bug
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
encounter_condition_id,language_id,name
|
encounter_condition_id,local_language_id,name
|
||||||
1,9,Swarm
|
1,9,Swarm
|
||||||
2,9,Time of day
|
2,9,Time of day
|
||||||
3,9,PokeRadar
|
3,9,PokeRadar
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
encounter_condition_value_id,language_id,name
|
encounter_condition_value_id,local_language_id,name
|
||||||
1,9,During a swarm
|
1,9,During a swarm
|
||||||
2,9,Not during a swarm
|
2,9,Not during a swarm
|
||||||
3,9,In the morning
|
3,9,In the morning
|
||||||
|
|
|
|
@ -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
|
1,9,Walking in tall grass or a cave
|
||||||
2,9,Fishing with an Old Rod
|
2,9,Fishing with an Old Rod
|
||||||
3,9,Fishing with a Good Rod
|
3,9,Fishing with a Good Rod
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
evolution_trigger_id,language_id,name
|
evolution_trigger_id,local_language_id,name
|
||||||
1,9,Level_up
|
1,9,Level_up
|
||||||
2,9,Trade
|
2,9,Trade
|
||||||
3,9,Use_item
|
3,9,Use_item
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
generation_id,language_id,name
|
generation_id,local_language_id,name
|
||||||
1,9,Generation I
|
1,9,Generation I
|
||||||
2,9,Generation II
|
2,9,Generation II
|
||||||
3,9,Generation III
|
3,9,Generation III
|
|
|
@ -1,4 +1,4 @@
|
||||||
growth_rate_id,language_id,name
|
growth_rate_id,local_language_id,name
|
||||||
1,9,slow
|
1,9,slow
|
||||||
2,9,medium
|
2,9,medium
|
||||||
3,9,fast
|
3,9,fast
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
item_category_id,language_id,name
|
item_category_id,local_language_id,name
|
||||||
1,9,Stat boosts
|
1,9,Stat boosts
|
||||||
2,9,Effort drop
|
2,9,Effort drop
|
||||||
3,9,Medicine
|
3,9,Medicine
|
||||||
|
|
|
|
@ -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
|
1,9,Countable,Has a count in the bag
|
||||||
2,9,Consumable,Consumed when used
|
2,9,Consumable,Consumed when used
|
||||||
3,9,Usable_overworld,Usable outside battle
|
3,9,Usable_overworld,Usable outside battle
|
||||||
|
|
|
|
@ -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.
|
1,9,Badly poisons the target.
|
||||||
2,9,Burns the target.
|
2,9,Burns the target.
|
||||||
3,9,Immediately activates the berry's effect on the target.
|
3,9,Immediately activates the berry's effect on the target.
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
item_id,generation_id,internal_id
|
item_id,generation_id,game_index
|
||||||
1,3,1
|
1,3,1
|
||||||
1,4,1
|
1,4,1
|
||||||
1,5,1
|
1,5,1
|
|
|
@ -1,4 +1,4 @@
|
||||||
item_id,language_id,name
|
item_id,local_language_id,name
|
||||||
1,1,マスターボール
|
1,1,マスターボール
|
||||||
1,5,Master Ball
|
1,5,Master Ball
|
||||||
1,6,Meisterball
|
1,6,Meisterball
|
|
|
@ -1,4 +1,4 @@
|
||||||
item_pocket_id,language_id,name
|
item_pocket_id,local_language_id,name
|
||||||
1,9,Items
|
1,9,Items
|
||||||
2,9,Medicine
|
2,9,Medicine
|
||||||
3,9,Poké Balls
|
3,9,Poké Balls
|
|
|
@ -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.
|
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.
|
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.
|
3,9,,In battle: Attempts to capture one Pokémon. Has a capture rate of 1.5.
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
lang_id,language_id,name
|
language_id,local_language_id,name
|
||||||
1,9,Japanese
|
1,9,Japanese
|
||||||
2,9,Official Roomaji
|
2,9,Official Roomaji
|
||||||
3,9,Korean
|
3,9,Korean
|
|
|
@ -1,4 +1,4 @@
|
||||||
location_area_id,language_id,name
|
location_area_id,local_language_id,name
|
||||||
1,9,
|
1,9,
|
||||||
2,9,
|
2,9,
|
||||||
3,9,
|
3,9,
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
id,location_id,internal_id,identifier
|
id,location_id,game_index,identifier
|
||||||
1,1,1,
|
1,1,1,
|
||||||
2,2,2,
|
2,2,2,
|
||||||
3,3,3,
|
3,3,3,
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
location_id,generation_id,internal_id
|
location_id,generation_id,game_index
|
||||||
1,4,7
|
1,4,7
|
||||||
2,4,9
|
2,4,9
|
||||||
3,4,11
|
3,4,11
|
|
|
@ -1,4 +1,4 @@
|
||||||
location_id,language_id,name
|
location_id,local_language_id,name
|
||||||
1,9,Canalave City
|
1,9,Canalave City
|
||||||
2,9,Eterna City
|
2,9,Eterna City
|
||||||
3,9,Pastoria City
|
3,9,Pastoria City
|
|
|
@ -1,4 +1,4 @@
|
||||||
move_battle_style_id,language_id,name
|
move_battle_style_id,local_language_id,name
|
||||||
1,9,Attack
|
1,9,Attack
|
||||||
2,9,Defense
|
2,9,Defense
|
||||||
3,9,Support
|
3,9,Support
|
||||||
|
|
|
|
@ -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
|
1,9,non-damaging,No damage
|
||||||
2,9,physical,"Physical damage, controlled by Attack and Defense"
|
2,9,physical,"Physical damage, controlled by Attack and Defense"
|
||||||
3,9,special,"Special damage, controlled by Special Attack and Special Defense"
|
3,9,special,"Special damage, controlled by Special Attack and Special Defense"
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
move_effect_category_id,language_id,name
|
move_effect_category_id,local_language_id,name
|
||||||
1,9,Regular damage
|
1,9,Regular damage
|
||||||
2,9,Power varies
|
2,9,Power varies
|
||||||
3,9,Special damage
|
3,9,Special damage
|
||||||
|
|
|
|
@ -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}."
|
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}.
|
2,9,Hits Pokémon under the effects of [Dig]{move} and [Fly]{move}.
|
||||||
3,9,Does nothing in trainer battles.
|
3,9,Does nothing in trainer battles.
|
||||||
|
|
|
|
@ -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}.
|
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}.
|
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.
|
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,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})."
|
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}.
|
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."
|
3,9,Must recharge,"The turn after this move is used, the Pokémon's action is skipped so it can recharge."
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
move_meta_ailment_id,language_id,name
|
move_meta_ailment_id,local_language_id,name
|
||||||
-1,9,????
|
-1,9,????
|
||||||
0,9,none
|
0,9,none
|
||||||
1,9,Paralysis
|
1,9,Paralysis
|
|
|
@ -1,4 +1,4 @@
|
||||||
move_meta_category_id,language_id,description
|
move_meta_category_id,local_language_id,description
|
||||||
0,9,Inflicts damage
|
0,9,Inflicts damage
|
||||||
1,9,No damage; inflicts status ailment
|
1,9,No damage; inflicts status ailment
|
||||||
2,9,No damage; lowers target's stats or raises user's stats
|
2,9,No damage; lowers target's stats or raises user's stats
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
move_id,language_id,name
|
move_id,local_language_id,name
|
||||||
1,1,はたく
|
1,1,はたく
|
||||||
1,5,Écras'Face
|
1,5,Écras'Face
|
||||||
1,6,Pfund
|
1,6,Pfund
|
|
|
@ -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.
|
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."
|
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).
|
3,9,Ally,The user's ally (if any).
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
nature_id,language_id,name
|
nature_id,local_language_id,name
|
||||||
1,1,がんばりや
|
1,1,がんばりや
|
||||||
1,5,Hardi
|
1,5,Hardi
|
||||||
1,6,Robust
|
1,6,Robust
|
|
|
@ -1,4 +1,4 @@
|
||||||
pokeathlon_stat_id,language_id,name
|
pokeathlon_stat_id,local_language_id,name
|
||||||
1,9,Speed
|
1,9,Speed
|
||||||
2,9,Power
|
2,9,Power
|
||||||
3,9,Skill
|
3,9,Skill
|
|
|
@ -1,4 +1,4 @@
|
||||||
pokedex_id,language_id,name,description
|
pokedex_id,local_language_id,name,description
|
||||||
1,9,National,Entire National dex
|
1,9,National,Entire National dex
|
||||||
2,9,Kanto,Red/Blue/Yellow Kanto 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"
|
3,9,Original Johto,"Gold/Silver/Crystal Johto dex—called the ""New"" Pokédex in-game"
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
pokemon_color_id,language_id,name
|
pokemon_color_id,local_language_id,name
|
||||||
1,9,Black
|
1,9,Black
|
||||||
2,9,Blue
|
2,9,Blue
|
||||||
3,9,Brown
|
3,9,Brown
|
|
|
@ -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."
|
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.
|
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."
|
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,4 +1,4 @@
|
||||||
pokemon_form_id,language_id,name
|
pokemon_form_id,local_language_id,name
|
||||||
1,9,
|
1,9,
|
||||||
2,9,
|
2,9,
|
||||||
3,9,
|
3,9,
|
|
|
@ -1,4 +1,4 @@
|
||||||
pokemon_id,generation_id,internal_id
|
pokemon_id,generation_id,game_index
|
||||||
1,1,153
|
1,1,153
|
||||||
1,2,1
|
1,2,1
|
||||||
1,3,1
|
1,3,1
|
|
|
@ -1,4 +1,4 @@
|
||||||
pokemon_habitat_id,language_id,name
|
pokemon_habitat_id,local_language_id,name
|
||||||
1,9,cave
|
1,9,cave
|
||||||
2,9,forest
|
2,9,forest
|
||||||
3,9,grassland
|
3,9,grassland
|
|
|
@ -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.
|
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."
|
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.
|
3,9,Tutor,Can be taught at any time by an NPC.
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
pokemon_id,language_id,name,species
|
pokemon_id,local_language_id,name,species
|
||||||
1,1,フシギダネ,
|
1,1,フシギダネ,
|
||||||
1,2,Fushigidane,
|
1,2,Fushigidane,
|
||||||
1,3,이상해씨,
|
1,3,이상해씨,
|
|
|
@ -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
|
1,9,Ball,Pomaceous
|
||||||
2,9,Squiggle,Caudal
|
2,9,Squiggle,Caudal
|
||||||
3,9,Fish,Ichthyic
|
3,9,Fish,Ichthyic
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
region_id,language_id,name
|
region_id,local_language_id,name
|
||||||
1,9,Kanto
|
1,9,Kanto
|
||||||
2,9,Johto
|
2,9,Johto
|
||||||
3,9,Hoenn
|
3,9,Hoenn
|
|
|
@ -1,4 +1,4 @@
|
||||||
stat_hint_id,language_id,message
|
stat_hint_id,local_language_id,message
|
||||||
1,9,Loves to eat
|
1,9,Loves to eat
|
||||||
2,9,Proud of its power
|
2,9,Proud of its power
|
||||||
3,9,Sturdy body
|
3,9,Sturdy body
|
|
|
@ -1,4 +1,4 @@
|
||||||
stat_id,language_id,name
|
stat_id,local_language_id,name
|
||||||
1,9,HP
|
1,9,HP
|
||||||
2,9,Attack
|
2,9,Attack
|
||||||
3,9,Defense
|
3,9,Defense
|
|
|
@ -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.
|
1,9,Enables the user to perform first in the next turn.
|
||||||
2,9,Enables the user to perform last 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.
|
4,9,Earn +2 if the Judge's Voltage goes up.
|
||||||
|
|
|
|
@ -1,4 +1,4 @@
|
||||||
type_id,language_id,name
|
type_id,local_language_id,name
|
||||||
1,1,ノーマル
|
1,1,ノーマル
|
||||||
1,5,Normal
|
1,5,Normal
|
||||||
1,6,Normal
|
1,6,Normal
|
|
|
@ -1,4 +1,4 @@
|
||||||
version_id,language_id,name
|
version_id,local_language_id,name
|
||||||
1,9,Red
|
1,9,Red
|
||||||
2,9,Blue
|
2,9,Blue
|
||||||
3,9,Yellow
|
3,9,Yellow
|
|
|
@ -2,6 +2,7 @@ from sqlalchemy import MetaData, Table, engine_from_config, orm
|
||||||
|
|
||||||
from ..defaults import get_default_db_uri
|
from ..defaults import get_default_db_uri
|
||||||
from .tables import metadata
|
from .tables import metadata
|
||||||
|
from .multilang import MultilangSession
|
||||||
|
|
||||||
|
|
||||||
def connect(uri=None, session_args={}, engine_args={}, engine_prefix=''):
|
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 = dict(autoflush=True, autocommit=False, bind=engine)
|
||||||
all_session_args.update(session_args)
|
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)
|
session = orm.scoped_session(sm)
|
||||||
|
|
||||||
return session
|
return session
|
||||||
|
|
|
@ -56,34 +56,25 @@ class MarkdownString(object):
|
||||||
"""
|
"""
|
||||||
return self.source_text
|
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):
|
return MarkdownString(effect_text)
|
||||||
def __init__(self, effect_column, move):
|
|
||||||
self.effect_column = effect_column
|
|
||||||
self.move = move
|
|
||||||
|
|
||||||
def __contains__(self, lang):
|
class MoveEffectProperty(object):
|
||||||
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):
|
|
||||||
"""Property that wraps move effects. Used like this:
|
"""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.effect # returns a MarkdownString
|
||||||
some_move.effects[lang].as_html # returns a chunk of HTML
|
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
|
This class also performs simple substitution on the effect, replacing
|
||||||
`$effect_chance` with the move's actual effect chance.
|
`$effect_chance` with the move's actual effect chance.
|
||||||
|
@ -92,8 +83,17 @@ class MoveEffectsProperty(object):
|
||||||
def __init__(self, effect_column):
|
def __init__(self, effect_column):
|
||||||
self.effect_column = effect_column
|
self.effect_column = effect_column
|
||||||
|
|
||||||
def __get__(self, move, move_class):
|
def __get__(self, obj, cls):
|
||||||
return _MoveEffects(self.effect_column, move)
|
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):
|
class MarkdownColumn(sqlalchemy.types.TypeDecorator):
|
||||||
"""Generic SQLAlchemy column type for Markdown text.
|
"""Generic SQLAlchemy column type for Markdown text.
|
||||||
|
|
164
pokedex/db/multilang.py
Normal file
164
pokedex/db/multilang.py
Normal 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
|
@ -225,7 +225,7 @@ class PokedexLookup(object):
|
||||||
# Some things also have other languages' names
|
# Some things also have other languages' names
|
||||||
# XXX other language form names..?
|
# XXX other language form names..?
|
||||||
seen = set()
|
seen = set()
|
||||||
for language, name in getattr(row, 'names', []).items():
|
for language, name in getattr(row, 'name_map', {}).items():
|
||||||
if name in seen:
|
if name in seen:
|
||||||
# Don't add the name again as a different
|
# Don't add the name again as a different
|
||||||
# language; no point and it makes spell results
|
# language; no point and it makes spell results
|
||||||
|
|
|
@ -128,8 +128,8 @@ class SaveFilePokemon(object):
|
||||||
|
|
||||||
self._held_item = None
|
self._held_item = None
|
||||||
if st.held_item_id:
|
if st.held_item_id:
|
||||||
self._held_item = session.query(tables.ItemInternalID) \
|
self._held_item = session.query(tables.ItemGameIndex) \
|
||||||
.filter_by(generation_id = 4, internal_id = st.held_item_id).one().item
|
.filter_by(generation_id = 4, game_index = st.held_item_id).one().item
|
||||||
|
|
||||||
self._stats = []
|
self._stats = []
|
||||||
for pokemon_stat in self._pokemon.stats:
|
for pokemon_stat in self._pokemon.stats:
|
||||||
|
@ -173,19 +173,19 @@ class SaveFilePokemon(object):
|
||||||
pokeball_id = st.hgss_pokeball - 17 + 492
|
pokeball_id = st.hgss_pokeball - 17 + 492
|
||||||
else:
|
else:
|
||||||
pokeball_id = st.dppt_pokeball
|
pokeball_id = st.dppt_pokeball
|
||||||
self._pokeball = session.query(tables.ItemInternalID) \
|
self._pokeball = session.query(tables.ItemGameIndex) \
|
||||||
.filter_by(generation_id = 4, internal_id = pokeball_id).one().item
|
.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
|
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
|
met_loc_id = st.pt_met_location_id or st.dp_met_location_id
|
||||||
|
|
||||||
self._egg_location = None
|
self._egg_location = None
|
||||||
if egg_loc_id:
|
if egg_loc_id:
|
||||||
self._egg_location = session.query(tables.LocationInternalID) \
|
self._egg_location = session.query(tables.LocationGameIndex) \
|
||||||
.filter_by(generation_id = 4, internal_id = egg_loc_id).one().location
|
.filter_by(generation_id = 4, game_index = egg_loc_id).one().location
|
||||||
|
|
||||||
self._met_location = session.query(tables.LocationInternalID) \
|
self._met_location = session.query(tables.LocationGameIndex) \
|
||||||
.filter_by(generation_id = 4, internal_id = met_loc_id).one().location
|
.filter_by(generation_id = 4, game_index = met_loc_id).one().location
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def species(self):
|
def species(self):
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
# encoding: utf8
|
# encoding: utf8
|
||||||
from nose.tools import *
|
from nose.tools import *
|
||||||
import unittest
|
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 import tables, markdown
|
||||||
|
from pokedex.db.multilang import create_translation_table
|
||||||
|
|
||||||
def test_variable_names():
|
def test_variable_names():
|
||||||
"""We want pokedex.db.tables to export tables using the class name"""
|
"""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:
|
for table in tables.table_classes:
|
||||||
assert getattr(tables, table.__name__) is table
|
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():
|
def test_texts():
|
||||||
"""Check DB schema for integrity of text columns & translations.
|
"""Check DB schema for integrity of text columns & translations.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue