2828from alembic .operations import Operations
2929from alembic .operations import ops
3030from alembic .operations import schemaobj
31+ from alembic .operations .toimpl import create_table as _create_table
32+ from alembic .operations .toimpl import drop_table as _drop_table
3133from alembic .testing import assert_raises_message
3234from alembic .testing import combinations
3335from alembic .testing import config
@@ -1362,6 +1364,7 @@ def test_create_table_literal_binds(self):
13621364
13631365class CustomOpTest (TestBase ):
13641366 def test_custom_op (self ):
1367+
13651368 @Operations .register_operation ("create_sequence" )
13661369 class CreateSequenceOp (MigrateOperation ):
13671370 """Create a SEQUENCE."""
@@ -1385,6 +1388,87 @@ def create_sequence(operations, operation):
13851388 op .create_sequence ("foob" )
13861389 context .assert_ ("CREATE SEQUENCE foob" )
13871390
1391+ def test_replace_op (self , restore_operations ):
1392+ restore_operations (Operations )
1393+ context = op_fixture ()
1394+
1395+ log_table = Table (
1396+ "log_table" ,
1397+ MetaData (),
1398+ Column ("action" , String ),
1399+ Column ("table_name" , String ),
1400+ )
1401+
1402+ @Operations .implementation_for (ops .CreateTableOp , replace = True )
1403+ def create_table_proxy_log (operations , operation ):
1404+ _create_table (operations , operation )
1405+ operations .execute (
1406+ log_table .insert ().values (["create" , operation .table_name ])
1407+ )
1408+
1409+ op .create_table ("some_table" , Column ("colname" , Integer ))
1410+
1411+ @Operations .implementation_for (ops .CreateTableOp , replace = True )
1412+ def create_table_proxy_invert (operations , operation ):
1413+ _drop_table (operations , ops .DropTableOp (operation .table_name ))
1414+ operations .execute (
1415+ log_table .insert ().values (["delete" , operation .table_name ])
1416+ )
1417+
1418+ op .create_table ("some_table" )
1419+
1420+ context .assert_ (
1421+ "CREATE TABLE some_table (colname INTEGER)" ,
1422+ "INSERT INTO log_table (action, table_name) "
1423+ "VALUES (:action, :table_name)" ,
1424+ "DROP TABLE some_table" ,
1425+ "INSERT INTO log_table (action, table_name) "
1426+ "VALUES (:action, :table_name)" ,
1427+ )
1428+
1429+ def test_replace_error (self ):
1430+ with expect_raises_message (
1431+ ValueError ,
1432+ "Can not set dispatch function for object "
1433+ "<class 'alembic.operations.ops.CreateTableOp'>: "
1434+ "key already exists. To replace existing function, use "
1435+ "replace=True." ,
1436+ ):
1437+
1438+ @Operations .implementation_for (ops .CreateTableOp )
1439+ def create_table (operations , operation ):
1440+ pass
1441+
1442+ def test_replace_custom_op (self , restore_operations ):
1443+ restore_operations (Operations )
1444+ context = op_fixture ()
1445+
1446+ @Operations .register_operation ("create_user" )
1447+ class CreateUserOp (MigrateOperation ):
1448+ def __init__ (self , user_name ):
1449+ self .user_name = user_name
1450+
1451+ @classmethod
1452+ def create_user (cls , operations , user_name ):
1453+ op = CreateUserOp (user_name )
1454+ return operations .invoke (op )
1455+
1456+ @Operations .implementation_for (CreateUserOp , replace = True )
1457+ def create_user (operations , operation ):
1458+ operations .execute ("CREATE USER %s" % operation .user_name )
1459+
1460+ op .create_user ("bob" )
1461+
1462+ @Operations .implementation_for (CreateUserOp , replace = True )
1463+ def create_user_alternative (operations , operation ):
1464+ operations .execute (
1465+ "CREATE ROLE %s WITH LOGIN" % operation .user_name
1466+ )
1467+
1468+ op .create_user ("bob" )
1469+
1470+ context .assert_ ("CREATE USER bob" , "CREATE ROLE bob WITH LOGIN" )
1471+
13881472
13891473class ObjectFromToTest (TestBase ):
13901474 """Test operation round trips for to_obj() / from_obj().
0 commit comments