Skip to content

Commit 7910138

Browse files
committed
Deprecate 'slient' param in get_blocks_()
- Restore 'slient' parameter support to fix API breaking change from previous commit - Add FutureWarning for deprecated 'slient' parameter using py::warnings - Implement robust argument validation with detailed error messages - Bump pybind11 requirement to >=3.0 (py::warnings available since pybind11 3.0) - Add comprehensive test suite covering deprecation warnings, argument validation, and edge cases
1 parent 6f67dd4 commit 7910138

File tree

3 files changed

+136
-7
lines changed

3 files changed

+136
-7
lines changed

pybind/unitensor_py.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
#include <format>
12
#include <vector>
23
#include <map>
34
#include <random>
5+
#include <string>
46

57
#include <pybind11/pybind11.h>
68
#include <pybind11/stl.h>
@@ -9,10 +11,9 @@
911
#include <pybind11/numpy.h>
1012
#include <pybind11/buffer_info.h>
1113
#include <pybind11/functional.h>
14+
#include <pybind11/warnings.h>
1215

1316
#include "cytnx.hpp"
14-
// #include "../include/cytnx_error.hpp"
15-
#include "complex.h"
1617

1718
namespace py = pybind11;
1819
using namespace pybind11::literals;
@@ -66,6 +67,33 @@ void f_UniTensor_setelem_scal_int(UniTensor &self, const cytnx_uint64 &locator,
6667
self.set_elem(tmp, rc);
6768
}
6869

70+
// Parse UniTensor.get_blocks_ function's silent argument.
71+
//
72+
// This function should be replaced with `py::arg("silent") = false` after stopping
73+
// support for the deprecated typo argument "slient".
74+
inline bool parse_get_blocks_silent_arg(const py::args &args, const py::kwargs &kwargs) {
75+
bool silent = false;
76+
if (args.size() + kwargs.size() > 1) {
77+
throw py::type_error("get_blocks_() takes at most 1 argument");
78+
}
79+
if (args.size() == 1) {
80+
silent = py::cast<bool>(args[0]);
81+
} else if (kwargs.contains("slient")) {
82+
py::warnings::warn(
83+
"Keyword 'slient' is deprecated and will be removed in v2.0.0; use 'silent' instead.",
84+
PyExc_FutureWarning, 2);
85+
silent = kwargs["slient"].cast<bool>();
86+
} else if (kwargs.contains("silent")) {
87+
silent = kwargs["silent"].cast<bool>();
88+
} else if (kwargs.size() == 1) {
89+
// The case that kwargs.size() > 1 has been caught above.
90+
std::string kwarg_name = py::str(kwargs.begin()->first);
91+
throw py::type_error(
92+
std::format("'{}' is an invalid keyword argument for get_blocks_()", kwarg_name));
93+
}
94+
return silent;
95+
}
96+
6997
void unitensor_binding(py::module &m) {
7098
py::class_<cHclass>(m, "Helpclass")
7199
.def("exists", &cHclass::exists)
@@ -700,11 +728,18 @@ void unitensor_binding(py::module &m) {
700728
.def("get_blocks", [](const UniTensor &self) { return self.get_blocks(); })
701729
.def(
702730
"get_blocks_",
703-
[](const UniTensor &self, const bool &silent) { return self.get_blocks_(silent); },
704-
py::arg("silent") = false)
731+
[](const UniTensor& self, py::args args, py::kwargs kwargs) {
732+
return self.get_blocks_(parse_get_blocks_silent_arg(args, kwargs));
733+
}
734+
// ,py::arg("silent") = false // Uncmment this line after removing the deprecated argument.
735+
)
705736
.def(
706-
"get_blocks_", [](UniTensor &self, const bool &silent) { return self.get_blocks_(silent); },
707-
py::arg("silent") = false)
737+
"get_blocks_",
738+
[](UniTensor &self, py::args args, py::kwargs kwargs) {
739+
return self.get_blocks_(parse_get_blocks_silent_arg(args, kwargs));
740+
}
741+
// ,py::arg("silent") = false // Uncmment this line after removing the deprecated argument.
742+
)
708743
.def(
709744
"put_block",
710745
[](UniTensor &self, const cytnx::Tensor &in, const cytnx_uint64 &idx) {

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build-system]
2-
requires = ["scikit-build-core >=0.11", "pybind11 >=2.6"]
2+
requires = ["scikit-build-core >=0.11", "pybind11 >=3.0"]
33
build-backend = "scikit_build_core.build"
44

55
[project]

pytests/unitensor_test.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import warnings
2+
3+
import pytest
4+
5+
import cytnx
6+
7+
8+
def test_get_blocks_deprecated_slient_warning():
9+
"""Test that using 'slient' parameter triggers FutureWarning"""
10+
# Create a BlockTensor for testing
11+
bond = cytnx.Bond(cytnx.BD_IN,
12+
[cytnx.Qs(1) >> 1, cytnx.Qs(-1) >> 1],
13+
[cytnx.Symmetry.U1()])
14+
unitensor = cytnx.UniTensor([bond])
15+
16+
# Test that using deprecated 'slient' parameter raises FutureWarning
17+
with pytest.warns(
18+
FutureWarning,
19+
match=
20+
"Keyword 'slient' is deprecated and will be removed in v2.0.0; use 'silent' instead."
21+
):
22+
unitensor.get_blocks_(slient=True)
23+
24+
# Test that the method still works with deprecated parameter
25+
with warnings.catch_warnings():
26+
warnings.simplefilter("ignore", FutureWarning)
27+
result_deprecated = unitensor.get_blocks_(slient=True)
28+
result_new = unitensor.get_blocks_(silent=True)
29+
# Both should return the same result
30+
assert len(result_deprecated) == len(result_new)
31+
32+
33+
def test_get_blocks_new_silent_parameter():
34+
"""Test that new 'silent' parameter works without warnings"""
35+
bond = cytnx.Bond(cytnx.BD_IN,
36+
[cytnx.Qs(1) >> 1, cytnx.Qs(-1) >> 1],
37+
[cytnx.Symmetry.U1()])
38+
unitensor = cytnx.UniTensor([bond])
39+
40+
# Test that new 'silent' parameter doesn't trigger warnings
41+
with warnings.catch_warnings():
42+
warnings.simplefilter("error") # Turn warnings into exceptions
43+
result_silent_true = unitensor.get_blocks_(silent=True)
44+
result_silent_false = unitensor.get_blocks_(silent=False)
45+
result_default = unitensor.get_blocks_()
46+
47+
# All should work without warnings
48+
assert isinstance(result_silent_true, list)
49+
assert isinstance(result_silent_false, list)
50+
assert isinstance(result_default, list)
51+
52+
53+
def test_get_blocks_positional_argument():
54+
"""Test that positional argument still works"""
55+
bond = cytnx.Bond(cytnx.BD_IN,
56+
[cytnx.Qs(1) >> 1, cytnx.Qs(-1) >> 1],
57+
[cytnx.Symmetry.U1()])
58+
unitensor = cytnx.UniTensor([bond])
59+
60+
# Test positional argument
61+
result_pos_true = unitensor.get_blocks_(True)
62+
result_pos_false = unitensor.get_blocks_(False)
63+
64+
assert isinstance(result_pos_true, list)
65+
assert isinstance(result_pos_false, list)
66+
67+
68+
def test_get_blocks_argument_validation():
69+
"""Test argument validation for get_blocks_"""
70+
bond = cytnx.Bond(cytnx.BD_IN,
71+
[cytnx.Qs(1) >> 1, cytnx.Qs(-1) >> 1],
72+
[cytnx.Symmetry.U1()])
73+
unitensor = cytnx.UniTensor([bond])
74+
75+
# Test too many arguments
76+
with pytest.raises(
77+
TypeError, match="get_blocks_\\(\\) takes at most 1 argument"):
78+
unitensor.get_blocks_(True, False)
79+
80+
with pytest.raises(
81+
TypeError, match="get_blocks_\\(\\) takes at most 1 argument"):
82+
unitensor.get_blocks_(silent=True, slient=False)
83+
84+
with pytest.raises(
85+
TypeError, match="get_blocks_\\(\\) takes at most 1 argument"):
86+
unitensor.get_blocks_(True, silent=False)
87+
88+
# Test invalid keyword argument
89+
with pytest.raises(
90+
TypeError,
91+
match=
92+
"'invalid_arg' is an invalid keyword argument for get_blocks_\\(\\)"
93+
):
94+
unitensor.get_blocks_(invalid_arg=True)

0 commit comments

Comments
 (0)