Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/codestyle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,24 @@ jobs:
webapp/public
webapp/config

pycodestyle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download pycodestyle python file
run: >
curl -L -o /tmp/pycodestyle.py 'https://github.com/PyCQA/pycodestyle/raw/refs/tags/2.12.1/pycodestyle.py'
- name: Check codestyle in python files
run: >
python3 /tmp/pycodestyle.py \
--exclude='./example_problems/,./doc/,./gitlab/,./webapp/vendor/' \
--max-line-length 120 \
--show-pep8 --show-source \
.

pyright:
runs-on: ubuntu-latest
steps:
- uses: jakebailey/pyright-action@v2
with:
version: 1.1.311
2 changes: 0 additions & 2 deletions judge/runguard_test/print_envvars.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
#!/usr/bin/python3

import os

envvars = os.environ.items()
print(f'COUNT: {len(envvars)}.')
for k, v in envvars:
print(f'{k}={v}')

11 changes: 7 additions & 4 deletions misc-tools/dj_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def parse_api_response(name: str, response: requests.Response):
# We got a successful HTTP response. It worked. Return the full response
try:
result = json.loads(response.text)
except json.decoder.JSONDecodeError as e:
except json.decoder.JSONDecodeError:
print(response.text)
raise RuntimeError(f'Failed to JSON decode the response for API request {name}')

Expand Down Expand Up @@ -79,15 +79,17 @@ def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}):
url = f'{domjudge_webapp_folder_or_api_url}/{name}'
parsed = urlparse(domjudge_webapp_folder_or_api_url)
auth = None
if parsed.username or parsed.password:
if parsed.username and parsed.password:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an actual bugfix, could you put that in a separate commit?

auth = (parsed.username, parsed.password)

try:
if method == 'GET':
response = requests.get(url, headers=headers, verify=ca_check, auth=auth)
elif method == 'PUT':
response = requests.put(url, headers=headers, verify=ca_check, json=jsonData, auth=auth)
except requests.exceptions.SSLError as e:
else:
raise RuntimeError("Method not supported")
except requests.exceptions.SSLError:
ca_check = not confirm(
"Can not verify certificate, ignore certificate check?", False)
if ca_check:
Expand All @@ -99,6 +101,7 @@ def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}):
raise RuntimeError(e)
return parse_api_response(name, response)


def upload_file(name: str, apifilename: str, file: str, data: dict = {}):
'''Upload the given file to the API at the given path with the given name.

Expand Down Expand Up @@ -128,7 +131,7 @@ def upload_file(name: str, apifilename: str, file: str, data: dict = {}):
try:
response = requests.post(
url, files=files, headers=headers, data=data, verify=ca_check)
except requests.exceptions.SSLError as e:
except requests.exceptions.SSLError:
ca_check = not confirm(
"Can not verify certificate, ignore certificate check?", False)
if ca_check:
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.pyright]
exclude = ["./doc/", "./example_problems/", "./webapp/vendor/", "./webapp/public/doc/manual/"]
90 changes: 52 additions & 38 deletions submit/submit
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import requests.utils
import stat
import sys
import time
from typing import NoReturn
try:
import magic
except ModuleNotFoundError:
Expand All @@ -40,12 +41,14 @@ num_warnings = 0

headers = {'user-agent': f'domjudge-submit-client ({requests.utils.default_user_agent()})'}


def confirm(message: str) -> bool:
answer = ''
while answer not in ['y', 'n']:
answer = input(f'{message} (y/n) ').lower()
return answer == 'y'


def warn_user(msg: str):
global num_warnings
num_warnings += 1
Expand All @@ -54,18 +57,21 @@ def warn_user(msg: str):
else:
print(f'WARNING: {msg}!')

def usage(msg: str):

def usage(msg: str) -> NoReturn:
logging.error(f'error: {msg}')
print(f"Type '{sys.argv[0]} --help' to get help.")
exit(1)

def error(msg: str):

def error(msg: str) -> NoReturn:
logging.error(msg)
exit(-1)


def read_contests() -> list:
'''Read all contests from the API.

Returns:
The contests or None if an error occurred.
'''
Expand All @@ -74,28 +80,29 @@ def read_contests() -> list:
data = do_api_request('contests')
except RuntimeError as e:
logging.warning(e)
return None
return []

if not isinstance(data, list):
logging.warning("DOMjudge's API returned unexpected JSON data for endpoint 'contests'.")
return None
return []

contests = []
for contest in data:
if ('id' not in contest
or 'shortname' not in contest
or not contest['id']
or not contest['shortname']):
or 'shortname' not in contest
or not contest['id']
or not contest['shortname']):
logging.warning("DOMjudge's API returned unexpected JSON data for 'contests'.")
return None
return []
contests.append(contest)

logging.info(f'Read {len(contests)} contest(s) from the API.')
return contests


def read_languages() -> list:
'''Read all languages for the current contest from the API.

Returns:
The languages or None if an error occurred.
'''
Expand All @@ -105,22 +112,22 @@ def read_languages() -> list:
data = do_api_request(endpoint)
except RuntimeError as e:
logging.warning(e)
return None
return []

if not isinstance(data, list):
logging.warning("DOMjudge's API returned unexpected JSON data for endpoint 'languages'.")
return None
return []

languages = []

for item in data:
if ('id' not in item
or 'extensions' not in item
or not item['id']
or not isinstance(item['extensions'], list)
or len(item['extensions']) == 0):
or 'extensions' not in item
or not item['id']
or not isinstance(item['extensions'], list)
or len(item['extensions']) == 0):
logging.warning("DOMjudge's API returned unexpected JSON data for 'languages'.")
return None
return []
language = {
'id': item['id'],
'name': item['name'],
Expand All @@ -134,9 +141,10 @@ def read_languages() -> list:

return languages


def read_problems() -> list:
'''Read all problems for the current contest from the API.

Returns:
The problems or None if an error occurred.
'''
Expand All @@ -146,30 +154,31 @@ def read_problems() -> list:
data = do_api_request(endpoint)
except RuntimeError as e:
logging.warning(e)
return None
return []

if not isinstance(data, list):
logging.warning("DOMjudge's API returned unexpected JSON data for endpoint 'problems'.")
return None
return []

problems = []

for problem in data:
if ('id' not in problem
or 'label' not in problem
or not problem['id']
or not problem['label']):
or 'label' not in problem
or not problem['id']
or not problem['label']):
logging.warning("DOMjudge's API returned unexpected JSON data for 'problems'.")
return None
return []
problems.append(problem)

logging.info(f'Read {len(problems)} problem(s) from the API.')

return problems


def do_api_request(name: str):
'''Perform an API call to the given endpoint and return its data.

Parameters:
name (str): the endpoint to call

Expand All @@ -184,7 +193,7 @@ def do_api_request(name: str):
raise RuntimeError('No baseurl set')

url = f'{baseurl}api/{api_version}{name}'

logging.info(f'Connecting to {url}')

try:
Expand All @@ -203,6 +212,7 @@ def do_api_request(name: str):

return json.loads(response.text)


def kotlin_base_entry_point(filebase: str) -> str:
if filebase == "":
return "_"
Expand All @@ -219,6 +229,7 @@ def kotlin_base_entry_point(filebase: str) -> str:

return filebase


def get_epilog():
'''Get the epilog for the help text.'''

Expand Down Expand Up @@ -248,7 +259,7 @@ drop-down box in the web interface.'''
language_part = 'For LANGUAGE use the ID or a common extension.'
else:
language_part = 'For LANGUAGE use one of the following IDs or extensions:'
max_length = max([len(l['name']) for l in languages])
max_length = max([len(lang['name']) for lang in languages])
for language in languages:
sorted_exts = ', '.join(sorted(language['extensions']))
language_part += f"\n {language['name']:<{max_length}} - {sorted_exts}"
Expand Down Expand Up @@ -287,6 +298,7 @@ Submit multiple files (the problem and language are taken from the first):

return "\n\n".join(part for part in epilog_parts if part)


def do_api_submit():
'''Submit to the API with the given data.'''

Expand Down Expand Up @@ -323,15 +335,16 @@ def do_api_submit():
error(f'Parsing DOMjudge\'s API output failed: {e}')

if (not isinstance(submission, dict)
or not 'id' in submission
or not isinstance(submission['id'], str)):
or 'id' not in submission
or not isinstance(submission['id'], str)):
error('DOMjudge\'s API returned unexpected JSON data.')

time = datetime.datetime.fromisoformat(submission['time']).strftime('%H:%M:%S')
sid = submission['id']
print(f"Submission received: id = s{sid}, time = {time}")
print(f"Check {baseurl}team/submission/{sid} for the result.")


version_text = '''
submit -- part of DOMjudge
Written by the DOMjudge developers
Expand Down Expand Up @@ -364,7 +377,7 @@ parser.add_argument('-c', '--contest', help='''submit for contest with ID or sho
parser.add_argument('-p', '--problem', help='submit for problem with ID or label PROBLEM', default='')
parser.add_argument('-l', '--language', help='submit in language with ID LANGUAGE', default='')
parser.add_argument('-e', '--entry_point', help='set an explicit entry_point, e.g. the java main class')
parser.add_argument('-v', '--verbose', help='increase verbosity', choices=loglevels.keys(), nargs='?', const='INFO', default='WARNING')
parser.add_argument('-v', '--verbose', help='increase verbosity', choices=loglevels.keys(), nargs='?', const='INFO', default='WARNING') # NOQA
parser.add_argument('-q', '--quiet', help='suppress warning/info messages', action='store_true')
parser.add_argument('-y', '--assume-yes', help='suppress user input and assume yes', action='store_true')
parser.add_argument('-u', '--url', help='''submit to server with base address URL
Expand All @@ -377,7 +390,7 @@ verbosity = args.verbose
if args.quiet:
verbosity = 'ERROR'

logging.basicConfig(format=f'%(message)s', level=loglevels[verbosity])
logging.basicConfig(format='%(message)s', level=loglevels[verbosity])
logging.info(f'set verbosity to {verbosity}')

problem_id = args.problem
Expand All @@ -403,9 +416,9 @@ contests = read_contests() if baseurl else None
if not contests and not args.help:
logging.warning('Could not obtain active contests.')

my_contest = None
my_language = None
my_problem = None
my_contest: dict = {}
my_language: dict = {}
my_problem: dict = {}

if not contest_id:
if not contests:
Expand All @@ -423,8 +436,8 @@ elif contests:
my_contest = contest
break

languages = None
problems = None
languages: list = []
problems: list = []
if my_contest and baseurl:
if 'allow_submit' in my_contest and not my_contest['allow_submit']:
warn_user('Submissions for contest (temporarily) disabled')
Expand Down Expand Up @@ -470,7 +483,7 @@ for index, filename in enumerate(args.filename, 1):
st = os.stat(filename)
except FileNotFoundError:
usage(f"Cannot find file `{filename}'.")

logging.debug(f"submission file {index}: `{filename}'")

# Do some checks on submission file and warn user
Expand All @@ -496,6 +509,7 @@ for index, filename in enumerate(args.filename, 1):

filebase = os.path.basename(filenames[0])

ext = ""
if '.' in filebase:
dot = filebase.rfind('.')
ext = filebase[dot+1:]
Expand Down Expand Up @@ -527,7 +541,7 @@ for problem in problems:
break

if not my_problem:
usage('No known problem specified or detected.')
usage('No known problem specified or detected.')

# Guess entry point if not already specified.
if not entry_point and my_language['entry_point_required']:
Expand Down
Loading