Skip to content

Commit

Permalink
Move Pool bindings to libmambapy.solver.libsolv
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoinePrv committed Feb 9, 2024
1 parent 4db0c06 commit 5ea0d37
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 71 deletions.
95 changes: 27 additions & 68 deletions libmambapy/src/libmambapy/bindings/legacy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -247,10 +245,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."
);
}
));
Expand Down Expand Up @@ -380,76 +396,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")
);

Expand Down Expand Up @@ -534,7 +493,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));
}
)
Expand Down
101 changes: 98 additions & 3 deletions libmambapy/src/libmambapy/bindings/solver_libsolv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <pybind11/pybind11.h>

#include "mamba/core/palette.hpp"
#include "mamba/fs/filesystem.hpp"
#include "mamba/solver/libsolv/database.hpp"
#include "mamba/solver/libsolv/parameters.hpp"
#include "mamba/solver/libsolv/repo_info.hpp"
Expand Down Expand Up @@ -98,15 +99,109 @@ 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__", &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)
Expand Down
95 changes: 95 additions & 0 deletions libmambapy/tests/test_solver_libsolv.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import copy
import json
import itertools

import pytest

import libmambapy
import libmambapy.solver.libsolv as libsolv


Expand Down Expand Up @@ -103,3 +106,95 @@ 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_repodata_json, add_pip_as_python_dependency, use_only_tar_bz2, repodata_parser
):
db = libsolv.Database(libmambapy.specs.ChannelResolveParams())

repo = db.add_repo_from_repodata_json(
path=str(tmp_repodata_json),
url="https://repo.mamba.pm",
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"]

0 comments on commit 5ea0d37

Please sign in to comment.