Skip to content

Commit e1f72ff

Browse files
qu3viponCaselIT
authored andcommitted
Add logging for config load source in verbose mode
### Description I added a log message to indicate where the Alembic configuration is being loaded from (file vs in-memory), which helps when verbose mode is enabled. I also wrote tests for both branches. Each test passes when run individually, but they fail when running the entire test suite. It seems to be related to how Alembic’s logging hierarchy interacts with the global test environment, and I'm having difficulty diagnosing the issue with my current understanding of the logging system. I'd appreciate any guidance or suggestions from maintainers on how the logging should be captured or how tests in this area are expected to be structured. ### Checklist This pull request is: - [ ] A documentation / typographical error fix - Good to go, no issue or tests are needed - [x] A short code fix - please include the issue number, and create an issue if none exists, which must include a complete example of the issue. one line code fixes without an issue and demonstration will not be accepted. - Please include: `Fixes: #<issue number>` in the commit message - please include tests. one line code fixes without tests will not be accepted. - [ ] A new feature implementation - please include the issue number, and create an issue if none exists, which must include a complete example of how the feature would look. - Please include: `Fixes: #<issue number>` in the commit message - please include tests. **Have a nice day!** Fixes: #1737 Closes: #1754 Pull-request: #1754 Pull-request-sha: c9cacc7 Change-Id: I9941f68b264bdc297c6afb2d5e8af34fe0d234fa
1 parent 13c9b71 commit e1f72ff

File tree

4 files changed

+106
-0
lines changed

4 files changed

+106
-0
lines changed

alembic/config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from argparse import Namespace
55
from configparser import ConfigParser
66
import inspect
7+
import logging
78
import os
89
from pathlib import Path
910
import re
@@ -28,6 +29,9 @@
2829
from .util.pyfiles import _preserving_path_as_str
2930

3031

32+
log = logging.getLogger(__name__)
33+
34+
3135
class Config:
3236
r"""Represent an Alembic configuration.
3337
@@ -244,10 +248,20 @@ def file_config(self) -> ConfigParser:
244248
here = Path()
245249
self.config_args["here"] = here.as_posix()
246250
file_config = ConfigParser(self.config_args)
251+
252+
verbose = getattr(self.cmd_opts, "verbose", False)
247253
if self._config_file_path:
248254
compat.read_config_parser(file_config, [self._config_file_path])
255+
if verbose:
256+
log.info(
257+
"Loading config from file: %s", self._config_file_path
258+
)
249259
else:
250260
file_config.add_section(self.config_ini_section)
261+
if verbose:
262+
log.info(
263+
"No config file provided; using in-memory default config"
264+
)
251265
return file_config
252266

253267
@util.memoized_property

alembic/testing/env.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import importlib.machinery
2+
import logging
23
import os
34
from pathlib import Path
45
import shutil
@@ -22,7 +23,29 @@ def _get_staging_directory():
2223
return "scratch"
2324

2425

26+
_restore_log = None
27+
28+
29+
def _replace_logger():
30+
global _restore_log
31+
if _restore_log is None:
32+
_restore_log = (logging.root, logging.Logger.manager)
33+
logging.root = logging.RootLogger(logging.WARNING)
34+
logging.Logger.root = logging.root
35+
logging.Logger.manager = logging.Manager(logging.root)
36+
37+
38+
def _restore_logger():
39+
global _restore_log
40+
41+
if _restore_log is not None:
42+
logging.root, logging.Logger.manager = _restore_log
43+
logging.Logger.root = logging.root
44+
_restore_log = None
45+
46+
2547
def staging_env(create=True, template="generic", sourceless=False):
48+
_replace_logger()
2649
cfg = _testing_config()
2750
if create:
2851
path = _join_path(_get_staging_directory(), "scripts")
@@ -61,6 +84,7 @@ def clear_staging_env():
6184

6285
engines.testing_reaper.close_all()
6386
shutil.rmtree(_get_staging_directory(), True)
87+
_restore_logger()
6488

6589

6690
def script_file_fixture(txt):

docs/build/unreleased/1737.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.. change::
2+
:tags: feature, operations
3+
:tickets: 1737
4+
5+
When alembic is run in "verbose" mode, alembic now logs a message to
6+
indicate from which file is used to load the configuration.

tests/test_config.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import io
2+
import logging
13
import os
24
import pathlib
35
import sys
@@ -45,6 +47,66 @@ def tearDown(self):
4547

4648

4749
class ConfigTest(TestBase):
50+
def test_config_logging_with_file(self):
51+
buf = io.StringIO()
52+
handler = logging.StreamHandler(buf)
53+
handler.setLevel(logging.INFO)
54+
55+
logger = logging.getLogger("alembic.config")
56+
# logger.x=True
57+
with (
58+
mock.patch.object(logger, "handlers", []),
59+
mock.patch.object(logger, "level", logging.NOTSET),
60+
):
61+
logger.addHandler(handler)
62+
logger.setLevel(logging.INFO)
63+
64+
cfg = _write_config_file(
65+
"""
66+
[alembic]
67+
script_location = %(base_path)s/db/migrations
68+
"""
69+
)
70+
test_cfg = config.Config(
71+
cfg.config_file_name, config_args=dict(base_path="/tmp")
72+
)
73+
test_cfg.cmd_opts = mock.Mock(verbose=True)
74+
75+
_ = test_cfg.file_config
76+
77+
output = buf.getvalue()
78+
assert "Loading config from file" in output
79+
assert cfg.config_file_name.replace("/", os.path.sep) in output
80+
81+
def tearDown(self):
82+
clear_staging_env()
83+
84+
def test_config_logging_without_file(self):
85+
buf = io.StringIO()
86+
handler = logging.StreamHandler(buf)
87+
handler.setLevel(logging.INFO)
88+
89+
logger = logging.getLogger("alembic.config")
90+
with (
91+
mock.patch.object(logger, "handlers", []),
92+
mock.patch.object(logger, "level", logging.NOTSET),
93+
):
94+
95+
logger.addHandler(handler)
96+
logger.setLevel(logging.INFO)
97+
98+
test_cfg = config.Config()
99+
test_cfg.cmd_opts = mock.Mock(verbose=True)
100+
101+
_ = test_cfg.file_config
102+
103+
output = buf.getvalue()
104+
assert "No config file provided" in output
105+
assert (
106+
test_cfg.config_file_name is None
107+
and test_cfg._config_file_path is None
108+
)
109+
48110
def test_config_no_file_main_option(self):
49111
cfg = config.Config()
50112
cfg.set_main_option("url", "postgresql://foo/bar")

0 commit comments

Comments
 (0)