Skip to content

Commit

Permalink
Merge pull request #108 from dusktreader/dusktreader/add-base-message…
Browse files Browse the repository at this point in the history
…-to-do-except-params

Made some improvements:
  • Loading branch information
dusktreader authored Oct 8, 2024
2 parents 6116cae + b292cb0 commit 2160fe1
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 203 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## v4.2.0 - 2024-10-08

* Added `base_message` option to `DoExceptParams`.
* Changed `exc_builder` option to expect a new `ExcBuilderParams` class.
* Updated documentation.

## v4.1.0 - 2022-08-31

* Added `ignore_exc_class` option to `handle_errors`.
Expand Down
7 changes: 2 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,15 @@ mypy: install

.PHONY: lint
lint: install
poetry run black --check ${PACKAGE_NAME}
poetry run isort --check ${PACKAGE_NAME}
poetry run flake8 ${PACKAGE_NAME}
poetry run ruff check ${PACKAGE_NAME} tests

.PHONY: qa
qa: test lint mypy
echo "All quality checks pass!"

.PHONY: format
format: install
poetry run black ${PACKAGE_NAME}
poetry run isort ${PACKAGE_NAME}
poetry run ruff format ${PACKAGE_NAME} tests

.PHONY: docs
docs: install
Expand Down
103 changes: 77 additions & 26 deletions buzz/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import dataclasses
import sys
import types
from typing import Any, Callable, Iterable, Iterator, Mapping, Tuple, TypeVar
from typing import Any, Callable, Iterable, Iterator, Mapping, Tuple, TypeVar, Generic, Type


def noop(*_, **__):
Expand All @@ -18,14 +18,37 @@ def noop(*_, **__):
TExc = TypeVar("TExc", bound=Exception)


def default_exc_builder(exc: type[TExc], message: str, *args, **kwargs) -> TExc:
@dataclasses.dataclass
class ExcBuilderParams(Generic[TExc]):
"""
Dataclass for the `exc_builder` user supplied exception constructor.
Attributes:
raise_exc_class: The exception class that should be built
message: The message to build the exception with
raise_args: The positional arguments that are needed to build the exception
raise_kwargs: The keyword arguments that are needed to build the exception
"""

raise_exc_class: Type[TExc]
message: str
raise_args: Iterable[Any]
raise_kwargs: Mapping[str, Any]


def default_exc_builder(params: ExcBuilderParams[TExc]) -> TExc:
"""
Build an exception instance using default behavior where message is passed as first positional argument.
Some exception types such as FastAPI's HTTPException do not take a message as the first positional argument, so
they will need a different exception builder.
"""
return exc(message, *args, **kwargs)
return params.raise_exc_class(
params.message,
*params.raise_args,
**params.raise_kwargs,
)


def require_condition(
Expand All @@ -34,7 +57,7 @@ def require_condition(
raise_exc_class: type[Exception] = Exception,
raise_args: Iterable[Any] | None = None,
raise_kwargs: Mapping[str, Any] | None = None,
exc_builder: Callable[..., Exception] = default_exc_builder,
exc_builder: Callable[[ExcBuilderParams], Exception] = default_exc_builder,
):
"""
Assert that an expression is truthy. If the assertion fails, raise an exception with the supplied message.
Expand All @@ -49,14 +72,21 @@ def require_condition(
raise_args: Additional positional args (after the constructed message) that will passed when raising
an instance of the ``raise_exc_class``.
raise_kwargs: Keyword args that will be passed when raising an instance of the ``raise_exc_class``.
exc_builder: A function that should be called to construct the raised ``raise_exc_class``. Useful for
exception classes that do not take a message as the first positional argument.
"""
if raise_exc_class is None:
raise ValueError("The raise_exc_class kwarg may not be None")

if not expr:
args = raise_args or []
kwargs = raise_kwargs or {}
raise exc_builder(raise_exc_class, message, *args, **kwargs)
raise exc_builder(
ExcBuilderParams(
raise_exc_class=raise_exc_class,
message=message,
raise_args=raise_args or [],
raise_kwargs=raise_kwargs or {},
)
)


TNonNull = TypeVar("TNonNull")
Expand All @@ -68,7 +98,7 @@ def enforce_defined(
raise_exc_class: type[Exception] = Exception,
raise_args: Iterable[Any] | None = None,
raise_kwargs: Mapping[str, Any] | None = None,
exc_builder: Callable[..., Exception] = default_exc_builder,
exc_builder: Callable[[ExcBuilderParams], Exception] = default_exc_builder,
) -> TNonNull:
"""
Assert that a value is not None. If the assertion fails, raise an exception with the supplied message.
Expand All @@ -86,13 +116,20 @@ def enforce_defined(
raise_args: Additional positional args (after the constructed message) that will passed when raising
an instance of the ``raise_exc_class``.
raise_kwargs: Keyword args that will be passed when raising an instance of the ``raise_exc_class``.
exc_builder: A function that should be called to construct the raised ``raise_exc_class``. Useful for
exception classes that do not take a message as the first positional argument.
"""
if value is not None:
return value
else:
args = raise_args or []
kwargs = raise_kwargs or {}
raise exc_builder(raise_exc_class, message, *args, **kwargs)
raise exc_builder(
ExcBuilderParams(
raise_exc_class=raise_exc_class,
message=message,
raise_args=raise_args or [],
raise_kwargs=raise_kwargs or {},
)
)


class _ExpressionChecker:
Expand Down Expand Up @@ -132,7 +169,7 @@ def check_expressions(
raise_exc_class: type[Exception] = Exception,
raise_args: Iterable[Any] | None = None,
raise_kwargs: Mapping[str, Any] | None = None,
exc_builder: Callable[..., Exception] = default_exc_builder,
exc_builder: Callable[[ExcBuilderParams], Exception] = default_exc_builder,
):
"""
Check a series of expressions inside of a context manager. If any fail an exception is raised that contains a
Expand All @@ -149,6 +186,8 @@ def check_expressions(
raise_args: Additional positional args (after the constructed message) that will passed when raising
an instance of the ``raise_exc_class``.
raise_kwargs: Keyword args that will be passed when raising an instance of the ``raise_exc_class``.
exc_builder: A function that should be called to construct the raised ``raise_exc_class``. Useful for
exception classes that do not take a message as the first positional argument.
Example:
Expand Down Expand Up @@ -209,12 +248,14 @@ class DoExceptParams:
Attributes:
err: The exception instance itself.
final_message: The final, combined message
err: The exception instance itself
base_message: The base message parameter that was passed to the `handle_errors()` function
final_message: The final, combined message including the base message and string formatted exception
trace: A traceback of the exception
"""

err: Exception
base_message: str
final_message: str
trace: types.TracebackType | None

Expand All @@ -230,20 +271,16 @@ def handle_errors(
do_finally: Callable[[], None] = noop,
do_except: Callable[[DoExceptParams], None] = noop,
do_else: Callable[[], None] = noop,
exc_builder: Callable[..., Exception] = default_exc_builder,
exc_builder: Callable[[ExcBuilderParams], Exception] = default_exc_builder,
) -> Iterator[None]:
"""
Provide a context manager that will intercept exceptions and repackage them with a message attached:
Args:
message: The message to attach to the raised exception.
raise_exc_class: The exception type to raise with the constructed message if an exception is caught in the
managed context.
Defaults to Exception.
If ``None`` is passed, no new exception will be raised and only the ``do_except``,
``do_else``, and ``do_finally`` functions will be called.
managed context. If ``None`` is passed, no new exception will be raised and only the
``do_except``, ``do_else``, and ``do_finally`` functions will be called.
raise_args: Additional positional args (after the constructed message) that will passed when raising
an instance of the ``raise_exc_class``.
raise_kwargs: Keyword args that will be passed when raising an instance of the ``raise_exc_class``.
Expand All @@ -263,10 +300,12 @@ def handle_errors(
parameter that is an instance of the ``DoExceptParams`` dataclass.
Note that the ``do_except`` method is passed the *original exception*.
do_else: A function that should be called only if there were no exceptions encountered.
exc_builder: A function that should be called to construct the raised ``raise_exc_class``. Useful for
exception classes that do not take a message as the first positional argument.
Example:
The following is an example usage::
The following is an example usage:
with handle_errors("It didn't work"):
some_code_that_might_raise_an_exception()
Expand Down Expand Up @@ -297,11 +336,23 @@ class _DefaultIgnoreException(Exception):

trace = get_traceback()

do_except(DoExceptParams(err, final_message, trace)) # type: ignore # For packport of dataclasses in python3.6
do_except(
DoExceptParams(
err=err,
base_message=message,
final_message=final_message,
trace=trace,
)
)
if raise_exc_class is not None:
args = raise_args or []
kwargs = raise_kwargs or {}
raise exc_builder(raise_exc_class, final_message, *args, **kwargs).with_traceback(trace) from err
raise exc_builder(
ExcBuilderParams(
raise_exc_class=raise_exc_class,
message=final_message,
raise_args=raise_args or [],
raise_kwargs=raise_kwargs or {},
)
).with_traceback(trace) from err
else:
do_else()
finally:
Expand Down
45 changes: 36 additions & 9 deletions docs/source/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ For example, [FastAPI](https://fastapi.tiangolo.com/)'s `HTTPException` takes a
passed as a keyword argument named `details`.

In this case, you need to define a builder function to construct the exception and pass
it to the `exc_builder` option:
it to the `exc_builder` option. The `exc_builder` option should be a callable function
that accepts a single parameter of type `ExcBuilderParams` that can be imported from
``buzz``.


```python
Expand All @@ -119,14 +121,14 @@ class WeirdArgsError(Exception):
self.weird_arg = weird_arg
self.detail = detail

def weird_builder(exc_class, message, *args, **kwargs):
return exc_class(*args, detail=message, **kwargs)
def weird_builder(params):
return exc_class(*params.raise_args, detail=params.message)

require_condition(
some_condition(),
"some_condition failed",
raise_exc_class=WeirdArgsError,
raise_kwargs=dict("foo", "bar"),
raise_args=["weird"],
exc_builder=weird_builder,
)
```
Expand Down Expand Up @@ -173,9 +175,30 @@ In this case, a `MyProjectError` with be raised with positional arguments of `"f

By default, `enforce_defined()` raises an exception with a basic message saying that the
value was not defined. However, you may pass in a custom message with the `message`
keyword argument. Like `require_condition()`, the `enforce_defined()` function also
accepts the `raise_exc_class`, `raise_args`, `raise_kwargs`, and `exc_builder` keyword
arguments.
keyword argument.

The `enforce_defined()` function also accepts some keyword arguments:


#### raise_exc_class

Functions the same as `require_condition`.


#### raise_args

Functions the same as `require_condition`.


#### raise_kwargs

Functions the same as `require_condition`.


#### exc_builder

Functions the same as `require_condition`.



### Exception handling context manager
Expand Down Expand Up @@ -268,7 +291,7 @@ be handled by `handle_errors`. For example, if you want to use
Often, it is useful to do some particular things when an exception is caught. Most
frequently this includes logging the exception. The `do_except` optional argument
provides the ability to do this. The `do_except` option should be a callable function
that accepts a paramter of type `DoExceptParams` that can be imported from ``buzz``.
that accepts a parameter of type `DoExceptParams` that can be imported from ``buzz``.
This `dataclass` has three attributes:

* err: The caught exception itself
Expand Down Expand Up @@ -316,6 +339,10 @@ with handle_errors("Something went wrong", do_finally=close_resource):
some_dangerous_function_that_uses_resource(resource)
```

#### exc_builder

Functions the same as `require_condition`.


## Additional Features
-------------------
Expand Down Expand Up @@ -345,7 +372,7 @@ Exception: Checked expressions failed: there will be errors
5: zero is still zero
```

The `check_expressions()` also accepts some keyword arguments:
The `check_expressions()` context manager also accepts some keyword arguments:


#### raise_exc_class
Expand Down
Loading

0 comments on commit 2160fe1

Please sign in to comment.