2023-08-08 11:10:03 +02:00
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.
2023-05-03 11:06:20 +02:00
== Why is this an issue?
2021-04-28 16:49:39 +02:00
:link-with-uscores1: https://docs.python.org/3/reference/datamodel.html?highlight=__exit__%20special#object.__exit__
2023-08-08 11:10:03 +02:00
Methods ``++__enter__++`` and ``++__exit__++`` make it possible to implement objects which can be used as the expression of a ``++with++`` statement:
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
[source,python]
----
with MyContextManager() as c :
... # do something with c
----
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
This statement can be rewritten as a ``++try...finally++`` and an explicit call to the ``++__enter__++`` and ``++__exit__++`` methods:
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
[source,python]
----
c = MyContextManager()
c.__enter__()
try:
... # do something with c
finally:
c.__exit__()
----
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
The ``++__exit__++`` is the method of a statement context manager which is called when exiting the runtime context related to this object.
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
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.
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
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.
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
== How to fix it
2021-04-28 18:08:03 +02:00
2023-08-08 11:10:03 +02:00
To fix this issue, make sure to avoid raising the exception provided to an ``++__exit__++`` method.
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
=== Code examples
==== Noncompliant code example
[source,python,diff-id=1,diff-type=noncompliant]
2021-04-28 16:49:39 +02:00
----
class MyContextManager:
def __enter__(self):
return self
2023-08-08 11:10:03 +02:00
2021-04-28 16:49:39 +02:00
def __exit__(self, *args):
2023-08-08 11:10:03 +02:00
raise # Noncompliant: __exit__ method has a bare raise outside of an except block.
2021-04-28 16:49:39 +02:00
class MyContextManager:
def __enter__(self):
return self
2023-08-08 11:10:03 +02:00
def __exit__(self, *args):
raise args[2] # Noncompliant: __exit__() methods should not reraise the provided exception; this is the caller’ s responsibility.
class MyContextManager:
def __enter__(self):
return self
2021-04-28 16:49:39 +02:00
def __exit__(self, exc_type, exc_value, traceback):
2023-08-08 11:10:03 +02:00
raise exc_value # Noncompliant: __exit__() methods should not reraise the provided exception; this is the caller’ s responsibility.
2021-04-28 16:49:39 +02:00
----
2021-04-28 18:08:03 +02:00
2023-08-08 11:10:03 +02:00
==== Compliant solution
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
[source,python,diff-id=1,diff-type=compliant]
2021-04-28 16:49:39 +02:00
----
2023-08-08 11:10:03 +02:00
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
2021-04-28 16:49:39 +02:00
class MyContextManager:
def __enter__(self):
return self
2023-08-08 11:10:03 +02:00
2021-04-28 16:49:39 +02:00
def __exit__(self, exc_type, exc_value, traceback):
2023-08-08 11:10:03 +02:00
pass # by default the function will return None, which is always False, and the exc_value will naturally raise.
2021-04-28 16:49:39 +02:00
class MyContextManager:
def __enter__(self, stop_exceptions):
return self
2023-08-08 11:10:03 +02:00
2021-04-28 16:49:39 +02:00
def __exit__(self, *args):
raise MemoryError("No more memory") # This is ok too.
----
2021-04-28 18:08:03 +02:00
2021-04-28 16:49:39 +02:00
:link-with-uscores1: https://docs.python.org/3/reference/datamodel.html?highlight=__exit__%20special#object.__exit__
2023-05-03 11:06:20 +02:00
== Resources
2021-04-28 16:49:39 +02:00
2023-08-08 11:10:03 +02:00
=== Documentation
2021-04-28 16:49:39 +02:00
* Python documentation – {link-with-uscores1}[The ``++__exit__++`` special method]
* PEP 343 – https://www.python.org/dev/peps/pep-0343/[The "with" Statement]
2021-04-28 18:08:03 +02:00
2021-06-02 20:44:38 +02:00
2021-06-03 09:05:38 +02:00
ifdef::env-github,rspecator-view[]
2021-09-20 15:38:42 +02:00
'''
== Implementation Specification
(visible only on this page)
2023-05-25 14:18:12 +02:00
=== Message
remove this "raise" statement and return "False" instead.
=== Highlighting
The "raise" statement.
2021-09-20 15:38:42 +02:00
2021-06-08 15:52:13 +02:00
'''
2021-06-02 20:44:38 +02:00
== Comments And Links
(visible only on this page)
2023-05-25 14:18:12 +02:00
=== is related to: S5747
2021-06-03 09:05:38 +02:00
endif::env-github,rspecator-view[]