From 4a52a71956a8d46fcb7294ac71734504bb09bcc2 Mon Sep 17 00:00:00 2001 From: S. Solomon Darnell Date: Fri, 28 Mar 2025 21:52:21 -0500 Subject: two version of R2R are here --- .../site-packages/pythonjsonlogger/__init__.py | 17 + .../site-packages/pythonjsonlogger/core.py | 394 +++++++++++++++++++++ .../site-packages/pythonjsonlogger/defaults.py | 241 +++++++++++++ .../site-packages/pythonjsonlogger/exception.py | 27 ++ .../site-packages/pythonjsonlogger/json.py | 119 +++++++ .../site-packages/pythonjsonlogger/jsonlogger.py | 18 + .../site-packages/pythonjsonlogger/msgspec.py | 63 ++++ .../site-packages/pythonjsonlogger/orjson.py | 71 ++++ .../site-packages/pythonjsonlogger/py.typed | 1 + .../site-packages/pythonjsonlogger/utils.py | 40 +++ 10 files changed, 991 insertions(+) create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/__init__.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/core.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/defaults.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/exception.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/json.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/jsonlogger.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/msgspec.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/orjson.py create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/py.typed create mode 100644 .venv/lib/python3.12/site-packages/pythonjsonlogger/utils.py (limited to '.venv/lib/python3.12/site-packages/pythonjsonlogger') 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 -- cgit v1.2.3