""" 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] 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')