Skip to content

Commit 8873e16

Browse files
committed
(chore): extract verbosity
1 parent 45ac804 commit 8873e16

File tree

3 files changed

+118
-107
lines changed

3 files changed

+118
-107
lines changed

src/scanpy/_settings.py renamed to src/scanpy/_settings/__init__.py

Lines changed: 31 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,32 @@
22

33
import inspect
44
import sys
5-
from contextlib import contextmanager
6-
from enum import EnumMeta, IntEnum
75
from functools import wraps
8-
from logging import getLevelNamesMapping
96
from pathlib import Path
107
from time import time
11-
from typing import TYPE_CHECKING, Literal, LiteralString, ParamSpec, TypeVar, get_args
8+
from typing import TYPE_CHECKING, Literal, ParamSpec, TypeVar, get_args
129

13-
from . import logging
14-
from ._compat import deprecated, old_positionals
15-
from ._singleton import SingletonMeta
16-
from .logging import _RootLogger, _set_log_file, _set_log_level
10+
from .. import logging
11+
from .._compat import deprecated, old_positionals
12+
from .._singleton import SingletonMeta
13+
from ..logging import _RootLogger, _set_log_file, _set_log_level
14+
from .verbosity import Verbosity
1715

1816
if TYPE_CHECKING:
19-
from collections.abc import Callable, Generator, Iterable
17+
from collections.abc import Callable, Iterable
2018
from types import UnionType
2119
from typing import ClassVar, Concatenate, Self, TextIO
2220

21+
from .verbosity import _VerbosityName
22+
2323
# Collected from the print_* functions in matplotlib.backends
2424
_Format = (
2525
Literal["png", "jpg", "tif", "tiff"] # noqa: PYI030
2626
| Literal["pdf", "ps", "eps", "svg", "svgz", "pgf"]
2727
| Literal["raw", "rgba"]
2828
)
29-
_VerbosityName = Literal["error", "warning", "info", "hint", "debug"]
30-
_LoggingLevelName = Literal["CRITICAL", "ERROR", "WARNING", "INFO", "HINT", "DEBUG"]
3129

32-
L = TypeVar("L", bound=LiteralString)
30+
3331
S = TypeVar("S")
3432
T = TypeVar("T")
3533
P = ParamSpec("P")
@@ -39,71 +37,6 @@
3937
AnnDataFileFormat = Literal["h5ad", "zarr"]
4038

4139

42-
_VERBOSITY_TO_LOGLEVEL: dict[int | _VerbosityName, _LoggingLevelName] = {
43-
"error": "ERROR",
44-
"warning": "WARNING",
45-
"info": "INFO",
46-
"hint": "HINT",
47-
"debug": "DEBUG",
48-
}
49-
_VERBOSITY_TO_LOGLEVEL.update(dict(enumerate(list(_VERBOSITY_TO_LOGLEVEL.values()))))
50-
51-
52-
class VerbosityMeta(EnumMeta):
53-
@property
54-
@deprecated("Use `Verbosity.warning` instead")
55-
def warn(cls) -> Verbosity:
56-
return Verbosity.warning
57-
58-
59-
class Verbosity(IntEnum, metaclass=VerbosityMeta):
60-
"""Logging verbosity levels for :attr:`scanpy.settings.verbosity`."""
61-
62-
error = 0
63-
"""Error (`0`)"""
64-
warning = 1
65-
"""Warning (`1`)"""
66-
info = 2
67-
"""Info (`2`)"""
68-
hint = 3
69-
"""Hint (`3`)"""
70-
debug = 4
71-
"""Debug (`4`)"""
72-
73-
def __eq__(self, other: object) -> bool:
74-
if isinstance(other, Verbosity):
75-
return self is other
76-
if isinstance(other, int):
77-
return self.value == other
78-
if isinstance(other, str):
79-
return self.name == other
80-
return NotImplemented
81-
82-
@property
83-
def level(self) -> int:
84-
"""The :ref:`logging level <levels>` corresponding to this verbosity level."""
85-
m = getLevelNamesMapping()
86-
return m[_VERBOSITY_TO_LOGLEVEL[self.name]]
87-
88-
@contextmanager
89-
def override(
90-
self, verbosity: Verbosity | _VerbosityName | int
91-
) -> Generator[Verbosity, None, None]:
92-
"""Temporarily override verbosity.
93-
94-
>>> import scanpy as sc
95-
>>> sc.settings.verbosity = sc.Verbosity.info
96-
>>> with sc.settings.verbosity.override(settings.verbosity.debug):
97-
... sc.settings.verbosity
98-
<Verbosity.debug: 4>
99-
>>> sc.settings.verbosity
100-
<Verbosity.info: 2>
101-
"""
102-
settings.verbosity = verbosity
103-
yield self
104-
settings.verbosity = self
105-
106-
10740
def _type_check(var: object, name: str, types: type | UnionType) -> None:
10841
if isinstance(var, types):
10942
return
@@ -173,25 +106,19 @@ def verbosity(cls) -> Verbosity:
173106

174107
@verbosity.setter
175108
def verbosity(cls, verbosity: Verbosity | _VerbosityName | int) -> None:
176-
verbosity_str_options: list[_VerbosityName] = [
177-
v for v in _VERBOSITY_TO_LOGLEVEL if isinstance(v, str)
178-
]
179-
if isinstance(verbosity, Verbosity):
180-
cls._verbosity = verbosity
181-
elif isinstance(verbosity, int):
182-
cls._verbosity = Verbosity(verbosity)
183-
elif isinstance(verbosity, str):
184-
verbosity = verbosity.lower()
185-
if verbosity not in verbosity_str_options:
186-
msg = (
187-
f"Cannot set verbosity to {verbosity}. "
188-
f"Accepted string values are: {verbosity_str_options}"
189-
)
190-
raise ValueError(msg)
191-
cls._verbosity = Verbosity(verbosity_str_options.index(verbosity))
192-
else:
193-
_type_check(verbosity, "verbosity", str | int)
194-
_set_log_level(cls, _VERBOSITY_TO_LOGLEVEL[cls._verbosity.name])
109+
try:
110+
cls._verbosity = (
111+
Verbosity[verbosity.lower()]
112+
if isinstance(verbosity, str)
113+
else Verbosity(verbosity)
114+
)
115+
except KeyError:
116+
msg = (
117+
f"Cannot set verbosity to {verbosity}. "
118+
f"Accepted string values are: {Verbosity.__members__.keys()}"
119+
)
120+
raise ValueError(msg) from None
121+
_set_log_level(cls, cls._verbosity.level)
195122

196123
@property
197124
def N_PCS(cls) -> int:
@@ -382,14 +309,14 @@ def logfile(cls) -> TextIO | None:
382309

383310
@logfile.setter
384311
def logfile(cls, logfile: Path | str | TextIO | None) -> None:
385-
if not hasattr(logfile, "write") and logfile:
312+
if not logfile: # "" or None
313+
logfile = sys.stdout if cls._is_run_from_ipython() else sys.stderr
314+
if isinstance(logfile, Path | str):
386315
cls.logpath = logfile
387-
else: # file object
388-
if not logfile: # None or ''
389-
logfile = sys.stdout if cls._is_run_from_ipython() else sys.stderr
390-
cls._logfile = logfile
391-
cls._logpath = None
392-
_set_log_file(cls)
316+
return
317+
cls._logfile = logfile
318+
cls._logpath = None
319+
_set_log_file(cls)
393320

394321
@property
395322
def categories_to_ignore(cls) -> list[str]:
@@ -500,7 +427,7 @@ def _set_figure_params( # noqa: PLR0913
500427
rcParams["figure.facecolor"] = facecolor
501428
rcParams["axes.facecolor"] = facecolor
502429
if scanpy:
503-
from .plotting._rcmod import set_rcParams_scanpy
430+
from ..plotting._rcmod import set_rcParams_scanpy
504431

505432
set_rcParams_scanpy(fontsize=fontsize, color_map=color_map)
506433
if figsize is not None:

src/scanpy/_settings/verbosity.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from __future__ import annotations
2+
3+
from contextlib import contextmanager
4+
from enum import EnumMeta, IntEnum
5+
from logging import getLevelNamesMapping
6+
from typing import TYPE_CHECKING
7+
8+
from .._compat import deprecated
9+
10+
if TYPE_CHECKING:
11+
from collections.abc import Generator
12+
from typing import Literal
13+
14+
_VerbosityName = Literal["error", "warning", "info", "hint", "debug"]
15+
_LoggingLevelName = Literal["CRITICAL", "ERROR", "WARNING", "INFO", "HINT", "DEBUG"]
16+
17+
18+
_VERBOSITY_TO_LOGLEVEL: dict[int | _VerbosityName, _LoggingLevelName] = {
19+
"error": "ERROR",
20+
"warning": "WARNING",
21+
"info": "INFO",
22+
"hint": "HINT",
23+
"debug": "DEBUG",
24+
}
25+
_VERBOSITY_TO_LOGLEVEL.update(dict(enumerate(list(_VERBOSITY_TO_LOGLEVEL.values()))))
26+
27+
28+
class VerbosityMeta(EnumMeta):
29+
@property
30+
@deprecated("Use `Verbosity.warning` instead")
31+
def warn(cls) -> Verbosity:
32+
return Verbosity.warning
33+
34+
35+
class Verbosity(IntEnum, metaclass=VerbosityMeta):
36+
"""Logging verbosity levels for :attr:`scanpy.settings.verbosity`."""
37+
38+
error = 0
39+
"""Error (`0`)"""
40+
warning = 1
41+
"""Warning (`1`)"""
42+
info = 2
43+
"""Info (`2`)"""
44+
hint = 3
45+
"""Hint (`3`)"""
46+
debug = 4
47+
"""Debug (`4`)"""
48+
49+
def __eq__(self, other: object) -> bool:
50+
if isinstance(other, Verbosity):
51+
return self is other
52+
if isinstance(other, int):
53+
return self.value == other
54+
if isinstance(other, str):
55+
return self.name == other
56+
return NotImplemented
57+
58+
@property
59+
def level(self) -> int:
60+
"""The :ref:`logging level <levels>` corresponding to this verbosity level."""
61+
m = getLevelNamesMapping()
62+
return m[_VERBOSITY_TO_LOGLEVEL[self.name]]
63+
64+
@contextmanager
65+
def override(
66+
self, verbosity: Verbosity | _VerbosityName | int
67+
) -> Generator[Verbosity, None, None]:
68+
"""Temporarily override verbosity.
69+
70+
>>> import scanpy as sc
71+
>>> sc.settings.verbosity = sc.Verbosity.info
72+
>>> with sc.settings.verbosity.override(settings.verbosity.debug):
73+
... sc.settings.verbosity
74+
<Verbosity.debug: 4>
75+
>>> sc.settings.verbosity
76+
<Verbosity.info: 2>
77+
"""
78+
from scanpy import settings
79+
80+
settings.verbosity = verbosity
81+
try:
82+
yield self
83+
finally:
84+
settings.verbosity = self

src/scanpy/logging.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from session_info2 import SessionInfo
2020

21-
from ._settings import settings
21+
from ._settings import SettingsMeta
2222

2323

2424
# This is currently the only documented API
@@ -74,7 +74,7 @@ def debug(self, msg, *, time=None, deep=None, extra=None) -> datetime:
7474
return self.log(DEBUG, msg, time=time, deep=deep, extra=extra)
7575

7676

77-
def _set_log_file(settings: settings) -> None:
77+
def _set_log_file(settings: SettingsMeta) -> None:
7878
file = settings.logfile
7979
name = settings.logpath
8080
root = settings._root_logger
@@ -86,7 +86,7 @@ def _set_log_file(settings: settings) -> None:
8686
root.addHandler(h)
8787

8888

89-
def _set_log_level(settings: settings, level: int) -> None:
89+
def _set_log_level(settings: SettingsMeta, level: int) -> None:
9090
root = settings._root_logger
9191
root.setLevel(level)
9292
for h in list(root.handlers):

0 commit comments

Comments
 (0)