Skip to content

Commit

Permalink
Exception and logging lesson (#36)
Browse files Browse the repository at this point in the history
* New python lesson, how to handle exceptions. Code + tests

* Add exceptions docs

* Add logging lesson
  • Loading branch information
McLargo authored May 13, 2024
1 parent 1e1ad84 commit 7112818
Show file tree
Hide file tree
Showing 16 changed files with 571 additions and 11 deletions.
1 change: 1 addition & 0 deletions cspell/dictionaries/python.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ defaultdict
getsizeof
kwargs
levelname
loguru
repr
timeit
38 changes: 38 additions & 0 deletions docs/intermediate/exceptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Exceptions

Exceptions are a mechanism for handling errors in Python (and most programming
language). When an error occurs when running your code, Python raises an
exception. There are multiple types of exceptions. Each exception has a name and
a message.

If the exception is not caught, the program will terminate immediately, raising
the corresponding exception.

::: src.intermediate.exceptions.exception_uncontrolled

## Controlling exceptions

Exceptions can be caught and handled using a `try` block. You can catch the
exception, do something (like logging), and continue running the program.

::: src.intermediate.exceptions.exception_controlled

You can catch the exception, logging and raise the same exception to terminate
the execution.

::: src.intermediate.exceptions.exception_controlled_raise_exception

Similar way, ou can catch the exception, logging and raise another type of
exception. You can terminate the execution of a running program by raising an
exception at any time.

::: src.intermediate.exceptions.exception_controlled_raise_custom_exception

With the finally block, you can run code that will always run, regardless if the
code in the try block raises an exception. It will be always executed.

::: src.intermediate.exceptions.exception_controlled_raise_custom_exception

## References

- [Concrete exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions)
65 changes: 65 additions & 0 deletions docs/intermediate/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Logging

Logging is a very important part of any application. It allows you to track the
code execution and to debug the application. Python has a built-in logging
module that allows you to log messages to the console, to a file, or to a remote
server. In contrast to the `print` function, the logging module is more
complete, allowing you to configure the log level, the log format, and the log
destination.

Logging is based in handlers. A handler is an object that receives the log
messages and decides what to do with them. The logging module has several
built-in handlers, such as `StreamHandler`, `FileHandler`, `RotatingFileHandler`
or `TimedRotatingFileHandler`. But you can create your own handler by inherit
the `Handler` class. [Notifiers](https://github.com/liiight/notifiers) is a 3pp
library that provides with extra handlers with the ability to send notifications
to different services.

## Best practices

- Set different log levels for different environments. For example, you may set
`DEBUG` level in development and `ERROR` level in production.
- Set a specific format for the log messages, including the timestamp or the log
level. Using a standard format makes it easier to read the log messages. Use
the `extra` parameter to pass the data to the log message.
- Use pipelines `|` to separate the different parts of the log message. It can be
useful to filter the log messages, or even to parse them.
- To include variables in your log message, don't use `format` or `f-string` in
the log call, instead use the `%s`, like `logger.info('Variable: %s', value)`.
- Use `logging.exception` to log an exception message and the stack trace.
- Set the different logger instance you are going to use with
`logging.getLogger`. This way you can configure the logger in one place and
use it in different modules.

## logging library

This is the built-in Python logging library. It is very flexible and allows you
to configure the log level, the log format, and the log destination.

Each logger has a name, and the loggers are organized in a tree-like structure.
The root logger is the top-level logger, and all other loggers are children of
the root logger.

::: src.intermediate.logging.default_logging

::: src.intermediate.logging.custom_logging_format

## loguru library

Loguru is a third-party library that simplifies the logging configuration to the
bare minimum, such as log level and log format. But you can also can configure
much more easily, such as:

- color customization.
- log rotation, retention and compression.
- custom log levels.
- lazy evaluation of log messages.

:::src.intermediate.logging.default_loguru

::: src.intermediate.logging.custom_loguru_format_and_level

## References

- [Python logging module](https://docs.python.org/3/library/logging.html)
- [Loguru repository](https://github.com/Delgan/loguru)
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ nav:
- "Dataclasses": beginner/dataclasses.md
- "dict vs defaultdict": beginner/dict_vs_defaultdict.md
- Intermediate:
- "Exceptions": intermediate/exceptions.md
- "Inheritance": intermediate/inheritance.md
- "Logging": intermediate/logging.md
- "Yield vs return": intermediate/yield_vs_return.md
- Advanced:
- "Decorators": advanced/decorators.md
Expand Down
34 changes: 33 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ include = '.pyi?$'

[tool.coverage.report]
skip_empty = true
fail_under = 100
show_missing = true
skip_covered = true
exclude_also = [
"@(abc\\.)?abstractmethod",
]
Expand All @@ -25,6 +28,7 @@ mkdocs = "^1.5.2"
mkdocstrings = {extras = ["python"], version = "^0.23.0"}
mkdocs-material = "^9.2.8"
mkdocs-git-revision-date-localized-plugin = "^1.2.4"
loguru = "^0.7.2"


[tool.poetry.group.dev.dependencies]
Expand Down
14 changes: 14 additions & 0 deletions src/intermediate/exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Module to show how to handle exceptions.
This module contains different methods to handle exceptions.
"""

from .custom_exceptions import CustomError # noqa: F401
from .exceptions import (
exception_controlled, # noqa: F401
exception_controlled_raise_custom_exception, # noqa: F401
exception_controlled_raise_exception, # noqa: F401
exception_uncontrolled, # noqa: F401
exception_with_finally, # noqa: F401
)
17 changes: 17 additions & 0 deletions src/intermediate/exceptions/custom_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Module with a sample CustomError class.
This class is just an example of a CustomError class.
"""


class CustomError(Exception):
"""Custom error class."""

message = str
exception = Exception

def __init__(self, **kwargs):
"""Init CustomError exception."""
self.message = kwargs["message"]
self.exception = kwargs["exception"]
90 changes: 90 additions & 0 deletions src/intermediate/exceptions/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Module to show how to handle exceptions.
This module contains different methods to handle exceptions.
"""

import logging

from .custom_exceptions import CustomError

logging.basicConfig(name=__name__, format="%(message)s", level=logging.DEBUG)
logger = logging.getLogger(__name__)


def exception_uncontrolled() -> None:
"""Method that raises an exception that is not handled correctly.
Raises:
TypeError: cannot sum int&string types
"""
number = 1
char = "a"
number + char # raise TypeError, cannot sum int&string types


def exception_controlled() -> None:
"""Method that control the exception, no raise."""
number = 1
char = "a"
try:
number + char # raise TypeError, cannot sum int&string types
except TypeError:
logger.warning("Cannot sum int + string. Continue.")
pass


def exception_controlled_raise_exception() -> None:
"""Method that control the exception, raising the same exception.
Raises:
TypeError: cannot sum int&string types
"""
number = 1
char = "a"
try:
number + char # raise TypeError, cannot sum int&string types
except TypeError as exc:
logger.error("Cannot sum int + string. Raising TypeError.")
raise exc


def exception_controlled_raise_custom_exception() -> None:
"""Method that control the exception, raising custom exception.
Raises:
TypeError: cannot sum int&string types
"""
number = 1
char = "a"
try:
number + char # raise TypeError, cannot sum int&string types
except TypeError as exc:
logger.error("Cannot sum int + string. Raising CustomError.")
raise CustomError(
message="Controlled TypeError",
exception=exc,
)


def exception_with_finally(raise_exception: bool) -> None:
"""Method that control the exception, raising custom exception.
Arguments:
raise_exception (bool): flag to raise exception or not
Raises:
TypeError: cannot sum int&string types
"""
number = 1
char = "a"
try:
if raise_exception:
number + char # raise TypeError, cannot sum int&string types
else:
number + 10
except TypeError as exc:
logger.error("Cannot sum int + string. Raising TypeError.")
raise exc
finally:
logger.debug("Finally is executed.")
2 changes: 1 addition & 1 deletion src/intermediate/inheritance/inheritance.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Module to show inheritance.
This module contains the an abstract class and 2 subclasses to show inheritance
This module contains an abstract class and 2 subclasses to show inheritance
with abstractmethod.
"""
Expand Down
13 changes: 13 additions & 0 deletions src/intermediate/logging/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Module to show the use of logging libraries.
This module contains different logging libraries, how to initialize, configure
and use.
"""

from .logging import (
custom_logging_format, # noqa: F401
custom_loguru_format_and_level, # noqa: F401
default_logging, # noqa: F401
default_loguru, # noqa: F401
)
Loading

0 comments on commit 7112818

Please sign in to comment.