errpath 0.1.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.
- errpath/__init__.py +19 -0
- errpath/__main__.py +4 -0
- errpath/_pydantic.py +102 -0
- errpath/analyse.py +1035 -0
- errpath/cli.py +141 -0
- errpath/diagram.py +135 -0
- errpath/ir.py +274 -0
- errpath/machine.py +165 -0
- errpath/py.typed +0 -0
- errpath/result.py +254 -0
- errpath/review.py +517 -0
- errpath/safe.py +129 -0
- errpath-0.1.0.dist-info/METADATA +622 -0
- errpath-0.1.0.dist-info/RECORD +17 -0
- errpath-0.1.0.dist-info/WHEEL +4 -0
- errpath-0.1.0.dist-info/entry_points.txt +2 -0
- errpath-0.1.0.dist-info/licenses/LICENSE +21 -0
errpath/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""errpath: Result types for Python. See every error path."""
|
|
2
|
+
|
|
3
|
+
from errpath.result import Err, Ok, Result, err_type, ok, traceback_of
|
|
4
|
+
from errpath.safe import safe, safe_async, safe_async_with, safe_with
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Err",
|
|
8
|
+
"Ok",
|
|
9
|
+
"Result",
|
|
10
|
+
"err_type",
|
|
11
|
+
"ok",
|
|
12
|
+
"safe",
|
|
13
|
+
"safe_async",
|
|
14
|
+
"safe_async_with",
|
|
15
|
+
"safe_with",
|
|
16
|
+
"traceback_of",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
errpath/__main__.py
ADDED
errpath/_pydantic.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Pydantic support for Ok / Err. Imported only when Pydantic asks for a schema.
|
|
2
|
+
|
|
3
|
+
JSON shape (the same as awaitly's):
|
|
4
|
+
|
|
5
|
+
{"ok": true, "value": ...}
|
|
6
|
+
{"ok": false, "error": ...}
|
|
7
|
+
|
|
8
|
+
An exception error is written as {"type": "KeyError", "message": "..."}. It can't be
|
|
9
|
+
read back from JSON (an exception isn't data), so use a model, str or enum as the
|
|
10
|
+
error type for Results that travel through APIs.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import types
|
|
16
|
+
import typing
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
|
|
21
|
+
from pydantic_core import PydanticSerializationUnexpectedValue
|
|
22
|
+
from pydantic_core import core_schema as cs
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _exception_types(tp: Any) -> tuple[type[BaseException], ...] | None:
|
|
26
|
+
"""The exception classes in ``tp`` (a class or a union of them), or None if it isn't one."""
|
|
27
|
+
members = (
|
|
28
|
+
typing.get_args(tp) if typing.get_origin(tp) in (typing.Union, types.UnionType) else (tp,)
|
|
29
|
+
)
|
|
30
|
+
if members and all(isinstance(m, type) and issubclass(m, BaseException) for m in members):
|
|
31
|
+
return members
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _describe_exception(error: BaseException) -> dict[str, str]:
|
|
36
|
+
return {"type": type(error).__name__, "message": str(error)}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def result_schema(
|
|
40
|
+
cls: type, source: Any, handler: GetCoreSchemaHandler, *, ok: bool
|
|
41
|
+
) -> cs.CoreSchema:
|
|
42
|
+
field = "value" if ok else "error"
|
|
43
|
+
args = typing.get_args(source)
|
|
44
|
+
payload_type = args[0] if args else Any
|
|
45
|
+
|
|
46
|
+
exceptions = _exception_types(payload_type)
|
|
47
|
+
to_payload: Callable[[Any], Any]
|
|
48
|
+
if exceptions:
|
|
49
|
+
payload = cs.union_schema([cs.is_instance_schema(e) for e in exceptions])
|
|
50
|
+
payload_out = cs.typed_dict_schema(
|
|
51
|
+
{
|
|
52
|
+
"type": cs.typed_dict_field(cs.str_schema()),
|
|
53
|
+
"message": cs.typed_dict_field(cs.str_schema()),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
to_payload = _describe_exception
|
|
57
|
+
else:
|
|
58
|
+
payload = handler.generate_schema(payload_type)
|
|
59
|
+
payload_out = payload
|
|
60
|
+
to_payload = lambda value: value # noqa: E731
|
|
61
|
+
|
|
62
|
+
def shape(inner: cs.CoreSchema) -> cs.CoreSchema:
|
|
63
|
+
return cs.typed_dict_schema(
|
|
64
|
+
{
|
|
65
|
+
"ok": cs.typed_dict_field(cs.literal_schema([ok])),
|
|
66
|
+
field: cs.typed_dict_field(inner),
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# {"ok": ..., "value"/"error": ...} -> Ok/Err, validating the payload.
|
|
71
|
+
from_dict = cs.no_info_after_validator_function(lambda d: cls(d[field]), shape(payload))
|
|
72
|
+
# An existing Ok/Err is re-checked the same way, so Ok("not a User") is rejected.
|
|
73
|
+
from_instance = cs.chain_schema(
|
|
74
|
+
[
|
|
75
|
+
cs.is_instance_schema(cls),
|
|
76
|
+
cs.no_info_plain_validator_function(lambda r: {"ok": ok, field: getattr(r, field)}),
|
|
77
|
+
from_dict,
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def serialize(result: Any, handler: cs.SerializerFunctionWrapHandler) -> Any:
|
|
82
|
+
if not isinstance(result, cls):
|
|
83
|
+
# In Ok | Err, both serializers are tried; this tells Pydantic to use the other.
|
|
84
|
+
raise PydanticSerializationUnexpectedValue(f"not an {cls.__name__}")
|
|
85
|
+
# Hand the dict back to Pydantic, so include/exclude reach the payload's fields
|
|
86
|
+
# (e.g. exclude={"result": {"value": {"secret"}}}).
|
|
87
|
+
return handler({"ok": ok, field: to_payload(getattr(result, field))})
|
|
88
|
+
|
|
89
|
+
def document_written_shape(schema: cs.CoreSchema, handler: GetJsonSchemaHandler) -> Any:
|
|
90
|
+
# An exception is validated by instance check (no JSON Schema) but written as
|
|
91
|
+
# {type, message}; document what is written. (return_schema would do this too, but
|
|
92
|
+
# it serializes a second time, which breaks include/exclude.)
|
|
93
|
+
if handler.mode == "serialization":
|
|
94
|
+
return handler(shape(payload_out))
|
|
95
|
+
return handler(schema)
|
|
96
|
+
|
|
97
|
+
return cs.json_or_python_schema(
|
|
98
|
+
json_schema=from_dict,
|
|
99
|
+
python_schema=cs.union_schema([from_instance, from_dict]),
|
|
100
|
+
serialization=cs.wrap_serializer_function_ser_schema(serialize, schema=shape(payload_out)),
|
|
101
|
+
metadata={"pydantic_js_functions": [document_written_shape]} if exceptions else None,
|
|
102
|
+
)
|