1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2012, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from sqlalchemy
import (
20 Table
, Column
, MetaData
, Index
,
21 Integer
, Float
, Unicode
, UnicodeText
, DateTime
, Boolean
,
22 ForeignKey
, UniqueConstraint
, PickleType
)
23 from sqlalchemy
.orm
import sessionmaker
, relationship
24 from sqlalchemy
.ext
.declarative
import declarative_base
25 from sqlalchemy
.sql
import select
, insert
26 from migrate
import changeset
28 from mediagoblin
.db
.sql
.base
import GMGTableBase
29 from mediagoblin
.db
.sql
.util
import MigrationManager
, RegisterMigration
32 # This one will get filled with local migrations
36 #######################################################
37 # Migration set 1: Define initial models, no migrations
38 #######################################################
40 Base1
= declarative_base(cls
=GMGTableBase
)
42 class Creature1(Base1
):
43 __tablename__
= "creature"
45 id = Column(Integer
, primary_key
=True)
46 name
= Column(Unicode
, unique
=True, nullable
=False, index
=True)
47 num_legs
= Column(Integer
, nullable
=False)
48 is_demon
= Column(Boolean
)
51 __tablename__
= "level"
53 id = Column(Unicode
, primary_key
=True)
54 name
= Column(Unicode
)
55 description
= Column(Unicode
)
56 exits
= Column(PickleType
)
58 SET1_MODELS
= [Creature1
, Level1
]
62 #######################################################
63 # Migration set 2: A few migrations and new model
64 #######################################################
66 Base2
= declarative_base(cls
=GMGTableBase
)
68 class Creature2(Base2
):
69 __tablename__
= "creature"
71 id = Column(Integer
, primary_key
=True)
72 name
= Column(Unicode
, unique
=True, nullable
=False, index
=True)
73 num_legs
= Column(Integer
, nullable
=False)
74 magical_powers
= relationship("CreaturePower2")
76 class CreaturePower2(Base2
):
77 __tablename__
= "creature_power"
79 id = Column(Integer
, primary_key
=True)
81 Integer
, ForeignKey('creature.id'), nullable
=False)
82 name
= Column(Unicode
)
83 description
= Column(Unicode
)
84 hitpower
= Column(Integer
, nullable
=False)
87 __tablename__
= "level"
89 id = Column(Unicode
, primary_key
=True)
90 name
= Column(Unicode
)
91 description
= Column(Unicode
)
93 class LevelExit2(Base2
):
94 __tablename__
= "level_exit"
96 id = Column(Integer
, primary_key
=True)
97 name
= Column(Unicode
)
99 Unicode
, ForeignKey('level.id'), nullable
=False)
101 Unicode
, ForeignKey('level.id'), nullable
=False)
103 SET2_MODELS
= [Creature2
, CreaturePower2
, Level2
, LevelExit2
]
106 @RegisterMigration(1, FULL_MIGRATIONS
)
107 def creature_remove_is_demon(db_conn
):
109 Remove the is_demon field from the creature model. We don't need
112 metadata
= MetaData(bind
=db_conn
.engine
)
113 creature_table
= Table(
114 'creature', metadata
,
115 autoload
=True, autoload_with
=db_conn
.engine
)
116 creature_table
.drop_column('is_demon')
119 @RegisterMigration(2, FULL_MIGRATIONS
)
120 def creature_powers_new_table(db_conn
):
122 Add a new table for creature powers. Nothing needs to go in it
123 yet though as there wasn't anything that previously held this
126 metadata
= MetaData(bind
=db_conn
.engine
)
127 creature_powers
= Table(
128 'creature_power', metadata
,
129 Column('id', Integer
, primary_key
=True),
131 Integer
, ForeignKey('creature.id'), nullable
=False),
132 Column('name', Unicode
),
133 Column('description', Unicode
),
134 Column('hitpower', Integer
, nullable
=False))
135 metadata
.create_all(db_conn
.engine
)
138 @RegisterMigration(3, FULL_MIGRATIONS
)
139 def level_exits_new_table(db_conn
):
141 Make a new table for level exits and move the previously pickled
142 stuff over to here (then drop the old unneeded table)
144 # First, create the table
145 # -----------------------
146 metadata
= MetaData(bind
=db_conn
.engine
)
148 'level_exit', metadata
,
149 Column('id', Integer
, primary_key
=True),
150 Column('name', Unicode
),
152 Integer
, ForeignKey('level.id'), nullable
=False),
154 Integer
, ForeignKey('level.id'), nullable
=False))
155 metadata
.create_all(db_conn
.engine
)
157 # And now, convert all the old exit pickles to new level exits
158 # ------------------------------------------------------------
160 # Minimal representation of level table.
161 # Not auto-introspecting here because of pickle table. I'm not
162 # sure sqlalchemy can auto-introspect pickle columns.
165 Column('id', Integer
, primary_key
=True),
166 Column('exits', PickleType
))
168 # query over and insert
169 result
= db_conn
.execute(
170 select([levels
], levels
.c
.exits
!=None))
173 this_exit
= level
['exits']
175 # Insert the level exit
177 level_exits
.insert().values(
178 name
=this_exit
['name'],
179 from_level
=this_exit
['from_level'],
180 to_level
=this_exit
['to_level']))
182 # Finally, drop the old level exits pickle table
183 # ----------------------------------------------
184 levels
.drop_column('exits')
187 # A hack! At this point we freeze-fame and get just a partial list of
190 SET2_MIGRATIONS
= copy
.copy(FULL_MIGRATIONS
)
192 #######################################################
193 # Migration set 3: Final migrations
194 #######################################################
196 Base3
= declarative_base(cls
=GMGTableBase
)
198 class Creature3(Base3
):
199 __tablename__
= "creature"
201 id = Column(Integer
, primary_key
=True)
202 name
= Column(Unicode
, unique
=True, nullable
=False, index
=True)
203 num_limbs
= Column(Integer
, nullable
=False)
204 magical_powers
= relationship("CreaturePower3")
206 class CreaturePower3(Base3
):
207 __tablename__
= "creature_power"
209 id = Column(Integer
, primary_key
=True)
211 Integer
, ForeignKey('creature.id'), nullable
=False, index
=True)
212 name
= Column(Unicode
)
213 description
= Column(Unicode
)
214 hitpower
= Column(Float
, nullable
=False)
217 __tablename__
= "level"
219 id = Column(Unicode
, primary_key
=True)
220 name
= Column(Unicode
)
221 description
= Column(Unicode
)
223 class LevelExit3(Base3
):
224 __tablename__
= "level_exit"
226 id = Column(Integer
, primary_key
=True)
227 name
= Column(Unicode
)
229 Unicode
, ForeignKey('level.id'), nullable
=False, index
=True)
231 Unicode
, ForeignKey('level.id'), nullable
=False, index
=True)
234 SET3_MODELS
= [Creature3
, CreaturePower3
, Level3
, LevelExit3
]
235 SET3_MIGRATIONS
= FULL_MIGRATIONS
238 @RegisterMigration(4, FULL_MIGRATIONS
)
239 def creature_num_legs_to_num_limbs(db_conn
):
241 Turns out we're tracking all sorts of limbs, not "legs"
242 specifically. Humans would be 4 here, for instance. So we
245 metadata
= MetaData(bind
=db_conn
.engine
)
246 creature_table
= Table(
247 'creature', metadata
,
248 autoload
=True, autoload_with
=db_conn
.engine
)
249 creature_table
.c
.num_legs
.alter(name
=u
"num_limbs")
252 @RegisterMigration(5, FULL_MIGRATIONS
)
253 def level_exit_index_from_and_to_level(db_conn
):
255 Index the from and to levels of the level exit table.
257 metadata
= MetaData(bind
=db_conn
.engine
)
259 'level_exit', metadata
,
260 autoload
=True, autoload_with
=db_conn
.engine
)
261 Index('ix_from_level', level_exit
.c
.from_level
).create(db_conn
.engine
)
262 Index('ix_to_exit', level_exit
.c
.to_exit
).create(db_conn
.engine
)
265 @RegisterMigration(6, FULL_MIGRATIONS
)
266 def creature_power_index_creature(db_conn
):
268 Index our foreign key relationship to the creatures
270 metadata
= MetaData(bind
=db_conn
.engine
)
271 creature_power
= Table(
272 'creature_power', metadata
,
273 autoload
=True, autoload_with
=db_conn
.engine
)
274 Index('ix_creature', creature_power
.c
.creature
).create(db_conn
.engine
)
277 @RegisterMigration(7, FULL_MIGRATIONS
)
278 def creature_power_hitpower_to_float(db_conn
):
280 Convert hitpower column on creature power table from integer to
283 Turns out we want super precise values of how much hitpower there
286 metadata
= MetaData(bind
=db_conn
.engine
)
287 creature_power
= Table(
288 'creature_power', metadata
,
289 autoload
=True, autoload_with
=db_conn
.engine
)
290 creature_power
.c
.hitpower
.alter(type=Float
)
293 def _insert_migration1_objects(session
):
295 Test objects to insert for the first set of things
299 [Creature1(name
=u
'centipede',
302 Creature1(name
=u
'wolf',
305 # don't ask me what a wizardsnake is.
306 Creature1(name
=u
'wizardsnake',
312 [Level1(id=u
'necroplex',
313 name
=u
'The Necroplex',
314 description
=u
'A complex full of pure deathzone.',
316 'deathwell': 'evilstorm',
317 'portal': 'central_park'}),
318 Level1(id=u
'evilstorm',
320 description
=u
'A storm full of pure evil.',
321 exits
={}), # you can't escape the evilstorm
322 Level1(id=u
'central_park',
323 name
=u
'Central Park, NY, NY',
324 description
=u
"New York's friendly Central Park.",
326 'portal': 'necroplex'})])
331 def _insert_migration2_objects(session
):
333 Test objects to insert for the second set of things
346 description
=u
"A blast of icy breath!",
350 description
=u
"A frightening stare, for sure!",
357 name
=u
'death_rattle',
358 description
=u
'A rattle... of DEATH!',
361 name
=u
'sneaky_stare',
362 description
=u
"The sneakiest stare you've ever seen!",
365 name
=u
'slithery_smoke',
366 description
=u
"A blast of slithery, slithery smoke.",
369 name
=u
'treacherous_tremors',
370 description
=u
"The ground shakes beneath footed animals!",
375 [Level2(id=u
'necroplex',
376 name
=u
'The Necroplex',
377 description
=u
'A complex full of pure deathzone.'),
378 Level2(id=u
'evilstorm',
380 description
=u
'A storm full of pure evil.',
381 exits
=[]), # you can't escape the evilstorm
382 Level2(id=u
'central_park',
383 name
=u
'Central Park, NY, NY',
384 description
=u
"New York's friendly Central Park.")])
388 [LevelExit2(name
=u
'deathwell',
389 from_level
=u
'necroplex',
390 to_level
=u
'evilstorm'),
391 LevelExit2(name
=u
'portal',
392 from_level
=u
'necroplex',
393 to_level
=u
'central_park')])
395 # there are no evilstorm exits because there is no exit from the
400 [LevelExit2(name
=u
'portal',
401 from_level
=u
'central_park',
402 to_level
=u
'necroplex')])
407 def _insert_migration3_objects(session
):
409 Test objects to insert for the third set of things
422 description
=u
"A blast of icy breath!",
426 description
=u
"A frightening stare, for sure!",
433 name
=u
'death_rattle',
434 description
=u
'A rattle... of DEATH!',
437 name
=u
'sneaky_stare',
438 description
=u
"The sneakiest stare you've ever seen!",
441 name
=u
'slithery_smoke',
442 description
=u
"A blast of slithery, slithery smoke.",
445 name
=u
'treacherous_tremors',
446 description
=u
"The ground shakes beneath footed animals!",
448 # annnnnd one more to test a floating point hitpower
455 description
=u
'Smitten by holy wrath!',
460 [Level3(id=u
'necroplex',
461 name
=u
'The Necroplex',
462 description
=u
'A complex full of pure deathzone.'),
463 Level3(id=u
'evilstorm',
465 description
=u
'A storm full of pure evil.',
466 exits
=[]), # you can't escape the evilstorm
467 Level3(id=u
'central_park',
468 name
=u
'Central Park, NY, NY',
469 description
=u
"New York's friendly Central Park.")])
473 [LevelExit3(name
=u
'deathwell',
474 from_level
=u
'necroplex',
475 to_level
=u
'evilstorm'),
476 LevelExit3(name
=u
'portal',
477 from_level
=u
'necroplex',
478 to_level
=u
'central_park')])
480 # there are no evilstorm exits because there is no exit from the
485 [LevelExit3(name
=u
'portal',
486 from_level
=u
'central_park',
487 to_level
=u
'necroplex')])
492 class CollectingPrinter(object):
496 def __call__(self
, string
):
497 self
.collection
.append(string
)
500 def combined_string(self
):
501 return u
''.join(self
.collection
)
504 def create_test_engine():
505 from sqlalchemy
import create_engine
506 engine
= create_engine('sqlite:///:memory:', echo
=False)
507 Session
= sessionmaker(bind
=engine
)
508 return engine
, Session
511 def assert_col_type(column
, this_class
):
512 assert isinstance(column
.type, this_class
)
515 def _get_level3_exits(session
, level
):
517 [(level_exit
.name
, level_exit
.to_level
)
519 session
.query(LevelExit3
).filter_by(from_level
=level
.id)])
522 def test_set1_to_set3():
523 # Create / connect to database
524 # ----------------------------
526 engine
, Session
= create_test_engine()
528 # Create tables by migrating on empty initial set
529 # -----------------------------------------------
531 printer
= CollectingPrinter()
532 migration_manager
= MigrationManager(
533 '__main__', SET1_MODELS
, SET1_MIGRATIONS
, Session(),
536 # Check latest migration and database current migration
537 assert migration_manager
.latest_migration
== 0
538 assert migration_manager
.database_current_migration
== None
540 result
= migration_manager
.init_or_migrate()
542 # Make sure output was "inited"
543 assert result
== u
'inited'
545 assert printer
.combined_string
== (
546 "-> Initializing main mediagoblin tables... done.\n")
547 # Check version in database
548 assert migration_manager
.latest_migration
== 0
549 assert migration_manager
.database_current_migration
== 0
551 # Install the initial set
552 # -----------------------
554 _insert_migration1_objects(Session())
556 # Try to "re-migrate" with same manager settings... nothing should happen
557 migration_manager
= MigrationManager(
558 '__main__', SET1_MODELS
, SET1_MIGRATIONS
, Session(),
560 assert migration_manager
.init_or_migrate() == None
562 # Check version in database
563 assert migration_manager
.latest_migration
== 0
564 assert migration_manager
.database_current_migration
== 0
566 # Sanity check a few things in the database...
567 metadata
= MetaData(bind
=engine
)
569 # Check the structure of the creature table
570 creature_table
= Table(
571 'creature', metadata
,
572 autoload
=True, autoload_with
=engine
)
573 assert set(creature_table
.c
.keys()) == set(
574 ['id', 'name', 'num_legs', 'is_demon'])
575 assert_col_type(creature_table
.c
.id, Integer
)
576 assert_col_type(creature_table
.c
.name
, Unicode
)
577 assert creature_table
.c
.name
.nullable
is False
578 assert creature_table
.c
.name
.index
is True
579 assert creature_table
.c
.name
.unique
is True
580 assert_col_type(creature_table
.c
.num_legs
, Integer
)
581 assert creature_table
.c
.num_legs
.nullable
is False
582 assert_col_type(creature_table
.c
.is_demon
, Boolean
)
584 # Check the structure of the level table
587 autoload
=True, autoload_with
=engine
)
588 assert set(level_table
.c
.keys()) == set(
589 ['id', 'name', 'description', 'exits'])
590 assert_col_type(level_table
.c
.id, Unicode
)
591 assert level_table
.c
.id.primary_key
is True
592 assert_col_type(level_table
.c
.name
, Unicode
)
593 assert_col_type(level_table
.c
.description
, Unicode
)
594 # Skipping exits... Not sure if we can detect pickletype, not a
595 # big deal regardless.
597 # Now check to see if stuff seems to be in there.
600 creature
= session
.query(Creature1
).filter_by(
601 name
=u
'centipede').one()
602 assert creature
.num_legs
== 100
603 assert creature
.is_demon
== False
605 creature
= session
.query(Creature1
).filter_by(
607 assert creature
.num_legs
== 4
608 assert creature
.is_demon
== False
610 creature
= session
.query(Creature1
).filter_by(
611 name
=u
'wizardsnake').one()
612 assert creature
.num_legs
== 0
613 assert creature
.is_demon
== True
615 level
= session
.query(Level1
).filter_by(
616 id=u
'necroplex').one()
617 assert level
.name
== u
'The Necroplex'
618 assert level
.description
== u
'A complex of pure deathzone.'
619 assert level
.exits
== {
620 'deathwell': 'evilstorm',
621 'portal': 'central_park'}
623 level
= session
.query(Level1
).filter_by(
624 id=u
'evilstorm').one()
625 assert level
.name
== u
'Evil Storm'
626 assert level
.description
== u
'A storm full of pure evil.'
627 assert level
.exits
== {} # You still can't escape the evilstorm!
629 level
= session
.query(Level1
).filter_by(
630 id=u
'central_park').one()
631 assert level
.name
== u
'Central Park, NY, NY'
632 assert level
.description
== u
"New York's friendly Central Park."
633 assert level
.exits
== {
634 'portal': 'necroplex'}
636 # Create new migration manager, but make sure the db migration
637 # isn't said to be updated yet
638 printer
= CollectingPrinter()
639 migration_manager
= MigrationManager(
640 '__main__', SET3_MODELS
, SET3_MIGRATIONS
, Session(),
643 assert migration_manager
.latest_migration
== 3
644 assert migration_manager
.database_current_migration
== 0
647 result
= migration_manager
.init_or_migrate()
649 # Make sure result was "migrated"
650 assert result
== u
'migrated'
652 # TODO: Check output to user
653 assert printer
.combined_string
== """\
654 -> Updating main mediagoblin tables...
655 + Running migration 1, "creature_remove_is_demon"... done.
656 + Running migration 2, "creature_powers_new_table"... done.
657 + Running migration 3, "level_exits_new_table"... done."""
659 # Make sure version matches expected
660 migration_manager
= MigrationManager(
661 '__main__', SET3_MODELS
, SET3_MIGRATIONS
, Session(),
663 assert migration_manager
.latest_migration
== 3
664 assert migration_manager
.database_current_migration
== 3
666 # Check all things in database match expected
668 # Check the creature table
669 creature_table
= Table(
670 'creature', metadata
,
671 autoload
=True, autoload_with
=engine
)
672 assert set(creature_table
.c
.keys()) == set(
673 ['id', 'name', 'num_limbs'])
674 assert_col_type(creature_table
.c
.id, Integer
)
675 assert_col_type(creature_table
.c
.name
, Unicode
)
676 assert creature_table
.c
.name
.nullable
is False
677 assert creature_table
.c
.name
.index
is True
678 assert creature_table
.c
.name
.unique
is True
679 assert_col_type(creature_table
.c
.num_legs
, Integer
)
680 assert creature_table
.c
.num_legs
.nullable
is False
682 # Check the CreaturePower table
683 creature_power_table
= Table(
684 'creature_power', metadata
,
685 autoload
=True, autoload_with
=engine
)
686 assert set(creature_power_table
.c
.keys()) == set(
687 ['id', 'creature', 'name', 'description', 'hitpower'])
688 assert_col_type(creature_power_table
.c
.id, Integer
)
689 assert_col_type(creature_power_table
.c
.creature
, Integer
)
690 assert creature_power_table
.c
.creature
.nullable
is False
691 assert_col_type(creature_power_table
.c
.name
, Unicode
)
692 assert_col_type(creature_power_table
.c
.description
, Unicode
)
693 assert_col_type(creature_power_table
.c
.hitpower
, Float
)
694 assert creature_power_table
.c
.hitpower
.nullable
is False
696 # Check the structure of the level table
699 autoload
=True, autoload_with
=engine
)
700 assert set(level_table
.c
.keys()) == set(
701 ['id', 'name', 'description'])
702 assert_col_type(level_table
.c
.id, Unicode
)
703 assert level_table
.c
.id.primary_key
is True
704 assert_col_type(level_table
.c
.name
, Unicode
)
705 assert_col_type(level_table
.c
.description
, Unicode
)
707 # Check the structure of the level_exits table
708 level_exit_table
= Table(
709 'level_exit', metadata
,
710 autoload
=True, autoload_with
=engine
)
711 assert set(level_exit_table
.c
.keys()) == set(
712 ['id', 'name', 'from_level', 'to_level'])
713 assert_col_type(level_exit_table
.c
.id, Integer
)
714 assert_col_type(level_exit_table
.c
.name
, Unicode
)
715 assert_col_type(level_exit_table
.c
.from_level
, Unicode
)
716 assert level_exit_table
.c
.from_level
.nullable
is False
717 assert level_exit_table
.c
.from_level
.indexed
is True
718 assert_col_type(level_exit_table
.c
.to_level
, Unicode
)
719 assert level_exit_table
.c
.to_level
.nullable
is False
720 assert level_exit_table
.c
.to_level
.indexed
is True
722 # Now check to see if stuff seems to be in there.
724 creature
= session
.query(Creature3
).filter_by(
725 name
=u
'centipede').one()
726 assert creature
.num_limbs
== 100.0
727 assert creature
.creature_powers
== []
729 creature
= session
.query(Creature3
).filter_by(
731 assert creature
.num_limbs
== 4.0
732 assert creature
.creature_powers
== []
734 creature
= session
.query(Creature3
).filter_by(
735 name
=u
'wizardsnake').one()
736 assert creature
.num_limbs
== 0.0
737 assert creature
.creature_powers
== []
739 level
= session
.query(Level3
).filter_by(
740 id=u
'necroplex').one()
741 assert level
.name
== u
'The Necroplex'
742 assert level
.description
== u
'A complex of pure deathzone.'
743 level_exits
= _get_level3_exits(session
, level
)
744 assert level_exits
== {
745 u
'deathwell': u
'evilstorm',
746 u
'portal': u
'central_park'}
748 level
= session
.query(Level3
).filter_by(
749 id=u
'evilstorm').one()
750 assert level
.name
== u
'Evil Storm'
751 assert level
.description
== u
'A storm full of pure evil.'
752 level_exits
= _get_level3_exits(session
, level
)
753 assert level_exits
== {} # You still can't escape the evilstorm!
755 level
= session
.query(Level3
).filter_by(
756 id=u
'central_park').one()
757 assert level
.name
== u
'Central Park, NY, NY'
758 assert level
.description
== u
"New York's friendly Central Park."
759 level_exits
= _get_level3_exits(session
, level
)
760 assert level_exits
== {
761 'portal': 'necroplex'}
764 def test_set2_to_set3():
765 # Create / connect to database
766 # Create tables by migrating on empty initial set
768 # Install the initial set
769 # Check version in database
770 # Sanity check a few things in the database
773 # Make sure version matches expected
774 # Check all things in database match expected
778 def test_set1_to_set2_to_set3():
779 # Create / connect to database
780 # Create tables by migrating on empty initial set
782 # Install the initial set
783 # Check version in database
784 # Sanity check a few things in the database
787 # Make sure version matches expected
788 # Check all things in database match expected
791 # Make sure version matches expected again
792 # Check all things in database match expected again
795 # creature_table = Table(
796 # 'creature', metadata,
797 # autoload=True, autoload_with=db_conn.engine)
798 # assert set(creature_table.c.keys()) == set(
799 # ['id', 'name', 'num_legs'])
800 # assert_col_type(creature_table.c.id, Integer)
801 # assert_col_type(creature_table.c.name, Unicode)
802 # assert creature_table.c.name.nullable is False
803 # assert creature_table.c.name.index is True
804 # assert creature_table.c.name.unique is True
805 # assert_col_type(creature_table.c.num_legs, Integer)
806 # assert creature_table.c.num_legs.nullable is False
808 # # Check the CreaturePower table
809 # creature_power_table = Table(
810 # 'creature_power', metadata,
811 # autoload=True, autoload_with=db_conn.engine)
812 # assert set(creature_power_table.c.keys()) == set(
813 # ['id', 'creature', 'name', 'description', 'hitpower'])
814 # assert_col_type(creature_power_table.c.id, Integer)
815 # assert_col_type(creature_power_table.c.creature, Integer)
816 # assert creature_power_table.c.creature.nullable is False
817 # assert_col_type(creature_power_table.c.name, Unicode)
818 # assert_col_type(creature_power_table.c.description, Unicode)
819 # assert_col_type(creature_power_table.c.hitpower, Integer)
820 # assert creature_power_table.c.hitpower.nullable is False
822 # # Check the structure of the level table
823 # level_table = Table(
825 # autoload=True, autoload_with=db_conn.engine)
826 # assert set(level_table.c.keys()) == set(
827 # ['id', 'name', 'description'])
828 # assert_col_type(level_table.c.id, Unicode)
829 # assert level_table.c.id.primary_key is True
830 # assert_col_type(level_table.c.name, Unicode)
831 # assert_col_type(level_table.c.description, Unicode)
833 # # Check the structure of the level_exits table
834 # level_exit_table = Table(
835 # 'level_exit', metadata,
836 # autoload=True, autoload_with=db_conn.engine)
837 # assert set(level_exit_table.c.keys()) == set(
838 # ['id', 'name', 'from_level', 'to_level'])
839 # assert_col_type(level_exit_table.c.id, Integer)
840 # assert_col_type(level_exit_table.c.name, Unicode)
841 # assert_col_type(level_exit_table.c.from_level, Unicode)
842 # assert level_exit_table.c.from_level.nullable is False
843 # assert_col_type(level_exit_table.c.to_level, Unicode)