Ejemplo n.º 1
0
class Status(with_metaclass(ABCMeta)):
    """Class describing a failure or success status, with logs.

    Values of this class evaluate to True in a boolean context
    if the status is successful.

    Values of this class are immutable.

    """
    def __init__(self):
        """Construct an abstract Status."""

    @property
    @abstractmethod
    def status_description(self):
        """Get a one-line-ish description of the status."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def errors(self):
        """Get error logs relevant to the status.

        A rule of thumb for this field is that anything in here should also have been
        logged to a ``Frontend`` instance, so this field is kind of just a cache
        of error messages generated by a particular operation for the convenience
        of the caller.
        """
        pass  # pragma: no cover
Ejemplo n.º 2
0
class Status(with_metaclass(ABCMeta)):
    """Class describing a failure or success status, with logs.

    Values of this class evaluate to True in a boolean context
    if the status is successful.

    Values of this class are immutable.

    """
    def __init__(self):
        """Construct an abstract Status."""

    @property
    @abstractmethod
    def status_description(self):
        """Get a one-line-ish description of the status."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def logs(self):
        """Get logs relevant to the status."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def errors(self):
        """Get error logs relevant to the status."""
        pass  # pragma: no cover
Ejemplo n.º 3
0
class Frontend(with_metaclass(ABCMeta)):
    """A UX (CLI, GUI, etc.) for project operations."""

    def __init__(self):
        """Construct a Frontend."""
        self._info_buf = ''
        self._error_buf = ''

    def _partial(self, data, buf, line_handler):
        buf = buf + data
        (start, sep, end) = buf.partition('\n')
        while sep != '':
            # we do this instead of using os.linesep in case
            # something on windows outputs unix-style line
            # endings, we don't want to go haywire.  On unix when
            # we actually want \r to carriage return, we'll be
            # overriding this "partial" handler and not using this
            # buffering implementation.
            if start.endswith('\r'):
                start = start[:-1]
            line_handler(start)
            buf = end
            (start, sep, end) = buf.partition('\n')
        return buf

    def partial_info(self, data):
        """Log only part of an info-level line.

        The default implementation buffers this until a line separator
        and then passes the entire line to info().
        Subtypes can override this if they want to print output
        immediately as it arrives.
        """
        self._info_buf = self._partial(data, self._info_buf, self.info)

    def partial_error(self, data):
        """Log only part of an error-level line.

        The default implementation buffers this until a line separator
        and then passes the entire line to error().
        Subtypes can override this if they want to print output
        immediately as it arrives.
        """
        self._error_buf = self._partial(data, self._error_buf, self.error)

    @abstractmethod
    def info(self, message):
        """Log an info-level message."""
        pass  # pragma: no cover

    @abstractmethod
    def error(self, message):
        """Log an error-level message.

        A rule of thumb is that if a function also returns a
        ``Status``, this message should also be appended to the
        ``errors`` field on that status.
        """
        pass  # pragma: no cover
Ejemplo n.º 4
0
class CondaManager(with_metaclass(ABCMeta)):
    """Methods for interacting with Conda.

    This is meant to be a stateless class. Multiple may be created
    and they may be used from multiple threads. If instances are
    implemented using any global state under the hood, that global
    state should be protected by locks, and shared among
    ``CondaManager`` instances.

    """
    @abstractmethod
    def resolve_dependencies(self, package_specs, channels, platforms):
        """Compute the full transitive graph to install to satisfy package_specs.

        Raised exceptions that are user-interesting conda problems
        should be subtypes of ``CondaManagerError``.

        The passed-in package specs can be any constraints we want
        to "hold constant" while computing the other deps.

        The returned value is a ``CondaLockSet``.

        Args:
            package_specs (list of str): list of specs to hold constant
            channels (list of str): list of channels to resolve against
            platforms (list of str): list of platforms to resolve for

        Returns:
            a ``CondaLockSet`` instance

        """
        pass  # pragma: no cover

    @abstractmethod
    def find_environment_deviations(self, prefix, spec):
        """Compute a ``CondaEnvironmentDeviations`` describing deviations of the env at prefix from the spec.

        Raised exceptions that are user-interesting conda problems
        should be subtypes of ``CondaManagerError``.

        The prefix may not exist (which would be considered a
        deviation).

        Args:
            prefix (str): the environment prefix (absolute path)
            spec (EnvSpec): specification for the environment

        Returns:
            a ``CondaEnvironmentDeviations`` instance

        """
        pass  # pragma: no cover

    @abstractmethod
    def fix_environment_deviations(self,
                                   prefix,
                                   spec,
                                   deviations=None,
                                   create=True):
        """Fix deviations of the env in prefix from the spec.

        Raised exceptions that are user-interesting conda problems
        should be subtypes of ``CondaManagerError``.

        The prefix may not exist (this method should then try to create it).

        Args:
            prefix (str): the environment prefix (absolute path)
            spec (EnvSpec): specification for the environment
            deviations (CondaEnvironmentDeviations): optional previous result from find_environment_deviations()
            create (bool): True if we should create if completely nonexistent

        Returns:
            None
        """
        pass  # pragma: no cover

    @abstractmethod
    def remove_packages(self, prefix, packages):
        """Remove the given package name from the environment in prefix.

        This method ideally would not exist. The ideal approach is
        that in find_enviroment_deviations, the generated
        deviation could include "pruned" or "unnecessary" packages
        that are in the prefix but aren't needed for the
        spec. fix_environment_deviations would then remove any
        extra packages. In effect we'd always force the
        environment to be the fresh env we would install from
        scratch, given the spec.

        Args:
           prefix (str): environment path
           package (list of str): package names

        Returns:
           None

        """
        pass  # pragma: no cover
Ejemplo n.º 5
0
class Requirement(with_metaclass(ABCMeta)):
    """Describes a requirement of the project (from the project config).

    Note that this is not specifically a package requirement;
    this class is more general, it can be a requirement for any
    condition at all (that a service is running, that a file
    exists, or even that a package is intalled - anything you can
    think of).

    """
    def __init__(self, registry, options):
        """Construct a Requirement.

        Args:
            registry (PluginRegistry): the plugin registry we came from
            options (dict): dict of requirement options from the project config
        """
        self.registry = registry

        if options is None:
            self.options = dict()
        else:
            self.options = deepcopy(options)
            # always convert the default to a string (it's allowed to be a number
            # in the config file, but env vars have to be strings), unless
            # it's a dict because we use a dict for encrypted defaults
            if 'default' in self.options and not (
                    is_string(self.options['default'])
                    or isinstance(self.options['default'], dict)):
                self.options['default'] = str(self.options['default'])

    @property
    @abstractmethod
    def title(self):
        """Human-readable short name of the requirement."""
        pass  # pragma: no cover

    def _description(self, default):
        """Use this in subclasses to implement the description property."""
        if 'description' in self.options:
            return self.options['description']
        else:
            return default

    @property
    @abstractmethod
    def description(self):
        """Human-readable about-one-sentence hint or tooltip for the requirement."""
        pass  # pragma: no cover

    @property
    def ignore_patterns(self):
        """Set of ignore patterns for files this requirement's provider might autogenerate."""
        return set()

    def _create_status(self, environ, local_state_file, default_env_spec_name,
                       overrides, latest_provide_result, has_been_provided,
                       status_description, provider_class_name):
        provider = self.registry.find_provider_by_class_name(
            provider_class_name)
        analysis = provider.analyze(self, environ, local_state_file,
                                    default_env_spec_name, overrides)
        return RequirementStatus(self,
                                 has_been_provided=has_been_provided,
                                 status_description=status_description,
                                 provider=provider,
                                 analysis=analysis,
                                 latest_provide_result=latest_provide_result)

    def _create_status_from_analysis(self, environ, local_state_file,
                                     default_env_spec_name, overrides,
                                     latest_provide_result,
                                     provider_class_name, status_getter):
        provider = self.registry.find_provider_by_class_name(
            provider_class_name)
        analysis = provider.analyze(self, environ, local_state_file,
                                    default_env_spec_name, overrides)
        (has_been_provided,
         status_description) = status_getter(environ, local_state_file,
                                             analysis)
        return RequirementStatus(self,
                                 has_been_provided=has_been_provided,
                                 status_description=status_description,
                                 provider=provider,
                                 analysis=analysis,
                                 latest_provide_result=latest_provide_result)

    @abstractmethod
    def check_status(self,
                     environ,
                     local_state_file,
                     default_env_spec_name,
                     overrides,
                     latest_provide_result=None):
        """Check on the requirement and return a ``RequirementStatus`` with the current status.

        This may attempt to talk to servers, check that files
        exist on disk, and other work of that nature to verify the
        requirement's status, so be careful about when and how
        often this gets called.

        Args:
            environ (dict): use this rather than the system environment directly
            local_state_file (LocalStateFile): project local state
            default_env_spec_name (str): fallback env spec name
            overrides (UserConfigOverrides): user-supplied forced choices
            latest_provide_result (ProvideResult): most recent result of ``provide()`` or None

        Returns:
            a ``RequirementStatus``

        """
        pass  # pragma: no cover (abstract method)
Ejemplo n.º 6
0
class Provider(with_metaclass(ABCMeta)):
    """A Provider can take some action to meet a Requirement."""

    @abstractmethod
    def missing_env_vars_to_configure(self, requirement, environ, local_state_file):
        """Get a list of unset environment variable names that must be set before configuring this provider.

        Args:
            requirement (Requirement): requirement instance we are providing for
            environ (dict): current environment variable dict
            local_state_file (LocalStateFile): local state file
        """
        pass  # pragma: no cover

    @abstractmethod
    def missing_env_vars_to_provide(self, requirement, environ, local_state_file):
        """Get a list of unset environment variable names that must be set before calling provide().

        Args:
            requirement (Requirement): requirement instance we are providing for
            environ (dict): current environment variable dict
            local_state_file (LocalStateFile): local state file
        """
        pass  # pragma: no cover

    @abstractmethod
    def read_config(self, requirement, environ, local_state_file, default_env_spec_name, overrides):
        """Read a config dict from the local state file for the given requirement.

        You can think of this as the GET returning a web form for
        configuring the provider. And in fact it was once used for
        that, though we deleted the html stuff now.

        The returned 'config' has a 'source' field which was
        essentially a selected radio option for where to get the
        requirement, and other fields are entry boxes underneath
        each radio option.

        This method still exists in the code in case we want to
        do a textual version (or a new HTML version, but probably
        outside of the anaconda-project codebase). See also
        UI_MODE_TEXT_ASK_QUESTIONS in the cli code.

        Args:
            requirement (Requirement): the requirement we're providing
            environ (dict): current environment variables
            local_state_file (LocalStateFile): file to read from
            default_env_spec_name (str): the fallback env spec name
            overrides (UserConfigOverrides): user-supplied forced config

        """
        pass  # pragma: no cover

    def set_config_values_as_strings(self, requirement, environ, local_state_file, default_env_spec_name, overrides,
                                     values):
        """Set some config values in the state file (should not save the file).

        You can think of this as the POST submitting a web form
        for configuring the provider. And in fact it was once used
        for that, though we deleted the html stuff now.

        Args:
            requirement (Requirement): the requirement we're providing
            environ (dict): current environment variables
            local_state_file (LocalStateFile): file to save to
            default_env_spec_name (str): default env spec name for this prepare
            overrides (UserConfigOverrides): if any values in here change, delete the override
            values (dict): dict from string to string

        """
        pass  # silently ignore unknown config values

    def analyze(self, requirement, environ, local_state_file, default_env_spec_name, overrides):
        """Analyze whether and how we'll be able to provide the requirement.

        This is used to show the situation in the UI, and also to
        consolidate all IO-type work in one place (inside
        Requirement.check_status()).

        Returns:
          A ``ProviderAnalysis`` instance.
        """
        config = self.read_config(requirement, environ, local_state_file, default_env_spec_name, overrides)
        missing_to_configure = self.missing_env_vars_to_configure(requirement, environ, local_state_file)
        missing_to_provide = self.missing_env_vars_to_provide(requirement, environ, local_state_file)
        return ProviderAnalysis(config=config,
                                missing_env_vars_to_configure=missing_to_configure,
                                missing_env_vars_to_provide=missing_to_provide)

    @abstractmethod
    def provide(self, requirement, context):
        """Execute the provider, fulfilling the requirement.

        The implementation should read and modify the passed-in
        ``environ`` rather than accessing the OS environment
        directly.

        Args:
            requirement (Requirement): requirement we want to meet
            context (ProvideContext): context containing project state

        Returns:
            a ``ProvideResult`` instance

        """
        pass  # pragma: no cover

    @abstractmethod
    def unprovide(self, requirement, environ, local_state_file, overrides, requirement_status=None):
        """Undo the provide, cleaning up any files or processes we created.

        The requirement may still be met after this, if our providing wasn't
        really needed.

        Args:
            requirement (Requirement): requirement we want to de-provide
            environ (dict): current env vars, often from a previous prepare
            local_state_file (LocalStateFile): the local state
            overrides (UserConfigOverrides): overrides to state
            requirement_status (RequirementStatus or None): requirement status if available

        Returns:
            a `Status` instance describing the (non)success of the unprovision
        """
        pass  # pragma: no cover
Ejemplo n.º 7
0
class PrepareResult(with_metaclass(ABCMeta)):
    """Abstract class describing the result of preparing the project to run."""
    def __init__(self, logs, statuses, environ, overrides):
        """Construct an abstract PrepareResult."""
        self._logs = logs
        self._statuses = tuple(statuses)
        self._environ = environ
        self._overrides = overrides

    def __bool__(self):
        """True if we were successful."""
        return not self.failed

    def __nonzero__(self):
        """True if we were successful."""
        return self.__bool__()  # pragma: no cover (py2 only)

    @property
    @abstractmethod
    def failed(self):
        """True if we failed to do what this stage was intended to do.

        If ``execute()`` returned non-None, the failure may not be fatal; stages
        can continue to be executed and may resolve the issue.
        """
        pass  # pragma: no cover

    @property
    def logs(self):
        """Get lines of debug log output.

        Does not include errors in case of failure. This is the
        "stdout" logs only.
        """
        return self._logs

    def print_output(self):
        """Print logs and errors to stdout and stderr."""
        for log in self.logs:
            print(log, file=sys.stdout)
            # be sure we print all these before the errors
            sys.stdout.flush()

    @property
    def statuses(self):
        """Get latest RequirementStatus if available.

        If we failed before we even checked statuses, this will be an empty list.
        """
        return self._statuses

    def status_for(self, env_var_or_class):
        """Get status for the given env var or class, or None if unknown."""
        for status in self.statuses:
            if is_string(env_var_or_class):
                if isinstance(status.requirement, EnvVarRequirement) and \
                   status.requirement.env_var == env_var_or_class:
                    return status
            elif isinstance(status.requirement, env_var_or_class):
                return status
        return None

    @property
    def environ(self):
        """Computed environment variables for the project.

        If ``failed`` is True, this environ dict may be unmodified
        from the original provided to the prepare function.
        """
        return self._environ

    @property
    def overrides(self):
        """Override object which was passed to prepare()."""
        return self._overrides

    @property
    def errors(self):
        """Get lines of error output."""
        raise NotImplementedError()  # pragma: no cover
Ejemplo n.º 8
0
class PrepareStage(with_metaclass(ABCMeta)):
    """A step in the project preparation process."""
    @property
    @abstractmethod
    def description_of_action(self):
        """Get a user-visible description of what happens if this step is executed."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def failed(self):
        """Synonym for result.failed, only available after ``execute()``."""
        pass  # pragma: no cover

    @abstractmethod
    def configure(self):
        """Get a ``ConfigurePrepareContext`` or None if no configuration is needed.

        Configuration should be done before execute().

        Returns:
          a ``ConfigurePrepareContext`` or None
        """
        pass  # pragma: no cover

    @abstractmethod
    def execute(self):
        """Run this step and return a new stage, or None if we are done or failed."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def result(self):
        """The ``PrepareResult`` (only available if ``execute()`` has been called)."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def environ(self):
        """The latest environment variables (from the result if any, otherwise the pre-execute ones)."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def overrides(self):
        """User overrides."""
        pass  # pragma: no cover

    @property
    @abstractmethod
    def statuses_before_execute(self):
        """``RequirementStatus`` list before execution.

        This list includes all known requirements and their statuses, while the list
        in the ``configure()`` context only includes those that should be configured
        prior to this stage's execution.
        """
        pass  # pragma: no cover

    @property
    @abstractmethod
    def statuses_after_execute(self):
        """``RequirementStatus`` list after execution.

        This list includes all known requirements and their statuses, as changed
        by ``execute()``. This property cannot be read prior to ``execute()``.
        """
        pass  # pragma: no cover
Ejemplo n.º 9
0
class PrepareResult(with_metaclass(ABCMeta)):
    """Abstract class describing the result of preparing the project to run."""
    def __init__(self, statuses, environ, overrides, env_spec_name):
        """Construct an abstract PrepareResult."""
        self._statuses = tuple(statuses)
        self._environ = environ
        self._overrides = overrides
        self._env_spec_name = env_spec_name

    def __bool__(self):
        """True if we were successful."""
        return not self.failed

    def __nonzero__(self):
        """True if we were successful."""
        return self.__bool__()  # pragma: no cover (py2 only)

    @property
    @abstractmethod
    def failed(self):
        """True if we failed to do what this stage was intended to do.

        If ``execute()`` returned non-None, the failure may not be fatal; stages
        can continue to be executed and may resolve the issue.
        """
        pass  # pragma: no cover

    @property
    def statuses(self):
        """Get latest RequirementStatus if available.

        If we failed before we even checked statuses, this will be an empty list.
        """
        return self._statuses

    def status_for(self, env_var_or_class):
        """Get status for the given env var or class, or None if unknown."""
        for status in self.statuses:
            if is_string(env_var_or_class):
                if isinstance(status.requirement, EnvVarRequirement) and \
                   status.requirement.env_var == env_var_or_class:
                    return status
            elif isinstance(status.requirement, env_var_or_class):
                return status
        return None

    @property
    def environ(self):
        """Computed environment variables for the project.

        If ``failed`` is True, this environ dict may be unmodified
        from the original provided to the prepare function.
        """
        return self._environ

    @property
    def overrides(self):
        """Override object which was passed to prepare()."""
        return self._overrides

    @property
    def errors(self):
        """Get lines of error output."""
        raise NotImplementedError()  # pragma: no cover

    @property
    def env_spec_name(self):
        """The env spec name we used for the prepare.

        If the project was broken or the user provided bad input
        before we could ask CondaEnvRequirement for the env spec
        name, at the moment we sort of take a guess at the right
        name in order to guarantee this is never None. The
        guessing is a little bit broken. But it would be a very
        obscure scenario where it matters.
        """
        return self._env_spec_name

    @property
    def env_prefix(self):
        """The prefix of the prepared env, or None if none was created."""
        status = self.status_for(CondaEnvRequirement)
        if status is None:
            return None
        varname = status.requirement.env_var
        return self._environ.get(varname, None)
Ejemplo n.º 10
0
class Provider(with_metaclass(ABCMeta)):
    """A Provider can take some action to meet a Requirement."""
    @abstractmethod
    def missing_env_vars_to_configure(self, requirement, environ,
                                      local_state_file):
        """Get a list of unset environment variable names that must be set before configuring this provider.

        Args:
            requirement (Requirement): requirement instance we are providing for
            environ (dict): current environment variable dict
            local_state_file (LocalStateFile): local state file
        """
        pass  # pragma: no cover

    @abstractmethod
    def missing_env_vars_to_provide(self, requirement, environ,
                                    local_state_file):
        """Get a list of unset environment variable names that must be set before calling provide().

        Args:
            requirement (Requirement): requirement instance we are providing for
            environ (dict): current environment variable dict
            local_state_file (LocalStateFile): local state file
        """
        pass  # pragma: no cover

    @abstractmethod
    def read_config(self, requirement, environ, local_state_file,
                    default_env_spec_name, overrides):
        """Read a config dict from the local state file for the given requirement.

        Args:
            requirement (Requirement): the requirement we're providing
            environ (dict): current environment variables
            local_state_file (LocalStateFile): file to read from
            default_env_spec_name (str): the fallback env spec name
            overrides (UserConfigOverrides): user-supplied forced config
        """
        pass  # pragma: no cover

    def set_config_values_as_strings(self, requirement, environ,
                                     local_state_file, default_env_spec_name,
                                     overrides, values):
        """Set some config values in the state file (should not save the file).

        Args:
            requirement (Requirement): the requirement we're providing
            environ (dict): current environment variables
            local_state_file (LocalStateFile): file to save to
            default_env_spec_name (str): default env spec name for this prepare
            overrides (UserConfigOverrides): if any values in here change, delete the override
            values (dict): dict from string to string
        """
        pass  # silently ignore unknown config values

    def config_html(self, requirement, environ, local_state_file, overrides,
                    status):
        """Get an HTML string for configuring the provider.

        The HTML string must contain a single <form> tag. Any
        <input>, <textarea>, and <select> elements should have
        their name attribute set to match the dict keys used in
        ``read_config()``'s result.  The <form> should not have a
        submit button, since it will be merged with other
        forms. The initial state of all the form fields will be
        auto-populated from the values in ``read_config()``.  When
        the form is submitted, any changes made by the user will
        be set back using ``set_config_values_as_strings()``.

        This is simple to use, but for now not very flexible; if you need
        more flexibility let us know and we can figure out what API
        to add in future versions.

        Args:
            requirement (Requirement): the requirement we're providing
            environ (dict): current environment variables
            local_state_file (LocalStateFile): file to save to
            status (RequirementStatus): last-computed status

        Returns:
            An HTML string or None if there's nothing to configure.

        """
        return None

    def analyze(self, requirement, environ, local_state_file,
                default_env_spec_name, overrides):
        """Analyze whether and how we'll be able to provide the requirement.

        This is used to show the situation in the UI, and also to
        consolidate all IO-type work in one place (inside
        Requirement.check_status()).

        Returns:
          A ``ProviderAnalysis`` instance.
        """
        config = self.read_config(requirement, environ, local_state_file,
                                  default_env_spec_name, overrides)
        missing_to_configure = self.missing_env_vars_to_configure(
            requirement, environ, local_state_file)
        missing_to_provide = self.missing_env_vars_to_provide(
            requirement, environ, local_state_file)
        return ProviderAnalysis(
            config=config,
            missing_env_vars_to_configure=missing_to_configure,
            missing_env_vars_to_provide=missing_to_provide)

    @abstractmethod
    def provide(self, requirement, context):
        """Execute the provider, fulfilling the requirement.

        The implementation should read and modify the passed-in
        ``environ`` rather than accessing the OS environment
        directly.

        Args:
            requirement (Requirement): requirement we want to meet
            context (ProvideContext): context containing project state

        Returns:
            a ``ProvideResult`` instance

        """
        pass  # pragma: no cover

    @abstractmethod
    def unprovide(self,
                  requirement,
                  environ,
                  local_state_file,
                  overrides,
                  requirement_status=None):
        """Undo the provide, cleaning up any files or processes we created.

        The requirement may still be met after this, if our providing wasn't
        really needed.

        Args:
            requirement (Requirement): requirement we want to de-provide
            environ (dict): current env vars, often from a previous prepare
            local_state_file (LocalStateFile): the local state
            overrides (UserConfigOverrides): overrides to state
            requirement_status (RequirementStatus or None): requirement status if available

        Returns:
            a `Status` instance describing the (non)success of the unprovision
        """
        pass  # pragma: no cover