diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml new file mode 100644 index 0000000..ed59389 --- /dev/null +++ b/.github/workflows/django.yml @@ -0,0 +1,40 @@ +name: Django CI + +on: + push: + branches: [ "**" ] +# pull_request: +# branches: [ "**" ] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.8"] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + sudo add-apt-repository -y ppa:deadsnakes/ppa + sudo apt-get -yq update + sudo apt-get -yq install python${{ matrix.python-version }} python${{ matrix.python-version }}-dev + python -m pip install --upgrade pip + pip install -r requirements-test.txt + + - name: Run PEP8 script + run: | + chmod +x ./pep8.sh + ./pep8.sh + + - name: Run tests with Tox + run: tox diff --git a/README.md b/README.md index 2b6c2e4..467ed7f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Chroniker - Django Controlled Cron Jobs ============================================================================= -[![](https://img.shields.io/pypi/v/django-chroniker.svg)](https://pypi.python.org/pypi/django-chroniker) [![Build Status](https://img.shields.io/travis/chrisspen/django-chroniker.svg?branch=master)](https://travis-ci.org/chrisspen/django-chroniker) [![](https://pyup.io/repos/github/chrisspen/django-chroniker/shield.svg)](https://pyup.io/repos/github/chrisspen/django-chroniker) +[![](https://img.shields.io/pypi/v/django-chroniker.svg)](https://pypi.python.org/pypi/django-chroniker) [![Build Status](https://github.com/chrisspen/django-chroniker/actions/workflows/django.yml/badge.svg)](https://github.com/chrisspen/django-chroniker) [![](https://pyup.io/repos/github/chrisspen/django-chroniker/shield.svg)](https://pyup.io/repos/github/chrisspen/django-chroniker) Overview -------- @@ -210,13 +210,13 @@ To run all [tests](http://tox.readthedocs.org/en/latest/): export TESTNAME=; tox -To run tests for a specific environment (e.g. Python 2.7 with Django 1.11): +To run tests for a specific environment (e.g. Python 3.8 with Django 3.0): - export TESTNAME=; tox -e py27-django111 + export TESTNAME=; tox -e py38-django30 To run a specific test: - export TESTNAME=.testTimezone2; tox -e py36-django21 + export TESTNAME=.testTimezone2; tox -e py38-django30 To run the [documentation server](http://www.mkdocs.org/#getting-started) locally: diff --git a/bin/chroniker b/bin/chroniker index b155ee1..d213d6f 100755 --- a/bin/chroniker +++ b/bin/chroniker @@ -15,33 +15,18 @@ logger = logging.getLogger('chroniker') if __name__ == "__main__": parser = argparse.ArgumentParser( description=('Run cron jobs for a Django ' - 'project using ' - 'django-chroniker.'), + 'project using ' + 'django-chroniker.'), epilog=("NOTE: You must provide one of " - "the following: settings or " - "project_dir.")) - parser.add_argument( - '-s', metavar='settings', type=str, nargs='?', - dest="settings", - help=('Django settings module. You must provide.')) - parser.add_argument( - '-p', metavar='project_dir', type=str, nargs='?', - dest="project_dir", help='Path to project directory') - parser.add_argument( - '-e', metavar='virtualenv', type=str, nargs='?', - dest="virtualenv", - help=('Path to virtual environment "activate_this.py" file')) - parser.add_argument( - '-q', action='store_true', dest="quite", default=False, - help="Suppress output") - parser.add_argument( - '-l', action='store_true', dest="loud", default=False, - help="Display more output") - parser.add_argument( - '--jobs', - dest="jobs", - default='', - help='A comma-delimited list of job ids to limit executions to.') + "the following: settings or " + "project_dir.") + ) + parser.add_argument('-s', metavar='settings', type=str, nargs='?', dest="settings", help=('Django settings module. You must provide.')) + parser.add_argument('-p', metavar='project_dir', type=str, nargs='?', dest="project_dir", help='Path to project directory') + parser.add_argument('-e', metavar='virtualenv', type=str, nargs='?', dest="virtualenv", help=('Path to virtual environment "activate_this.py" file')) + parser.add_argument('-q', action='store_true', dest="quite", default=False, help="Suppress output") + parser.add_argument('-l', action='store_true', dest="loud", default=False, help="Display more output") + parser.add_argument('--jobs', dest="jobs", default='', help='A comma-delimited list of job ids to limit executions to.') args = parser.parse_args() log_level = logging.INFO @@ -58,7 +43,8 @@ if __name__ == "__main__": if args.virtualenv: virtualenv = os.path.abspath(args.virtualenv) assert os.path.isfile(virtualenv), 'Virtualenv file "%s" does not exist.' % virtualenv - exec(open(virtualenv).read(), dict(__file__=virtualenv)) # pylint: disable=exec-used + with open(virtualenv, encoding='utf8') as fin: + exec(fin.read(), dict(__file__=virtualenv)) # pylint: disable=exec-used # Now setup django project_dir = args.project_dir @@ -85,7 +71,7 @@ if __name__ == "__main__": if settings: os.environ['DJANGO_SETTINGS_MODULE'] = settings - jobs = args.jobs#(args.jobs or '').split(',') + jobs = args.jobs #(args.jobs or '').split(',') logger.debug("Project dir: %s", project_dir) logger.debug("Settings mod: %s", settings) diff --git a/chroniker/admin.py b/chroniker/admin.py index b7269b7..eef084b 100644 --- a/chroniker/admin.py +++ b/chroniker/admin.py @@ -1,16 +1,13 @@ from django import forms from django.conf import settings +from django.urls import re_path as url from django.contrib import admin from django.core.management import get_commands +from django.urls import reverse, NoReverseMatch from django.db import models from django.forms import TextInput from django.shortcuts import render -from django.urls import reverse, NoReverseMatch -try: - from django.urls import re_path -except ImportError: - from django.conf.urls import url as re_path -from django.utils.encoding import force_str +from django.utils.encoding import force_str as force_text from django.http import HttpResponseRedirect, Http404, HttpResponse from django.utils import dateformat, timezone from django.utils.datastructures import MultiValueDict @@ -367,7 +364,7 @@ def view_duration_graph(self, request, object_id): media = self.media context = { - 'title': _('Change %s') % force_str(opts.verbose_name), + 'title': _('Change %s') % force_text(opts.verbose_name), 'object_id': object_id, 'original': obj, 'is_popup': False, @@ -384,9 +381,9 @@ def view_duration_graph(self, request, object_id): def get_urls(self): urls = super().get_urls() my_urls = [ - re_path(r'^(.+)/run/$', self.admin_site.admin_view(self.run_job_view), name="chroniker_job_run"), - re_path(r'^(.+)/stop/$', self.admin_site.admin_view(self.stop_job_view), name="chroniker_job_stop"), - re_path(r'^(.+)/graph/duration/$', self.admin_site.admin_view(self.view_duration_graph), name='chroniker_job_duration_graph'), + url(r'^(.+)/run/$', self.admin_site.admin_view(self.run_job_view), name="chroniker_job_run"), + url(r'^(.+)/stop/$', self.admin_site.admin_view(self.stop_job_view), name="chroniker_job_stop"), + url(r'^(.+)/graph/duration/$', self.admin_site.admin_view(self.view_duration_graph), name='chroniker_job_duration_graph'), ] return my_urls + urls @@ -559,8 +556,8 @@ def view_full_stderr(self, request, log_id): def get_urls(self): urls = super().get_urls() my_urls = [ - re_path(r'^(?P[0-9]+)/stdout/?$', self.admin_site.admin_view(self.view_full_stdout), name='chroniker_log_stdout'), - re_path(r'^(?P[0-9]+)/stderr/?$', self.admin_site.admin_view(self.view_full_stderr), name='chroniker_log_stderr'), + url(r'^(?P[0-9]+)/stdout/?$', self.admin_site.admin_view(self.view_full_stdout), name='chroniker_log_stdout'), + url(r'^(?P[0-9]+)/stderr/?$', self.admin_site.admin_view(self.view_full_stderr), name='chroniker_log_stderr'), ] return my_urls + urls @@ -690,7 +687,7 @@ def run_job_view(self, request, pk): def get_urls(self): urls = super().get_urls() my_urls = [ - re_path(r'^(.+)/run/$', self.admin_site.admin_view(self.run_job_view), name="chroniker_job_run"), + url(r'^(.+)/run/$', self.admin_site.admin_view(self.run_job_view), name="chroniker_job_run"), ] return my_urls + urls diff --git a/chroniker/constants.py b/chroniker/constants.py index 21b93ab..92cae95 100644 --- a/chroniker/constants.py +++ b/chroniker/constants.py @@ -1,8 +1,4 @@ -try: - from django.utils.translation import gettext_lazy as _ -except ImportError: - from django.utils.translation import ugettext_lazy as _ - +from django.utils.translation import gettext_lazy as _ YEARLY = 'YEARLY' MONTHLY = 'MONTHLY' diff --git a/chroniker/management/commands/cron.py b/chroniker/management/commands/cron.py index 7441bfd..597866c 100644 --- a/chroniker/management/commands/cron.py +++ b/chroniker/management/commands/cron.py @@ -6,7 +6,6 @@ from collections import defaultdict from functools import partial from multiprocessing import Queue -from optparse import make_option import psutil @@ -129,7 +128,7 @@ def run_cron(jobs=None, **kwargs): pass elif os.path.isfile(pid_fn): try: - old_pid = int(open(pid_fn, 'r').read()) + old_pid = int(open(pid_fn, 'r', encoding='utf8').read()) if utils.pid_exists(old_pid): print('%s already exists, exiting' % pid_fn) sys.exit() @@ -139,7 +138,7 @@ def run_cron(jobs=None, **kwargs): pass except TypeError: pass - open(pid_fn, 'w').write(pid) + open(pid_fn, 'w', encoding='utf8').write(pid) clear_pid = True procs = [] @@ -167,13 +166,13 @@ def run_cron(jobs=None, **kwargs): Job.objects.update() job = Job.objects.get(id=job.id) if not force_run and not job.is_due_with_dependencies_met(running_ids=running_ids): - utils.smart_print(u'Job {} {} is due but has unmet dependencies.'\ + utils.smart_print('Job {} {} is due but has unmet dependencies.'\ .format(job.id, job)) continue # Immediately mark the job as running so the next jobs can # update their dependency check. - utils.smart_print(u'Running job {} {}.'.format(job.id, job)) + utils.smart_print('Running job {} {}.'.format(job.id, job)) running_ids.add(job.id) if dryrun: continue @@ -270,38 +269,6 @@ def run_cron(jobs=None, **kwargs): class Command(BaseCommand): help = 'Runs all jobs that are due.' - option_list = getattr(BaseCommand, 'option_list', ()) + ( - make_option('--update_heartbeat', - dest='update_heartbeat', - default=1, - help='If given, launches a thread to asynchronously update ' + \ - 'job heartbeat status.'), - make_option('--force_run', - dest='force_run', - action='store_true', - default=False, - help='If given, forces all jobs to run.'), - make_option('--dryrun', - action='store_true', - default=False, - help='If given, only displays jobs to be run.'), - make_option('--jobs', - dest='jobs', - default='', - help='A comma-delimited list of job ids to limit executions to.'), - make_option('--name', - dest='name', - default='', - help='A name to give this process.'), - make_option('--sync', - action='store_true', - default=False, - help='If given, runs jobs one at a time.'), - make_option('--verbose', - action='store_true', - default=False, - help='If given, shows debugging info.'), - ) def add_arguments(self, parser): parser.add_argument('--update_heartbeat', diff --git a/chroniker/management/commands/cronserver.py b/chroniker/management/commands/cronserver.py index 69e0fb9..b999b5d 100644 --- a/chroniker/management/commands/cronserver.py +++ b/chroniker/management/commands/cronserver.py @@ -6,10 +6,7 @@ from django.core.management.base import BaseCommand from django.core.management import call_command -try: - from django.utils.translation import gettext_lazy as _ -except ImportError: - from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ logger = logging.getLogger('chroniker.commands.cronserver') @@ -24,7 +21,7 @@ def run(self): class Command(BaseCommand): args = "time" - help = _("Emulates a reoccurring cron call to run jobs at a specified " "interval. This is meant primarily for development use.") + help = _("Emulates a reoccurring cron call to run jobs at a specified interval. This is meant primarily for development use.") def handle(self, *args, **options): diff --git a/chroniker/migrations/0004_auto_20240328_2035.py b/chroniker/migrations/0004_auto_20240328_2035.py new file mode 100644 index 0000000..582f6f7 --- /dev/null +++ b/chroniker/migrations/0004_auto_20240328_2035.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.23 on 2024-03-28 20:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('chroniker', '0003_auto_20200822_2026'), + ] + + operations = [ + migrations.AlterField( + model_name='job', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='jobdependency', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='log', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/chroniker/models.py b/chroniker/models.py index 3c38c2e..c1ccbb2 100644 --- a/chroniker/models.py +++ b/chroniker/models.py @@ -37,12 +37,7 @@ from django.utils.encoding import smart_str from django.utils.safestring import mark_safe from django.utils.timesince import timeuntil -try: - from django.utils.translation import ngettext, gettext, gettext_lazy as _ -except ImportError: - from django.utils.translation import ( - ungettext as ngettext, ugettext as gettext, ugettext_lazy as _ - ) +from django.utils.translation import ngettext as ungettext, gettext as ugettext, gettext_lazy as _ from django.core.exceptions import ValidationError from django.utils.html import format_html from toposort import toposort_flatten @@ -459,7 +454,7 @@ class Job(models.Model): to the frequency options.''') ) - next_run = models.DateTimeField(_("next run"), blank=True, null=True, help_text=_("If you don't set this it will" " be determined automatically")) + next_run = models.DateTimeField(_("next run"), blank=True, null=True, help_text=_("If you don't set this it will be determined automatically")) last_run_start_timestamp = models.DateTimeField(_("last run start timestamp"), editable=False, blank=True, null=True) @@ -552,9 +547,9 @@ class Meta: def __unicode__(self): if self.enabled: - ret = u"{} - {} - {}".format(self.id, self.name, self.timeuntil) + ret = "{} - {} - {}".format(self.id, self.name, self.timeuntil) else: - ret = u"{id} - {name} - disabled".format(**{'name': self.name, 'id': self.id}) + ret = "{id} - {name} - disabled".format(**{'name': self.name, 'id': self.id}) if not isinstance(ret, str): ret = str(ret) return ret @@ -753,7 +748,7 @@ def save(self, **kwargs): log_q = self.logs.all().order_by('-run_start_datetime')[cutoff:] if log_q.exists(): cutoff_dt = log_q[0].run_start_datetime - qs = Log.objects.filter(run_start_datetime__lte=cutoff_dt) + qs = Log.objects.filter(job=self.id, run_start_datetime__lte=cutoff_dt) for o in qs: o.delete() @@ -799,8 +794,8 @@ def get_timeuntil(self): return _('due') if delta.seconds < 60: # Adapted from django.utils.timesince - count = lambda n: ngettext('second', 'seconds', n) - return gettext('%(number)d %(type)s') % {'number': delta.seconds, 'type': count(delta.seconds)} + count = lambda n: ungettext('second', 'seconds', n) + return ugettext('%(number)d %(type)s') % {'number': delta.seconds, 'type': count(delta.seconds)} return timeuntil(self.next_run) get_timeuntil.short_description = _('time until next run') @@ -835,8 +830,7 @@ def param_to_int(self, param_value): val = int(param_value) except ValueError as exc: raise ValueError('rrule parameter should be integer or weekday ' 'constant (e.g. MO, TU, etc.). ' 'Error on: %s' % param_value) from exc - else: - return val + return val def get_params(self): """ @@ -985,15 +979,15 @@ def handle_run(self, update_heartbeat=True, stdout_queue=None, stderr_queue=None original_pid = os.getpid() - try: - # Redirect output so that we can log and easily check for errors. - stdout = utils.TeeFile(sys.stdout, auto_flush=True, queue=stdout_queue, local=self.log_stdout) - stderr = utils.TeeFile(sys.stderr, auto_flush=True, queue=stderr_queue, local=self.log_stderr) - ostdout = sys.stdout - ostderr = sys.stderr - sys.stdout = stdout - sys.stderr = stderr + # Redirect output so that we can log and easily check for errors. + stdout = utils.TeeFile(sys.stdout, auto_flush=True, queue=stdout_queue, local=self.log_stdout) + stderr = utils.TeeFile(sys.stderr, auto_flush=True, queue=stderr_queue, local=self.log_stderr) + ostdout = sys.stdout + ostderr = sys.stderr + sys.stdout = stdout + sys.stderr = stderr + try: args, options = self.get_args() heartbeat = None @@ -1019,14 +1013,17 @@ def handle_run(self, update_heartbeat=True, stdout_queue=None, stderr_queue=None if heartbeat: heartbeat.start() + + _raw_status = 0 try: logger.debug("Calling command '%s'", self.command) if self.raw_command and not getattr(settings, 'CHRONIKER_DISABLE_RAW_COMMAND', False): completed_process = subprocess.run( - shlex.split(self.raw_command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, universal_newlines=True + shlex.split(self.raw_command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, universal_newlines=True ) _stdout_str = completed_process.stdout _stderr_str = completed_process.stderr + _raw_status = completed_process.returncode if self.log_stdout: stdout_str = _stdout_str @@ -1067,7 +1064,7 @@ def handle_run(self, update_heartbeat=True, stdout_queue=None, stderr_queue=None assert next_run != _next_run, 'RRule failed to increment next run datetime.' # next_run = next_run.replace(tzinfo=timezone.get_current_timezone()) - last_run_successful = not bool(stderr.length) + last_run_successful = not _raw_status and not bool(stderr.length) try: with lock: diff --git a/chroniker/tests/settings.py b/chroniker/tests/settings.py index ce17708..93bc0de 100644 --- a/chroniker/tests/settings.py +++ b/chroniker/tests/settings.py @@ -5,6 +5,7 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', # Don't do this. It dramatically slows down the test. # 'NAME': '/tmp/chroniker.db', # 'TEST_NAME': '/tmp/chroniker.db', diff --git a/chroniker/tests/tests.py b/chroniker/tests/tests.py index aba3a14..3391347 100644 --- a/chroniker/tests/tests.py +++ b/chroniker/tests/tests.py @@ -709,3 +709,23 @@ def testJobFailure(self): def test_widgets(self): print('django.version:', django.VERSION) from chroniker import widgets # pylint: disable=unused-import,import-outside-toplevel + + def test_non_zero_return_status_command(self): + """ + Confirm that jobs running a raw command that return a non-zero exit code don't break the runner + and simply save the exit code. + """ + job = Job.objects.create(name="Test Job with Non-Zero Status", raw_command="false") + + call_command('run_job', str(job.id), update_heartbeat=0) + + job.refresh_from_db() + self.assertEqual(job.last_run_successful, False) + + job.raw_command = "true" + job.save() + + call_command('run_job', str(job.id), update_heartbeat=0) + + job.refresh_from_db() + self.assertEqual(job.last_run_successful, True) diff --git a/chroniker/utils.py b/chroniker/utils.py index c16a496..449cd72 100644 --- a/chroniker/utils.py +++ b/chroniker/utils.py @@ -205,8 +205,7 @@ def pid_exists(pid): os.kill(pid, 0) except OSError as e: return e.errno == errno.EPERM - else: - return True + return True def get_cpu_usage(pid, interval=1): @@ -277,10 +276,7 @@ class TimedProcess(Process): def __init__(self, max_seconds, time_type=c.MAX_TIME, fout=None, check_freq=1, *args, **kwargs): super().__init__(*args, **kwargs) self.fout = fout or sys.stdout - try: - self.t0 = time.process_time() - except AttributeError: - self.t0 = time.clock() + self.t0 = time.process_time() self.t0_objective = time.time() self.max_seconds = float(max_seconds) self.t1 = None @@ -336,10 +332,7 @@ def get_duration_seconds_cpu(self): if self.t1 is not None: return self.t1 - self.t0 - try: - now = time.process_time() - except AttributeError: - now = time.clock() + now = time.process_time() return now - self.t0 @@ -452,10 +445,7 @@ def start_then_kill(self, verbose=True): print('\nAttempting to terminate expired process %s...' % (self.pid,), file=self.fout) timeout = True self.terminate() - try: - self.t0 = time.process_time() - except AttributeError: - self.t0 = time.clock() + self.t0 = time.process_time() self.t1_objective = time.time() return timeout diff --git a/pylint.rc b/pylint.rc index 6d25eb4..d20cc03 100644 --- a/pylint.rc +++ b/pylint.rc @@ -8,16 +8,6 @@ # [MASTER] -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Profiled execution. -profile=no - # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. # Ignore all auto-generated South migration directories. @@ -26,84 +16,13 @@ ignore=migrations,south_migrations # Pickle collected data for later comparisons. persistent=yes -# Set the cache size for astng objects. -cache-size=500 - # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [MESSAGES CONTROL] -# Enable only checker(s) with the given id(s). This option conflicts with the -# disable-checker option -#enable-checker= - -# Enable all checker(s) except those with the given id(s). This option -# conflicts with the enable-checker option -#disable-checker= - -# Enable all messages in the listed categories (IRCWEF). -#enable-msg-cat= - -# Disable all messages in the listed categories (IRCWEF). -disable-msg-cat=I - -# Enable the message(s) with the given id(s). -#enable-msg= - -#http://docs.pylint.org/features.html -#http://pylint-messages.wikidot.com/all-codes -#pylint --list-msgs > pylint.messages - -# All these are disabled below. -# C1001: old-style class defined (Django uses these for Meta options) -# C0103: variable regex check. -# C0111: missing docstring check. It's too vague. Complains about no docstrings in __init__ and other places we don't care about. -# C0330: bad-continuation -# E1101: member check...this is usually wrong. -# E1103: type inference...this is usually wrong. -# F0401: unable to import -# R0201: method should be function check. -# R0401: cyclic import check...because sometimes it's wrong. -# R0902: too many instance attributes check. -# R0903: too few public methods check...makes no sense with Django. -# R0904: too many public method check. -# R0913: too many argument check. -# R0921: abstract class not referenced check. -# W0104: no effect check. -# W0142: magic check. -# W0212: protected data check. -# W0232: __init__ check. -# W0311: bad-indentation -# W0401: wildcard import. -# W0404: reimport check...this is sometimes wrong. -# W0511: TODO check. -# W0612: unused variable check. -# W0613: unused argument check. Too vague. -# W0614: wildcard import usage check. -# W0704: empty except check. -# E1002: Use of super on an old style class -# E1120: No value for argument -# R0901: Too many ancestors -# E1123: Unexpected keyword argument %r in %s call -# C0302: *Too many lines in module (%s)* -# R0801: *Similar lines in %s files* -# R0914: *Too many local variables (%s/%s)* -# R0912: *Too many branches (%s/%s)* -# R0915: *Too many statements (%s/%s)* -# W0703: *Catching too general exception %s* -# E1003: *Bad first argument %r given to super()* -# E0202: *An attribute defined in %s line %s hides this method* -# W0201: *Attribute %r defined outside __init__* -# W0221: *Arguments number differs from %s method* -# C0325: *Unnecessary parens after %r keyword* -# R0916: too-many-boolean-expressions -# R0204: *Redefinition of %s type from %s to %s* -# R0101: *Too many nested blocks (%s/%s)* -# I0011: *Locally disabling %s (%s)* -# W1001: *Use of "property" on an old style class* -disable=C1001,C0103,R0201,W0212,W0614,W0401,W0704,E1101,W0142,R0904,R0913,W0404,R0903,W0232,C0111,W0613,W0612,W0511,W0104,R0902,R0921,R0401,E1103,W0311,C0330,F0401,E1002,E1120,R0901,E1123,C0302,R0801,R0914,R0912,R0915,W0703,E1003,E0202,W0201,W0221,C0325,R0916,R0204,R0101,I0011,W1001,consider-using-ternary,unsubscriptable-object,inconsistent-return-statements,keyword-arg-before-vararg,wrong-import-order +disable=C0103,W0212,W0614,W0401,E1101,R0904,R0913,W0404,R0903,C0111,W0613,W0612,W0511,W0104,R0902,R0401,E1103,W0311,F0401,E1120,R0901,E1123,C0302,R0801,R0914,R0912,R0915,W0703,E1003,E0202,W0201,W0221,C0325,R0916,R0101,I0011,consider-using-ternary,unsubscriptable-object,inconsistent-return-statements,keyword-arg-before-vararg,wrong-import-order,use-dict-literal,consider-using-f-string,consider-using-with,unsupported-binary-operation,broad-exception-raised,unnecessary-lambda-assignment,no-name-in-module [REPORTS] @@ -111,14 +30,6 @@ disable=C1001,C0103,R0201,W0212,W0614,W0401,W0704,E1101,W0142,R0904,R0913,W0404, # (visual studio) and html output-format=text -# Include message's id in output -include-ids=yes - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - # Tells whether to display a full report or only the messages reports=no @@ -129,16 +40,6 @@ reports=no # (R0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) -# Add a comment according to your evaluation note. This is used by the global -# evaluation report (R0004). -comment=no - -# Enable the report(s) with the given id(s). -#enable-report= - -# Disable the report(s) with the given id(s). -#disable-report= - # checks for : # * doc strings @@ -190,9 +91,6 @@ good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,apply,input - # try to find bugs in the code using type inference # @@ -206,10 +104,6 @@ ignore-mixin-members=yes # (useful for classes with attributes dynamically set). ignored-classes=SQLObject -# When zope mode is activated, add a predefined set of Zope acquired attributes -# to generated-members. -zope=no - # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. generated-members=REQUEST,acl_users,aq_parent @@ -273,9 +167,6 @@ max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 -# Maximum number of branch for function / method body -max-branchs=12 - # Maximum number of statements in function / method body max-statements=50 diff --git a/requirements-min-django.txt b/requirements-min-django.txt index f227a9a..5762ddd 100644 --- a/requirements-min-django.txt +++ b/requirements-min-django.txt @@ -1 +1 @@ -Django>=2.0 +Django>=3.0 diff --git a/setup.py b/setup.py index 9e5dd62..88ac95c 100644 --- a/setup.py +++ b/setup.py @@ -6,18 +6,14 @@ CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) -try: - with open(os.path.join(CURRENT_DIR, 'README.md'), encoding='utf-8') as f: - long_description = f.read() -except TypeError: - with open(os.path.join(CURRENT_DIR, 'README.md')) as f: - long_description = f.read() +with open(os.path.join(CURRENT_DIR, 'README.md'), encoding='utf-8') as f: + long_description = f.read() def get_reqs(*fns): lst = [] for fn in fns: - for package in open(os.path.join(CURRENT_DIR, fn)).readlines(): + for package in open(os.path.join(CURRENT_DIR, fn), encoding='utf-8').readlines(): package = package.strip() if not package: continue diff --git a/tox.ini b/tox.ini index 23b0463..3a1e099 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,12 @@ [tox] -envlist = py{36,37}-django{21},py{36,37}-django{30} +envlist = py{38}-django{30} recreate = True [testenv] basepython = - py36: python3.6 - py37: python3.7 + py38: python3.8 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-test.txt - django21: Django>=2.1,<2.2 django30: Django>=3.0,<3.1 commands = django-admin.py test --traceback --settings=chroniker.tests.settings chroniker.tests.tests.JobTestCase{env:TESTNAME:}