From 99c8be655970e4bab0a8430875b16a0881589a9f Mon Sep 17 00:00:00 2001 From: jgoclawski Date: Thu, 27 Mar 2014 14:26:28 +0100 Subject: [PATCH 1/7] Add support for Python 3 Replaces "print" with appropriate call to sys.stdout.write. Similar to self.stdout.write available in BaseCommand.handle method. Now "django_maven" works with Python 3 as well. --- django_maven/management/commands/maven.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_maven/management/commands/maven.py b/django_maven/management/commands/maven.py index 65301a8..0b4ba57 100644 --- a/django_maven/management/commands/maven.py +++ b/django_maven/management/commands/maven.py @@ -42,7 +42,8 @@ def create_parser(self, prog_name, subcommand, subcommand_class): def run_from_argv(self, argv): if len(argv) <= 2 or argv[2] in ['-h', '--help']: - print self.usage(argv[1]) + stdout = OutputWrapper(sys.stdout) + stdout.write(self.usage(argv[1])) sys.exit(1) subcommand_class = self._get_subcommand_class(argv[2]) From 5e0fe91e5a51315bee95d84f8b19269e0224eef5 Mon Sep 17 00:00:00 2001 From: jogwen Date: Tue, 14 Jul 2015 10:29:02 +0100 Subject: [PATCH 2/7] Raise the original exception if the sentry client is not enabled (it's equivalent to there being no SENTRY_DSN setting and no RAVEN_CONFIG setting). The man command output will still include the info msg "Raven is not configured (logging is disabled). Please see the documentation for more information.". --- django_maven/management/commands/maven.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django_maven/management/commands/maven.py b/django_maven/management/commands/maven.py index de3f6d8..3cadf6b 100644 --- a/django_maven/management/commands/maven.py +++ b/django_maven/management/commands/maven.py @@ -68,6 +68,8 @@ def run_from_argv(self, argv): else: raise sentry = Client(dsn) + if not sentry.is_enabled(): + raise sentry.get_ident(sentry.captureException()) self._write_error_in_stderr(e) From 5babc7e89e12bf6b06a1e2923ba84f9f35ef7532 Mon Sep 17 00:00:00 2001 From: Subrata Das Date: Fri, 28 Aug 2015 19:25:34 -0400 Subject: [PATCH 3/7] target command now gets a chance to add arguments to the expected arguments list --- django_maven/management/commands/maven.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django_maven/management/commands/maven.py b/django_maven/management/commands/maven.py index 80addc0..f3705f5 100644 --- a/django_maven/management/commands/maven.py +++ b/django_maven/management/commands/maven.py @@ -51,6 +51,7 @@ def run_from_argv(self, argv): subcommand_class = self._get_subcommand_class(argv[2]) parser = self.create_parser(argv[0], argv[2], subcommand_class) if hasattr(self, 'use_argparse') and self.use_argparse: + subcommand_class.add_arguments(parser) options = parser.parse_args(argv[3:]) cmd_options = vars(options) args = cmd_options.pop('args', ()) From bdf8b3778fcefc4a38cc769f603f10e064908f71 Mon Sep 17 00:00:00 2001 From: "Evgeny V. Generalov" Date: Sat, 12 Dec 2015 15:40:07 +0500 Subject: [PATCH 4/7] Refactored command line argument parsing (fixes #7) --- README.md | 8 ++ django_maven/compat.py | 10 +- django_maven/management/argparse_command.py | 13 ++ django_maven/management/commands/maven.py | 121 +++++++++--------- django_maven/management/optparse_command.py | 54 ++++++++ django_maven/tests.py | 72 +++++++++++ setup.py | 5 +- .../test_project => tests}/__init__.py | 0 {test_project => tests}/manage.py | 0 {test_project => tests}/requirements.txt | 0 tests/test_app/__init__.py | 0 tests/test_app/management/__init__.py | 0 .../test_app/management/commands/__init__.py | 0 tests/test_app/management/commands/testcmd.py | 24 ++++ tests/test_app/models.py | 1 + tests/test_app/tests.py | 68 ++++++++++ tests/test_project/__init__.py | 0 .../test_project/settings.py | 16 +-- {test_project => tests}/test_project/urls.py | 0 {test_project => tests}/test_project/wsgi.py | 0 tox.ini | 22 ++++ 21 files changed, 345 insertions(+), 69 deletions(-) create mode 100644 django_maven/management/argparse_command.py create mode 100644 django_maven/management/optparse_command.py create mode 100644 django_maven/tests.py rename {test_project/test_project => tests}/__init__.py (100%) rename {test_project => tests}/manage.py (100%) rename {test_project => tests}/requirements.txt (100%) create mode 100644 tests/test_app/__init__.py create mode 100644 tests/test_app/management/__init__.py create mode 100644 tests/test_app/management/commands/__init__.py create mode 100644 tests/test_app/management/commands/testcmd.py create mode 100644 tests/test_app/models.py create mode 100644 tests/test_app/tests.py create mode 100644 tests/test_project/__init__.py rename {test_project => tests}/test_project/settings.py (86%) rename {test_project => tests}/test_project/urls.py (100%) rename {test_project => tests}/test_project/wsgi.py (100%) create mode 100644 tox.ini diff --git a/README.md b/README.md index 71a5b8d..c722bd6 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,14 @@ And command with `django-maven`: If `rebuild_index` command raising exception (server die or error creating index) you see their in your Sentry. +Tests +----- + +Tests should run with tox >=1.8. Running `tox` will run all tests for all environments. +Use tox -e to run a certain environment, a list of all environments can be found with tox -l. + +Hint: it is possible to run all environments in parallel with `detox`. + The name -------- diff --git a/django_maven/compat.py b/django_maven/compat.py index baaa9b5..f7ceeed 100644 --- a/django_maven/compat.py +++ b/django_maven/compat.py @@ -1,5 +1,7 @@ from django import VERSION +from django.core.management.base import BaseCommand +__all__ = ['OutputWrapper', 'MavenBaseCommand'] if VERSION > (1, 5,): from django.core.management.base import OutputWrapper @@ -10,6 +12,7 @@ class OutputWrapper(object): """ Wrapper around stdout/stderr from django 1.5 """ + def __init__(self, out, style_func=None, ending='\n'): self._out = out self.style_func = None @@ -24,6 +27,11 @@ def write(self, msg, style_func=None, ending=None): ending = ending is None and self.ending or ending if ending and not msg.endswith(ending): msg += ending - style_func = [f for f in (style_func, self.style_func, lambda x:x) + style_func = [f for f in (style_func, self.style_func, lambda x: x) if f is not None][0] self._out.write(force_unicode(style_func(msg))) + +if hasattr(BaseCommand, 'use_argparse'): + from django_maven.management.argparse_command import MavenBaseCommand +else: + from django_maven.management.optparse_command import MavenBaseCommand diff --git a/django_maven/management/argparse_command.py b/django_maven/management/argparse_command.py new file mode 100644 index 0000000..452b8b7 --- /dev/null +++ b/django_maven/management/argparse_command.py @@ -0,0 +1,13 @@ +import argparse + +from django.core.management.base import BaseCommand + + +class MavenBaseCommand(BaseCommand): + """The *argparse* compatible command""" + + def add_arguments(self, parser): + """ + :type parser: argparse.ArgumentParser + """ + parser.add_argument('args', nargs=argparse.REMAINDER) diff --git a/django_maven/management/commands/maven.py b/django_maven/management/commands/maven.py index 8fa599f..e437720 100644 --- a/django_maven/management/commands/maven.py +++ b/django_maven/management/commands/maven.py @@ -1,77 +1,84 @@ import sys -from optparse import OptionParser from django.conf import settings from django.core.management import get_commands, load_command_class -from django.core.management.base import (BaseCommand, handle_default_options, - CommandError) - +from django.core.management.base import CommandError, handle_default_options +from django_maven.compat import MavenBaseCommand, OutputWrapper from raven import Client -from django_maven.compat import OutputWrapper - +VERBOSE_OUTPUT = 2 -class Command(BaseCommand): +class Command(MavenBaseCommand): help = 'Capture exceptions and send in Sentry' - args = '' - def _get_subcommand_class(self, command): - commands = get_commands() - app_name = commands[command] - return load_command_class(app_name, command) + def __init__(self, *args, **kw): + super(MavenBaseCommand, self).__init__(*args, **kw) + self._argv = None - def _write_error_in_stderr(self, exc): - stderr = getattr(self, 'stderr', OutputWrapper(sys.stderr, - self.style.ERROR)) - stderr.write('%s: %s' % (exc.__class__.__name__, exc)) - sys.exit(1) + def run_from_argv(self, argv): + self._argv = argv + return super(Command, self).run_from_argv(argv) - def usage(self, subcommand): - usage = 'Usage: %s %s [command options]' % (subcommand, self.args) - if self.help: - return '%s\n\n%s' % (usage, self.help) - else: - return usage + def execute(self, *args, **options): + if not args: + self.print_help(self._argv[0], self._argv[1]) + return + + subcommand = self._get_subcommand_class(args[0]) + subcommand_argv = [self._argv[0]] + list(args) + + # this is a lightweight version of the BaseCommand.run_from_argv + # it should be compatible with Django-1.5..1.9 + subcommand_args, subcommand_options = self._handle_argv( + subcommand, subcommand_argv) + try: + subcommand.execute(*subcommand_args, **subcommand_options) + except Exception as e: + if not isinstance(e, CommandError): + if int(options['verbosity']) >= VERBOSE_OUTPUT: + # self.stderr is not guaranteed to be set here + stderr = getattr(self, 'stderr', OutputWrapper( + sys.stderr, self.style.ERROR)) + stderr.write('Beautiful is better than %s. ' + 'Errors should never pass silently.' % + (e.__class__.__name__)) - def create_parser(self, prog_name, subcommand, subcommand_class): - if hasattr(self, 'use_argparse') and self.use_argparse: - return super(Command, self).create_parser(prog_name, subcommand) + sentry = self._get_sentry() + if sentry: + sentry.captureException() + # use default 'maven' options to deal with traceback + raise + + def _get_sentry(self): + if hasattr(settings, 'SENTRY_DSN'): + dsn = settings.SENTRY_DSN + elif hasattr(settings, 'RAVEN_CONFIG'): + dsn = settings.RAVEN_CONFIG.get('dsn') else: - return OptionParser(prog=prog_name, - usage=subcommand_class.usage(subcommand), - version=subcommand_class.get_version(), - option_list=subcommand_class.option_list) + return None - def run_from_argv(self, argv): - if len(argv) <= 2 or argv[2] in ['-h', '--help']: - stdout = OutputWrapper(sys.stdout) - stdout.write(self.usage(argv[1])) - sys.exit(1) + sentry = Client(dsn) + if not sentry.is_enabled(): + return None + return sentry + + def _get_subcommand_class(self, command): + commands = get_commands() + app_name = commands[command] + return load_command_class(app_name, command) - subcommand_class = self._get_subcommand_class(argv[2]) - parser = self.create_parser(argv[0], argv[2], subcommand_class) - if hasattr(self, 'use_argparse') and self.use_argparse: - subcommand_class.add_arguments(parser) - options = parser.parse_args(argv[3:]) + def _handle_argv(self, subcommand, argv): + """The universal Django command arguments parser.""" + parser = subcommand.create_parser(argv[0], argv[1]) + + if hasattr(subcommand, 'use_argparse') and subcommand.use_argparse: + options = parser.parse_args(argv[2:]) cmd_options = vars(options) + # Move positional args out of options to mimic legacy optparse args = cmd_options.pop('args', ()) else: - options, args = parser.parse_args(argv[3:]) + options, args = parser.parse_args(argv[2:]) + cmd_options = vars(options) handle_default_options(options) - try: - subcommand_class.execute(*args, **options.__dict__) - except Exception as e: - if not isinstance(e, CommandError): - if hasattr(settings, 'SENTRY_DSN'): - dsn = settings.SENTRY_DSN - elif hasattr(settings, 'RAVEN_CONFIG'): - dsn = settings.RAVEN_CONFIG.get('dsn') - else: - raise - sentry = Client(dsn) - if not sentry.is_enabled(): - raise - sentry.get_ident(sentry.captureException()) - - self._write_error_in_stderr(e) + return args, cmd_options diff --git a/django_maven/management/optparse_command.py b/django_maven/management/optparse_command.py new file mode 100644 index 0000000..df8d425 --- /dev/null +++ b/django_maven/management/optparse_command.py @@ -0,0 +1,54 @@ +import types +from optparse import AmbiguousOptionError, BadOptionError + +from django.core.management.base import BaseCommand + + +class MavenBaseCommand(BaseCommand): + """The *optparse* compatible command + + manage.py maven [maven options] subcommand [subcommand options] + """ + + @property + def use_argparse(self): + return False + + def create_parser(self, prog_name, subcommand): + parser = super(MavenBaseCommand, self).create_parser( + prog_name, subcommand) + parser._process_args = types.MethodType(_process_args, parser) + return parser + + +def _process_args(self, largs, rargs, values): + max_own_positional_args = 1 + + while rargs: + try: + arg = rargs[0] + # XXX: e.generalov@ added condition to skip processing + # arguments after subcommand name + if len(largs) >= max_own_positional_args: + return # stop now, leave this arg in rargs + # We handle bare "--" explicitly, and bare "-" is handled by the + # standard arg handler since the short arg case ensures that the + # len of the opt string is greater than 1. + elif arg == '--': + del rargs[0] + return + elif arg[0:2] == '--': + # process a single long option (possibly with value(s)) + self._process_long_opt(rargs, values) + elif arg[:1] == '-' and len(arg) > 1: + # process a cluster of short options (possibly with + # value(s) for the last one only) + self._process_short_opts(rargs, values) + elif self.allow_interspersed_args: + largs.append(arg) + del rargs[0] + else: + return # stop now, leave this arg in rargs + + except (BadOptionError, AmbiguousOptionError) as e: + largs.append(e.opt_str) diff --git a/django_maven/tests.py b/django_maven/tests.py new file mode 100644 index 0000000..6bd20b7 --- /dev/null +++ b/django_maven/tests.py @@ -0,0 +1,72 @@ +try: + from unittest.case import skipIf +except ImportError: + from unittest2.case import skipIf + +from django.core.management.base import BaseCommand, handle_default_options +from django.test import TestCase +from django_maven.management.argparse_command import \ + MavenBaseCommand as ArgparseMavenBaseCommand +from django_maven.management.optparse_command import \ + MavenBaseCommand as OptparseMavenBaseCommand + + +class TestCommandParserMixin(object): + + def test_parse_maven_default_options(self): + args, options = self._handle_argv(['--verbosity', '2']) + self.assertEqual(args, []) + self.assertEqual(int(options['verbosity']), 2) + + def test_parse_subcommand(self): + args, options = self._handle_argv(['subcommand']) + self.assertEqual(args, ['subcommand']) + + def test_should_keep_subcommand_default_options(self): + args, options = self._handle_argv(['subcommand', '--help']) + self.assertEqual(args, ['subcommand', '--help']) + + def test_should_keep_subcommand_custom_options(self): + args, options = self._handle_argv(['subcommand', '--foo']) + self.assertEqual(args, ['subcommand', '--foo']) + + def test_mavens_and_subcommands_default_options_should_not_conflict(self): + args, options = self._handle_argv( + ['--verbosity', '2', 'subcommand', '--verbosity', '3']) + self.assertEqual(int(options['verbosity']), 2) + self.assertEqual(args, ['subcommand', '--verbosity', '3']) + + def _handle_argv(self, argv): + raise NotImplementedError() + + +@skipIf(not hasattr(BaseCommand, 'use_argparse'), + 'This Django doesn\'t use argparse') +class ArgparseCommandParserTest(TestCommandParserMixin, TestCase): + """ + :type parser: django.core.management.base.CommandParser""" + + def setUp(self): + self.cmd = ArgparseMavenBaseCommand() + self.parser = self.cmd.create_parser('manage.py', 'maven') + + def _handle_argv(self, argv): + options = self.parser.parse_args(argv) + cmd_options = vars(options) + args = cmd_options.pop('args', ()) + handle_default_options(options) + return args, cmd_options + + +class OptparseCommandParserTest(TestCommandParserMixin, TestCase): + """:type parser: django.core.management.base.OptionParser""" + + def setUp(self): + self.cmd = OptparseMavenBaseCommand() + self.parser = self.cmd.create_parser('manage.py', 'maven') + + def _handle_argv(self, argv): + options, args = self.parser.parse_args(argv) + cmd_options = vars(options) + handle_default_options(options) + return args, cmd_options diff --git a/setup.py b/setup.py index a26f12b..0d62edd 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ import os -from setuptools import setup, find_packages + +from setuptools import find_packages, setup def read(filename): @@ -32,6 +33,8 @@ def read(filename): 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP' ], ) diff --git a/test_project/test_project/__init__.py b/tests/__init__.py similarity index 100% rename from test_project/test_project/__init__.py rename to tests/__init__.py diff --git a/test_project/manage.py b/tests/manage.py similarity index 100% rename from test_project/manage.py rename to tests/manage.py diff --git a/test_project/requirements.txt b/tests/requirements.txt similarity index 100% rename from test_project/requirements.txt rename to tests/requirements.txt diff --git a/tests/test_app/__init__.py b/tests/test_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_app/management/__init__.py b/tests/test_app/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_app/management/commands/__init__.py b/tests/test_app/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_app/management/commands/testcmd.py b/tests/test_app/management/commands/testcmd.py new file mode 100644 index 0000000..902e9af --- /dev/null +++ b/tests/test_app/management/commands/testcmd.py @@ -0,0 +1,24 @@ +from optparse import make_option + +from django.core.management.base import BaseCommand + + +class IgnoramusException(Exception): + pass + + +class Command(BaseCommand): + help = 'Test command' + + option_list = BaseCommand.option_list + ( + make_option('--fail', + action='store_true', + dest='fail', + default=False, + help='Raise exception'), + ) + + def handle(self, *args, **options): + if options['fail']: + raise IgnoramusException('use jQuery') + print ('OK') diff --git a/tests/test_app/models.py b/tests/test_app/models.py new file mode 100644 index 0000000..3997c55 --- /dev/null +++ b/tests/test_app/models.py @@ -0,0 +1 @@ +# naked file diff --git a/tests/test_app/tests.py b/tests/test_app/tests.py new file mode 100644 index 0000000..0a00e94 --- /dev/null +++ b/tests/test_app/tests.py @@ -0,0 +1,68 @@ +from __future__ import unicode_literals + +import locale +import os +import subprocess + +from django.test import TestCase + + +class MavenCommandTest(TestCase): + + def test_subcommand(self): + result = get_output(['django-admin.py', + 'testcmd', '--settings=test_project.settings']) + self.assertEqual('OK', result.output.strip()) + self.assertEqual(0, result.returncode) + + def test_maven_subcommand(self): + result = get_output(['django-admin.py', + 'maven', '--settings=test_project.settings', + 'testcmd']) + self.assertEqual('OK', result.output.strip()) + self.assertEqual(0, result.returncode) + + def test_maven_should_capture_exceptions_from_subcommand(self): + result = get_output(['django-admin.py', + 'maven', '--settings=test_project.settings', + '--traceback', '--verbosity=2', + 'testcmd', '--fail']) + self.assertIn('Beautiful is better than IgnoramusException. ' + 'Errors should never pass silently.', + result.output.strip()) + self.assertIn('Traceback', result.output.strip()) + self.assertNotEqual(0, result.returncode) + + +def get_output(*popenargs, **kwargs): + r"""Run command with arguments and return its output as a string.""" + + def decode(bytes): + return bytes.decode(locale.getpreferredencoding(False), 'ignore') + + process = subprocess.Popen(stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=dict(os.environ), + *popenargs, **kwargs) + output, err = process.communicate() + retcode = process.poll() + cmd = kwargs.get('args') + if cmd is None: + cmd = popenargs[0] + if retcode: + error = subprocess.CalledProcessError(retcode, cmd) + error.output = decode(err) + return error + else: + result = CalledProcessResult(cmd, decode(output)) + return result + + +class CalledProcessResult(object): + + def __init__(self, cmd, output=None): + self.cmd = cmd + self.output = output + self.returncode = 0 + + def __str__(self): + return "Command '%s' success" % (self.cmd) diff --git a/tests/test_project/__init__.py b/tests/test_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_project/test_project/settings.py b/tests/test_project/settings.py similarity index 86% rename from test_project/test_project/settings.py rename to tests/test_project/settings.py index 8b3d383..e33434e 100644 --- a/test_project/test_project/settings.py +++ b/tests/test_project/settings.py @@ -11,14 +11,9 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'sqlite3.db', # Or path to database file if using sqlite3. - # The following settings are not used with sqlite3: - 'USER': '', - 'PASSWORD': '', - 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. - 'PORT': '', # Set to empty string for default. - } + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + }, } # Hosts/domain names that are valid for this site; required if DEBUG is False @@ -79,7 +74,7 @@ STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) # Make this unique, and don't share it with anybody. @@ -89,7 +84,7 @@ TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', + # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( @@ -121,6 +116,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_maven', + 'test_app' # Uncomment the next line to enable the admin: # 'django.contrib.admin', # Uncomment the next line to enable admin documentation: diff --git a/test_project/test_project/urls.py b/tests/test_project/urls.py similarity index 100% rename from test_project/test_project/urls.py rename to tests/test_project/urls.py diff --git a/test_project/test_project/wsgi.py b/tests/test_project/wsgi.py similarity index 100% rename from test_project/test_project/wsgi.py rename to tests/test_project/wsgi.py diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..e8ab86e --- /dev/null +++ b/tox.ini @@ -0,0 +1,22 @@ +[tox] +envlist = + py{26}-dj{15,16} + py{27,34}-dj{15,16,17,18,19} + py{33}-dj{15,16,17,18} + py{35}-dj{18,19} +skipsdist = True + +[testenv] +changedir = {toxinidir}/tests +commands = python manage.py test django_maven test_app +deps = + raven + py26: unittest2 + dj15: Django>=1.5,<1.6 + dj16: Django>=1.6,<1.7 + dj17: Django>=1.7,<1.8 + dj18: Django>=1.8,<1.9 + dj19: Django>=1.9,<1.10 +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/tests +usedevelop = True From 65f95fd67c77a637f0f39b3df8ef441a63f2ff6a Mon Sep 17 00:00:00 2001 From: "Evgeny V. Generalov" Date: Sun, 13 Dec 2015 14:27:38 +0500 Subject: [PATCH 5/7] Fixed the packaging The current version of django-maven on [PyPI](https://pypi.python.org/pypi/django-maven) is 0.3, but it's the 0.2 on github. I've checked the difference between them and applied the patch. Additionally I've added the bdist_wheel section in the setup.cfg to build the universal wheel and some another small tweaks about packaging. --- CHANGES.md | 5 +++++ MANIFEST.in | 1 + setup.cfg | 8 ++++++++ setup.py | 12 ++++++------ tests/__init__.py | 0 5 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 setup.cfg delete mode 100644 tests/__init__.py diff --git a/CHANGES.md b/CHANGES.md index a953a95..0b71a93 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,11 @@ Changes ------- +0.3 (2015-04-30) +---------------- + +* Add Django 1.8 support + 0.2 (2013-07-22) ---------------- diff --git a/MANIFEST.in b/MANIFEST.in index 373f7b7..7954781 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE include README.md include CHANGES.md +recursive-exclude tests * diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3ce74c5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + +[bdist_wheel] +universal = 1 + diff --git a/setup.py b/setup.py index 0d62edd..bce79c2 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ def read(filename): setup( name='django-maven', - version='0.2', + version='0.3', license='ISC', description='Capture exceptions in django management commands ' 'into Sentry by Raven', @@ -19,11 +19,10 @@ def read(filename): url='https://github.com/saippuakauppias/django-maven', author='Denis Veselov', author_email='progr.mail@gmail.com', - include_package_data=True, - packages=find_packages(), - install_requires=[ - 'django' - ], + include_package_data=False, + packages=find_packages( + exclude=['*.tests', '*.tests.*', 'tests.*', 'tests']), + install_requires=[], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Framework :: Django', @@ -37,4 +36,5 @@ def read(filename): 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP' ], + zip_safe=True, ) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 From ebb7c1a78cb6906e3d994565628953953f738422 Mon Sep 17 00:00:00 2001 From: "Evgeny V. Generalov" Date: Mon, 11 Jan 2016 18:45:06 +0500 Subject: [PATCH 6/7] fix django-py matrix in tox.ini --- tox.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index e8ab86e..f126fa9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,10 @@ [tox] envlist = - py{26}-dj{15,16} - py{27,34}-dj{15,16,17,18,19} - py{33}-dj{15,16,17,18} - py{35}-dj{18,19} + dj{15}-py{26,27,32,33} + dj{16}-py{26,27,32,33} + dj{17}-py{27,32,33,34} + dj{18}-py{27,32,33,34,35} + dj{19}-py{27,34,35} skipsdist = True [testenv] From 9b08931345357eb38a73a1da0d5f6a8a338b4922 Mon Sep 17 00:00:00 2001 From: "Evgeny V. Generalov" Date: Mon, 11 Jan 2016 20:24:18 +0500 Subject: [PATCH 7/7] handle import error exceptions in command files --- django_maven/management/commands/maven.py | 16 +++++------ .../management/commands/maven_check.py | 27 +++++++++++++++++++ .../commands/maven_test_import_error.py | 11 ++++++++ tests/test_app/management/commands/testcmd.py | 24 ----------------- tests/test_app/tests.py | 18 ++++++++----- 5 files changed, 57 insertions(+), 39 deletions(-) create mode 100644 django_maven/management/commands/maven_check.py create mode 100644 tests/test_app/management/commands/maven_test_import_error.py delete mode 100644 tests/test_app/management/commands/testcmd.py diff --git a/django_maven/management/commands/maven.py b/django_maven/management/commands/maven.py index e437720..e4f53b4 100644 --- a/django_maven/management/commands/maven.py +++ b/django_maven/management/commands/maven.py @@ -25,14 +25,14 @@ def execute(self, *args, **options): self.print_help(self._argv[0], self._argv[1]) return - subcommand = self._get_subcommand_class(args[0]) - subcommand_argv = [self._argv[0]] + list(args) + try: + subcommand = self._get_subcommand_class(args[0]) + subcommand_argv = [self._argv[0]] + list(args) - # this is a lightweight version of the BaseCommand.run_from_argv - # it should be compatible with Django-1.5..1.9 - subcommand_args, subcommand_options = self._handle_argv( + # this is a lightweight version of the BaseCommand.run_from_argv + # it should be compatible with Django-1.5..1.9 + subcommand_args, subcommand_options = self._handle_argv( subcommand, subcommand_argv) - try: subcommand.execute(*subcommand_args, **subcommand_options) except Exception as e: if not isinstance(e, CommandError): @@ -40,9 +40,7 @@ def execute(self, *args, **options): # self.stderr is not guaranteed to be set here stderr = getattr(self, 'stderr', OutputWrapper( sys.stderr, self.style.ERROR)) - stderr.write('Beautiful is better than %s. ' - 'Errors should never pass silently.' % - (e.__class__.__name__)) + stderr.write('Maven: got %s.' % (e.__class__.__name__)) sentry = self._get_sentry() if sentry: diff --git a/django_maven/management/commands/maven_check.py b/django_maven/management/commands/maven_check.py new file mode 100644 index 0000000..c9b26ff --- /dev/null +++ b/django_maven/management/commands/maven_check.py @@ -0,0 +1,27 @@ +from optparse import make_option + +from django.core.management.base import BaseCommand + + +class MavenCheckException(Exception): + pass + + +class Command(BaseCommand): + help = 'Run `manage.py maven maven_check` and look for MavenCheckException in the Sentry issues list.' + + option_list = BaseCommand.option_list + ( + make_option('--ok', + action='store_true', + dest='ok', + default=False, + help='just print "OK" message'), + ) + + def handle(self, *args, **options): + if options['ok']: + print ('OK') + else: + raise MavenCheckException("Errors should never pass silently.") + + diff --git a/tests/test_app/management/commands/maven_test_import_error.py b/tests/test_app/management/commands/maven_test_import_error.py new file mode 100644 index 0000000..c6ff0fe --- /dev/null +++ b/tests/test_app/management/commands/maven_test_import_error.py @@ -0,0 +1,11 @@ +"""This module is about the case of invalid import in the django management command +or absend requirement. + +The command:: + + manage.py maven test_maven_import_error + +should send traceback +""" + +import not_installed_module diff --git a/tests/test_app/management/commands/testcmd.py b/tests/test_app/management/commands/testcmd.py deleted file mode 100644 index 902e9af..0000000 --- a/tests/test_app/management/commands/testcmd.py +++ /dev/null @@ -1,24 +0,0 @@ -from optparse import make_option - -from django.core.management.base import BaseCommand - - -class IgnoramusException(Exception): - pass - - -class Command(BaseCommand): - help = 'Test command' - - option_list = BaseCommand.option_list + ( - make_option('--fail', - action='store_true', - dest='fail', - default=False, - help='Raise exception'), - ) - - def handle(self, *args, **options): - if options['fail']: - raise IgnoramusException('use jQuery') - print ('OK') diff --git a/tests/test_app/tests.py b/tests/test_app/tests.py index 0a00e94..66742c3 100644 --- a/tests/test_app/tests.py +++ b/tests/test_app/tests.py @@ -11,14 +11,14 @@ class MavenCommandTest(TestCase): def test_subcommand(self): result = get_output(['django-admin.py', - 'testcmd', '--settings=test_project.settings']) + 'maven_check', '--ok', '--settings=test_project.settings']) self.assertEqual('OK', result.output.strip()) self.assertEqual(0, result.returncode) def test_maven_subcommand(self): result = get_output(['django-admin.py', 'maven', '--settings=test_project.settings', - 'testcmd']) + 'maven_check', '--ok']) self.assertEqual('OK', result.output.strip()) self.assertEqual(0, result.returncode) @@ -26,13 +26,19 @@ def test_maven_should_capture_exceptions_from_subcommand(self): result = get_output(['django-admin.py', 'maven', '--settings=test_project.settings', '--traceback', '--verbosity=2', - 'testcmd', '--fail']) - self.assertIn('Beautiful is better than IgnoramusException. ' - 'Errors should never pass silently.', - result.output.strip()) + 'maven_check']) + self.assertIn('MavenCheckException', result.output.strip()) self.assertIn('Traceback', result.output.strip()) self.assertNotEqual(0, result.returncode) + def test_maven_should_capture_import_error(self): + result = get_output(['django-admin.py', + 'maven', '--settings=test_project.settings', + '--traceback', '--verbosity=2', + 'maven_test_import_error']) + self.assertIn('Maven: got ImportError', result.output.strip()) + self.assertNotEqual(0, result.returncode) + def get_output(*popenargs, **kwargs): r"""Run command with arguments and return its output as a string."""