In DRY Python code, when should I catch/raise exceptions?
Your Option 1 and Option 2 are almmost exactly the same (except that Option 2 is more confusing)

This is because if you don't catch an exception, it will just rise to the top, where it will halt your program. Catching and immediately re-raising it is (almost) the same as never catching it at all.

The only difference can be seen here:

In [1]: def f(a, b):
   ...:     return a / b
   ...: 

In [2]: def g(a, b):
   ...:     try:
   ...:         return f(a, b)
   ...:     except ZeroDivisionError as e:
   ...:         raise ZeroDivisionError(e)


When calling g(2, 0), the traceback shows us the place where the error was last raised, which is in the function g:

In [3]: g(2, 0)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
&lt;ipython-input-3-9920cf4d7d78&gt; in &lt;module&gt;()
----&gt; 1 g(2, 0)

&lt;ipython-input-2-a48ddeb59e34&gt; in g(a, b)
      3         return f(a, b)
      4     except ZeroDivisionError as e:
----&gt; 5         raise ZeroDivisionError(e)
      6 

ZeroDivisionError: integer division or modulo by zero


When defining a third function h, that does not re-raise the exception, this does not happen and we see the full context of calling f from within h:

In [4]: def h(a, b):
   ...:     return f(a, b)
   ...: 

In [5]: h(2, 0)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
&lt;ipython-input-5-b455367d475f&gt; in &lt;module&gt;()
----&gt; 1 h(2, 0)

&lt;ipython-input-4-7bfa64a165c7&gt; in h(a, b)
      1 def h(a, b):
----&gt; 2     return f(a, b)

&lt;ipython-input-1-44ecddb27ac3&gt; in f(a, b)
      1 def f(a, b):
----&gt; 2     return a / b

ZeroDivisionError: integer division or modulo by zero


Finally note that if you use just a raise (without the explicit exception type and passing it the exception), it will also behave like the function h:

In [6]: def k(a, b):
   ...:     try:
   ...:         return f(a,b)
   ...:     except ZeroDivisionError:
   ...:         raise
   ...:     

In [7]: k(2, 0)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
&lt;ipython-input-7-545f9b93e361&gt; in &lt;module&gt;()
----&gt; 1 k(2, 0)

&lt;ipython-input-6-5891fde4d79f&gt; in k(a, b)
      1 def k(a, b):
      2     try:
----&gt; 3         return f(a,b)
      4     except ZeroDivisionError:
      5         raise

&lt;ipython-input-1-44ecddb27ac3&gt; in f(a, b)
      1 def f(a, b):
----&gt; 2     return a / b

ZeroDivisionError: integer division or modulo by zero


So re-raising the exception in this way just obfuscates where the exception originally occurred. This can be a good or a bad thing, but I would not recommend using it unless that is your intention.