diff --git a/libmamba/include/mamba/specs/version.hpp b/libmamba/include/mamba/specs/version.hpp index d717ce1a1d..358e1b3ccc 100644 --- a/libmamba/include/mamba/specs/version.hpp +++ b/libmamba/include/mamba/specs/version.hpp @@ -32,13 +32,13 @@ namespace mamba::specs VersionPartAtom(std::size_t numeral, std::string_view literal); // The use of a template is only meant to prevent ambiguous conversions template - VersionPartAtom(std::size_t numeral, std::basic_string&& literal); + VersionPartAtom(std::size_t numeral, std::basic_string literal); - auto numeral() const noexcept -> std::size_t; - auto literal() const& noexcept -> const std::string&; + [[nodiscard]] auto numeral() const noexcept -> std::size_t; + [[nodiscard]] auto literal() const& noexcept -> const std::string&; auto literal() && noexcept -> std::string; - auto str() const -> std::string; + [[nodiscard]] auto str() const -> std::string; auto operator==(const VersionPartAtom& other) const -> bool; auto operator!=(const VersionPartAtom& other) const -> bool; @@ -54,7 +54,7 @@ namespace mamba::specs std::size_t m_numeral = 0; }; - extern template VersionPartAtom::VersionPartAtom(std::size_t, std::string&&); + extern template VersionPartAtom::VersionPartAtom(std::size_t, std::string); /** * A sequence of VersionPartAtom meant to represent a part of a version (e.g. major, minor). @@ -104,7 +104,7 @@ namespace mamba::specs /** Construct version ``0.0``. */ Version() noexcept = default; - Version(std::size_t epoch, CommonVersion&& version, CommonVersion&& local = {}) noexcept; + Version(std::size_t epoch, CommonVersion version, CommonVersion local = {}) noexcept; [[nodiscard]] auto epoch() const noexcept -> std::size_t; [[nodiscard]] auto version() const noexcept -> const CommonVersion&; diff --git a/libmamba/src/specs/version.cpp b/libmamba/src/specs/version.cpp index 44e2773c37..6221fcfe9a 100644 --- a/libmamba/src/specs/version.cpp +++ b/libmamba/src/specs/version.cpp @@ -11,7 +11,6 @@ #include #include -#include "mamba/core/error_handling.hpp" #include "mamba/specs/version.hpp" #include "mamba/util/cast.hpp" #include "mamba/util/string.hpp" @@ -65,13 +64,13 @@ namespace mamba::specs } template - VersionPartAtom::VersionPartAtom(std::size_t numeral, std::basic_string&& literal) + VersionPartAtom::VersionPartAtom(std::size_t numeral, std::basic_string literal) : m_literal{ util::to_lower(std::move(literal)) } , m_numeral{ numeral } { } - template VersionPartAtom::VersionPartAtom(std::size_t, std::string&&); + template VersionPartAtom::VersionPartAtom(std::size_t, std::string); auto VersionPartAtom::numeral() const noexcept -> std::size_t { @@ -180,7 +179,7 @@ namespace mamba::specs * Implementation of Version * *******************************/ - Version::Version(std::size_t epoch, CommonVersion&& version, CommonVersion&& local) noexcept + Version::Version(std::size_t epoch, CommonVersion version, CommonVersion local) noexcept : m_version{ std::move(version) } , m_local{ std::move(local) } , m_epoch{ epoch } diff --git a/libmambapy/src/libmambapy/bindings/legacy.cpp b/libmambapy/src/libmambapy/bindings/legacy.cpp index 49b43c7abd..4802d492df 100644 --- a/libmambapy/src/libmambapy/bindings/legacy.cpp +++ b/libmambapy/src/libmambapy/bindings/legacy.cpp @@ -263,11 +263,6 @@ bind_submodule_impl(pybind11::module_ m) { using namespace mamba; - py::class_(m, "Version") - .def_static("parse", &specs::Version::parse) - .def("__str__", &specs::Version::str); - - // declare earlier to avoid C++ types in docstrings auto pyPackageInfo = py::class_(m, "PackageInfo"); auto pyPrefixData = py::class_(m, "PrefixData"); diff --git a/libmambapy/src/libmambapy/bindings/specs.cpp b/libmambapy/src/libmambapy/bindings/specs.cpp index 3ba7c2d819..7336dfe8f5 100644 --- a/libmambapy/src/libmambapy/bindings/specs.cpp +++ b/libmambapy/src/libmambapy/bindings/specs.cpp @@ -8,17 +8,23 @@ #include #include +#include "mamba/specs/archive.hpp" #include "mamba/specs/authentication_info.hpp" #include "mamba/specs/channel.hpp" #include "mamba/specs/channel_spec.hpp" #include "mamba/specs/conda_url.hpp" #include "mamba/specs/platform.hpp" +#include "mamba/specs/version.hpp" +#include "mamba/specs/version_spec.hpp" #include "bindings.hpp" #include "flat_set_caster.hpp" #include "utils.hpp" #include "weakening_map_bind.hpp" +PYBIND11_MAKE_OPAQUE(mamba::specs::VersionPart); +PYBIND11_MAKE_OPAQUE(mamba::specs::CommonVersion); + namespace mambapy { void bind_submodule_specs(pybind11::module_ m) @@ -26,6 +32,26 @@ namespace mambapy namespace py = pybind11; using namespace mamba::specs; + m.def("archive_extensions", []() { return ARCHIVE_EXTENSIONS; }); + + m.def( + "has_archive_extension", + [](std::string_view str) { return has_archive_extension(str); } + ); + m.def( + "has_archive_extension", + [](const mamba::fs::u8path& p) { return has_archive_extension(p); } + ); + + m.def( + "strip_archive_extension", + [](std::string_view str) { return strip_archive_extension(str); } + ); + m.def( + "strip_archive_extension", + [](const mamba::fs::u8path& p) { return strip_archive_extension(p); } + ); + py::enum_(m, "Platform") .value("noarch", Platform::noarch) .value("linux_32", Platform::linux_32) @@ -285,7 +311,7 @@ namespace mambapy py::arg("type") = ChannelSpec::Type::Unknown ) .def("__copy__", ©) - .def("__deepcopy__", &deepcopy, pybind11::arg("memo")) + .def("__deepcopy__", &deepcopy, py::arg("memo")) .def_property_readonly("type", &ChannelSpec::type) .def_property_readonly("location", &ChannelSpec::location) .def_property_readonly("platform_filters", &ChannelSpec::platform_filters); @@ -309,7 +335,7 @@ namespace mambapy .def(py::self == py::self) .def(py::self != py::self) .def("__copy__", ©) - .def("__deepcopy__", &deepcopy, pybind11::arg("memo")) + .def("__deepcopy__", &deepcopy, py::arg("memo")) .def("__hash__", &hash); py::class_(m, "BearerToken") @@ -321,7 +347,7 @@ namespace mambapy .def(py::self == py::self) .def(py::self != py::self) .def("__copy__", ©) - .def("__deepcopy__", &deepcopy, pybind11::arg("memo")) + .def("__deepcopy__", &deepcopy, py::arg("memo")) .def("__hash__", &hash); py::class_(m, "CondaToken") @@ -333,7 +359,7 @@ namespace mambapy .def(py::self == py::self) .def(py::self != py::self) .def("__copy__", ©) - .def("__deepcopy__", &deepcopy, pybind11::arg("memo")) + .def("__deepcopy__", &deepcopy, py::arg("memo")) .def("__hash__", &hash); bind_weakening_map(m, "AuthenticationDataBase"); @@ -384,7 +410,7 @@ namespace mambapy .def_readwrite("home_dir", &ChannelResolveParams::home_dir) .def_readwrite("current_working_dir", &ChannelResolveParams::current_working_dir) .def("__copy__", ©) - .def("__deepcopy__", &deepcopy, pybind11::arg("memo")); + .def("__deepcopy__", &deepcopy, py::arg("memo")); py_channel // .def_property_readonly_static( @@ -453,9 +479,83 @@ namespace mambapy .def("contains_equivalent", &Channel::contains_equivalent) .def(py::self == py::self) .def(py::self != py::self) - .def(py::self != py::self) .def("__hash__", &hash) .def("__copy__", ©) - .def("__deepcopy__", &deepcopy, pybind11::arg("memo")); + .def("__deepcopy__", &deepcopy, py::arg("memo")); + + py::class_(m, "VersionPartAtom") + .def(py::init<>()) + .def(py::init(), py::arg("numeral"), py::arg("literal") = "") + .def_property_readonly("numeral", &VersionPartAtom::numeral) + .def_property_readonly( + "literal", + [](const VersionPartAtom& atom) { return atom.literal(); } + ) + .def("__str__", &VersionPartAtom::str) + .def(py::self == py::self) + .def(py::self != py::self) + .def(py::self < py::self) + .def(py::self <= py::self) + .def(py::self > py::self) + .def(py::self >= py::self) + .def("__copy__", ©) + .def("__deepcopy__", &deepcopy, py::arg("memo")); + + // Type made opaque at the top of this file + py::bind_vector(m, "VersionPart"); + + // Type made opaque at the top of this file + py::bind_vector(m, "CommonVersion"); + + py::class_(m, "Version") + .def_readonly_static("epoch_delim", &Version::epoch_delim) + .def_readonly_static("local_delim", &Version::local_delim) + .def_readonly_static("part_delim", &Version::part_delim) + .def_readonly_static("part_delim_alt", &Version::part_delim_alt) + .def_readonly_static("part_delim_special", &Version::part_delim_special) + .def_static("parse", &Version::parse, py::arg("str")) + .def( + py::init(), + py::arg("epoch") = 0, + py::arg("version") = CommonVersion(), + py::arg("local") = CommonVersion() + ) + .def_property_readonly("epoch", &Version::epoch) + .def_property_readonly("version", &Version::version) + .def_property_readonly("local", &Version::local) + .def("starts_with", &Version::starts_with, py::arg("prefix")) + .def("compatible_with", &Version::compatible_with, py::arg("older"), py::arg("level")) + .def("__str__", &Version::str) + .def(py::self == py::self) + .def(py::self != py::self) + .def(py::self < py::self) + .def(py::self <= py::self) + .def(py::self > py::self) + .def(py::self >= py::self) + .def("__copy__", ©) + .def("__deepcopy__", &deepcopy, py::arg("memo")); + + // Bindings for VersionSpec currently ignores VersionPredicate and flat_bool_expr_tree + // which would be tedious to bind, and even more to make extendable through Python + + py::class_(m, "VersionSpec") + .def_readonly_static("and_token", &VersionSpec::and_token) + .def_readonly_static("or_token", &VersionSpec::or_token) + .def_readonly_static("left_parenthesis_token", &VersionSpec::left_parenthesis_token) + .def_readonly_static("right_parenthesis_token", &VersionSpec::right_parenthesis_token) + .def_readonly_static("starts_with_str", &VersionSpec::starts_with_str) + .def_readonly_static("equal_str", &VersionSpec::equal_str) + .def_readonly_static("not_equal_str", &VersionSpec::not_equal_str) + .def_readonly_static("greater_str", &VersionSpec::greater_str) + .def_readonly_static("greater_equal_str", &VersionSpec::greater_equal_str) + .def_readonly_static("less_str", &VersionSpec::less_str) + .def_readonly_static("less_equal_str", &VersionSpec::less_equal_str) + .def_readonly_static("compatible_str", &VersionSpec::compatible_str) + .def_readonly_static("glob_suffix_str", &VersionSpec::glob_suffix_str) + .def_readonly_static("glob_suffix_token", &VersionSpec::glob_suffix_token) + .def_static("parse", &VersionSpec::parse, py::arg("str")) + .def("contains", &VersionSpec::contains, py::arg("point")) + .def("__copy__", ©) + .def("__deepcopy__", &deepcopy, py::arg("memo")); } } diff --git a/libmambapy/tests/test_specs.py b/libmambapy/tests/test_specs.py index d998f0b243..6af55330ef 100644 --- a/libmambapy/tests/test_specs.py +++ b/libmambapy/tests/test_specs.py @@ -19,6 +19,16 @@ def test_import_recursive(): _p = mamba.specs.Platform.noarch +def test_archive_extension(): + assert libmambapy.specs.archive_extensions() == [".tar.bz2", ".conda"] + + assert libmambapy.specs.has_archive_extension("pkg.conda") + assert not libmambapy.specs.has_archive_extension("conda.pkg") + + assert libmambapy.specs.strip_archive_extension("pkg.conda") == "pkg" + assert libmambapy.specs.strip_archive_extension("conda.pkg") == "conda.pkg" + + def test_Platform(): Platform = libmambapy.specs.Platform @@ -515,3 +525,130 @@ def test_Channel_resolve(): ) assert len(chans) == 2 assert {c.display_name for c in chans} == {"best-forge", "conda-forge"} + + +def test_VersionPartAtom(): + VersionPartAtom = libmambapy.specs.VersionPartAtom + + a = VersionPartAtom(numeral=1, literal="alpha") + + # Getters + assert a.numeral == 1 + assert a.literal == "alpha" + assert str(a) == "1alpha" + + # Comparison + b = VersionPartAtom(2) + assert a == a + assert a != b + assert a <= a + assert a <= b + assert a < b + assert a >= a + assert b >= a + assert b > a + + # Copy + assert copy.deepcopy(a) == a + + +def test_VersionPart(): + VersionPartAtom = libmambapy.specs.VersionPartAtom + VersionPart = libmambapy.specs.VersionPart + + p = VersionPart([VersionPartAtom(1, "a"), VersionPartAtom(3)]) + assert len(p) == 2 + + +def test_CommonVersion(): + VersionPartAtom = libmambapy.specs.VersionPartAtom + VersionPart = libmambapy.specs.VersionPart + CommonVersion = libmambapy.specs.CommonVersion + + p = VersionPart([VersionPartAtom(1, "a"), VersionPartAtom(3)]) + v = CommonVersion([p, p]) + assert len(v) == 2 + + +def test_Version(): + VersionPartAtom = libmambapy.specs.VersionPartAtom + VersionPart = libmambapy.specs.VersionPart + CommonVersion = libmambapy.specs.CommonVersion + Version = libmambapy.specs.Version + + # Static data + assert isinstance(Version.epoch_delim, str) + assert isinstance(Version.local_delim, str) + assert isinstance(Version.part_delim, str) + assert isinstance(Version.part_delim_alt, str) + assert isinstance(Version.part_delim_special, str) + + # Parse + v = Version.parse("3!1.3ab2.4+42.0alpha") + + # Getters + assert v.epoch == 3 + assert v.version == CommonVersion( + [ + VersionPart([VersionPartAtom(1)]), + VersionPart([VersionPartAtom(3, "ab"), VersionPartAtom(2)]), + VersionPart([VersionPartAtom(4)]), + ] + ) + assert v.local == CommonVersion( + [ + VersionPart([VersionPartAtom(42)]), + VersionPart([VersionPartAtom(0, "alpha")]), + ] + ) + + # str + assert str(v) == "3!1.3ab2.4+42.0alpha" + + # Copy + assert copy.deepcopy(v) == v + + # Comparison + v1 = Version.parse("1.0.1") + v2 = Version.parse("1.2.3alpha2") + assert v1 == v1 + assert v1 != v2 + assert v1 <= v1 + assert v1 <= v2 + assert v2 >= v1 + assert v2 >= v2 + assert v2 > v1 + assert v1.starts_with(Version.parse("1.0")) + assert not v1.starts_with(v2) + assert v2.compatible_with(older=v1, level=1) + assert not v2.compatible_with(older=v1, level=2) + assert not v1.compatible_with(older=v2, level=1) + + +def test_VersionSpec(): + Version = libmambapy.specs.Version + VersionSpec = libmambapy.specs.VersionSpec + + # Static data + assert isinstance(VersionSpec.and_token, str) + assert isinstance(VersionSpec.or_token, str) + assert isinstance(VersionSpec.left_parenthesis_token, str) + assert isinstance(VersionSpec.right_parenthesis_token, str) + assert isinstance(VersionSpec.starts_with_str, str) + assert isinstance(VersionSpec.equal_str, str) + assert isinstance(VersionSpec.not_equal_str, str) + assert isinstance(VersionSpec.greater_str, str) + assert isinstance(VersionSpec.greater_equal_str, str) + assert isinstance(VersionSpec.less_str, str) + assert isinstance(VersionSpec.less_equal_str, str) + assert isinstance(VersionSpec.compatible_str, str) + assert isinstance(VersionSpec.glob_suffix_str, str) + assert isinstance(VersionSpec.glob_suffix_token, str) + + vs = VersionSpec.parse(">2.0,<3.0") + + assert not vs.contains(Version.parse("1.1")) + assert vs.contains(Version.parse("2.1")) + + # Copy + copy.deepcopy(vs) # No easy comaprison diff --git a/libmambapy/tests/test_version.py b/libmambapy/tests/test_version.py index 7924f6f1ba..918f4c7a33 100644 --- a/libmambapy/tests/test_version.py +++ b/libmambapy/tests/test_version.py @@ -2,6 +2,5 @@ def test_version(): - ver_str = "1.0" - ver = libmambapy.Version.parse(ver_str) - assert str(ver) == ver_str + assert isinstance(libmambapy.__version__, str) + assert libmambapy.version.__version__ == libmambapy.__version__