diff --git a/accounts/utils.py b/accounts/utils.py index ddf8a29bb893d4f09e9a055ebc6333e1f4f41443..c796140f3e999d96350edfd8670072681c3c08f9 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six from django.utils.functional import cached_property from django.utils.module_loading import import_string @@ -76,7 +75,7 @@ class RelateUserMethodSettingsInitializer(object): if custom_user_profile_mask_method is None: return errors - if isinstance(custom_user_profile_mask_method, six.string_types): + if isinstance(custom_user_profile_mask_method, str): try: custom_user_profile_mask_method = ( import_string(custom_user_profile_mask_method)) @@ -105,16 +104,8 @@ class RelateUserMethodSettingsInitializer(object): )) else: import inspect - if six.PY3: - sig = inspect.signature(custom_user_profile_mask_method) - n_args = len([p.name for p in sig.parameters.values()]) - else: - # Don't count the number of defaults. - # (getargspec returns args, varargs, varkw, defaults) - n_args = sum( - [len(arg) for arg - in inspect.getargspec(custom_user_profile_mask_method)[:3] - if arg is not None]) + sig = inspect.signature(custom_user_profile_mask_method) + n_args = len([p.name for p in sig.parameters.values()]) if not n_args or n_args > 1: errors.append(RelateCriticalCheckMessage( @@ -209,7 +200,7 @@ class RelateUserMethodSettingsInitializer(object): if relate_user_full_name_format_method is None: return errors - if isinstance(relate_user_full_name_format_method, six.string_types): + if isinstance(relate_user_full_name_format_method, str): try: relate_user_full_name_format_method = ( import_string(relate_user_full_name_format_method)) @@ -265,7 +256,7 @@ class RelateUserMethodSettingsInitializer(object): unexpected_return_value = "" if returned_name is None: unexpected_return_value = "None" - if not isinstance(returned_name, six.string_types): + if not isinstance(returned_name, str): unexpected_return_value = type(returned_name).__name__ elif not returned_name.strip(): unexpected_return_value = "empty string %s" % returned_name diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7fd86d69b93e5277a0779114d6354ae644cc0b3a..d72c7b849a7caa608faf6c8207ae1ec5999b9799 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -122,3 +122,11 @@ jobs: set -e curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-flake8.sh . ./prepare-and-run-flake8.sh relate course accounts tests bin + +schedules: +- + cron: "0 0 * * 0" + displayName: Weekly build + branches: + include: + - master diff --git a/build-validator-pyz.sh b/build-validator-pyz.sh index dacfb67e0a1d55ca8e6435fffb740add4cf206b7..9a801fba6457e1a7f6b75d63cccf15b236118fa3 100755 --- a/build-validator-pyz.sh +++ b/build-validator-pyz.sh @@ -16,7 +16,6 @@ function pywhichpkg() } pyzzer.pyz course relate -r \ - $(pywhichmod six) \ $(pywhichpkg markdown) \ $(pywhichpkg django) \ $(pywhichpkg yaml) \ diff --git a/contrib/get_nbconvert_minfied_css.py b/contrib/get_nbconvert_minfied_css.py index abd8f0eded92c9bd7164f04b34daa7224119d22e..ffa3e7486d1789623a564bc1a1e35ada845d9716 100644 --- a/contrib/get_nbconvert_minfied_css.py +++ b/contrib/get_nbconvert_minfied_css.py @@ -56,14 +56,14 @@ CSS_MINIFIED_DEST = os.path.join(DEST_DIR, 'ipynb.style.min.css') def retry_urlopen(request, timeout=REQUEST_TIMEOUT, n_retries=REQUEST_MAX_RETRIES): - from six.moves.urllib.request import urlopen + from urllib.request import urlopen i = 0 while True: try: result = urlopen(request, timeout=timeout).read() return result except Exception as e: - from six.moves.urllib.error import URLError + from urllib.error import URLError from socket import timeout as TimeoutError # noqa: N812 if not isinstance(e, (URLError, TimeoutError)): raise e @@ -81,8 +81,8 @@ def retry_urlopen(request, timeout=REQUEST_TIMEOUT, n_retries=REQUEST_MAX_RETRIE def minify_css(css_string): url = 'https://cssminifier.com/raw' post_fields = {'input': css_string} - from six.moves.urllib.parse import urlencode - from six.moves.urllib.request import Request + from urllib.parse import urlencode + from urllib.request import Request request = Request(url, urlencode(post_fields).encode()) return retry_urlopen(request) diff --git a/course/admin.py b/course/admin.py index 14bf01595bfdbfd6ab76025f38978cb69063a242..56e3b672c5eb923ca78e00c5a03326f1a26a91a4 100644 --- a/course/admin.py +++ b/course/admin.py @@ -22,8 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six - from django.utils.translation import ( ugettext_lazy as _, pgettext) from django.contrib import admin @@ -192,8 +190,7 @@ class EventAdmin(admin.ModelAdmin): " (%s)" % str(self.ordinal) if self.ordinal is not None else "", self.course) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ list_editable = ("ordinal", "time", "end_time", "shown_in_calendar") @@ -298,12 +295,12 @@ class ParticipationAdmin(admin.ModelAdmin): form = ParticipationForm def get_roles(self, obj): - return ", ".join(six.text_type(role.name) for role in obj.roles.all()) + return ", ".join(str(role.name) for role in obj.roles.all()) get_roles.short_description = _("Roles") # type: ignore def get_tags(self, obj): - return ", ".join(six.text_type(tag.name) for tag in obj.tags.all()) + return ", ".join(str(tag.name) for tag in obj.tags.all()) get_tags.short_description = _("Tags") # type: ignore @@ -394,7 +391,7 @@ admin.site.register(Participation, ParticipationAdmin) class ParticipationPreapprovalAdmin(admin.ModelAdmin): def get_roles(self, obj): - return ", ".join(six.text_type(role.name) for role in obj.roles.all()) + return ", ".join(str(role.name) for role in obj.roles.all()) get_roles.short_description = _("Roles") # type: ignore diff --git a/course/analytics.py b/course/analytics.py index e5e7629cdb93a473f70c1d5599a312d736a1f178..05165b59d355678e9f4bdf56f09a305b48e45375 100644 --- a/course/analytics.py +++ b/course/analytics.py @@ -25,7 +25,6 @@ THE SOFTWARE. """ -import six from django.utils.translation import ugettext as _, pgettext from django.shortcuts import ( # noqa render, get_object_or_404, redirect) @@ -97,7 +96,7 @@ class Histogram(object): self.num_bin_title_formatter = num_bin_title_formatter def add_data_point(self, value, weight=1): - if isinstance(value, six.string_types): + if isinstance(value, str): self.string_weights[value] = \ self.string_weights.get(value, 0) + weight elif value is None: @@ -130,7 +129,7 @@ class Histogram(object): def total_weight(self): return ( sum(weight for val, weight in self.num_values) - + sum(six.itervalues(self.string_weights))) + + sum(self.string_weights.values())) def get_bin_info_list(self): min_value = self.num_min_value @@ -202,7 +201,7 @@ class Histogram(object): title=key, raw_weight=temp_string_weights[key], percentage=100*temp_string_weights[key]/total_weight) - for key in sorted(six.iterkeys(temp_string_weights))] + for key in sorted(temp_string_weights)] return num_bin_info + str_bin_info @@ -568,7 +567,7 @@ def page_analytics(pctx, flow_id, group_id, page_id): answer_stats = [] for (normalized_answer, correctness), count in \ - six.iteritems(normalized_answer_and_correctness_to_count): + normalized_answer_and_correctness_to_count.items(): answer_stats.append( AnswerStats( normalized_answer=normalized_answer, diff --git a/course/calendar.py b/course/calendar.py index aca6dd68697dedb379b0854a30fea712988db465..be08f64630d4360ab9bfa6e0fd9e70c7abf9e670 100644 --- a/course/calendar.py +++ b/course/calendar.py @@ -24,9 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six -from six.moves import range - from django.utils.translation import ( ugettext_lazy as _, pgettext_lazy) from django.contrib.auth.decorators import login_required @@ -398,7 +395,7 @@ def view_calendar(pctx): for event in events: kind_desc = event_kinds_desc.get(event.kind) - human_title = six.text_type(event) + human_title = str(event) event_json = { "id": event.id, @@ -419,7 +416,7 @@ def view_calendar(pctx): description = None show_description = True - event_desc = event_info_desc.get(six.text_type(event)) + event_desc = event_info_desc.get(str(event)) if event_desc is not None: if "description" in event_desc: description = markup_to_html( diff --git a/course/content.py b/course/content.py index b6978593fcc29bbef639bedeb8fe8ffa5b828ec6..0e0715f6056dd1a34e4988d50314cdc6d6617139 100644 --- a/course/content.py +++ b/course/content.py @@ -32,7 +32,6 @@ from django.utils.translation import ugettext as _ import os import re import datetime -import six import sys from django.utils.timezone import now @@ -42,7 +41,7 @@ from django.urls import NoReverseMatch from markdown.extensions import Extension from markdown.treeprocessors import Treeprocessor -from six.moves import html_parser +import html.parser as html_parser from jinja2 import ( BaseLoader as BaseTemplateLoader, TemplateNotFound, FileSystemLoader) @@ -284,8 +283,8 @@ def get_repo_blob_data_cached(repo, full_name, commit_sha): :arg commit_sha: A byte string containing the commit hash """ - if isinstance(commit_sha, six.binary_type): - from six.moves.urllib.parse import quote_plus + if isinstance(commit_sha, bytes): + from urllib.parse import quote_plus cache_key = "%s%R%1".join(( CACHE_KEY_ROOT, quote_plus(repo.controldir()), @@ -305,7 +304,7 @@ def get_repo_blob_data_cached(repo, full_name, commit_sha): if cache_key is None: result = get_repo_blob(repo, full_name, commit_sha, allow_tree=False).data - assert isinstance(result, six.binary_type) + assert isinstance(result, bytes) return result # Byte string is wrapped in a tuple to force pickling because memcache's @@ -320,7 +319,7 @@ def get_repo_blob_data_cached(repo, full_name, commit_sha): if cached_result is not None: (result,) = cached_result - assert isinstance(result, six.binary_type), cache_key + assert isinstance(result, bytes), cache_key return result result = get_repo_blob(repo, full_name, commit_sha, @@ -330,7 +329,7 @@ def get_repo_blob_data_cached(repo, full_name, commit_sha): if len(result) <= getattr(settings, "RELATE_CACHE_MAX_BYTES", 0): def_cache.add(cache_key, (result,), None) - assert isinstance(result, six.binary_type) + assert isinstance(result, bytes) return result @@ -368,7 +367,7 @@ def is_repo_file_accessible_as(access_kinds, repo, commit_sha, path): from fnmatch import fnmatch if isinstance(access_patterns, list): for pattern in access_patterns: - if isinstance(pattern, six.string_types): + if isinstance(pattern, str): if fnmatch(path_basename, pattern): return True @@ -517,7 +516,7 @@ class YamlBlockEscapingFileSystemLoader(FileSystemLoader): def expand_yaml_macros(repo, commit_sha, yaml_str): # type: (Repo_ish, bytes, Text) -> Text - if isinstance(yaml_str, six.binary_type): + if isinstance(yaml_str, bytes): yaml_str = yaml_str.decode("utf-8") from jinja2 import Environment, StrictUndefined @@ -559,7 +558,7 @@ def get_raw_yaml_from_repo(repo, full_name, commit_sha): :arg commit_sha: A byte string containing the commit hash """ - from six.moves.urllib.parse import quote_plus + from urllib.parse import quote_plus cache_key = "%RAW%%2".join(( CACHE_KEY_ROOT, quote_plus(repo.controldir()), quote_plus(full_name), commit_sha.decode(), @@ -606,7 +605,7 @@ def get_yaml_from_repo(repo, full_name, commit_sha, cached=True): except ImproperlyConfigured: cached = False else: - from six.moves.urllib.parse import quote_plus + from urllib.parse import quote_plus cache_key = "%%%2".join( (CACHE_KEY_ROOT, quote_plus(repo.controldir()), quote_plus(full_name), @@ -666,7 +665,7 @@ class TagProcessingHTMLParser(html_parser.HTMLParser): attrs.update(self.process_tag_func(tag, attrs)) self.out_file.write("<%s %s>" % (tag, " ".join( - _attr_to_string(k, v) for k, v in six.iteritems(attrs)))) + _attr_to_string(k, v) for k, v in attrs.items()))) def handle_endtag(self, tag): self.out_file.write("%s>" % tag) @@ -676,7 +675,7 @@ class TagProcessingHTMLParser(html_parser.HTMLParser): attrs.update(self.process_tag_func(tag, attrs)) self.out_file.write("<%s %s/>" % (tag, " ".join( - _attr_to_string(k, v) for k, v in six.iteritems(attrs)))) + _attr_to_string(k, v) for k, v in attrs.items()))) def handle_data(self, data): self.out_file.write(data) @@ -830,7 +829,7 @@ class LinkFixerTreeprocessor(Treeprocessor): def process_etree_element(self, element): changed_attrs = self.process_tag(element.tag, element.attrib) - for key, val in six.iteritems(changed_attrs): + for key, val in changed_attrs.items(): element.set(key, val) def walk_and_process_tree(self, root): @@ -843,10 +842,10 @@ class LinkFixerTreeprocessor(Treeprocessor): self.walk_and_process_tree(root) # root through and process Markdown's HTML stash (gross!) - from six.moves import cStringIO + from io import StringIO for i, (html, safe) in enumerate(self.md.htmlStash.rawHtmlBlocks): - outf = cStringIO() + outf = StringIO() parser = TagProcessingHTMLParser(outf, self.process_tag) parser.feed(html) @@ -889,8 +888,8 @@ def expand_markup( ): # type: (...) -> Text - if not isinstance(text, six.text_type): - text = six.text_type(text) + if not isinstance(text, str): + text = str(text) # {{{ process through Jinja @@ -948,7 +947,7 @@ def markup_to_html( def_cache = cache.caches["default"] result = def_cache.get(cache_key) if result is not None: - assert isinstance(result, six.text_type) + assert isinstance(result, str) return result if text.lstrip().startswith(JINJA_PREFIX): @@ -991,7 +990,7 @@ def markup_to_html( extensions=extensions, output_format="html5") - assert isinstance(result, six.text_type) + assert isinstance(result, str) if cache_key is not None: def_cache.add(cache_key, result, None) diff --git a/course/enrollment.py b/course/enrollment.py index 22dd2d36d45475b559fbeae645455ff10c03dc0e..5264262ae6ef780ecf2759952ea18606caf32ff5 100644 --- a/course/enrollment.py +++ b/course/enrollment.py @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from six.moves import intern +from sys import intern from django.utils.translation import ( ugettext_lazy as _, @@ -383,7 +383,7 @@ def send_enrollment_decision(participation, approved, request=None): else: # This will happen when this method is triggered by # a model signal which doesn't contain a request object. - from six.moves.urllib.parse import urljoin + from urllib.parse import urljoin course_uri = urljoin(getattr(settings, "RELATE_BASE_URL"), course.get_absolute_url()) diff --git a/course/exam.py b/course/exam.py index ea8dbdf2a018bf2ebea09b52090f559be5083731..ff0e8b28f1185705fe0d601492e2f3e910ab2d32 100644 --- a/course/exam.py +++ b/course/exam.py @@ -24,8 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six - from django.contrib.auth import get_user_model import django.forms as forms from django.utils.translation import ( @@ -606,7 +604,7 @@ def check_in_for_exam(request): def is_from_exams_only_facility(request): from course.utils import get_facilities_config - for name, props in six.iteritems(get_facilities_config(request)): + for name, props in get_facilities_config(request).items(): if not props.get("exams_only", False): continue diff --git a/course/flow.py b/course/flow.py index e8a55598fee0c2d212feed4cb48cb18e3072f6ae..eca742d7475188dba2ff60f62c489d95ccfd5126 100644 --- a/course/flow.py +++ b/course/flow.py @@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from django.utils import six from django.utils.translation import ( ugettext, ugettext_lazy as _) from django.contrib.auth.decorators import login_required @@ -38,7 +37,7 @@ from django.core.exceptions import ( from django.db import transaction from django.db.models import query # noqa from django.utils.safestring import mark_safe -mark_safe_lazy = lazy(mark_safe, six.text_type) +mark_safe_lazy = lazy(mark_safe, str) from django import forms from django import http from django.conf import settings @@ -2338,7 +2337,7 @@ def send_email_about_flow_page(pctx, flow_session_id, page_ordinal): } ) - from six.moves.urllib.parse import urljoin + from urllib.parse import urljoin review_uri = urljoin(getattr(settings, "RELATE_BASE_URL"), review_url) diff --git a/course/grades.py b/course/grades.py index 3f6040b63f59e47fbab819fa03cd7b0bfd9ccbf9..bf776239403fe646a6edb95b8e8d5007c241fdb5 100644 --- a/course/grades.py +++ b/course/grades.py @@ -25,7 +25,6 @@ THE SOFTWARE. """ import re -import six from decimal import Decimal from typing import cast @@ -308,13 +307,10 @@ def export_gradebook_csv(pctx): participations, grading_opps, grade_table = get_grade_table(pctx.course) - from six import StringIO + from io import StringIO csvfile = StringIO() - if six.PY2: - import unicodecsv as csv - else: - import csv + import csv fieldnames = ['user_name', 'last_name', 'first_name'] + [ gopp.identifier for gopp in grading_opps] @@ -1140,8 +1136,9 @@ class ImportGradesForm(StyledForm): from course.utils import csv_data_importable + import io importable, err_msg = csv_data_importable( - six.StringIO( + io.StringIO( file_contents.read().decode("utf-8", errors="replace")), column_idx_list, header_count) @@ -1352,6 +1349,7 @@ def import_grades(pctx): log_lines = [] + import io request = pctx.request if request.method == "POST": form = ImportGradesForm( @@ -1368,7 +1366,7 @@ def import_grades(pctx): course=pctx.course, grading_opportunity=form.cleaned_data["grading_opportunity"], attempt_id=form.cleaned_data["attempt_id"], - file_contents=six.StringIO(data), + file_contents=io.StringIO(data), attr_type=form.cleaned_data["attr_type"], attr_column=form.cleaned_data["attr_column"], points_column=form.cleaned_data["points_column"], @@ -1579,12 +1577,12 @@ def download_all_submissions(pctx, flow_id): submissions[key] = ( bytes_answer, list(visit.grades.all())) - from six import BytesIO + from io import BytesIO from zipfile import ZipFile bio = BytesIO() with ZipFile(bio, "w") as subm_zip: for key, ((extension, bytes_answer), visit_grades) in \ - six.iteritems(submissions): + submissions.items(): basename = "-".join(key) subm_zip.writestr( basename + extension, diff --git a/course/models.py b/course/models.py index 93dc880c36424da1ed9b9c7e72593ea063d04c99..e15cc62b202d0c6fd5f76b239312756fa1aba90c 100644 --- a/course/models.py +++ b/course/models.py @@ -26,8 +26,6 @@ THE SOFTWARE. from typing import cast -import six - from django.db import models from django.utils.timezone import now from django.urls import reverse @@ -244,8 +242,7 @@ class Course(models.Model): def __unicode__(self): return self.identifier - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ def clean(self): if self.force_lang: @@ -357,8 +354,7 @@ class Event(models.Model): self.full_clean() return super(Event, self).save(*args, **kwargs) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ # }}} @@ -391,8 +387,7 @@ class ParticipationTag(models.Model): def __unicode__(self): return "%s (%s)" % (self.name, self.course) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class Meta: verbose_name = _("Participation tag") @@ -463,8 +458,7 @@ class ParticipationRole(models.Model): return (perm, argument) in self.permission_tuples() # }}} - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class Meta: verbose_name = _("Participation role") @@ -490,8 +484,7 @@ class ParticipationPermissionBase(models.Model): else: return self.permission - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class ParticipationRolePermission(ParticipationPermissionBase): @@ -505,8 +498,7 @@ class ParticipationRolePermission(ParticipationPermissionBase): "permission": super(ParticipationRolePermission, self).__unicode__(), "role": self.role} - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class Meta: verbose_name = _("Participation role permission") @@ -558,8 +550,7 @@ class Participation(models.Model): for role in self.roles.all()) } - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class Meta: verbose_name = _("Participation") @@ -648,8 +639,7 @@ class ParticipationPreapproval(models.Model): return _("Preapproval with pk %(pk)s in %(course)s") % { "pk": self.pk, "course": self.course} - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class Meta: verbose_name = _("Participation preapproval") @@ -826,8 +816,7 @@ class AuthenticationToken(models.Model): "participation": self.participation, "description": self.description} - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class Meta: verbose_name = _("Authentication token") @@ -864,8 +853,7 @@ class InstantFlowRequest(models.Model): "start_time": self.start_time, } - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ # }}} @@ -950,8 +938,7 @@ class FlowSession(models.Model): 'session_id': self.id, 'flow_id': self.flow_id} - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ def append_comment(self, s): # type: (Optional[Text]) -> None @@ -1040,8 +1027,7 @@ class FlowPageData(models.Model): 'page_ordinal': self.page_ordinal, 'flow_session': self.flow_session}) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ # Django's templates are a little daft. No arithmetic--really? def previous_ordinal(self): @@ -1118,12 +1104,11 @@ class FlowPageVisit(models.Model): if self.answer is not None: # Translators: flow page visit: if an answer is # provided by user then append the string. - result += six.text_type(_(" (with answer)")) + result += str(_(" (with answer)")) return result - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class Meta: verbose_name = _("Flow page visit") @@ -1232,8 +1217,7 @@ class FlowPageVisitGrade(models.Model): return _("grade of %(visit)s: %(percentage)s") % { "visit": self.visit, "percentage": self.percentage()} - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class FlowPageBulkFeedback(models.Model): @@ -1354,8 +1338,7 @@ class FlowAccessException(models.Model): # pragma: no cover (deprecated and not "course": self.participation.course }) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class FlowAccessExceptionEntry(models.Model): # pragma: no cover (deprecated and not tested) # noqa @@ -1375,8 +1358,7 @@ class FlowAccessExceptionEntry(models.Model): # pragma: no cover (deprecated an def __unicode__(self): return self.permission - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ # }}} @@ -1419,8 +1401,7 @@ class FlowRuleException(models.Model): "exception_id": " id %d" % self.id if self.id is not None else ""}) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ def clean(self): # type: () -> None @@ -1467,12 +1448,12 @@ class FlowRuleException(models.Model): try: if self.kind == flow_rule_kind.start: - validate_session_start_rule(ctx, six.text_type(self), rule, tags) + validate_session_start_rule(ctx, str(self), rule, tags) elif self.kind == flow_rule_kind.access: - validate_session_access_rule(ctx, six.text_type(self), rule, tags) + validate_session_access_rule(ctx, str(self), rule, tags) elif self.kind == flow_rule_kind.grading: validate_session_grading_rule( - ctx, six.text_type(self), rule, tags, + ctx, str(self), rule, tags, grade_identifier) else: # pragma: no cover. This won't happen raise ValueError("invalid exception rule kind") @@ -1564,8 +1545,7 @@ class GradingOpportunity(models.Model): "opportunity_id": self.identifier, "course": self.course}) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ def get_aggregation_strategy_descr(self): return dict(GRADE_AGGREGATION_STRATEGY_CHOICES).get( @@ -1632,8 +1612,7 @@ class GradeChange(models.Model): 'state': self.state, 'opportunityname': self.opportunity.name} - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ def clean(self): super(GradeChange, self).clean() @@ -1887,8 +1866,7 @@ class InstantMessage(models.Model): def __unicode__(self): return "%s: %s" % (self.participation, self.text) - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ # }}} @@ -1930,8 +1908,7 @@ class Exam(models.Model): 'course': self.course, } - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ class ExamTicket(models.Model): @@ -1985,8 +1962,7 @@ class ExamTicket(models.Model): 'exam': self.exam, } - if six.PY3: - __str__ = __unicode__ + __str__ = __unicode__ def clean(self): super(ExamTicket, self).clean() diff --git a/course/page/base.py b/course/page/base.py index e7bbef5a904f83c099ffbe03b703ac35931c2e75..b486a32373eaf2d615cf589ff4be3623adfebe81 100644 --- a/course/page/base.py +++ b/course/page/base.py @@ -24,8 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six - import django.forms as forms from course.validation import validate_struct, ValidationError @@ -54,7 +52,7 @@ if False: # }}} -mark_safe_lazy = lazy(mark_safe, six.text_type) +mark_safe_lazy = lazy(mark_safe, str) class PageContext(object): @@ -190,28 +188,28 @@ def get_auto_feedback(correctness): correctness = validate_point_count(correctness) if correctness is None: - return six.text_type( + return str( ugettext_noop("No information on correctness of answer.")) if correctness == 0: - return six.text_type(ugettext_noop("Your answer is not correct.")) + return str(ugettext_noop("Your answer is not correct.")) elif correctness == 1: - return six.text_type(ugettext_noop("Your answer is correct.")) + return str(ugettext_noop("Your answer is correct.")) elif correctness > 1: - return six.text_type( + return str( string_concat( ugettext_noop( "Your answer is correct and earned bonus points."), " (%.1f %%)") % (100*correctness)) elif correctness > 0.5: - return six.text_type( + return str( string_concat( ugettext_noop("Your answer is mostly correct."), " (%.1f %%)") % (100*correctness)) else: - return six.text_type( + return str( string_concat( ugettext_noop("Your answer is somewhat correct. "), "(%.1f%%)") diff --git a/course/page/choice.py b/course/page/choice.py index 8925c737713d6dc3f3b8363762016241275cfb49..d6082e07f25c7c169b2908c151cf2f1d17191b54 100644 --- a/course/page/choice.py +++ b/course/page/choice.py @@ -24,8 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six -from six.moves import range import django.forms as forms from django.utils.safestring import mark_safe from django.utils.translation import ( @@ -107,7 +105,7 @@ class ChoiceInfo(object): item_mode = [None] def find_tag_by_mode(mode): - for k, v in six.iteritems(tag_mode_dict): # pragma: no branch + for k, v in tag_mode_dict.items(): # pragma: no branch if v == mode: return k diff --git a/course/page/code.py b/course/page/code.py index 439994453bcee8a353035b8bde0e4245bf413d2e..248889474c100cb26e75154cba821c589c325be5 100644 --- a/course/page/code.py +++ b/course/page/code.py @@ -24,8 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six - from course.validation import ValidationError import django.forms as forms from django.core.exceptions import ObjectDoesNotExist @@ -93,7 +91,7 @@ class InvalidPingResponse(RuntimeError): def request_run(run_req, run_timeout, image=None): import json - from six.moves import http_client + import http.client as http_client import docker import socket import errno @@ -671,7 +669,7 @@ class CodeQuestion(PageBaseWithTitle, PageBaseWithValue): for key, val in sorted(response_dict.items()): if (key not in ["result", "figures"] and val - and isinstance(val, six.string_types)): + and isinstance(val, str)): error_msg_parts.append("-------------------------------------") error_msg_parts.append(key) error_msg_parts.append("-------------------------------------") @@ -721,7 +719,7 @@ class CodeQuestion(PageBaseWithTitle, PageBaseWithValue): except Exception: from traceback import format_exc feedback_bits.append( - six.text_type(string_concat( + str(string_concat( "
", _( "Both the grading code and the attempt to " @@ -923,7 +921,7 @@ class CodeQuestion(PageBaseWithTitle, PageBaseWithValue): else: return False - if not isinstance(s, six.text_type): + if not isinstance(s, str): return _("(Non-string in 'HTML' output filtered out)") return bleach.clean(s, diff --git a/course/page/text.py b/course/page/text.py index cf8d1e0bbc8cdda1688faac9abe4cce2b070ba6d..5f1fd629088160a905d424d95d6d746a2f82e423 100644 --- a/course/page/text.py +++ b/course/page/text.py @@ -25,7 +25,6 @@ THE SOFTWARE. """ -import six from django.utils.translation import ( ugettext_lazy as _, ugettext) from course.validation import validate_struct, ValidationError @@ -314,11 +313,6 @@ class CaseSensitiveRegexMatcher(RegexMatcher): def parse_sympy(s): - if six.PY2: - if isinstance(s, unicode): # noqa -- has Py2/3 guard - # Sympy is not spectacularly happy with unicode function names - s = s.encode() - from pymbolic import parse from pymbolic.interop.sympy import PymbolicToSympyMapper @@ -391,10 +385,10 @@ class SymbolicExpressionMatcher(TextAnswerMatcher): def float_or_sympy_evalf(s): - if isinstance(s, six.integer_types + (float,)): + if isinstance(s, (int, float,)): return s - if not isinstance(s, six.string_types): + if not isinstance(s, str): raise TypeError("expected string, int or float for floating point " "literal") @@ -426,11 +420,11 @@ class FloatMatcher(TextAnswerMatcher): matcher_desc, required_attrs=( ("type", str), - ("value", six.integer_types + (float, str)), + ("value", (int, float, str)), ), allowed_attrs=( - ("rtol", six.integer_types + (float, str)), - ("atol", six.integer_types + (float, str)), + ("rtol", (int, float, str)), + ("atol", (int, float, str)), ), ) @@ -599,7 +593,7 @@ def parse_matcher_string(vctx, location, matcher_desc): def parse_matcher(vctx, location, matcher_desc): - if isinstance(matcher_desc, six.string_types): + if isinstance(matcher_desc, str): return parse_matcher_string(vctx, location, matcher_desc) else: if not isinstance(matcher_desc, Struct): diff --git a/course/utils.py b/course/utils.py index 68468fe86ff1020fa7b8e1cdc7bd420fc721de76..08d95dbf912d098d9e877aad8a59ec5b2e1c2a49 100644 --- a/course/utils.py +++ b/course/utils.py @@ -26,7 +26,6 @@ THE SOFTWARE. from typing import cast, Text -import six import datetime # noqa import markdown @@ -1042,14 +1041,14 @@ class FacilityFindingMiddleware(object): else: import ipaddress remote_address = ipaddress.ip_address( - six.text_type(request.META['REMOTE_ADDR'])) + str(request.META['REMOTE_ADDR'])) facilities = set() - for name, props in six.iteritems(get_facilities_config(request)): + for name, props in get_facilities_config(request).items(): ip_ranges = props.get("ip_ranges", []) for ir in ip_ranges: - if remote_address in ipaddress.ip_network(six.text_type(ir)): + if remote_address in ipaddress.ip_network(str(ir)): facilities.add(name) request.relate_facilities = frozenset(facilities) @@ -1071,10 +1070,7 @@ def csv_data_importable(file_contents, column_idx_list, header_count): spamreader = csv.reader(file_contents) n_header_row = 0 try: - if six.PY2: - row0 = spamreader.next() - else: - row0 = spamreader.__next__() + row0 = spamreader.__next__() except Exception as e: err_msg = type(e).__name__ err_str = str(e) @@ -1108,7 +1104,7 @@ def csv_data_importable(file_contents, column_idx_list, header_count): try: for column_idx in column_idx_list: if column_idx is not None: - six.text_type(get_col_contents_or_empty(row, column_idx-1)) + str(get_col_contents_or_empty(row, column_idx-1)) except UnicodeDecodeError: return False, ( _("Error: Columns to be imported contain " @@ -1186,7 +1182,7 @@ def get_course_specific_language_choices(): filtered_options = ( [get_default_option()] + [get_formatted_options(k, v) - for k, v in six.iteritems(filtered_options_dict)]) + for k, v in filtered_options_dict.items()]) # filtered_options[1] is the option for settings.LANGUAGE_CODE # it's already displayed when settings.USE_I18N is False diff --git a/course/validation.py b/course/validation.py index d1b764bd5cba9931a7851b8ead38c590c870c626..4cd273192b15faff0c0d73fddb7d540a99273ef9 100644 --- a/course/validation.py +++ b/course/validation.py @@ -26,7 +26,6 @@ THE SOFTWARE. import re import datetime -import six import sys from django.core.exceptions import ObjectDoesNotExist @@ -193,10 +192,6 @@ def validate_struct( allowed_types = str is_markup = True - if allowed_types == str: - # Love you, too, Python 2. - allowed_types = six.string_types - if not isinstance(val, allowed_types): raise ValidationError( string_concat("%(location)s: ", @@ -219,7 +214,7 @@ def validate_struct( % {'location': location, 'attr': ",".join(present_attrs)}) -datespec_types = (datetime.date, six.string_types, datetime.datetime) +datespec_types = (datetime.date, str, datetime.datetime) # }}} @@ -604,11 +599,11 @@ def validate_session_start_rule(vctx, location, nrule, tags): ("if_has_participation_tags_all", list), ("if_in_facility", str), ("if_has_in_progress_session", bool), - ("if_has_session_tagged", (six.string_types, type(None))), + ("if_has_session_tagged", (str, type(None))), ("if_has_fewer_sessions_than", int), ("if_has_fewer_tagged_sessions_than", int), ("if_signed_in_with_matching_exam_ticket", bool), - ("tag_session", (six.string_types, type(None))), + ("tag_session", (str, type(None))), ("may_start_new_session", bool), ("may_list_existing_sessions", bool), ("lock_down_as_exam_session", bool), @@ -698,7 +693,7 @@ def validate_session_access_rule(vctx, location, arule, tags): ("if_has_participation_tags_any", list), ("if_has_participation_tags_all", list), ("if_in_facility", str), - ("if_has_tag", (six.string_types, type(None))), + ("if_has_tag", (str, type(None))), ("if_in_progress", bool), ("if_completed_before", datespec_types), ("if_expiration_mode", str), @@ -794,7 +789,7 @@ def validate_session_grading_rule( ("if_has_role", list), ("if_has_participation_tags_any", list), ("if_has_participation_tags_all", list), - ("if_has_tag", (six.string_types, type(None))), + ("if_has_tag", (str, type(None))), ("if_started_before", datespec_types), ("if_completed_before", datespec_types), @@ -1149,7 +1144,7 @@ def validate_flow_desc(vctx, location, flow_desc): if hasattr(flow_desc, "notify_on_submit"): for i, item in enumerate(flow_desc.notify_on_submit): - if not isinstance(item, six.string_types): + if not isinstance(item, str): raise ValidationError( string_concat( "%s, ", @@ -1241,7 +1236,7 @@ def get_yaml_from_repo_safely(repo, full_name, commit_sha): "%(fullname)s: %(err_type)s: %(err_str)s" % { 'fullname': full_name, "err_type": tp.__name__, - "err_str": six.text_type(e)}) + "err_str": str(e)}) def check_attributes_yml(vctx, repo, path, tree, access_kinds): @@ -1308,7 +1303,7 @@ def check_attributes_yml(vctx, repo, path, tree, access_kinds): for access_kind in access_kinds: if hasattr(att_yml, access_kind): for i, l in enumerate(getattr(att_yml, access_kind)): - if not isinstance(l, six.string_types): + if not isinstance(l, str): raise ValidationError( "%s: entry %d in '%s' is not a string" % (loc, i+1, access_kind)) @@ -1607,7 +1602,7 @@ def validate_course_content(repo, course_file, events_file, class FileSystemFakeRepo(object): # pragma: no cover def __init__(self, root): self.root = root - assert isinstance(self.root, six.binary_type) + assert isinstance(self.root, bytes) def controldir(self): return self.root @@ -1635,7 +1630,7 @@ class FileSystemFakeRepoTreeEntry(object): # pragma: no cover class FileSystemFakeRepoTree(object): # pragma: no cover def __init__(self, root): self.root = root - assert isinstance(self.root, six.binary_type) + assert isinstance(self.root, bytes) def __getitem__(self, name): if not name: diff --git a/course/versioning.py b/course/versioning.py index b69e61cb0cb1d742ea9edd58f218848fa71f6180..f42ae51f259e62e6d7353a0c8fa71dc5add1b7c8 100644 --- a/course/versioning.py +++ b/course/versioning.py @@ -28,7 +28,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import re from django.shortcuts import ( # noqa @@ -105,7 +104,7 @@ def transfer_remote_refs(repo, remote_refs): valid_refs = [] if remote_refs is not None: - for ref, sha in six.iteritems(remote_refs): + for ref, sha in remote_refs.items(): if (ref.startswith(b"refs/heads/") and not ref.startswith(b"refs/heads/origin/")): new_ref = b"refs/remotes/origin/"+_remove_prefix(b"refs/heads/", ref) @@ -121,7 +120,7 @@ def get_dulwich_client_and_remote_path_from_course(course): # type: (Course) -> Tuple[Union[dulwich.client.GitClient, dulwich.client.SSHGitClient], bytes] # noqa ssh_kwargs = {} if course.ssh_private_key: - from six import StringIO + from io import StringIO key_file = StringIO(course.ssh_private_key) ssh_kwargs["pkey"] = paramiko.RSAKey.from_private_key(key_file) @@ -486,12 +485,9 @@ class GitUpdateForm(StyledForm): def _get_commit_message_as_html(repo, commit_sha): - if six.PY2: - from cgi import escape - else: - from html import escape + from html import escape - if isinstance(commit_sha, six.text_type): + if isinstance(commit_sha, str): commit_sha = commit_sha.encode() try: diff --git a/course/views.py b/course/views.py index 5e470b2e89405b92641fd3e44628407013e5d652..d209b9590b851107bfec16bc8275037be5f17424 100644 --- a/course/views.py +++ b/course/views.py @@ -38,7 +38,6 @@ import django.views.decorators.http as http_dec from django import http from django.utils.safestring import mark_safe from django.db import transaction -from django.utils import six from django.utils.translation import ( ugettext_lazy as _, ugettext, @@ -50,7 +49,7 @@ from django.contrib.auth.decorators import login_required from django_select2.forms import Select2Widget -mark_safe_lazy = lazy(mark_safe, six.text_type) +mark_safe_lazy = lazy(mark_safe, str) from django.views.decorators.cache import cache_control @@ -1338,15 +1337,15 @@ def generate_ssh_keypair(request): key_class = RSAKey prv = key_class.generate(bits=2048) - import six - prv_bio = six.StringIO() + import io + prv_bio = io.StringIO() prv.write_private_key(prv_bio) - prv_bio_read = six.StringIO(prv_bio.getvalue()) + prv_bio_read = io.StringIO(prv_bio.getvalue()) pub = key_class.from_private_key(prv_bio_read) - pub_bio = six.StringIO() + pub_bio = io.StringIO() pub_bio.write("%s %s relate-course-key" % (pub.get_name(), pub.get_base64())) return render(request, "course/keypair.html", { diff --git a/relate/checks.py b/relate/checks.py index d040c2a1461b165b1cc0bd7a0a0bc0988c0f1c89..07caa2dde728d0f9d04aa12932ca45a71f5c40f9 100644 --- a/relate/checks.py +++ b/relate/checks.py @@ -25,7 +25,6 @@ THE SOFTWARE. """ import os -import six from django.conf import settings from django.core.checks import Critical, Warning, register from django.core.exceptions import ImproperlyConfigured @@ -72,7 +71,7 @@ class DeprecatedException(Exception): def get_ip_network(ip_range): import ipaddress - return ipaddress.ip_network(six.text_type(ip_range)) + return ipaddress.ip_network(str(ip_range)) def check_relate_settings(app_configs, **kwargs): @@ -122,7 +121,7 @@ def check_relate_settings(app_configs, **kwargs): id="email_connections.E001" )) else: - for label, c in six.iteritems(email_connections): + for label, c in email_connections.items(): if not isinstance(c, dict): errors.append(RelateCriticalCheckMessage( msg=( @@ -178,7 +177,7 @@ def check_relate_settings(app_configs, **kwargs): id="relate_facilities.E002") ) else: - for facility, conf in six.iteritems(facilities): + for facility, conf in facilities.items(): if not isinstance(conf, dict): errors.append(RelateCriticalCheckMessage( msg=( @@ -383,7 +382,7 @@ def check_relate_settings(app_configs, **kwargs): from django.utils.itercompat import is_iterable - if (isinstance(languages, six.string_types) + if (isinstance(languages, str) or not is_iterable(languages)): errors.append(RelateCriticalCheckMessage( msg=(INSTANCE_ERROR_PATTERN @@ -392,7 +391,7 @@ def check_relate_settings(app_configs, **kwargs): id="relate_languages.E001") ) else: - if any(isinstance(choice, six.string_types) + if any(isinstance(choice, str) or not is_iterable(choice) or len(choice) != 2 for choice in languages): errors.append(RelateCriticalCheckMessage( @@ -431,7 +430,7 @@ def check_relate_settings(app_configs, **kwargs): id="relate_site_name.E002") ) else: - if not isinstance(site_name, six.string_types): + if not isinstance(site_name, str): errors.append(RelateCriticalCheckMessage( msg=(INSTANCE_ERROR_PATTERN % {"location": "%s/%s" % (RELATE_SITE_NAME, @@ -457,7 +456,7 @@ def check_relate_settings(app_configs, **kwargs): relate_override_templates_dirs = getattr(settings, RELATE_OVERRIDE_TEMPLATES_DIRS, None) if relate_override_templates_dirs is not None: - if (isinstance(relate_override_templates_dirs, six.string_types) + if (isinstance(relate_override_templates_dirs, str) or not is_iterable(relate_override_templates_dirs)): errors.append(RelateCriticalCheckMessage( msg=(INSTANCE_ERROR_PATTERN @@ -465,7 +464,7 @@ def check_relate_settings(app_configs, **kwargs): "types": "an iterable (e.g., a list or tuple)."}), id="relate_override_templates_dirs.E001")) else: - if any(not isinstance(directory, six.string_types) + if any(not isinstance(directory, str) for directory in relate_override_templates_dirs): errors.append(RelateCriticalCheckMessage( msg=("'%s' must contain only string of paths." diff --git a/relate/utils.py b/relate/utils.py index 64b7776c20bfbf9df9ca8b824e19ec1bd06962a0..f2cb55b3497069f3080e6a3ae51173963cef92c3 100644 --- a/relate/utils.py +++ b/relate/utils.py @@ -25,7 +25,6 @@ THE SOFTWARE. """ -import six import datetime import django.forms as forms @@ -139,10 +138,10 @@ def is_maintenance_mode(request): import ipaddress remote_address = ipaddress.ip_address( - six.text_type(request.META['REMOTE_ADDR'])) + str(request.META['REMOTE_ADDR'])) for exc in exceptions: - if remote_address in ipaddress.ip_network(six.text_type(exc)): + if remote_address in ipaddress.ip_network(str(exc)): maintenance_mode = False break @@ -255,10 +254,10 @@ def format_datetime_local(datetime, format='DATETIME_FORMAT'): class Struct(object): def __init__(self, entries): # type: (Dict) -> None - for name, val in six.iteritems(entries): + for name, val in entries.items(): self.__dict__[name] = val - self._field_names = list(six.iterkeys(entries)) + self._field_names = list(entries.keys()) def __repr__(self): return repr(self.__dict__) @@ -269,7 +268,7 @@ def dict_to_struct(data): if isinstance(data, list): return [dict_to_struct(d) for d in data] elif isinstance(data, dict): - return Struct({k: dict_to_struct(v) for k, v in six.iteritems(data)}) + return Struct({k: dict_to_struct(v) for k, v in data.items()}) else: return data @@ -278,7 +277,7 @@ def struct_to_dict(data): # type: (Struct) -> Dict return dict( (name, val) - for name, val in six.iteritems(data.__dict__) + for name, val in data.__dict__.items() if not name.startswith("_")) # }}} @@ -346,8 +345,8 @@ def dumpstacks(signal, frame): # pragma: no cover id2name = dict([(th.ident, th.name) for th in threading.enumerate()]) code = [] - for threadId, stack in sys._current_frames().items(): - code.append("\n# Thread: %s(%d)" % (id2name.get(threadId, ""), threadId)) + for thread_id, stack in sys._current_frames().items(): + code.append("\n# Thread: %s(%d)" % (id2name.get(thread_id, ""), thread_id)) for filename, lineno, name, line in traceback.extract_stack(stack): code.append('File: "%s", line %d, in %s' % (filename, lineno, name)) if line: diff --git a/requirements.txt b/requirements.txt index b516033030d171ae5f2da8715432ed4c98175293..71239f95a9eccf7d994be0dd4feee6144248056b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django>=2.1.11 +django>=2.1.11,<3 # Automatically renders Django forms in a pretty, Bootstrap-compatible way. django-crispy-forms>=1.5.1 @@ -15,8 +15,6 @@ markdown>=2.6.3,<3.0 # For rendering macros in content: jinja2 -six - # For math/symbolic questions pymbolic sympy @@ -30,7 +28,9 @@ pyyaml # dulwich dulwich>=0.19 ecdsa -paramiko + +# FIXME: 2.7.1 gives bogus 'incorrect padding' error on PEM parsing +paramiko<2.7 # A date picker widget # https://github.com/tutorcruncher/django-bootstrap3-datetimepicker diff --git a/setup.py b/setup.py index 924d04ab06eb9a5722d14e813eb5f67fcc022c88..62c4ac626e9034e23302c30b6fb0033f1f8d3a9c 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup(name="relate-courseware", license="MIT", packages=find_packages(exclude=['tests']), install_requires=[ - "django>=2.1.11,<2.3", + "django>=2.1.11,<3", "django-crispy-forms>=1.5.1", "colorama", "markdown<3.0", diff --git a/tests/base_test_mixins.py b/tests/base_test_mixins.py index f3ec3523cbe50f41869e8dcf90541b560ed2bd74..04653f47ba28fd5d6d3ea785621d8fc84f187954 100644 --- a/tests/base_test_mixins.py +++ b/tests/base_test_mixins.py @@ -1,5 +1,3 @@ -from __future__ import division - __copyright__ = "Copyright (C) 2017 Dong Zhuang, Andreas Kloeckner, Zesheng Wang" __license__ = """ @@ -23,7 +21,6 @@ THE SOFTWARE. """ import sys -import six import re import tempfile import os @@ -252,7 +249,7 @@ class ResponseContextMixin(object): self, resp, # noqa context_name, expected_value_regex): value = self.get_response_context_value_by_name(resp, context_name) - six.assertRegex(self, value, expected_value_regex) + self.assertRegex(value, expected_value_regex) def get_response_context_answer_feedback(self, response): return self.get_response_context_value_by_name(response, "feedback") @@ -361,7 +358,7 @@ class ResponseContextMixin(object): select2_url = reverse(select2_urlname) params = {"field_id": field_id} if term is not None: - assert isinstance(term, six.string_types) + assert isinstance(term, str) term = term.strip() if term: params["term"] = term @@ -504,7 +501,7 @@ class SuperuserCreateMixin(ResponseContextMixin): url += ("?%s" % "&".join( ["%s=%s" % (k, v) - for (k, v) in six.iteritems(querystring)])) + for (k, v) in querystring.items()])) return url def get_reset_password_stage2(self, user_id, sign_in_key, **kwargs): @@ -774,7 +771,7 @@ class CoursesTestMixinBase(SuperuserCreateMixin): "\n".join(["%s:%s" % (type(e).__name__, str(e)) for e in errs])) for field, errs - in six.iteritems(form_context.errors.as_data())] + in form_context.errors.as_data().items()] non_field_errors = form_context.non_field_errors() if non_field_errors: error_list.append(repr(non_field_errors)) @@ -1845,8 +1842,7 @@ class SingleCourseTestMixin(CoursesTestMixinBase): kwargs = Course.objects.first().__dict__ kwargs.update(attrs_dict) - import six - for k, v in six.iteritems(kwargs): + for k, v in kwargs.items(): if v is None: kwargs[k] = "" return kwargs @@ -1887,7 +1883,7 @@ class SingleCourseTestMixin(CoursesTestMixinBase): if commit_sha is None: commit_sha = cls.course.active_git_commit_sha - if isinstance(commit_sha, six.text_type): + if isinstance(commit_sha, str): commit_sha = commit_sha.encode() from course.content import get_flow_desc @@ -2076,8 +2072,10 @@ class SingleCourseQuizPageTestMixin(SingleCoursePageTestMixin): prefix, zip_file = resp["Content-Disposition"].split('=') assert prefix == "attachment; filename" assert resp.get('Content-Type') == "application/zip" + + import io if dl_file_extension: - buf = six.BytesIO(resp.content) + buf = io.BytesIO(resp.content) import zipfile with zipfile.ZipFile(buf, 'r') as zf: assert zf.testzip() is None @@ -2510,12 +2508,9 @@ class SubprocessRunpyContainerMixin(object): def improperly_configured_cache_patch(): # can be used as context manager or decorator - if six.PY3: - built_in_import_path = "builtins.__import__" - import builtins # noqa - else: - built_in_import_path = "__builtin__.__import__" - import __builtin__ as builtins # noqa + built_in_import_path = "builtins.__import__" + import builtins # noqa + built_in_import = builtins.__import__ def my_disable_cache_import(name, globals=None, locals=None, fromlist=(), @@ -2687,7 +2682,7 @@ class HackRepoMixin(object): import json error_msg = ("\n%s" % json.dumps(OrderedDict( sorted( - [(k, v) for (k, v) in six.iteritems(grade_info.__dict__)])), + [(k, v) for (k, v) in grade_info.__dict__.items()])), indent=4)) error_msg = error_msg.replace("null", "None") self.fail(error_msg) diff --git a/tests/factories.py b/tests/factories.py index cfe9345a7fdb0174e930a06a835cfe9d58e4d979..724e24e5294d42a9692eb04f3e9d9ddb1179104e 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import pytz from datetime import datetime @@ -103,7 +102,7 @@ class ParticipationFactory(factory.django.DjangoModelFactory): if extracted: for role in extracted: - if isinstance(role, six.string_types): + if isinstance(role, str): role = ParticipationRoleFactory( course=self.course, identifier=role) else: @@ -122,7 +121,7 @@ class ParticipationFactory(factory.django.DjangoModelFactory): if extracted: for tag in extracted: - if isinstance(tag, six.string_types): + if isinstance(tag, str): tag = ParticipationTagFactory( course=self.course, name=tag) else: @@ -250,7 +249,7 @@ class ParticipationPreapprovalFactory(factory.django.DjangoModelFactory): return if extracted: for role in extracted: - if isinstance(role, six.string_types): + if isinstance(role, str): role = ParticipationRoleFactory( course=self.course, identifier=role) else: diff --git a/tests/test_accounts/test_admin.py b/tests/test_accounts/test_admin.py index 31636546f4c8e47b30d62e79dd3a9fd1bfcf368b..0ea2336958b45c4ecf7b76d8098aa5ff31017bfa 100644 --- a/tests/test_accounts/test_admin.py +++ b/tests/test_accounts/test_admin.py @@ -22,8 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six -from unittest import skipIf, skipUnless +from unittest import skipUnless from django.test import TestCase, RequestFactory from django.urls import reverse @@ -74,7 +73,6 @@ class AccountsAdminTest(AdminTestMixin, TestCase): self.superuser.refresh_from_db() self.rf = RequestFactory() - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_change_view(self): with self.subTest("superuser admin change/changelist for " @@ -147,7 +145,6 @@ class AccountsAdminTest(AdminTestMixin, TestCase): self.assertEqual(get_user_model().objects.count(), user_count + 1) self.assertTrue(new_user.has_usable_password()) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_admin_user_change_fieldsets(self): # This test is using request factory change_url = reverse('admin:accounts_user_change', diff --git a/tests/test_admin.py b/tests/test_admin.py index f01eceb3ebd88746147cf21f8d9e641f9f0e6f49..c7b48d1c125a02dd842d26d30ac2da8003967b14 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -22,8 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six -from unittest import skipIf, skipUnless +from unittest import skipUnless from django.test import TestCase, RequestFactory from django.contrib.admin import site @@ -123,12 +122,10 @@ class CourseAdminTestMixin(AdminTestMixin): @skipUnless(may_run_expensive_tests(), SKIP_EXPENSIVE_TESTS_REASON) class CourseAdminGenericTest(CourseAdminTestMixin, TestCase): - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_course(self): # todo: make assertion self.navigate_admin_view_by_model(models.Course) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_event_filter_result(self): factories.EventFactory.create( course=self.course1, kind="course1_kind") @@ -147,7 +144,6 @@ class CourseAdminGenericTest(CourseAdminTestMixin, TestCase): "course2": course2_event_count }) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_participation_filter_result(self): self.navigate_admin_view_by_model(models.Participation) self.list_filter_result( @@ -160,33 +156,27 @@ class CourseAdminGenericTest(CourseAdminTestMixin, TestCase): models.Participation.objects.filter(course=self.course2).count(), }) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_participation_tag(self): self.navigate_admin_view_by_model( models.ParticipationTag) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_participation_role(self): self.navigate_admin_view_by_model( models.ParticipationRole) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_flow_rule_exception(self): factories.FlowRuleExceptionFactory( participation=self.course2_student_participation) self.navigate_admin_view_by_model(models.FlowRuleException) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_instant_message(self): factories.InstantMessageFactory( participation=self.course1_student_participation) self.navigate_admin_view_by_model(models.InstantMessage) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_instant_flow_request(self): self.navigate_admin_view_by_model(models.InstantFlowRequest) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_exam(self): factories.ExamFactory(course=self.course1) self.navigate_admin_view_by_model(models.Exam) @@ -241,7 +231,6 @@ class CourseAdminSessionRelatedMixin(CourseAdminTestMixin): @skipUnless(may_run_expensive_tests(), SKIP_EXPENSIVE_TESTS_REASON) class CourseAdminSessionRelatedTest(CourseAdminSessionRelatedMixin, TestCase): - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_flowsession_filter_result(self): self.navigate_admin_view_by_model(models.FlowSession) self.list_filter_result( @@ -252,11 +241,9 @@ class CourseAdminSessionRelatedTest(CourseAdminSessionRelatedMixin, TestCase): "course2": self.course2_session_count }) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_grading_opportunity(self): self.navigate_admin_view_by_model(models.GradingOpportunity) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_flowpagevisit_filter_result(self): self.navigate_admin_view_by_model(models.FlowPageVisit) self.list_filter_result( @@ -267,7 +254,6 @@ class CourseAdminSessionRelatedTest(CourseAdminSessionRelatedMixin, TestCase): "course2": self.course2_visits_count }) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_flowpagevisit_flow_id_filter_result(self): modeladmin = admin.FlowPageVisitAdmin(models.FlowPageVisit, site) @@ -475,7 +461,6 @@ class ParticipationFormTest(CourseAdminTestMixin, TestCase): @skipUnless(may_run_expensive_tests(), SKIP_EXPENSIVE_TESTS_REASON) class ParticipationPreapprovalAdminTest(CourseAdminTestMixin, TestCase): - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_participation_preapproval(self): factories.ParticipationPreapprovalFactory(course=self.course1) self.navigate_admin_view_by_model(models.ParticipationPreapproval) @@ -495,7 +480,6 @@ class ParticipationPreapprovalAdminTest(CourseAdminTestMixin, TestCase): @skipUnless(may_run_expensive_tests(), SKIP_EXPENSIVE_TESTS_REASON) class GradeChangeAdminTest(CourseAdminSessionRelatedMixin, TestCase): - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_grade_change(self): gopp = factories.GradingOpportunityFactory(course=self.course2) @@ -534,7 +518,6 @@ class ExamTicketAdminTest(CourseAdminTestMixin, TestCase): def setUp(self): self.exam = factories.ExamFactory(course=self.course1) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_navigate(self): factories.ExamTicketFactory( exam=self.exam, diff --git a/tests/test_auth.py b/tests/test_auth.py index 88fbad522639d6d82d18358513897852a7c65e2a..86d6332360489bd22dce3a08b0239e3821f797c6 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,5 +1,3 @@ -from __future__ import division - __copyright__ = "Copyright (C) 2018 Dong Zhuang" __license__ = """ @@ -22,8 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six -from six.moves.urllib.parse import ParseResult, quote, urlparse +from urllib.parse import ParseResult, quote, urlparse from djangosaml2.urls import urlpatterns as djsaml2_urlpatterns from django.test import TestCase, override_settings, RequestFactory from django.conf import settings @@ -34,7 +31,6 @@ from django.contrib.auth import ( from django.http import QueryDict, HttpResponse from django.urls import NoReverseMatch, reverse import unittest -from unittest import skipIf from course.auth import ( get_impersonable_user_qset, get_user_model, Saml2Backend, EmailedTokenBackend, @@ -644,7 +640,6 @@ class AuthTestMixin(object): data=data, follow=follow) -@skipIf(six.PY2, "PY2 doesn't support subTest") class AuthViewNamedURLTests(AuthTestMixin, TestCase): need_logout_confirmation_named_urls = [ ('relate-sign_in_choice', [], {}), @@ -746,7 +741,6 @@ class SignInByPasswordTest(CoursesTestMixinBase, self.assertAddMessageCallCount(1) self.assertAddMessageCalledWith(expected_msg) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_security_check(self): self.do_test_security_check(url_name="relate-sign_in_by_user_pw") @@ -1195,7 +1189,7 @@ class UserProfileTest(CoursesTestMixinBase, AuthTestMixin, "%s?%s" % ( url, "&".join(["%s=%s" % (k, v) - for k, v in six.iteritems(query_string_dict)]))) + for k, v in query_string_dict.items()]))) request = self.rf.post(url, data) request.user = self.test_user request.session = mock.MagicMock() @@ -1269,7 +1263,6 @@ class UserProfileTest(CoursesTestMixinBase, AuthTestMixin, form_data = self.generate_profile_form_data(**update_profile_dict) return self.post_profile_by_request_factory(form_data, query_string_dict) - @skipIf(six.PY2, "Python2 doesn't support subTest") def test_update_profile_with_different_settings(self): disabled_inst_id_html_pattern = ( 'print' @@ -776,7 +774,6 @@ content: | class TagProcessingHTMLParserAndLinkFixerTreeprocessorTest( SingleCoursePageSandboxTestBaseMixin, TestCase): - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_embedded_raw_block1(self): another_course = factories.CourseFactory(identifier="another-course") markdown = MARKDOWN_WITH_LINK_FRAGMENT diff --git a/tests/test_enrollment.py b/tests/test_enrollment.py index 0b9e3d7094d01a0c2ae7877eb8688c0df46a28e2..1ac22d1dea86d0c0a9d86f227a86bbda052e66c1 100644 --- a/tests/test_enrollment.py +++ b/tests/test_enrollment.py @@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import unittest from django.test import TestCase, RequestFactory from django.conf import settings @@ -275,7 +274,6 @@ class EnrollViewTest(EnrollmentTestMixin, TestCase): self.assertParticiaptionStatusCallCount([0, 0, 0, 0]) self.assertEqual(len(mail.outbox), 0) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_user_not_active(self): for status in dict(constants.USER_STATUS_CHOICES).keys(): if status != u_status.active: @@ -341,7 +339,6 @@ class EnrollViewTest(EnrollmentTestMixin, TestCase): self.assertParticiaptionStatusCallCount([1, 0, 0, 0]) self.assertEqual(len(mail.outbox), 1) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_coures_not_require_inst_id_verified(self): self.update_require_approval_course( preapproval_require_verified_inst_id=False) diff --git a/tests/test_exam.py b/tests/test_exam.py index b57adc2102056b24d62f002aaf4bd7b0cd9ecabb..8f950ec185b5e3960e97b2ca0b49b99104ad8220 100644 --- a/tests/test_exam.py +++ b/tests/test_exam.py @@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import datetime import pytz @@ -698,7 +697,6 @@ class ExamFacilityMiddlewareTest(SingleCoursePageTestMixin, resp, self.get_view_start_flow_url(self.flow_id), fetch_redirect_response=False) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_ok_views(self): # we only test ta participation from course.auth import make_sign_in_key @@ -780,7 +778,6 @@ class ExamFacilityMiddlewareTest(SingleCoursePageTestMixin, resp = self.select2_get_request(field_id=field_id, term=term) self.assertEqual(resp.status_code, 200) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_ok_issue_exam_ticket_view_with_pperm(self): tup = ( (self.student_participation.user, 302), @@ -861,7 +858,6 @@ class ExamLockdownMiddlewareTest(SingleCoursePageTestMixin, "Error while processing exam lockdown: " "flow session not found.") - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_ok_views(self): # we only test student participation user @@ -941,7 +937,6 @@ class ExamLockdownMiddlewareTest(SingleCoursePageTestMixin, resp = self.select2_get_request(field_id=field_id, term=term) self.assertEqual(resp.status_code, 200) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_flow_page_related_view_ok(self): for url, args, kwargs, code_or_redirect in [ ("relate-view_resume_flow", [], @@ -975,7 +970,6 @@ class ExamLockdownMiddlewareTest(SingleCoursePageTestMixin, resp, code_or_redirect, fetch_redirect_response=False) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_flow_page_related_view_not_ok(self): another_flow_id = "jinja-yaml" self.start_flow(flow_id=another_flow_id) @@ -1020,7 +1014,6 @@ class ExamLockdownMiddlewareTest(SingleCoursePageTestMixin, "RELATE is not currently allowed. " "To exit this exam, log out.") - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_start_flow_ok(self): for url, args, kwargs, code_or_redirect in [ ("relate-view_start_flow", [], @@ -1040,7 +1033,6 @@ class ExamLockdownMiddlewareTest(SingleCoursePageTestMixin, self.assertAddMessageCallCount(0, reset=True) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_start_flow_not_ok(self): another_flow_id = "jinja-yaml" diff --git a/tests/test_flow/test_flow.py b/tests/test_flow/test_flow.py index 70f49e8cedd14ef1b99f5483ce21463d50238c58..cfded35c6b56b19d75a153eaa36717d3b847c824 100644 --- a/tests/test_flow/test_flow.py +++ b/tests/test_flow/test_flow.py @@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import itertools import unittest @@ -3422,7 +3421,6 @@ class GetAndCheckFlowSessionTest(SingleCourseTestMixin, TestCase): student_session) -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class WillReceiveFeedbackTest(unittest.TestCase): # test flow.will_receive_feedback def test_false(self): @@ -3457,7 +3455,6 @@ class WillReceiveFeedbackTest(unittest.TestCase): will_receive) -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class MaySendEmailAboutFlowPageTest(unittest.TestCase): # test flow.may_send_email_about_flow_page @classmethod @@ -3515,7 +3512,6 @@ class MaySendEmailAboutFlowPageTest(unittest.TestCase): may_send) -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class GetPageBehaviorTest(unittest.TestCase): # test flow.get_page_behavior def setUp(self): @@ -4029,7 +4025,6 @@ class AddButtonsToFormTest(unittest.TestCase): self.assertIn("submit", names) self.assertIn("Submit answer for feedback", values) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_not_add_submit_answer_for_feedback_button(self): self.mock_will_receive_feedback.return_value = True @@ -4047,7 +4042,6 @@ class AddButtonsToFormTest(unittest.TestCase): names, values = self.get_form_submit_inputs(form) self.assertNotIn("Submit answer for feedback", values) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_add_save_and_next(self): self.mock_will_receive_feedback.return_value = False @@ -4071,7 +4065,6 @@ class AddButtonsToFormTest(unittest.TestCase): self.assertNotIn("submit", names) self.assertNotIn("save_and_finish", names) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_add_save_and_finish(self): self.mock_will_receive_feedback.return_value = False @@ -4376,7 +4369,6 @@ class ViewFlowPageTest(SingleCourseQuizPageTestMixin, HackRepoMixin, TestCase): self.assertResponseContextEqual(resp, "is_optional_page", True) -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class GetPressedButtonTest(unittest.TestCase): def test_success(self): buttons = ["save", "save_and_next", "save_and_finish", "submit"] @@ -4999,7 +4991,6 @@ class RegradeFlowsViewTest(SingleCourseQuizPageTestMixin, TestCase): self.assertEqual(self.mock_regrade_task.call_count, 0) self.assertEqual(self.mock_redirect.call_count, 0) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_success(self): # get success resp = self.c.get(self.get_regrade_flows_view_url()) @@ -5014,7 +5005,7 @@ class RegradeFlowsViewTest(SingleCourseQuizPageTestMixin, TestCase): "no": False, } for regraded_session_in_progress, inprog_value in ( - six.iteritems(inprog_value_map)): + inprog_value_map.items()): with self.subTest( regraded_session_in_progress=regraded_session_in_progress): self.mock_regrade_task.return_value = mock.MagicMock() diff --git a/tests/test_grades/test_csv.py b/tests/test_grades/test_csv.py index acbe0cdeedb178ae2ad39405645ea2e171ddf1f6..0de5c063df2968d915cfaeb41aa477c68857f2ad 100644 --- a/tests/test_grades/test_csv.py +++ b/tests/test_grades/test_csv.py @@ -22,10 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import csv import os -from six import StringIO +from io import StringIO from django.test import TestCase import unittest @@ -424,9 +423,6 @@ class ImportGradesTest(GradesTestMixin, TestCase): self.assertFormError(resp, "form", "file", expected_file_error_msg) self.assertEqual(models.GradeChange.objects.count(), 0) - @unittest.skipIf( - six.PY2, "csv for py2 seems won't raise expected error when " - "import an excel file.") def test_import_csv_reader_next_error(self): error_msg = "This is a faked error" expected_file_error_msg = ( diff --git a/tests/test_grades/test_generic.py b/tests/test_grades/test_generic.py index 509d765ccf95c1a08738e1d62394be590b133973..8e6b36be2a2afd6737c5bc0f30584d499889ba3d 100644 --- a/tests/test_grades/test_generic.py +++ b/tests/test_grades/test_generic.py @@ -1,5 +1,3 @@ -from __future__ import division - __copyright__ = "Copyright (C) 2017 Zesheng Wang, Andreas Kloeckner, Zhuang Dong" __license__ = """ @@ -22,10 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six from django.urls import reverse, NoReverseMatch from django.test import TestCase -from unittest import skipIf, skipUnless +from unittest import skipUnless from course.models import ( Participation, GradingOpportunity, FlowSession, @@ -419,7 +416,6 @@ class GradePermissionsTests(SingleCoursePageTestMixin, TestCase): self.view_grades_permission(self.instructor_participation.user, status_codes) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_view_grades_ta(self): status_codes = {"default_status_code": 200, "relate-edit_grading_opportunity": 403, diff --git a/tests/test_grades/test_grades.py b/tests/test_grades/test_grades.py index 44ad812386184e70aa0a113f8dd6fdbbf6fbdeaa..31797ed00fd5d3c5bdd88e136d4d039b77d9eefa 100644 --- a/tests/test_grades/test_grades.py +++ b/tests/test_grades/test_grades.py @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six +import io import datetime from django.test import TestCase from django.urls import reverse @@ -228,7 +228,6 @@ class ViewParticipantGradesTest(GradesTestMixin, TestCase): resp = self.get_view_participant_grades(self.student_participation.pk) self.assertEqual(resp.status_code, 200) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_view(self): # {{{ gopps. Notice: there is another gopp created in setUp @@ -738,7 +737,6 @@ class ViewGradesByOpportunityTest(GradesTestMixin, TestCase): another_course_gopp.id)) self.assertEqual(resp.status_code, 400) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_batch_op_no_permission(self): with self.temporarily_switch_to_user(self.ta_participation.user): for op in ["expire", "end", "regrade", "recalculate"]: @@ -761,7 +759,6 @@ class ViewGradesByOpportunityTest(GradesTestMixin, TestCase): self.assertEqual( self.mock_recalculate_ended_sessions.call_count, 0) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_batch_op_no_permission2(self): # with partitial permission permission_ops = [ @@ -805,7 +802,6 @@ class ViewGradesByOpportunityTest(GradesTestMixin, TestCase): self.assertEqual( self.mock_recalculate_ended_sessions.call_count, 0) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_batch_op(self): for op in ["expire", "end", "regrade", "recalculate"]: for rule_tag in [fake_access_rules_tag, grades.RULE_TAG_NONE_STRING]: @@ -1496,7 +1492,6 @@ class ViewSingleGradeTest(GradesTestMixin, TestCase): force_login_instructor=False) self.assertEqual(resp.status_code, 403) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_post_no_pperm(self): another_participation = factories.ParticipationFactory( course=self.course) @@ -1524,7 +1519,6 @@ class ViewSingleGradeTest(GradesTestMixin, TestCase): data={"blablabal": ''}) self.assertEqual(resp.status_code, 400) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_post(self): fs = factories.FlowSessionFactory( participation=self.student_participation, flow_id=self.flow_id) @@ -1556,7 +1550,6 @@ class ViewSingleGradeTest(GradesTestMixin, TestCase): data={"blablabal_%d" % fs.pk: ''}) self.assertEqual(resp.status_code, 400) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_post_keyboard_interrupt(self): fs = factories.FlowSessionFactory( participation=self.student_participation, flow_id=self.flow_id) @@ -1839,7 +1832,7 @@ class DownloadAllSubmissionsTest(SingleCourseQuizPageTestMixin, return "%s/%s" % (group_id, self.page_id) def get_zip_file_buf_from_response(self, resp): - return six.BytesIO(resp.content) + return io.BytesIO(resp.content) def assertDownloadedFileZippedExtensionCount(self, resp, extensions, counts): # noqa @@ -1849,7 +1842,7 @@ class DownloadAllSubmissionsTest(SingleCourseQuizPageTestMixin, prefix, zip_file = resp["Content-Disposition"].split('=') self.assertEqual(prefix, "attachment; filename") self.assertEqual(resp.get('Content-Type'), "application/zip") - buf = six.BytesIO(resp.content) + buf = io.BytesIO(resp.content) import zipfile with zipfile.ZipFile(buf, 'r') as zf: self.assertIsNone(zf.testzip()) @@ -1894,9 +1887,6 @@ class DownloadAllSubmissionsTest(SingleCourseQuizPageTestMixin, self.assertDownloadedFileZippedExtensionCount( resp, [".txt"], [2]) - # Fixme - @unittest.skipIf(six.PY2, "'utf8' codec can't decode byte 0x99 in " - "position 10: invalid start byte") def test_download_include_feedback(self): with self.temporarily_switch_to_user(self.instructor_participation.user): resp = self.post_download_all_submissions_by_group_page_id( diff --git a/tests/test_misc.py b/tests/test_misc.py index 3dea98c9b5b329fe954d093bc23a6cc2dc13bd5c..f8b20187488478cf6ab5baa32e18c60ec7786402 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import re import unittest import datetime @@ -311,8 +310,7 @@ class GetCurrentLanguageJsLangNameTest(TestCase): msg = ("'get_current_js_lang_name' requires 'as variable' " "(got [u'get_current_js_lang_name'])") - if six.PY3: - msg = msg.replace("u'", "'") + msg = msg.replace("u'", "'") with self.assertRaisesMessage(TemplateSyntaxError, expected_message=msg): self.engines["django"].from_string( @@ -321,8 +319,7 @@ class GetCurrentLanguageJsLangNameTest(TestCase): msg = ("'get_current_js_lang_name' requires 'as variable' " "(got [u'get_current_js_lang_name', u'AS', u'LANG'])") - if six.PY3: - msg = msg.replace("u'", "'") + msg = msg.replace("u'", "'") with self.assertRaisesMessage(TemplateSyntaxError, expected_message=msg): self.engines["django"].from_string( diff --git a/tests/test_models.py b/tests/test_models.py index d5938ec38af4c2fb6ce5288674ac86f4938b9920..1a04b53dcdb42b587da3f209702d44b9d4461c9e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,3 @@ -from __future__ import division - __doc__ = """ This is testing uncovering part of course.models by other tests """ @@ -27,7 +25,6 @@ THE SOFTWARE. """ from datetime import datetime, timedelta -import six import unittest import pytz @@ -329,7 +326,7 @@ class ParticipationTest(RelateModelTestMixin, unittest.TestCase): participation2 = factories.ParticipationFactory(course=course2, user=user) - self.assertIsInstance(participation1.get_role_desc(), six.text_type) + self.assertIsInstance(participation1.get_role_desc(), str) self.assertEqual( participation1.get_role_desc(), participation2.get_role_desc()) diff --git a/tests/test_pages/test_base.py b/tests/test_pages/test_base.py index e3dbc2b5314c042966493f1ad3bdb93ac6aca782..39178eb710aeb47ec8c6f3e0490212bd822695e0 100644 --- a/tests/test_pages/test_base.py +++ b/tests/test_pages/test_base.py @@ -1,5 +1,3 @@ -from __future__ import division - __copyright__ = "Copyright (C) 2018 Dong Zhuang" __license__ = """ @@ -22,7 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six from django.test import TestCase import unittest @@ -138,7 +135,6 @@ choices: """ -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class CreateDefaultPointScaleTest(unittest.TestCase): # test create_default_point_scale def test_create_default_point_scale(self): @@ -150,7 +146,7 @@ class CreateDefaultPointScaleTest(unittest.TestCase): 15: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 70: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70], } - for k, v in six.iteritems(test_dict): + for k, v in test_dict.items(): with self.subTest(total_points=k): returned_value = create_default_point_scale(k) self.assertIsNotNone(returned_value) @@ -245,7 +241,6 @@ class PageBasePageDescBackwardCompatibilityTest(unittest.TestCase): self.fail("'%s' is not warned as expected" % expected_warn_msg) -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class PageBaseGetModifiedPermissionsForPageTest(unittest.TestCase): # test page_base.get_modified_permissions_for_page def test_get_modified_permissions_for_page(self): diff --git a/tests/test_pages/test_code.py b/tests/test_pages/test_code.py index 463b7117b024f59e84799ef0cac4a8f0b93acc2f..4d891ea71f3a6a9eb7c56b396c6ac4613363020a 100644 --- a/tests/test_pages/test_code.py +++ b/tests/test_pages/test_code.py @@ -1,5 +1,3 @@ -from __future__ import division - __copyright__ = "Copyright (C) 2018 Dong Zhuang" __license__ = """ @@ -22,9 +20,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six +import io import unittest -from unittest import skipIf from django.test import TestCase, override_settings, RequestFactory from docker.errors import APIError as DockerAPIError @@ -526,14 +523,14 @@ class CodeQuestionTest(SingleCoursePageSandboxTestBaseMixin, answer_data={"answer": ['c = 1 + 2\r']}) if expected_msgs is not None: - if isinstance(expected_msgs, six.text_type): + if isinstance(expected_msgs, str): expected_msgs = [expected_msgs] for msg in expected_msgs: self.assertResponseContextAnswerFeedbackContainsFeedback( resp, msg, html=in_html) if not_expected_msgs is not None: - if isinstance(not_expected_msgs, six.text_type): + if isinstance(not_expected_msgs, str): not_expected_msgs = [not_expected_msgs] for msg in not_expected_msgs: self.assertResponseContextAnswerFeedbackNotContainsFeedback( @@ -1034,7 +1031,6 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): self.assertEqual(mock_create_ctn.call_count, 1) self.assertIn(my_image, mock_create_ctn.call_args[0]) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_docker_container_ping_failure(self): with ( mock.patch("docker.client.Client.create_container")) as mock_create_ctn, ( # noqa @@ -1042,7 +1038,7 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): mock.patch("docker.client.Client.logs")) as mock_ctn_logs, ( mock.patch("docker.client.Client.remove_container")) as mock_remove_ctn, ( # noqa mock.patch("docker.client.Client.inspect_container")) as mock_inpect_ctn, ( # noqa - mock.patch("six.moves.http_client.HTTPConnection.request")) as mock_ctn_request: # noqa + mock.patch("http.client.HTTPConnection.request")) as mock_ctn_request: # noqa mock_create_ctn.return_value = {"Id": "someid"} mock_ctn_start.side_effect = lambda x: None @@ -1059,7 +1055,7 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): }} with self.subTest(case="Docker ping timeout with BadStatusLine Error"): - from six.moves.http_client import BadStatusLine + from http.client import BadStatusLine fake_bad_statusline_msg = "my custom bad status" mock_ctn_request.side_effect = BadStatusLine(fake_bad_statusline_msg) @@ -1181,7 +1177,6 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): self.assertNotIn(DockerAPIError.__name__, res["traceback"]) self.assertNotIn(fake_response_content, res["traceback"]) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_docker_container_ping_return_not_ok(self): with ( mock.patch("docker.client.Client.create_container")) as mock_create_ctn, ( # noqa @@ -1189,8 +1184,8 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): mock.patch("docker.client.Client.logs")) as mock_ctn_logs, ( mock.patch("docker.client.Client.remove_container")) as mock_remove_ctn, ( # noqa mock.patch("docker.client.Client.inspect_container")) as mock_inpect_ctn, ( # noqa - mock.patch("six.moves.http_client.HTTPConnection.request")) as mock_ctn_request, ( # noqa - mock.patch("six.moves.http_client.HTTPConnection.getresponse")) as mock_ctn_get_response: # noqa + mock.patch("http.client.HTTPConnection.request")) as mock_ctn_request, ( # noqa + mock.patch("http.client.HTTPConnection.getresponse")) as mock_ctn_get_response: # noqa mock_create_ctn.return_value = {"Id": "someid"} mock_ctn_start.side_effect = lambda x: None @@ -1211,7 +1206,7 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): with self.subTest( case="Docker ping response not OK"): mock_ctn_request.side_effect = lambda x, y: None - mock_ctn_get_response.return_value = six.BytesIO(b"NOT OK") + mock_ctn_get_response.return_value = io.BytesIO(b"NOT OK") res = request_run_with_retries( run_req={}, run_timeout=0.1, retry_count=0) @@ -1221,7 +1216,6 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): self.assertEqual(res["exec_host"], fake_host_ip) self.assertIn(InvalidPingResponse.__name__, res["traceback"]) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_docker_container_runpy_timeout(self): with ( mock.patch("docker.client.Client.create_container")) as mock_create_ctn, ( # noqa @@ -1229,8 +1223,8 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): mock.patch("docker.client.Client.logs")) as mock_ctn_logs, ( mock.patch("docker.client.Client.remove_container")) as mock_remove_ctn, ( # noqa mock.patch("docker.client.Client.inspect_container")) as mock_inpect_ctn, ( # noqa - mock.patch("six.moves.http_client.HTTPConnection.request")) as mock_ctn_request, ( # noqa - mock.patch("six.moves.http_client.HTTPConnection.getresponse")) as mock_ctn_get_response: # noqa + mock.patch("http.client.HTTPConnection.request")) as mock_ctn_request, ( # noqa + mock.patch("http.client.HTTPConnection.getresponse")) as mock_ctn_get_response: # noqa mock_create_ctn.return_value = {"Id": "someid"} mock_ctn_start.side_effect = lambda x: None @@ -1251,14 +1245,13 @@ class RequestPythonRunWithRetriesTest(unittest.TestCase): # first request is ping, second request raise socket.timeout mock_ctn_request.side_effect = [None, sock_timeout] - mock_ctn_get_response.return_value = six.BytesIO(b"OK") + mock_ctn_get_response.return_value = io.BytesIO(b"OK") res = request_run_with_retries( run_req={}, run_timeout=0.1, retry_count=0) self.assertEqual(res["result"], "timeout") self.assertEqual(res["exec_host"], fake_host_ip) - @skipIf(six.PY2, "PY2 doesn't support subTest") def test_docker_container_runpy_retries_count(self): with ( mock.patch("course.page.code.request_run")) as mock_req_run, ( # noqa diff --git a/tests/test_pages/test_generic.py b/tests/test_pages/test_generic.py index 30fa66e2db78cf85cd5a8b71068e0726f5ea2629..71657c3b862eaf248e4939d91688a74c58201af3 100644 --- a/tests/test_pages/test_generic.py +++ b/tests/test_pages/test_generic.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import division - __copyright__ = "Copyright (C) 2014 Andreas Kloeckner, Zesheng Wang, Dong Zhuang" __license__ = """ @@ -24,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six from base64 import b64encode import unittest @@ -72,7 +69,7 @@ class SingleCourseQuizPageTest(SingleCourseQuizPageTestMixin, self.assertEqual(int(params["page_ordinal"]), page_count - 1) # {{{ auto graded questions - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") + def test_quiz_no_answer(self): self.assertEqual(self.end_flow().status_code, 200) self.assertSessionScoreEqual(0) diff --git a/tests/test_pages/test_text.py b/tests/test_pages/test_text.py index 96629d581c8369fd652945c3d44da0274118636d..499380402cf4ae51299c65697abeb53fc8a5bb7b 100644 --- a/tests/test_pages/test_text.py +++ b/tests/test_pages/test_text.py @@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import sys from django.test import TestCase from django import forms @@ -210,15 +209,9 @@ class MatcherTest(unittest.TestCase): def test_regex_matcher(self): # test RegexMatcher failed_pattern = "[\n" - if six.PY2: - expected_error_msg = ( - "regex '[\n' did not compile: error: " - "unexpected end of regular expression" - ) - else: - expected_error_msg = ( - "regex '[\n' did not compile: error: " - "unterminated character set at position 0 (line 1, column 1)") + expected_error_msg = ( + "regex '[\n' did not compile: error: " + "unterminated character set at position 0 (line 1, column 1)") with self.assertRaises(ValidationError) as cm: RegexMatcher(None, "", failed_pattern) self.assertIn(expected_error_msg, str(cm.exception)) @@ -233,15 +226,9 @@ class MatcherTest(unittest.TestCase): def test_case_sensitive_regex_matcher(self): # test CaseSensitiveRegexMatcher failed_pattern = "[\n" - if six.PY2: - expected_error_msg = ( - "regex '[\n' did not compile: error: " - "unexpected end of regular expression" - ) - else: - expected_error_msg = ( - "regex '[\n' did not compile: error: " - "unterminated character set at position 0 (line 1, column 1)") + expected_error_msg = ( + "regex '[\n' did not compile: error: " + "unterminated character set at position 0 (line 1, column 1)") with self.assertRaises(ValidationError) as cm: CaseSensitiveRegexMatcher(None, "", failed_pattern) self.assertIn(expected_error_msg, str(cm.exception)) @@ -310,10 +297,7 @@ class FloatOrSympyEvalfTest(unittest.TestCase): def test_float_or_sympy_evalf(self): # long int - if six.PY2: - long_int = sys.maxint + 1 - else: - long_int = sys.maxsize + 1 + long_int = sys.maxsize + 1 self.assertEqual(float_or_sympy_evalf(long_int), long_int) self.assertEqual(float_or_sympy_evalf(1), 1) diff --git a/tests/test_utils.py b/tests/test_utils.py index 5931627ef767b697375bf69e749beb2107964db6..4f50b3368219490e7374939d458e5401a08798d4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six from datetime import datetime from copy import deepcopy @@ -294,14 +293,14 @@ class LanguageOverrideTest(SingleCoursePageTestMixin, self.course.force_lang = course_force_lang self.course.save() - for page_id, v in six.iteritems(self.page_id_literal_dict): + for page_id, v in self.page_id_literal_dict.items(): if "answer" not in v: continue self.post_answer_by_page_id(page_id, answer_data=v["answer"]) self.end_flow() - for page_id, v in six.iteritems(self.page_id_literal_dict): + for page_id, v in self.page_id_literal_dict.items(): with self.subTest(page_id=page_id, course_force_lang=course_force_lang): resp = self.c.get(self.get_page_url_by_page_id(page_id)) for literal in v["literals"]: @@ -729,7 +728,6 @@ class GetFlowRulesTest(SingleCourseTestMixin, TestCase): self.assertListEqual(result, default_rules_desc) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_rules_with_given_kind(self): # use real rules @@ -751,7 +749,6 @@ class GetFlowRulesTest(SingleCourseTestMixin, TestCase): # there are existing rule for those kind self.assertNotEqual(result, default_rules_desc) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_rules_with_no_given_kind(self): flow_desc_dict = self.get_hacked_flow_desc(as_dict=True) diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 4fcf81b71357606dc22b625aa0c725f395d747a3..2a36127eacac9af815456b1165a3becaf74bfc0e 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six from copy import deepcopy import unittest from django.test import TestCase, RequestFactory @@ -324,11 +323,7 @@ class ParamikoSSHVendorTest(unittest.TestCase): self.assertIn(expected_error_msg, str(cm.exception)) - if six.PY2: - exception = IOError - else: - exception = FileNotFoundError - with self.assertRaises(exception) as cm: + with self.assertRaises(FileNotFoundError) as cm: self.ssh_vendor.run_command( host="github.com", command="git-upload-pack '/bar/baz'", @@ -716,7 +711,6 @@ class RunCourseUpdateCommandTest(MockAddMessageMixing, unittest.TestCase): for course in Course.objects.all(): course.delete() - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_is_ancestor_commit_checked(self): may_update = True prevent_discarding_revisions = True @@ -769,7 +763,6 @@ class RunCourseUpdateCommandTest(MockAddMessageMixing, unittest.TestCase): "'is_ancestor_commit' is not expected for command '%s' to " "be called while called" % command) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_is_content_validated(self): may_update = True @@ -888,7 +881,6 @@ class RunCourseUpdateCommandTest(MockAddMessageMixing, unittest.TestCase): ) self.assertIsNone(self.participation.preview_git_commit_sha) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_fetch_not_prevent_discarding_revisions(self): self.mock_client.fetch.return_value = { b"HEAD": self.default_lastest_sha.encode()} @@ -1034,7 +1026,6 @@ class RunCourseUpdateCommandTest(MockAddMessageMixing, unittest.TestCase): self.participation.preview_git_commit_sha, self.default_preview_sha) self.assertEqual(self.course.active_git_commit_sha, self.default_old_sha) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_fetch_not_may_update(self): self.mock_client.fetch.return_value = { b"HEAD": self.default_lastest_sha.encode()} diff --git a/tests/test_views.py b/tests/test_views.py index d52db7cd633c9b937a45f58caf29f2d0c0a74d98..8910e20552ceef96326b22b47a70a551035cf2b8 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import six import datetime import unittest from celery import states, uuid @@ -704,7 +703,6 @@ class GetRepoFileTestMixin(SingleCourseTestMixin): self.get_current_repo_file_url(path, course_identifier)) -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class GetRepoFileTest(GetRepoFileTestMixin, TestCase): # test views.get_repo_file def test_file_not_exist(self): @@ -773,7 +771,6 @@ class GetRepoFileTest(GetRepoFileTestMixin, TestCase): self.assertEqual(resp["Content-Type"], content_type) -@unittest.skipIf(six.PY2, "PY2 doesn't support subTest") class GetRepoFileTestMocked(GetRepoFileTestMixin, HackRepoMixin, TestCase): """ Test views.get_repo_file, with get_repo_blob mocked as class level, @@ -1847,7 +1844,6 @@ class GrantExceptionStage3Test(GrantExceptionTestMixin, TestCase): exc_rule = models.FlowRuleException.objects.last().rule self.assertEqual(exc_rule["if_has_tag"], another_fs_tag) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_access_permissions_created(self): # ensure all flow permission is in the form, and will be # saved to the FlowRuleException permissions @@ -1876,7 +1872,6 @@ class GrantExceptionStage3Test(GrantExceptionTestMixin, TestCase): self.assertSetEqual( set(exc_rule["permissions"]), set(permissions)) - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_access_expires_created(self): expiration_time = as_local_time(now() + timedelta(days=3)) resp = self.post_grant_exception_stage_3_view( @@ -2081,7 +2076,6 @@ class GrantExceptionStage3Test(GrantExceptionTestMixin, TestCase): self.assertAddMessageCallCount(1) self.assertAddMessageCalledWith("'Grading' exception granted to ") - @unittest.skipIf(six.PY2, "PY2 doesn't support subTest") def test_grading_rule_item_set(self): from random import random items = ["credit_percent", "bonus_points", "max_points", diff --git a/tests/utils.py b/tests/utils.py index 760e4639f6fb74f2e25f5492593d6af66108d604..999402c721639bcc4d9f94e6af7dcea07fc83012 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,5 +1,3 @@ -from __future__ import division - import sys try: from importlib import reload @@ -7,8 +5,7 @@ except ImportError: pass # PY2 import os from importlib import import_module -import six -from six import StringIO +from io import StringIO from functools import wraps from django.urls import clear_url_caches @@ -185,9 +182,6 @@ SKIP_NON_PSQL_REASON = "PostgreSQL specific SQL used" def may_run_expensive_tests(): - if six.PY2: - return False - # Allow run expensive tests locally, i.e., CI not detected. if not any([os.getenv(ci) for ci in ["RL_CI_TEST", "GITLAB_CI", "APPVEYOR"]]):