Source code for gjson

"""GJSON module."""
import json
import os
import re
from importlib.metadata import PackageNotFoundError, version
from typing import Any

from gjson._gjson import MODIFIER_NAME_RESERVED_CHARS, GJSONObj
from gjson._protocols import ModifierProtocol
from gjson.exceptions import GJSONError, GJSONParseError

# Explicit export of modules for the import * syntax, custom order to force the documentation order
__all__ = ['GJSON', 'GJSONError', 'GJSONObj', 'GJSONParseError', 'ModifierProtocol', '__version__', 'get']


# TODO: use a proper type hint for obj once https://github.com/python/typing/issues/182 will be fixed
[docs] def get(obj: Any, query: str, *, as_str: bool = False, quiet: bool = False) -> Any: """Quick accessor to GJSON functionalities exposed for simplicity of use. Examples: Import and directly use this quick helper for the simpler usage:: >>> import gjson >>> data = {'items': [{'name': 'a', 'size': 1}, {'name': 'b', 'size': 2}]} >>> gjson.get(data, 'items.#.size') [1, 2] Arguments: obj: the object to query. It must be accessible in JSON-like fashion so it must be an object that can be converted to JSON. query: the query string to evaluate to extract the data from the object. as_str: if set to :py:data:`True` returns a JSON-encoded string, a Python object otherwise. quiet: on error, if set to :py:data:`True`, will raises an GJSONError exception. Otherwise returns :py:data:`None` on error. Return: the resulting object. """ gjson_obj = GJSON(obj) if as_str: return gjson_obj.getj(query, quiet=quiet) return gjson_obj.get(query, quiet=quiet)
[docs] class GJSON: """The GJSON class to operate on JSON-like objects.""" def __init__(self, obj: Any): """Initialize the instance with the given object. Examples: Use the :py:class:`gjson.GJSON` class for more complex usage or to perform multiple queries on the same object:: >>> import gjson >>> data = {'items': [{'name': 'a', 'size': 1}, {'name': 'b', 'size': 2}]} >>> gjson_obj = gjson.GJSON(data) Arguments: obj: the object to query. """ self._obj = obj self._custom_modifiers: dict[str, ModifierProtocol] = {}
[docs] def __str__(self) -> str: """Return the current object as a JSON-encoded string. Examples: Converting to string a :py:class:`gjson.GJSON` object returns it as a JSON-encoded string:: >>> str(gjson_obj) '{"items": [{"name": "a", "size": 1}, {"name": "b", "size": 2}]}' Returns: the JSON-encoded string representing the instantiated object. """ return json.dumps(self._obj, ensure_ascii=False)
[docs] def get(self, query: str, *, quiet: bool = False) -> Any: """Perform a query on the instantiated object and return the resulting object. Examples: Perform a query and get the resulting object:: >>> gjson_obj.get('items.#.size') [1, 2] Arguments: query: the GJSON query to apply to the object. quiet: wheter to raise a :py:class:`gjson.GJSONError` exception on error or just return :py:data:`None` in case of error. Raises: gjson.GJSONError: on error if the quiet parameter is not :py:data:`True`. Returns: the resulting object or :py:data:`None` if the ``quiet`` parameter is :py:data:`True` and there was an error. """ try: return GJSONObj(self._obj, query, custom_modifiers=self._custom_modifiers).get() except GJSONError: if quiet: return None raise
[docs] def getj(self, query: str, *, quiet: bool = False) -> str: """Perform a query on the instantiated object and return the resulting object as JSON-encoded string. Examples: Perform a query and get the resulting object as a JSON-encoded string:: >>> gjson_obj.getj('items.#.size') '[1, 2]' Arguments: query: the GJSON query to apply to the object. quiet: wheter to raise a :py:class:`gjson.GJSONError` exception on error or just return :py:data:`None` in case of error. Raises: gjson.GJSONError: on error if the quiet parameter is not :py:data:`True`. Returns: the JSON-encoded string representing the resulting object or :py:data:`None` if the ``quiet`` parameter is :py:data:`True` and there was an error. """ try: return str(GJSONObj(self._obj, query, custom_modifiers=self._custom_modifiers)) except GJSONError: if quiet: return '' raise
[docs] def get_gjson(self, query: str, *, quiet: bool = False) -> 'GJSON': """Perform a query on the instantiated object and return the resulting object as a GJSON instance. Examples: Perform a query and get the resulting object already encapsulated into a :py:class:`gjson.GJSON` object:: >>> sizes = gjson_obj.get_gjson('items.#.size') >>> str(sizes) '[1, 2]' >>> sizes.get('0') 1 Arguments: query: the GJSON query to apply to the object. quiet: wheter to raise a :py:class:`gjson.GJSONError` exception on error or just return :py:data:`None` in case of error. Raises: gjson.GJSONError: on error if the quiet parameter is not :py:data:`True`. Returns: the resulting object encapsulated as a :py:class:`gjson.GJSON` object or :py:data:`None` if the ``quiet`` parameter is :py:data:`True` and there was an error. """ return GJSON(self.get(query, quiet=quiet))
[docs] def register_modifier(self, name: str, func: ModifierProtocol) -> None: """Register a custom modifier. Examples: Register a custom modifier that sums all the numbers in a list: >>> def custom_sum(options, obj, *, last): ... # insert sanity checks code here ... return sum(obj) ... >>> gjson_obj.register_modifier('sum', custom_sum) >>> gjson_obj.get('items.#.size.@sum') 3 Arguments: name: the modifier name. It will be called where ``@name`` is used in the query. If two custom modifiers are registered with the same name the last one will be used. func: the modifier code in the form of a callable object that adhere to the :py:class:`gjson.ModifierProtocol`. Raises: gjson.GJSONError: if the provided callable doesn't adhere to the :py:class:`gjson.ModifierProtocol`. """ # Escape the ] as they are inside a [...] block not_allowed_regex = ''.join(MODIFIER_NAME_RESERVED_CHARS).replace(']', r'\]') if re.search(fr'[{not_allowed_regex}]', name): not_allowed_string = ', '.join(f'`{i}`' for i in MODIFIER_NAME_RESERVED_CHARS) raise GJSONError(f'Unable to register modifier `{name}`, contains at least one not allowed character: ' f'{not_allowed_string}') if name in GJSONObj.builtin_modifiers(): raise GJSONError(f'Unable to register a modifier with the same name of the built-in modifier: @{name}.') if not isinstance(func, ModifierProtocol): raise GJSONError(f'The given func "{func}" for the custom modifier @{name} does not adhere ' 'to the gjson.ModifierProtocol.') self._custom_modifiers[name] = func
try: __version__: str = version('gjson') """str: the version of the current gjson module.""" except PackageNotFoundError: # pragma: no cover - this should never happen during tests # Read the override from the environment, if present (like inside Debian build system) if 'SETUPTOOLS_SCM_PRETEND_VERSION' in os.environ: __version__ = os.environ['SETUPTOOLS_SCM_PRETEND_VERSION']