Source code for audible.json_provider.ujson_provider

"""ujson provider using the ujson library.

This module implements the JSON protocol using ujson, a C-based JSON library
that provides excellent performance while maintaining good compatibility with
stdlib json.

ujson characteristics:
- 2-3x faster than stdlib for serialization/deserialization
- Native support for custom indentation (including indent=4)
- Good compatibility with stdlib json API
- No native separators support (uses default: ', ' and ': ')

This is the preferred fallback provider for orjson when indent=4 is needed.
"""

from __future__ import annotations

import json
import logging
from typing import Any

from .exceptions import JSONDecodeError, JSONEncodeError


# Optional import - only available if ujson is installed
try:
    import ujson

    UJSON_AVAILABLE = True
except ImportError:
    UJSON_AVAILABLE = False


logger = logging.getLogger("audible.json_provider.ujson")


[docs] class UjsonProvider: r"""JSON provider using the ujson library. This provider implements JSON operations using ujson, a C-based library that offers excellent performance. It supports custom indentation natively, making it an ideal fallback for orjson when indent=4 is required. Performance characteristics: - 2-3x faster than stdlib (C implementation) - Native indent support (any positive integer) - No separators customization (uses defaults) - Excellent for pretty-printed output Limitations: - No custom separators support - Falls back to stdlib for separators Raises: ImportError: If ujson library is not installed. Example: >>> from audible.json_provider import get_json_provider, UjsonProvider >>> provider = get_json_provider(UjsonProvider) >>> provider.provider_name 'ujson' >>> print(provider.dumps({"key": "value"}, indent=4)) { "key": "value" } """ def __init__(self) -> None: if not UJSON_AVAILABLE: raise ImportError( "ujson is not installed. Install with: pip install " "audible[ujson] or audible[json-full] for complete coverage." )
[docs] def dumps( self, obj: Any, *, indent: int | None = None, separators: tuple[str, str] | None = None, ensure_ascii: bool = True, ) -> str: """Serialize obj to JSON string using ujson. Args: obj: Python object to serialize. indent: Number of spaces for indentation. separators: If specified, falls back to stdlib (ujson doesn't support this). ensure_ascii: If True, escape non-ASCII characters. Returns: JSON string representation. Raises: JSONEncodeError: If obj cannot be serialized to JSON. Note: Falls back to stdlib json if separators are specified, as ujson doesn't support custom separators. """ # Fallback to stdlib for separators (ujson doesn't support this) if separators is not None: logger.debug("ujson -> stdlib fallback (separators not supported)") try: return json.dumps( obj, indent=indent, separators=separators, ensure_ascii=ensure_ascii, ) except (TypeError, ValueError) as e: raise JSONEncodeError(f"Object not JSON serializable: {e}") from e # ujson native handling try: return ujson.dumps( obj, indent=indent if indent is not None else 0, ensure_ascii=ensure_ascii, ) except (TypeError, ValueError, OverflowError) as e: raise JSONEncodeError(f"Object not JSON serializable: {e}") from e
[docs] def loads(self, s: str | bytes) -> Any: """Deserialize JSON string using ujson. Args: s: JSON string or bytes to deserialize. Returns: Deserialized Python object. Raises: JSONDecodeError: If s contains invalid JSON or invalid UTF-8. """ if isinstance(s, bytes): try: s = s.decode("utf-8") except UnicodeDecodeError as e: raise JSONDecodeError(f"Invalid UTF-8 in JSON bytes: {e}") from e try: return ujson.loads(s) except (ujson.JSONDecodeError, ValueError) as e: raise JSONDecodeError(str(e)) from e
@property def provider_name(self) -> str: """Return provider name.""" return "ujson"
__all__ = ["UJSON_AVAILABLE", "UjsonProvider"]