rspec/rules/S5706/python/rule.adoc
2023-08-08 11:10:03 +02:00

132 lines
3.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This rule raises an issue when:
* an ``++__exit__++`` method has a bare ``++raise++`` outside of an ``++except++`` block.
* an ``++__exit__++`` method raises the exception provided as parameter.
== Why is this an issue?
:link-with-uscores1: https://docs.python.org/3/reference/datamodel.html?highlight=__exit__%20special#object.__exit__
Methods ``++__enter__++`` and ``++__exit__++`` make it possible to implement objects which can be used as the expression of a ``++with++`` statement:
[source,python]
----
with MyContextManager() as c :
... # do something with c
----
This statement can be rewritten as a ``++try...finally++`` and an explicit call to the ``++__enter__++`` and ``++__exit__++`` methods:
[source,python]
----
c = MyContextManager()
c.__enter__()
try:
... # do something with c
finally:
c.__exit__()
----
The ``++__exit__++`` is the method of a statement context manager which is called when exiting the runtime context related to this object.
If an exception is supplied as an argument, its propagation can be suppressed by having the method return a truthy value. Otherwise, the exception will be processed normally upon exit from the method.
The special method {link-with-uscores1}[``++__exit__++``] should only raise an exception when it fails. It should never raise the provided exception, it is the caller's responsibility. The ``++__exit__++`` method can filter provided exceptions by simply returning True or False. Raising this exception will make the stack trace difficult to understand.
== How to fix it
To fix this issue, make sure to avoid raising the exception provided to an ``++__exit__++`` method.
=== Code examples
==== Noncompliant code example
[source,python,diff-id=1,diff-type=noncompliant]
----
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
raise # Noncompliant: __exit__ method has a bare raise outside of an except block.
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
raise args[2] # Noncompliant: __exit__() methods should not reraise the provided exception; this is the callers responsibility.
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
raise exc_value # Noncompliant: __exit__() methods should not reraise the provided exception; this is the callers responsibility.
----
==== Compliant solution
[source,python,diff-id=1,diff-type=compliant]
----
class MyContextManager:
def __enter__(self, stop_exceptions):
return self
def __exit__(self, *args):
try:
...
except:
raise # No issue when raising another exception. The __exit__ method can fail and raise an exception
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass # by default the function will return None, which is always False, and the exc_value will naturally raise.
class MyContextManager:
def __enter__(self, stop_exceptions):
return self
def __exit__(self, *args):
raise MemoryError("No more memory") # This is ok too.
----
:link-with-uscores1: https://docs.python.org/3/reference/datamodel.html?highlight=__exit__%20special#object.__exit__
== Resources
=== Documentation
* Python documentation {link-with-uscores1}[The ``++__exit__++`` special method]
* PEP 343 https://www.python.org/dev/peps/pep-0343/[The "with" Statement]
ifdef::env-github,rspecator-view[]
'''
== Implementation Specification
(visible only on this page)
=== Message
remove this "raise" statement and return "False" instead.
=== Highlighting
The "raise" statement.
'''
== Comments And Links
(visible only on this page)
=== is related to: S5747
endif::env-github,rspecator-view[]