Set stacklevel of DeprecationWarning as 2#538
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## develop #538 +/- ##
========================================
Coverage 84.24% 84.24%
========================================
Files 26 26
Lines 8209 8209
Branches 1697 1702 +5
========================================
Hits 6916 6916
Misses 1293 1293
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
I have no concerns to merge. @LecrisUT What do you think? Unfortunately, Some packages using spglib are slow in migration... |
|
Specifically, I was using pytest and flooded by lines like below. I think this is because pyxtal is using the old API. I wrote I believe DeprecationWarning generally should be emitted from where deprecated code is used rather than from deprecated code itself so I would like to set stacklevel to all of them, but if there's any concern I will follow your decision. |
|
@ktns can you check on how to make sure the warning is emitted only once. That would be a reasonable thing to add at the very least. It seems that the intended filter did not work as I thought: spglib/python/spglib/spglib.py Lines 81 to 83 in 8a968e4 The deprecation in info = get_symmetry_dataset(s, symprec=1e-1)
s.write("1.vasp", format="vasp", vasp5=True, direct=True)
os.system("cat 1.vasp >> " + file)
- print("{:4d} {:8.3f} {:s}".format(i, calc.energy / 8, info["international"]))
+ print("{:4d} {:8.3f} {:s}".format(i, calc.energy / 8, info.international))If you could make a PR change there it would be awesome. Either bump the minimum spglib version and use the new interface, or add |
|
I think why many warning lines are emitted, not once, is because warning messages include different keys. PR to pyxtal is right way to go of course but I can't send PRs (and wait them to be merged) to every library I come across, so I would like a way to filter warnings per caller basis. |
That should not be the case if I read the usage of spglib/python/spglib/spglib.py Lines 115 to 119 in 8a968e4
Not sure I got what you mean. But I think a good design is to add the Overall if I read the |
import warnings
warnings.filterwarnings("module", category=UserWarning)
for i in range(10):
warnings.warn(f"i={i}", UserWarning)
for i in range(10):
warnings.warn(f"i={i}", UserWarning)will emit 10 warning lines. |
I don't really understand this part. Could you elaborate why pyxtal project itself get effected by |
Even if you include We can simplify the warning message than.I thought I've also added
Quoting from the python doc:
To my reading we should be emitting the warning when it calls |
Yes, no matter filterwarnings have message argument, warnings with different messages will be treated as different warnings and get suppressed by filters independently. python3 -c '
import warnings
warnings.filterwarnings("module", category=UserWarning, message=r"i=.*")
for i in range(10):
warnings.warn(f"i={i}", UserWarning)
for i in range(10):
warnings.warn(f"i={i}", UserWarning)
'
<string>:6: UserWarning: i=0
<string>:6: UserWarning: i=1
<string>:6: UserWarning: i=2
<string>:6: UserWarning: i=3
<string>:6: UserWarning: i=4
<string>:6: UserWarning: i=5
<string>:6: UserWarning: i=6
<string>:6: UserWarning: i=7
<string>:6: UserWarning: i=8
<string>:6: UserWarning: i=9
What do you mean by FWIW, if stacklevel is larger than the size of the actual stack trace, it will be emitted like this.
I'm not sure what you mean but setting stacklevel does not delay the timing when the warning is emitted. It just refer the stack trace and emit the warning immediately as if it is emitted from the module/line of specified level of stack. |
|
I'm afraid my intension was not clear so let me clarify my goal. Suppose 1st party and 3rd party library using deprecated methods from 2nd party library like below. # second.py
def deprecated():
warnings.warn('bla', DeprecationWarning)
# third.py
def foo():
second.deprecated()
# first.py
def bar():
second.deprecated()
def baz():
third.foo()My goal is to treat warnings only from first.py as errors and not from third.py. To do that, warnings.warn in deprecated() should have stacklevel=2 and filterwarnings should be like below. Side note, if stacklevel=2 is passed, warning lines will include caller modules' names, not spglib itself. So it will be clear to viewers that the problem is in the caller, not in spglib. |
Yes, but I don't think it's the appropriate way for that. I need to think this a bit and look at some other references. There is a reason why the default is What about using Another design in your example is to use |
|
Ok I've done some experimentation and the
Using the As for my hunch that
Overall I think you are right with the approach here and setting the |
|
Please let me take time to understand your concerns...
This is because by default python suppresses warnings with identical message/category/module/line second time or later. |
@LecrisUT Sorry I didn't see this PR (only searched issues)
Lines 39 to 43 in 8a968e4 |
This is backported in |
I guess that means we would need an additional dependency for Line 18 in 8a968e4 Would that be a very high ask to install an additional dependency just to use |
Not at all and that is good practice (that is how these packages are meant to be used), we just need to update the dependencies. What I strive for is to make sure at least the LTS distros have all the required dependencies necessary, but so far I only have access to Fedora based ones through EPEL, so those are my targets on deciding if these can be added. Other environments like conda, pypi, spack, homebrew, have more freedom on getting the required version of dependencies so it's not much of a worry there |
|
would also appreciate this PR being merged. didn't read the entire thread so apologies if this is redundant but let me link https://docs.astral.sh/ruff/rules/no-explicit-stacklevel for why passing explicit |
|
@LecrisUT, is it possible to merge this PR and then make another PR to improve it? |
This is worth looking and link two more detailed explanation on the stacktrace behaviour related to
In my opinion though two stacks up should suffice for a deprecated API, see https://docs.python.org/3/library/warnings.html#warnings.warn:
|
There was a problem hiding this comment.
I can't properly review this week until I get furniture to work at, but I found 1 of them that should have stacklevel=1. Other than double checking for that, no issues with the current approach, but I will convert all of these to warnings.deprecated before release and fully support mypy. Happy if anyone wants to tackle these (in a separate PR) before I can get to them, they're super easy PRs.
python/spglib/spglib.py
Outdated
| warnings.warn( | ||
| "Use get_magnetic_symmetry() for cell with magnetic moments.", | ||
| DeprecationWarning, | ||
| stacklevel=2, |
There was a problem hiding this comment.
This one should be stacklevel=1
There was a problem hiding this comment.
Changed. But can I ask you why?
There was a problem hiding this comment.
I'm curious why using one stack here too, IMO stacklevel=1 is useful when you want to point to the implementation itself instead of the API. But this deprecation is pointing to the get_symmetry interface?
There was a problem hiding this comment.
Because the usage of get_symmetry is not deprecated, it's just one mode of operation is deprecated. When we change to warnings.deprecated we can move it to the typing part and distinguish between those.
Thinks about it more I think stacklevel=2 could also work in some sense, but I would want it to show up as any usage of get_symmetry is deprecated. Probably we can change it once the mypy part is also added.
| @@ -116,6 +116,7 @@ def __getitem__(self, key: str) -> Any: | |||
| f"dict interface ({self.__class__.__name__}['{key}']) is deprecated." | |||
| f"Use attribute interface ({self.__class__.__name__}.{key}) instead", | |||
There was a problem hiding this comment.
I have no strong intention to drop that since we will be able to filter it via module.
There was a problem hiding this comment.
But I thought you found that if the message differs the filter doesn't work.
There was a problem hiding this comment.
I said that if messages differ they will independently filtered and it cannot be warned only once. I don't want warnings to show up at all during pytest if they are called in a 3rd party module, so I don't care if there's a way to filter them based on caller module.
Hi.
I want to silence DeprecationWarning from 3rd party packages which is using deprecated interfaces of spglib and show warnings from our 1st party package only.
To do that, I want stacklevel of DeprecationWarning to be set as 2.
Could you accept this PR? Thanks.