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.
- with_err-1.0.0/PKG-INFO +117 -0
- with_err-1.0.0/README.md +103 -0
- with_err-1.0.0/pyproject.toml +48 -0
- with_err-1.0.0/pyproject.toml.orig +55 -0
- with_err-1.0.0/src/with_err/__init__.py +7 -0
- with_err-1.0.0/src/with_err/utils.py +11 -0
- with_err-1.0.0/src/with_err/with_err.py +65 -0
with_err-1.0.0/PKG-INFO
ADDED
|
@@ -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
|
+
```
|
with_err-1.0.0/README.md
ADDED
|
@@ -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,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
|