with-err 1.0.0__tar.gz

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.

Potentially problematic release.


This version of with-err might be problematic. Click here for more details.

@@ -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,103 @@
1
+ # with-err
2
+
3
+ `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.
4
+
5
+ ## Getting Started
6
+
7
+ ### Install
8
+
9
+ ```sh
10
+ pip install with-err
11
+ ```
12
+
13
+ (with [`uv`](https://docs.astral.sh/uv/))
14
+ ```sh
15
+ uv pip install with-err
16
+ ```
17
+
18
+ ### Use as Function
19
+
20
+ ```python
21
+ import json
22
+ from with_err import with_err
23
+
24
+ json_loads_e = with_err()(json.loads)
25
+
26
+ data, err = json_loads_e('{"a": 1}')
27
+ assert err is None
28
+ assert data = {"a": 1}
29
+
30
+ data, err = json_loads_e('{"a": }')
31
+ assert err is not None
32
+ assert data is None
33
+ ```
34
+
35
+ ### Use as Decorator
36
+
37
+ ```python
38
+ import json
39
+ from with_err import with_err
40
+
41
+ @with_err()
42
+ def json_loads_e(a: str | bytes | bytearray):
43
+ return json.loads(a)
44
+
45
+ data, err = json_loads_e('{"a": 1}')
46
+ assert err is None
47
+ assert data = {"a": 1}
48
+
49
+ data, err = json_loads_e('{"a": }')
50
+ assert err is not None
51
+ assert data is None
52
+ ```
53
+
54
+ ### `with_err` with Specified Exceptions
55
+
56
+ ```python
57
+ import json
58
+ from with_err import with_err
59
+
60
+ @with_err(json.decoder.JSONDecodeError)
61
+ def json_loads_e(a: str | bytes | bytearray):
62
+ return json.loads(a)
63
+
64
+ data, err = json_loads_e('{"a": 1}')
65
+ assert err is None
66
+ assert data = {"a": 1}
67
+
68
+ data, err = json_loads_e('{"a": }')
69
+ assert err is not None
70
+ assert data is None
71
+ ```
72
+
73
+ ### Get `err` Traceback Stack
74
+
75
+ ```python
76
+ import json
77
+ from with_err import with_err, get_err_strs
78
+
79
+ def json_loads_e(a: str | bytes | bytearray):
80
+ return json.loads(a)
81
+
82
+ _data, err = json_loads_e('{"a": 1}')
83
+ err_stack = get_err_strs(err)
84
+ assert err is None
85
+ assert err_stack == []
86
+ ```
87
+
88
+ ```python
89
+ import json
90
+ import re
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": }')
97
+ err_stack = get_err_strs(err)
98
+ err_str = '\n'.join(err_stack)
99
+ assert err is not None
100
+ assert len(err_stack) > 0
101
+ assert re.search(r'json/__init__.py", line \d+, in loads', err_str)
102
+ assert re.search(r', line \d+, in json_loads_e', err_str)
103
+ ```
@@ -0,0 +1,48 @@
1
+ [project]
2
+ name = "with-err"
3
+ version = "1.0.0"
4
+ description = "converting `try-except` pattern to Go-like `result, err` pattern."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.12"
8
+ dependencies = []
9
+ keywords = []
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+
15
+ [[project.authors]]
16
+ name = "Chuan-Heng Hsiao"
17
+ email = "hsiao.chuanheng@gmail.com"
18
+
19
+ [project.urls]
20
+ Repository = "https://github.com/chhsiao1981/with-err"
21
+
22
+ [project.scripts]
23
+
24
+ [build-system]
25
+ requires = ["uv_build>=0.12.5,<0.13.0"]
26
+ build-backend = "uv_build"
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "ruff==0.16.3",
31
+ "pytest==9.1.1",
32
+ "pytest-cov==7.1.0",
33
+ "lefthook==2.1.10",
34
+ "autopep8==2.3.2",
35
+ ]
36
+
37
+ [tool.autopep8]
38
+ max_line_length = 100
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ preview = true
43
+
44
+ [tool.ruff.lint]
45
+ extend-select = [
46
+ "E",
47
+ "W",
48
+ ]
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = 'with-err'
3
+ version = '1.0.0'
4
+ description = 'converting `try-except` pattern to Go-like `result, err` pattern.'
5
+ readme = 'README.md'
6
+ license = 'MIT'
7
+ authors = [
8
+ {name = 'Chuan-Heng Hsiao', email = 'hsiao.chuanheng@gmail.com'},
9
+ ]
10
+ requires-python = '>=3.12'
11
+ dependencies = [
12
+ ]
13
+ keywords = [
14
+ ]
15
+ classifiers = [
16
+ 'Programming Language :: Python :: 3',
17
+ 'Operating System :: OS Independent',
18
+ ]
19
+
20
+ [project.urls]
21
+ # Homepage = ''
22
+ # Documentation = ''
23
+ Repository = 'https://github.com/chhsiao1981/with-err'
24
+ # 'Bug Tracker' = ''
25
+ # Changelog = ''
26
+
27
+ [project.scripts]
28
+
29
+ [build-system]
30
+ requires = [
31
+ 'uv_build>=0.12.5,<0.13.0',
32
+ ]
33
+ build-backend = 'uv_build'
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ 'ruff==0.16.3', # use ruff check --fix, not ruff format.
38
+ 'pytest==9.1.1',
39
+ 'pytest-cov==7.1.0',
40
+ 'lefthook==2.1.10',
41
+ 'autopep8==2.3.2', # autopep8 is better in whitespace formatting.
42
+ ]
43
+
44
+ [tool.autopep8]
45
+ max_line_length = 100
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ preview = true
50
+
51
+ [tool.ruff.lint]
52
+ extend-select = [
53
+ 'E',
54
+ 'W',
55
+ ]
@@ -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
+ ]
@@ -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)
@@ -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