Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ __pycache__
/docs/_build
/.htmlcov
*.egg-info/
.vscode/
.vscode/
venv/
9 changes: 2 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,14 @@ addons:
packages:
- pypy
- python3.6
- python3.7
- python3.8

python:
- "3.6"
- "3.7"
- "3.8"

env:
matrix:
- TOXENV=py36-dj22
- TOXENV=py37-dj22
- TOXENV=py38-dj22
- TOXENV=dj22
- TOXENV=dj30

- TOXENV=flake8

Expand Down
5 changes: 3 additions & 2 deletions djclick/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
from .adapter import CommandRegistrator as command # NOQA
from .adapter import GroupRegistrator as group, pass_verbosity # NOQA


# The RegEx in setup.py requires single quotes. Rather than change it, turn off Black.
# fmt: off
__version__ = '2.2.0'
__url__ = 'https://github.com/GaretJax/django-click'
__author__ = 'Jonathan Stoppani'
__email__ = 'jonathan@stoppani.name'
__license__ = 'MIT'

# fmt: on

del click
105 changes: 64 additions & 41 deletions djclick/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

class OptionParseAdapter(object):
"""Django pre-1.10-compatible adapter, deprecated"""

def parse_args(self, args):
return (self, None) # NOCOV

Expand All @@ -22,7 +23,7 @@ def __init__(self, args):

def _get_kwargs(self):
return {
'args': self._args,
"args": self._args,
}


Expand All @@ -43,8 +44,7 @@ class DjangoCommandMixin(object):
@property
def stealth_options(self):
return sum(
([p.name] + [i.lstrip('-') for i in p.opts] for p in self.params),
[],
([p.name] + [i.lstrip("-") for i in p.opts] for p in self.params), [],
)

def invoke(self, ctx):
Expand All @@ -54,22 +54,26 @@ def invoke(self, ctx):
# Honor the --traceback flag
if ctx.traceback: # NOCOV
raise
styled_message = click.style('{}: {}'.format(e.__class__.__name__, e), fg='red', bold=True)
styled_message = click.style(
"{}: {}".format(e.__class__.__name__, e), fg="red", bold=True
)
click.echo(styled_message, err=True)
ctx.exit(1)

def run_from_argv(self, argv):
"""
Called when run from the command line.
"""
prog_name = '{} {}'.format(os.path.basename(argv[0]), argv[1])
prog_name = "{} {}".format(os.path.basename(argv[0]), argv[1])
try:
# We won't get an exception here in standalone_mode=False
exit_code = self.main(args=argv[2:], prog_name=prog_name, standalone_mode=False)
exit_code = self.main(
args=argv[2:], prog_name=prog_name, standalone_mode=False
)
if exit_code:
sys.exit(exit_code)
except click.ClickException as e:
if getattr(e.ctx, 'traceback', False): # NOCOV
if getattr(e.ctx, "traceback", False): # NOCOV
raise
e.show()
sys.exit(e.exit_code)
Expand All @@ -84,13 +88,13 @@ def create_parser(self, progname, subcommand):
return OptionParseAdapter()

def print_help(self, prog_name, subcommand):
prog_name = '{} {}'.format(prog_name, subcommand)
self.main(['--help'], prog_name=prog_name, standalone_mode=False)
prog_name = "{} {}".format(prog_name, subcommand)
self.main(["--help"], prog_name=prog_name, standalone_mode=False)

def map_names(self):
for param in self.params:
for opt in param.opts:
yield opt.lstrip('--').replace('-', '_'), param.name
yield opt.lstrip("--").replace("-", "_"), param.name

def execute(self, *args, **kwargs):
"""
Expand All @@ -100,13 +104,14 @@ def execute(self, *args, **kwargs):
`call_command`.
"""
# Remove internal Django command handling machinery
kwargs.pop('skip_checks', None)
kwargs.pop("skip_checks", None)
parent_ctx = click.get_current_context(silent=True)
with self.make_context('', list(args), parent=parent_ctx) as ctx:
with self.make_context("", list(args), parent=parent_ctx) as ctx:
# Rename kwargs to to the appropriate destination argument name
opt_mapping = dict(self.map_names())
arg_options = {opt_mapping.get(key, key): value
for key, value in six.iteritems(kwargs)}
arg_options = {
opt_mapping.get(key, key): value for key, value in six.iteritems(kwargs)
}

# Update the context with the passed (renamed) kwargs
ctx.params.update(arg_options)
Expand Down Expand Up @@ -150,53 +155,63 @@ def suppress_colors(ctx, param, value):
class BaseRegistrator(object):
common_options = [
click.option(
'-v', '--verbosity',
"-v",
"--verbosity",
expose_value=False,
default='1',
default="1",
callback=register_on_context,
type=click.IntRange(min=0, max=3),
help=('Verbosity level; 0=minimal output, 1=normal ''output, '
'2=verbose output, 3=very verbose output.'),
help=(
"Verbosity level; 0=minimal output, 1=normal "
"output, "
"2=verbose output, 3=very verbose output."
),
),
click.option(
'--settings',
metavar='SETTINGS',
"--settings",
metavar="SETTINGS",
expose_value=False,
help=('The Python path to a settings module, e.g. '
'"myproject.settings.main". If this is not provided, the '
'DJANGO_SETTINGS_MODULE environment variable will be used.'),
help=(
"The Python path to a settings module, e.g. "
'"myproject.settings.main". If this is not provided, the '
"DJANGO_SETTINGS_MODULE environment variable will be used."
),
),
click.option(
'--pythonpath',
metavar='PYTHONPATH',
"--pythonpath",
metavar="PYTHONPATH",
expose_value=False,
help=('A directory to add to the Python path, e.g. '
'"/home/djangoprojects/myproject".'),
help=(
"A directory to add to the Python path, e.g. "
'"/home/djangoprojects/myproject".'
),
),
click.option(
'--traceback/--no-traceback',
"--traceback/--no-traceback",
is_flag=True,
default=False,
expose_value=False,
callback=register_on_context,
help='Raise on CommandError exceptions.',
help="Raise on CommandError exceptions.",
),
click.option(
'--color/--no-color',
"--color/--no-color",
default=None,
expose_value=False,
callback=suppress_colors,
help=('Enable or disable output colorization. Default is to '
'autodetect the best behavior.'),
help=(
"Enable or disable output colorization. Default is to "
"autodetect the best behavior."
),
),
]

def __init__(self, **kwargs):
self.kwargs = kwargs
self.version = self.kwargs.pop('version', get_version())
self.version = self.kwargs.pop("version", get_version())

context_settings = kwargs.setdefault('context_settings', {})
context_settings['help_option_names'] = ['-h', '--help']
context_settings = kwargs.setdefault("context_settings", {})
context_settings["help_option_names"] = ["-h", "--help"]

def get_params(self, name):
def show_help(ctx, param, value):
Expand All @@ -205,17 +220,23 @@ def show_help(ctx, param, value):
ctx.exit()

return [
click.version_option(version=self.version, message='%(version)s'),
click.option('-h', '--help', is_flag=True, is_eager=True,
expose_value=False, callback=show_help,
help='Show this message and exit.',),
click.version_option(version=self.version, message="%(version)s"),
click.option(
"-h",
"--help",
is_flag=True,
is_eager=True,
expose_value=False,
callback=show_help,
help="Show this message and exit.",
),
] + self.common_options

def __call__(self, func):
module = sys.modules[func.__module__]

# Get the command name as Django expects it
self.name = func.__module__.rsplit('.', 1)[-1]
self.name = func.__module__.rsplit(".", 1)[-1]

# Build the click command
decorators = [
Expand All @@ -237,9 +258,11 @@ def pass_verbosity(f):
"""
Marks a callback as wanting to receive the verbosity as a keyword argument.
"""

def new_func(*args, **kwargs):
kwargs['verbosity'] = click.get_current_context().verbosity
kwargs["verbosity"] = click.get_current_context().verbosity
return f(*args, **kwargs)

return update_wrapper(new_func, f)


Expand Down
12 changes: 5 additions & 7 deletions djclick/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@


class ModelInstance(click.ParamType):
def __init__(self, qs, lookup='pk'):
def __init__(self, qs, lookup="pk"):
from django.db import models

if isinstance(qs, type) and issubclass(qs, models.Model):
qs = qs.objects.all()
self.qs = qs
self.name = '{}.{}'.format(
qs.model._meta.app_label,
qs.model.__name__,
)
self.name = "{}.{}".format(qs.model._meta.app_label, qs.model.__name__,)
self.lookup = lookup

def convert(self, value, param, ctx):
Expand All @@ -25,6 +22,7 @@ def convert(self, value, param, ctx):
pass
# call `fail` outside of exception context to avoid nested exception
# handling on Python 3
msg = 'could not find {s.name} with {s.lookup}={value}'.format(
s=self, value=value)
msg = "could not find {s.name} with {s.lookup}={value}".format(
s=self, value=value
)
self.fail(msg, param, ctx)
6 changes: 3 additions & 3 deletions djclick/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
import pytest


@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def manage():
def call(*args, **kwargs):
ignore_errors = kwargs.pop('ignore_errors', False)
ignore_errors = kwargs.pop("ignore_errors", False)
assert not kwargs
cmd = [
sys.executable,
os.path.join(os.path.dirname(__file__), 'testprj', 'manage.py'),
os.path.join(os.path.dirname(__file__), "testprj", "manage.py"),
] + list(args)
try:
return subprocess.check_output(cmd, stderr=subprocess.STDOUT)
Expand Down
Loading