Commit 80ab5863 authored by Kirill Smelkov's avatar Kirill Smelkov

golang, errors, fmt: Provide top-level documentation for error chaining

This is top-level documentation for error chaining that was promised
and marked as TODO in

- fd95c88a (golang, errors, fmt: Error chaining (C++/Pyx))
- 17798442 (golang: Expose error at Py level)
- 78d0c76f (golang: Teach pyerror to be a base class)
- 337de0d7 (golang, errors, fmt: Error chaining (Python))
- 03f88c0b (errors: Take .__cause__ into account)
parent 073d81a8
...@@ -9,6 +9,7 @@ Package `golang` provides Go-like features for Python: ...@@ -9,6 +9,7 @@ Package `golang` provides Go-like features for Python:
- `chan` and `select` provide channels with Go semantic. - `chan` and `select` provide channels with Go semantic.
- `func` allows to define methods separate from class. - `func` allows to define methods separate from class.
- `defer` allows to schedule a cleanup from the main control flow. - `defer` allows to schedule a cleanup from the main control flow.
- `error` and package `errors` provide error chaining.
- `b` and `u` provide way to make sure an object is either bytes or unicode. - `b` and `u` provide way to make sure an object is either bytes or unicode.
- `gimport` allows to import python modules by full path in a Go workspace. - `gimport` allows to import python modules by full path in a Go workspace.
...@@ -169,6 +170,60 @@ If `defer` is used, the function that uses it must be wrapped with `@func` ...@@ -169,6 +170,60 @@ If `defer` is used, the function that uses it must be wrapped with `@func`
decorator. decorator.
Errors
------
In concurrent systems operational stack generally differs from execution code
flow, which makes code stack traces significantly less useful to understand an
error. Pygolang provides support for error chaining that gives ability to build
operational error stack and to inspect resulting errors:
`error` is error type that can be used by itself or subclassed. By
providing `.Unwrap()` method, an error can optionally wrap another error this
way forming an error chain. `errors.Is` reports whether an item in error chain
matches target. `fmt.Errorf` provides handy way to build wrapping errors.
For example::
e1 = error("problem")
e2 = fmt.Errorf("doing something for %s: %w", "joe", e1)
print(e2) # prints "doing something for joe: problem"
errors.Is(e2, e1) # gives True
# OpError is example class to represents an error of operation op(path).
class OpError(error):
def __init__(e, op, path, err):
e.op = op
e.path = path
e.err = err
# .Error() should be used to define what error's string is.
# it is automatically used by error to also provide both .__str__ and .__repr__.
def Error(e):
return "%s %s: %s" % (e.op, e.path, e.err)
# provided .Unwrap() indicates that this error is chained.
def Unwrap(e):
return e.err
mye = OpError("read", "file.txt", io.ErrUnexpectedEOF)
print(mye) # prints "read file.txt: unexpected EOF"
errors.Is(mye, io.EOF) # gives False
errors.Is(mye. io.ErrUnexpectedEOF) # gives True
Both wrapped and wrapping error can be of arbitrary Python type - not
necessarily of `error` or its subclass.
`error` is also used to represent at Python level an error returned by
Cython/nogil call (see `Cython/nogil API`_) and preserves Cython/nogil error
chain for inspection at Python level.
Pygolang error chaining integrates with Python error chaining and takes
`.__cause__` attribute into account for exception created via `raise X from Y`
(`PEP 3134`__).
__ https://www.python.org/dev/peps/pep-3134/
Strings Strings
------- -------
...@@ -300,6 +355,22 @@ in between *nogil* and Python worlds. For example:: ...@@ -300,6 +355,22 @@ in between *nogil* and Python worlds. For example::
return pych return pych
`error` is the interface that represents errors. `errors.New` and `fmt.errorf`
provide way to build errors from text. An error can optionally wrap another
error by implementing `errorWrapper` interface and providing `.Unwrap()` method.
`errors.Is` reports whether an item in error chain matches target. `fmt.errorf`
with `%w` specifier provide handy way to build wrapping errors. For example::
e1 = errors.New("problem")
e2 = fmt.errorf("doing something for %s: %w", "joe", e1)
e2.Error() # gives "doing something for joe: problem"
errors.Is(e2, e1) # gives True
An `error` can be exposed to Python via `pyerror` cdef class wrapper
instantiated by `pyerror.from_error()`. `pyerror` preserves Cython/nogil error
chain for inspection by Python-level `error.Is`.
`panic` stops normal execution of current goroutine by throwing a C-level `panic` stops normal execution of current goroutine by throwing a C-level
exception. On Python/C boundaries C-level exceptions have to be converted to exception. On Python/C boundaries C-level exceptions have to be converted to
Python-level exceptions with `topyexc`. For example:: Python-level exceptions with `topyexc`. For example::
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
- `chan` and `select` provide channels with Go semantic. - `chan` and `select` provide channels with Go semantic.
- `func` allows to define methods separate from class. - `func` allows to define methods separate from class.
- `defer` allows to schedule a cleanup from the main control flow. - `defer` allows to schedule a cleanup from the main control flow.
- `error` and package `errors` provide error chaining.
- `b` and `u` provide way to make sure an object is either bytes or unicode. - `b` and `u` provide way to make sure an object is either bytes or unicode.
- `gimport` allows to import python modules by full path in a Go workspace. - `gimport` allows to import python modules by full path in a Go workspace.
......
...@@ -24,6 +24,7 @@ Cython/nogil API ...@@ -24,6 +24,7 @@ Cython/nogil API
- `go` spawns lightweight thread. - `go` spawns lightweight thread.
- `chan[T]`, `makechan[T]` and `select` provide C-level channels with Go semantic. - `chan[T]`, `makechan[T]` and `select` provide C-level channels with Go semantic.
- `error` is the interface that represents errors.
- `panic` stops normal execution of current goroutine by throwing a C-level exception. - `panic` stops normal execution of current goroutine by throwing a C-level exception.
Everything in Cython/nogil API do not depend on Python runtime and in Everything in Cython/nogil API do not depend on Python runtime and in
...@@ -40,6 +41,7 @@ Golang.py runtime ...@@ -40,6 +41,7 @@ Golang.py runtime
In addition to Cython/nogil API, golang.pyx provides runtime for golang.py: In addition to Cython/nogil API, golang.pyx provides runtime for golang.py:
- Python-level channels are represented by pychan + pyselect. - Python-level channels are represented by pychan + pyselect.
- Python-level error is represented by pyerror.
- Python-level panic is represented by pypanic. - Python-level panic is represented by pypanic.
""" """
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
// - `chan<T>`, and `select` provide channels with Go semantic and automatic // - `chan<T>`, and `select` provide channels with Go semantic and automatic
// lifetime management. // lifetime management.
// - `defer` schedules cleanup. // - `defer` schedules cleanup.
// - `error` is the interface that represents errors.
// - `panic` throws exception that represent C-level panic. // - `panic` throws exception that represent C-level panic.
// //
// For example: // For example:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment