Skip to content

Commit

Permalink
Write the machinery required to make Importer and Reimporter fully mo…
Browse files Browse the repository at this point in the history
…dular.

Now there can be many loader, selectable at run time, with also
the possibility of autodetection.
  • Loading branch information
giomasce committed May 28, 2013
1 parent 53705b2 commit 9f4c65d
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 26 deletions.
35 changes: 23 additions & 12 deletions cmscontrib/YamlImporter.py → cmscontrib/Importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-

# Programming contest management system
# Copyright © 2010-2012 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2013 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2012 Stefano Maggiolo <[email protected]>
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013 Luca Wehrstedt <[email protected]>
Expand All @@ -20,9 +20,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""This script imports a contest from disk using YamlLoader.
"""This script imports a contest from disk using one of the available
loaders.
The data parsed by YamlLoader is used to create a new Contest in the
The data parsed by the loader is used to create a new Contest in the
database.
"""
Expand All @@ -36,30 +37,31 @@

from cms import logger
from cms.db.FileCacher import FileCacher
from cms.db.SQLAlchemyAll import metadata, SessionGen, FSObject, User, \
from cms.db.SQLAlchemyAll import metadata, SessionGen, User, \
drop_everything

from cmscontrib.YamlLoader import YamlLoader
from cmscontrib.Loaders import choose_loader, build_epilog


class Importer:

"""This script imports a contest from disk using YamlLoader.
"""This script imports a contest from disk using one of the
available loaders.
The data parsed by YamlLoader is used to create a new Contest in
The data parsed by the loader is used to create a new Contest in
the database.
"""

def __init__(self, path, drop, test, zero_time, user_number):
def __init__(self, path, drop, test, zero_time, user_number, loader_class):
self.drop = drop
self.test = test
self.zero_time = zero_time
self.user_number = user_number

self.file_cacher = FileCacher()

self.loader = YamlLoader(os.path.realpath(path), self.file_cacher)
self.loader = loader_class(os.path.realpath(path), self.file_cacher)

def _prepare_db(self):
logger.info("Creating database structure.")
Expand Down Expand Up @@ -123,29 +125,38 @@ def do_import(self):

def main():
"""Parse arguments and launch process."""

parser = argparse.ArgumentParser(
description="Import a contest from disk using YamlLoader")
description="Import a contest from disk",
epilog=build_epilog(),
formatter_class=argparse.RawDescriptionHelpFormatter)
group = parser.add_mutually_exclusive_group()
group.add_argument("-z", "--zero-time", action="store_true",
help="set to zero contest start and stop time")
group.add_argument("-t", "--test", action="store_true",
help="setup a contest for testing "
"(times: 0, 2*10^9; ips: unset, passwords: a)")
"(times: 1970, 2100; ips: unset, passwords: a)")
parser.add_argument("-d", "--drop", action="store_true",
help="drop everything from the database "
"before importing")
parser.add_argument("-n", "--user-number", action="store", type=int,
help="put N random users instead of importing them")
parser.add_argument("-L", "--loader", action="store", default=None,
help="use the specified loader (default: autodetect)")
parser.add_argument("import_directory",
help="source directory from where import")

args = parser.parse_args()
loader_class = choose_loader(args.loader,
args.import_directory,
parser.error)

Importer(path=args.import_directory,
drop=args.drop,
test=args.test,
zero_time=args.zero_time,
user_number=args.user_number).do_import()
user_number=args.user_number,
loader_class=loader_class).do_import()


if __name__ == "__main__":
Expand Down
75 changes: 75 additions & 0 deletions cmscontrib/Loaders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# Programming contest management system
# Copyright © 2013 Giovanni Mascellani <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""List of loaders known by CMS, with some support functions.
"""

from cmscontrib.YamlLoader import YamlLoader

LOADERS = dict((loader_class.short_name(), loader_class)
for loader_class in [YamlLoader])


def choose_loader(arg, path, error_callback):
"""Decide which loader to use, depending upon the specified
argument and possibly performing an autodetection.
The autodetection is done by calling detect() on all the known
loaders and returning the only one that returns True. If no one or
more than one return True, then the autodetection is considered
failed and None is returned.
arg (string): the argument, possibly None, passed to the program
as loader specification.
path (string): the path passed to the program from which to
perform the loading.
error_callback (method): a method to call to report errors.
return (class): the chosen loader class.
"""
if arg is not None:
try:
return LOADERS[arg]
except KeyError:
error_callback("Specified loader doesn't exist")
else:
res = None
for loader in LOADERS.itervalues():
if loader.detect(path):
if res is None:
res = loader
else:
error_callback(
"Couldn't autodetect the loader, "
"please specify it: more than one "
"loader accepted the detection")
if res is None:
error_callback("Couldn't autodetect the loader, "
"please specify it: no "
"loader accepted the detection")
return res


def build_epilog():
epilog = "The following loaders are supported:\n"
for short_name, loader_class in sorted(LOADERS.items()):
epilog += " * %s (%s)\n" % (short_name, loader_class.description())
return epilog
30 changes: 20 additions & 10 deletions cmscontrib/YamlReimporter.py → cmscontrib/Reimporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-

# Programming contest management system
# Copyright © 2010-2012 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2013 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2012 Stefano Maggiolo <[email protected]>
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013 Luca Versari <[email protected]>
Expand All @@ -21,9 +21,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""This script reimports a contest from disk using YamlLoader.
"""This script reimports a contest from disk using one of the
available loaders.
The data parsed by YamlLoader is used to update a Contest that's
The data parsed by the loader is used to update a Contest that's
already existing in the database.
"""
Expand All @@ -38,7 +39,7 @@
from cms.db.SQLAlchemyAll import \
SessionGen, Base, Contest, User, Task, Submission

from cmscontrib.YamlLoader import YamlLoader
from cmscontrib.Loaders import choose_loader, build_epilog


def _is_rel(prp, attr):
Expand All @@ -48,20 +49,21 @@ def _is_rel(prp, attr):

class Reimporter:

"""This script reimports a contest from disk using YamlLoader
"""This script reimports a contest from disk using the specified
loader.
The data parsed by YamlLoader is used to update a Contest that's
The data parsed by the loader is used to update a Contest that's
already existing in the database.
"""

def __init__(self, path, contest_id, force):
def __init__(self, path, contest_id, force, loader_class):
self.old_contest_id = contest_id
self.force = force

self.file_cacher = FileCacher()

self.loader = YamlLoader(os.path.realpath(path), self.file_cacher)
self.loader = loader_class(os.path.realpath(path), self.file_cacher)

def _update_columns(self, old_object, new_object):
for prp in old_object._col_props:
Expand Down Expand Up @@ -285,23 +287,31 @@ def do_reimport(self):
def main():
"""Parse arguments and launch process."""
parser = argparse.ArgumentParser(
description="Reimport a contest from disk using YamlLoader")
description="Reimport a contest from disk",
epilog=build_epilog(),
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-c", "--contest-id", action="store", type=int,
help="id of contest to overwrite")
parser.add_argument("-f", "--force", action="store_true",
help="force the reimport even if some users or tasks "
"may get lost")
parser.add_argument("-L", "--loader", action="store", default=None,
help="use the specified loader (default: autodetect)")
parser.add_argument("import_directory",
help="source directory from where import")

args = parser.parse_args()
loader_class = choose_loader(args.loader,
args.import_directory,
parser.error)

if args.contest_id is None:
args.contest_id = ask_for_contest()

Reimporter(path=args.import_directory,
contest_id=args.contest_id,
force=args.force).do_reimport()
force=args.force,
loader_class=loader_class).do_reimport()


if __name__ == "__main__":
Expand Down
30 changes: 29 additions & 1 deletion cmscontrib/YamlLoader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-

# Programming contest management system
# Copyright © 2010-2012 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2013 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2012 Stefano Maggiolo <[email protected]>
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013 Luca Wehrstedt <[email protected]>
Expand Down Expand Up @@ -72,6 +72,34 @@ def __init__(self, path, file_cacher):
self.path = path
self.file_cacher = file_cacher

@classmethod
def short_name(cls):
"""Short name of this loader.
"""
return 'italy_yaml'

@classmethod
def description(cls):
"""Description of this loader.
"""
return 'Italian YAML-based format'

@classmethod
def detect(cls, path):
"""Detect whether this loader is able to interpret the given
path.
path (string): the path to scan.
return (bool): True if the loader is able to interpret the
given path.
"""
# Not really refined...
return os.path.exists(os.path.join(path, "contest.yaml"))

def get_contest(self):

"""Produce a Contest object.
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-

# Programming contest management system
# Copyright © 2010-2012 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2013 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2013 Stefano Maggiolo <[email protected]>
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
#
Expand Down Expand Up @@ -125,8 +125,8 @@ def do_setup():
"cmsRemoveUser=cmscontrib.RemoveUser:main",
"cmsRemoveTask=cmscontrib.RemoveTask:main",
"cmsComputeComplexity=cmscontrib.ComputeComplexity:main",
"cmsYamlImporter=cmscontrib.YamlImporter:main",
"cmsYamlReimporter=cmscontrib.YamlReimporter:main",
"cmsImporter=cmscontrib.Importer:main",
"cmsReimporter=cmscontrib.Reimporter:main",
"cmsSpoolExporter=cmscontrib.SpoolExporter:main",
"cmsContestExporter=cmscontrib.ContestExporter:main",
"cmsContestImporter=cmscontrib.ContestImporter:main",
Expand Down

0 comments on commit 9f4c65d

Please sign in to comment.