Bind text_style and graphic params (#3266)

* Rename binding utils.hpp > bind_utils.hpp

* Bind fmt::text_style

* Change format type in Unsolvable explain message

* Bind attributes

* Bind GraphicsParams
This commit is contained in:
Antoine Prouvost 2024-04-08 16:39:38 +02:00 committed by GitHub
parent b5195d96b0
commit 80f1e84d49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 372 additions and 32 deletions

View File

@ -26,7 +26,6 @@ namespace mamba
/** Some action is unsafe or not trusted. */
fmt::text_style unsafe;
/** Reference to some input from the user. */
fmt::text_style user;
/** Input from the user was ignored or has no effect. */

View File

@ -50,11 +50,11 @@ namespace mamba::solver::libsolv
auto explain_problems_to( //
Database& pool,
std::ostream& out,
const Palette& palette
const ProblemsMessageFormat& format
) const -> std::ostream&;
[[nodiscard]] auto
explain_problems(Database& pool, const Palette& palette) const -> std::string;
explain_problems(Database& pool, const ProblemsMessageFormat& format) const -> std::string;
private:

View File

@ -588,7 +588,14 @@ namespace mamba
if (auto* unsolvable = std::get_if<solver::libsolv::UnSolvable>(&outcome))
{
unsolvable->explain_problems_to(db, LOG_ERROR, ctx.graphics_params.palette);
unsolvable->explain_problems_to(
db,
LOG_ERROR,
{
/* .unavailable= */ ctx.graphics_params.palette.failure,
/* .available= */ ctx.graphics_params.palette.success,
}
);
if (retry_clean_cache && !is_retry)
{
ctx.local_repodata_ttl = 2;

View File

@ -482,30 +482,26 @@ namespace mamba::solver::libsolv
return ProblemsGraphCreator(Database::Impl::get(pool), *m_solver).problem_graph();
}
auto
UnSolvable::explain_problems_to(Database& pool, std::ostream& out, const Palette& palette) const
-> std::ostream&
auto UnSolvable::explain_problems_to(
Database& pool,
std::ostream& out,
const ProblemsMessageFormat& format
) const -> std::ostream&
{
out << "Could not solve for environment specs\n";
const auto pbs = problems_graph(pool);
const auto pbs_simplified = simplify_conflicts(pbs);
const auto cp_pbs = CompressedProblemsGraph::from_problems_graph(pbs_simplified);
print_problem_tree_msg(
out,
cp_pbs,
{
/* .unavailable= */ palette.failure,
/* .available= */ palette.success,
}
);
print_problem_tree_msg(out, cp_pbs, format);
return out;
}
auto UnSolvable::explain_problems(Database& pool, const Palette& palette) const -> std::string
auto
UnSolvable::explain_problems(Database& pool, const ProblemsMessageFormat& format) const -> std::string
{
std::stringstream ss;
explain_problems_to(pool, ss, palette);
explain_problems_to(pool, ss, format);
return ss.str();
}
}

View File

@ -31,6 +31,7 @@ pybind11_add_module(
# All bindings used to live in a global module
src/libmambapy/bindings/legacy.cpp
# Submodules
src/libmambapy/bindings/utils.cpp
src/libmambapy/bindings/specs.cpp
src/libmambapy/bindings/solver.cpp
src/libmambapy/bindings/solver_libsolv.cpp

View File

@ -1,4 +1,5 @@
# Import all submodules so that one can use them directly with `import libmambapy`
import libmambapy.utils
import libmambapy.version
import libmambapy.specs
import libmambapy.solver

View File

@ -4,8 +4,8 @@
//
// The full license is in the file LICENSE, distributed with this software.
#ifndef LIBMAMBAPY_UTILS_HPP
#define LIBMAMBAPY_UTILS_HPP
#ifndef LIBMAMBAPY_BIND_UTILS_HPP
#define LIBMAMBAPY_BIND_UTILS_HPP
#include <memory>

View File

@ -8,6 +8,7 @@
PYBIND11_MODULE(bindings, m)
{
mambapy::bind_submodule_utils(m.def_submodule("utils"));
mambapy::bind_submodule_specs(m.def_submodule("specs"));
auto solver_submodule = m.def_submodule("solver");
mambapy::bind_submodule_solver(solver_submodule);

View File

@ -11,6 +11,7 @@
namespace mambapy
{
void bind_submodule_utils(pybind11::module_ m);
void bind_submodule_specs(pybind11::module_ m);
void bind_submodule_solver(pybind11::module_ m);
void bind_submodule_solver_libsolv(pybind11::module_ m);

View File

@ -35,11 +35,11 @@
#include "mamba/validation/tools.hpp"
#include "mamba/validation/update_framework_v0_6.hpp"
#include "bind_utils.hpp"
#include "bindings.hpp"
#include "expected_caster.hpp"
#include "flat_set_caster.hpp"
#include "path_caster.hpp"
#include "utils.hpp"
namespace py = pybind11;
@ -620,7 +620,25 @@ bind_submodule_impl(pybind11::module_ m)
py::class_<Palette>(m, "Palette")
.def_static("no_color", &Palette::no_color)
.def_static("terminal", &Palette::terminal);
.def_static("terminal", &Palette::terminal)
.def_readwrite("success", &Palette::success)
.def_readwrite("failure", &Palette::failure)
.def_readwrite("external", &Palette::external)
.def_readwrite("shown", &Palette::shown)
.def_readwrite("safe", &Palette::safe)
.def_readwrite("unsafe", &Palette::unsafe)
.def_readwrite("user", &Palette::user)
.def_readwrite("ignored", &Palette::ignored)
.def_readwrite("addition", &Palette::addition)
.def_readwrite("deletion", &Palette::deletion)
.def_readwrite("progress_bar_none", &Palette::progress_bar_none)
.def_readwrite("progress_bar_downloaded", &Palette::progress_bar_downloaded)
.def_readwrite("progress_bar_extracted", &Palette::progress_bar_extracted);
py::class_<Context::GraphicsParams>(m, "GraphicsParams")
.def(py::init())
.def_readwrite("no_progress_bars", &Context::GraphicsParams::no_progress_bars)
.def_readwrite("palette", &Context::GraphicsParams::palette);
py::class_<Context, std::unique_ptr<Context, py::nodelete>> ctx(m, "Context");
ctx //
@ -641,6 +659,7 @@ bind_submodule_impl(pybind11::module_ m)
}
))
.def_static("use_default_signal_handler", &Context::use_default_signal_handler)
.def_readwrite("graphics_params", &Context::graphics_params)
.def_readwrite("offline", &Context::offline)
.def_readwrite("local_repodata_ttl", &Context::local_repodata_ttl)
.def_readwrite("use_index_cache", &Context::use_index_cache)

View File

@ -11,9 +11,9 @@
#include "mamba/solver/request.hpp"
#include "mamba/solver/solution.hpp"
#include "bind_utils.hpp"
#include "bindings.hpp"
#include "flat_set_caster.hpp"
#include "utils.hpp"
namespace mamba::solver
{
@ -388,6 +388,31 @@ namespace mambapy
)
.def_static("simplify_conflicts", &solver::simplify_conflicts);
py::class_<ProblemsMessageFormat>(m, "ProblemsMessageFormat")
.def(py::init<>())
.def(
py::init(
[](fmt::text_style unavailable,
fmt::text_style available,
std::array<std::string_view, 4> indents) -> ProblemsMessageFormat
{
return {
/* .unavailable= */ unavailable,
/* .available= */ available,
/* .indents= */ indents,
};
}
),
py::arg("unavailable"),
py::arg("available"),
py::arg("indents")
)
.def_readwrite("unavailable", &ProblemsMessageFormat::unavailable)
.def_readwrite("available", &ProblemsMessageFormat::available)
.def_readwrite("indents", &ProblemsMessageFormat::indents)
.def("__copy__", &copy<ProblemsMessageFormat>)
.def("__deepcopy__", &deepcopy<ProblemsMessageFormat>, py::arg("memo"));
auto py_compressed_problems_graph = py::class_<CompressedProblemsGraph>(
m,
"CompressedProblemsGraph"
@ -481,9 +506,6 @@ namespace mambapy
return std::pair(g.nodes(), g.edges());
}
)
.def(
"tree_message",
[](const CompressedProblemsGraph& self) { return problem_tree_msg(self); }
);
.def("tree_message", &problem_tree_msg, py::arg("format") = ProblemsMessageFormat());
}
}

View File

@ -8,17 +8,16 @@
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include "mamba/core/palette.hpp"
#include "mamba/solver/libsolv/database.hpp"
#include "mamba/solver/libsolv/parameters.hpp"
#include "mamba/solver/libsolv/repo_info.hpp"
#include "mamba/solver/libsolv/solver.hpp"
#include "mamba/solver/libsolv/unsolvable.hpp"
#include "bind_utils.hpp"
#include "bindings.hpp"
#include "expected_caster.hpp"
#include "path_caster.hpp"
#include "utils.hpp"
namespace mambapy
{
@ -223,7 +222,7 @@ namespace mambapy
"explain_problems",
&UnSolvable::explain_problems,
py::arg("database"),
py::arg("palette")
py::arg("format")
);
constexpr auto solver_flags_v2_migrator = [](Solver&, py::args, py::kwargs) {

View File

@ -21,10 +21,10 @@
#include "mamba/specs/version.hpp"
#include "mamba/specs/version_spec.hpp"
#include "bind_utils.hpp"
#include "bindings.hpp"
#include "expected_caster.hpp"
#include "flat_set_caster.hpp"
#include "utils.hpp"
#include "weakening_map_bind.hpp"
PYBIND11_MAKE_OPAQUE(mamba::specs::VersionPart);

View File

@ -0,0 +1,151 @@
// 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 <optional>
#include <variant>
#include <fmt/color.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include "bind_utils.hpp"
namespace mambapy
{
void bind_submodule_utils(pybind11::module_ m)
{
namespace py = pybind11;
py::enum_<fmt::emphasis>(m, "TextEmphasis")
.value("Bold", fmt::emphasis::bold)
.value("Faint", fmt::emphasis::faint)
.value("Italic", fmt::emphasis::italic)
.value("Underline", fmt::emphasis::underline)
.value("Blink", fmt::emphasis::blink)
.value("Reverse", fmt::emphasis::reverse)
.value("Conceal", fmt::emphasis::conceal)
.value("Strikethrough", fmt::emphasis::strikethrough)
.def(py::init(&enum_from_str<fmt::emphasis>));
py::implicitly_convertible<py::str, fmt::emphasis>();
py::enum_<fmt::terminal_color>(m, "TextTerminalColor")
.value("Black", fmt::terminal_color::black)
.value("Red", fmt::terminal_color::red)
.value("Green", fmt::terminal_color::green)
.value("Yellow", fmt::terminal_color::yellow)
.value("Blue", fmt::terminal_color::blue)
.value("Magenta", fmt::terminal_color::magenta)
.value("Cyan", fmt::terminal_color::cyan)
.value("White", fmt::terminal_color::white)
.value("BrightBlack", fmt::terminal_color::bright_black)
.value("BrightRed", fmt::terminal_color::bright_red)
.value("BrightGreen", fmt::terminal_color::bright_green)
.value("BrightYellow", fmt::terminal_color::bright_yellow)
.value("BrightBlue", fmt::terminal_color::bright_blue)
.value("BrightMagenta", fmt::terminal_color::bright_magenta)
.value("BrightCyan", fmt::terminal_color::bright_cyan)
.value("BrightWhite", fmt::terminal_color::bright_white)
.def(py::init(&enum_from_str<fmt::terminal_color>));
py::implicitly_convertible<py::str, fmt::terminal_color>();
py::class_<fmt::rgb>(m, "TextRGBColor")
.def(py::init())
.def(
py::init<std::uint8_t, std::uint8_t, std::uint8_t>(),
py::arg("red") = 0,
py::arg("green") = 0,
py::arg("blue") = 0
)
.def_readwrite("red", &fmt::rgb::r)
.def_readwrite("green", &fmt::rgb::g)
.def_readwrite("blue", &fmt::rgb::b)
.def("__copy__", &copy<fmt::rgb>)
.def("__deepcopy__", &deepcopy<fmt::rgb>, py::arg("memo"));
using ColorType = std::variant<fmt::terminal_color, fmt::rgb>;
py::class_<fmt::text_style>(m, "TextStyle")
.def(py::init<>())
.def(
// We rely on ``std::optional`` and ``std::variant`` because
// ``fmt::detail::color_type`` is semi-private and would lead to introducing
// a new intermediary type.
// The bitwise ``|`` syntax of fmt is also not Pythonic.
// A more elegant option could be to implement a ``type_caster`` for ``color_type``.
py::init(
[](std::optional<ColorType> fg,
std::optional<ColorType> bg,
std::optional<fmt::emphasis> em) -> fmt::text_style
{
auto out = fmt::text_style();
if (em)
{
out = fmt::text_style(em.value());
}
if (fg)
{
std::visit([&out](const auto& color) { out |= fmt::fg(color); }, fg.value());
}
if (bg)
{
std::visit([&out](const auto& color) { out |= fmt::bg(color); }, bg.value());
}
return out;
}
),
py::arg("foreground") = std::nullopt,
py::arg("background") = std::nullopt,
py::arg("emphasis") = std::nullopt
)
.def_property_readonly(
"foreground",
[](const fmt::text_style& style) -> std::optional<ColorType>
{
if (!style.has_foreground())
{
return std::nullopt;
}
const auto fg = style.get_foreground();
if (fg.is_rgb)
{
return { { fmt::rgb(fg.value.rgb_color) } };
}
return { { static_cast<fmt::terminal_color>(fg.value.term_color) } };
}
)
.def_property_readonly(
"background",
[](const fmt::text_style& style) -> std::optional<ColorType>
{
if (!style.has_background())
{
return std::nullopt;
}
const auto bg = style.get_background();
if (bg.is_rgb)
{
return { { fmt::rgb(bg.value.rgb_color) } };
}
return { { static_cast<fmt::terminal_color>(bg.value.term_color) } };
}
)
.def_property_readonly(
"emphasis",
[](const fmt::text_style& style) -> std::optional<fmt::emphasis>
{
if (!style.has_emphasis())
{
return std::nullopt;
}
return { style.get_emphasis() };
}
)
.def("__copy__", &copy<fmt::text_style>)
.def("__deepcopy__", &deepcopy<fmt::text_style>, py::arg("memo"));
}
}

View File

@ -0,0 +1,2 @@
# This file exists on its own rather than in `__init__.py` to make `import libmambapy.utils` work.
from libmambapy.bindings.utils import * # noqa: F403

View File

@ -221,6 +221,36 @@ def test_Solution():
assert len(other.actions) == len(sol.actions)
def test_ProblemsMessageFormat():
ProblemsMessageFormat = libmambapy.solver.ProblemsMessageFormat
format = ProblemsMessageFormat()
format = ProblemsMessageFormat(
available=libmambapy.utils.TextStyle(foreground="Green"),
unavailable=libmambapy.utils.TextStyle(foreground="Red"),
indents=["a", "b", "c", "d"],
)
# Getters
assert format.available.foreground == libmambapy.utils.TextTerminalColor.Green
assert format.unavailable.foreground == libmambapy.utils.TextTerminalColor.Red
assert format.indents == ["a", "b", "c", "d"]
# Setters
format.available = libmambapy.utils.TextStyle(foreground="White")
format.unavailable = libmambapy.utils.TextStyle(foreground="Black")
format.indents = ["1", "2", "3", "4"]
assert format.available.foreground == libmambapy.utils.TextTerminalColor.White
assert format.unavailable.foreground == libmambapy.utils.TextTerminalColor.Black
assert format.indents == ["1", "2", "3", "4"]
# Copy
other = copy.deepcopy(format)
assert other is not format
assert other.indents == format.indents
def test_ProblemsGraph():
# Create a ProblemsGraph
db = libmambapy.solver.libsolv.Database(libmambapy.specs.ChannelResolveParams())
@ -278,7 +308,7 @@ def test_ProblemsGraph():
nodes, edges = cp_pbg.graph()
assert len(nodes) > 0
assert len(edges) > 0
assert "is not installable" in cp_pbg.tree_message()
assert "is not installable" in cp_pbg.tree_message(libmambapy.solver.ProblemsMessageFormat())
def test_CompressedProblemsGraph_NamedList():

View File

@ -278,7 +278,7 @@ def test_Solver_UnSolvable():
assert isinstance(outcome.problems_to_str(db), str)
assert isinstance(outcome.all_problems_to_str(db), str)
assert "The following package could not be installed" in outcome.explain_problems(
db, libmambapy.Palette.no_color()
db, libmambapy.solver.ProblemsMessageFormat()
)
assert outcome.problems_graph(db).graph() is not None

View File

@ -0,0 +1,111 @@
import copy
import pytest
import libmambapy
def test_import_submodule():
import libmambapy.utils as utils
# Dummy execution
_p = utils.TextEmphasis
def test_import_recursive():
import libmambapy as mamba
# Dummy execution
_p = mamba.utils.TextEmphasis
def test_TextEmphasis():
TextEmphasis = libmambapy.utils.TextEmphasis
assert TextEmphasis.Bold.name == "Bold"
assert TextEmphasis.Faint.name == "Faint"
assert TextEmphasis.Italic.name == "Italic"
assert TextEmphasis.Underline.name == "Underline"
assert TextEmphasis.Blink.name == "Blink"
assert TextEmphasis.Reverse.name == "Reverse"
assert TextEmphasis.Conceal.name == "Conceal"
assert TextEmphasis.Strikethrough.name == "Strikethrough"
assert TextEmphasis("Italic") == TextEmphasis.Italic
with pytest.raises(KeyError):
# No parsing, explicit name
TextEmphasis(" bold")
def test_TextTerminalColor():
TextTerminalColor = libmambapy.utils.TextTerminalColor
assert TextTerminalColor.Black.name == "Black"
assert TextTerminalColor.Red.name == "Red"
assert TextTerminalColor.Green.name == "Green"
assert TextTerminalColor.Yellow.name == "Yellow"
assert TextTerminalColor.Blue.name == "Blue"
assert TextTerminalColor.Magenta.name == "Magenta"
assert TextTerminalColor.Cyan.name == "Cyan"
assert TextTerminalColor.White.name == "White"
assert TextTerminalColor.BrightBlack.name == "BrightBlack"
assert TextTerminalColor.BrightRed.name == "BrightRed"
assert TextTerminalColor.BrightGreen.name == "BrightGreen"
assert TextTerminalColor.BrightYellow.name == "BrightYellow"
assert TextTerminalColor.BrightBlue.name == "BrightBlue"
assert TextTerminalColor.BrightMagenta.name == "BrightMagenta"
assert TextTerminalColor.BrightCyan.name == "BrightCyan"
assert TextTerminalColor.BrightWhite.name == "BrightWhite"
assert TextTerminalColor("Red") == TextTerminalColor.Red
with pytest.raises(KeyError):
# No parsing, explicit name
TextTerminalColor("red ")
def test_TextRGBColor():
TextRGBColor = libmambapy.utils.TextRGBColor
color = TextRGBColor(red=11, blue=33, green=22)
# Getters
assert color.red == 11
assert color.green == 22
assert color.blue == 33
# Setters
color.red = 1
color.green = 2
color.blue = 3
assert color.red == 1
assert color.green == 2
assert color.blue == 3
# Copy
other = copy.deepcopy(color)
assert other is not color
assert other.red == color.red
def test_TextStyle():
TextTerminalColor = libmambapy.utils.TextTerminalColor
TextRGBColor = libmambapy.utils.TextRGBColor
TextEmphasis = libmambapy.utils.TextEmphasis
TextStyle = libmambapy.utils.TextStyle
style = TextStyle()
assert style.foreground is None
assert style.background is None
assert style.emphasis is None
style = TextStyle(foreground="Red", background=TextRGBColor(red=123), emphasis="Underline")
assert style.foreground == TextTerminalColor.Red
assert style.background.red == 123
assert style.emphasis == TextEmphasis.Underline
# Copy
other = copy.deepcopy(style)
assert other is not style
assert other.emphasis == style.emphasis