about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pythonjsonlogger
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/pythonjsonlogger')
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/__init__.py17
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/core.py394
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/defaults.py241
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/exception.py27
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/json.py119
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/jsonlogger.py18
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/msgspec.py63
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/orjson.py71
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/py.typed1
-rw-r--r--.venv/lib/python3.12/site-packages/pythonjsonlogger/utils.py40
10 files changed, 991 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/__init__.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/__init__.py
new file mode 100644
index 00000000..298a3feb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/__init__.py
@@ -0,0 +1,17 @@
+### IMPORTS
+### ============================================================================
+## Future
+
+## Standard Library
+import warnings
+
+## Installed
+
+## Application
+from . import json
+from . import utils
+
+### CONSTANTS
+### ============================================================================
+ORJSON_AVAILABLE = utils.package_is_available("orjson")
+MSGSPEC_AVAILABLE = utils.package_is_available("msgspec")
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/core.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/core.py
new file mode 100644
index 00000000..1a4dee38
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/core.py
@@ -0,0 +1,394 @@
+"""Core functionality shared by all JSON loggers"""
+
+### IMPORTS
+### ============================================================================
+## Future
+from __future__ import annotations
+
+## Standard Library
+from datetime import datetime, timezone
+import importlib
+import logging
+import re
+import sys
+from typing import Optional, Union, Callable, List, Dict, Container, Any, Sequence
+
+if sys.version_info >= (3, 10):
+    from typing import TypeAlias
+else:
+    from typing_extensions import TypeAlias
+
+## Installed
+
+## Application
+
+
+### CONSTANTS
+### ============================================================================
+RESERVED_ATTRS: List[str] = [
+    "args",
+    "asctime",
+    "created",
+    "exc_info",
+    "exc_text",
+    "filename",
+    "funcName",
+    "levelname",
+    "levelno",
+    "lineno",
+    "module",
+    "msecs",
+    "message",
+    "msg",
+    "name",
+    "pathname",
+    "process",
+    "processName",
+    "relativeCreated",
+    "stack_info",
+    "thread",
+    "threadName",
+]
+"""Default reserved attributes.
+
+These come from the [default attributes of `LogRecord` objects](http://docs.python.org/library/logging.html#logrecord-attributes).
+
+Note:
+    Although considered a constant, this list is dependent on the Python version due to
+    different `LogRecord` objects having different attributes in different Python versions.
+
+*Changed in 3.0*: `RESERVED_ATTRS` is now `list[str]` instead of `tuple[str, ...]`.
+"""
+
+if sys.version_info >= (3, 12):
+    # taskName added in python 3.12
+    RESERVED_ATTRS.append("taskName")
+    RESERVED_ATTRS.sort()
+
+
+STYLE_STRING_TEMPLATE_REGEX = re.compile(r"\$\{(.+?)\}", re.IGNORECASE)  # $ style
+STYLE_STRING_FORMAT_REGEX = re.compile(r"\{(.+?)\}", re.IGNORECASE)  # { style
+STYLE_PERCENT_REGEX = re.compile(r"%\((.+?)\)", re.IGNORECASE)  # % style
+
+## Type Aliases
+## -----------------------------------------------------------------------------
+OptionalCallableOrStr: TypeAlias = Optional[Union[Callable, str]]
+"""Type alias"""
+
+LogRecord: TypeAlias = Dict[str, Any]
+"""Type alias"""
+
+
+### FUNCTIONS
+### ============================================================================
+def str_to_object(obj: Any) -> Any:
+    """Import strings to an object, leaving non-strings as-is.
+
+    Args:
+        obj: the object or string to process
+
+    *New in 3.1*
+    """
+
+    if not isinstance(obj, str):
+        return obj
+
+    module_name, attribute_name = obj.rsplit(".", 1)
+    return getattr(importlib.import_module(module_name), attribute_name)
+
+
+def merge_record_extra(
+    record: logging.LogRecord,
+    target: Dict,
+    reserved: Container[str],
+    rename_fields: Optional[Dict[str, str]] = None,
+) -> Dict:
+    """
+    Merges extra attributes from LogRecord object into target dictionary
+
+    Args:
+        record: logging.LogRecord
+        target: dict to update
+        reserved: dict or list with reserved keys to skip
+        rename_fields: an optional dict, used to rename field names in the output.
+            e.g. Rename `levelname` to `log.level`: `{'levelname': 'log.level'}`
+
+    *Changed in 3.1*: `reserved` is now `Container[str]`.
+    """
+    if rename_fields is None:
+        rename_fields = {}
+    for key, value in record.__dict__.items():
+        # this allows to have numeric keys
+        if key not in reserved and not (hasattr(key, "startswith") and key.startswith("_")):
+            target[rename_fields.get(key, key)] = value
+    return target
+
+
+### CLASSES
+### ============================================================================
+class BaseJsonFormatter(logging.Formatter):
+    """Base class for all formatters
+
+    Must not be used directly.
+
+    *New in 3.1*
+
+    *Changed in 3.2*: `defaults` argument is no longer ignored.
+
+    *Added in UNRELEASED*: `exc_info_as_array` and `stack_info_as_array` options are added.
+    """
+
+    _style: Union[logging.PercentStyle, str]  # type: ignore[assignment]
+
+    ## Parent Methods
+    ## -------------------------------------------------------------------------
+    # pylint: disable=too-many-arguments,super-init-not-called
+    def __init__(
+        self,
+        fmt: Optional[str] = None,
+        datefmt: Optional[str] = None,
+        style: str = "%",
+        validate: bool = True,
+        *,
+        prefix: str = "",
+        rename_fields: Optional[Dict[str, str]] = None,
+        rename_fields_keep_missing: bool = False,
+        static_fields: Optional[Dict[str, Any]] = None,
+        reserved_attrs: Optional[Sequence[str]] = None,
+        timestamp: Union[bool, str] = False,
+        defaults: Optional[Dict[str, Any]] = None,
+        exc_info_as_array: bool = False,
+        stack_info_as_array: bool = False,
+    ) -> None:
+        """
+        Args:
+            fmt: string representing fields to log
+            datefmt: format to use when formatting `asctime` field
+            style: how to extract log fields from `fmt`
+            validate: validate `fmt` against style, if implementing a custom `style` you
+                must set this to `False`.
+            defaults: a dictionary containing default fields that are added before all other fields and
+                may be overridden. The supplied fields are still subject to `rename_fields`.
+            prefix: an optional string prefix added at the beginning of
+                the formatted string
+            rename_fields: an optional dict, used to rename field names in the output.
+                Rename `message` to `@message`: `{'message': '@message'}`
+            rename_fields_keep_missing: When renaming fields, include missing fields in the output.
+            static_fields: an optional dict, used to add fields with static values to all logs
+            reserved_attrs: an optional list of fields that will be skipped when
+                outputting json log record. Defaults to [all log record attributes][pythonjsonlogger.core.RESERVED_ATTRS].
+            timestamp: an optional string/boolean field to add a timestamp when
+                outputting the json log record. If string is passed, timestamp will be added
+                to log record using string as key. If True boolean is passed, timestamp key
+                will be "timestamp". Defaults to False/off.
+            exc_info_as_array: break the exc_info into a list of lines based on line breaks.
+            stack_info_as_array: break the stack_info into a list of lines based on line breaks.
+
+        *Changed in 3.1*:
+
+        - you can now use custom values for style by setting validate to `False`.
+          The value is stored in `self._style` as a string. The `parse` method will need to be
+          overridden in order to support the new style.
+        - Renaming fields now preserves the order that fields were added in and avoids adding
+          missing fields. The original behaviour, missing fields have a value of `None`, is still
+          available by setting `rename_fields_keep_missing` to `True`.
+        """
+        ## logging.Formatter compatibility
+        ## ---------------------------------------------------------------------
+        # Note: validate added in 3.8, defaults added in 3.10
+        if style in logging._STYLES:
+            _style = logging._STYLES[style][0](fmt)  # type: ignore[operator]
+            if validate:
+                _style.validate()
+            self._style = _style
+            self._fmt = _style._fmt
+
+        elif not validate:
+            self._style = style
+            self._fmt = fmt
+
+        else:
+            raise ValueError(f"Style must be one of: {','.join(logging._STYLES.keys())}")
+
+        self.datefmt = datefmt
+
+        ## JSON Logging specific
+        ## ---------------------------------------------------------------------
+        self.prefix = prefix
+        self.rename_fields = rename_fields if rename_fields is not None else {}
+        self.rename_fields_keep_missing = rename_fields_keep_missing
+        self.static_fields = static_fields if static_fields is not None else {}
+        self.reserved_attrs = set(reserved_attrs if reserved_attrs is not None else RESERVED_ATTRS)
+        self.timestamp = timestamp
+
+        self._required_fields = self.parse()
+        self._skip_fields = set(self._required_fields)
+        self._skip_fields.update(self.reserved_attrs)
+        self.defaults = defaults if defaults is not None else {}
+        self.exc_info_as_array = exc_info_as_array
+        self.stack_info_as_array = stack_info_as_array
+        return
+
+    def format(self, record: logging.LogRecord) -> str:
+        """Formats a log record and serializes to json
+
+        Args:
+            record: the record to format
+        """
+        message_dict: Dict[str, Any] = {}
+        # TODO: logging.LogRecord.msg and logging.LogRecord.message in typeshed
+        #        are always type of str. We shouldn't need to override that.
+        if isinstance(record.msg, dict):
+            message_dict = record.msg
+            record.message = ""
+        else:
+            record.message = record.getMessage()
+
+        # only format time if needed
+        if "asctime" in self._required_fields:
+            record.asctime = self.formatTime(record, self.datefmt)
+
+        # Display formatted exception, but allow overriding it in the
+        # user-supplied dict.
+        if record.exc_info and not message_dict.get("exc_info"):
+            message_dict["exc_info"] = self.formatException(record.exc_info)
+        if not message_dict.get("exc_info") and record.exc_text:
+            message_dict["exc_info"] = record.exc_text
+
+        # Display formatted record of stack frames
+        # default format is a string returned from :func:`traceback.print_stack`
+        if record.stack_info and not message_dict.get("stack_info"):
+            message_dict["stack_info"] = self.formatStack(record.stack_info)
+
+        log_record: LogRecord = {}
+        self.add_fields(log_record, record, message_dict)
+        log_record = self.process_log_record(log_record)
+
+        return self.serialize_log_record(log_record)
+
+    ## JSON Formatter Specific Methods
+    ## -------------------------------------------------------------------------
+    def parse(self) -> List[str]:
+        """Parses format string looking for substitutions
+
+        This method is responsible for returning a list of fields (as strings)
+        to include in all log messages.
+
+        You can support custom styles by overriding this method.
+
+        Returns:
+            list of fields to be extracted and serialized
+        """
+        if isinstance(self._style, logging.StringTemplateStyle):
+            formatter_style_pattern = STYLE_STRING_TEMPLATE_REGEX
+
+        elif isinstance(self._style, logging.StrFormatStyle):
+            formatter_style_pattern = STYLE_STRING_FORMAT_REGEX
+
+        elif isinstance(self._style, logging.PercentStyle):
+            # PercentStyle is parent class of StringTemplateStyle and StrFormatStyle
+            # so it must be checked last.
+            formatter_style_pattern = STYLE_PERCENT_REGEX
+
+        else:
+            raise ValueError(f"Style {self._style!r} is not supported")
+
+        if self._fmt:
+            return formatter_style_pattern.findall(self._fmt)
+
+        return []
+
+    def serialize_log_record(self, log_record: LogRecord) -> str:
+        """Returns the final representation of the log record.
+
+        Args:
+            log_record: the log record
+        """
+        return self.prefix + self.jsonify_log_record(log_record)
+
+    def add_fields(
+        self,
+        log_record: Dict[str, Any],
+        record: logging.LogRecord,
+        message_dict: Dict[str, Any],
+    ) -> None:
+        """Extract fields from a LogRecord for logging
+
+        This method can be overridden to implement custom logic for adding fields.
+
+        Args:
+            log_record: data that will be logged
+            record: the record to extract data from
+            message_dict: dictionary that was logged instead of a message. e.g
+                `logger.info({"is_this_message_dict": True})`
+        """
+        for field in self.defaults:
+            log_record[self._get_rename(field)] = self.defaults[field]
+
+        for field in self._required_fields:
+            log_record[self._get_rename(field)] = record.__dict__.get(field)
+
+        for data_dict in [self.static_fields, message_dict]:
+            for key, value in data_dict.items():
+                log_record[self._get_rename(key)] = value
+
+        merge_record_extra(
+            record,
+            log_record,
+            reserved=self._skip_fields,
+            rename_fields=self.rename_fields,
+        )
+
+        if self.timestamp:
+            key = self.timestamp if isinstance(self.timestamp, str) else "timestamp"
+            log_record[self._get_rename(key)] = datetime.fromtimestamp(
+                record.created, tz=timezone.utc
+            )
+
+        if self.rename_fields_keep_missing:
+            for field in self.rename_fields.values():
+                if field not in log_record:
+                    log_record[field] = None
+        return
+
+    def _get_rename(self, key: str) -> str:
+        return self.rename_fields.get(key, key)
+
+    # Child Methods
+    # ..........................................................................
+    def jsonify_log_record(self, log_record: LogRecord) -> str:
+        """Convert this log record into a JSON string.
+
+        Child classes MUST override this method.
+
+        Args:
+            log_record: the data to serialize
+        """
+        raise NotImplementedError()
+
+    def process_log_record(self, log_record: LogRecord) -> LogRecord:
+        """Custom processing of the log record.
+
+        Child classes can override this method to alter the log record before it
+        is serialized.
+
+        Args:
+            log_record: incoming data
+        """
+        return log_record
+
+    def formatException(self, ei) -> Union[str, list[str]]:  # type: ignore
+        """Format and return the specified exception information.
+
+        If exc_info_as_array is set to True, This method returns an array of strings.
+        """
+        exception_info_str = super().formatException(ei)
+        return exception_info_str.splitlines() if self.exc_info_as_array else exception_info_str
+
+    def formatStack(self, stack_info) -> Union[str, list[str]]:  # type: ignore
+        """Format and return the specified stack information.
+
+        If stack_info_as_array is set to True, This method returns an array of strings.
+        """
+        stack_info_str = super().formatStack(stack_info)
+        return stack_info_str.splitlines() if self.stack_info_as_array else stack_info_str
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/defaults.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/defaults.py
new file mode 100644
index 00000000..0a002a90
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/defaults.py
@@ -0,0 +1,241 @@
+"""Collection of functions for building custom `json_default` functions.
+
+In general functions come in pairs of `use_x_default` and `x_default`, where the former is used
+to determine if you should call the latter.
+
+Most `use_x_default` functions also act as a [`TypeGuard`](https://mypy.readthedocs.io/en/stable/type_narrowing.html#user-defined-type-guards).
+"""
+
+### IMPORTS
+### ============================================================================
+## Future
+from __future__ import annotations
+
+## Standard Library
+import base64
+import dataclasses
+import datetime
+import enum
+import sys
+from types import TracebackType
+from typing import Any
+import traceback
+import uuid
+
+if sys.version_info >= (3, 10):
+    from typing import TypeGuard
+else:
+    from typing_extensions import TypeGuard
+
+## Installed
+
+## Application
+
+
+### FUNCTIONS
+### ============================================================================
+def unknown_default(obj: Any) -> str:
+    """Backup default function for any object type.
+
+    Will attempt to use `str` or `repr`. If both functions error will return
+    the string `"__could_not_encode__"`.
+
+    Args:
+        obj: object to handle
+    """
+    try:
+        return str(obj)
+    except Exception:  # pylint: disable=broad-exception-caught
+        pass
+    try:
+        return repr(obj)
+    except Exception:  # pylint: disable=broad-exception-caught
+        pass
+    return "__could_not_encode__"
+
+
+## Types
+## -----------------------------------------------------------------------------
+def use_type_default(obj: Any) -> TypeGuard[type]:
+    """Default check function for `type` objects (aka classes)."""
+    return isinstance(obj, type)
+
+
+def type_default(obj: type) -> str:
+    """Default function for `type` objects.
+
+    Args:
+        obj: object to handle
+    """
+    return obj.__name__
+
+
+## Dataclasses
+## -----------------------------------------------------------------------------
+def use_dataclass_default(obj: Any) -> bool:
+    """Default check function for dataclass instances"""
+    return dataclasses.is_dataclass(obj) and not isinstance(obj, type)
+
+
+def dataclass_default(obj) -> dict[str, Any]:
+    """Default function for dataclass instances
+
+    Args:
+        obj: object to handle
+    """
+    return dataclasses.asdict(obj)
+
+
+## Dates and Times
+## -----------------------------------------------------------------------------
+def use_time_default(obj: Any) -> TypeGuard[datetime.time]:
+    """Default check function for `datetime.time` instances"""
+    return isinstance(obj, datetime.time)
+
+
+def time_default(obj: datetime.time) -> str:
+    """Default function for `datetime.time` instances
+
+    Args:
+        obj: object to handle
+    """
+    return obj.isoformat()
+
+
+def use_date_default(obj: Any) -> TypeGuard[datetime.date]:
+    """Default check function for `datetime.date` instances"""
+    return isinstance(obj, datetime.date)
+
+
+def date_default(obj: datetime.date) -> str:
+    """Default function for `datetime.date` instances
+
+    Args:
+        obj: object to handle
+    """
+    return obj.isoformat()
+
+
+def use_datetime_default(obj: Any) -> TypeGuard[datetime.datetime]:
+    """Default check function for `datetime.datetime` instances"""
+    return isinstance(obj, datetime.datetime)
+
+
+def datetime_default(obj: datetime.datetime) -> str:
+    """Default function for `datetime.datetime` instances
+
+    Args:
+        obj: object to handle
+    """
+    return obj.isoformat()
+
+
+def use_datetime_any(obj: Any) -> TypeGuard[datetime.time | datetime.date | datetime.datetime]:
+    """Default check function for `datetime` related instances"""
+    return isinstance(obj, (datetime.time, datetime.date, datetime.datetime))
+
+
+def datetime_any(obj: datetime.time | datetime.date | datetime.date) -> str:
+    """Default function for `datetime` related instances
+
+    Args:
+        obj: object to handle
+    """
+    return obj.isoformat()
+
+
+## Exception and Tracebacks
+## -----------------------------------------------------------------------------
+def use_exception_default(obj: Any) -> TypeGuard[BaseException]:
+    """Default check function for exception instances.
+
+    Exception classes are not treated specially and should be handled by the
+    `[use_]type_default` functions.
+    """
+    return isinstance(obj, BaseException)
+
+
+def exception_default(obj: BaseException) -> str:
+    """Default function for exception instances
+
+    Args:
+        obj: object to handle
+    """
+    return f"{obj.__class__.__name__}: {obj}"
+
+
+def use_traceback_default(obj: Any) -> TypeGuard[TracebackType]:
+    """Default check function for tracebacks"""
+    return isinstance(obj, TracebackType)
+
+
+def traceback_default(obj: TracebackType) -> str:
+    """Default function for tracebacks
+
+    Args:
+        obj: object to handle
+    """
+    return "".join(traceback.format_tb(obj)).strip()
+
+
+## Enums
+## -----------------------------------------------------------------------------
+def use_enum_default(obj: Any) -> TypeGuard[enum.Enum | enum.EnumMeta]:
+    """Default check function for enums.
+
+    Supports both enum classes and enum values.
+    """
+    return isinstance(obj, (enum.Enum, enum.EnumMeta))
+
+
+def enum_default(obj: enum.Enum | enum.EnumMeta) -> Any | list[Any]:
+    """Default function for enums.
+
+    Supports both enum classes and enum values.
+
+    Args:
+        obj: object to handle
+    """
+    if isinstance(obj, enum.Enum):
+        return obj.value
+    return [e.value for e in obj]  # type: ignore[var-annotated]
+
+
+## UUIDs
+## -----------------------------------------------------------------------------
+def use_uuid_default(obj: Any) -> TypeGuard[uuid.UUID]:
+    """Default check function for `uuid.UUID` instances"""
+    return isinstance(obj, uuid.UUID)
+
+
+def uuid_default(obj: uuid.UUID) -> str:
+    """Default function for `uuid.UUID` instances
+
+    Formats the UUID using "hyphen" format.
+
+    Args:
+        obj: object to handle
+    """
+    return str(obj)
+
+
+## Bytes
+## -----------------------------------------------------------------------------
+def use_bytes_default(obj: Any) -> TypeGuard[bytes | bytearray]:
+    """Default check function for bytes"""
+    return isinstance(obj, (bytes, bytearray))
+
+
+def bytes_default(obj: bytes | bytearray, url_safe: bool = True) -> str:
+    """Default function for bytes
+
+    Args:
+        obj: object to handle
+        url_safe: use URL safe base 64 character set.
+
+    Returns:
+        The byte data as a base 64 string.
+    """
+    if url_safe:
+        return base64.urlsafe_b64encode(obj).decode("utf8")
+    return base64.b64encode(obj).decode("utf8")
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/exception.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/exception.py
new file mode 100644
index 00000000..1233f1ab
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/exception.py
@@ -0,0 +1,27 @@
+### IMPORTS
+### ============================================================================
+## Future
+from __future__ import annotations
+
+## Standard Library
+
+## Installed
+
+## Application
+
+
+### CLASSES
+### ============================================================================
+class PythonJsonLoggerError(Exception):
+    "Generic base clas for all Python JSON Logger exceptions"
+
+
+class MissingPackageError(ImportError, PythonJsonLoggerError):
+    "A required package is missing"
+
+    def __init__(self, name: str, extras_name: str | None = None) -> None:
+        msg = f"The {name!r} package is required but could not be found."
+        if extras_name is not None:
+            msg += f" It can be installed using 'python-json-logger[{extras_name}]'."
+        super().__init__(msg)
+        return
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/json.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/json.py
new file mode 100644
index 00000000..21e78d0f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/json.py
@@ -0,0 +1,119 @@
+"""JSON formatter using the standard library's `json` for encoding.
+
+Module contains the `JsonFormatter` and a custom `JsonEncoder` which supports a greater
+variety of types.
+"""
+
+### IMPORTS
+### ============================================================================
+## Future
+from __future__ import annotations
+
+## Standard Library
+import datetime
+import json
+from typing import Any, Callable, Optional, Union
+import warnings
+
+## Application
+from . import core
+from . import defaults as d
+
+
+### CLASSES
+### ============================================================================
+class JsonEncoder(json.JSONEncoder):
+    """A custom encoder extending [json.JSONEncoder](https://docs.python.org/3/library/json.html#json.JSONEncoder)"""
+
+    def default(self, o: Any) -> Any:
+        if d.use_datetime_any(o):
+            return self.format_datetime_obj(o)
+
+        if d.use_exception_default(o):
+            return d.exception_default(o)
+
+        if d.use_traceback_default(o):
+            return d.traceback_default(o)
+
+        if d.use_enum_default(o):
+            return d.enum_default(o)
+
+        if d.use_bytes_default(o):
+            return d.bytes_default(o)
+
+        if d.use_dataclass_default(o):
+            return d.dataclass_default(o)
+
+        if d.use_type_default(o):
+            return d.type_default(o)
+
+        try:
+            return super().default(o)
+        except TypeError:
+            return d.unknown_default(o)
+
+    def format_datetime_obj(self, o: datetime.time | datetime.date | datetime.datetime) -> str:
+        """Format datetime objects found in `self.default`
+
+        This allows subclasses to change the datetime format without understanding the
+        internals of the default method.
+        """
+        return d.datetime_any(o)
+
+
+class JsonFormatter(core.BaseJsonFormatter):
+    """JSON formatter using the standard library's [`json`](https://docs.python.org/3/library/json.html) for encoding"""
+
+    def __init__(
+        self,
+        *args,
+        json_default: core.OptionalCallableOrStr = None,
+        json_encoder: core.OptionalCallableOrStr = None,
+        json_serializer: Union[Callable, str] = json.dumps,
+        json_indent: Optional[Union[int, str]] = None,
+        json_ensure_ascii: bool = True,
+        **kwargs,
+    ) -> None:
+        """
+        Args:
+            args: see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]
+            json_default: a function for encoding non-standard objects
+            json_encoder: custom JSON encoder
+            json_serializer: a [`json.dumps`](https://docs.python.org/3/library/json.html#json.dumps)-compatible callable
+                that will be used to serialize the log record.
+            json_indent: indent parameter for the `json_serializer`
+            json_ensure_ascii: `ensure_ascii` parameter for the `json_serializer`
+            kwargs: see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]
+        """
+        super().__init__(*args, **kwargs)
+
+        self.json_default = core.str_to_object(json_default)
+        self.json_encoder = core.str_to_object(json_encoder)
+        self.json_serializer = core.str_to_object(json_serializer)
+        self.json_indent = json_indent
+        self.json_ensure_ascii = json_ensure_ascii
+        if not self.json_encoder and not self.json_default:
+            self.json_encoder = JsonEncoder
+        return
+
+    def jsonify_log_record(self, log_record: core.LogRecord) -> str:
+        """Returns a json string of the log record."""
+        return self.json_serializer(
+            log_record,
+            default=self.json_default,
+            cls=self.json_encoder,
+            indent=self.json_indent,
+            ensure_ascii=self.json_ensure_ascii,
+        )
+
+
+### DEPRECATED COMPATIBILITY
+### ============================================================================
+def __getattr__(name: str):
+    if name == "RESERVED_ATTRS":
+        warnings.warn(
+            "RESERVED_ATTRS has been moved to pythonjsonlogger.core",
+            DeprecationWarning,
+        )
+        return core.RESERVED_ATTRS
+    raise AttributeError(f"module {__name__} has no attribute {name}")
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/jsonlogger.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/jsonlogger.py
new file mode 100644
index 00000000..0b283b2d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/jsonlogger.py
@@ -0,0 +1,18 @@
+"""Stub module retained for compatibility.
+
+It retains access to old names whilst sending deprecation warnings.
+"""
+
+# pylint: disable=wrong-import-position,unused-import
+
+import warnings
+
+## Throw warning
+warnings.warn(
+    "pythonjsonlogger.jsonlogger has been moved to pythonjsonlogger.json",
+    DeprecationWarning,
+)
+
+## Import names
+from .json import JsonFormatter, JsonEncoder
+from .core import RESERVED_ATTRS
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/msgspec.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/msgspec.py
new file mode 100644
index 00000000..8646f85d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/msgspec.py
@@ -0,0 +1,63 @@
+"""JSON Formatter using [`msgspec`](https://github.com/jcrist/msgspec)"""
+
+### IMPORTS
+### ============================================================================
+## Future
+from __future__ import annotations
+
+## Standard Library
+from typing import Any
+
+## Installed
+
+## Application
+from . import core
+from . import defaults as d
+from .utils import package_is_available
+
+# We import msgspec after checking it is available
+package_is_available("msgspec", throw_error=True)
+import msgspec.json  # pylint: disable=wrong-import-position,wrong-import-order
+
+
+### FUNCTIONS
+### ============================================================================
+def msgspec_default(obj: Any) -> Any:
+    """msgspec default encoder function for non-standard types"""
+    if d.use_exception_default(obj):
+        return d.exception_default(obj)
+    if d.use_traceback_default(obj):
+        return d.traceback_default(obj)
+    if d.use_enum_default(obj):
+        return d.enum_default(obj)
+    if d.use_type_default(obj):
+        return d.type_default(obj)
+    return d.unknown_default(obj)
+
+
+### CLASSES
+### ============================================================================
+class MsgspecFormatter(core.BaseJsonFormatter):
+    """JSON formatter using [`msgspec.json.Encoder`](https://jcristharif.com/msgspec/api.html#msgspec.json.Encoder) for encoding."""
+
+    def __init__(
+        self,
+        *args,
+        json_default: core.OptionalCallableOrStr = msgspec_default,
+        **kwargs,
+    ) -> None:
+        """
+        Args:
+            args: see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]
+            json_default: a function for encoding non-standard objects
+            kwargs: see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]
+        """
+        super().__init__(*args, **kwargs)
+
+        self.json_default = core.str_to_object(json_default)
+        self._encoder = msgspec.json.Encoder(enc_hook=self.json_default)
+        return
+
+    def jsonify_log_record(self, log_record: core.LogRecord) -> str:
+        """Returns a json string of the log record."""
+        return self._encoder.encode(log_record).decode("utf8")
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/orjson.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/orjson.py
new file mode 100644
index 00000000..16db8426
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/orjson.py
@@ -0,0 +1,71 @@
+"""JSON Formatter using [orjson](https://github.com/ijl/orjson)"""
+
+### IMPORTS
+### ============================================================================
+## Future
+from __future__ import annotations
+
+## Standard Library
+from typing import Any
+
+## Installed
+
+## Application
+from . import core
+from . import defaults as d
+from .utils import package_is_available
+
+# We import msgspec after checking it is available
+package_is_available("orjson", throw_error=True)
+import orjson  # pylint: disable=wrong-import-position,wrong-import-order
+
+
+### FUNCTIONS
+### ============================================================================
+def orjson_default(obj: Any) -> Any:
+    """orjson default encoder function for non-standard types"""
+    if d.use_exception_default(obj):
+        return d.exception_default(obj)
+    if d.use_traceback_default(obj):
+        return d.traceback_default(obj)
+    if d.use_bytes_default(obj):
+        return d.bytes_default(obj)
+    if d.use_enum_default(obj):
+        return d.enum_default(obj)
+    if d.use_type_default(obj):
+        return d.type_default(obj)
+    return d.unknown_default(obj)
+
+
+### CLASSES
+### ============================================================================
+class OrjsonFormatter(core.BaseJsonFormatter):
+    """JSON formatter using [orjson](https://github.com/ijl/orjson) for encoding."""
+
+    def __init__(
+        self,
+        *args,
+        json_default: core.OptionalCallableOrStr = orjson_default,
+        json_indent: bool = False,
+        **kwargs,
+    ) -> None:
+        """
+        Args:
+            args: see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]
+            json_default: a function for encoding non-standard objects
+            json_indent: indent output with 2 spaces.
+            kwargs: see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]
+        """
+        super().__init__(*args, **kwargs)
+
+        self.json_default = core.str_to_object(json_default)
+        self.json_indent = json_indent
+        return
+
+    def jsonify_log_record(self, log_record: core.LogRecord) -> str:
+        """Returns a json string of the log record."""
+        opt = orjson.OPT_NON_STR_KEYS
+        if self.json_indent:
+            opt |= orjson.OPT_INDENT_2
+
+        return orjson.dumps(log_record, default=self.json_default, option=opt).decode("utf8")
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/py.typed b/.venv/lib/python3.12/site-packages/pythonjsonlogger/py.typed
new file mode 100644
index 00000000..89afa56d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/py.typed
@@ -0,0 +1 @@
+# PEP-561 marker. https://mypy.readthedocs.io/en/latest/installed_packages.html
diff --git a/.venv/lib/python3.12/site-packages/pythonjsonlogger/utils.py b/.venv/lib/python3.12/site-packages/pythonjsonlogger/utils.py
new file mode 100644
index 00000000..d810a130
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pythonjsonlogger/utils.py
@@ -0,0 +1,40 @@
+"""Utilities for Python JSON Logger"""
+
+### IMPORTS
+### ============================================================================
+## Future
+from __future__ import annotations
+
+## Standard Library
+import importlib.util
+
+## Installed
+
+## Application
+from .exception import MissingPackageError
+
+
+### FUNCTIONS
+### ============================================================================
+def package_is_available(
+    name: str, *, throw_error: bool = False, extras_name: str | None = None
+) -> bool:
+    """Determine if the given package is available for import.
+
+    Args:
+        name: Import name of the package to check.
+        throw_error: Throw an error if the package is unavailable.
+        extras_name: Extra dependency name to use in `throw_error`'s message.
+
+    Raises:
+        MissingPackageError: When `throw_error` is `True` and the return value would be `False`
+
+    Returns:
+        If the package is available for import.
+    """
+    available = importlib.util.find_spec(name) is not None
+
+    if not available and throw_error:
+        raise MissingPackageError(name, extras_name)
+
+    return available