# Buggy code
def post_create_table(self, table):
    """Build table-level CREATE options like ENGINE and COLLATE."""

    table_opts = []

    opts = dict(
        (k[len(self.dialect.name) + 1 :].upper(), v)
        for k, v in table.kwargs.items()
        if k.startswith("%s_" % self.dialect.name)
    )

    if table.comment is not None:
        opts["COMMENT"] = table.comment

    partition_options = [
        "PARTITION_BY",
        "PARTITIONS",
        "SUBPARTITIONS",
        "SUBPARTITION_BY",
    ]

    nonpart_options = set(opts).difference(partition_options)
    part_options = set(opts).intersection(partition_options)

    for opt in topological.sort(
        [
            ("DEFAULT_CHARSET", "COLLATE"),
            ("DEFAULT_CHARACTER_SET", "COLLATE"),
        ],
        nonpart_options,
    ):
        arg = opts[opt]
        if opt in _reflection._options_of_type_string:
            arg = self.sql_compiler.render_literal_value(arg, sqltypes.String())

        if opt in (
            "DATA_DIRECTORY",
            "INDEX_DIRECTORY",
            "DEFAULT_CHARACTER_SET",
            "CHARACTER_SET",
            "DEFAULT_CHARSET",
            "DEFAULT_COLLATE",
        ):
            opt = opt.replace("_", " ")

        joiner = "="
        if opt in (
            "TABLESPACE",
            "DEFAULT CHARACTER SET",
            "CHARACTER SET",
            "COLLATE",
        ):
            joiner = " "

        table_opts.append(joiner.join((opt, arg)))

    for opt in topological.sort(
        [
            ("PARTITION_BY", "PARTITIONS"),
            ("PARTITION_BY", "SUBPARTITION_BY"),
            ("PARTITION_BY", "SUBPARTITIONS"),
            ("PARTITIONS", "SUBPARTITIONS"),
            ("PARTITIONS", "SUBPARTITION_BY"),
            ("SUBPARTITION_BY", "SUBPARTITIONS"),
        ],
        part_options,
    ):
        arg = opts[opt]
        if opt in _reflection._options_of_type_string:
            arg = self.sql_compiler.render_literal_value(arg, sqltypes.String())

        opt = opt.replace("_", " ")
        joiner = " "

        table_opts.append(joiner.join((opt, arg)))

    return " ".join(table_opts)


# Traceback (AssertionError)
/tmp/sqlalchemybug/env/bin/python /tmp/sqlalchemybug/test_bug.py
2020-06-20 11:18:15,850 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2020-06-20 11:18:15,850 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,851 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-06-20 11:18:15,851 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,852 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-06-20 11:18:15,852 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,852 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2020-06-20 11:18:15,852 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,854 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2020-06-20 11:18:15,854 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,854 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2020-06-20 11:18:15,854 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,855 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2020-06-20 11:18:15,855 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,856 INFO sqlalchemy.engine.base.Engine DROP TABLE IF EXISTS test_table
2020-06-20 11:18:15,856 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,859 INFO sqlalchemy.engine.base.Engine COMMIT
2020-06-20 11:18:15,860 INFO sqlalchemy.engine.base.Engine DESCRIBE `test_table`
2020-06-20 11:18:15,860 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,863 INFO sqlalchemy.engine.base.Engine ROLLBACK
2020-06-20 11:18:15,863 INFO sqlalchemy.engine.base.Engine
CREATE TABLE test_table (
id INTEGER NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
)COLLATE utf8_unicode_ci ENGINE=InnoDB CHARSET=utf8


2020-06-20 11:18:15,863 INFO sqlalchemy.engine.base.Engine ()
2020-06-20 11:18:15,875 INFO sqlalchemy.engine.base.Engine COMMIT
2020-06-20 11:18:15,876 INFO sqlalchemy.engine.base.Engine select TABLE_COLLATION from information_schema.TABLES where table_name = 'test_table'
2020-06-20 11:18:15,876 INFO sqlalchemy.engine.base.Engine ()
Table collation is utf8_general_ci
Traceback (most recent call last):
File "/tmp/sqlalchemybug/test_bug.py", line 36, in <module>
assert result.TABLE_COLLATION == "utf8_unicode_ci"
AssertionError

# Fixed code
def post_create_table(self, table):
    """Build table-level CREATE options like ENGINE and COLLATE."""

    table_opts = []

    opts = dict(
        (k[len(self.dialect.name) + 1 :].upper(), v)
        for k, v in table.kwargs.items()
        if k.startswith("%s_" % self.dialect.name)
    )

    if table.comment is not None:
        opts["COMMENT"] = table.comment

    partition_options = [
        "PARTITION_BY",
        "PARTITIONS",
        "SUBPARTITIONS",
        "SUBPARTITION_BY",
    ]

    nonpart_options = set(opts).difference(partition_options)
    part_options = set(opts).intersection(partition_options)

    for opt in topological.sort(
        [
            ("DEFAULT_CHARSET", "COLLATE"),
            ("DEFAULT_CHARACTER_SET", "COLLATE"),
            ("CHARSET", "COLLATE"),
            ("CHARACTER_SET", "COLLATE"),
        ],
        nonpart_options,
    ):
        arg = opts[opt]
        if opt in _reflection._options_of_type_string:
            arg = self.sql_compiler.render_literal_value(arg, sqltypes.String())

        if opt in (
            "DATA_DIRECTORY",
            "INDEX_DIRECTORY",
            "DEFAULT_CHARACTER_SET",
            "CHARACTER_SET",
            "DEFAULT_CHARSET",
            "DEFAULT_COLLATE",
        ):
            opt = opt.replace("_", " ")

        joiner = "="
        if opt in (
            "TABLESPACE",
            "DEFAULT CHARACTER SET",
            "CHARACTER SET",
            "COLLATE",
        ):
            joiner = " "

        table_opts.append(joiner.join((opt, arg)))

    for opt in topological.sort(
        [
            ("PARTITION_BY", "PARTITIONS"),
            ("PARTITION_BY", "SUBPARTITION_BY"),
            ("PARTITION_BY", "SUBPARTITIONS"),
            ("PARTITIONS", "SUBPARTITIONS"),
            ("PARTITIONS", "SUBPARTITION_BY"),
            ("SUBPARTITION_BY", "SUBPARTITIONS"),
        ],
        part_options,
    ):
        arg = opts[opt]
        if opt in _reflection._options_of_type_string:
            arg = self.sql_compiler.render_literal_value(arg, sqltypes.String())

        opt = opt.replace("_", " ")
        joiner = " "

        table_opts.append(joiner.join((opt, arg)))

    return " ".join(table_opts)