diff --git a/submit.py b/submit.py
index 1003136..beebfd4 100755
--- a/submit.py
+++ b/submit.py
@@ -3,32 +3,18 @@
import optparse
import os
import sys
-import itertools
-import mimetypes
-import random
-import string
import re
import webbrowser
+import requests
+import requests.exceptions
+
# Python 2/3 compatibility
if sys.version_info[0] >= 3:
import configparser
- from urllib.parse import urlencode
- from urllib.error import URLError
- from urllib.request import Request, build_opener, HTTPCookieProcessor
-
- def form_body(form):
- return str(form).encode('utf-8')
else:
# Python 2, import modules with Python 3 names
import ConfigParser as configparser
- from urllib import urlencode
- from urllib2 import URLError
- from urllib2 import Request, build_opener, HTTPCookieProcessor
-
-
- def form_body(form):
- return str(form)
# End Python 2/3 compatibility
@@ -56,96 +42,6 @@ def form_body(form):
_GUESS_MAINCLASS = set(['Java', 'Python'])
-class MultiPartForm(object):
- """MultiPartForm based on code from
- http://blog.doughellmann.com/2009/07/pymotw-urllib2-library-for-opening-urls.html
-
- This since the default libraries still lack support for posting
- multipart/form-data (which is required to post files in HTTP).
- http://bugs.python.org/issue3244
- """
-
- def __init__(self):
- self.form_fields = []
- self.files = []
- self.boundary = ''.join(
- random.SystemRandom().choice(string.ascii_letters)
- for _ in range(50))
- return
-
- def get_content_type(self):
- return 'multipart/form-data; boundary=%s' % self.boundary
-
- @staticmethod
- def escape_field_name(name):
- """Should escape a field name escaped following RFC 2047 if needed.
- Skipped for now as we only call it with hard coded constants.
- """
- return name
-
- def add_field(self, name, value):
- """Add a simple field to the form data."""
- if value is None:
- # Assume the field is empty
- value = ""
- # ensure value is a string
- value = str(value)
- self.form_fields.append((name, value))
- return
-
- def add_file(self, fieldname, filename, file_handle, mimetype=None):
- """Add a file to be uploaded."""
- body = file_handle.read()
- if mimetype is None:
- mimetype = (mimetypes.guess_type(filename)[0] or
- 'application/octet-stream')
- self.files.append((fieldname, filename, mimetype, body))
- return
-
- def make_request(self, url):
- body = form_body(self)
- request = Request(url, data=body)
- request.add_header('Content-type', self.get_content_type())
- request.add_header('Content-length', len(body))
- return request
-
- def __str__(self):
- """Return a string representing the form data, including attached
- files."""
- # Build a list of lists, each containing "lines" of the
- # request. Each part is separated by a boundary string.
- # Once the list is built, return a string where each
- # line is separated by '\r\n'.
- parts = []
- part_boundary = '--' + self.boundary
-
- # Add the form fields
- parts.extend([part_boundary,
- ('Content-Disposition: form-data; name="%s"' %
- self.escape_field_name(name)),
- '',
- value]
- for name, value in self.form_fields)
-
- # Add the files to upload
- parts.extend([part_boundary,
- ('Content-Disposition: file; name="%s"; filename="%s"' %
- (self.escape_field_name(field_name), filename)),
- # FIXME: filename should be escaped using RFC 2231
- 'Content-Type: %s' % content_type,
- '',
- body]
- for field_name, filename, content_type, body in self.files
- )
-
- # Flatten the list and add closing boundary marker,
- # then return CR+LF separated data
- flattened = list(itertools.chain(*parts))
- flattened.append('--' + self.boundary + '--')
- flattened.append('')
- return '\r\n'.join(flattened)
-
-
class ConfigError(Exception):
pass
@@ -187,8 +83,7 @@ def login(login_url, username, password=None, token=None):
At least one of password or token needs to be provided.
- Returns a urllib OpenerDirector object that can be used to access
- URLs while logged in.
+ Returns a requests.Response where the cookies are needed to be able to submit
"""
login_args = {'user': username, 'script': 'true'}
if password:
@@ -196,16 +91,13 @@ def login(login_url, username, password=None, token=None):
if token:
login_args['token'] = token
- opener = build_opener(HTTPCookieProcessor())
- opener.open(login_url, urlencode(login_args).encode('ascii'))
- return opener
+ return requests.post(login_url, data=login_args)
def login_from_config(cfg):
"""Log in to Kattis using the access information in a kattisrc file
- Returns a urllib OpenerDirector object that can be used to access
- URLs while logged in.
+ Returns a requests.Response where the cookies are needed to be able to submit
"""
username = cfg.get('user', 'username')
password = token = None
@@ -228,36 +120,29 @@ def login_from_config(cfg):
return login(loginurl, username, password, token)
-def submit(url_opener, submit_url, problem, language, files,
- mainclass=None, tag=None):
+def submit(submit_url, cookies, problem, language, files, mainclass='', tag=''):
"""Make a submission.
The url_opener argument is an OpenerDirector object to use (as
returned by the login() function)
- """
- if mainclass is None:
- mainclass = ""
- if tag is None:
- tag = ""
- form = MultiPartForm()
- form.add_field('submit', 'true')
- form.add_field('submit_ctr', '2')
- form.add_field('language', language)
- form.add_field('mainclass', mainclass)
- form.add_field('problem', problem)
- form.add_field('tag', tag)
- form.add_field('script', 'true')
+ Returns the requests.Result from the submission
+ """
- if len(files) > 0:
- for filename in files:
- form.add_file('sub_file[]', os.path.basename(filename),
- open(filename))
+ data = {'submit': 'true',
+ 'submit_ctr': 2,
+ 'language': language,
+ 'mainclass': mainclass,
+ 'problem': problem,
+ 'tag': tag,
+ 'script': 'true'}
- request = form.make_request(submit_url)
+ sub_files = []
+ for f in files:
+ with open(f) as sub_file:
+ sub_files.append(('sub_file[]', (os.path.basename(f), sub_file.read(), 'application/octet-stream')))
- return url_opener.open(request).read().decode('utf-8').replace("
",
- "\n")
+ return requests.post(submit_url, data=data, files=sub_files, cookies=cookies)
def confirm_or_die(problem, language, files, mainclass, tag):
@@ -298,10 +183,10 @@ def main():
help='''Sets language to LANGUAGE.
Overrides default guess (based on suffix of first filename)''', default=None)
opt.add_option('-t', '--tag', dest='tag', metavar='TAG',
- help=optparse.SUPPRESS_HELP, default="")
+ help=optparse.SUPPRESS_HELP, default='')
opt.add_option('-f', '--force', dest='force',
help='Force, no confirmation prompt before submission',
- action="store_true", default=False)
+ action='store_true', default=False)
opts, args = opt.parse_args()
@@ -309,6 +194,12 @@ def main():
opt.print_help()
sys.exit(1)
+ try:
+ cfg = get_config()
+ except ConfigError as exc:
+ print(exc)
+ sys.exit(1)
+
problem, ext = os.path.splitext(os.path.basename(args[0]))
language = _LANGUAGE_GUESS.get(ext, None)
mainclass = problem if language in _GUESS_MAINCLASS else None
@@ -320,6 +211,18 @@ def main():
mainclass = opts.mainclass
if opts.language:
language = opts.language
+ else:
+ if language == 'Python':
+ try:
+ pyver = cfg.get('defaults', 'python-version')
+ if not pyver in ['2', '3']:
+ print('python-version in .kattisrc must be 2 or 3')
+ sys.exit(1)
+ elif pyver == '3':
+ language = 'Python 3'
+ except Exception:
+ pass
+
if language is None:
print('''\
@@ -335,23 +238,22 @@ def main():
seen.add(arg)
try:
- cfg = get_config()
- opener = login_from_config(cfg)
+ login_reply = login_from_config(cfg)
except ConfigError as exc:
print(exc)
sys.exit(1)
- except URLError as exc:
- if hasattr(exc, 'code'):
- print('Login failed.')
- if exc.code == 403:
- print('Incorrect username or password/token (403)')
- elif exc.code == 404:
- print("Incorrect login URL (404)")
- else:
- print(exc)
+ except requests.exceptions.RequestException as err:
+ print('Login connection failed:', err)
+ sys.exit(1)
+
+ if not login_reply.status_code == 200:
+ print('Login failed.')
+ if login_reply.status_code == 403:
+ print('Incorrect username or password/token (403)')
+ elif login_reply.status_code == 404:
+ print('Incorrect login URL (404)')
else:
- print('Failed to connect to Kattis server.')
- print('Reason: ', exc.reason)
+ print('Status code:', login_reply.status_code)
sys.exit(1)
submit_url = get_url(cfg, 'submissionurl', 'submit')
@@ -360,25 +262,26 @@ def main():
confirm_or_die(problem, language, files, mainclass, tag)
try:
- result = submit(opener, submit_url, problem, language, files, mainclass, tag)
- except URLError as exc:
- if hasattr(exc, 'code'):
- print('Submission failed.')
- if exc.code == 403:
- print('Access denied (403)')
- elif exc.code == 404:
- print('Incorrect submit URL (404)')
- else:
- print(exc)
+ result = submit(submit_url, login_reply.cookies, problem, language, files, mainclass, tag)
+ except requests.exceptions.RequestException as err:
+ print('Submit connection failed:', err)
+ sys.exit(1)
+
+ if result.status_code != 200:
+ print('Submission failed.')
+ if result.status_code == 403:
+ print('Access denied (403)')
+ elif result.status_code == 404:
+ print('Incorrect submit URL (404)')
else:
- print('Failed to connect to Kattis server.')
- print('Reason: ', exc.reason)
+ print('Status code:', login_reply.status_code)
sys.exit(1)
- print(result)
+ plain_result = result.content.decode('utf-8').replace('
', '\n')
+ print(plain_result)
try:
- open_submission(result, cfg)
+ open_submission(plain_result, cfg)
except configparser.NoOptionError:
pass