Skip to content

Commit 25617fe

Browse files
committed
(squashed) Improve compatibility with python3
This commit contains multiple changes that improve the compatibility with python3.7. In particular: - bumps sioworkers version to 1.4 (without Celery, with pinned dependencies, and some minor changes) - pins dependencies versions - migrates from slate+pdfminer+pdfminer3k to pdfminer.six for parsing pdf files - resolves (10s of) unidecode warnings on python2 - makes enum34 a python2-only dependency - migrates to badochov/djsupervisor - a django-supervisor fork that supports python3 and other changes, mainly related to bytes/str differences between python2 and python3. The complete log of squashed commits: commit 167f856 Author: Jakub Molinski <[email protected]> Date: Tue Mar 23 10:51:03 2021 +0100 Pin sioworkers version Change-Id: I821baeafc232248ed008e3a16622f3940912c411 commit 88f05ff Author: Jakub Molinski <[email protected]> Date: Mon Mar 22 19:44:04 2021 +0100 Update WIP comments also isort & black Change-Id: I7513d2903fbaf039e585f52c2de0e7c9e3dad011 commit db0a01f Author: Hubert Badocha <[email protected]> Date: Mon Mar 22 20:27:24 2021 +0100 Limited dependencies versions. Pinned django-supervisor. Limited to last version supporting python2: - pygments - shortuuid. Limited to newest version rest of packages. Change-Id: I786221b13ef996e51a7af30e392b26518f01a75a commit e4d3653 Author: Jakub Molinski <[email protected]> Date: Mon Mar 22 19:20:40 2021 +0100 Migrate to sioworkers version 1.4 - bumped required version to 1.4 - fixed celery configuration in default_settings.py Change-Id: Ic7b03ffca5f0142fcedca968383ee4c3d0d20956 commit 055a09c Author: Jakub Molinski <[email protected]> Date: Sun Mar 14 14:26:25 2021 +0100 Reenable mail tests Change-Id: I26f92a611d7167a021c49ddc99213acbd33bef7b commit 5fd3203 Author: Jakub Molinski <[email protected]> Date: Sun Mar 14 15:34:03 2021 +0100 Resolve unicode warings Change-Id: I3331d6bb140f9164a89702edff71303337ef007f commit f20546b Author: Hubert Badocha <[email protected]> Date: Sat Mar 13 22:44:44 2021 +0100 Readded pylint directive. Moved enum34 to python2 specific packages. Change-Id: Icd910407956195047f667ffe74936febeeba5b18 commit 3218993 Author: Hubert Badocha <[email protected]> Date: Sat Mar 13 19:14:04 2021 +0100 Removed deprecated django import. Fixed pylint issues in programs/test.py. Reenabled treating warnings as errors in pylint. Change-Id: I74de9904ca3a0e819fd54bec7cb001e235b73656 commit 2c41d24 Author: Jakub Molinski <[email protected]> Date: Sat Mar 13 01:01:13 2021 +0100 Fix sending binary data in test client Test client accepts the value as string and in python3 .decode changes type from str to bytes, so the decoding needs to be done conditionally. Change-Id: I0b9d3b594cddecd4960d21f2fa286327d7d940d8 commit d3e3a36 Author: Jakub Molinski <[email protected]> Date: Fri Mar 12 20:05:48 2021 +0100 Change slate to pdfminer.six for parsing pdf Slate and its dependencies do not support python2 and python3 very well. I have replaced slate, slate3k, pdfminer and pdfminer3k dependencies with a single dependency on pdfminer.six. Change-Id: I59b2e42a74fe971e566bc504b1c8e1e219209e07 commit 70eaf4b Author: Jakub Molinski <[email protected]> Date: Fri Mar 12 14:55:24 2021 +0100 Fix detecting memory limit from package Change-Id: I12283833a442389058495df21af7dbfc1842a6d8 commit 85eece1 Author: Hubert Badocha <[email protected]> Date: Fri Mar 12 14:00:56 2021 +0100 Fixed problems apps. Investigated evalmgr tests failures. Change-Id: I1b9f976b4e4773e1acaa65564425c78f2bb0754d commit 454849e Author: Jakub Molinski <[email protected]> Date: Fri Mar 12 14:05:07 2021 +0100 Fix text type problem in pa, checking dict keys presence in statistics Change-Id: I66a789ade08c213a7e3c3ecb78b5245d802ecafd commit 282b3c3 Author: Jakub Molinski <[email protected]> Date: Fri Mar 12 13:14:28 2021 +0100 Pin some packages version so that tests use same env on py2 and py3 Change-Id: I5bca96d05246b8b60232caa242bf6c801bf65c8d commit ef8f869 Author: Jakub Molinski <[email protected]> Date: Fri Mar 12 13:10:47 2021 +0100 Make sure input passed to pipe is binary Converts data passed to popen to binary (six.binary_type). Temporarily comments-out mail tests. Change-Id: Ib846c98f363f16545d57a70997e4bb9fcc6ba40f commit 6992426 Author: Hubert Badocha <[email protected]> Date: Fri Mar 12 00:26:17 2021 +0100 Changed djsupervisor and pytest.ini setting to be able to run test. Fixed tests from problems. Investigated failing tests from oireports and statistics. Change-Id: I3488b6d02560a9a864c56936d0edacdb5d283650 commit 06e3c75 Author: Jakub Molinski <[email protected]> Date: Thu Mar 11 21:48:58 2021 +0100 Change djsupervisor source repo in requirements_py3 Change-Id: I6749de4abfc96e597159cc3b90f63157df1b3fab Change-Id: I26959bf871c9c761e7fb971d44966d7e7b9305c4
1 parent 4298e2a commit 25617fe

File tree

23 files changed

+187
-140
lines changed

23 files changed

+187
-140
lines changed

oioioi/base/utils/execute.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import string
33
import subprocess
44

5+
import six
6+
57
# Reliable quoting, taken as-is from `pipes` standard module.
68
# There is a slight chance it may work on non-Unix platforms, but
79
# I wouldn't count on it. Either way tests will show.
@@ -94,7 +96,7 @@ def set_cwd():
9496
preexec_fn=set_cwd,
9597
)
9698

97-
stdout, _ = p.communicate(stdin)
99+
stdout, _ = p.communicate(six.ensure_binary(stdin))
98100
rc = p.returncode
99101

100102
if split_lines:

oioioi/base/utils/pdf.py

+43-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
1-
# pylint: disable=dangerous-default-value
21
import codecs
2+
import io
33
import os.path
44
import shutil
55
import tempfile
66

7+
import pdfminer.layout
8+
import six
79
from django.core.files.base import File
10+
from pdfminer.converter import TextConverter
11+
from pdfminer.pdfinterp import PDFPageInterpreter, PDFResourceManager
12+
from pdfminer.pdfpage import PDFPage
813
from six.moves import range
914

1015
from oioioi.base.utils.execute import execute
1116
from oioioi.filetracker.utils import stream_file
1217

1318

14-
def generate_pdf(tex_code, filename, extra_args=[], num_passes=3):
19+
def generate_pdf(tex_code, filename, extra_args=None, num_passes=3):
20+
if extra_args is None:
21+
extra_args = []
22+
1523
# Create temporary file and folder
1624
tmp_folder = tempfile.mkdtemp()
1725
try:
@@ -28,7 +36,39 @@ def generate_pdf(tex_code, filename, extra_args=[], num_passes=3):
2836
execute(command, cwd=tmp_folder)
2937

3038
# Get PDF file contents
31-
pdf_file = open(os.path.splitext(tex_path)[0] + '.pdf')
39+
pdf_file = io.open(os.path.splitext(tex_path)[0] + '.pdf', "rb")
3240
return stream_file(File(pdf_file), filename)
3341
finally:
3442
shutil.rmtree(tmp_folder)
43+
44+
45+
def extract_text_from_pdf(pdf_file):
46+
# pdf_file must be a a file-like object
47+
# returns a list of strings, each string containing text from one page
48+
49+
# the char_margin is needed because pdfminer.six has a problem
50+
# that causes lines with big spacing between text blocks to be split into
51+
# many lines, sometimes out-of-reasonable-orded
52+
# the value needs to be high enough so that char_width * char_margin > page_width
53+
54+
laparams = pdfminer.layout.LAParams(char_margin=2000)
55+
56+
pages = []
57+
output_string = six.BytesIO()
58+
rsrcmgr = PDFResourceManager()
59+
device = TextConverter(rsrcmgr, output_string, codec='latin-1', laparams=laparams)
60+
interpreter = PDFPageInterpreter(rsrcmgr, device)
61+
62+
for page in PDFPage.get_pages(
63+
pdf_file,
64+
None,
65+
maxpages=0,
66+
password='',
67+
caching=False,
68+
check_extractable=True,
69+
):
70+
interpreter.process_page(page)
71+
pages.append(output_string.getvalue())
72+
73+
output_string.close()
74+
return pages

oioioi/contests/tests/tests.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -2851,9 +2851,7 @@ def setUp(self):
28512851

28522852
def test_submission_view_without_contest(self):
28532853
submission = Submission.objects.get(id=1)
2854-
response = self.client.get(
2855-
reverse('submission', kwargs={'submission_id': 1})
2856-
)
2854+
response = self.client.get(reverse('submission', kwargs={'submission_id': 1}))
28572855
self.assertEqual(response.status_code, 200)
28582856

28592857
# Submit another button

oioioi/default_settings.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# pylint: disable=wildcard-import
21
import sys
32

43
from oioioi.base.utils.finders import find_executable_path
@@ -497,22 +496,23 @@
497496

498497
# Celery configuration
499498

500-
from sio.celery.default_config import *
499+
CELERY_QUEUES = {}
500+
CELERY_RESULT_BACKEND = 'amqp'
501+
CELERY_ACKS_LATE = True
502+
CELERY_SEND_EVENTS = True
501503

502504
BROKER_URL = 'sqla+sqlite:///' + os.path.join(tempfile.gettempdir(),
503505
'celerydb.sqlite')
504506

505-
# pylint: disable=undefined-variable
506-
507-
CELERY_IMPORTS += [
507+
CELERY_IMPORTS = [
508508
'oioioi.evalmgr.tasks',
509509
'oioioi.problems.unpackmgr',
510510
]
511511

512-
CELERY_ROUTES.update({
512+
CELERY_ROUTES = {
513513
'oioioi.evalmgr.tasks.evalmgr_job': dict(queue='evalmgr'),
514514
'oioioi.problems.unpackmgr.unpackmgr_job': dict(queue='unpackmgr'),
515-
})
515+
}
516516

517517
# Number of concurrently evaluated submissions
518518
EVALMGR_CONCURRENCY = 1

oioioi/disqualification/admin.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
from django.db import models
2-
from django.forms import Textarea
31
from django.contrib.auth.models import User
42
from django.core.urlresolvers import reverse
3+
from django.db import models
4+
from django.forms import Textarea
55
from django.utils.encoding import force_text
66
from django.utils.translation import ugettext_lazy as _
77

88
from oioioi.base import admin
99
from oioioi.base.utils import make_html_link
10-
from oioioi.contests.admin import contest_site, ContestAdmin
10+
from oioioi.contests.admin import ContestAdmin, contest_site
1111
from oioioi.contests.menu import contest_admin_menu_registry
1212
from oioioi.contests.models import Submission
1313
from oioioi.contests.utils import is_contest_admin

oioioi/oireports/tests.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
from datetime import datetime # pylint: disable=E0611
22

3-
import six
3+
from six import BytesIO
44
from django import VERSION as DJANGO_VERSION
55
from django.contrib.auth.models import User
66
from django.core.urlresolvers import reverse
77
from django.utils.timezone import utc
8-
from six import BytesIO
9-
10-
if six.PY2:
11-
import slate
12-
else:
13-
import slate3k as slate
148

159
from oioioi.base.tests import TestCase, fake_time
10+
from oioioi.base.utils.pdf import extract_text_from_pdf
1611
from oioioi.contests.models import Contest
1712
from oioioi.filetracker.tests import TestStreamingMixin
1813
from oioioi.oireports.views import CONTEST_REPORT_KEY
@@ -58,12 +53,15 @@ def test_pdf_report_view(self):
5853
self.assertTrue(self.client.login(username='test_admin'))
5954
with fake_time(datetime(2015, 8, 5, tzinfo=utc)):
6055
response = self.client.post(url, post_vars)
61-
pages = slate.PDF(BytesIO(self.streamingContent(response)))
62-
self.assertIn("test_user", pages[0])
63-
self.assertIn("Wynik:34", pages[0])
64-
self.assertIn("ZAD1", pages[0])
65-
self.assertIn("1bRuntimeerror0.00s/0.10sprogramexited", pages[0])
66-
self.assertNotIn("test_user2", pages.text())
56+
57+
pages = extract_text_from_pdf(BytesIO(self.streamingContent(response)))
58+
self.assertIn(b"test_user", pages[0])
59+
self.assertIn(b"Wynik:34", pages[0].replace(b' ', b''))
60+
self.assertIn(b"ZAD1", pages[0])
61+
self.assertIn(
62+
b"1bRuntimeerror0.00s/0.10sprogramexited", pages[0].replace(b' ', b'')
63+
)
64+
self.assertTrue(all(b"test_user2" not in page for page in pages))
6765

6866
def test_xml_view(self):
6967
contest = Contest.objects.get()

oioioi/pa/score.py

+3
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ def __unicode__(self):
9191
def __repr__(self):
9292
return "PAScore(%r, %r)" % (self.points, self.distribution)
9393

94+
def __str__(self):
95+
return str(self.points)
96+
9497
@classmethod
9598
def _from_repr(cls, value):
9699
points, distribution = value.split(';')

oioioi/pa/tests.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import re
2-
import urllib
32
from datetime import datetime # pylint: disable=E0611
43

54
import six
@@ -225,7 +224,7 @@ def check_order(response, expected):
225224
# 28 (10, 8, 6, 4), 28 (9, 9, 7, 3), 10 (10)
226225
response = self.client.get(self._ranking_url(A_PLUS_B_RANKING_KEY))
227226
check_order(response, [b'Test User', b'Test User 2', b'Test User 3'])
228-
self.assertContains(response, '28</td>')
227+
self.assertContains(response, b'28</td>')
229228

230229
# 10 (10), 10 (7, 3), 10 (6, 4)
231230
response = self.client.get(self._ranking_url(B_RANKING_KEY))

oioioi/printing/pdf.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22

3+
import six
34
from django.conf import settings
45
from django.utils.translation import ugettext_lazy as _
56
from fpdf import FPDF
@@ -111,4 +112,4 @@ def generator(source, header):
111112
pdf.multi_cell(
112113
w=pdf.column_width, h=pdf.cell_height, txt=source, border=0, align='L'
113114
)
114-
return pdf.output(dest='S')
115+
return six.ensure_binary(pdf.output(dest='S'), 'latin-1')

oioioi/printing/tests.py

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import six
1+
from six import BytesIO
22
from django.core.files.base import ContentFile
33
from django.core.urlresolvers import reverse
44
from django.test.utils import override_settings
5-
from six import StringIO
6-
7-
if six.PY2:
8-
import slate
9-
else:
10-
import slate3k as slate
115

126
from oioioi.base.tests import TestCase
7+
from oioioi.base.utils.pdf import extract_text_from_pdf
138
from oioioi.contests.controllers import ContestController
149
from oioioi.contests.models import Contest
1510
from oioioi.printing.pdf import generator
@@ -37,11 +32,11 @@ def can_print_files(self, request):
3732

3833
class TestPDFGenerator(TestCase):
3934
def test_pdf_generation(self):
40-
pdf = StringIO(generator(source=SAMPLE_TEXT, header='header'))
41-
text = slate.PDF(pdf)
35+
pdf = BytesIO(generator(source=SAMPLE_TEXT, header='header'))
36+
text = extract_text_from_pdf(pdf)
4237
self.assertEqual(9, len(text))
43-
self.assertIn('Lorem ipsum dolor', text[0])
44-
self.assertIn('Sed egestas dui tellus', text[4])
38+
self.assertIn(b'Lorem ipsum dolor', text[0])
39+
self.assertIn(b'Sed egestas dui tellus', text[4])
4540

4641

4742
class TestPrintingView(TestCase):

oioioi/problems/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def __str__(self):
161161
}
162162

163163
def save(self, *args, **kwargs):
164-
self.ascii_name = unidecode(self.name)
164+
self.ascii_name = unidecode(six.text_type(self.name))
165165
super(Problem, self).save(*args, **kwargs)
166166

167167

oioioi/problems/tests.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os.path
44
from datetime import datetime # pylint: disable=E0611
5+
from functools import cmp_to_key
56

67
import pytest
78
import six.moves.urllib.parse
@@ -1993,14 +1994,29 @@ def _get_table_contents(self, html):
19931994
pos = pos2 + len('</td>')
19941995
return rows
19951996

1997+
@staticmethod
1998+
def _cmp_str_with_none(key_fn):
1999+
def _cmp(a, b):
2000+
key_a = key_fn(a)
2001+
key_b = key_fn(b)
2002+
if key_a is None:
2003+
return True
2004+
if key_b is None:
2005+
return False
2006+
return key_a < key_b
2007+
2008+
return _cmp
2009+
19962010
def _assert_rows_sorted(self, rows, order_by=0, desc=False):
19972011
# Nones should be treated as if they were less than zeroes
19982012
# (i.e. listed last when desc=True and listed first otherwise).
1999-
denullified_rows = [map(lambda x: -1 if x is None else x, row) for row in rows]
2000-
20012013
self.assertEqual(
2002-
denullified_rows,
2003-
sorted(denullified_rows, key=lambda x: x[order_by], reverse=desc),
2014+
rows,
2015+
sorted(
2016+
rows,
2017+
key=cmp_to_key(self._cmp_str_with_none(lambda x: x[order_by])),
2018+
reverse=desc,
2019+
),
20042020
)
20052021

20062022
def test_statistics_problem_list(self):

oioioi/problems/views.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def filter_problems_by_origin(problems, origintags):
246246

247247

248248
def search_problems_in_problemset(datadict):
249-
query = unidecode(datadict.get('q', ''))
249+
query = unidecode(six.text_type(datadict.get('q', '')))
250250
tags = datadict.getlist('tag')
251251
algorithmtags = datadict.getlist('algorithm')
252252
difficultytags = datadict.getlist('difficulty')
@@ -1137,12 +1137,11 @@ def get_search_hints_view(request, view_type):
11371137
raise PermissionDenied
11381138
query = unidecode(request.GET.get('q', ''))
11391139

1140-
result = (
1141-
list(get_problem_hints(query, view_type, request.user))
1142-
+ get_tag_hints(query)
1143-
+ list(get_origintag_hints(query))
1144-
+ list(get_origininfovalue_hints(query))
1145-
)
1140+
result = []
1141+
result.extend(list(get_problem_hints(query, view_type, request.user)))
1142+
result.extend(get_tag_hints(query))
1143+
result.extend(get_origintag_hints(query))
1144+
result.extend(get_origininfovalue_hints(query))
11461145

11471146
# Convert category names in results from lazy translation to strings
11481147
# Since jsonify throws error if given lazy translation objects

0 commit comments

Comments
 (0)