with-err 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
with_err/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .utils import get_err_strs as get_err_strs
2
+ from .with_err import with_err as with_err
3
+
4
+ all = [
5
+ 'with_err',
6
+ 'get_err_strs'
7
+ ]
with_err/utils.py ADDED
@@ -0,0 +1,11 @@
1
+ # https://share.gemini.google/BDvazjX2RWsE
2
+
3
+ import traceback
4
+
5
+
6
+ def get_err_strs(err: Exception | None) -> list[str]:
7
+ """Accepts an exception instance and returns the full traceback as list[str]."""
8
+ if err is None:
9
+ return []
10
+
11
+ return traceback.format_exception(err)
with_err/with_err.py ADDED
@@ -0,0 +1,65 @@
1
+ # https://share.gemini.google/BDvazjX2RWsE
2
+
3
+ import sys
4
+ import types
5
+ from collections.abc import Callable
6
+ from functools import wraps
7
+ from typing import Protocol
8
+
9
+
10
+ # @type_check_only
11
+ class RetWithErr[**P, R](Protocol):
12
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> tuple[R | None, Exception | None]:
13
+ ...
14
+
15
+
16
+ def with_err[**P, R](*exceptions: type[Exception]) -> Callable[[Callable[P, R]], RetWithErr[P, R]]:
17
+ """
18
+ Wraps a function to return (result, Exception) instead of raising.
19
+
20
+ XXX Reason repeating exceptions and Exception:
21
+ We don't want to create another layer of function tracestack.
22
+ """
23
+
24
+ def decorator(func: Callable[P, R]) -> Callable[P, tuple[R | None, Exception | None]]:
25
+ @wraps(func)
26
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> tuple[R | None, Exception | None]:
27
+ try:
28
+ result = func(*args, **kwargs)
29
+ return result, None
30
+ except exceptions as e:
31
+ # 1. Fetch exception's original internal traceback.
32
+ tb = sys.exc_info()[2]
33
+
34
+ # 2. Capture the caller frame executing func.
35
+ caller_frame = sys._getframe(1)
36
+
37
+ # 3. Create a parent traceback frame and link it above 'tb'.
38
+ combined_tb = types.TracebackType(
39
+ tb_next=tb,
40
+ tb_frame=caller_frame,
41
+ tb_lasti=caller_frame.f_lasti,
42
+ tb_lineno=caller_frame.f_lineno
43
+ )
44
+
45
+ # 4. Attach the combined traceback back to the error instance
46
+ return None, e.with_traceback(combined_tb)
47
+ except Exception as e: # ruff: ignore[blind-except]
48
+ # 1. Fetch exception's original internal traceback.
49
+ tb = sys.exc_info()[2]
50
+
51
+ # 2. Capture the caller frame executing func.
52
+ caller_frame = sys._getframe(1)
53
+
54
+ # 3. Create a parent traceback frame and link it above 'tb'.
55
+ combined_tb = types.TracebackType(
56
+ tb_next=tb,
57
+ tb_frame=caller_frame,
58
+ tb_lasti=caller_frame.f_lasti,
59
+ tb_lineno=caller_frame.f_lineno
60
+ )
61
+
62
+ # 4. Attach the combined traceback back to the error instance
63
+ return None, e.with_traceback(combined_tb)
64
+ return wrapper
65
+ return decorator
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: with-err
3
+ Version: 1.0.0
4
+ Summary: converting `try-except` pattern to Go-like `result, err` pattern.
5
+ Keywords:
6
+ Author: Chuan-Heng Hsiao
7
+ Author-email: Chuan-Heng Hsiao <hsiao.chuanheng@gmail.com>
8
+ License-Expression: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.12
12
+ Project-URL: Repository, https://github.com/chhsiao1981/with-err
13
+ Description-Content-Type: text/markdown
14
+
15
+ # with-err
16
+
17
+ `with-err` is a python library that converts `try-except` pattern into Go-like `result, err` pattern. I feel `result, err` pattern easier to maintain in large projects.
18
+
19
+ ## Getting Started
20
+
21
+ ### Install
22
+
23
+ ```sh
24
+ pip install with-err
25
+ ```
26
+
27
+ (with [`uv`](https://docs.astral.sh/uv/))
28
+ ```sh
29
+ uv pip install with-err
30
+ ```
31
+
32
+ ### Use as Function
33
+
34
+ ```python
35
+ import json
36
+ from with_err import with_err
37
+
38
+ json_loads_e = with_err()(json.loads)
39
+
40
+ data, err = json_loads_e('{"a": 1}')
41
+ assert err is None
42
+ assert data = {"a": 1}
43
+
44
+ data, err = json_loads_e('{"a": }')
45
+ assert err is not None
46
+ assert data is None
47
+ ```
48
+
49
+ ### Use as Decorator
50
+
51
+ ```python
52
+ import json
53
+ from with_err import with_err
54
+
55
+ @with_err()
56
+ def json_loads_e(a: str | bytes | bytearray):
57
+ return json.loads(a)
58
+
59
+ data, err = json_loads_e('{"a": 1}')
60
+ assert err is None
61
+ assert data = {"a": 1}
62
+
63
+ data, err = json_loads_e('{"a": }')
64
+ assert err is not None
65
+ assert data is None
66
+ ```
67
+
68
+ ### `with_err` with Specified Exceptions
69
+
70
+ ```python
71
+ import json
72
+ from with_err import with_err
73
+
74
+ @with_err(json.decoder.JSONDecodeError)
75
+ def json_loads_e(a: str | bytes | bytearray):
76
+ return json.loads(a)
77
+
78
+ data, err = json_loads_e('{"a": 1}')
79
+ assert err is None
80
+ assert data = {"a": 1}
81
+
82
+ data, err = json_loads_e('{"a": }')
83
+ assert err is not None
84
+ assert data is None
85
+ ```
86
+
87
+ ### Get `err` Traceback Stack
88
+
89
+ ```python
90
+ import json
91
+ from with_err import with_err, get_err_strs
92
+
93
+ def json_loads_e(a: str | bytes | bytearray):
94
+ return json.loads(a)
95
+
96
+ _data, err = json_loads_e('{"a": 1}')
97
+ err_stack = get_err_strs(err)
98
+ assert err is None
99
+ assert err_stack == []
100
+ ```
101
+
102
+ ```python
103
+ import json
104
+ import re
105
+ from with_err import with_err, get_err_strs
106
+
107
+ def json_loads_e(a: str | bytes | bytearray):
108
+ return json.loads(a)
109
+
110
+ _data, err = json_loads_e('{"a": }')
111
+ err_stack = get_err_strs(err)
112
+ err_str = '\n'.join(err_stack)
113
+ assert err is not None
114
+ assert len(err_stack) > 0
115
+ assert re.search(r'json/__init__.py", line \d+, in loads', err_str)
116
+ assert re.search(r', line \d+, in json_loads_e', err_str)
117
+ ```
@@ -0,0 +1,7 @@
1
+ with_err/__init__.py,sha256=430ZmzuYzLEyGMGFFI-U21DuPG76yvo-8IwKQAPGY_E,137
2
+ with_err/utils.py,sha256=39OxLBB-rUOYgCrm9nmzyhsOV4AVhTusATC-dXM3NXE,284
3
+ with_err/with_err.py,sha256=xyLbgmhUewopFQVUFX5H_5V3jr1kSXmJyiczikJPcdY,2450
4
+ with_err-1.0.0.dist-info/WHEEL,sha256=4OL6Foqnnp3xRY5wMkjgc25_i5YJC6dKsC6LPcjqEoU,80
5
+ with_err-1.0.0.dist-info/entry_points.txt,sha256=i2oN8Ae25gU-XoxtDndkB7we3AiSvh8-LPe0e6_tgrM,19
6
+ with_err-1.0.0.dist-info/METADATA,sha256=fljLZz_FK4rU2G3NIe-DdkKiyGujOqkdpnrhnGppDmE,2477
7
+ with_err-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.5
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+