diff --git a/.travis.yml b/.travis.yml index b36badb..d43dd96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - 3.5 - - 3.8 + - '3.8' install: - make travis.requirements script: @@ -9,5 +8,5 @@ script: after_script: - coveralls branches: - only: - - master + only: + - master diff --git a/grader_support/gradelib.py b/grader_support/gradelib.py index 6f16721..be9bd0d 100644 --- a/grader_support/gradelib.py +++ b/grader_support/gradelib.py @@ -18,7 +18,7 @@ def __init__(self, message=None): Exception.__init__(self, message) -class Test(object): +class Test: """ A simple class to wrap a test function and its descriptions. @@ -53,7 +53,7 @@ def compare_results(self, expected, actual): return expected == actual -class Grader(object): +class Grader: def __init__(self): """ Create an empty grader. @@ -239,7 +239,7 @@ def _tokens(code): """ # Protect against pathological inputs: http://bugs.python.org/issue16152 code = code.rstrip() + "\n" - if isinstance(code, six.text_type): + if isinstance(code, str): code = code.encode('utf8') code = "# coding: utf8\n" + code toks = tokenize.generate_tokens(six.BytesIO(code).readline) @@ -481,7 +481,7 @@ def exec_wrapped_code(environment=None, post_process=None): environment = {} def test_fn(submission_module): with capture_stdout() as stdout: - six.exec_(submission_module.submission_code, environment) + exec(submission_module.submission_code, environment) stdout_text = stdout.getvalue() if post_process: stdout_text = post_process(stdout_text) @@ -503,7 +503,7 @@ def exec_code_and_inspect_values(environment=None, vars_to_inspect=None, post_pr environment = {} def test_fn(submission_module): with capture_stdout() as stdout: - six.exec_(submission_module.submission_code, environment) + exec(submission_module.submission_code, environment) for var in vars_to_inspect: print(var) @@ -530,7 +530,7 @@ def invoke_student_function(fn_name, args, environment=None, output_writer=None) """ output_writer = output_writer or repr def doit(submission_module): - for name, value in six.iteritems(environment or {}): + for name, value in (environment or {}).items(): setattr(submission_module, name, value) fn = getattr(submission_module, fn_name) print(output_writer(fn(*args))) diff --git a/grader_support/graderutil.py b/grader_support/graderutil.py index ec8fcd2..aa560c4 100644 --- a/grader_support/graderutil.py +++ b/grader_support/graderutil.py @@ -37,7 +37,7 @@ def captured_stdout(): sys.stdout = old_stdout -class ChangeDirectory(object): +class ChangeDirectory: def __init__(self, new_dir): self.old_dir = os.getcwd() os.chdir(new_dir) @@ -58,7 +58,7 @@ def change_directory(new_dir): cd.clean_up() -class TempDirectory(object): +class TempDirectory: def __init__(self, delete_when_done=True): self.delete_when_done = delete_when_done self.temp_dir = tempfile.mkdtemp(prefix="grader-") @@ -84,7 +84,7 @@ def temp_directory(delete_when_done=True): tmp.clean_up() -class ModuleIsolation(object): +class ModuleIsolation: """ Manage changes to sys.modules so that we can roll back imported modules. diff --git a/grader_support/run.py b/grader_support/run.py old mode 100755 new mode 100644 index 9134ac6..5323598 --- a/grader_support/run.py +++ b/grader_support/run.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ Run a set of tests on a submission, printing the outputs to stdout as a json string. @@ -31,10 +30,7 @@ fallback=True, languages=[graderutil.LANGUAGE] ) -if six.PY2: - trans.install(names=None, unicode=True) -else: - trans.install(names=None) +trans.install(names=None) def run(grader_name, submission_name, seed=1): diff --git a/load_test/mock_xqueue.py b/load_test/mock_xqueue.py index 49b05d4..c7e40ef 100644 --- a/load_test/mock_xqueue.py +++ b/load_test/mock_xqueue.py @@ -69,7 +69,7 @@ def get_submission(): response = { 'return_code': 0, 'content': flask.json.dumps({ - 'xqueue_header': '{}.{}'.format(counter.next(), idx), + 'xqueue_header': f'{counter.next()}.{idx}', 'xqueue_body': flask.json.dumps({ 'student_response': submission, 'grader_payload': flask.json.dumps(payload) diff --git a/requirements/base.txt b/requirements/base.txt index 3185ab4..2df95b2 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,20 +5,14 @@ # make upgrade # -e git+https://github.com/edx/codejail.git@4127fc4bd5775cc72aee8d7f0a70e31405e22439#egg=codejail # via -r requirements/base.in -backports.os==0.1.1 # via path.py -certifi==2020.6.20 # via requests -chardet==3.0.4 # via requests -configparser==4.0.2 # via importlib-metadata -contextlib2==0.6.0.post1 # via importlib-metadata, zipp +certifi==2020.12.5 # via requests +chardet==4.0.0 # via requests dogstatsd-python==0.5.6 # via -r requirements/base.in -future==0.18.2 # via backports.os idna==2.10 # via requests -importlib-metadata==1.7.0 # via path.py -newrelic==5.14.1.144 # via -r requirements/base.in +importlib-metadata==3.4.0 # via path.py +newrelic==6.0.1.155 # via -r requirements/base.in path.py==11.5.2 # via -c requirements/constraints.txt, -r requirements/base.in -pathlib2==2.3.5 # via importlib-metadata -requests==2.24.0 # via -r requirements/base.in -scandir==1.10.0 # via pathlib2 -six==1.15.0 # via -r requirements/base.in, pathlib2 -urllib3==1.25.9 # via requests -zipp==1.2.0 # via importlib-metadata +requests==2.25.1 # via -r requirements/base.in +six==1.15.0 # via -r requirements/base.in +urllib3==1.26.3 # via requests +zipp==3.4.0 # via importlib-metadata diff --git a/requirements/constraints.txt b/requirements/constraints.txt index bdbe1f2..e57f13d 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -8,5 +8,8 @@ # pin when possible. Writing an issue against the offending project and # linking to it here is good. +# Common constraints for edx repos +-c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # After this version drops support for python 2.7 path.py<=11.5.2 diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index 279019f..a6f6c30 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -5,8 +5,7 @@ # make upgrade # click==7.1.2 # via pip-tools -pip-tools==5.2.1 # via -r requirements/pip_tools.in -six==1.15.0 # via pip-tools +pip-tools==5.5.0 # via -r requirements/pip_tools.in # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/requirements/production.txt b/requirements/production.txt index eee07bc..eeb11eb 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -5,20 +5,14 @@ # make upgrade # -e git+https://github.com/edx/codejail.git@4127fc4bd5775cc72aee8d7f0a70e31405e22439#egg=codejail # via -r requirements/base.txt -backports.os==0.1.1 # via -r requirements/base.txt, path.py -certifi==2020.6.20 # via -r requirements/base.txt, requests -chardet==3.0.4 # via -r requirements/base.txt, requests -configparser==4.0.2 # via -r requirements/base.txt, importlib-metadata -contextlib2==0.6.0.post1 # via -r requirements/base.txt, importlib-metadata, zipp +certifi==2020.12.5 # via -r requirements/base.txt, requests +chardet==4.0.0 # via -r requirements/base.txt, requests dogstatsd-python==0.5.6 # via -r requirements/base.txt -future==0.18.2 # via -r requirements/base.txt, backports.os idna==2.10 # via -r requirements/base.txt, requests -importlib-metadata==1.7.0 # via -r requirements/base.txt, path.py -newrelic==5.14.1.144 # via -r requirements/base.txt +importlib-metadata==3.4.0 # via -r requirements/base.txt, path.py +newrelic==6.0.1.155 # via -r requirements/base.txt path.py==11.5.2 # via -c requirements/constraints.txt, -r requirements/base.txt -pathlib2==2.3.5 # via -r requirements/base.txt, importlib-metadata -requests==2.24.0 # via -r requirements/base.txt -scandir==1.10.0 # via -r requirements/base.txt, pathlib2 -six==1.15.0 # via -r requirements/base.txt, pathlib2 -urllib3==1.25.9 # via -r requirements/base.txt, requests -zipp==1.2.0 # via -r requirements/base.txt, importlib-metadata +requests==2.25.1 # via -r requirements/base.txt +six==1.15.0 # via -r requirements/base.txt +urllib3==1.26.3 # via -r requirements/base.txt, requests +zipp==3.4.0 # via -r requirements/base.txt, importlib-metadata diff --git a/requirements/test.txt b/requirements/test.txt index 4492a51..1975190 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -5,34 +5,25 @@ # make upgrade # -e git+https://github.com/edx/codejail.git@4127fc4bd5775cc72aee8d7f0a70e31405e22439#egg=codejail # via -r requirements/production.txt -atomicwrites==1.4.0 # via pytest -attrs==19.3.0 # via pytest -backports.functools-lru-cache==1.6.1 # via wcwidth -backports.os==0.1.1 # via -r requirements/production.txt, path.py -certifi==2020.6.20 # via -r requirements/production.txt, requests -chardet==3.0.4 # via -r requirements/production.txt, requests -configparser==4.0.2 # via -r requirements/production.txt, importlib-metadata -contextlib2==0.6.0.post1 # via -r requirements/production.txt, importlib-metadata, zipp -coverage==5.1 # via pytest-cov +attrs==20.3.0 # via pytest +certifi==2020.12.5 # via -r requirements/production.txt, requests +chardet==4.0.0 # via -r requirements/production.txt, requests +coverage==5.4 # via pytest-cov dogstatsd-python==0.5.6 # via -r requirements/production.txt -funcsigs==1.0.2 # via mock, pytest -future==0.18.2 # via -r requirements/production.txt, backports.os idna==2.10 # via -r requirements/production.txt, requests -importlib-metadata==1.7.0 # via -r requirements/production.txt, path.py, pluggy, pytest -mock==3.0.5 # via -r requirements/test.in -more-itertools==5.0.0 # via pytest -newrelic==5.14.1.144 # via -r requirements/production.txt -packaging==20.4 # via pytest +importlib-metadata==3.4.0 # via -r requirements/production.txt, path.py +iniconfig==1.1.1 # via pytest +mock==4.0.3 # via -r requirements/test.in +newrelic==6.0.1.155 # via -r requirements/production.txt +packaging==20.8 # via pytest path.py==11.5.2 # via -c requirements/constraints.txt, -r requirements/production.txt -pathlib2==2.3.5 # via -r requirements/production.txt, importlib-metadata, pytest pluggy==0.13.1 # via pytest -py==1.9.0 # via pytest +py==1.10.0 # via pytest pyparsing==2.4.7 # via packaging -pytest-cov==2.10.0 # via -r requirements/test.in -pytest==4.6.11 # via pytest-cov -requests==2.24.0 # via -r requirements/production.txt -scandir==1.10.0 # via -r requirements/production.txt, pathlib2 -six==1.15.0 # via -r requirements/production.txt, mock, more-itertools, packaging, pathlib2, pytest -urllib3==1.25.9 # via -r requirements/production.txt, requests -wcwidth==0.2.5 # via pytest -zipp==1.2.0 # via -r requirements/production.txt, importlib-metadata +pytest-cov==2.11.1 # via -r requirements/test.in +pytest==6.2.2 # via pytest-cov +requests==2.25.1 # via -r requirements/production.txt +six==1.15.0 # via -r requirements/production.txt +toml==0.10.2 # via pytest +urllib3==1.26.3 # via -r requirements/production.txt, requests +zipp==3.4.0 # via -r requirements/production.txt, importlib-metadata diff --git a/requirements/travis.txt b/requirements/travis.txt index 0a6c7e5..aae2c4d 100644 --- a/requirements/travis.txt +++ b/requirements/travis.txt @@ -5,34 +5,25 @@ # make upgrade # -e git+https://github.com/edx/codejail.git@4127fc4bd5775cc72aee8d7f0a70e31405e22439#egg=codejail # via -r requirements/test.txt -atomicwrites==1.4.0 # via -r requirements/test.txt, pytest -attrs==19.3.0 # via -r requirements/test.txt, pytest -backports.functools-lru-cache==1.6.1 # via -r requirements/test.txt, wcwidth -backports.os==0.1.1 # via -r requirements/test.txt, path.py -certifi==2020.6.20 # via -r requirements/test.txt, requests -chardet==3.0.4 # via -r requirements/test.txt, requests -configparser==4.0.2 # via -r requirements/test.txt, importlib-metadata -contextlib2==0.6.0.post1 # via -r requirements/test.txt, importlib-metadata, zipp -coverage==5.1 # via -r requirements/test.txt, -r requirements/travis.in, pytest-cov +attrs==20.3.0 # via -r requirements/test.txt, pytest +certifi==2020.12.5 # via -r requirements/test.txt, requests +chardet==4.0.0 # via -r requirements/test.txt, requests +coverage==5.4 # via -r requirements/test.txt, -r requirements/travis.in, pytest-cov dogstatsd-python==0.5.6 # via -r requirements/test.txt -funcsigs==1.0.2 # via -r requirements/test.txt, mock, pytest -future==0.18.2 # via -r requirements/test.txt, backports.os idna==2.10 # via -r requirements/test.txt, requests -importlib-metadata==1.7.0 # via -r requirements/test.txt, path.py, pluggy, pytest -mock==3.0.5 # via -r requirements/test.txt -more-itertools==5.0.0 # via -r requirements/test.txt, pytest -newrelic==5.14.1.144 # via -r requirements/test.txt -packaging==20.4 # via -r requirements/test.txt, pytest +importlib-metadata==3.4.0 # via -r requirements/test.txt, path.py +iniconfig==1.1.1 # via -r requirements/test.txt, pytest +mock==4.0.3 # via -r requirements/test.txt +newrelic==6.0.1.155 # via -r requirements/test.txt +packaging==20.8 # via -r requirements/test.txt, pytest path.py==11.5.2 # via -c requirements/constraints.txt, -r requirements/test.txt -pathlib2==2.3.5 # via -r requirements/test.txt, importlib-metadata, pytest pluggy==0.13.1 # via -r requirements/test.txt, pytest -py==1.9.0 # via -r requirements/test.txt, pytest +py==1.10.0 # via -r requirements/test.txt, pytest pyparsing==2.4.7 # via -r requirements/test.txt, packaging -pytest-cov==2.10.0 # via -r requirements/test.txt -pytest==4.6.11 # via -r requirements/test.txt, pytest-cov -requests==2.24.0 # via -r requirements/test.txt -scandir==1.10.0 # via -r requirements/test.txt, pathlib2 -six==1.15.0 # via -r requirements/test.txt, mock, more-itertools, packaging, pathlib2, pytest -urllib3==1.25.9 # via -r requirements/test.txt, requests -wcwidth==0.2.5 # via -r requirements/test.txt, pytest -zipp==1.2.0 # via -r requirements/test.txt, importlib-metadata +pytest-cov==2.11.1 # via -r requirements/test.txt +pytest==6.2.2 # via -r requirements/test.txt, pytest-cov +requests==2.25.1 # via -r requirements/test.txt +six==1.15.0 # via -r requirements/test.txt +toml==0.10.2 # via -r requirements/test.txt, pytest +urllib3==1.26.3 # via -r requirements/test.txt, requests +zipp==3.4.0 # via -r requirements/test.txt, importlib-metadata diff --git a/setup.py b/setup.py index a4c14f6..b1ff925 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='xqueue_watcher', - version='0.2', + version='1.0', description='XQueue Pull Grader', packages=[ 'xqueue_watcher', diff --git a/tests/test_grader.py b/tests/test_grader.py index 2eae22a..bccd5b3 100644 --- a/tests/test_grader.py +++ b/tests/test_grader.py @@ -1,5 +1,5 @@ import unittest -import mock +from unittest import mock import json import sys from path import Path diff --git a/tests/test_manager.py b/tests/test_manager.py index 8e12518..126b482 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -1,7 +1,7 @@ import unittest from path import Path import json -from mock import Mock +from unittest.mock import Mock import time import sys diff --git a/tests/test_xqueue_client.py b/tests/test_xqueue_client.py index 7c737ca..24cee9d 100644 --- a/tests/test_xqueue_client.py +++ b/tests/test_xqueue_client.py @@ -1,5 +1,5 @@ import unittest -import mock +from unittest import mock import json import collections import requests diff --git a/xqueue_watcher/client.py b/xqueue_watcher/client.py index 574ebae..b72a724 100644 --- a/xqueue_watcher/client.py +++ b/xqueue_watcher/client.py @@ -10,7 +10,7 @@ log = logging.getLogger(__name__) -class XQueueClient(object): +class XQueueClient: def __init__(self, queue_name, xqueue_server='http://localhost:18040', @@ -20,7 +20,7 @@ def __init__(self, poll_interval=MANAGER_CONFIG_DEFAULTS['POLL_INTERVAL'], login_poll_interval=MANAGER_CONFIG_DEFAULTS['LOGIN_POLL_INTERVAL'], follow_client_redirects=MANAGER_CONFIG_DEFAULTS['FOLLOW_CLIENT_REDIRECTS']): - super(XQueueClient, self).__init__() + super().__init__() self.session = requests.session() self.xqueue_server = xqueue_server self.queue_name = queue_name @@ -41,7 +41,7 @@ def __init__(self, self.processing = False def __repr__(self): - return '{}({})'.format(self.__class__.__name__, self.queue_name) + return f'{self.__class__.__name__}({self.queue_name})' def _parse_response(self, response, is_reply=True): if response.status_code not in [200]: @@ -97,7 +97,7 @@ def _request(self, method, uri, **kwargs): else: return (False, "Could not log in") else: - message = "Received un expected response status code, {0}, calling {1}.".format( + message = "Received un expected response status code, {}, calling {}.".format( r.status_code,url) log.error(message) return (False, message) @@ -106,7 +106,7 @@ def _login(self): if self.username is None: return True url = self.xqueue_server + '/xqueue/login/' - log.debug("Trying to login to {0} with user: {1} and pass {2}".format(url, self.username, self.password)) + log.debug(f"Trying to login to {url} with user: {self.username} and pass {self.password}") response = self.session.request('post', url, auth=self.http_basic_auth, data={ 'username': self.username, 'password': self.password, diff --git a/xqueue_watcher/grader.py b/xqueue_watcher/grader.py index d3b50d6..da6ad86 100644 --- a/xqueue_watcher/grader.py +++ b/xqueue_watcher/grader.py @@ -17,9 +17,9 @@ def format_errors(errors): error_string = '' error_list = [esc(e) for e in errors or []] if error_list: - items = '\n'.join(['
  • {0}
  • \n'.format(e) for e in error_list]) - error_string = '\n'.format(items) - error_string = '
    {0}
    '.format(error_string) + items = '\n'.join([f'
  • {e}
  • \n' for e in error_list]) + error_string = f'\n' + error_string = f'
    {error_string}
    ' return error_string @@ -28,7 +28,7 @@ def to_dict(result): # TODO: replace with mako template esc = html.escape if result[1]: - long_desc = '

    {0}

    '.format(esc(result[1])) + long_desc = '

    {}

    '.format(esc(result[1])) else: long_desc = '' return {'short-description': esc(result[0]), @@ -39,7 +39,7 @@ def to_dict(result): } -class Grader(object): +class Grader: results_template = """
    Test results
    @@ -126,10 +126,10 @@ def process_item(self, content, queue=None): # However, for debugging, still want to see what the problem is statsd.increment('xqueuewatcher.grader_payload_error') - self.log.debug("error parsing: '{0}' -- {1}".format(payload, err)) + self.log.debug(f"error parsing: '{payload}' -- {err}") raise - self.log.debug("Processing submission, grader payload: {0}".format(payload)) + self.log.debug(f"Processing submission, grader payload: {payload}") relative_grader_path = grader_config['grader'] grader_path = (self.grader_root / relative_grader_path).abspath() start = time.time() diff --git a/xqueue_watcher/jailedgrader.py b/xqueue_watcher/jailedgrader.py index 8bab17f..b9c4f45 100644 --- a/xqueue_watcher/jailedgrader.py +++ b/xqueue_watcher/jailedgrader.py @@ -18,7 +18,6 @@ import grader_support from .grader import Grader -from six.moves import zip TIMEOUT = 1 @@ -67,7 +66,7 @@ class JailedGrader(Grader): """ def __init__(self, *args, **kwargs): self.codejail_python = kwargs.pop("codejail_python", "python") - super(JailedGrader, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.locale_dir = self.grader_root / "conf" / "locale" self.fork_per_item = False # it's probably safe not to fork # EDUCATOR-3368: OpenBLAS library is allowed to allocate 1 thread @@ -87,7 +86,7 @@ def _run(self, grader_path, thecode, seed): return r def grade(self, grader_path, grader_config, submission): - if type(submission) != six.text_type: + if type(submission) != str: self.log.warning("Submission is NOT unicode") results = { @@ -123,7 +122,7 @@ def grade(self, grader_path, grader_config, submission): # Import the grader, straight from the original file. (It probably isn't in # sys.path, and we may be in a long running gunicorn process, so we don't # want to add stuff to sys.path either.) - grader_module = imp.load_source("grader_module", six.text_type(grader_path)) + grader_module = imp.load_source("grader_module", str(grader_path)) grader = grader_module.grader # Preprocess for grader-specified errors diff --git a/xqueue_watcher/manager.py b/xqueue_watcher/manager.py index db64152..e9c0da1 100644 --- a/xqueue_watcher/manager.py +++ b/xqueue_watcher/manager.py @@ -13,10 +13,9 @@ from codejail import jail_code from .settings import get_manager_config_values, MANAGER_CONFIG_DEFAULTS -from six.moves import range -class Manager(object): +class Manager: """ Manages polling connections to XQueue. """