Skip to content

Commit

Permalink
Move conda.core.solve._get_solver_class to `CondaPluginManager.get_…
Browse files Browse the repository at this point in the history
…solver_backend` (conda#12093)
  • Loading branch information
jezdez authored Nov 14, 2022
1 parent ee2eaeb commit ba7d9bc
Show file tree
Hide file tree
Showing 19 changed files with 166 additions and 106 deletions.
6 changes: 3 additions & 3 deletions conda/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
# SPDX-License-Identifier: BSD-3-Clause

from .base.constants import DepsModifier as _DepsModifier, UpdateModifier as _UpdateModifier
from .base.context import context
from .common.constants import NULL
from .core.package_cache_data import PackageCacheData as _PackageCacheData
from .core.prefix_data import PrefixData as _PrefixData
from .core.solve import _get_solver_class
from .core.subdir_data import SubdirData as _SubdirData
from .models.channel import Channel

Expand Down Expand Up @@ -47,8 +47,8 @@ def __init__(self, prefix, channels, subdirs=(), specs_to_add=(), specs_to_remov
The set of package specs to remove from the prefix.
"""
SolverType = _get_solver_class()
self._internal = SolverType(prefix, channels, subdirs, specs_to_add, specs_to_remove)
solver_backend = context.plugin_manager.get_cached_solver_backend()
self._internal = solver_backend(prefix, channels, subdirs, specs_to_add, specs_to_remove)

def solve_final_state(self, update_modifier=NULL, deps_modifier=NULL, prune=NULL,
ignore_pinned=NULL, force_remove=NULL):
Expand Down
5 changes: 2 additions & 3 deletions conda/base/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,12 +842,11 @@ def user_agent(self):
if self.libc_family_version[0]:
builder.append("%s/%s" % self.libc_family_version)
if self.solver != "classic":
from ..core.solve import _get_solver_class

user_agent_str = "solver/%s" % self.solver
try:
solver_backend = self.plugin_manager.get_cached_solver_backend()
# Solver.user_agent has to be a static or class method
user_agent_str += f" {_get_solver_class().user_agent()}"
user_agent_str += f" {solver_backend().user_agent()}"
except Exception as exc:
log.debug(
"User agent could not be fetched from solver class '%s'.",
Expand Down
13 changes: 9 additions & 4 deletions conda/cli/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from ..common.path import paths_equal, is_package_file
from ..core.index import calculate_channel_urls, get_index
from ..core.prefix_data import PrefixData
from ..core.solve import _get_solver_class
from ..exceptions import (CondaExitZero, CondaImportError, CondaOSError, CondaSystemExit,
CondaValueError, DirectoryNotACondaEnvironmentError,
DirectoryNotFoundError, DryRunExit, EnvironmentLocationNotFound,
Expand Down Expand Up @@ -246,9 +245,15 @@ def install(args, parser, command='install'):
unlink_link_transaction = revert_actions(prefix, get_revision(args.revision),
index)
else:
SolverType = _get_solver_class()
solver = SolverType(prefix, context.channels, context.subdirs, specs_to_add=specs,
repodata_fn=repodata_fn, command=args.cmd)
solver_backend = context.plugin_manager.get_cached_solver_backend()
solver = solver_backend(
prefix,
context.channels,
context.subdirs,
specs_to_add=specs,
repodata_fn=repodata_fn,
command=args.cmd,
)
update_modifier = context.update_modifier
if (isinstall or isremove) and args.update_modifier == NULL:
update_modifier = UpdateModifier.FREEZE_INSTALLED
Expand Down
4 changes: 2 additions & 2 deletions conda/cli/main_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from ..core.envs_manager import unregister_env
from ..core.link import PrefixSetup, UnlinkLinkTransaction
from ..core.prefix_data import PrefixData
from ..core.solve import _get_solver_class
from ..exceptions import CondaEnvironmentError, CondaValueError, DirectoryNotACondaEnvironmentError
from ..gateways.disk.delete import rm_rf, path_is_clean
from ..models.match_spec import MatchSpec
Expand Down Expand Up @@ -84,7 +83,8 @@ def execute(args, parser):
specs = specs_from_args(args.package_names)
channel_urls = ()
subdirs = ()
solver = _get_solver_class()(prefix, channel_urls, subdirs, specs_to_remove=specs)
solver_backend = context.plugin_manager.get_cached_solver_backend()
solver = solver_backend(prefix, channel_urls, subdirs, specs_to_remove=specs)
txn = solver.solve_for_transaction()
handle_txn(txn, prefix, args, False, True)

Expand Down
25 changes: 9 additions & 16 deletions conda/core/solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from logging import DEBUG, getLogger
from os.path import join
import sys
import warnings
from textwrap import dedent

try:
Expand All @@ -29,7 +30,7 @@
from ..common.constants import NULL
from ..common.io import Spinner, dashlist, time_recorder
from ..common.path import get_major_minor_version, paths_equal
from ..exceptions import (CondaValueError, PackagesNotFoundError, SpecsConfigurationConflictError,
from ..exceptions import (PackagesNotFoundError, SpecsConfigurationConflictError,
UnsatisfiableError)
from ..history import History
from ..models.channel import Channel
Expand All @@ -48,21 +49,13 @@ def _get_solver_class(key=None):
See ``context.solver`` for more details.
"""
solvers = context.plugin_manager.get_hook_results("solvers")
key = (key or context.solver).lower()

solvers_mapping = {}
for solver in solvers:
solvers_mapping[solver.name.lower()] = solver.backend

if key in solvers_mapping:
return solvers_mapping[key]
else:
raise CondaValueError(
f"You have chosen a non-default solver backend ({key}) "
f"but it was not recognized. Choose one of: "
f"{', '.join(solvers_mapping.keys())}"
)
warnings.warn(
"`conda.core.solve._get_solver_class` is pending deprecation and will be removed in a "
"future release. Please use `conda.base.context.plugin_manager.get_cached_solver_backend "
"instead.",
PendingDeprecationWarning,
)
return context.plugin_manager.get_cached_solver_backend(key or context.solver)


class Solver:
Expand Down
4 changes: 2 additions & 2 deletions conda/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ def install_actions(prefix, index, specs, force=False, only_names=None, always_c
}, stack_callback=stack_context_default):
from os.path import basename
from ._vendor.boltons.setutils import IndexedSet
from .core.solve import _get_solver_class
from .models.channel import Channel
from .models.dist import Dist
if channel_priority_map:
Expand All @@ -464,7 +463,8 @@ def install_actions(prefix, index, specs, force=False, only_names=None, always_c
from .core.prefix_data import PrefixData
PrefixData._cache_.clear()

solver = _get_solver_class()(prefix, channels, subdirs, specs_to_add=specs)
solver_backend = context.plugin_manager.get_cached_solver_backend()
solver = solver_backend(prefix, channels, subdirs, specs_to_add=specs)
if index:
solver._index = {prec: prec for prec in index.values()}
txn = solver.solve_for_transaction(prune=prune, ignore_pinned=not pinned)
Expand Down
4 changes: 4 additions & 0 deletions conda/plugins/hookspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@


class CondaSpecs:
"""
The conda plugin hookspecs, to be used by developers.
"""

@_hookspec
def conda_solvers(self) -> Iterable[CondaSolver]:
"""
Expand Down
62 changes: 57 additions & 5 deletions conda/plugins/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@
from . import solvers, virtual_packages
from .hookspec import CondaSpecs, spec_name
from ..auxlib.ish import dals
from ..exceptions import PluginError
from ..base.context import context
from ..core.solve import Solver
from ..exceptions import CondaValueError, PluginError


class CondaPluginManager(pluggy.PluginManager):
"""
The conda plugin manager to implement behavior additional to
pluggy's default plugin manager.
"""
#: Cached version of the :meth:`~conda.plugins.manager.CondaPluginManager.get_solver_backend`
#: method.
get_cached_solver_backend = None

def __init__(self, project_name: str | None = None, *args, **kwargs):
def __init__(self, project_name: str | None = None, *args, **kwargs) -> None:
# Setting the default project name to the spec name for ease of use
if project_name is None:
project_name = spec_name
super().__init__(project_name, *args, **kwargs)
# Make the cache containers local to the instances so that the
# reference from cache to the instance gets garbage collected with the instance
self.get_cached_solver_backend = functools.lru_cache(maxsize=None)(self.get_solver_backend)

def load_plugins(self, *plugins) -> list[str]:
"""
Load the provided list of plugins and fail gracefully on failure.
Load the provided list of plugins and fail gracefully on error.
The provided list plugins can either be classes or modules with
:attr:`~conda.plugins.hook_impl`.
"""
plugin_names = []
for plugin in plugins:
Expand All @@ -41,8 +51,11 @@ def load_plugins(self, *plugins) -> list[str]:
return plugin_names

def load_setuptools_entrypoints(self, *args, **kwargs) -> int:
""""
Overloading the parent method to add conda specific exception
"""
Overloading the parent method from pluggy to add conda specific exceptions.
See :meth:`pluggy.PluginManager.load_setuptools_entrypoints` for
more information.
"""
try:
return super().load_setuptools_entrypoints(*args, **kwargs)
Expand Down Expand Up @@ -83,9 +96,48 @@ def get_hook_results(self, name: str) -> list:
)
return plugins

def get_solver_backend(self, name: str = None) -> type[Solver]:
"""
Get the solver backend with the given name (or fall back to the
name provided in the context).
See ``context.solver`` for more details.
Please use the cached version of this method called
:meth:`get_cached_solver_backend` for high-throughput code paths
which is set up as a instance-specific LRU cache.
"""
# Some light data validation in case name isn't given.
if name is None:
name = context.solver
name = name.lower()

# Build a mapping between a lower cased backend name and
# solver backend class provided by the installed plugins.
solvers_mapping = {
solver.name.lower(): solver.backend
for solver in self.get_hook_results("solvers")
}

# Look up the solver mapping an fail loudly if it can't
# find the requested solver.
backend = solvers_mapping.get(name, None)
if backend is None:
raise CondaValueError(
f"You have chosen a non-default solver backend ({name}) "
f"but it was not recognized. Choose one of: "
f"{', '.join(solvers_mapping.keys())}"
)

return backend


@functools.lru_cache(maxsize=None) # FUTURE: Python 3.9+, replace w/ functools.cache
def get_plugin_manager() -> CondaPluginManager:
"""
Get a cached version of the :class:`~conda.plugins.manager.CondaPluginManager`
instance, with the built-in and the entrypoints provided plugins loaded.
"""
plugin_manager = CondaPluginManager()
plugin_manager.add_hookspecs(CondaSpecs)
plugin_manager.load_plugins(solvers, *virtual_packages.plugins)
Expand Down
19 changes: 9 additions & 10 deletions conda/testing/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from ..common.compat import encode_arguments
from ..common.io import argv, captured as common_io_captured, env_var
from ..core.prefix_data import PrefixData
from ..core.solve import _get_solver_class
from ..core.subdir_data import SubdirData, make_feature_record
from ..gateways.disk.delete import rm_rf
from ..gateways.disk.read import lexists
Expand Down Expand Up @@ -591,7 +590,7 @@ def get_solver(tmpdir, specs_to_add=(), specs_to_remove=(), prefix_records=(), h
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(Channel(f"{EXPORTED_CHANNELS_DIR}/channel-1"),),
(context.subdir,),
Expand Down Expand Up @@ -620,7 +619,7 @@ def get_solver_2(tmpdir, specs_to_add=(), specs_to_remove=(), prefix_records=(),
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(Channel(f"{EXPORTED_CHANNELS_DIR}/channel-2"),),
(context.subdir,),
Expand Down Expand Up @@ -649,7 +648,7 @@ def get_solver_4(tmpdir, specs_to_add=(), specs_to_remove=(), prefix_records=(),
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(Channel(f"{EXPORTED_CHANNELS_DIR}/channel-4"),),
(context.subdir,),
Expand Down Expand Up @@ -678,7 +677,7 @@ def get_solver_5(tmpdir, specs_to_add=(), specs_to_remove=(), prefix_records=(),
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(Channel(f"{EXPORTED_CHANNELS_DIR}/channel-5"),),
(context.subdir,),
Expand Down Expand Up @@ -711,7 +710,7 @@ def get_solver_aggregate_1(
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(
Channel(f"{EXPORTED_CHANNELS_DIR}/channel-2"),
Expand Down Expand Up @@ -747,7 +746,7 @@ def get_solver_aggregate_2(
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(
Channel(f"{EXPORTED_CHANNELS_DIR}/channel-4"),
Expand Down Expand Up @@ -781,7 +780,7 @@ def get_solver_must_unfreeze(
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(Channel(f"{EXPORTED_CHANNELS_DIR}/channel-freeze"),),
(context.subdir,),
Expand Down Expand Up @@ -812,7 +811,7 @@ def get_solver_cuda(
# We need CONDA_ADD_PIP_AS_PYTHON_DEPENDENCY=false here again (it's also in
# get_index_r_*) to cover solver logics that need to load from disk instead of
# hitting the SubdirData cache
solver = _get_solver_class()(
solver = context.plugin_manager.get_cached_solver_backend()(
tmpdir,
(Channel(f"{EXPORTED_CHANNELS_DIR}/channel-1"),),
(context.subdir,),
Expand All @@ -836,4 +835,4 @@ def convert_to_dist_str(solution):

@pytest.fixture()
def solver_class():
return _get_solver_class()
return context.plugin_manager.get_cached_solver_backend()
5 changes: 3 additions & 2 deletions conda_env/installers/conda.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from conda.base.constants import UpdateModifier
from conda.base.context import context
from conda.common.constants import NULL
from conda.core.solve import _get_solver_class
from conda.exceptions import UnsatisfiableError
from conda.models.channel import Channel, prioritize_channels

from ..env import Environment


def _solve(prefix, specs, args, env, *_, **kwargs):
# TODO: support all various ways this happens
# Including 'nodefaults' in the channels list disables the defaults
Expand All @@ -26,7 +26,8 @@ def _solve(prefix, specs, args, env, *_, **kwargs):
channels = IndexedSet(Channel(url) for url in _channel_priority_map)
subdirs = IndexedSet(basename(url) for url in _channel_priority_map)

solver = _get_solver_class()(prefix, channels, subdirs, specs_to_add=specs)
solver_backend = context.plugin_manager.get_cached_solver_backend()
solver = solver_backend(prefix, channels, subdirs, specs_to_add=specs)
return solver


Expand Down
7 changes: 7 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"sphinx.ext.graphviz",
"sphinx.ext.ifconfig",
"sphinx.ext.inheritance_diagram",
"sphinx.ext.intersphinx",
"sphinxcontrib.plantuml",
"conda_umls",
"sphinx_sitemap",
Expand Down Expand Up @@ -234,3 +235,9 @@
plantuml = f"java -Djava.awt.headless=true -jar {plantuml_jarfile_path}"

add_module_names = False


intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'pluggy': ('https://pluggy.readthedocs.io/en/stable/', None),
}
Loading

0 comments on commit ba7d9bc

Please sign in to comment.