diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 978bd4bb3d..d5036d58e9 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -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 diff --git a/judge/runguard_test/print_envvars.py b/judge/runguard_test/print_envvars.py index 0ddef33c79..d8109b59e3 100755 --- a/judge/runguard_test/print_envvars.py +++ b/judge/runguard_test/print_envvars.py @@ -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}') - diff --git a/misc-tools/dj_utils.py b/misc-tools/dj_utils.py index c37a3508f4..dd27b9865f 100644 --- a/misc-tools/dj_utils.py +++ b/misc-tools/dj_utils.py @@ -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}') @@ -79,7 +79,7 @@ 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: auth = (parsed.username, parsed.password) try: @@ -87,7 +87,9 @@ def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}): 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: @@ -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. @@ -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: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..5af57abba0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.pyright] +exclude = ["./doc/", "./example_problems/", "./webapp/vendor/", "./webapp/public/doc/manual/"] diff --git a/submit/submit b/submit/submit index f04fcd07f7..69979d21bb 100755 --- a/submit/submit +++ b/submit/submit @@ -17,6 +17,7 @@ import requests.utils import stat import sys import time +from typing import NoReturn try: import magic except ModuleNotFoundError: @@ -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 @@ -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. ''' @@ -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. ''' @@ -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'], @@ -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. ''' @@ -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 @@ -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: @@ -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 "_" @@ -219,6 +229,7 @@ def kotlin_base_entry_point(filebase: str) -> str: return filebase + def get_epilog(): '''Get the epilog for the help text.''' @@ -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}" @@ -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.''' @@ -323,8 +335,8 @@ 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') @@ -332,6 +344,7 @@ def do_api_submit(): 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 @@ -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 @@ -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 @@ -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: @@ -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') @@ -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 @@ -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:] @@ -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']: