Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: Allow configuring logs level #251

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/griffe/agents/nodes/_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def __init__(self, obj: Any, name: str, parent: ObjectNode | None = None) -> Non
# which triggers the __getattr__ method of the object, which in
# turn can raise various exceptions. Probably not just __getattr__.
# See https://github.com/pawamoy/pytkdocs/issues/45
logger.debug(f"Could not unwrap {name}: {error!r}")
logger.could_not_unwrap(name=name, error=error)

self.obj: Any = obj
"""The actual Python object."""
Expand Down
2 changes: 1 addition & 1 deletion src/griffe/c3linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def c3linear_merge(*lists: list[T]) -> list[T]:
break
else:
# Loop never broke, no linearization could possibly be found.
raise ValueError("Cannot compute C3 linearization")
raise ValueError(f"Cannot compute C3 linearization for {lists!r}")


__all__ = ["c3linear_merge"]
44 changes: 30 additions & 14 deletions src/griffe/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Callable, Sequence
from typing import IO, TYPE_CHECKING, Any, Callable, ClassVar, Sequence

import colorama
from colorama import Fore, Style

from griffe import debug
from griffe.diff import find_breaking_changes
Expand All @@ -32,7 +33,7 @@
from griffe.extensions.base import load_extensions
from griffe.git import get_latest_tag, get_repo_root
from griffe.loader import GriffeLoader, load, load_git
from griffe.logger import get_logger
from griffe.logger import LogLevel, get_logger
from griffe.stats import _format_stats

if TYPE_CHECKING:
Expand Down Expand Up @@ -89,31 +90,28 @@ def _load_packages(
# Load each package.
for package in packages:
if not package:
logger.debug("Empty package name, continuing")
logger.empty_package_name()
continue
logger.info(f"Loading package {package}")
logger.loading_package(package=package)
try:
loader.load(package, try_relative_path=True, find_stubs_package=find_stubs_package)
except ModuleNotFoundError as error:
logger.error(f"Could not find package {package}: {error}") # noqa: TRY400
logger.could_not_find_package(package=package, error=error)
except ImportError as error:
logger.exception(f"Tried but could not import package {package}: {error}") # noqa: TRY401
logger.info("Finished loading packages")
logger.finished_loading()

# Resolve aliases.
if resolve_aliases:
logger.info("Starting alias resolution")
logger.starting_alias_resolution()
unresolved, iterations = loader.resolve_aliases(implicit=resolve_implicit, external=resolve_external)
if unresolved:
logger.info(f"{len(unresolved)} aliases were still unresolved after {iterations} iterations")
logger.unresolved_aliases(unresolved=len(unresolved), iterations=iterations)
else:
logger.info(f"All aliases were resolved after {iterations} iterations")
logger.all_resolved(iterations=iterations)
return loader


_level_choices = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")


def _extensions_type(value: str) -> Sequence[str | dict[str, Any]]:
try:
return json.loads(value)
Expand Down Expand Up @@ -190,7 +188,7 @@ def add_common_options(subparser: argparse.ArgumentParser) -> None:
"--log-level",
metavar="LEVEL",
default=DEFAULT_LOG_LEVEL,
choices=_level_choices,
choices=[level.value.upper() for level in LogLevel],
type=str.upper,
help="Set the log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`.",
)
Expand Down Expand Up @@ -489,6 +487,22 @@ def check(
return 0


class _ColorFormatter(logging.Formatter):
COLORS: ClassVar[dict[int, str]] = {
LogLevel.trace.number: Style.DIM,
LogLevel.debug.number: "",
LogLevel.info.number: Fore.BLUE,
LogLevel.success.number: Fore.GREEN,
LogLevel.warning.number: Fore.YELLOW,
LogLevel.error.number: Fore.RED,
LogLevel.critical.number: Style.BRIGHT + Fore.RED,
}

def format(self, record: logging.LogRecord):
formatter = logging.Formatter(f"{self.COLORS[record.levelno]}{self._fmt}{Style.RESET_ALL}")
return formatter.format(record)


def main(args: list[str] | None = None) -> int:
"""Run the main program.

Expand Down Expand Up @@ -519,7 +533,9 @@ def main(args: list[str] | None = None) -> int:
)
return 1
else:
logging.basicConfig(format="%(levelname)-10s %(message)s", level=level)
handler = logging.StreamHandler()
handler.setFormatter(_ColorFormatter())
logging.basicConfig(format="%(message)s", level=level, handlers=[handler])

# Run subcommand.
commands: dict[str, Callable[..., int]] = {"check": check, "dump": dump}
Expand Down
4 changes: 2 additions & 2 deletions src/griffe/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ def inherited_members(self) -> dict[str, Alias]:
try:
mro = self.mro()
except ValueError as error:
logger.debug(error)
logger.cannot_compute_mro(error=error)
return {}
inherited_members = {}
for base in reversed(mro):
Expand Down Expand Up @@ -1588,7 +1588,7 @@ def resolved_bases(self) -> list[Object]:
if resolved_base.is_alias:
resolved_base = resolved_base.final_target
except (AliasResolutionError, CyclicAliasError, KeyError):
logger.debug(f"Base class {base_path} is not loaded, or not static, it cannot be resolved")
logger.cannot_resolve_base_class(base_path=base_path)
else:
resolved_bases.append(resolved_base)
return resolved_bases
Expand Down
6 changes: 3 additions & 3 deletions src/griffe/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ def _alias_incompatibilities(
old_member = old_obj.target if old_obj.is_alias else old_obj # type: ignore[union-attr]
new_member = new_obj.target if new_obj.is_alias else new_obj # type: ignore[union-attr]
except AliasResolutionError:
logger.debug(f"API check: {old_obj.path} | {new_obj.path}: skip alias with unknown target")
logger.skip_alias(old_obj_path=old_obj.path, new_obj_path=new_obj.path)
return

yield from _type_based_yield(old_member, new_member, ignore_private=ignore_private, seen_paths=seen_paths)
Expand All @@ -463,10 +463,10 @@ def _member_incompatibilities(
seen_paths = set() if seen_paths is None else seen_paths
for name, old_member in old_obj.all_members.items():
if ignore_private and name.startswith("_"):
logger.debug(f"API check: {old_obj.path}.{name}: skip private object")
logger.skip_private_object(obj_path=f"{old_obj.path}.{name}")
continue

logger.debug(f"API check: {old_obj.path}.{name}")
logger.checking(obj_path=f"{old_obj.path}.{name}")
try:
new_member = new_obj.all_members[name]
except KeyError:
Expand Down
2 changes: 1 addition & 1 deletion src/griffe/docstrings/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ def parse(
return [DocstringSectionText(docstring.value)]


__all__ = ["parse", "Parser", "parsers"]
__all__ = ["parse", "parsers"]
6 changes: 1 addition & 5 deletions src/griffe/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,11 +899,7 @@ def _build_constant(
optimize=1,
)
except SyntaxError:
logger.debug(
f"Tried and failed to parse {node.value!r} as Python code, "
"falling back to using it as a string literal "
"(postponed annotations might help: https://peps.python.org/pep-0563/)",
)
logger.failed_to_parse_node(node=node)
else:
return _build(parsed.body, parent, **kwargs) # type: ignore[attr-defined]
return {type(...): lambda _: "..."}.get(type(node.value), repr)(node.value)
Expand Down
2 changes: 1 addition & 1 deletion src/griffe/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def iter_submodules(
for subpath in self._filter_py_modules(path):
rel_subpath = subpath.relative_to(path)
if rel_subpath.parent in skip:
logger.debug(f"Skip {subpath}, another module took precedence")
logger.skip_module(module_path=subpath)
continue
py_file = rel_subpath.suffix == ".py"
stem = rel_subpath.stem
Expand Down
49 changes: 21 additions & 28 deletions src/griffe/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ def load(
)
obj_path: str
if objspec in _builtin_modules:
logger.debug(f"{objspec} is a builtin module")
logger.builtin_module(module=objspec)
if self.allow_inspection:
logger.debug(f"Inspecting {objspec}")
logger.inspecting_module(module=objspec)
obj_path = objspec # type: ignore[assignment]
top_module = self._inspect_module(objspec) # type: ignore[arg-type]
self.modules_collection.set_member(top_module.path, top_module)
Expand All @@ -185,16 +185,16 @@ def load(
find_stubs_package=find_stubs_package,
)
except ModuleNotFoundError:
logger.debug(f"Could not find {objspec}")
logger.could_not_find_module(module=objspec)
if self.allow_inspection:
logger.debug(f"Trying inspection on {objspec}")
logger.trying_inspection(module=objspec)
obj_path = objspec # type: ignore[assignment]
top_module = self._inspect_module(objspec) # type: ignore[arg-type]
self.modules_collection.set_member(top_module.path, top_module)
else:
raise
else:
logger.debug(f"Found {objspec}: loading")
logger.found(objspec=objspec)
try:
top_module = self._load_package(package, submodules=submodules)
except LoadingError as error:
Expand Down Expand Up @@ -252,9 +252,7 @@ def resolve_aliases(
)
resolved |= next_resolved
unresolved |= next_unresolved
logger.debug(
f"Iteration {iteration} finished, {len(resolved)} aliases resolved, still {len(unresolved)} to go",
)
logger.finished_iteration(iteration=iteration, resolved=len(resolved), unresolved=len(unresolved))
return unresolved, iteration

def expand_exports(self, module: Module, seen: set | None = None) -> None:
Expand All @@ -277,14 +275,14 @@ def expand_exports(self, module: Module, seen: set | None = None) -> None:
try:
next_module = self.modules_collection.get_member(module_path)
except KeyError:
logger.debug(f"Cannot expand '{export.canonical_path}', try pre-loading corresponding package")
logger.cannot_expand(path=export.canonical_path)
continue
if next_module.path not in seen:
self.expand_exports(next_module, seen)
try:
expanded |= next_module.exports
except TypeError:
logger.warning(f"Unsupported item in {module.path}.__all__: {export} (use strings only)")
logger.unsupported_all_item(module_path=module.path, item=export)
# It's a string, simply add it to the current exports.
else:
expanded.add(export)
Expand Down Expand Up @@ -324,16 +322,15 @@ def expand_wildcards(
try:
self.load(package, try_relative_path=False)
except ImportError as error:
logger.debug(f"Could not expand wildcard import {member.name} in {obj.path}: {error}")
logger.could_not_expand_wildcard_error(name=member.name, path=obj.path, error=error)
continue

# Try getting the module from which every public object is imported.
try:
target = self.modules_collection.get_member(member.target_path) # type: ignore[union-attr]
except KeyError:
logger.debug(
f"Could not expand wildcard import {member.name} in {obj.path}: "
f"{cast(Alias, member).target_path} not found in modules collection",
logger.could_not_expand_wildcard_import_error(
name=member.name, path=obj.path, target=cast(Alias, member).target_path
)
continue

Expand All @@ -342,7 +339,7 @@ def expand_wildcards(
try:
self.expand_wildcards(target, external=external, seen=seen)
except (AliasResolutionError, CyclicAliasError) as error:
logger.debug(f"Could not expand wildcard import {member.name} in {obj.path}: {error}")
logger.could_not_expand_wildcard_error(name=member.name, path=obj.path, error=error)
continue

# Collect every imported object.
Expand Down Expand Up @@ -454,16 +451,16 @@ def resolve_module_aliases(
and package not in self.modules_collection
)
if load_module:
logger.debug(f"Failed to resolve alias {member.path} -> {target}")
logger.failed_to_resolve_alias(path=member.path, target=target)
try:
self.load(package, try_relative_path=False)
except ImportError as error:
logger.debug(f"Could not follow alias {member.path}: {error}")
logger.could_not_follow_alias(path=member.path, error=error)
load_failures.add(package)
except CyclicAliasError as error:
logger.debug(str(error))
logger.cyclic_alias_error(error=error)
else:
logger.debug(f"Alias {member.path} was resolved to {member.final_target.path}") # type: ignore[union-attr]
logger.alias_resolved(path=member.path, final_path=member.final_target.path) # type: ignore[union-attr]
resolved.add(member.path)

# Recurse into unseen modules and classes.
Expand Down Expand Up @@ -531,7 +528,7 @@ def _load_module_path(
submodules: bool = True,
parent: Module | None = None,
) -> Module:
logger.debug(f"Loading path {module_path}")
logger.loading_path(filepath=module_path)
if isinstance(module_path, list):
module = self._create_module(module_name, module_path)
elif module_path.suffix in {".py", ".pyi"}:
Expand All @@ -552,7 +549,7 @@ def _load_submodules(self, module: Module) -> None:
def _load_submodule(self, module: Module, subparts: tuple[str, ...], subpath: Path) -> None:
for subpart in subparts:
if "." in subpart:
logger.debug(f"Skip {subpath}, dots in filenames are not supported")
logger.skip_module_with_dots(module=subpath)
return
try:
parent_module = self._get_or_create_parent_module(module, subparts, subpath)
Expand All @@ -575,8 +572,8 @@ def _load_submodule(self, module: Module, subparts: tuple[str, ...], subpath: Pa
# but that's because in that case Python acts like the whole tree is a regular package.
# It works when the namespace package appears in only one search path (`sys.path`),
# but will fail if it appears in multiple search paths: Python will only find the first occurrence.
# It's better to not falsely suuport this, and to warn users.
logger.debug(f"{error}. Missing __init__ module?")
# It's better to not falsely support this, and to warn users.
logger.missing_init_module(error=error)
return
submodule_name = subparts[-1]
try:
Expand All @@ -590,11 +587,7 @@ def _load_submodule(self, module: Module, subparts: tuple[str, ...], subpath: Pa
logger.debug(str(error))
else:
if submodule_name in parent_module.members:
logger.debug(
f"Submodule '{submodule.path}' is shadowing the member at the same path. "
"We recommend renaming the member or the submodule (for example prefixing it with `_`), "
"see https://mkdocstrings.github.io/griffe/best_practices/#avoid-member-submodule-name-shadowing.",
)
logger.submodule_shadowing_member(filepath=submodule.filepath, path=submodule.path)
parent_module.set_member(submodule_name, submodule)

def _create_module(self, module_name: str, module_path: Path | list[Path]) -> Module:
Expand Down
Loading
Loading