Skip to content

Commit 7c9a33c

Browse files
zzzeekGerrit Code Review
authored andcommitted
Merge "Add logging for config load source in verbose mode" into main
2 parents 4c42276 + e1f72ff commit 7c9a33c

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)