mirror of https://github.com/mamba-org/mamba.git
Add libsolv.Database Bindings and tests (#3186)
This commit is contained in:
parent
4db0c06cfa
commit
00219a578e
|
@ -87,6 +87,9 @@ namespace mamba::fs
|
|||
{
|
||||
public:
|
||||
|
||||
using value_type = char;
|
||||
using string_type = std::basic_string<value_type>;
|
||||
|
||||
u8path() = default;
|
||||
|
||||
// Copy is allowed.
|
||||
|
@ -303,6 +306,12 @@ namespace mamba::fs
|
|||
return to_utf8(m_path);
|
||||
}
|
||||
|
||||
// Returns a default encoded string.
|
||||
decltype(auto) native() const
|
||||
{
|
||||
return m_path.native();
|
||||
}
|
||||
|
||||
// Returns an utf-8 string.
|
||||
operator std::string() const
|
||||
{
|
||||
|
|
|
@ -62,9 +62,22 @@ namespace mamba::solver
|
|||
|
||||
private:
|
||||
|
||||
auto base() const -> const Base&;
|
||||
|
||||
auto remove_asym(const key_type& a, const key_type& b) -> bool;
|
||||
|
||||
template <typename TT>
|
||||
friend auto operator==(const conflict_map<TT>& lhs, const conflict_map<TT>& rhs) -> bool;
|
||||
template <typename TT>
|
||||
friend auto operator!=(const conflict_map<TT>& lhs, const conflict_map<TT>& rhs) -> bool;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
auto operator==(const conflict_map<T>& lhs, const conflict_map<T>& rhs) -> bool;
|
||||
|
||||
template <typename T>
|
||||
auto operator!=(const conflict_map<T>& lhs, const conflict_map<T>& rhs) -> bool;
|
||||
|
||||
/**
|
||||
* A directed graph of the packages involved in a libsolv conflict.
|
||||
*/
|
||||
|
@ -313,6 +326,12 @@ namespace mamba::solver
|
|||
return inserted;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto conflict_map<T>::base() const -> const Base&
|
||||
{
|
||||
return static_cast<const Base&>(*this);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto conflict_map<T>::remove_asym(const key_type& a, const key_type& b) -> bool
|
||||
{
|
||||
|
@ -355,6 +374,18 @@ namespace mamba::solver
|
|||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto operator==(const conflict_map<T>& lhs, const conflict_map<T>& rhs) -> bool
|
||||
{
|
||||
return lhs.base() == rhs.base();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto operator!=(const conflict_map<T>& lhs, const conflict_map<T>& rhs) -> bool
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/*********************************
|
||||
* Implementation of NamedList *
|
||||
*********************************/
|
||||
|
@ -369,5 +400,4 @@ namespace mamba::solver
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MAMBA_PROBLEMS_GRAPH_HPP
|
||||
#endif
|
||||
|
|
|
@ -41,9 +41,9 @@ namespace mamba::solver::libsolv
|
|||
return *m_solver;
|
||||
}
|
||||
|
||||
auto UnSolvable::problems(Database& mpool) const -> std::vector<std::string>
|
||||
auto UnSolvable::problems(Database& db) const -> std::vector<std::string>
|
||||
{
|
||||
auto& pool = Database::Impl::get(mpool);
|
||||
auto& pool = Database::Impl::get(db);
|
||||
std::vector<std::string> problems;
|
||||
solver().for_each_problem_id([&](solv::ProblemId pb)
|
||||
{ problems.emplace_back(solver().problem_to_string(pool, pb)); }
|
||||
|
@ -51,9 +51,9 @@ namespace mamba::solver::libsolv
|
|||
return problems;
|
||||
}
|
||||
|
||||
auto UnSolvable::problems_to_str(Database& mpool) const -> std::string
|
||||
auto UnSolvable::problems_to_str(Database& db) const -> std::string
|
||||
{
|
||||
auto& pool = Database::Impl::get(mpool);
|
||||
auto& pool = Database::Impl::get(db);
|
||||
std::stringstream problems;
|
||||
problems << "Encountered problems while solving:\n";
|
||||
solver().for_each_problem_id(
|
||||
|
@ -63,9 +63,9 @@ namespace mamba::solver::libsolv
|
|||
return problems.str();
|
||||
}
|
||||
|
||||
auto UnSolvable::all_problems_to_str(Database& mpool) const -> std::string
|
||||
auto UnSolvable::all_problems_to_str(Database& db) const -> std::string
|
||||
{
|
||||
auto& pool = Database::Impl::get(mpool);
|
||||
auto& pool = Database::Impl::get(db);
|
||||
std::stringstream problems;
|
||||
solver().for_each_problem_id(
|
||||
[&](solv::ProblemId pb)
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <pybind11/cast.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
@ -20,58 +18,22 @@ namespace PYBIND11_NAMESPACE
|
|||
{
|
||||
namespace detail
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template <
|
||||
typename Expected,
|
||||
typename T = typename Expected::value_type,
|
||||
typename E = typename Expected::error_type>
|
||||
auto expected_to_variant(Expected&& expected) -> std::variant<T, E>
|
||||
{
|
||||
if (expected)
|
||||
{
|
||||
return { std::forward<Expected>(expected).value() };
|
||||
}
|
||||
return { std::forward<Expected>(expected).error() };
|
||||
}
|
||||
|
||||
template <
|
||||
typename Variant,
|
||||
typename T = std::decay_t<decltype(std::get<0>(std::declval<Variant>()))>,
|
||||
typename E = std::decay_t<decltype(std::get<1>(std::declval<Variant>()))>>
|
||||
auto expected_to_variant(Variant&& var) -> tl::expected<T, E>
|
||||
{
|
||||
static_assert(std::variant_size_v<Variant> == 2);
|
||||
return std::visit(
|
||||
[](auto&& v) -> tl::expected<T, E> { return { std::forward<deltype(v)>(v) }; },
|
||||
var
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A caster for tl::expected that converts to a union.
|
||||
*
|
||||
* The caster works by converting to a the expected to a variant and then calls the
|
||||
* variant caster.
|
||||
*
|
||||
* A future direction could be considered to wrap the union into a Python "Expected",
|
||||
* with methods such as ``and_then``, ``or_else``, and thowing method like ``value``
|
||||
* and ``error``.
|
||||
* A caster for tl::expected that throws on unexpected.
|
||||
*/
|
||||
template <typename T, typename E>
|
||||
struct type_caster<tl::expected<T, E>>
|
||||
{
|
||||
using value_type = tl::expected<T, E>;
|
||||
using variant_type = std::variant<T, E>;
|
||||
using caster_type = variant_caster<variant_type>;
|
||||
using value_type = T;
|
||||
|
||||
auto load(handle src, bool convert) -> bool
|
||||
{
|
||||
auto caster = caster_type();
|
||||
auto caster = make_caster<T>();
|
||||
if (caster.load(src, convert))
|
||||
{
|
||||
value = variant_to_expected(cast_op<variant_type>(std::move(caster)));
|
||||
value = cast_op<T>(std::move(caster));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -79,14 +41,17 @@ namespace PYBIND11_NAMESPACE
|
|||
template <typename Expected>
|
||||
static auto cast(Expected&& src, return_value_policy policy, handle parent) -> handle
|
||||
{
|
||||
return caster_type::cast(expected_to_variant(std::forward<Expected>(src)), policy, parent);
|
||||
if (src)
|
||||
{
|
||||
return make_caster<T>::cast(std::forward<Expected>(src).value(), policy, parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::forward<Expected>(src).error();
|
||||
}
|
||||
}
|
||||
|
||||
PYBIND11_TYPE_CASTER(
|
||||
value_type,
|
||||
const_name(R"(Union[)") + detail::concat(make_caster<T>::name, make_caster<E>::name)
|
||||
+ const_name(R"(])")
|
||||
);
|
||||
PYBIND11_TYPE_CASTER(value_type, detail::concat(make_caster<T>::name, make_caster<E>::name));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
#include "mamba/core/transaction.hpp"
|
||||
#include "mamba/core/util_os.hpp"
|
||||
#include "mamba/core/virtual_packages.hpp"
|
||||
#include "mamba/solver/libsolv/database.hpp"
|
||||
#include "mamba/solver/libsolv/repo_info.hpp"
|
||||
#include "mamba/solver/problems_graph.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
#include "mamba/validation/update_framework_v0_6.hpp"
|
||||
|
@ -40,6 +38,7 @@
|
|||
#include "bindings.hpp"
|
||||
#include "expected_caster.hpp"
|
||||
#include "flat_set_caster.hpp"
|
||||
#include "path_caster.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
@ -247,10 +246,28 @@ bind_submodule_impl(pybind11::module_ m)
|
|||
throw std::runtime_error( //
|
||||
"Use Pool.add_repo_from_repodata_json or Pool.add_repo_from_native_serialization"
|
||||
" instead and cache with Pool.native_serialize_repo."
|
||||
" Also consider load_subdir_in_pool for a high_level function to load"
|
||||
" subdir index and manage cache, and load_installed_packages_in_pool for loading"
|
||||
" prefix packages."
|
||||
"The Repo class itself has been moved to libmambapy.solver.libsolv.RepoInfo."
|
||||
" Also consider load_subdir_in_database for a high_level function to load"
|
||||
" subdir index and manage cache, and load_installed_packages_in_database for"
|
||||
" loading prefix packages."
|
||||
" The Repo class itself has been moved to libmambapy.solver.libsolv.RepoInfo."
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
struct PoolV2Migrator
|
||||
{
|
||||
};
|
||||
|
||||
py::class_<PoolV2Migrator>(m, "Pool").def(py::init(
|
||||
[](py::args, py::kwargs) -> PoolV2Migrator
|
||||
{
|
||||
throw std::runtime_error( //
|
||||
"libmambapy.Pool has been moved to libmambapy.solver.libsolv.Database."
|
||||
" The database contains functions to directly load packages, from a list or a"
|
||||
" repodata.json."
|
||||
" High level functions such as libmambapy.load_subdir_in_database and"
|
||||
" libmambapy.load_installed_packages_in_database are also available to work"
|
||||
" with other Mamba objects and Context parameters."
|
||||
);
|
||||
}
|
||||
));
|
||||
|
@ -380,76 +397,19 @@ bind_submodule_impl(pybind11::module_ m)
|
|||
|
||||
py::add_ostream_redirect(m, "ostream_redirect");
|
||||
|
||||
py::class_<solver::libsolv::Database>(m, "Pool")
|
||||
.def(py::init<specs::ChannelResolveParams>(), py::arg("channel_params"))
|
||||
.def(
|
||||
"set_logger",
|
||||
&solver::libsolv::Database::set_logger,
|
||||
py::call_guard<py::gil_scoped_acquire>()
|
||||
)
|
||||
.def(
|
||||
"add_repo_from_repodata_json",
|
||||
&solver::libsolv::Database::add_repo_from_repodata_json,
|
||||
py::arg("path"),
|
||||
py::arg("url"),
|
||||
py::arg("add_pip_as_python_dependency") = solver::libsolv::PipAsPythonDependency::No,
|
||||
py::arg("use_only_tar_bz2") = solver::libsolv::UseOnlyTarBz2::No,
|
||||
py::arg("repodata_parsers") = solver::libsolv::RepodataParser::Mamba
|
||||
)
|
||||
.def(
|
||||
"add_repo_from_native_serialization",
|
||||
&solver::libsolv::Database::add_repo_from_native_serialization,
|
||||
py::arg("path"),
|
||||
py::arg("expected"),
|
||||
py::arg("add_pip_as_python_dependency") = solver::libsolv::PipAsPythonDependency::No
|
||||
)
|
||||
.def(
|
||||
"add_repo_from_packages",
|
||||
[](solver::libsolv::Database& db,
|
||||
py::iterable packages,
|
||||
std::string_view name,
|
||||
solver::libsolv::PipAsPythonDependency add)
|
||||
{
|
||||
// TODO(C++20): No need to copy in a vector, simply transform the input range.
|
||||
auto pkg_infos = std::vector<specs::PackageInfo>();
|
||||
for (py::handle pkg : packages)
|
||||
{
|
||||
pkg_infos.push_back(pkg.cast<specs::PackageInfo>());
|
||||
}
|
||||
return db.add_repo_from_packages(pkg_infos, name, add);
|
||||
},
|
||||
py::arg("packages"),
|
||||
py::arg("name") = "",
|
||||
py::arg("add_pip_as_python_dependency") = solver::libsolv::PipAsPythonDependency::No
|
||||
)
|
||||
.def(
|
||||
"native_serialize_repo",
|
||||
&solver::libsolv::Database::native_serialize_repo,
|
||||
py::arg("repo"),
|
||||
py::arg("path"),
|
||||
py::arg("metadata")
|
||||
)
|
||||
.def("set_installed_repo", &solver::libsolv::Database::set_installed_repo, py::arg("repo"))
|
||||
.def(
|
||||
"set_repo_priority",
|
||||
&solver::libsolv::Database::set_repo_priority,
|
||||
py::arg("repo"),
|
||||
py::arg("priorities")
|
||||
);
|
||||
|
||||
m.def(
|
||||
"load_subdir_in_pool",
|
||||
"load_subdir_in_database",
|
||||
&load_subdir_in_database,
|
||||
py::arg("context"),
|
||||
py::arg("pool"),
|
||||
py::arg("database"),
|
||||
py::arg("subdir")
|
||||
);
|
||||
|
||||
m.def(
|
||||
"load_installed_packages_in_pool",
|
||||
"load_installed_packages_in_database",
|
||||
&load_installed_packages_in_database,
|
||||
py::arg("context"),
|
||||
py::arg("pool"),
|
||||
py::arg("database"),
|
||||
py::arg("prefix_data")
|
||||
);
|
||||
|
||||
|
@ -534,7 +494,7 @@ bind_submodule_impl(pybind11::module_ m)
|
|||
"create_repo",
|
||||
[](SubdirData& subdir, solver::libsolv::Database& db) -> solver::libsolv::RepoInfo
|
||||
{
|
||||
deprecated("Use `load_subdir_in_pool` instead", "2.0");
|
||||
deprecated("Use libmambapy.load_subdir_in_database instead", "2.0");
|
||||
return extract(load_subdir_in_database(mambapy::singletons.context(), db, subdir));
|
||||
}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2024, QuantStack and Mamba Contributors
|
||||
//
|
||||
// Distributed under the terms of the BSD 3-Clause License.
|
||||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl/filesystem.h>
|
||||
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
|
||||
namespace PYBIND11_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <>
|
||||
struct type_caster<mamba::fs::u8path> : path_caster<mamba::fs::u8path>
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
#include "mamba/solver/solution.hpp"
|
||||
|
||||
#include "bindings.hpp"
|
||||
#include "flat_set_caster.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace mamba::solver
|
||||
|
@ -356,7 +357,12 @@ namespace mambapy
|
|||
.def("conflicts", &ProblemsGraph::conflicts_t::conflicts)
|
||||
.def("in_conflict", &ProblemsGraph::conflicts_t::in_conflict)
|
||||
.def("clear", [](ProblemsGraph::conflicts_t& self) { return self.clear(); })
|
||||
.def("add", &ProblemsGraph::conflicts_t::add);
|
||||
.def("add", &ProblemsGraph::conflicts_t::add)
|
||||
.def(py::self == py::self)
|
||||
.def(py::self != py::self)
|
||||
.def("__copy__", ©<ProblemsGraph::conflicts_t>)
|
||||
.def("__deepcopy__", &deepcopy<ProblemsGraph::conflicts_t>, py::arg("memo"));
|
||||
|
||||
|
||||
py_problems_graph.def("root_node", &ProblemsGraph::root_node)
|
||||
.def("conflicts", &ProblemsGraph::conflicts)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "bindings.hpp"
|
||||
#include "expected_caster.hpp"
|
||||
#include "path_caster.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace mambapy
|
||||
|
@ -98,21 +99,120 @@ namespace mambapy
|
|||
.def("__deepcopy__", &deepcopy<RepodataOrigin>, py::arg("memo"));
|
||||
|
||||
py::class_<RepoInfo>(m, "RepoInfo")
|
||||
.def("id", &RepoInfo::id)
|
||||
.def("name", &RepoInfo::name)
|
||||
.def("priority", &RepoInfo::priority)
|
||||
.def_property_readonly("id", &RepoInfo::id)
|
||||
.def_property_readonly("name", &RepoInfo::name)
|
||||
.def_property_readonly("priority", &RepoInfo::priority)
|
||||
.def("package_count", &RepoInfo::package_count)
|
||||
.def(py::self == py::self)
|
||||
.def(py::self != py::self)
|
||||
.def("__copy__", ©<RepoInfo>)
|
||||
.def("__deepcopy__", &deepcopy<RepoInfo>, py::arg("memo"));
|
||||
|
||||
py::class_<Database>(m, "Database")
|
||||
.def(py::init<specs::ChannelResolveParams>(), py::arg("channel_params"))
|
||||
.def("set_logger", &Database::set_logger, py::call_guard<py::gil_scoped_acquire>())
|
||||
.def(
|
||||
"add_repo_from_repodata_json",
|
||||
&Database::add_repo_from_repodata_json,
|
||||
py::arg("path"),
|
||||
py::arg("url"),
|
||||
py::arg("add_pip_as_python_dependency") = PipAsPythonDependency::No,
|
||||
py::arg("use_only_tar_bz2") = UseOnlyTarBz2::No,
|
||||
py::arg("repodata_parser") = RepodataParser::Mamba
|
||||
)
|
||||
.def(
|
||||
"add_repo_from_native_serialization",
|
||||
&Database::add_repo_from_native_serialization,
|
||||
py::arg("path"),
|
||||
py::arg("expected"),
|
||||
py::arg("add_pip_as_python_dependency") = PipAsPythonDependency::No
|
||||
)
|
||||
.def(
|
||||
"add_repo_from_packages",
|
||||
[](Database& db, py::iterable packages, std::string_view name, PipAsPythonDependency add)
|
||||
{
|
||||
// TODO(C++20): No need to copy in a vector, simply transform the input range.
|
||||
auto pkg_infos = std::vector<specs::PackageInfo>();
|
||||
for (py::handle pkg : packages)
|
||||
{
|
||||
pkg_infos.push_back(pkg.cast<specs::PackageInfo>());
|
||||
}
|
||||
return db.add_repo_from_packages(pkg_infos, name, add);
|
||||
},
|
||||
py::arg("packages"),
|
||||
py::arg("name") = "",
|
||||
py::arg("add_pip_as_python_dependency") = PipAsPythonDependency::No
|
||||
)
|
||||
.def(
|
||||
"native_serialize_repo",
|
||||
&Database::native_serialize_repo,
|
||||
py::arg("repo"),
|
||||
py::arg("path"),
|
||||
py::arg("metadata")
|
||||
)
|
||||
.def("set_installed_repo", &Database::set_installed_repo, py::arg("repo"))
|
||||
.def("installed_repo", &Database::installed_repo)
|
||||
.def("set_repo_priority", &Database::set_repo_priority, py::arg("repo"), py::arg("priorities"))
|
||||
.def("remove_repo", &Database::remove_repo, py::arg("repo"))
|
||||
.def("repo_count", &Database::repo_count)
|
||||
.def("package_count", &Database::package_count)
|
||||
.def(
|
||||
"packages_in_repo",
|
||||
[](const Database& db, RepoInfo repo)
|
||||
{
|
||||
// TODO(C++20): When Database function are refactored to use range, take the
|
||||
// opportunity here to make a Python iterator to avoid large alloc.
|
||||
auto out = py::list();
|
||||
db.for_each_package_in_repo(
|
||||
repo,
|
||||
[&](specs::PackageInfo&& pkg) { out.append(std::move(pkg)); }
|
||||
);
|
||||
return out;
|
||||
},
|
||||
py::arg("repo")
|
||||
)
|
||||
.def(
|
||||
"packages_matching",
|
||||
[](Database& db, const specs::MatchSpec& ms)
|
||||
{
|
||||
// TODO(C++20): When Database function are refactored to use range, take the
|
||||
// opportunity here to make a Python iterator to avoid large alloc.
|
||||
auto out = py::list();
|
||||
db.for_each_package_matching(
|
||||
ms,
|
||||
[&](specs::PackageInfo&& pkg) { out.append(std::move(pkg)); }
|
||||
);
|
||||
return out;
|
||||
},
|
||||
py::arg("spec")
|
||||
)
|
||||
.def(
|
||||
"packages_depending_on",
|
||||
[](Database& db, const specs::MatchSpec& ms)
|
||||
{
|
||||
// TODO(C++20): When Database function are refactored to use range, take the
|
||||
// opportunity here to make a Python iterator to avoid large alloc.
|
||||
auto out = py::list();
|
||||
db.for_each_package_depending_on(
|
||||
ms,
|
||||
[&](specs::PackageInfo&& pkg) { out.append(std::move(pkg)); }
|
||||
);
|
||||
return out;
|
||||
},
|
||||
py::arg("spec")
|
||||
);
|
||||
|
||||
py::class_<UnSolvable>(m, "UnSolvable")
|
||||
.def("problems", &UnSolvable::problems)
|
||||
.def("problems_to_str", &UnSolvable::problems_to_str)
|
||||
.def("all_problems_to_str", &UnSolvable::all_problems_to_str)
|
||||
.def("problems_graph", &UnSolvable::problems_graph)
|
||||
.def("explain_problems", &UnSolvable::explain_problems);
|
||||
.def("problems", &UnSolvable::problems, py::arg("database"))
|
||||
.def("problems_to_str", &UnSolvable::problems_to_str, py::arg("database"))
|
||||
.def("all_problems_to_str", &UnSolvable::all_problems_to_str, py::arg("database"))
|
||||
.def("problems_graph", &UnSolvable::problems_graph, py::arg("database"))
|
||||
.def(
|
||||
"explain_problems",
|
||||
&UnSolvable::explain_problems,
|
||||
py::arg("database"),
|
||||
py::arg("palette")
|
||||
);
|
||||
|
||||
constexpr auto solver_flags_v2_migrator = [](Solver&, py::args, py::kwargs) {
|
||||
throw std::runtime_error("All flags need to be passed in the libmambapy.solver.Request.");
|
||||
|
|
|
@ -590,11 +590,69 @@ namespace mambapy
|
|||
py::class_<PackageInfo>(m, "PackageInfo")
|
||||
.def_static("from_url", PackageInfo::from_url)
|
||||
.def(
|
||||
py::init<std::string, std::string, std::string, std::size_t>(),
|
||||
py::arg("name") = "",
|
||||
py::arg("version") = "",
|
||||
py::arg("build_string") = "",
|
||||
py::arg("build_number") = 0
|
||||
py::init(
|
||||
[](decltype(PackageInfo::name) name,
|
||||
decltype(PackageInfo::version) version,
|
||||
decltype(PackageInfo::build_string) build_string,
|
||||
decltype(PackageInfo::build_number) build_number,
|
||||
decltype(PackageInfo::channel) channel,
|
||||
decltype(PackageInfo::package_url) package_url,
|
||||
decltype(PackageInfo::subdir) subdir,
|
||||
decltype(PackageInfo::filename) filename,
|
||||
decltype(PackageInfo::license) license,
|
||||
decltype(PackageInfo::md5) md5,
|
||||
decltype(PackageInfo::sha256) sha256,
|
||||
decltype(PackageInfo::signatures) signatures,
|
||||
decltype(PackageInfo::track_features) track_features,
|
||||
decltype(PackageInfo::depends) depends,
|
||||
decltype(PackageInfo::constrains) constrains,
|
||||
decltype(PackageInfo::defaulted_keys) defaulted_keys,
|
||||
decltype(PackageInfo::noarch) noarch,
|
||||
decltype(PackageInfo::size) size,
|
||||
decltype(PackageInfo::timestamp) timestamp) -> PackageInfo
|
||||
{
|
||||
auto pkg = PackageInfo();
|
||||
pkg.name = std::move(name);
|
||||
pkg.version = std::move(version);
|
||||
pkg.build_string = std::move(build_string);
|
||||
pkg.build_number = std::move(build_number);
|
||||
pkg.channel = channel;
|
||||
pkg.package_url = package_url;
|
||||
pkg.subdir = subdir;
|
||||
pkg.filename = filename;
|
||||
pkg.license = license;
|
||||
pkg.md5 = md5;
|
||||
pkg.sha256 = sha256;
|
||||
pkg.signatures = signatures;
|
||||
pkg.track_features = track_features;
|
||||
pkg.depends = depends;
|
||||
pkg.constrains = constrains;
|
||||
pkg.defaulted_keys = defaulted_keys;
|
||||
pkg.noarch = noarch;
|
||||
pkg.size = size;
|
||||
pkg.timestamp = timestamp;
|
||||
return pkg;
|
||||
}
|
||||
),
|
||||
py::arg("name") = decltype(PackageInfo::name)(),
|
||||
py::arg("version") = decltype(PackageInfo::version)(),
|
||||
py::arg("build_string") = decltype(PackageInfo::build_string)(),
|
||||
py::arg("build_number") = decltype(PackageInfo::build_number)(),
|
||||
py::arg("channel") = decltype(PackageInfo::channel)(),
|
||||
py::arg("package_url") = decltype(PackageInfo::package_url)(),
|
||||
py::arg("subdir") = decltype(PackageInfo::subdir)(),
|
||||
py::arg("filename") = decltype(PackageInfo::filename)(),
|
||||
py::arg("license") = decltype(PackageInfo::license)(),
|
||||
py::arg("md5") = decltype(PackageInfo::md5)(),
|
||||
py::arg("sha256") = decltype(PackageInfo::sha256)(),
|
||||
py::arg("signatures") = decltype(PackageInfo::signatures)(),
|
||||
py::arg("track_features") = decltype(PackageInfo::track_features)(),
|
||||
py::arg("depends") = decltype(PackageInfo::depends)(),
|
||||
py::arg("constrains") = decltype(PackageInfo::constrains)(),
|
||||
py::arg("defaulted_keys") = decltype(PackageInfo::defaulted_keys)(),
|
||||
py::arg("noarch") = decltype(PackageInfo::noarch)(),
|
||||
py::arg("size") = decltype(PackageInfo::size)(),
|
||||
py::arg("timestamp") = decltype(PackageInfo::timestamp)()
|
||||
)
|
||||
.def_readwrite("name", &PackageInfo::name)
|
||||
.def_readwrite("version", &PackageInfo::version)
|
||||
|
|
|
@ -3,7 +3,7 @@ import copy
|
|||
|
||||
import pytest
|
||||
|
||||
import libmambapy.solver
|
||||
import libmambapy
|
||||
|
||||
|
||||
def test_import_submodule():
|
||||
|
@ -193,3 +193,49 @@ def test_Solution():
|
|||
other = copy.deepcopy(sol)
|
||||
assert other is not sol
|
||||
assert len(other.actions) == len(sol.actions)
|
||||
|
||||
|
||||
def test_ProblemsGraph():
|
||||
# Create a ProblemsGraph
|
||||
db = libmambapy.solver.libsolv.Database(libmambapy.specs.ChannelResolveParams())
|
||||
db.add_repo_from_packages(
|
||||
[
|
||||
libmambapy.specs.PackageInfo(name="a", version="1.0", depends=["b>=2.0", "c>=2.1"]),
|
||||
libmambapy.specs.PackageInfo(name="b", version="2.0", depends=["c<2.0"]),
|
||||
libmambapy.specs.PackageInfo(name="c", version="1.0"),
|
||||
libmambapy.specs.PackageInfo(name="c", version="3.0"),
|
||||
],
|
||||
)
|
||||
|
||||
request = libmambapy.solver.Request(
|
||||
[libmambapy.solver.Request.Install(libmambapy.specs.MatchSpec.parse("a"))]
|
||||
)
|
||||
|
||||
outcome = libmambapy.solver.libsolv.Solver().solve(db, request)
|
||||
|
||||
assert isinstance(outcome, libmambapy.solver.libsolv.UnSolvable)
|
||||
pbg = outcome.problems_graph(db)
|
||||
|
||||
# ProblemsGraph conflicts
|
||||
conflicts = pbg.conflicts()
|
||||
assert len(conflicts) == 2
|
||||
assert len(list(conflicts)) == 2
|
||||
node, in_conflict = next(iter(conflicts))
|
||||
assert conflicts.has_conflict(node)
|
||||
for other in in_conflict:
|
||||
assert conflicts.in_conflict(node, other)
|
||||
|
||||
other_conflicts = copy.deepcopy(conflicts)
|
||||
assert other_conflicts is not conflicts
|
||||
assert other_conflicts == conflicts
|
||||
|
||||
other_conflicts.clear()
|
||||
assert len(other_conflicts) == 0
|
||||
|
||||
other_conflicts.add(7, 42)
|
||||
assert other_conflicts.in_conflict(7, 42)
|
||||
|
||||
# ProblemsGraph graph
|
||||
nodes, edges = pbg.graph()
|
||||
assert len(nodes) > 0
|
||||
assert len(edges) > 0
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import copy
|
||||
import json
|
||||
import itertools
|
||||
|
||||
import pytest
|
||||
|
||||
import libmambapy
|
||||
import libmambapy.solver.libsolv as libsolv
|
||||
|
||||
|
||||
|
@ -103,3 +106,163 @@ def test_RepodataOrigin():
|
|||
other = copy.deepcopy(orig)
|
||||
assert other is not orig
|
||||
assert other == orig
|
||||
|
||||
|
||||
@pytest.mark.parametrize("add_pip_as_python_dependency", [True, False])
|
||||
def test_Database_RepoInfo_from_packages(add_pip_as_python_dependency):
|
||||
db = libsolv.Database(libmambapy.specs.ChannelResolveParams())
|
||||
assert db.repo_count() == 0
|
||||
assert db.installed_repo() is None
|
||||
assert db.package_count() == 0
|
||||
|
||||
repo = db.add_repo_from_packages(
|
||||
[libmambapy.specs.PackageInfo(name="python")],
|
||||
name="duck",
|
||||
add_pip_as_python_dependency=add_pip_as_python_dependency,
|
||||
)
|
||||
db.set_installed_repo(repo)
|
||||
|
||||
assert repo.id > 0
|
||||
assert repo.name == "duck"
|
||||
assert repo.priority == libsolv.Priorities()
|
||||
assert repo.package_count() == 1
|
||||
assert db.repo_count() == 1
|
||||
assert db.package_count() == 1
|
||||
assert db.installed_repo() == repo
|
||||
|
||||
new_priority = libsolv.Priorities(2, 3)
|
||||
db.set_repo_priority(repo, new_priority)
|
||||
assert repo.priority == new_priority
|
||||
|
||||
pkgs = db.packages_in_repo(repo)
|
||||
assert len(pkgs) == 1
|
||||
assert pkgs[0].name == "python"
|
||||
assert pkgs[0].depends == [] if add_pip_as_python_dependency else ["pip"]
|
||||
|
||||
db.remove_repo(repo)
|
||||
assert db.repo_count() == 0
|
||||
assert db.package_count() == 0
|
||||
assert db.installed_repo() is None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_repodata_json(tmp_path):
|
||||
file = tmp_path / "repodata.json"
|
||||
with open(file, "w+") as f:
|
||||
json.dump(
|
||||
{
|
||||
"packages": {
|
||||
"python-1.0-bld": {
|
||||
"name": "python",
|
||||
"version": "1.0",
|
||||
"build": "bld",
|
||||
"build_number": 0,
|
||||
},
|
||||
},
|
||||
"packages.conda": {
|
||||
"foo-1.0-bld": {
|
||||
"name": "foo",
|
||||
"version": "1.0",
|
||||
"build": "bld",
|
||||
"build_number": 0,
|
||||
}
|
||||
},
|
||||
},
|
||||
f,
|
||||
)
|
||||
return file
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["add_pip_as_python_dependency", "use_only_tar_bz2", "repodata_parser"],
|
||||
itertools.product([True, False], [True, False], ["Mamba", "Libsolv"]),
|
||||
)
|
||||
def test_Database_RepoInfo_from_repodata(
|
||||
tmp_path, tmp_repodata_json, add_pip_as_python_dependency, use_only_tar_bz2, repodata_parser
|
||||
):
|
||||
db = libsolv.Database(libmambapy.specs.ChannelResolveParams())
|
||||
|
||||
url = "https://repo.mamba.pm"
|
||||
|
||||
# Json
|
||||
repo = db.add_repo_from_repodata_json(
|
||||
path=tmp_repodata_json,
|
||||
url=url,
|
||||
add_pip_as_python_dependency=add_pip_as_python_dependency,
|
||||
use_only_tar_bz2=use_only_tar_bz2,
|
||||
repodata_parser=repodata_parser,
|
||||
)
|
||||
db.set_installed_repo(repo)
|
||||
|
||||
assert repo.package_count() == 1 if use_only_tar_bz2 else 2
|
||||
assert db.package_count() == repo.package_count()
|
||||
|
||||
pkgs = db.packages_in_repo(repo)
|
||||
assert len(pkgs) == repo.package_count()
|
||||
assert pkgs[0].name == "python"
|
||||
assert pkgs[0].depends == [] if add_pip_as_python_dependency else ["pip"]
|
||||
|
||||
# Native serialize repo
|
||||
solv_file = tmp_path / "repodata.solv"
|
||||
|
||||
origin = libsolv.RepodataOrigin(url=url)
|
||||
repo_saved = db.native_serialize_repo(repo, path=solv_file, metadata=origin)
|
||||
assert repo_saved == repo
|
||||
|
||||
# Native deserialize repo
|
||||
db.remove_repo(repo)
|
||||
assert db.package_count() == 0
|
||||
|
||||
repo_loaded = db.add_repo_from_native_serialization(
|
||||
path=solv_file, expected=origin, add_pip_as_python_dependency=add_pip_as_python_dependency
|
||||
)
|
||||
assert repo_loaded.package_count() == 1 if use_only_tar_bz2 else 2
|
||||
|
||||
|
||||
def test_Database_RepoInfo_from_repodata_error():
|
||||
db = libsolv.Database(libmambapy.specs.ChannelResolveParams())
|
||||
|
||||
with pytest.raises(libmambapy.MambaNativeException, match=r"[/\\]does[/\\]not[/\\]exists"):
|
||||
db.add_repo_from_repodata_json(path="/does/not/exists", url="https://repo..mamba.pm")
|
||||
|
||||
with pytest.raises(libmambapy.MambaNativeException, match=r"[/\\]does[/\\]not[/\\]exists"):
|
||||
db.add_repo_from_native_serialization(
|
||||
path="/does/not/exists", expected=libsolv.RepodataOrigin()
|
||||
)
|
||||
|
||||
|
||||
def test_Solver_UnSolvable():
|
||||
Request = libmambapy.solver.Request
|
||||
|
||||
db = libsolv.Database(libmambapy.specs.ChannelResolveParams())
|
||||
|
||||
request = Request([Request.Install(libmambapy.specs.MatchSpec.parse("a>1.0"))])
|
||||
|
||||
solver = libsolv.Solver()
|
||||
outcome = solver.solve(db, request)
|
||||
|
||||
assert isinstance(outcome, libsolv.UnSolvable)
|
||||
assert "nothing provides" in "\n".join(outcome.problems(db))
|
||||
assert "nothing provides" in outcome.problems_to_str(db)
|
||||
assert "nothing provides" in outcome.all_problems_to_str(db)
|
||||
assert "The following package could not be installed" in outcome.explain_problems(
|
||||
db, libmambapy.Palette.no_color()
|
||||
)
|
||||
assert outcome.problems_graph(db).graph() is not None
|
||||
|
||||
|
||||
def test_Solver_Solution():
|
||||
Request = libmambapy.solver.Request
|
||||
|
||||
db = libsolv.Database(libmambapy.specs.ChannelResolveParams())
|
||||
db.add_repo_from_packages(
|
||||
[libmambapy.specs.PackageInfo(name="foo")],
|
||||
)
|
||||
|
||||
request = Request([Request.Install(libmambapy.specs.MatchSpec.parse("foo"))])
|
||||
|
||||
solver = libsolv.Solver()
|
||||
outcome = solver.solve(db, request)
|
||||
|
||||
assert isinstance(outcome, libmambapy.solver.Solution)
|
||||
assert len(outcome.actions) == 1
|
||||
|
|
Loading…
Reference in New Issue