compress-json 1.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ """Compress JSON is a package to handle reading and writing of compressed JSON documents."""
2
+
3
+ from compress_json.compress_json import (
4
+ load,
5
+ dump,
6
+ local_load,
7
+ local_dump,
8
+ get_supported_extensions,
9
+ )
10
+
11
+ __all__ = ["load", "dump", "local_load", "local_dump", "get_supported_extensions"]
@@ -0,0 +1,3 @@
1
+ """Current version of package compress_json."""
2
+
3
+ __version__ = "1.1.2"
@@ -0,0 +1,316 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A thin wrapper of standard ``json`` with standard compression libraries.
4
+ """
5
+ import json
6
+ from typing import Dict, Any, Optional, List
7
+ import traceback
8
+ import os
9
+
10
+ __all__ = ["dump", "load", "local_dump", "local_load"]
11
+
12
+ _DEFAULT_EXTENSION_MAP = {
13
+ "json": "json",
14
+ "gz": "gzip",
15
+ "bz": "bz2",
16
+ "lzma": "lzma",
17
+ "xz": "lzma",
18
+ }
19
+
20
+ _DEFAULT_COMPRESSION_WRITE_MODES = {
21
+ "json": "w",
22
+ "gzip": "wt",
23
+ "bz2": "wt",
24
+ "lzma": "wt",
25
+ }
26
+
27
+ _DEFAULT_COMPRESSION_READ_MODES = {"json": "r", "gzip": "rt", "bz2": "rt", "lzma": "rt"}
28
+
29
+ _CACHE: Dict = {}
30
+
31
+
32
+ def get_supported_extensions() -> List[str]:
33
+ """Return the supported extensions for compression algorithms."""
34
+ return list(_DEFAULT_EXTENSION_MAP.keys())
35
+
36
+
37
+ def get_compression_write_mode(compression: str) -> str:
38
+ """Return mode for opening file buffer for writing.
39
+
40
+ Parameters
41
+ -------------------
42
+ compression: str
43
+ The extension of the compression to be used.
44
+
45
+ Returns
46
+ -------------------
47
+ The code to use for opening the file in write mode.
48
+ """
49
+ return _DEFAULT_COMPRESSION_WRITE_MODES[compression]
50
+
51
+
52
+ def get_compression_read_mode(compression: str) -> str:
53
+ """Return mode for opening file buffer for reading.
54
+
55
+ Parameters
56
+ -------------------
57
+ compression: str
58
+ The extension of the compression to be used.
59
+
60
+ Returns
61
+ -------------------
62
+ The code to use for opening the file in read mode.
63
+ """
64
+ return _DEFAULT_COMPRESSION_READ_MODES[compression]
65
+
66
+
67
+ def infer_compression_from_filename(filename: str) -> str:
68
+ """Return the compression protocal inferred from given filename.
69
+
70
+ Parameters
71
+ ----------
72
+ filename: str
73
+ The filename for which to infer the compression protocol
74
+ """
75
+ return _DEFAULT_EXTENSION_MAP[filename.split(".")[-1]]
76
+
77
+
78
+ def dump(
79
+ obj: Any,
80
+ path: str,
81
+ compression: Optional[str] = None,
82
+ compression_kwargs: Optional[Dict] = None,
83
+ json_kwargs: Optional[Dict] = None,
84
+ encoding: str = "utf-8",
85
+ ):
86
+ """Dump the contents of an object to disk as json using the detected compression protocol.
87
+
88
+ Parameters
89
+ ----------------
90
+ obj: any
91
+ The object that will be saved to disk
92
+ path: str
93
+ The path to the file to which to dump ``obj``
94
+ compression: Optional[str] = None
95
+ The compression protocol to use. If None, it will be inferred from the file extension.
96
+ compression_kwargs: Optional[Dict] = None
97
+ Keywords argument to pass to the compressed file opening protocol.
98
+ json_kwargs: Optional[Dict] = None
99
+ Keywords argument to pass to the json file opening protocol.
100
+ encoding: str = "utf-8"
101
+ The encoding to use to dump the document. By default, UTF8.
102
+
103
+ Raises
104
+ ----------------
105
+ ValueError
106
+ If given path is not a valid string.
107
+ """
108
+ if not isinstance(path, str):
109
+ if isinstance(obj, str):
110
+ raise ValueError(
111
+ (
112
+ "The object you have provided to the dump method is a string "
113
+ "while the object you have provided as a path is NOT a string "
114
+ f"but an object of type {type(path)}. Maybe you need to swap them?"
115
+ )
116
+ )
117
+ raise ValueError("The given path is not a string.")
118
+
119
+ compression_kwargs = {} if compression_kwargs is None else compression_kwargs
120
+ json_kwargs = {} if json_kwargs is None else json_kwargs
121
+ compression = (
122
+ infer_compression_from_filename(path) if compression is None else compression
123
+ )
124
+ mode = get_compression_write_mode(compression)
125
+
126
+ directory = os.path.dirname(path)
127
+ if directory:
128
+ os.makedirs(directory, exist_ok=True)
129
+
130
+ if compression is None or compression == "json":
131
+ fout = open(path, mode=mode, encoding=encoding, **compression_kwargs)
132
+ elif compression == "gzip":
133
+ import gzip # pylint: disable=import-outside-toplevel
134
+
135
+ fout = gzip.open(path, mode=mode, encoding=encoding, **compression_kwargs) # type: ignore
136
+ elif compression == "bz2":
137
+ import bz2 # pylint: disable=import-outside-toplevel
138
+
139
+ fout = bz2.open(path, mode=mode, encoding=encoding, **compression_kwargs) # type: ignore
140
+ elif compression == "lzma":
141
+ import lzma # pylint: disable=import-outside-toplevel
142
+
143
+ fout = lzma.open(path, mode=mode, encoding=encoding, **compression_kwargs) # type: ignore
144
+ else:
145
+ raise ValueError(f"Unsupported compression protocol: {compression}")
146
+
147
+ with fout:
148
+ json.dump(obj, fout, **json_kwargs)
149
+
150
+
151
+ def load(
152
+ path: str,
153
+ compression: Optional[str] = None,
154
+ compression_kwargs: Optional[Dict] = None,
155
+ json_kwargs: Optional[Dict] = None,
156
+ encoding: str = "utf-8",
157
+ use_cache: bool = False,
158
+ ):
159
+ """Return json object at given path uncompressed with detected compression protocol.
160
+
161
+ Parameters
162
+ ----------
163
+ path: str
164
+ The path to the file from which to load the ``obj``
165
+ compression: Optional[str] = None
166
+ The compression protocol to use. If None, it will be inferred from the file extension.
167
+ compression_kwargs: Optional[Dict] = None
168
+ Keywords argument to pass to the compressed file opening protocol.
169
+ json_kwargs: Optional[Dict] = None
170
+ Keywords argument to pass to the json file opening protocol.
171
+ encoding: str = "utf-8"
172
+ The encoding to use to load the document. By default, UTF8.
173
+ use_cache: bool = False
174
+ Whether to put loaded JSON files in a static cache object.
175
+
176
+ Raises
177
+ ----------------
178
+ ValueError
179
+ If given path is not a valid string.
180
+ """
181
+ if not isinstance(path, str):
182
+ raise ValueError("The given path is not a string.")
183
+
184
+ if use_cache and path in _CACHE:
185
+ return _CACHE[path]
186
+
187
+ compression_kwargs = {} if compression_kwargs is None else compression_kwargs
188
+ json_kwargs = {} if json_kwargs is None else json_kwargs
189
+ compression = (
190
+ infer_compression_from_filename(path) if compression is None else compression
191
+ )
192
+ mode = get_compression_read_mode(compression)
193
+
194
+ if compression is None or compression == "json":
195
+ file = open(path, mode=mode, encoding=encoding, **compression_kwargs)
196
+ elif compression == "gzip":
197
+ import gzip # pylint: disable=import-outside-toplevel
198
+
199
+ file = gzip.open(path, mode=mode, encoding=encoding, **compression_kwargs) # type: ignore
200
+ elif compression == "bz2":
201
+ import bz2 # pylint: disable=import-outside-toplevel
202
+
203
+ file = bz2.open(path, mode=mode, encoding=encoding, **compression_kwargs)
204
+ elif compression == "lzma":
205
+ import lzma # pylint: disable=import-outside-toplevel
206
+
207
+ file = lzma.open(path, mode=mode, encoding=encoding, **compression_kwargs)
208
+ else:
209
+ raise ValueError(f"Unsupported compression protocol: {compression}")
210
+
211
+ with file:
212
+ json_content = json.load(file, **json_kwargs)
213
+
214
+ if use_cache:
215
+ _CACHE[path] = json_content
216
+
217
+ return json_content
218
+
219
+
220
+ def local_path(relative_path: str) -> str:
221
+ """Return path localized to caller function.
222
+
223
+ Parameters
224
+ -----------------------
225
+ relative_path: str
226
+ The path to be made absolute to the caller function.
227
+
228
+ Returns
229
+ -----------------------
230
+ The absolute path with as root the caller function.
231
+ """
232
+ return os.path.join(
233
+ os.path.dirname(os.path.abspath(traceback.extract_stack()[-3].filename)),
234
+ relative_path,
235
+ )
236
+
237
+
238
+ def local_load(
239
+ path: str,
240
+ compression: Optional[str] = None,
241
+ compression_kwargs: Optional[Dict] = None,
242
+ json_kwargs: Optional[Dict] = None,
243
+ encoding: str = "utf-8",
244
+ use_cache: bool = False,
245
+ ) -> Any:
246
+ """Return json object at given local path uncompressed with detected compression protocol.
247
+
248
+ Parameters
249
+ ----------
250
+ path: str
251
+ The path to the local file from which to load the ``obj``
252
+ compression: Optional[str] = None
253
+ The compression protocol to use. If None, it will be inferred from the file extension.
254
+ compression_kwargs: Optional[Dict] = None
255
+ keywords argument to pass to the compressed file opening protocol.
256
+ json_kwargs: Optional[Dict] = None
257
+ keywords argument to pass to the json file opening protocol.
258
+ encoding: str = "utf-8"
259
+ The encoding to use to load the document. By default, UTF8.
260
+ use_cache: bool = False
261
+ Whether to put loaded JSON files in a static cache object.
262
+
263
+ Raises
264
+ ----------------
265
+ ValueError,
266
+ If given path is not a valid string.
267
+ """
268
+ return load(
269
+ path=local_path(path),
270
+ compression=compression,
271
+ compression_kwargs=compression_kwargs,
272
+ json_kwargs=json_kwargs,
273
+ encoding=encoding,
274
+ use_cache=use_cache,
275
+ )
276
+
277
+
278
+ def local_dump(
279
+ obj: Any,
280
+ path: str,
281
+ compression: Optional[str] = None,
282
+ compression_kwargs: Optional[Dict] = None,
283
+ json_kwargs: Optional[Dict] = None,
284
+ encoding: str = "utf-8",
285
+ ):
286
+ """Dump the contents of an object to disk as json, using the detected compression protocol.
287
+
288
+ Parameters
289
+ ----------
290
+ obj: Any
291
+ The object that will be saved to disk
292
+ compression: Optional[str] = None
293
+ The compression protocol to use. If None, it will be inferred from the file extension.
294
+ path: str
295
+ The local path to the file to which to dump ``obj``
296
+ compression_kwargs: Optional[Dict] = None
297
+ keywords argument to pass to the compressed file opening protocol.
298
+ json_kwargs: Optional[Dict] = None
299
+ keywords argument to pass to the json file opening protocol.
300
+ encoding: str = "utf-8"
301
+ The encoding to use to dump the document. By default, UTF8.
302
+
303
+
304
+ Raises
305
+ ----------------
306
+ ValueError
307
+ If given path is not a valid string.
308
+ """
309
+ dump(
310
+ obj,
311
+ path=local_path(path),
312
+ compression=compression,
313
+ compression_kwargs=compression_kwargs,
314
+ json_kwargs=json_kwargs,
315
+ encoding=encoding,
316
+ )
compress_json/py.typed ADDED
File without changes
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: compress_json
3
+ Version: 1.1.2
4
+ Summary: The missing Python utility to read and write large compressed JSONs.
5
+ Author-email: Luca Cappelletti <cappelletti.luca94@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/LucaCappelletti94/compress_json
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Programming Language :: Python :: 3
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest; extra == "test"
14
+ Requires-Dist: pytest-readme; extra == "test"
15
+ Requires-Dist: validate_version_code; extra == "test"
16
+ Requires-Dist: random_dict; extra == "test"
17
+ Requires-Dist: dict_hash; extra == "test"
18
+ Dynamic: license-file
19
+
20
+ # compress_json
21
+
22
+ [![pip](https://badge.fury.io/py/compress-json.svg)](https://pypi.org/project/compress-json/)
23
+ [![python](https://img.shields.io/pypi/pyversions/compress-json.svg)](https://pypi.org/project/compress-json/)
24
+ [![license](https://img.shields.io/pypi/l/compress-json.svg)](https://pypi.org/project/compress-json/)
25
+ [![downloads](https://pepy.tech/badge/compress-json)](https://pepy.tech/project/compress-json)
26
+ [![Github Actions](https://github.com/LucaCappelletti94/compress_json/actions/workflows/python.yml/badge.svg)](https://github.com/LucaCappelletti94/compress_json/actions/workflows/python.yml)
27
+
28
+ The missing Python utility to read and write compressed JSONs.
29
+
30
+ The library is loosely based on the [compress_pickle](https://github.com/lucianopaz/compress_pickle) library.
31
+
32
+ ## How do I install this package?
33
+
34
+ As usual, just download it using pip:
35
+
36
+ ```shell
37
+ pip install compress_json
38
+ ```
39
+
40
+ ## Available compression modes
41
+
42
+ The compression modes, detected automatically by the file name, are **gzip**, **bz2**, and **lzma** (or **xz**), with the notable exception of **zip** which seems difficult to integrate into the JSON pipeline.
43
+
44
+ ## Usage example
45
+
46
+ The library is extremely easy to use:
47
+
48
+ ```python
49
+ import compress_json
50
+
51
+ D = {
52
+ "A": {
53
+ "B": "C"
54
+ }
55
+ }
56
+ compress_json.dump(D, "filepath.json.gz") # for a gzip file
57
+ compress_json.dump(D, "filepath.json.bz") # for a bz2 file
58
+ compress_json.dump(D, "filepath.json.lzma") # for a lzma file
59
+ compress_json.dump(D, "filepath.json.xz") # for a lzma file
60
+
61
+ D1 = compress_json.load("filepath.json.gz") # for loading a gzip file
62
+ D2 = compress_json.load("filepath.json.bz") # for loading a bz2 file
63
+ D3 = compress_json.load("filepath.json.lzma") # for loading a lzma file
64
+ D3 = compress_json.load("filepath.json.xz") # for loading a lzma file
65
+ ```
66
+
67
+ If it happens that you have to load or dump a JSON object with a custom extension, you can specify the compression mode by passing the `compression` parameter to the `load` and `dump` methods:
68
+
69
+ ```python
70
+ import compress_json
71
+
72
+ D = {
73
+ "A": {
74
+ "B": "C"
75
+ }
76
+ }
77
+
78
+ compress_json.dump(D, "filepath.custom_extension1", compression="gzip") # for a gzip file
79
+ compress_json.dump(D, "filepath.custom_extension2", compression="bz2") # for a bz2 file
80
+ compress_json.dump(D, "filepath.custom_extension3", compression="lzma") # for a lzma file
81
+
82
+ D1 = compress_json.load("filepath.custom_extension1", compression="gzip") # for loading a gzip file
83
+ D2 = compress_json.load("filepath.custom_extension2", compression="bz2") # for loading a bz2 file
84
+ D3 = compress_json.load("filepath.custom_extension3", compression="lzma") # for loading a lzma file
85
+
86
+ assert D == D1 == D2 == D3
87
+ ```
88
+
89
+ ## Some extra perks: local loading and dumping
90
+
91
+ The library makes available, other than the usual `load` and `dump` from the JSON library, the methods `local_load` and `local_dump`, which let you load and dump files in the same directory as wherever you are calling them, by using the call stack.
92
+
93
+ This can be useful, especially when loading files within packages.
94
+
95
+ ```python
96
+ import compress_json
97
+
98
+ D = {
99
+ "A": {
100
+ "B": "C"
101
+ }
102
+ }
103
+ compress_json.local_dump(D, "filepath.json.gz") # for a gzip file
104
+ compress_json.local_dump(D, "filepath.json.bz") # for a bz2 file
105
+ compress_json.local_dump(D, "filepath.json.lzma") # for a lzma file
106
+
107
+ D1 = compress_json.local_load("filepath.json.gz") # for loading a gzip file
108
+ D2 = compress_json.local_load("filepath.json.bz") # for loading a bz2 file
109
+ D3 = compress_json.local_load("filepath.json.lzma") # for loading a lzma file
110
+
111
+ assert D == D1 == D2 == D3
112
+ ```
113
+
114
+ ## Loading with RAM cache
115
+
116
+ Sometimes you need to load a compressed JSON file a LOT of times, and you may want to put this document in a cache or something of the sort. Fortunately, we already provide this option for you:
117
+
118
+ ```python
119
+ import compress_json
120
+
121
+ # The first time you load the file, it will be cached in RAM
122
+ D1 = compress_json.load(
123
+ "filepath.json.gz",
124
+ use_cache=True
125
+ )
126
+
127
+ # The second time you load the file, it will be loaded from the cache
128
+ D2 = compress_json.local_load(
129
+ "filepath.json.gz",
130
+ use_cache=True
131
+ )
132
+
133
+ assert D1 == D2
134
+ ```
135
+
136
+ ## Advanced usage
137
+
138
+ You can pass parameters to either the chosen compression mode or the JSON library.
139
+
140
+ With the `json_kwargs` parameter, you can specify any of the kwargs that should be forwarded to the JSON library method, which you can obtain for your Python version by running `help(json.dump)` and `help(json.load)`, depending on whether you are dumping or loading the JSON object.
141
+
142
+ Similarly, with the `compression_kwargs` parameter, you can specify any parameter that has to be forwarded to the compression library that you intend to use, whether that is `lzma`, `gzip`, or `bz2`, and as per JSON will depend on which version you have installed.
143
+
144
+ Whether you are dumping or loading a compressed JSON object, you can get the list of parameters you have available to forward to the compression method by running `help(lzma.open)`, `help(gzip.open)`, or `help(bz2.open)`, respectively.
145
+
146
+ ```python
147
+ import compress_json
148
+
149
+ D = {
150
+ "A": {
151
+ "B": "C"
152
+ }
153
+ }
154
+ compress_json.dump(
155
+ D, "filepath.json.gz",
156
+ compression_kwargs={
157
+ "compresslevel": 9 # The kwargs for gzip
158
+ },
159
+ json_kwargs={
160
+ "indent": 4 # The kwargs for json
161
+ }
162
+ )
163
+
164
+ D4 = compress_json.load(
165
+ "filepath.json.gz",
166
+ compression_kwargs={
167
+ "compresslevel": 9 # The kwargs for gzip
168
+ },
169
+ json_kwargs={} # The kwargs for json
170
+ )
171
+
172
+ assert D == D4
173
+ ```
174
+
175
+ ## Building
176
+
177
+ Run `uv build` to create the source distribution and wheel.
178
+
179
+ ## Releasing
180
+
181
+ Set a new version in `compress_json/__version__.py` and publish a GitHub release with a matching tag, optionally prefixed with `v`. The `publish.yml` workflow tests, builds, and publishes the source distribution and wheel through the `pypi` environment using PyPI Trusted Publishing.
182
+
183
+ ## License
184
+ The library is released under the MIT license.
@@ -0,0 +1,9 @@
1
+ compress_json/__init__.py,sha256=fhtJ3CZ40fPT343lz_h6K6KF9S2-wKfr4aA3HK1RQz8,304
2
+ compress_json/__version__.py,sha256=wn6hBWafD884d7BvsdlIOHVc27UQf_WvQ77c5UpE3tY,71
3
+ compress_json/compress_json.py,sha256=BvgbWOidcWvKRezFttMWSVJ9ZAJp1PsO2VY1rSkwLGs,10029
4
+ compress_json/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ compress_json-1.1.2.dist-info/licenses/LICENSE,sha256=kVjMPCxT2u-df_moY6ewQUZXHsUMDbmFxD0rNRCiV0A,1073
6
+ compress_json-1.1.2.dist-info/METADATA,sha256=8q1fQDaFvW5j0Yp93TywqyNSYiUsPimD5hklAoGs4No,6670
7
+ compress_json-1.1.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
8
+ compress_json-1.1.2.dist-info/top_level.txt,sha256=ZnXQYQDDh0upVcLx2g52dIBtii99Flgj5yin--JgmzI,14
9
+ compress_json-1.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Luca Cappelletti
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ compress_json