Skip to content
This repository has been archived by the owner on Mar 27, 2021. It is now read-only.

Commit

Permalink
Enable logging on import of module instead of only in main().
Browse files Browse the repository at this point in the history
  • Loading branch information
dangle committed Oct 21, 2019
1 parent fed484a commit 37f543f
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 216 deletions.
16 changes: 16 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[tool.black]
line-length = 79
exclude = '''
/(
\.eggs
| \.git
| \.mypy_cache
| \.pytest_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''
11 changes: 11 additions & 0 deletions rcli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
# -*- coding: utf-8 -*-
"""The primary module for the program."""

import sys


if sys.excepthook is sys.__excepthook__:
import logging

from . import log

sys.excepthook = log.excepthook
log.enable_logging(None)
99 changes: 62 additions & 37 deletions rcli/autodetect.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@


_EntryPoint = collections.namedtuple( # All data representing an entry point.
'_EntryPoint', ('command', 'subcommand', 'callable'))
"_EntryPoint", ("command", "subcommand", "callable")
)


def setup_keyword(dist, _, value):
Expand All @@ -43,12 +44,13 @@ def setup_keyword(dist, _, value):
dist.entry_points = _ensure_entry_points_is_dict(dist.entry_points)

for command, subcommands in six.iteritems(_get_commands(dist)):
entry_point = '{command} = rcli.dispatcher:main'.format(
command=command)
entry_points = dist.entry_points.setdefault('console_scripts', [])
entry_point = "{command} = rcli.dispatcher:main".format(
command=command
)
entry_points = dist.entry_points.setdefault("console_scripts", [])
if entry_point not in entry_points:
entry_points.append(entry_point)
dist.entry_points.setdefault('rcli', []).extend(subcommands)
dist.entry_points.setdefault("rcli", []).extend(subcommands)


def _ensure_entry_points_is_dict(entry_points):
Expand All @@ -57,9 +59,11 @@ def _ensure_entry_points_is_dict(entry_points):
elif isinstance(entry_points, str):
config = configparser.ConfigParser()
config.read_string(entry_points)
return {k: ['='.join(t) for t in section.items()]
for k, section in config.items()
if k != config.default_section}
return {
k: ["=".join(t) for t in section.items()]
for k, section in config.items()
if k != config.default_section
}
return entry_points


Expand All @@ -72,19 +76,25 @@ def egg_info_writer(cmd, basename, filename):
basename: The basename of the file to write.
filename: The full path of the file to write into the egg info.
"""
setupcfg = next((f for f in setuptools.findall()
if os.path.basename(f) == 'setup.cfg'), None)
setupcfg = next(
(
f
for f in setuptools.findall()
if os.path.basename(f) == "setup.cfg"
),
None,
)
if not setupcfg:
return
parser = six.moves.configparser.ConfigParser() # type: ignore
parser.read(setupcfg)
if not parser.has_section('rcli') or not parser.items('rcli'):
if not parser.has_section("rcli") or not parser.items("rcli"):
return
config = dict(parser.items('rcli')) # type: typing.Dict[str, typing.Any]
config = dict(parser.items("rcli")) # type: typing.Dict[str, typing.Any]
for k, v in six.iteritems(config):
if v.lower() in ('y', 'yes', 'true'):
if v.lower() in ("y", "yes", "true"):
config[k] = True
elif v.lower() in ('n', 'no', 'false'):
elif v.lower() in ("n", "no", "false"):
config[k] = False
else:
try:
Expand All @@ -94,8 +104,9 @@ def egg_info_writer(cmd, basename, filename):
cmd.write_file(basename, filename, json.dumps(config))


def _get_commands(dist # type: setuptools.dist.Distribution
):
def _get_commands(
dist # type: setuptools.dist.Distribution
):
# type: (...) -> typing.Dict[str, typing.Set[str]]
"""Find all commands belonging to the given distribution.
Expand All @@ -107,8 +118,11 @@ def _get_commands(dist # type: setuptools.dist.Distribution
A dictionary containing a mapping of primary commands to sets of
subcommands.
"""
py_files = (f for f in setuptools.findall()
if os.path.splitext(f)[1].lower() == '.py')
py_files = (
f
for f in setuptools.findall()
if os.path.splitext(f)[1].lower() == ".py"
)
pkg_files = (f for f in py_files if _get_package_name(f) in dist.packages)
commands = {} # type: typing.Dict[str, typing.Set[str]]
for file_name in pkg_files:
Expand All @@ -121,10 +135,11 @@ def _get_commands(dist # type: setuptools.dist.Distribution
return commands


def _append_commands(dct, # type: typing.Dict[str, typing.Set[str]]
module_name, # type: str
commands # type:typing.Iterable[_EntryPoint]
):
def _append_commands(
dct, # type: typing.Dict[str, typing.Set[str]]
module_name, # type: str
commands, # type:typing.Iterable[_EntryPoint]
):
# type: (...) -> None
"""Append entry point strings representing the given Command objects.
Expand All @@ -137,13 +152,15 @@ def _append_commands(dct, # type: typing.Dict[str, typing.Set[str]]
commands: A list of Command objects to convert to entry point strings.
"""
for command in commands:
entry_point = '{command}{subcommand} = {module}{callable}'.format(
entry_point = "{command}{subcommand} = {module}{callable}".format(
command=command.command,
subcommand=(':{}'.format(command.subcommand)
if command.subcommand else ''),
subcommand=(
":{}".format(command.subcommand) if command.subcommand else ""
),
module=module_name,
callable=(':{}'.format(command.callable)
if command.callable else ''),
callable=(
":{}".format(command.callable) if command.callable else ""
),
)
dct.setdefault(command.command, set()).add(entry_point)

Expand All @@ -159,7 +176,7 @@ def _get_package_name(file_name):
Converts the file name to a python-style module name and retrieves the
package component.
"""
return _get_module_name(file_name).rsplit('.', 1)[0]
return _get_module_name(file_name).rsplit(".", 1)[0]


def _get_module_name(file_name):
Expand All @@ -172,7 +189,7 @@ def _get_module_name(file_name):
Returns:
Converts the file name to a python-style module and returns the name.
"""
return file_name[:-3].replace('/', '.')
return file_name[:-3].replace("/", ".")


def _get_module_commands(module):
Expand All @@ -188,12 +205,18 @@ def _get_module_commands(module):
Yields:
Command objects that represent entry points to append to setup.py.
"""
cls = next((n for n in module.body
if isinstance(n, ast.ClassDef) and n.name == 'Command'), None)
cls = next(
(
n
for n in module.body
if isinstance(n, ast.ClassDef) and n.name == "Command"
),
None,
)
if not cls:
return
methods = (n.name for n in cls.body if isinstance(n, ast.FunctionDef))
if '__call__' not in methods:
if "__call__" not in methods:
return
docstring = ast.get_docstring(module)
for commands, _ in usage.parse_commands(docstring):
Expand All @@ -216,11 +239,12 @@ def _get_class_commands(module):
nodes = (n for n in module.body if isinstance(n, ast.ClassDef))
for cls in nodes:
methods = (n.name for n in cls.body if isinstance(n, ast.FunctionDef))
if '__call__' in methods:
if "__call__" in methods:
docstring = ast.get_docstring(cls)
for commands, _ in usage.parse_commands(docstring):
yield _EntryPoint(commands[0], next(iter(commands[1:]), None),
cls.name)
yield _EntryPoint(
commands[0], next(iter(commands[1:]), None), cls.name
)


def _get_function_commands(module):
Expand All @@ -240,5 +264,6 @@ def _get_function_commands(module):
for func in nodes:
docstring = ast.get_docstring(func)
for commands, _ in usage.parse_commands(docstring):
yield _EntryPoint(commands[0], next(iter(commands[1:]), None),
func.name)
yield _EntryPoint(
commands[0], next(iter(commands[1:]), None), func.name
)
6 changes: 3 additions & 3 deletions rcli/backports/get_terminal_size.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
try:
from shutil import get_terminal_size
except ImportError:
from backports.shutil_get_terminal_size import ( # noqa: F401
get_terminal_size
)
from backports.shutil_get_terminal_size import (
get_terminal_size,
) # noqa: F401
48 changes: 26 additions & 22 deletions rcli/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,22 @@
from types import ( # noqa: F401 pylint: disable=unused-import
FunctionType,
MethodType,
ModuleType
ModuleType,
)
import collections
import inspect
import keyword
import logging
import re

from typingplus import ( # noqa: F401 pylint: disable=unused-import
from typing import ( # noqa: F401 pylint: disable=unused-import
Any,
Dict,
Generator,
Tuple,
Union,
cast,
get_type_hints
)

from typet.typing import cast, get_type_hints
import collections
import inspect
import keyword
import logging
import re
import six

from . import config # noqa: F401 pylint: disable=unused-import
Expand All @@ -38,8 +37,8 @@

_LOGGER = logging.getLogger(__name__)

_getspec = getattr(inspect, 'get{}argspec'.format('full' if six.PY3 else ''))
_ArgSpec = collections.namedtuple('_ArgSpec', ('args', 'varargs', 'varkw'))
_getspec = getattr(inspect, "get{}argspec".format("full" if six.PY3 else ""))
_ArgSpec = collections.namedtuple("_ArgSpec", ("args", "varargs", "varkw"))


def call(func, args):
Expand All @@ -52,10 +51,12 @@ def call(func, args):
Returns:
The return value of func.
"""
assert hasattr(func, '__call__'), 'Cannot call func: {}'.format(
func.__name__)
assert hasattr(func, "__call__"), "Cannot call func: {}".format(
func.__name__
)
raw_func = (
func if isinstance(func, FunctionType) else func.__class__.__call__)
func if isinstance(func, FunctionType) else func.__class__.__call__
)
hints = collections.defaultdict(lambda: Any, get_type_hints(raw_func))
argspec = _getargspec(raw_func)
named_args = {}
Expand All @@ -73,7 +74,8 @@ def call(func, args):
if nk == argspec.varargs:
varargs = value
elif (nk in argspec.args or argspec.varkw) and (
nk not in named_args or named_args[nk] is None):
nk not in named_args or named_args[nk] is None
):
named_args[nk] = value
return func(*varargs, **named_args)

Expand All @@ -96,11 +98,13 @@ def get_callable(subcommand):
callable class named Command.
"""
_LOGGER.debug(
'Creating callable from subcommand "%s".', subcommand.__name__)
'Creating callable from subcommand "%s".', subcommand.__name__
)
if isinstance(subcommand, ModuleType):
_LOGGER.debug('Subcommand is a module.')
assert hasattr(subcommand, 'Command'), (
'Module subcommand must have callable "Command" class definition.')
_LOGGER.debug("Subcommand is a module.")
assert hasattr(
subcommand, "Command"
), 'Module subcommand must have callable "Command" class definition.'
callable_ = subcommand.Command # type: ignore
else:
callable_ = subcommand
Expand Down Expand Up @@ -148,9 +152,9 @@ def _normalize(args):
the parameter.
"""
for k, v in six.iteritems(args):
nk = re.sub(r'\W|^(?=\d)', '_', k).strip('_').lower()
nk = re.sub(r"\W|^(?=\d)", "_", k).strip("_").lower()
do_not_shadow = dir(six.moves.builtins) # type: ignore
if keyword.iskeyword(nk) or nk in do_not_shadow:
nk += '_'
nk += "_"
_LOGGER.debug('Normalized "%s" to "%s".', k, nk)
yield k, nk, v
Loading

0 comments on commit 37f543f

Please sign in to comment.