From f85eab9a2f01c0792e5d7b8dd38cfc015c439388 Mon Sep 17 00:00:00 2001 From: Gunnar Kreitz Date: Fri, 31 Jul 2026 13:58:22 +0200 Subject: [PATCH 1/2] ruff 0.16.1 check --fix (what ruff considers safe fixes) --- .../ProblemPlasTeX/ProblemsetMacros.py | 12 ++--- problemtools/ProblemPlasTeX/__init__.py | 14 +++--- problemtools/ProblemPlasTeX/graphicx.py | 3 +- problemtools/ProblemPlasTeX/import.py | 3 +- problemtools/ProblemPlasTeX/listingsutf8.py | 7 ++- problemtools/config.py | 5 +- problemtools/context.py | 6 ++- problemtools/formatversion.py | 3 +- problemtools/judge/execute.py | 1 - problemtools/languages.py | 6 +-- problemtools/md2html.py | 3 +- problemtools/metadata.py | 19 ++++---- problemtools/model/__init__.py | 4 +- problemtools/problem2html.py | 5 +- problemtools/problem2pdf.py | 4 +- problemtools/run/__init__.py | 7 +-- problemtools/run/buildrun.py | 7 ++- problemtools/run/checktestdata.py | 3 +- problemtools/run/errors.py | 2 - problemtools/run/executable.py | 3 +- problemtools/run/program.py | 7 ++- problemtools/run/rutil.py | 2 +- problemtools/run/source.py | 6 +-- problemtools/run/tools.py | 1 + problemtools/run/viva.py | 5 +- problemtools/statement_util.py | 11 ++--- problemtools/template.py | 4 +- problemtools/tex2html.py | 10 ++-- problemtools/update_from_old_problemformat.py | 3 +- problemtools/verifyproblem.py | 46 ++++++++----------- .../run_and_generate_expected.py | 2 +- tests/hello/input_validators/validate.py | 2 +- tests/test_config.py | 1 - tests/test_default_validator.py | 3 +- tests/test_languages.py | 6 +-- tests/test_latex.py | 2 +- tests/test_markdown.py | 3 +- tests/test_metadata.py | 3 +- tests/test_output_validator.py | 2 +- tests/test_run_limit.py | 3 +- tests/test_verify_hello.py | 1 + tests/test_xss.py | 6 +-- 42 files changed, 116 insertions(+), 130 deletions(-) diff --git a/problemtools/ProblemPlasTeX/ProblemsetMacros.py b/problemtools/ProblemPlasTeX/ProblemsetMacros.py index b5bd4f41..133f4543 100644 --- a/problemtools/ProblemPlasTeX/ProblemsetMacros.py +++ b/problemtools/ProblemPlasTeX/ProblemsetMacros.py @@ -1,9 +1,9 @@ -import sys import os import os.path +import sys + +from plasTeX.Base import Command, DimenCommand from plasTeX.DOM import Node -from plasTeX.Base import Command -from plasTeX.Base import DimenCommand from plasTeX.Logging import getLogger log = getLogger() @@ -56,7 +56,7 @@ def invoke(self, tex): status.info(') ( verbatim %s ' % file2) self.attributes['data2'] = self.read_sample_file(file2) status.info(') ') - except (OSError, IOError): + except OSError: log.warning('\nProblem opening files "%s" and "%s"', file1, file2) @@ -97,7 +97,7 @@ def invoke(self, tex): status.info(' ( sampletableinteractive %s ' % file) self.attributes['messages'] = self.read_sample_interaction(file) status.info(') ') - except (OSError, IOError): + except OSError: log.warning('\nProblem opening file "%s"', file) @@ -131,7 +131,7 @@ def invoke(self, tex): try: img = os.path.abspath(basetex.kpsewhich(f + e)) break - except (OSError, IOError): + except OSError: pass if img is None or not os.path.isfile(img): diff --git a/problemtools/ProblemPlasTeX/__init__.py b/problemtools/ProblemPlasTeX/__init__.py index 39122bc2..352e671d 100644 --- a/problemtools/ProblemPlasTeX/__init__.py +++ b/problemtools/ProblemPlasTeX/__init__.py @@ -1,17 +1,18 @@ -import re import os +import re import shutil import subprocess -from plasTeX.Renderers.PageTemplate import Renderer + from plasTeX.Filenames import Filenames from plasTeX.Imagers import Image from plasTeX.Logging import getLogger +from plasTeX.Renderers.PageTemplate import Renderer log = getLogger() # Adapted from plasTeX.Imagers.Imager class -class ImageConverter(object): +class ImageConverter: fileExtension = '.png' imageAttrs = '' imageUnits = '' @@ -74,7 +75,6 @@ def getImage(self, node): except Exception as msg: log.warning('%s in image "%s".' % (msg, name)) - pass return None @@ -117,12 +117,12 @@ def processFileContent(self, document, s): s = Renderer.processFileContent(self, document, s) # Force XHTML syntax on empty tags - s = re.compile(r'(<(?:hr|br|img|link|meta)\b.*?)\s*/?\s*(>)', re.I | re.S).sub(r'\1 /\2', s) + s = re.compile(r'(<(?:hr|br|img|link|meta)\b.*?)\s*/?\s*(>)', re.IGNORECASE | re.DOTALL).sub(r'\1 /\2', s) # Remove empty paragraphs - s = re.compile(r'

\s*

', re.I).sub(r'', s) + s = re.compile(r'

\s*

', re.IGNORECASE).sub(r'', s) # Add a non-breaking space to empty table cells - s = re.compile(r'(<(td|th)\b[^>]*>)\s*()', re.I).sub(r'\1 \3', s) + s = re.compile(r'(<(td|th)\b[^>]*>)\s*()', re.IGNORECASE).sub(r'\1 \3', s) return s diff --git a/problemtools/ProblemPlasTeX/graphicx.py b/problemtools/ProblemPlasTeX/graphicx.py index e9669397..c96cced8 100644 --- a/problemtools/ProblemPlasTeX/graphicx.py +++ b/problemtools/ProblemPlasTeX/graphicx.py @@ -1,4 +1,5 @@ -import plasTeX.Packages.graphics as graphics +from plasTeX.Packages import graphics + from problemtools.ProblemPlasTeX.ProblemsetMacros import _graphics_command, clean_width # Reimplementation of graphicx package because plasTeX is broken and diff --git a/problemtools/ProblemPlasTeX/import.py b/problemtools/ProblemPlasTeX/import.py index e2d4e14f..98b39f1c 100644 --- a/problemtools/ProblemPlasTeX/import.py +++ b/problemtools/ProblemPlasTeX/import.py @@ -1,5 +1,6 @@ import codecs import os + from plasTeX.Base import Command from plasTeX.Logging import getLogger @@ -22,7 +23,7 @@ def invoke(self, tex): try: encoding = self.config['files']['input-encoding'] tex.input(codecs.open(fullpath, 'r', encoding, 'replace')) - except (OSError, IOError): + except OSError: log.warning('\nProblem opening file "%s"', fullpath) status.info(' ) ') return [] diff --git a/problemtools/ProblemPlasTeX/listingsutf8.py b/problemtools/ProblemPlasTeX/listingsutf8.py index d022ef03..4ba8e62d 100644 --- a/problemtools/ProblemPlasTeX/listingsutf8.py +++ b/problemtools/ProblemPlasTeX/listingsutf8.py @@ -1,9 +1,8 @@ +import os + from plasTeX.Base import Command from plasTeX.Logging import getLogger -import os -import io - log = getLogger() # Implementation of (parts) of listingsutf8 package since PlasTeX does @@ -14,7 +13,7 @@ class lstinputlisting(Command): args = '* [ options:dict ] file:str' def read_file(self, filename) -> str: - return io.open(filename, 'r', encoding='utf-8').read() + return open(filename, 'r', encoding='utf-8').read() def invoke(self, tex) -> None: super().invoke(tex) diff --git a/problemtools/config.py b/problemtools/config.py index a85493be..6a5c016b 100644 --- a/problemtools/config.py +++ b/problemtools/config.py @@ -1,8 +1,9 @@ import collections import os -import yaml +from collections.abc import Mapping from pathlib import Path -from typing import Mapping + +import yaml class ConfigError(Exception): diff --git a/problemtools/context.py b/problemtools/context.py index 28022246..8b781ba7 100644 --- a/problemtools/context.py +++ b/problemtools/context.py @@ -1,9 +1,11 @@ from __future__ import annotations import concurrent.futures -from concurrent.futures import ThreadPoolExecutor import re -from typing import Callable, Pattern, ParamSpec, TypeVar +from collections.abc import Callable +from concurrent.futures import ThreadPoolExecutor +from re import Pattern +from typing import ParamSpec, TypeVar _T = TypeVar('_T') _P = ParamSpec('_P') diff --git a/problemtools/formatversion.py b/problemtools/formatversion.py index c13cae95..52213687 100644 --- a/problemtools/formatversion.py +++ b/problemtools/formatversion.py @@ -1,7 +1,8 @@ -import yaml from enum import StrEnum from pathlib import Path +import yaml + class FormatVersion(StrEnum): LEGACY = 'legacy' diff --git a/problemtools/judge/execute.py b/problemtools/judge/execute.py index 285ba9e8..6ee81a84 100644 --- a/problemtools/judge/execute.py +++ b/problemtools/judge/execute.py @@ -23,7 +23,6 @@ import signal import tempfile from pathlib import Path - from typing import TYPE_CHECKING from ..diagnostics import Diagnostics diff --git a/problemtools/languages.py b/problemtools/languages.py index 000f0d15..77f0f94d 100644 --- a/problemtools/languages.py +++ b/problemtools/languages.py @@ -14,10 +14,8 @@ class LanguageConfigError(Exception): """Exception class for errors in language configuration.""" - pass - -class Language(object): +class Language: """ Class representing a single language. """ @@ -151,7 +149,7 @@ def __matches_shebang(self, filename): return self.shebang.search(shebang_line) is not None -class Languages(object): +class Languages: """A set of languages.""" def __init__(self, data=None): diff --git a/problemtools/md2html.py b/problemtools/md2html.py index 445eca0f..3eb58c18 100644 --- a/problemtools/md2html.py +++ b/problemtools/md2html.py @@ -1,14 +1,13 @@ #! /usr/bin/env python3 -# -*- coding: utf-8 -*- import argparse import hashlib import html import os -from pathlib import Path import re import shutil import string import subprocess +from pathlib import Path import nh3 diff --git a/problemtools/metadata.py b/problemtools/metadata.py index d01f7f10..1ad82461 100644 --- a/problemtools/metadata.py +++ b/problemtools/metadata.py @@ -4,14 +4,13 @@ from dataclasses import dataclass, field from enum import StrEnum from pathlib import Path -from typing import Any, Literal, Self, Type, Union +from typing import Any, Literal, Self, Union from uuid import UUID -from pydantic import BaseModel, ConfigDict, Field import yaml +from pydantic import BaseModel, ConfigDict, Field -from . import config -from . import statement_util +from . import config, statement_util from .formatversion import FormatVersion @@ -41,7 +40,7 @@ class Person: kattis: str | None = None @classmethod - def from_string(cls: Type[Self], s: str) -> Self: + def from_string(cls: type[Self], s: str) -> Self: match = re.match(r'^(.*?)\s+<(.*)>$', s.strip()) if match: return cls(name=match.group(1), email=match.group(2)) @@ -99,7 +98,7 @@ class InputCredits: """ # Type in the input format is messy - PersonOrPersons = Union[str | list[Union[Person, str]]] + PersonOrPersons = Union[str | list[Person | str]] authors: PersonOrPersons = field(default_factory=list) contributors: PersonOrPersons = field(default_factory=list) @@ -120,7 +119,7 @@ class Metadata2023_07(BaseModel): type: list[ProblemType] | ProblemType = ProblemType.PASS_FAIL version: str | None = None credits: dict | str | None = None - source: list[Union[str, Source]] | Source | str = [] + source: list[str | Source] | Source | str = [] license: License = License.UNKNOWN rights_owner: str | None = None embargo_until: datetime.datetime | None = None @@ -165,7 +164,7 @@ class MetadataLegacy(BaseModel): """ problem_format_version: FormatVersion = FormatVersion.LEGACY - type: Literal['pass-fail'] | Literal['scoring'] = 'pass-fail' + type: Literal['pass-fail', 'scoring'] = 'pass-fail' name: str | None = None uuid: UUID | None = None author: str | None = None @@ -243,7 +242,7 @@ def is_submit_answer(self) -> bool: return ProblemType.SUBMIT_ANSWER in self.type @classmethod - def from_legacy(cls: Type[Self], legacy: MetadataLegacy, names_from_statements: dict[str, str]) -> Self: + def from_legacy(cls: type[Self], legacy: MetadataLegacy, names_from_statements: dict[str, str]) -> Self: metadata = legacy.model_dump() metadata['type'] = [metadata['type']] # Support for *ancient* problems where names_from_statements is empty @@ -293,7 +292,7 @@ def parse_author_field(author: str) -> list[Person]: return cls.model_validate(metadata) @classmethod - def from_2023_07(cls: Type[Self], md2023_07: Metadata2023_07) -> Self: + def from_2023_07(cls: type[Self], md2023_07: Metadata2023_07) -> Self: metadata = md2023_07.model_dump() metadata['type'] = [metadata['type']] if isinstance(metadata['type'], str) else metadata['type'] metadata['name'] = {'en': metadata['name']} if isinstance(metadata['name'], str) else metadata['name'] diff --git a/problemtools/model/__init__.py b/problemtools/model/__init__.py index 80be73af..871bee6c 100644 --- a/problemtools/model/__init__.py +++ b/problemtools/model/__init__.py @@ -1,9 +1,9 @@ -from .includes import DEFAULT_LANGUAGE, IncludeFile, LanguageIncludes, Includes, load_includes +from .includes import DEFAULT_LANGUAGE, IncludeFile, Includes, LanguageIncludes, load_includes __all__ = [ 'DEFAULT_LANGUAGE', 'IncludeFile', - 'LanguageIncludes', 'Includes', + 'LanguageIncludes', 'load_includes', ] diff --git a/problemtools/problem2html.py b/problemtools/problem2html.py index 918438f2..1e73ed38 100644 --- a/problemtools/problem2html.py +++ b/problemtools/problem2html.py @@ -1,5 +1,4 @@ #! /usr/bin/env python3 -# -*- coding: utf-8 -*- import argparse import os.path import re @@ -8,9 +7,7 @@ import sys from pathlib import Path -from . import tex2html -from . import md2html -from . import statement_util +from . import md2html, statement_util, tex2html from .version import add_version_arg diff --git a/problemtools/problem2pdf.py b/problemtools/problem2pdf.py index 1eaa38a6..f242047e 100644 --- a/problemtools/problem2pdf.py +++ b/problemtools/problem2pdf.py @@ -1,5 +1,4 @@ #! /usr/bin/env python3 -# -*- coding: utf-8 -*- import argparse import os import re @@ -10,8 +9,7 @@ import tempfile from pathlib import Path -from . import template -from . import statement_util +from . import statement_util, template from .version import add_version_arg diff --git a/problemtools/run/__init__.py b/problemtools/run/__init__.py index fe74dac7..8bbbb2d0 100644 --- a/problemtools/run/__init__.py +++ b/problemtools/run/__init__.py @@ -2,17 +2,18 @@ Problemtools. """ -import re import os +import re +from . import rutil from .buildrun import BuildRun from .checktestdata import Checktestdata from .errors import ProgramError as ProgramError from .program import Program from .source import SourceCode +from .tools import get_tool as get_tool +from .tools import get_tool_path as get_tool_path from .viva import Viva -from .tools import get_tool as get_tool, get_tool_path as get_tool_path -from . import rutil def find_programs( diff --git a/problemtools/run/buildrun.py b/problemtools/run/buildrun.py index 404bcf5e..c5f33e46 100644 --- a/problemtools/run/buildrun.py +++ b/problemtools/run/buildrun.py @@ -2,15 +2,14 @@ Implementation of programs provided by a directory with build/run scripts. """ +import logging import os -import tempfile import subprocess +import tempfile -import logging - +from . import rutil from .errors import ProgramError from .program import Program -from . import rutil log = logging.getLogger(__file__) diff --git a/problemtools/run/checktestdata.py b/problemtools/run/checktestdata.py index a8a65fd2..0b9b36d1 100644 --- a/problemtools/run/checktestdata.py +++ b/problemtools/run/checktestdata.py @@ -4,6 +4,7 @@ import os import sys + from .executable import Executable @@ -54,7 +55,7 @@ def run( runtime (float): runtime of the Checktestdata process in seconds """ - (status, runtime) = super(Checktestdata, self).run( + (status, runtime) = super().run( infile=infile, outfile=outfile, errfile=errfile, args=args, timelim=timelim, memlim=memlim, work_dir=work_dir ) # This is ugly, switches the accept exit status and our accept diff --git a/problemtools/run/errors.py b/problemtools/run/errors.py index 4b8d1db9..a44c2abf 100644 --- a/problemtools/run/errors.py +++ b/problemtools/run/errors.py @@ -5,5 +5,3 @@ class ProgramError(Exception): """Base exception class for errors within the run package.""" - - pass diff --git a/problemtools/run/executable.py b/problemtools/run/executable.py index 8f3a67e7..b738275e 100644 --- a/problemtools/run/executable.py +++ b/problemtools/run/executable.py @@ -3,8 +3,9 @@ """ import os -from .program import Program + from .errors import ProgramError +from .program import Program class Executable(Program): diff --git a/problemtools/run/program.py b/problemtools/run/program.py index 38c789d8..4c305c95 100644 --- a/problemtools/run/program.py +++ b/problemtools/run/program.py @@ -1,16 +1,15 @@ """Abstract base class for programs.""" +import logging import os -from . import limit import resource import signal -import logging import threading +from abc import ABC, abstractmethod +from . import limit from .errors import ProgramError -from abc import ABC, abstractmethod - log = logging.getLogger(__name__) diff --git a/problemtools/run/rutil.py b/problemtools/run/rutil.py index 62df8fbe..fcd4ecb3 100644 --- a/problemtools/run/rutil.py +++ b/problemtools/run/rutil.py @@ -34,7 +34,7 @@ def add_files(src, dstdir): shutil.copytree(srcfile, destfile, dirs_exist_ok=True) else: shutil.copy(srcfile, destfile) - except IOError as exc: + except OSError as exc: # FIXME why is this specific error special-cased if exc.errno == errno.ENOENT: raise ProgramError('File not found when copying program:\n %s' % exc.filename) diff --git a/problemtools/run/source.py b/problemtools/run/source.py index 6b619428..f6403e2a 100644 --- a/problemtools/run/source.py +++ b/problemtools/run/source.py @@ -2,15 +2,15 @@ Implementation of programs provided by source code. """ +import logging import os import shlex -import tempfile -import logging import subprocess +import tempfile +from . import rutil from .errors import ProgramError from .program import Program -from . import rutil log = logging.getLogger(__name__) diff --git a/problemtools/run/tools.py b/problemtools/run/tools.py index 23c30614..471bd7cb 100644 --- a/problemtools/run/tools.py +++ b/problemtools/run/tools.py @@ -1,4 +1,5 @@ import os + from .executable import Executable diff --git a/problemtools/run/viva.py b/problemtools/run/viva.py index 23e35c68..86f96ac6 100644 --- a/problemtools/run/viva.py +++ b/problemtools/run/viva.py @@ -3,8 +3,9 @@ """ import os -from .executable import Executable + from .errors import ProgramError +from .executable import Executable from .tools import get_tool_path @@ -62,7 +63,7 @@ def run( if infile != '/dev/null': args = args + [infile] - (status, runtime) = super(Viva, self).run( + (status, runtime) = super().run( outfile=outfile, errfile=errfile, args=args, timelim=timelim, memlim=memlim, work_dir=work_dir ) # This is ugly, switches the accept exit status and our accept diff --git a/problemtools/statement_util.py b/problemtools/statement_util.py index c5a3f6a8..fc9fabbd 100644 --- a/problemtools/statement_util.py +++ b/problemtools/statement_util.py @@ -1,13 +1,12 @@ import collections import html import json +import logging import os import re import subprocess import tempfile -import logging from pathlib import Path -from typing import Optional, List, Tuple from urllib.parse import urlparse from . import metadata @@ -67,7 +66,7 @@ def find_statement(problem_root: Path, language: str) -> Path: if language not in candidates: raise FileNotFoundError(f'No statement found in language {language}. Found languages: {", ".join(candidates)}') elif len(candidates[language]) > 1: - raise ValueError(f'Multiple statements in language {language}: {", ".join((file.name for file in candidates[language]))}') + raise ValueError(f'Multiple statements in language {language}: {", ".join(file.name for file in candidates[language])}') else: return candidates[language][0] @@ -136,7 +135,7 @@ def assert_images_are_valid_md(statement_path: Path) -> None: foreach_image(statement_path, lambda img_name: assert_image_is_valid(statement_dir, img_name)) -def find_footnotes(statement_html: str) -> Optional[int]: +def find_footnotes(statement_html: str) -> int | None: """Find the position of the footnotes in the statement and return it or None""" for footnote_string in FOOTNOTES_STRINGS: if footnote_string in statement_html: @@ -144,7 +143,7 @@ def find_footnotes(statement_html: str) -> Optional[int]: return None -def inject_samples(statement_html: str, samples: List[str]) -> Tuple[str, List[str]]: +def inject_samples(statement_html: str, samples: list[str]) -> tuple[str, list[str]]: """Injects samples at occurences of {{nextsample}} and {{remainingsamples}} Non-destructive @@ -172,7 +171,7 @@ def inject_samples(statement_html: str, samples: List[str]) -> Tuple[str, List[s return statement_html, samples -def format_samples(problem_root: Path) -> List[str]: +def format_samples(problem_root: Path) -> list[str]: """Read all samples from the problem directory and convert them to pandoc-valid markdown Args: diff --git a/problemtools/template.py b/problemtools/template.py index f055a60d..5c3999d1 100644 --- a/problemtools/template.py +++ b/problemtools/template.py @@ -1,6 +1,6 @@ import os.path -import tempfile import shutil +import tempfile from pathlib import Path @@ -123,6 +123,6 @@ def _non_ws_unicode_in_sample(self, sample_dir: Path) -> str: for char in line: if not char.isascii() and not char.isspace(): res.add(char) - except (UnicodeDecodeError, IOError): + except (OSError, UnicodeDecodeError): pass return ''.join(sorted(list(res))) diff --git a/problemtools/tex2html.py b/problemtools/tex2html.py index 571a7b4d..8a04e2c8 100644 --- a/problemtools/tex2html.py +++ b/problemtools/tex2html.py @@ -1,7 +1,7 @@ -import os +import argparse import logging +import os import string -import argparse from pathlib import Path from . import template @@ -9,10 +9,10 @@ def convert(problem_root: Path, options: argparse.Namespace, statement_file: Path) -> None: # PlasTeX.Logging statically overwrites logging and formatting, so delay loading - import plasTeX.TeX import plasTeX.Logging - from .ProblemPlasTeX import ProblemRenderer - from .ProblemPlasTeX import ProblemsetMacros + import plasTeX.TeX + + from .ProblemPlasTeX import ProblemRenderer, ProblemsetMacros if options.quiet: plasTeX.Logging.disableLogging() diff --git a/problemtools/update_from_old_problemformat.py b/problemtools/update_from_old_problemformat.py index 22922f46..e933d2b0 100644 --- a/problemtools/update_from_old_problemformat.py +++ b/problemtools/update_from_old_problemformat.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- - import argparse import glob import os.path + import yaml diff --git a/problemtools/verifyproblem.py b/problemtools/verifyproblem.py index 7347dc1b..8668d478 100644 --- a/problemtools/verifyproblem.py +++ b/problemtools/verifyproblem.py @@ -1,47 +1,39 @@ #! /usr/bin/env python3 -# -*- coding: utf-8 -*- from __future__ import annotations import argparse -import math +import collections +import difflib import glob -import string import hashlib -import collections +import logging +import math import os +import random import re import shutil -import logging -import tempfile +import string import sys -import random +import tempfile import traceback import uuid -import difflib +from abc import ABC +from collections.abc import Callable +from functools import cached_property from pathlib import Path +from re import Match, Pattern +from typing import Any, ClassVar, Literal, ParamSpec, TypeVar import yaml +from pydantic import ValidationError -from . import checks -from . import config -from . import languages -from . import metadata -from . import model -from . import problem2html -from . import problem2pdf -from . import run -from . import statement_util -from .context import Context, PROBLEM_PARTS +from . import checks, config, languages, metadata, model, problem2html, problem2pdf, run, statement_util +from .context import PROBLEM_PARTS, Context from .diagnostics import Diagnostics, LoggingDiagnostics, VerifyError from .formatversion import FormatVersion, get_format_version from .judge import CacheKey, SubmissionJudge, SubmissionResult, Verdict, validate_output from .version import add_version_arg -from abc import ABC -from functools import cached_property -from typing import Any, Callable, ClassVar, Literal, Pattern, Match, ParamSpec, TypeVar -from pydantic import ValidationError - random.seed(42) @@ -425,7 +417,7 @@ def check(self, context: Context) -> bool: hashes[filehash].append(os.path.relpath(filepath, self._problem.probdir)) for _, files in hashes.items(): if len(files) > 1: - self.warning(f"Identical input files: '{str(files)}'") + self.warning(f"Identical input files: '{files!s}'") infiles = glob.glob(os.path.join(self._datadir, '*.in')) ansfiles = glob.glob(os.path.join(self._datadir, '*.ans')) @@ -537,7 +529,7 @@ def _latex_heuristic(name: str) -> bool: for lang, files in self.statements.items(): if len(files) > 1: - self.error(f'Found multiple statements in the same language {lang}: {", ".join((file.name for file in files))}') + self.error(f'Found multiple statements in the same language {lang}: {", ".join(file.name for file in files)}') if lang not in self.problem.metadata.name: self.error(f'No problem name given in language {lang}') @@ -591,7 +583,7 @@ def setup(self): self._metadata, self._origdata = metadata.load_metadata(Path(self.problem.probdir)) self.problem._set_metadata(self._metadata) except ValidationError as e: - error_str = '\n'.join([f' {"->".join((str(loc) for loc in err["loc"]))}: {err["msg"]}' for err in e.errors()]) + error_str = '\n'.join([f' {"->".join(str(loc) for loc in err["loc"])}: {err["msg"]}' for err in e.errors()]) self.fatal(f'Failed parsing problem.yaml. Found {len(e.errors())} errors:\n{error_str}') except Exception as e: self.fatal(f'Failed loading problem configuration: {e}') @@ -705,7 +697,7 @@ class Attachments(ProblemPart): def setup(self): attachments_dir = Path(self.problem.probdir) / 'attachments' self.attachments = [p for p in attachments_dir.iterdir()] if attachments_dir.is_dir() else [] - self.debug(f'Adding attachments {str(self.attachments)}') + self.debug(f'Adding attachments {self.attachments!s}') def check(self, context: Context) -> bool: if self._check_res is not None: diff --git a/tests/default_validator_tests/run_and_generate_expected.py b/tests/default_validator_tests/run_and_generate_expected.py index 6323fdfb..48c61cac 100755 --- a/tests/default_validator_tests/run_and_generate_expected.py +++ b/tests/default_validator_tests/run_and_generate_expected.py @@ -16,9 +16,9 @@ import argparse import subprocess +import sys import tempfile from pathlib import Path -import sys def main(): diff --git a/tests/hello/input_validators/validate.py b/tests/hello/input_validators/validate.py index 2f71d601..d98eacf9 100755 --- a/tests/hello/input_validators/validate.py +++ b/tests/hello/input_validators/validate.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from sys import stdin import sys +from sys import stdin # There shouldn't be any input assert len(stdin.readline()) == 0 diff --git a/tests/test_config.py b/tests/test_config.py index 35628a1b..c9d60e5d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest from problemtools import config diff --git a/tests/test_default_validator.py b/tests/test_default_validator.py index 6623dccb..de2c084c 100644 --- a/tests/test_default_validator.py +++ b/tests/test_default_validator.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -import pytest import subprocess import tempfile from pathlib import Path +import pytest + # The validator executable path, resolved relative to this test file. VALIDATOR_PATH = Path(__file__).parent.parent / 'support' / 'default_validator' / 'default_validator' # The directory containing test cases for the validator. diff --git a/tests/test_languages.py b/tests/test_languages.py index 024558c5..588e9cfc 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- -from unittest import TestCase -import pytest import os import re +from unittest import TestCase + +import pytest from problemtools import languages diff --git a/tests/test_latex.py b/tests/test_latex.py index f3a55296..fcacf13e 100644 --- a/tests/test_latex.py +++ b/tests/test_latex.py @@ -1,6 +1,6 @@ -from pathlib import Path import re import tempfile +from pathlib import Path from problemtools import problem2html, problem2pdf diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 56fa4b66..4382be48 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,8 +1,9 @@ from pathlib import Path import pytest -from tests.test_xss import render, renderpdf + from problemtools.statement_util import find_footnotes +from tests.test_xss import render, renderpdf # TODO: add when guess is updated to 2023-07 # def test_pdf_render(): diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 9652c906..64153874 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- from pathlib import Path import pytest - from pydantic import ValidationError + from problemtools import metadata from problemtools.formatversion import FormatVersion diff --git a/tests/test_output_validator.py b/tests/test_output_validator.py index 04759d0d..3562eb7f 100644 --- a/tests/test_output_validator.py +++ b/tests/test_output_validator.py @@ -1,5 +1,5 @@ -import random import pathlib +import random import string import tempfile diff --git a/tests/test_run_limit.py b/tests/test_run_limit.py index d128954a..73985b7d 100644 --- a/tests/test_run_limit.py +++ b/tests/test_run_limit.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- -from unittest import TestCase import resource +from unittest import TestCase from problemtools.run import limit diff --git a/tests/test_verify_hello.py b/tests/test_verify_hello.py index 180a7f7e..29bc00c3 100644 --- a/tests/test_verify_hello.py +++ b/tests/test_verify_hello.py @@ -1,5 +1,6 @@ import logging import pathlib + import problemtools.verifyproblem as verify from problemtools.diagnostics import LoggingDiagnostics diff --git a/tests/test_xss.py b/tests/test_xss.py index 162c5994..67b40258 100644 --- a/tests/test_xss.py +++ b/tests/test_xss.py @@ -1,8 +1,8 @@ import os -from pathlib import Path -from problemtools import problem2html -from problemtools import problem2pdf import tempfile +from pathlib import Path + +from problemtools import problem2html, problem2pdf def render(problem_path): From f6cbde15b17e3e68d593d377bc25ed63ed7c67d2 Mon Sep 17 00:00:00 2001 From: Gunnar Kreitz Date: Fri, 31 Jul 2026 14:12:11 +0200 Subject: [PATCH 2/2] Explicitly use builtins.type to avoid mypy being confused by the class having a field called type --- problemtools/metadata.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/problemtools/metadata.py b/problemtools/metadata.py index 1ad82461..7548df65 100644 --- a/problemtools/metadata.py +++ b/problemtools/metadata.py @@ -1,3 +1,4 @@ +import builtins import copy import datetime import re @@ -242,7 +243,7 @@ def is_submit_answer(self) -> bool: return ProblemType.SUBMIT_ANSWER in self.type @classmethod - def from_legacy(cls: type[Self], legacy: MetadataLegacy, names_from_statements: dict[str, str]) -> Self: + def from_legacy(cls: builtins.type[Self], legacy: MetadataLegacy, names_from_statements: dict[str, str]) -> Self: metadata = legacy.model_dump() metadata['type'] = [metadata['type']] # Support for *ancient* problems where names_from_statements is empty @@ -292,7 +293,7 @@ def parse_author_field(author: str) -> list[Person]: return cls.model_validate(metadata) @classmethod - def from_2023_07(cls: type[Self], md2023_07: Metadata2023_07) -> Self: + def from_2023_07(cls: builtins.type[Self], md2023_07: Metadata2023_07) -> Self: metadata = md2023_07.model_dump() metadata['type'] = [metadata['type']] if isinstance(metadata['type'], str) else metadata['type'] metadata['name'] = {'en': metadata['name']} if isinstance(metadata['name'], str) else metadata['name']