File: __init__.py

package info (click to toggle)
python-gjson 1.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 348 kB
  • sloc: python: 2,034; makefile: 20
file content (215 lines) | stat: -rw-r--r-- 8,376 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""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
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)


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] = {}

    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)

    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

    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

    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))

    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']