|
28 | 28 |
|
29 | 29 | import logging |
30 | 30 | import unittest |
| 31 | +import sys |
31 | 32 |
|
32 | 33 | from sqlalchemy import event, inspect, select |
33 | 34 | from sqlalchemy.dialects.mssql.base import MSDialect, DECIMAL as MS_DECIMAL |
34 | 35 | from sqlalchemy.dialects.mysql.base import MySQLDialect |
35 | 36 | from sqlalchemy.engine import create_engine |
36 | 37 | from sqlalchemy.exc import NoSuchTableError, OperationalError |
37 | 38 | from sqlalchemy.ext import compiler |
38 | | -from sqlalchemy.orm import declarative_base |
| 39 | +from sqlalchemy.orm import declarative_base, Session, sessionmaker |
39 | 40 | from sqlalchemy.schema import ( |
40 | 41 | Column, |
41 | 42 | CreateTable, |
| 43 | + DDL, |
42 | 44 | DDLElement, |
43 | 45 | Index, |
44 | 46 | MetaData, |
|
59 | 61 | Time, |
60 | 62 | ) |
61 | 63 |
|
| 64 | +from cardinal_pythonlib.sqlalchemy.engine_func import ( |
| 65 | + get_dialect_name, |
| 66 | + SqlaDialectName, |
| 67 | +) |
62 | 68 | from cardinal_pythonlib.sqlalchemy.schema import ( |
63 | 69 | add_index, |
64 | 70 | column_creation_ddl, |
|
98 | 104 | view_exists, |
99 | 105 | ) |
100 | 106 | from cardinal_pythonlib.sqlalchemy.session import SQLITE_MEMORY_URL |
| 107 | +from cardinal_pythonlib.sqlalchemy.sqlserver import ( |
| 108 | + if_sqlserver_disable_constraints, |
| 109 | +) |
101 | 110 |
|
102 | 111 | Base = declarative_base() |
103 | 112 | log = logging.getLogger(__name__) |
@@ -485,7 +494,7 @@ def test_mssql_transaction_count(self) -> None: |
485 | 494 |
|
486 | 495 |
|
487 | 496 | class YetMoreSchemaTests(unittest.TestCase): |
488 | | - def __init__(self, *args, echo: bool = False, **kwargs) -> None: |
| 497 | + def __init__(self, *args, echo: bool = True, **kwargs) -> None: |
489 | 498 | self.echo = echo |
490 | 499 | super().__init__(*args, **kwargs) |
491 | 500 |
|
@@ -631,6 +640,75 @@ def test_execute_ddl(self) -> None: |
631 | 640 | with self.assertRaises(AssertionError): |
632 | 641 | execute_ddl(self.engine) # neither |
633 | 642 |
|
| 643 | + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 644 | + # Dialect conditionality for DDL |
| 645 | + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 646 | + @staticmethod |
| 647 | + def _present_in_log_output(_cm, msg: str) -> bool: |
| 648 | + """ |
| 649 | + Detects whether a string is present, INCLUDING AS A SUBSTRING, in |
| 650 | + log output captured from an assertLogs() context manager. |
| 651 | + """ |
| 652 | + return any(msg in line for line in _cm.output) |
| 653 | + |
| 654 | + def test_ddl_dialect_conditionality_1(self) -> None: |
| 655 | + self.engine.echo = True # will write to log at INFO level |
| 656 | + |
| 657 | + # 1. Check that logging capture works, and our _present_in_log_output |
| 658 | + # function. |
| 659 | + with self.assertLogs(level=logging.INFO) as cm: |
| 660 | + log.info("dummy call") |
| 661 | + self.assertTrue(self._present_in_log_output(cm, "dummy")) |
| 662 | + |
| 663 | + # 2. Check our dialect is as expected: SQLite. |
| 664 | + dialect_name = get_dialect_name(self.engine) |
| 665 | + self.assertEqual(dialect_name, SqlaDialectName.SQLITE) |
| 666 | + |
| 667 | + # 3. Seeing if DDL built with execute_if() will execute "directly" when |
| 668 | + # set to execute-if-SQLite. It executes - but not conditionally! |
| 669 | + ddl_yes = DDL("CREATE TABLE yesplease (a INT)").execute_if( |
| 670 | + dialect=SqlaDialectName.SQLITE |
| 671 | + ) |
| 672 | + with self.assertLogs(level=logging.INFO) as cm: |
| 673 | + execute_ddl(self.engine, ddl=ddl_yes) |
| 674 | + self.assertTrue( |
| 675 | + self._present_in_log_output(cm, "CREATE TABLE yesplease") |
| 676 | + ) |
| 677 | + |
| 678 | + # 4. Seeing if DDL built with execute_if() will execute "directly" when |
| 679 | + # set to execute-if-MySQL. It executes - therefore not conditionally! |
| 680 | + # I'd misunderstood this: it is NOT conditionally executed. |
| 681 | + ddl_no = DDL("CREATE TABLE nothanks (a INT)").execute_if( |
| 682 | + dialect=SqlaDialectName.MYSQL |
| 683 | + ) |
| 684 | + with self.assertLogs(level=logging.INFO) as cm: |
| 685 | + execute_ddl(self.engine, ddl=ddl_no) |
| 686 | + self.assertTrue( |
| 687 | + self._present_in_log_output(cm, "CREATE TABLE nothanks") |
| 688 | + ) |
| 689 | + # I'd thought this would be false, but it is true. |
| 690 | + |
| 691 | + def test_ddl_dialect_conditionality_2(self) -> None: |
| 692 | + # Therefore: |
| 693 | + self.engine.echo = True # will write to log at INFO level |
| 694 | + # The test above (test_ddl_dialect_conditionality_1) proves that |
| 695 | + # this code will log something if SQL is emitted. |
| 696 | + |
| 697 | + session = sessionmaker( |
| 698 | + bind=self.engine, future=True |
| 699 | + )() # type: Session |
| 700 | + |
| 701 | + if sys.version_info < (3, 10): |
| 702 | + log.warning( |
| 703 | + "Unable to use unittest.TestCase.assertNoLogs; " |
| 704 | + "needs Python 3.10; skipping test" |
| 705 | + ) |
| 706 | + return |
| 707 | + with self.assertNoLogs(level=logging.INFO): |
| 708 | + with if_sqlserver_disable_constraints(session, tablename="person"): |
| 709 | + pass |
| 710 | + # Should do nothing, therefore emit no logs. |
| 711 | + |
634 | 712 |
|
635 | 713 | class SchemaAbstractTests(unittest.TestCase): |
636 | 714 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
0 commit comments