Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fully bind MatchSpec #3213

Merged
merged 3 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion libmamba/src/specs/match_spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,10 @@ namespace mamba::specs
/* spec= */ str.substr(spec_pos + 1),
};
}
assert(spec_pos >= ns_pos + 1);
return {
/* channel= */ str.substr(0, ns_pos),
/* namespace= */ str.substr(ns_pos + 1, spec_pos),
/* namespace= */ str.substr(ns_pos + 1, spec_pos - ns_pos - 1),
/* spec= */ str.substr(spec_pos + 1),
};
}
Expand Down
8 changes: 8 additions & 0 deletions libmamba/tests/src/specs/test_match_spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ TEST_SUITE("specs::match_spec")
CHECK_EQ(ms.str(), "numpy=1.7");
}

SUBCASE("conda-forge:pypi:xtensor==0.12.3")
{
auto ms = MatchSpec::parse("conda-forge:pypi:xtensor==0.12.3").value();
CHECK_EQ(ms.name().str(), "xtensor");
CHECK_EQ(ms.channel().value().str(), "conda-forge");
CHECK_EQ(ms.name_space(), "pypi");
}

SUBCASE("conda-forge/linux-64::xtensor==0.12.3")
{
auto ms = MatchSpec::parse("numpy[version='1.7|1.8']").value();
Expand Down
51 changes: 49 additions & 2 deletions libmambapy/src/libmambapy/bindings/specs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "mamba/specs/authentication_info.hpp"
#include "mamba/specs/channel.hpp"
#include "mamba/specs/conda_url.hpp"
#include "mamba/specs/error.hpp"
#include "mamba/specs/glob_spec.hpp"
#include "mamba/specs/match_spec.hpp"
#include "mamba/specs/package_info.hpp"
#include "mamba/specs/platform.hpp"
Expand All @@ -35,6 +37,8 @@ namespace mambapy
namespace py = pybind11;
using namespace mamba::specs;

py::register_local_exception<ParseError>(m, "ParseError", PyExc_ValueError);

m.def("archive_extensions", []() { return ARCHIVE_EXTENSIONS; });

m.def(
Expand Down Expand Up @@ -323,6 +327,7 @@ namespace mambapy
py::arg("platform_filters"),
py::arg("type") = UnresolvedChannel::Type::Unknown
)
.def("__str__", &UnresolvedChannel::str)
.def("__copy__", &copy<UnresolvedChannel>)
.def("__deepcopy__", &deepcopy<UnresolvedChannel>, py::arg("memo"))
.def_property_readonly("type", &UnresolvedChannel::type)
Expand Down Expand Up @@ -694,11 +699,36 @@ namespace mambapy
.def("__copy__", &copy<PackageInfo>)
.def("__deepcopy__", &deepcopy<PackageInfo>, py::arg("memo"));

// WIP MatchSpec class
py::class_<GlobSpec>(m, "GlobSpec")
.def_readonly_static("free_pattern", &GlobSpec::free_pattern)
.def_readonly_static("glob_pattern", &GlobSpec::glob_pattern)
.def(py::init<>())
.def(py::init<std::string>(), py::arg("spec"))
.def("contains", &GlobSpec::contains)
.def("is_free", &GlobSpec::is_free)
.def("is_exact", &GlobSpec::is_exact)
.def("__str__", &GlobSpec::str)
.def("__copy__", &copy<GlobSpec>)
.def("__deepcopy__", &deepcopy<GlobSpec>, py::arg("memo"));

py::class_<MatchSpec>(m, "MatchSpec")
.def_property_readonly_static("NameSpec", &py::type::of<GlobSpec>)
.def_property_readonly_static("BuildStringSpec", &py::type::of<GlobSpec>)
.def_readonly_static("url_md5_sep", &MatchSpec::url_md5_sep)
.def_readonly_static("prefered_list_open", &MatchSpec::prefered_list_open)
.def_readonly_static("prefered_list_close", &MatchSpec::prefered_list_close)
.def_readonly_static("alt_list_open", &MatchSpec::alt_list_open)
.def_readonly_static("alt_list_close", &MatchSpec::alt_list_close)
.def_readonly_static("prefered_quote", &MatchSpec::prefered_quote)
.def_readonly_static("alt_quote", &MatchSpec::alt_quote)
.def_readonly_static("channel_namespace_spec_sep", &MatchSpec::channel_namespace_spec_sep)
.def_readonly_static("attribute_sep", &MatchSpec::attribute_sep)
.def_readonly_static("attribute_assign", &MatchSpec::attribute_assign)
.def_readonly_static("package_version_sep", &MatchSpec::package_version_sep)
.def_static("parse", &MatchSpec::parse)
.def_static("parse_url", &MatchSpec::parse_url)
.def(
// Hard deperecation since errors would be hard to track.
// V2 Migation: Hard deperecation since errors would be hard to track.
py::init(
[](std::string_view) -> MatchSpec {
throw std::invalid_argument(
Expand All @@ -708,6 +738,23 @@ namespace mambapy
),
py::arg("spec")
)
.def_property("channel", &MatchSpec::channel, &MatchSpec::set_channel)
.def_property("filename", &MatchSpec::filename, &MatchSpec::set_filename)
.def_property("subdirs", &MatchSpec::subdirs, &MatchSpec::set_subdirs)
.def_property("name_space", &MatchSpec::name_space, &MatchSpec::set_name_space)
.def_property("name", &MatchSpec::name, &MatchSpec::set_name)
.def_property("version", &MatchSpec::version, &MatchSpec::set_version)
.def_property("build_number", &MatchSpec::build_number, &MatchSpec::set_build_number)
.def_property("build_string", &MatchSpec::build_string, &MatchSpec::set_build_string)
.def_property("md5", &MatchSpec::md5, &MatchSpec::set_md5)
.def_property("sha256", &MatchSpec::sha256, &MatchSpec::set_sha256)
.def_property("license", &MatchSpec::license, &MatchSpec::set_license)
.def_property("license_family", &MatchSpec::license_family, &MatchSpec::set_license_family)
.def_property("features", &MatchSpec::features, &MatchSpec::set_features)
.def_property("track_features", &MatchSpec::track_features, &MatchSpec::set_track_features)
.def_property("optional", &MatchSpec::optional, &MatchSpec::set_optional)
.def("is_file", &MatchSpec::is_file)
.def("is_simple", &MatchSpec::is_simple)
.def("conda_build_form", &MatchSpec::conda_build_form)
.def("__str__", &MatchSpec::str)
.def("__copy__", &copy<MatchSpec>)
Expand Down
87 changes: 85 additions & 2 deletions libmambapy/tests/test_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def test_import_recursive():
_p = mamba.specs.Platform.noarch


def test_ParseError():
assert issubclass(libmambapy.specs.ParseError, ValueError)


def test_archive_extension():
assert libmambapy.specs.archive_extensions() == [".tar.bz2", ".conda", ".whl"]

Expand Down Expand Up @@ -173,6 +177,10 @@ def test_CondaURL_setters():
def test_CondaURL_parse():
CondaURL = libmambapy.specs.CondaURL

# Errors
with pytest.raises(libmambapy.specs.ParseError):
CondaURL.parse("py>#")

url = CondaURL.parse(
"https://user%40mail.com:pas%23@repo.mamba.pm:400/t/xy-12345678-1234/%20conda/linux-64/pkg.conda"
)
Expand Down Expand Up @@ -262,12 +270,20 @@ def test_UnresolvedChannel():
uc = UnresolvedChannel(location="conda-forge", platform_filters=set(), type="Name")
assert uc.type == UnresolvedChannel.Type.Name

# str
uc = UnresolvedChannel(location="conda-forge", platform_filters=set(), type="Name")
assert str(uc) == "conda-forge"

# Parser
uc = UnresolvedChannel.parse("conda-forge[linux-64]")
assert uc.location == "conda-forge"
assert uc.platform_filters == {"linux-64"}
assert uc.type == UnresolvedChannel.Type.Name

# Errors
with pytest.raises(libmambapy.specs.ParseError):
UnresolvedChannel.parse("conda-forge]")

# Copy
other = copy.deepcopy(uc)
assert other.location == uc.location
Expand Down Expand Up @@ -612,6 +628,10 @@ def test_Version():
# Parse
v = Version.parse("3!1.3ab2.4+42.0alpha")

# Errors
with pytest.raises(libmambapy.specs.ParseError):
Version.parse("#!33")

# Getters
assert v.epoch == 3
assert v.version == CommonVersion(
Expand Down Expand Up @@ -678,6 +698,10 @@ def test_VersionSpec():

vs = VersionSpec.parse(">2.0,<3.0")

# Errors
with pytest.raises(libmambapy.specs.ParseError):
VersionSpec.parse("=2,")

assert not vs.contains(Version.parse("1.1"))
assert vs.contains(Version.parse("2.1"))

Expand Down Expand Up @@ -778,13 +802,72 @@ def test_PackageInfo_V2Migrator():
pkg.url = "https://repo.mamba.pm/conda-forge/linux-64/foo-4.0-mybld.conda"


def test_GlobSpec():
GlobSpec = libmambapy.specs.GlobSpec
spec = libmambapy.specs.GlobSpec("py*")

assert GlobSpec().is_free()
assert not spec.is_free()

assert GlobSpec("python").is_exact()
assert not spec.is_exact()

assert spec.contains("python")

assert str(spec) == "py*"

# Copy
other = copy.deepcopy(spec)
assert str(other) == str(spec)
assert other is not spec


def test_MatchSpec():
MatchSpec = libmambapy.specs.MatchSpec

ms = MatchSpec.parse("conda-forge::python=3.7=*pypy")
# Errors
with pytest.raises(libmambapy.specs.ParseError):
MatchSpec.parse_url("httos:/")

ms = MatchSpec.parse_url("https://conda.com/pkg-2-bld.conda")
assert ms.is_file()
assert str(ms.name) == "pkg"
assert ms.filename == "pkg-2-bld.conda"

# Errors
with pytest.raises(libmambapy.specs.ParseError):
MatchSpec.parse("py>#")

ms = MatchSpec.parse(
"conda-forge[plat]:ns:python=3.7=*pypy"
"[md5=m,sha256=s,license=l, license_family=lf,track_features=ft,optional]"
)

assert str(ms.channel) == "conda-forge[plat]"
assert ms.subdirs == {"plat"}
assert ms.name_space == "ns"
assert str(ms.name) == "python"
assert str(ms.version) == "=3.7"
assert str(ms.build_string) == "*pypy"
assert ms.md5 == "m"
assert ms.sha256 == "s"
assert ms.license == "l"
assert ms.license_family == "lf"
assert ms.track_features == "ft"
assert ms.optional
assert not ms.is_file()
assert not ms.is_simple()

# str
assert str(ms) == "conda-forge::python=3.7[build='*pypy']"
assert str(ms) == (
"conda-forge[plat]::python=3.7"
"[build='*pypy',track_features=ft,md5=m,sha256=s,license=l,license_family=lf,optional]"
)

# Copy
other = copy.deepcopy(ms)
assert str(other) == str(ms)
assert other is not ms


def test_MatchSpec_V2Migrator():
Expand Down
Loading