<commit_before>"""
Run tool command.
"""
from everest.entities.utils import get_root_aggregate
from everest.mime import JsonMime
from everest.querying.specifications import eq
from everest.repositories.interfaces import IRepositoryManager
from everest.repositories.rdb import Session
from everest.representers.utils import as_representer
from everest.resources.interfaces import IService
from everest.resources.utils import get_collection_class
from everest.utils import classproperty
from paste.deploy import appconfig # pylint: disable=E0611,F0401
from paste.script.command import Command # pylint: disable=E0611,F0401
from pyramid.path import DottedNameResolver
from pyramid.registry import Registry
from pyramid.testing import DummyRequest
from thelma.automation.tools.stock.sampleregistration import \
    IMoleculeDesignPoolRegistrationItem
from thelma.automation.tools.stock.sampleregistration import \
    IMoleculeDesignRegistrationItem
from thelma.automation.tools.stock.sampleregistration import \
    ISampleRegistrationItem
from thelma.automation.tools.stock.sampleregistration import \
    ISupplierSampleRegistrationItem
from thelma.interfaces import ILibraryCreationIso
from thelma.interfaces import IMoleculeDesignLibrary
from thelma.interfaces import ITube
from thelma.interfaces import ITubeTransferWorklist
from thelma.interfaces import IUser
from thelma.run import create_config
from zope.sqlalchemy import ZopeTransactionExtension # pylint: disable=E0611,F0401
import logging
import os
import sys
import transaction
#from thelma.interfaces import ITubeTransfer
#from thelma.automation.tools.libcreation.ticket \
#    import LibraryCreationStockTransferReporter
#from thelma.automation.tools.libcreation.ticket \
#    import LibraryCreationTicketWorklistUploader

__docformat__ = 'reStructuredText en'
__all__ = ['EmptyTubeRegistrarToolCommand',
           'LibraryCreationExecutorToolCommand',
           'LibraryCreationWorklistWriterToolCommand',
           'LibraryGeneratorToolCommand',
           'LibraryIsoGeneratorToolCommand',
           'LibraryIsoPopulatorToolCommand',
           'MetaToolCommand',
           'ToolCommand',
           'XL20ExecutorToolCommand',
           ]


class MetaToolCommand(type):
    """
    Meta class for tool commands.

    This ensures the option parser and the summary and usage doc strings are
    set up automatically for the tool to run; this is much preferable to
    having to define a new entry point for every individual tool we want to
    run.
    """
    def __new__(mcs, name, bases, class_dict):
        # Ignore base classes.
        if name != 'ToolCommand' and not name.startswith('_'):
            parser = ToolCommand.make_standard_parser(simulate=True)
            if not 'name' in class_dict:
                raise ValueError('You need to define a name for the tool '
                                 'to run as "name" attribute in the class '
                                 'namespace of your tool (%s).' % name)
            if not 'tool' in class_dict:
                raise ValueError('You need to define the pkg resource '
                                 'style dotted name (absolute) of the '
                                 'tool to run as "tool" attribute in the '
                                 'class namespace of your tool (%s).' % name)
            option_defs = \
                class_dict.get('option_defs') or bases[-1].option_defs
            if option_defs is None:
                raise ValueError('You need to define a list of option '
                                 'definitions as "option_defs" attribute '
                                 'in the class namespace of your tool (%s).'
                                 % name)
            for opt_name, attr_name, opt_kw in option_defs:
                if 'dest' in opt_kw:
                    raise ValueError('Cannot use the "dest" parameter for '
                                     'tool command options.')
                # We need a copy here in case the option defs are reused in
                # derived classes.
                copied_opt_kw = opt_kw.copy()
                copied_opt_kw['dest'] = attr_name
                parser.add_option(opt_name, **copied_opt_kw)
            summary = class_dict.__doc__.strip()
            class_dict['summary'] = summary
            usage = ToolCommand.__dict__['_usage_template'] \
                                  % class_dict['name']
            class_dict['usage'] = usage
            class_dict['parser'] = parser
        cls = type.__new__(mcs, name, bases, class_dict)
        # This is the magic: If this is the command we are running,
        # set the new class as the target class of the ToolCommand.
        if class_dict.get('name') == sys.argv[-2]:
            ToolCommand.set_target_class(cls)
        return cls


class LazyOption(object):
    """
    Simple capsule for options that can only be initialized when TheLMA has
    started up.
    """
    def __init__(self, value_callback):
        self.__value_callback = value_callback
        self.__option_value = None

    def __call__(self, option, option_name, option_value, parser): # pylint: disable=W0613
        self.__option_value = option_value
        setattr(parser.values, option.dest, self)

    def initialize(self, tool_class, options):
        return self.__value_callback(tool_class, self.__option_value, options)


class ToolCommand(Command): # no __init__ pylint: disable=W0232
    """
    Abstract base class for paste commands which run a TheLMA tool from the
    command line.
    """
    __metaclass__ = MetaToolCommand
    #: Template for usage strings in derived classes.
    _usage_template = "%s <ini file>"
    #: This is the target tool command class (set by the meta class).
    __target_class = None
    #: pkg resource style (absolute) dotted name to the tool to run.
    tool = None
    #: A sequence of the form
    #:     [<option name>,<attribute name>, <option kewyword dict>]
    #: This is used to configure the option parser in the derived command
    #: class and to extract the constructor arguments from the options.
    option_defs = None
    #: The name of the tool used on the command line.
    name = None
    #: Options group name for help messages.
    group_name = "thelma"
    #: Minimum number of command line arguments.
    min_args = 2
    #: Maximum number of command line arguments.
    max_args = 2

    @classmethod
    def make_standard_parser(verbose=True,
                             interactive=False,
                             no_interactive=False,
                             simulate=False,
                             quiet=False,
                             overwrite=False):
        parser = Command.standard_parser(verbose=verbose,
                                         interactive=interactive,
                                         no_interactive=no_interactive,
                                         simulate=simulate,
                                         quiet=quiet,
                                         overwrite=overwrite)
        parser.add_option('--ignore-warnings',
                          action='store_true',
                          dest='ignore_warnings',
                          help="If true, changes are committed even when the "
                               "tool reports warnings (unless the --simulate "
                               "flag is set).")
        return parser

    @classmethod
    def set_target_class(cls, target_class):
        cls.__target_class = target_class

    @classmethod
    def finalize(cls, tool):
        """
        Override this method in derived classes to perform actions after the
        tool has run.
        """
        pass

    @classmethod
    def report(cls, tool):
        """
        Override this method in drived classes to perform reporting actions
        after the tool has run and all data have been committed.
        """
        pass

    @classproperty
    def summary(cls):
        if cls.__target_class is None:
            raise RuntimeError('Tool command "%s" not found!' % sys.argv[-2])
        return cls.__target_class.summary

    @classproperty
    def usage(cls):
        if cls.__target_class is None:
            raise RuntimeError('Tool command "%s" not found!' % sys.argv[-2])
        return cls.__target_class.usage

    @classproperty
    def parser(cls):
        if cls.__target_class is None:
            raise RuntimeError('Tool command "%s" not found!' % sys.argv[-2])
        return cls.__target_class.parser

    def command(self):
        ini_file = self.args[-1] # pylint: disable=E1101
        # TheLMA setup.
        config = self.__setup_thelma(ini_file)
        # Initialize the tool and run it.
        rsv = DottedNameResolver(None)
        tool_cls = rsv.resolve(self.__target_class.tool)
        arg_names = [od[1] for od in self.__target_class.option_defs]
        kw = {}
        # Initializing lazy options. We pass the target class and the
        # options so the callback has access to them.
        opts = self.options # pylint: disable=E1101
        for arg_name in arg_names:
            arg_value = getattr(opts, arg_name)
            if isinstance(arg_value, LazyOption):
                arg_value = arg_value.initialize(self.__target_class, opts)
                setattr(opts, arg_name, arg_value)
        kw = dict((arg_name, getattr(opts, arg_name))
                  for arg_name in arg_names)
        tool = tool_cls(**kw)
        try:
            tool.run()
        except: # catch all pylint: disable=W0702
            transaction.abort()
            raise
        else:
            if tool.has_errors():
                err_msgs = tool.log.get_messages()
                msg = 'Errors occurred during the tool run. Error messages:\n'
                raise RuntimeError(msg + os.linesep.join(err_msgs))
            warn_msgs = tool.get_messages(logging_level=logging.WARNING)
            if warn_msgs \
               and not self.options.ignore_warnings: # pylint: disable=E1101
                msg = 'Warnings occurred during the tool run. You can ' \
                      'repeat the run with the --ignore-warnings switch ' \
                      'to force changes to be committed. Warning messages:\n'
                raise RuntimeError(msg + os.linesep.join(warn_msgs))
            run_report = False
            try:
                # This gives the tool command a chance to perform actions after
                # the tool has run.
                self.__target_class.finalize(tool)
                # All good - check if we should commit.
                if not self.options.simulate: # pylint: disable=E1101
                    transaction.commit()
                else:
                    transaction.abort()
                run_report = True
            except: # catch all pylint: disable=W0702
                transaction.abort()
                raise
            if run_report:
                try:
                    self.__target_class.report(tool)
                except Exception as err: # catch all pylint: disable=W0703
                    msg = 'There were errors during report generation ' \
                          'after the transaction had already finished.' \
                          'Error message: %s.' % str(err)
                    raise RuntimeError(msg)
        config.end()

    def __setup_thelma(self, ini_file):
        here_dir = os.getcwd()
        config_uri = 'config:%s' % ini_file
        self.logging_file_config(ini_file) # pylint: disable=E1101
        settings = appconfig(config_uri, 'thelma', relative_to=here_dir)
        reg = Registry('thelma')
        # Some tools need to resolve URLs, so we need to set up a request
        # and a service.
        url = 'http://0.0.0.0:6543'
        req = DummyRequest(application_url=url,
                           host_url=url,
                           path_url=url,
                           url=url,
                           registry=reg)
        config = create_config(settings, registry=reg)
        config.setup_registry(settings=settings)
        config.begin(request=req)
        config.load_zcml('configure.zcml')
        srvc = config.get_registered_utility(IService)
        req.root = srvc
        # Set up repositories.
        repo_mgr = config.get_registered_utility(IRepositoryManager)
        repo_mgr.initialize_all()
        #
        srvc.start()
        # Configure the session maker. We need the Zope transaction extension
        # to not close the session such that we can write tool reports after
        # the commit. Also, we do not expire objects on commit to avoid
        # having to refresh the attributes needed for reporting.
        Session.configure(extension=
                                ZopeTransactionExtension(keep_session=True),
                          expire_on_commit=False)
        return config


class EmptyTubeRegistrarToolCommand(ToolCommand): # no __init__ pylint: disable=W0232
    """
    Runs the empty tube registrar tool.
    """
    name = 'emptytuberegistrar'
    tool = \
        'thelma.automation.tools.stock.emptytuberegistrar:EmptyTubeRegistrar'
    option_defs = [('--scanfile-dir',
                    'scanfile_directory',
                    dict(help="Directory where scanfiles are located.")
                    ),
                   ]

    @classmethod
    def finalize(cls, tool):
        if not tool.has_errors():
            tube_agg = get_root_aggregate(ITube)
            for tube in tool.return_value:
                tube_agg.add(tube)


class _RegistrarCommand(ToolCommand): # no __init__ pylint: disable=W0232
    registration_resource = None

    option_defs = \
        [('--report-directory',
          'report_directory',
          dict(help='Directory where report files will be written. Defaults '
                    'to the directory the data file is in.',
               type='string'),
          ),
         ('--data-file',
          'registration_items',
          dict(help='File containing JSON registration data.',
               action='callback',
               type='string',
               callback=LazyOption(lambda cls, value, options:
                                        cls._data_callback(value, options)) # pylint: disable=W0212
               ),
          ),
         ]

    @classmethod
    def _data_callback(cls, value, options):
        # Set the default for the report directory.
        if options.report_directory is None:
            options.report_directory = os.path.dirname(value)
        coll_cls = get_collection_class(cls.registration_resource)
        rpr = as_representer(object.__new__(coll_cls), JsonMime)
        return [rc.get_entity() for rc in rpr.from_stream(open(value, 'rU'))]

    @classmethod
    def report(cls, tool):
        # Write out report files for registered items.
        tool.write_report()


class DesignPoolRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the design registrar (for internal registration of new molecule
    designs).
    """
    name = 'designpoolregistrar'
    tool = 'thelma.automation.tools.stock.' \
           'sampleregistration.MoleculeDesignPoolRegistrar'
    registration_resource = IMoleculeDesignPoolRegistrationItem


class DesignRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the design registrar (for internal registration of new molecule
    designs).
    """
    name = 'designregistrar'
    tool = \
    'thelma.automation.tools.stock.sampleregistration.MoleculeDesignRegistrar'
    registration_resource = IMoleculeDesignRegistrationItem


class SampleRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the sample registrar (for internal registration of new samples).
    """
    name = 'sampleregistrar'
    tool = \
        'thelma.automation.tools.stock.sampleregistration.SampleRegistrar'
    option_defs = _RegistrarCommand.option_defs + \
        [('--validation-files',
          'validation_files',
          dict(help='Comma-separated list of rack scanning files '
                    'for validation of tube positions.',
               type='string')
          ),
         ]
    registration_resource = ISampleRegistrationItem


class SupplierSampleRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the supplier sample registrar (for registration of supplier samples).
    """
    name = 'deliveryregistrar'
    tool = \
        'thelma.automation.tools.stock.sampleregistration.DeliveryRegistrar'
    option_defs = SampleRegistrarCommand.option_defs
    registration_resource = ISupplierSampleRegistrationItem


class XL20ExecutorToolCommand(ToolCommand): # no __init__ pylint: disable=W0232
    """
    Runs the XL20 executor that executes tube transfers on DB level.
    """
    _user_callback = \
        LazyOption(lambda cls, value, options:
                                get_root_aggregate(IUser).get_by_slug(value))
    _output_file_callback = \
            LazyOption(lambda cls, value, options: open(value, 'rb').read())<commit_msg>Renamed deliveryregistrar tool to suppliersampleregistrar<commit_after>"""
Run tool command.
"""
from everest.entities.utils import get_root_aggregate
from everest.mime import JsonMime
from everest.querying.specifications import eq
from everest.repositories.interfaces import IRepositoryManager
from everest.repositories.rdb import Session
from everest.representers.utils import as_representer
from everest.resources.interfaces import IService
from everest.resources.utils import get_collection_class
from everest.utils import classproperty
from paste.deploy import appconfig # pylint: disable=E0611,F0401
from paste.script.command import Command # pylint: disable=E0611,F0401
from pyramid.path import DottedNameResolver
from pyramid.registry import Registry
from pyramid.testing import DummyRequest
from thelma.automation.tools.stock.sampleregistration import \
    IMoleculeDesignPoolRegistrationItem
from thelma.automation.tools.stock.sampleregistration import \
    IMoleculeDesignRegistrationItem
from thelma.automation.tools.stock.sampleregistration import \
    ISampleRegistrationItem
from thelma.automation.tools.stock.sampleregistration import \
    ISupplierSampleRegistrationItem
from thelma.interfaces import ILibraryCreationIso
from thelma.interfaces import IMoleculeDesignLibrary
from thelma.interfaces import ITube
from thelma.interfaces import ITubeTransferWorklist
from thelma.interfaces import IUser
from thelma.run import create_config
from zope.sqlalchemy import ZopeTransactionExtension # pylint: disable=E0611,F0401
import logging
import os
import sys
import transaction
#from thelma.interfaces import ITubeTransfer
#from thelma.automation.tools.libcreation.ticket \
#    import LibraryCreationStockTransferReporter
#from thelma.automation.tools.libcreation.ticket \
#    import LibraryCreationTicketWorklistUploader

__docformat__ = 'reStructuredText en'
__all__ = ['EmptyTubeRegistrarToolCommand',
           'LibraryCreationExecutorToolCommand',
           'LibraryCreationWorklistWriterToolCommand',
           'LibraryGeneratorToolCommand',
           'LibraryIsoGeneratorToolCommand',
           'LibraryIsoPopulatorToolCommand',
           'MetaToolCommand',
           'ToolCommand',
           'XL20ExecutorToolCommand',
           ]


class MetaToolCommand(type):
    """
    Meta class for tool commands.

    This ensures the option parser and the summary and usage doc strings are
    set up automatically for the tool to run; this is much preferable to
    having to define a new entry point for every individual tool we want to
    run.
    """
    def __new__(mcs, name, bases, class_dict):
        # Ignore base classes.
        if name != 'ToolCommand' and not name.startswith('_'):
            parser = ToolCommand.make_standard_parser(simulate=True)
            if not 'name' in class_dict:
                raise ValueError('You need to define a name for the tool '
                                 'to run as "name" attribute in the class '
                                 'namespace of your tool (%s).' % name)
            if not 'tool' in class_dict:
                raise ValueError('You need to define the pkg resource '
                                 'style dotted name (absolute) of the '
                                 'tool to run as "tool" attribute in the '
                                 'class namespace of your tool (%s).' % name)
            option_defs = \
                class_dict.get('option_defs') or bases[-1].option_defs
            if option_defs is None:
                raise ValueError('You need to define a list of option '
                                 'definitions as "option_defs" attribute '
                                 'in the class namespace of your tool (%s).'
                                 % name)
            for opt_name, attr_name, opt_kw in option_defs:
                if 'dest' in opt_kw:
                    raise ValueError('Cannot use the "dest" parameter for '
                                     'tool command options.')
                # We need a copy here in case the option defs are reused in
                # derived classes.
                copied_opt_kw = opt_kw.copy()
                copied_opt_kw['dest'] = attr_name
                parser.add_option(opt_name, **copied_opt_kw)
            summary = class_dict.__doc__.strip()
            class_dict['summary'] = summary
            usage = ToolCommand.__dict__['_usage_template'] \
                                  % class_dict['name']
            class_dict['usage'] = usage
            class_dict['parser'] = parser
        cls = type.__new__(mcs, name, bases, class_dict)
        # This is the magic: If this is the command we are running,
        # set the new class as the target class of the ToolCommand.
        if class_dict.get('name') == sys.argv[-2]:
            ToolCommand.set_target_class(cls)
        return cls


class LazyOption(object):
    """
    Simple capsule for options that can only be initialized when TheLMA has
    started up.
    """
    def __init__(self, value_callback):
        self.__value_callback = value_callback
        self.__option_value = None

    def __call__(self, option, option_name, option_value, parser): # pylint: disable=W0613
        self.__option_value = option_value
        setattr(parser.values, option.dest, self)

    def initialize(self, tool_class, options):
        return self.__value_callback(tool_class, self.__option_value, options)


class ToolCommand(Command): # no __init__ pylint: disable=W0232
    """
    Abstract base class for paste commands which run a TheLMA tool from the
    command line.
    """
    __metaclass__ = MetaToolCommand
    #: Template for usage strings in derived classes.
    _usage_template = "%s <ini file>"
    #: This is the target tool command class (set by the meta class).
    __target_class = None
    #: pkg resource style (absolute) dotted name to the tool to run.
    tool = None
    #: A sequence of the form
    #:     [<option name>,<attribute name>, <option kewyword dict>]
    #: This is used to configure the option parser in the derived command
    #: class and to extract the constructor arguments from the options.
    option_defs = None
    #: The name of the tool used on the command line.
    name = None
    #: Options group name for help messages.
    group_name = "thelma"
    #: Minimum number of command line arguments.
    min_args = 2
    #: Maximum number of command line arguments.
    max_args = 2

    @classmethod
    def make_standard_parser(verbose=True,
                             interactive=False,
                             no_interactive=False,
                             simulate=False,
                             quiet=False,
                             overwrite=False):
        parser = Command.standard_parser(verbose=verbose,
                                         interactive=interactive,
                                         no_interactive=no_interactive,
                                         simulate=simulate,
                                         quiet=quiet,
                                         overwrite=overwrite)
        parser.add_option('--ignore-warnings',
                          action='store_true',
                          dest='ignore_warnings',
                          help="If true, changes are committed even when the "
                               "tool reports warnings (unless the --simulate "
                               "flag is set).")
        return parser

    @classmethod
    def set_target_class(cls, target_class):
        cls.__target_class = target_class

    @classmethod
    def finalize(cls, tool):
        """
        Override this method in derived classes to perform actions after the
        tool has run.
        """
        pass

    @classmethod
    def report(cls, tool):
        """
        Override this method in drived classes to perform reporting actions
        after the tool has run and all data have been committed.
        """
        pass

    @classproperty
    def summary(cls):
        if cls.__target_class is None:
            raise RuntimeError('Tool command "%s" not found!' % sys.argv[-2])
        return cls.__target_class.summary

    @classproperty
    def usage(cls):
        if cls.__target_class is None:
            raise RuntimeError('Tool command "%s" not found!' % sys.argv[-2])
        return cls.__target_class.usage

    @classproperty
    def parser(cls):
        if cls.__target_class is None:
            raise RuntimeError('Tool command "%s" not found!' % sys.argv[-2])
        return cls.__target_class.parser

    def command(self):
        ini_file = self.args[-1] # pylint: disable=E1101
        # TheLMA setup.
        config = self.__setup_thelma(ini_file)
        # Initialize the tool and run it.
        rsv = DottedNameResolver(None)
        tool_cls = rsv.resolve(self.__target_class.tool)
        arg_names = [od[1] for od in self.__target_class.option_defs]
        kw = {}
        # Initializing lazy options. We pass the target class and the
        # options so the callback has access to them.
        opts = self.options # pylint: disable=E1101
        for arg_name in arg_names:
            arg_value = getattr(opts, arg_name)
            if isinstance(arg_value, LazyOption):
                arg_value = arg_value.initialize(self.__target_class, opts)
                setattr(opts, arg_name, arg_value)
        kw = dict((arg_name, getattr(opts, arg_name))
                  for arg_name in arg_names)
        tool = tool_cls(**kw)
        try:
            tool.run()
        except: # catch all pylint: disable=W0702
            transaction.abort()
            raise
        else:
            if tool.has_errors():
                err_msgs = tool.log.get_messages()
                msg = 'Errors occurred during the tool run. Error messages:\n'
                raise RuntimeError(msg + os.linesep.join(err_msgs))
            warn_msgs = tool.get_messages(logging_level=logging.WARNING)
            if warn_msgs \
               and not self.options.ignore_warnings: # pylint: disable=E1101
                msg = 'Warnings occurred during the tool run. You can ' \
                      'repeat the run with the --ignore-warnings switch ' \
                      'to force changes to be committed. Warning messages:\n'
                raise RuntimeError(msg + os.linesep.join(warn_msgs))
            run_report = False
            try:
                # This gives the tool command a chance to perform actions after
                # the tool has run.
                self.__target_class.finalize(tool)
                # All good - check if we should commit.
                if not self.options.simulate: # pylint: disable=E1101
                    transaction.commit()
                else:
                    transaction.abort()
                run_report = True
            except: # catch all pylint: disable=W0702
                transaction.abort()
                raise
            if run_report:
                try:
                    self.__target_class.report(tool)
                except Exception as err: # catch all pylint: disable=W0703
                    msg = 'There were errors during report generation ' \
                          'after the transaction had already finished.' \
                          'Error message: %s.' % str(err)
                    raise RuntimeError(msg)
        config.end()

    def __setup_thelma(self, ini_file):
        here_dir = os.getcwd()
        config_uri = 'config:%s' % ini_file
        self.logging_file_config(ini_file) # pylint: disable=E1101
        settings = appconfig(config_uri, 'thelma', relative_to=here_dir)
        reg = Registry('thelma')
        # Some tools need to resolve URLs, so we need to set up a request
        # and a service.
        url = 'http://0.0.0.0:6543'
        req = DummyRequest(application_url=url,
                           host_url=url,
                           path_url=url,
                           url=url,
                           registry=reg)
        config = create_config(settings, registry=reg)
        config.setup_registry(settings=settings)
        config.begin(request=req)
        config.load_zcml('configure.zcml')
        srvc = config.get_registered_utility(IService)
        req.root = srvc
        # Set up repositories.
        repo_mgr = config.get_registered_utility(IRepositoryManager)
        repo_mgr.initialize_all()
        #
        srvc.start()
        # Configure the session maker. We need the Zope transaction extension
        # to not close the session such that we can write tool reports after
        # the commit. Also, we do not expire objects on commit to avoid
        # having to refresh the attributes needed for reporting.
        Session.configure(extension=
                                ZopeTransactionExtension(keep_session=True),
                          expire_on_commit=False)
        return config


class EmptyTubeRegistrarToolCommand(ToolCommand): # no __init__ pylint: disable=W0232
    """
    Runs the empty tube registrar tool.
    """
    name = 'emptytuberegistrar'
    tool = \
        'thelma.automation.tools.stock.emptytuberegistrar:EmptyTubeRegistrar'
    option_defs = [('--scanfile-dir',
                    'scanfile_directory',
                    dict(help="Directory where scanfiles are located.")
                    ),
                   ]

    @classmethod
    def finalize(cls, tool):
        if not tool.has_errors():
            tube_agg = get_root_aggregate(ITube)
            for tube in tool.return_value:
                tube_agg.add(tube)


class _RegistrarCommand(ToolCommand): # no __init__ pylint: disable=W0232
    registration_resource = None

    option_defs = \
        [('--report-directory',
          'report_directory',
          dict(help='Directory where report files will be written. Defaults '
                    'to the directory the data file is in.',
               type='string'),
          ),
         ('--data-file',
          'registration_items',
          dict(help='File containing JSON registration data.',
               action='callback',
               type='string',
               callback=LazyOption(lambda cls, value, options:
                                        cls._data_callback(value, options)) # pylint: disable=W0212
               ),
          ),
         ]

    @classmethod
    def _data_callback(cls, value, options):
        # Set the default for the report directory.
        if options.report_directory is None:
            options.report_directory = os.path.dirname(value)
        coll_cls = get_collection_class(cls.registration_resource)
        rpr = as_representer(object.__new__(coll_cls), JsonMime)
        return [rc.get_entity() for rc in rpr.from_stream(open(value, 'rU'))]

    @classmethod
    def report(cls, tool):
        # Write out report files for registered items.
        tool.write_report()


class DesignPoolRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the design registrar (for internal registration of new molecule
    designs).
    """
    name = 'designpoolregistrar'
    tool = 'thelma.automation.tools.stock.' \
           'sampleregistration.MoleculeDesignPoolRegistrar'
    registration_resource = IMoleculeDesignPoolRegistrationItem


class DesignRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the design registrar (for internal registration of new molecule
    designs).
    """
    name = 'designregistrar'
    tool = \
    'thelma.automation.tools.stock.sampleregistration.MoleculeDesignRegistrar'
    registration_resource = IMoleculeDesignRegistrationItem


class SampleRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the sample registrar (for internal registration of new samples).
    """
    name = 'sampleregistrar'
    tool = \
        'thelma.automation.tools.stock.sampleregistration.SampleRegistrar'
    option_defs = _RegistrarCommand.option_defs + \
        [('--validation-files',
          'validation_files',
          dict(help='Comma-separated list of rack scanning files '
                    'for validation of tube positions.',
               type='string')
          ),
         ]
    registration_resource = ISampleRegistrationItem


class SupplierSampleRegistrarCommand(_RegistrarCommand): # no __init__ pylint: disable=W0232
    """
    Runs the supplier sample registrar (for registration of supplier samples).
    """
    name = 'suppliersampleregistrar'
    tool = \
    'thelma.automation.tools.stock.sampleregistration.SupplierSampleRegistrar'
    option_defs = SampleRegistrarCommand.option_defs
    registration_resource = ISupplierSampleRegistrationItem


class XL20ExecutorToolCommand(ToolCommand): # no __init__ pylint: disable=W0232
    """
    Runs the XL20 executor that executes tube transfers on DB level.
    """
    _user_callback = \
        LazyOption(lambda cls, value, options:
                                get_root_aggregate(IUser).get_by_slug(value))
    _output_file_callback = \
            LazyOption(lambda cls, value, options: open(value, 'rb').read())
{'fraction_non_alphanumeric': 0.07507542353214203, 'fraction_numerical': 0.006904154096077976, 'mean_word_length': 2.098139660285791, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 42, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1970749', 'n_tokens_mistral': 9278, 'n_tokens_neox': 8796, 'n_words': 2565}