From 105d9422ca8151aff88465d8ba62e64116cbe32d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 19:34:18 +0300 Subject: [PATCH 1/7] Change item short effect format to markdown --- pokedex/db/tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index d9905b6..727582c 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -501,7 +501,7 @@ create_translation_table('item_names', Item, 'names', ) create_translation_table('item_prose', Item, 'prose', short_effect = Column(markdown.MarkdownColumn(256), nullable=False, - info=dict(description="A short summary of the effect", format='plaintext')), + info=dict(description="A short summary of the effect", format='markdown')), effect = Column(markdown.MarkdownColumn(5120), nullable=False, info=dict(description=u"Detailed description of the item's effect.", format='markdown')), ) From 6f369073412743e00d835591c2a5ebed40c1c6ce Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 19:42:46 +0300 Subject: [PATCH 2/7] Enable nullable MarkdownColumns --- pokedex/db/markdown.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pokedex/db/markdown.py b/pokedex/db/markdown.py index 616830a..257c8de 100644 --- a/pokedex/db/markdown.py +++ b/pokedex/db/markdown.py @@ -106,6 +106,9 @@ class MarkdownColumn(sqlalchemy.types.TypeDecorator): impl = sqlalchemy.types.Unicode def process_bind_param(self, value, dialect): + if value is None: + return None + if not isinstance(value, basestring): # Can't assign, e.g., MarkdownString objects yet raise NotImplementedError @@ -113,4 +116,7 @@ class MarkdownColumn(sqlalchemy.types.TypeDecorator): return unicode(value) def process_result_value(self, value, dialect): + if value is None: + return None + return MarkdownString(value) From a06498cb39127eecedb0dc6e7ffb7601935e764c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 19:34:44 +0300 Subject: [PATCH 3/7] Make a bunch of text columns nullable to support missing translations --- pokedex/db/tables.py | 56 ++++++++++++++++++------------------ pokedex/tests/test_schema.py | 7 +++++ 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index 727582c..5670da9 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -121,9 +121,9 @@ create_translation_table('ability_names', Ability, 'names', info=dict(description="The name", format='plaintext', official=True, ripped=True)), ) create_translation_table('ability_prose', Ability, 'prose', - effect = Column(markdown.MarkdownColumn(5120), nullable=False, + effect = Column(markdown.MarkdownColumn(5120), nullable=True, info=dict(description="A detailed description of this ability's effect", format='markdown')), - short_effect = Column(markdown.MarkdownColumn(255), nullable=False, + short_effect = Column(markdown.MarkdownColumn(255), nullable=True, info=dict(description="A short summary of this ability's effect", format='markdown')), ) @@ -232,9 +232,9 @@ class ContestEffect(TableBase): info=dict(description="The base number of hearts the user's opponent loses")) create_translation_table('contest_effect_prose', ContestEffect, 'prose', - flavor_text = Column(Unicode(64), nullable=False, + flavor_text = Column(Unicode(64), nullable=True, info=dict(description="The in-game description of this effect", official=True, format='gametext')), - effect = Column(Unicode(255), nullable=False, + effect = Column(Unicode(255), nullable=True, info=dict(description="A detailed description of the effect", format='plaintext')), ) @@ -250,11 +250,11 @@ class ContestType(TableBase): create_translation_table('contest_type_names', ContestType, 'names', relation_lazy='joined', - name = Column(Unicode(6), nullable=False, index=True, + name = Column(Unicode(6), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=True)), - flavor = Column(Unicode(6), nullable=False, + flavor = Column(Unicode(6), nullable=True, info=dict(description="The name of the corresponding Berry flavor", official=True, format='plaintext')), - color = Column(Unicode(6), nullable=False, + color = Column(Unicode(6), nullable=True, info=dict(description=u"The name of the corresponding Pokéblock color", official=True, format='plaintext')), ) @@ -500,9 +500,9 @@ create_translation_table('item_names', Item, 'names', info=dict(description="The name", format='plaintext', official=True, ripped=True)), ) create_translation_table('item_prose', Item, 'prose', - short_effect = Column(markdown.MarkdownColumn(256), nullable=False, + short_effect = Column(markdown.MarkdownColumn(256), nullable=True, info=dict(description="A short summary of the effect", format='markdown')), - effect = Column(markdown.MarkdownColumn(5120), nullable=False, + effect = Column(markdown.MarkdownColumn(5120), nullable=True, info=dict(description=u"Detailed description of the item's effect.", format='markdown')), ) create_translation_table('item_flavor_summaries', Item, 'flavor_summaries', @@ -540,9 +540,9 @@ class ItemFlag(TableBase): info=dict(description="Identifier of the flag", format='identifier')) create_translation_table('item_flag_prose', ItemFlag, 'prose', - name = Column(Unicode(24), nullable=False, index=True, + name = Column(Unicode(24), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=False)), - description = Column(Unicode(64), nullable=False, + description = Column(Unicode(64), nullable=True, info=dict(description="Short description of the flag", format='plaintext')), ) @@ -784,9 +784,9 @@ class MoveDamageClass(TableBase): create_translation_table('move_damage_class_prose', MoveDamageClass, 'prose', relation_lazy='joined', - name = Column(Unicode(16), nullable=False, index=True, + name = Column(Unicode(16), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=False)), - description = Column(Unicode(64), nullable=False, + description = Column(Unicode(64), nullable=True, info=dict(description="A description of the class", format='plaintext')), ) @@ -799,9 +799,9 @@ class MoveEffect(TableBase): info=dict(description="A numeric ID")) create_translation_table('move_effect_prose', MoveEffect, 'prose', - short_effect = Column(Unicode(256), nullable=False, + short_effect = Column(Unicode(256), nullable=True, info=dict(description="A short summary of the effect", format='plaintext')), - effect = Column(Unicode(5120), nullable=False, + effect = Column(Unicode(5120), nullable=True, info=dict(description="A detailed description of the effect", format='plaintext')), ) @@ -877,9 +877,9 @@ class MoveFlagType(TableBase): create_translation_table('move_flag_type_prose', MoveFlagType, 'prose', relation_lazy='joined', - name = Column(Unicode(32), nullable=False, index=True, + name = Column(Unicode(32), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=False)), - description = Column(markdown.MarkdownColumn(128), nullable=False, + description = Column(markdown.MarkdownColumn(128), nullable=True, info=dict(description="A short description of the flag", format='markdown')), ) @@ -979,9 +979,9 @@ class MoveTarget(TableBase): create_translation_table('move_target_prose', MoveTarget, 'prose', relation_lazy='joined', - name = Column(Unicode(32), nullable=False, index=True, + name = Column(Unicode(32), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=False)), - description = Column(Unicode(128), nullable=False, + description = Column(Unicode(128), nullable=True, info=dict(description="A description", format='plaintext')), ) @@ -1072,9 +1072,9 @@ class Pokedex(TableBase): create_translation_table('pokedex_prose', Pokedex, 'prose', relation_lazy='joined', - name = Column(Unicode(16), nullable=False, index=True, + name = Column(Unicode(16), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=False)), - description = Column(Unicode(512), nullable=False, + description = Column(Unicode(512), nullable=True, info=dict(description=u"A longer description of the Pokédex", format='plaintext')), ) @@ -1202,9 +1202,9 @@ class Pokemon(TableBase): create_translation_table('pokemon_names', Pokemon, 'names', relation_lazy='joined', - name = Column(Unicode(20), nullable=False, index=True, + name = Column(Unicode(20), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=True, ripped=True)), - species = Column(Unicode(16), nullable=False, + species = Column(Unicode(16), nullable=True, info=dict(description=u'The short flavor text, such as "Seed" or "Lizard"; usually affixed with the word "Pokémon"', official=True, format='plaintext')), ) @@ -1389,7 +1389,7 @@ PokemonFormGroup.id = PokemonFormGroup.pokemon_id create_translation_table('pokemon_form_group_prose', PokemonFormGroup, 'prose', term = Column(Unicode(16), nullable=True, 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')), - description = Column(markdown.MarkdownColumn(1024), nullable=False, + description = Column(markdown.MarkdownColumn(1024), nullable=True, info=dict(description=u"Description of how the forms work", format='markdown')), ) @@ -1481,9 +1481,9 @@ class PokemonMoveMethod(TableBase): create_translation_table('pokemon_move_method_prose', PokemonMoveMethod, 'prose', relation_lazy='joined', - name = Column(Unicode(64), nullable=False, index=True, + name = Column(Unicode(64), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=False)), - description = Column(Unicode(255), nullable=False, + description = Column(Unicode(255), nullable=True, info=dict(description=u"A detailed description of how the method works", format='plaintext')), ) @@ -1499,9 +1499,9 @@ class PokemonShape(TableBase): create_translation_table('pokemon_shape_prose', PokemonShape, 'prose', relation_lazy='joined', - name = Column(Unicode(24), nullable=False, index=True, + name = Column(Unicode(24), nullable=True, index=True, info=dict(description="The name", format='plaintext', official=False)), - awesome_name = Column(Unicode(16), nullable=False, + awesome_name = Column(Unicode(16), nullable=True, info=dict(description=u"A splendiferous name of the body shape", format='plaintext')), ) diff --git a/pokedex/tests/test_schema.py b/pokedex/tests/test_schema.py index 83f9de7..2ad795c 100644 --- a/pokedex/tests/test_schema.py +++ b/pokedex/tests/test_schema.py @@ -171,6 +171,7 @@ def test_texts(): good_formats = 'identifier latex'.split() assert_text = '%s is not language-specific' columns = sorted(cls.__table__.c, key=lambda c: c.name) + text_columns = [] for column in columns: format = column.info.get('format', None) if format is not None: @@ -183,6 +184,7 @@ def test_texts(): raise AssertionError('%s: identifier column name/type mismatch' % column) if column.info.get('official', None) and format not in 'gametext plaintext': raise AssertionError('%s: official text with bad format' % column) + text_columns.append(column) else: if isinstance(column.type, (markdown.MarkdownColumn, tables.Unicode)): raise AssertionError('%s: text column without format' % column) @@ -190,6 +192,11 @@ def test_texts(): raise AssertionError('%s: non-plaintext name' % column) # No mention of English in the description assert 'English' not in column.info['description'], column + # If there's more than one text column in a translation table, + # they have to be nullable, to support missing translations + if hasattr(cls, 'local_language') and len(text_columns) > 1: + for column in text_columns: + assert column.nullable def test_identifiers_with_names(): """Test that named tables have identifiers, and non-named tables don't From 8cddfefc4cbb7490fd501d606e74ec40e59114fc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 21:00:58 +0300 Subject: [PATCH 4/7] Add __repr__ to mapped classes This makes string representations of lists, tuples, dicts, etc. with the DB objects make sense --- pokedex/db/tables.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index 5670da9..55cd487 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -62,6 +62,9 @@ class TableSuperclass(object): def __str__(self): return unicode(self).encode('utf8') + def __repr__(self): + return unicode(self).encode('utf8') + mapped_classes = [] class TableMetaclass(DeclarativeMeta): def __init__(cls, name, bases, attrs): From 9fdb8e1bd09afa4130303354c0460bf606160f8c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 22:05:56 +0300 Subject: [PATCH 5/7] Make session.default_language set/get work --- pokedex/db/multilang.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py index 4adb68d..b5f87e4 100644 --- a/pokedex/db/multilang.py +++ b/pokedex/db/multilang.py @@ -159,12 +159,15 @@ class MultilangSession(Session): @property def default_language(self): - # XXX need to get the right mapped class for this to work - raise NotImplementedError + # Need to import tables here to avoid a circular dependency + from pokedex.db import tables + query = self.query(tables.Language) + query = query.filter_by(id=self._default_language_id) + return query.one() @default_language.setter def default_language(self, new): - self._default_language_id = new#.id + self._default_language_id = new.id @default_language.deleter def default_language(self): From 4291c33c007b0158458344f507f807633b90082e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 4 Apr 2011 21:51:12 +0300 Subject: [PATCH 6/7] Make MultilangSession's language class configurable --- pokedex/db/multilang.py | 12 +++++++++++- pokedex/tests/test_schema.py | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py index b5f87e4..6690b8a 100644 --- a/pokedex/db/multilang.py +++ b/pokedex/db/multilang.py @@ -157,11 +157,21 @@ class MultilangSession(Session): """A tiny Session subclass that adds support for a default language.""" _default_language_id = 9 # English. XXX magic constant + def __init__(self, *args, **kwargs): + try: + self.language_class = kwargs.pop('language_class') + except KeyError: + # Set the default language_class + # We need to import here, to prevent a circular depencency + from pokedex.db.tables import Language + self.language_class = Language + super(MultilangSession, self).__init__(*args, **kwargs) + @property def default_language(self): # Need to import tables here to avoid a circular dependency from pokedex.db import tables - query = self.query(tables.Language) + query = self.query(self.language_class) query = query.filter_by(id=self._default_language_id) return query.one() diff --git a/pokedex/tests/test_schema.py b/pokedex/tests/test_schema.py index 2ad795c..bd42502 100644 --- a/pokedex/tests/test_schema.py +++ b/pokedex/tests/test_schema.py @@ -66,7 +66,7 @@ def test_i18n_table_creation(): # OK, create all the tables and gimme a session Base.metadata.create_all() - sm = sessionmaker(class_=MultilangSession) + sm = sessionmaker(class_=MultilangSession, language_class=Language) sess = MultilangScopedSession(sm) # Create some languages and foos to bind together @@ -82,7 +82,7 @@ def test_i18n_table_creation(): # Commit so the above get primary keys filled in sess.commit() - sess.default_language = lang_en.id + sess.default_language = lang_en # Give our foo some names, as directly as possible foo_text = FooText() From 9441ffb165298af87c930543cc1f43ee2a40230b Mon Sep 17 00:00:00 2001 From: Eevee Date: Tue, 5 Apr 2011 21:03:41 -0700 Subject: [PATCH 7/7] Remove the responsibility of setting a default language from multilang. Caller now has to do it. No need to avoid circular deps, no need to do much of anything at all. --- pokedex/db/__init__.py | 12 +++++++++--- pokedex/db/multilang.py | 24 ++++++++++-------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pokedex/db/__init__.py b/pokedex/db/__init__.py index e2790da..ea103e7 100644 --- a/pokedex/db/__init__.py +++ b/pokedex/db/__init__.py @@ -1,7 +1,7 @@ -from sqlalchemy import MetaData, Table, engine_from_config, orm +from sqlalchemy import engine_from_config, orm from ..defaults import get_default_db_uri -from .tables import metadata +from .tables import Language, metadata from .multilang import MultilangSession, MultilangScopedSession @@ -41,7 +41,13 @@ 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(class_=MultilangSession, **all_session_args) + sm = orm.sessionmaker(class_=MultilangSession, language_class=Language, + **all_session_args) session = MultilangScopedSession(sm) + # Default to English + session.default_language = session.query(Language) \ + .filter_by(identifier=u'en') \ + .one() + return session diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py index 6690b8a..bd4266f 100644 --- a/pokedex/db/multilang.py +++ b/pokedex/db/multilang.py @@ -154,26 +154,22 @@ def create_translation_table(_table_name, foreign_class, relation_name, return Translations class MultilangSession(Session): - """A tiny Session subclass that adds support for a default language.""" - _default_language_id = 9 # English. XXX magic constant + """A tiny Session subclass that adds support for a default language. + + Caller will need to assign something to `default_language` before this will + actually work. + """ + _default_language_id = 0 # Better fill this in, caller def __init__(self, *args, **kwargs): - try: - self.language_class = kwargs.pop('language_class') - except KeyError: - # Set the default language_class - # We need to import here, to prevent a circular depencency - from pokedex.db.tables import Language - self.language_class = Language + self.language_class = kwargs.pop('language_class') super(MultilangSession, self).__init__(*args, **kwargs) @property def default_language(self): - # Need to import tables here to avoid a circular dependency - from pokedex.db import tables - query = self.query(self.language_class) - query = query.filter_by(id=self._default_language_id) - return query.one() + return self.query(self.language_class) \ + .filter_by(id=self._default_language_id) \ + .one() @default_language.setter def default_language(self, new):