Source code for oioioi.problems.package

""" This module contains a problem package backend interface. You should
    create a ``package.py`` file in your new app and implement your package
    backend (inheriting from
    :class:`~oioioi.problems.package.ProblemPackageBackend`)
    whenever you introduce a new problem package format.
"""

import logging
import sys

from django.conf import settings
from django.core.files import File
from django.utils.module_loading import import_string
from oioioi.base.utils import ObjectWithMixins, RegisteredSubclassesBase
from oioioi.problems.models import Problem, ProblemPackage

[docs]logger = logging.getLogger(__name__)
[docs]class ProblemPackageError(Exception): """A generic exception to be used by or subclassed by backends.""" pass
[docs]class PackageProcessingError(ProblemPackageError): def __init__(self, func_name, func_doc): super(PackageProcessingError, self).__init__() self.original_exception_info = sys.exc_info() self.raiser = func_name self.raiser_desc = ' '.join(func_doc.split())
[docs]class ProblemPackageBackend(RegisteredSubclassesBase, ObjectWithMixins): """A class which manages problem packages. The main functionality is extracting archives with problem statements, data, model solutions etc. and building :class:`~oioioi.problems.models.Problem` instances. """
[docs] description = '__human_readable_name_here__'
[docs] abstract = True
[docs] modules_with_subclasses = 'package'
[docs] def identify(self, path, original_filename=None): """Checks if the backend is suitable for processing the specified problem package. :param path: a path to the processed problem package :param original_filename: the name of the package specified by the uploading user. Returns ``True`` if the backend can handle the specified problem package file. """ raise NotImplementedError
[docs] def get_short_name(self, path, original_filename=None): """Returns the problem's short name. :param path: a path to the processed problem package :param original_filename: the name of the package specified by the uploading user. """ raise NotImplementedError
[docs] def unpack(self, env): """Processes a package, creating a new :class:`~oioioi.problems.models.Problem` or updating an existing one. This function will be called either from :func:`~oioioi.problems.unpackmgr.unpackmgr_job` (Celery task) or from :func:`~oioioi.problems.package.simple_unpack` (e.g. when a problem is added from a command line). Used ``env`` keys: ``package_id``: an id of the :class:`~oioioi.problems.models.ProblemPackage` instance with the package file to unpack. Produced ``env`` keys: ``problem_id``: an id of the :class:`~oioioi.problems.models.Problem` instance representing the created or modified problem. """ raise NotImplementedError
[docs] def simple_unpack(self, filename, existing_problem=None): """This function may be used for unpacking outside unpackmgr. :param filename: a path to the problem package file :param existing_problem: an instance of :class:`~oioioi.problems.models.Problem` to be changed. If ``None``, a new :class:`~oioioi.problems.models.Problem` is created. Returns a :class:`~oioioi.problems.models.Problem` instance. """ problem = None pp = ProblemPackage(problem=existing_problem) pp.package_file.save(filename, File(open(filename, 'rb'))) env = {} if existing_problem: if existing_problem.author: env['author'] = existing_problem.author.username pp.problem_name = existing_problem.short_name else: pp.problem_name = self.get_short_name(filename) pp.save() env['package_id'] = pp.id with pp.save_operation_status(): self.unpack(env) problem = Problem.objects.get(id=env['problem_id']) pp.problem = problem pp.save() return problem
[docs] def pack(self, problem): """Creates a package from problem, returns a :class:`django.http.HttpResponse` instance. Should raise ``NotImplementedError`` if creating packages is not supported. """ raise NotImplementedError
[docs]class NoBackend(NotImplementedError): pass
[docs]def backend_for_package(filename, original_filename=None): """Finds a backend suitable for unpacking the given package and returns its dotted name. :param filename: a path to the processed problem package :param original_filename: the name of the package specified by the uploading user. """ for backend_name in settings.PROBLEM_PACKAGE_BACKENDS: try: backend = import_string(backend_name)() if backend.identify(filename, original_filename): return backend_name # pylint: disable=broad-except except Exception: logger.warning('Backend %s probe failed', backend_name, exc_info=True) raise NoBackend('Problem pack format not recognized')