Skip to content

Commit 0b855e1

Browse files
authored
Replace TypeGuard with TypeIs (#194)
Closes #193
1 parent f4d5c07 commit 0b855e1

File tree

7 files changed

+136
-129
lines changed

7 files changed

+136
-129
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Possible log types:
1313

1414
## [Unreleased]
1515

16+
- `[changed]` Improve type narrowing for `is_ok` and `is_err` type guards by
17+
replacing `typing.TypeGuard` with `typing.TypeIs` (#193)
18+
1619
## [0.17.0] - 2024-06-02
1720

1821
- `[added]` Add `inspect()` and `inspect_err()` methods (#185)

README.md

+45-48
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ def get_user_by_email(email: str) -> Result[User, str]:
6464
return Ok(user)
6565

6666
user_result = get_user_by_email(email)
67-
if isinstance(user_result, Ok): # or `is_ok(user_result)`
67+
if is_ok(user_result):
6868
# type(user_result.ok_value) == User
6969
do_something(user_result.ok_value)
70-
else: # or `elif is_err(user_result)`
70+
else:
7171
# type(user_result.err_value) == str
7272
raise RuntimeError('Could not fetch user: %s' % user_result.err_value)
7373
```
@@ -97,12 +97,9 @@ for a, b in values:
9797

9898
Not all methods
9999
(<https://doc.rust-lang.org/std/result/enum.Result.html>) have been
100-
implemented, only the ones that make sense in the Python context. By
101-
using `isinstance` to check for `Ok` or `Err` you get type safe access
102-
to the contained value when using [MyPy](https://mypy.readthedocs.io/)
103-
to typecheck your code. All of this in a package allowing easier
104-
handling of values that can be OK or not, without resorting to custom
105-
exceptions.
100+
implemented, only the ones that make sense in the Python context.
101+
All of this in a package allowing easier handling of values that can
102+
be OK or not, without resorting to custom exceptions.
106103

107104
## API
108105

@@ -117,26 +114,29 @@ Creating an instance:
117114
>>> res2 = Err('nay')
118115
```
119116

120-
Checking whether a result is `Ok` or `Err`. You can either use `is_ok`
121-
and `is_err` type guard **functions** or `isinstance`. This way you get
122-
type safe access that can be checked with MyPy. The `is_ok()` or
123-
`is_err()` **methods** can be used if you don't need the type safety
124-
with MyPy:
117+
Checking whether a result is `Ok` or `Err`:
125118

126119
``` python
127-
>>> res = Ok('yay')
128-
>>> isinstance(res, Ok)
129-
True
130-
>>> is_ok(res)
131-
True
132-
>>> isinstance(res, Err)
133-
False
134-
>>> is_err(res)
135-
False
136-
>>> res.is_ok()
137-
True
138-
>>> res.is_err()
139-
False
120+
if is_err(result):
121+
raise RuntimeError(result.err_value)
122+
do_something(result.ok_value)
123+
```
124+
or
125+
``` python
126+
if is_ok(result):
127+
do_something(result.ok_value)
128+
else:
129+
raise RuntimeError(result.err_value)
130+
```
131+
132+
Alternatively, `isinstance` can be used (interchangeably to type guard functions
133+
`is_ok` and `is_err`). However, relying on `isinstance` may result in code that
134+
is slightly less readable and less concise:
135+
136+
``` python
137+
if isinstance(result, Err):
138+
raise RuntimeError(result.err_value)
139+
do_something(result.ok_value)
140140
```
141141

142142
You can also check if an object is `Ok` or `Err` by using the `OkErr`
@@ -156,27 +156,6 @@ False
156156
True
157157
```
158158

159-
The benefit of `isinstance` is better type checking that type guards currently
160-
do not offer,
161-
162-
```python
163-
res1: Result[int, str] = some_result()
164-
if isinstance(res1, Err):
165-
print("Error...:", res1.err_value) # res1 is narrowed to an Err
166-
return
167-
res1.ok()
168-
169-
res2: Result[int, str] = some_result()
170-
if res1.is_err():
171-
print("Error...:", res2.err_value) # res1 is NOT narrowed to an Err here
172-
return
173-
res1.ok()
174-
```
175-
176-
There is a proposed [PEP 724 – Stricter Type Guards](https://peps.python.org/pep-0724/)
177-
which may allow the `is_ok` and `is_err` type guards to work as expected in
178-
future versions of Python.
179-
180159
Convert a `Result` to the value or `None`:
181160

182161
``` python
@@ -358,7 +337,7 @@ x = third_party.do_something(...) # could raise; who knows?
358337
safe_do_something = as_result(Exception)(third_party.do_something)
359338

360339
res = safe_do_something(...) # Ok(...) or Err(...)
361-
if isinstance(res, Ok):
340+
if is_ok(res):
362341
print(res.ok_value)
363342
```
364343

@@ -468,6 +447,24 @@ from the non-unix shell you're using on Windows.
468447

469448
## FAQ
470449

450+
- **Why should I use the `is_ok` (`is_err`) type guard function over the `is_ok` (`is_err`) method?**
451+
452+
As you can see in the following example, MyPy can only narrow the type correctly
453+
while using the type guard **functions**:
454+
```python
455+
result: Result[int, str]
456+
457+
if is_ok(result):
458+
reveal_type(result) # "result.result.Ok[builtins.int]"
459+
else:
460+
reveal_type(result) # "result.result.Err[builtins.str]"
461+
462+
if result.is_ok():
463+
reveal_type(result) # "Union[result.result.Ok[builtins.int], result.result.Err[builtins.str]]"
464+
else:
465+
reveal_type(result) # "Union[result.result.Ok[builtins.int], result.result.Err[builtins.str]]"
466+
```
467+
471468
- **Why do I get the "Cannot infer type argument" error with MyPy?**
472469

473470
There is [a bug in MyPy](https://github.com/python/mypy/issues/230)

docs/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
- [`result.as_result`](./result.md#function-as_result): Make a decorator to turn a function into one that returns a ``Result``.
2020
- [`result.do`](./result.md#function-do): Do notation for Result (syntactic sugar for sequence of `and_then()` calls).
2121
- [`result.do_async`](./result.md#function-do_async): Async version of do. Example:
22-
- [`result.is_err`](./result.md#function-is_err): A typeguard to check if a result is an Err
23-
- [`result.is_ok`](./result.md#function-is_ok): A typeguard to check if a result is an Ok
22+
- [`result.is_err`](./result.md#function-is_err): A type guard to check if a result is an Err
23+
- [`result.is_ok`](./result.md#function-is_ok): A type guard to check if a result is an Ok
2424

2525

2626
---

0 commit comments

Comments
 (0)