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
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
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
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
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)
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
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
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
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)
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