diff --git a/src/sio3pack/packages/package/model.py b/src/sio3pack/packages/package/model.py index cea7afd..85972e1 100644 --- a/src/sio3pack/packages/package/model.py +++ b/src/sio3pack/packages/package/model.py @@ -4,8 +4,6 @@ from sio3pack.graph.graph import Graph from sio3pack.packages.exceptions import UnknownPackageType from sio3pack.test.test import Test - -from sio3pack.packages import all_packages from sio3pack.utils.archive import Archive from sio3pack.utils.classinit import RegisteredSubclassesBase @@ -14,6 +12,7 @@ class Package(RegisteredSubclassesBase): """ Base class for all packages. """ + abstract = True def __init__(self, file: File): diff --git a/src/sio3pack/packages/sinolpack/enums.py b/src/sio3pack/packages/sinolpack/enums.py index 6f81e47..0f44533 100644 --- a/src/sio3pack/packages/sinolpack/enums.py +++ b/src/sio3pack/packages/sinolpack/enums.py @@ -8,10 +8,10 @@ class ModelSolutionKind(Enum): @classmethod def from_regex(cls, group): - if group == '': + if group == "": return cls.NORMAL - if group == 's': + if group == "s": return cls.SLOW - if group == 'b': + if group == "b": return cls.INCORRECT raise ValueError(f"Invalid model solution kind: {group}") diff --git a/src/sio3pack/packages/sinolpack/model.py b/src/sio3pack/packages/sinolpack/model.py index 374f5e1..65fb3b8 100644 --- a/src/sio3pack/packages/sinolpack/model.py +++ b/src/sio3pack/packages/sinolpack/model.py @@ -1,9 +1,9 @@ -import json import os import re -import yaml import tempfile +import yaml + from sio3pack.files.file import File from sio3pack.graph.graph import Graph from sio3pack.graph.graph_manager import GraphManager @@ -26,7 +26,7 @@ def _find_main_dir(cls, archive: Archive) -> str | None: toplevel_dirs = list(set(f.split(os.sep)[0] for f in dirs)) problem_dirs = [] for dir in toplevel_dirs: - for required_subdir in ('in', 'out'): + for required_subdir in ("in", "out"): if all(f.split(os.sep)[:2] != [dir, required_subdir] for f in dirs): break else: @@ -68,7 +68,7 @@ def __init__(self, file: File, django_settings=None): self.rootdir = file.path try: - graph_file = self.get_in_root('graph.json') + graph_file = self.get_in_root("graph.json") self.graph_manager = GraphManager.from_file(graph_file) except FileNotFoundError: self.has_custom_graph = False @@ -76,12 +76,16 @@ def __init__(self, file: File, django_settings=None): self.django_settings = django_settings def _default_graph_manager(self) -> GraphManager: - return GraphManager({ - "unpack": Graph.from_dict({ - "name": "unpack", - # ... - }) - }) + return GraphManager( + { + "unpack": Graph.from_dict( + { + "name": "unpack", + # ... + } + ) + } + ) def _get_from_django_settings(self, key: str, default=None): if self.django_settings is None: @@ -92,7 +96,7 @@ def get_doc_dir(self) -> str: """ Returns the path to the directory containing the problem's documents. """ - return os.path.join(self.rootdir, 'doc') + return os.path.join(self.rootdir, "doc") def get_in_doc_dir(self, filename: str) -> File: """ @@ -110,7 +114,7 @@ def get_prog_dir(self) -> str: """ Returns the path to the directory containing the problem's program files. """ - return os.path.join(self.rootdir, 'prog') + return os.path.join(self.rootdir, "prog") def get_in_prog_dir(self, filename: str) -> File: """ @@ -122,7 +126,7 @@ def get_attachments_dir(self) -> str: """ Returns the path to the directory containing the problem's attachments. """ - return os.path.join(self.rootdir, 'attachments') + return os.path.join(self.rootdir, "attachments") def _process_package(self): self._process_config_yml() @@ -141,7 +145,7 @@ def _process_config_yml(self): Process the config.yml file. If it exists, it will be loaded into the config attribute. """ try: - config = self.get_in_root('config.yml') + config = self.get_in_root("config.yml") self.config = yaml.safe_load(config.read()) except FileNotFoundError: self.config = {} @@ -155,14 +159,14 @@ def _detect_full_name(self): Example of how the ``title`` tag may look like: \title{A problem} """ - if 'title' in self.config: - self.full_name = self.config['title'] + if "title" in self.config: + self.full_name = self.config["title"] return try: - source = self.get_in_doc_dir(self.short_name + 'zad.tex') + source = self.get_in_doc_dir(self.short_name + "zad.tex") text = source.read() - r = re.search(r'^[^%]*\\title{(.+)}', text, re.MULTILINE) + r = re.search(r"^[^%]*\\title{(.+)}", text, re.MULTILINE) if r is not None: self.full_name = r.group(1) except FileNotFoundError: @@ -174,8 +178,8 @@ def _detect_full_name_translations(self): two-letter language code defined in ``settings.py``), if any such key is given. """ self.lang_titles = {} - for lang_code, lang in self._get_from_django_settings('LANGUAGES', [('en', 'English')]): - key = 'title_%s' % lang_code + for lang_code, lang in self._get_from_django_settings("LANGUAGES", [("en", "English")]): + key = "title_%s" % lang_code if key in self.config: self.lang_titles[lang_code] = self.config[key] @@ -184,8 +188,7 @@ def get_submittable_extensions(self): Returns a list of extensions that are submittable. """ return self.config.get( - 'submittable_langs', - self._get_from_django_settings('SUBMITTABLE_LANGUAGES', ['c', 'cpp', 'cxx', 'py']) + "submittable_langs", self._get_from_django_settings("SUBMITTABLE_LANGUAGES", ["c", "cpp", "cxx", "py"]) ) def get_model_solution_regex(self): @@ -193,7 +196,7 @@ def get_model_solution_regex(self): Returns the regex used to determine model solutions. """ extensions = self.get_submittable_extensions() - return rf'^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.(' + '|'.join(extensions) + ')' + return rf"^{self.short_name}[0-9]*([bs]?)[0-9]*(_.*)?\.(" + "|".join(extensions) + ")" def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, str]]: """ @@ -208,13 +211,17 @@ def _get_model_solutions(self) -> list[tuple[ModelSolutionKind, str]]: return model_solutions - def sort_model_solutions(self, model_solutions: list[tuple[ModelSolutionKind, str]]) -> list[tuple[ModelSolutionKind, str]]: + def sort_model_solutions( + self, model_solutions: list[tuple[ModelSolutionKind, str]] + ) -> list[tuple[ModelSolutionKind, str]]: """ Sorts model solutions by kind. """ + def sort_key(model_solution): kind, name = model_solution return kind.value, naturalsort_key(name[: name.index(".")]) + return list(sorted(model_solutions, key=sort_key)) def _process_prog_files(self): @@ -233,13 +240,17 @@ def _process_prog_files(self): self.additional_files = self.graph_manager.get_prog_files() else: self.additional_files = [] - self.additional_files.extend(self.config.get('extra_compilation_files', [])) - self.additional_files.extend(self.config.get('extra_execution_files', [])) + self.additional_files.extend(self.config.get("extra_compilation_files", [])) + self.additional_files.extend(self.config.get("extra_execution_files", [])) extensions = self.get_submittable_extensions() self.special_files: dict[str, bool] = {} - for file in ('ingen', 'inwer', 'soc', 'chk'): + for file in ("ingen", "inwer", "soc", "chk"): try: - self.additional_files.append(File.get_file_matching_extension(self.get_prog_dir(), self.short_name + file, extensions).filename) + self.additional_files.append( + File.get_file_matching_extension( + self.get_prog_dir(), self.short_name + file, extensions + ).filename + ) self.special_files[file] = True except FileNotFoundError: self.special_files[file] = False @@ -256,12 +267,14 @@ def _process_statements(self): if not os.path.exists(docdir): return - lang_prefs = [''] + ['-' + l[0] for l in self._get_from_django_settings('LANGUAGES', [('en', 'English'), ('pl', 'Polish')])] + lang_prefs = [""] + [ + "-" + l[0] for l in self._get_from_django_settings("LANGUAGES", [("en", "English"), ("pl", "Polish")]) + ] self.lang_statements = {} for lang in lang_prefs: try: - htmlzipfile = self.get_in_doc_dir(self.short_name + 'zad' + lang + '.html.zip') + htmlzipfile = self.get_in_doc_dir(self.short_name + "zad" + lang + ".html.zip") # TODO: what to do with html? # if self._html_disallowed(): # raise ProblemPackageError( @@ -281,8 +294,8 @@ def _process_statements(self): pass try: - pdffile = self.get_in_doc_dir(self.short_name + 'zad' + lang + '.pdf') - if lang == '': + pdffile = self.get_in_doc_dir(self.short_name + "zad" + lang + ".pdf") + if lang == "": self.statement = pdffile else: self.lang_statements[lang[1:]] = pdffile @@ -290,9 +303,7 @@ def _process_statements(self): pass def _process_attachments(self): - """ - - """ + """ """ attachments_dir = self.get_attachments_dir() if not os.path.isdir(attachments_dir): return @@ -305,7 +316,7 @@ def _process_attachments(self): def get_unpack_graph(self) -> GraphOperation | None: try: return GraphOperation( - self.graph_manager.get('unpack'), + self.graph_manager.get("unpack"), True, self._unpack_return_data, ) diff --git a/src/sio3pack/util.py b/src/sio3pack/util.py index 0bc26a3..855f96f 100644 --- a/src/sio3pack/util.py +++ b/src/sio3pack/util.py @@ -1,9 +1,6 @@ import re -import tarfile -import zipfile -from sio3pack.files.file import File def naturalsort_key(key): convert = lambda text: int(text) if text.isdigit() else text - return [convert(c) for c in re.split('([0-9]+)', key)] + return [convert(c) for c in re.split("([0-9]+)", key)] diff --git a/src/sio3pack/utils/archive.py b/src/sio3pack/utils/archive.py index 1a86127..847eb81 100644 --- a/src/sio3pack/utils/archive.py +++ b/src/sio3pack/utils/archive.py @@ -41,7 +41,7 @@ class UnsafeArchive(ArchiveException): """ -def extract(path, to_path='', ext='', **kwargs): +def extract(path, to_path="", ext="", **kwargs): """ Unpack the tar or zip file at the specified path to the directory specified by to_path. @@ -54,7 +54,7 @@ class Archive(object): The external API class that encapsulates an archive implementation. """ - def __init__(self, file, ext=''): + def __init__(self, file, ext=""): """ Arguments: * 'file' can be a string path to a file or a file-like object. @@ -66,7 +66,7 @@ def __init__(self, file, ext=''): self._archive = self._archive_cls(self.filename, ext=ext)(self.filename) @staticmethod - def _archive_cls(file, ext=''): + def _archive_cls(file, ext=""): """ Return the proper Archive implementation class, based on the file type. """ @@ -78,9 +78,7 @@ def _archive_cls(file, ext=''): try: filename = file.name except AttributeError: - raise UnrecognizedArchiveFormat( - "File object not a recognized archive format." - ) + raise UnrecognizedArchiveFormat("File object not a recognized archive format.") lookup_filename = filename + ext base, tail_ext = os.path.splitext(lookup_filename.lower()) cls = extension_map.get(tail_ext) @@ -88,9 +86,7 @@ def _archive_cls(file, ext=''): base, ext = os.path.splitext(base) cls = extension_map.get(ext) if not cls: - raise UnrecognizedArchiveFormat( - "Path not a recognized archive format: %s" % filename - ) + raise UnrecognizedArchiveFormat("Path not a recognized archive format: %s" % filename) return cls @classmethod @@ -152,10 +148,10 @@ def _extract(self, to_path): """ self._archive.extractall(to_path) - def extract(self, to_path='', method='safe'): - if method == 'safe': + def extract(self, to_path="", method="safe"): + if method == "safe": self.check_files(to_path) - elif method == 'insecure': + elif method == "insecure": pass else: raise ValueError("Invalid method option") @@ -175,8 +171,7 @@ def check_files(self, to_path=None): extract_path = os.path.normpath(os.path.realpath(extract_path)) if not extract_path.startswith(target_path): raise UnsafeArchive( - "Archive member destination is outside the target" - " directory. member: %s" % filename + "Archive member destination is outside the target" " directory. member: %s" % filename ) @@ -189,14 +184,10 @@ def __init__(self, file): self._archive = tarfile.open(fileobj=file) def filenames(self): - return [ - tarinfo.name for tarinfo in self._archive.getmembers() if tarinfo.isfile() - ] + return [tarinfo.name for tarinfo in self._archive.getmembers() if tarinfo.isfile()] def dirnames(self): - return [ - tarinfo.name for tarinfo in self._archive.getmembers() if tarinfo.isdir() - ] + return [tarinfo.name for tarinfo in self._archive.getmembers() if tarinfo.isdir()] def extracted_size(self): total = 0 @@ -226,25 +217,17 @@ def extracted_size(self): return total def filenames(self): - return [ - zipinfo.filename - for zipinfo in self._archive.infolist() - if not zipinfo.is_dir() - ] + return [zipinfo.filename for zipinfo in self._archive.infolist() if not zipinfo.is_dir()] def dirnames(self): - return [ - zipinfo.filename - for zipinfo in self._archive.infolist() - if zipinfo.is_dir() - ] + return [zipinfo.filename for zipinfo in self._archive.infolist() if zipinfo.is_dir()] extension_map = { - '.tar': TarArchive, - '.tar.bz2': TarArchive, - '.tar.gz': TarArchive, - '.tgz': TarArchive, - '.tz2': TarArchive, - '.zip': ZipArchive, + ".tar": TarArchive, + ".tar.bz2": TarArchive, + ".tar.gz": TarArchive, + ".tgz": TarArchive, + ".tz2": TarArchive, + ".zip": ZipArchive, } diff --git a/src/sio3pack/utils/classinit.py b/src/sio3pack/utils/classinit.py index 5330916..a1356b4 100644 --- a/src/sio3pack/utils/classinit.py +++ b/src/sio3pack/utils/classinit.py @@ -1,5 +1,6 @@ # From oioioi/base/utils/__init__.py + class ClassInitMeta(type): """Meta class triggering __classinit__ on class intialization.""" @@ -56,28 +57,24 @@ class RegisteredSubclassesBase(ClassInitBase): @classmethod def __classinit__(cls): - this_cls = globals().get('RegisteredSubclassesBase', cls) + this_cls = globals().get("RegisteredSubclassesBase", cls) super(this_cls, cls).__classinit__() if this_cls is cls: # This is RegisteredSubclassesBase class. return - assert 'subclasses' not in cls.__dict__, ( - '%s defines attribute subclasses, but has ' - 'RegisteredSubclassesMeta metaclass' % (cls,) - ) + assert ( + "subclasses" not in cls.__dict__ + ), "%s defines attribute subclasses, but has " "RegisteredSubclassesMeta metaclass" % (cls,) cls.subclasses = [] - cls.abstract = cls.__dict__.get('abstract', False) + cls.abstract = cls.__dict__.get("abstract", False) def find_superclass(cls): superclasses = [c for c in cls.__bases__ if issubclass(c, this_cls)] if not superclasses: return None if len(superclasses) > 1: - raise AssertionError( - '%s derives from more than one ' - 'RegisteredSubclassesBase' % (cls.__name__,) - ) + raise AssertionError("%s derives from more than one " "RegisteredSubclassesBase" % (cls.__name__,)) superclass = superclasses[0] return superclass diff --git a/tests/fixtures.py b/tests/fixtures.py index 5da0f01..7e31c57 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -24,9 +24,9 @@ def _tar_archive(dir, dest, compression=None): Create a tar archive of the specified directory. """ if compression is None: - mode = 'w' + mode = "w" else: - mode = f'w:{compression}' + mode = f"w:{compression}" with tarfile.open(dest, mode) as tar: tar.add(dir, arcname=os.path.basename(dir)) @@ -35,7 +35,7 @@ def _zip_archive(dir, dest): """ Create a zip archive of the specified directory. """ - with zipfile.ZipFile(dest, 'w') as zip: + with zipfile.ZipFile(dest, "w") as zip: for root, dirs, files in os.walk(dir): for file in files: zip.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), dir)) @@ -70,6 +70,7 @@ def _create_package(package_name, tmpdir, archive=False, extension="zip"): return package_path, type + @pytest.fixture def package(request): """ diff --git a/tests/packages/sinolpack/test_sinolpack.py b/tests/packages/sinolpack/test_sinolpack.py index f4c5351..c591fad 100644 --- a/tests/packages/sinolpack/test_sinolpack.py +++ b/tests/packages/sinolpack/test_sinolpack.py @@ -2,7 +2,7 @@ import pytest -from tests.fixtures import package, package_archived, Compression, all_compressions +from tests.fixtures import Compression, all_compressions, package, package_archived @pytest.mark.parametrize("package", ["simple"], indirect=True) diff --git a/tests/test_packages/simple/__init__.py b/tests/test_packages/simple/__init__.py index b2b61e9..0f21af5 100644 --- a/tests/test_packages/simple/__init__.py +++ b/tests/test_packages/simple/__init__.py @@ -1,2 +1,2 @@ -TASK_ID = 'abc' -TYPE = 'sinolpack' +TASK_ID = "abc" +TYPE = "sinolpack"