vecorel-cli 0.3.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (85) hide show
  1. {vecorel_cli-0.3.0/vecorel_cli.egg-info → vecorel_cli-0.3.1}/PKG-INFO +2 -1
  2. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/pyproject.toml +12 -1
  3. vecorel_cli-0.3.1/tests/test_deflate64.py +68 -0
  4. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/conversion/base.py +3 -10
  5. vecorel_cli-0.3.1/vecorel_cli/conversion/deflate64.py +54 -0
  6. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1/vecorel_cli.egg-info}/PKG-INFO +2 -1
  7. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli.egg-info/SOURCES.txt +2 -0
  8. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli.egg-info/requires.txt +1 -0
  9. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/LICENSE +0 -0
  10. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/README.md +0 -0
  11. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/setup.cfg +0 -0
  12. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_convert.py +0 -0
  13. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_convert_duckdb.py +0 -0
  14. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_converters.py +0 -0
  15. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_create_geojson.py +0 -0
  16. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_create_geoparquet.py +0 -0
  17. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_create_stac.py +0 -0
  18. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_describe.py +0 -0
  19. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_download.py +0 -0
  20. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_encoding_auto.py +0 -0
  21. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_encoding_geojson.py +0 -0
  22. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_encoding_geoparquet.py +0 -0
  23. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_hilbert.py +0 -0
  24. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_improve.py +0 -0
  25. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_jsonschema.py +0 -0
  26. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_merge.py +0 -0
  27. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_ops.py +0 -0
  28. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_rename_extension.py +0 -0
  29. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_validate.py +0 -0
  30. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/tests/test_validate_schema.py +0 -0
  31. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/__init__.py +0 -0
  32. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/basecommand.py +0 -0
  33. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/cli/__init__.py +0 -0
  34. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/cli/logger.py +0 -0
  35. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/cli/options.py +0 -0
  36. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/cli/path_url.py +0 -0
  37. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/cli/setup.py +0 -0
  38. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/cli/util.py +0 -0
  39. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/const.py +0 -0
  40. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/conversion/__init__.py +0 -0
  41. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/conversion/admin.py +0 -0
  42. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/conversion/duckdb.py +0 -0
  43. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/conversion/flatdict.py +0 -0
  44. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/convert.py +0 -0
  45. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/converters.py +0 -0
  46. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/create_geojson.py +0 -0
  47. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/create_geoparquet.py +0 -0
  48. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/create_jsonschema.py +0 -0
  49. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/create_stac.py +0 -0
  50. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/datasets/__init__.py +0 -0
  51. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/datasets/template.py +0 -0
  52. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/describe.py +0 -0
  53. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/encoding/__init__.py +0 -0
  54. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/encoding/auto.py +0 -0
  55. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/encoding/base.py +0 -0
  56. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/encoding/geojson.py +0 -0
  57. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/encoding/geoparquet.py +0 -0
  58. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/improve.py +0 -0
  59. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/jsonschema/__init__.py +0 -0
  60. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/jsonschema/template.py +0 -0
  61. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/merge.py +0 -0
  62. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/parquet/__init__.py +0 -0
  63. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/parquet/geopandas.py +0 -0
  64. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/parquet/types.py +0 -0
  65. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/registry.py +0 -0
  66. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/rename_extension.py +0 -0
  67. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/validate.py +0 -0
  68. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/validate_schema.py +0 -0
  69. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/validation/__init__.py +0 -0
  70. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/validation/base.py +0 -0
  71. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/validation/data.py +0 -0
  72. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/validation/geojson.py +0 -0
  73. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/validation/geoparquet.py +0 -0
  74. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/__init__.py +0 -0
  75. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/collection.py +0 -0
  76. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/extensions.py +0 -0
  77. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/hilbert.py +0 -0
  78. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/ops.py +0 -0
  79. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/schemas.py +0 -0
  80. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/typing.py +0 -0
  81. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/util.py +0 -0
  82. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli/vecorel/version.py +0 -0
  83. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli.egg-info/dependency_links.txt +0 -0
  84. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli.egg-info/entry_points.txt +0 -0
  85. {vecorel_cli-0.3.0 → vecorel_cli-0.3.1}/vecorel_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vecorel-cli
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: CLI tools such as validation and file format conversion for vecorel.
5
5
  Author: Matthias Mohr
6
6
  License-Expression: Apache-2.0
@@ -24,6 +24,7 @@ Requires-Dist: shapely<3.0,>=2.1
24
24
  Requires-Dist: numpy<3.0,>=2.0
25
25
  Requires-Dist: pyarrow<24.0,>=21.0
26
26
  Requires-Dist: py7zr<2.0,>=1.0
27
+ Requires-Dist: inflate64<2.0,>=1.0
27
28
  Requires-Dist: multivolumefile<1.0,>=0.2.3
28
29
  Requires-Dist: fsspec>=2025.7.0
29
30
  Requires-Dist: jsonschema[format]<5.0,>=4.20
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vecorel-cli"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "CLI tools such as validation and file format conversion for vecorel."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -30,6 +30,7 @@ dependencies = [
30
30
  "numpy>=2.0,<3.0",
31
31
  "pyarrow>=21.0,<24.0",
32
32
  "py7zr>=1.0,<2.0",
33
+ "inflate64>=1.0,<2.0",
33
34
  "multivolumefile>=0.2.3,<1.0",
34
35
  "fsspec>=2025.7.0",
35
36
  "jsonschema[format]>=4.20,<5.0",
@@ -72,6 +73,7 @@ shapely = ">=2.1,<3.0"
72
73
  numpy = ">=2.0,<3.0"
73
74
  pyarrow = ">=21.0,<24.0"
74
75
  py7zr = ">=1.0,<2.0"
76
+ inflate64 = ">=1.0,<2.0"
75
77
  multivolumefile = ">=0.2.3,<1.0"
76
78
  unrar = ">=0.4"
77
79
 
@@ -89,6 +91,12 @@ GitPython = ">=3.0.0,<4.0.0"
89
91
  [tool.pixi.feature.dev.pypi-dependencies]
90
92
  build = ">=1.0.0,<2.0.0"
91
93
 
94
+ [tool.pixi.feature.py311.dependencies]
95
+ python = "3.11.*"
96
+
97
+ [tool.pixi.feature.py314.dependencies]
98
+ python = "3.14.*"
99
+
92
100
  [tool.pixi.feature.s3.dependencies]
93
101
  s3fs = ">=2025.7.0"
94
102
 
@@ -102,6 +110,9 @@ s3 = {features = ["s3"], solve-group = "default"}
102
110
  gcs = {features = ["gcs"], solve-group = "default"}
103
111
  cloud = {features = ["s3", "gcs"], solve-group = "default"}
104
112
  all = {features = ["dev", "s3", "gcs"], solve-group = "default"}
113
+ # CI tests the oldest and newest supported Python; pixi, not setup-python, decides the version
114
+ dev-py311 = {features = ["dev", "py311"], solve-group = "py311"}
115
+ dev-py314 = {features = ["dev", "py314"], solve-group = "py314"}
105
116
 
106
117
  [tool.pixi.tasks]
107
118
  # Development tasks
@@ -0,0 +1,68 @@
1
+ import random
2
+ import struct
3
+ import zipfile
4
+ import zlib
5
+
6
+ import inflate64
7
+ from pytest import mark, raises
8
+
9
+ from vecorel_cli.conversion.base import BaseConverter
10
+ from vecorel_cli.conversion.deflate64 import ZIP_DEFLATED64, deflate64_zip
11
+
12
+ # Compressible content fits one read; random bytes need many, which exercises the
13
+ # streaming decompressor protocol of ZipExtFile.
14
+ CONTENTS = {
15
+ "compressible": b"deflate64 " * 20_000,
16
+ "incompressible": random.Random(64).randbytes(2_000_000),
17
+ }
18
+
19
+
20
+ def _write_deflate64_zip(path, data, name="member.txt"):
21
+ """A single-member ZIP compressed with Deflate64, which zipfile cannot write."""
22
+ deflater = inflate64.Deflater()
23
+ compressed = deflater.deflate(data) + deflater.flush()
24
+ crc = zlib.crc32(data)
25
+ encoded = name.encode()
26
+ sizes = (crc, len(compressed), len(data), len(encoded))
27
+ local = struct.pack("<4s5H3L2H", b"PK\x03\x04", 21, 0, ZIP_DEFLATED64, 0, 0, *sizes, 0)
28
+ central = struct.pack(
29
+ "<4s6H3L5H2L", b"PK\x01\x02", 21, 21, 0, ZIP_DEFLATED64, 0, 0, *sizes, 0, 0, 0, 0, 0, 0
30
+ )
31
+ offset = len(local) + len(encoded) + len(compressed)
32
+ end = struct.pack("<4s4H2LH", b"PK\x05\x06", 0, 0, 1, 1, len(central) + len(encoded), offset, 0)
33
+ path.write_bytes(local + encoded + compressed + central + encoded + end)
34
+ return path
35
+
36
+
37
+ @mark.parametrize("kind", CONTENTS)
38
+ def test_zipfile_reads_deflate64_only_inside_the_block(tmp_path, kind):
39
+ archive = _write_deflate64_zip(tmp_path / "a.zip", CONTENTS[kind])
40
+ with raises(NotImplementedError):
41
+ zipfile.ZipFile(archive).read("member.txt")
42
+ with deflate64_zip():
43
+ # read() verifies the CRC, so a truncated or corrupt stream fails here
44
+ assert zipfile.ZipFile(archive).read("member.txt") == CONTENTS[kind]
45
+ with raises(NotImplementedError):
46
+ zipfile.ZipFile(archive).read("member.txt")
47
+
48
+
49
+ def test_overlapping_blocks_restore_zipfile_whatever_order_they_exit_in():
50
+ original = zipfile._get_decompressor
51
+ first, second = deflate64_zip(), deflate64_zip()
52
+ first.__enter__()
53
+ second.__enter__()
54
+ first.__exit__(None, None, None)
55
+ assert zipfile._get_decompressor is not original # the second block still needs it
56
+ second.__exit__(None, None, None)
57
+ assert zipfile._get_decompressor is original
58
+
59
+
60
+ @mark.parametrize("kind", CONTENTS)
61
+ def test_download_files_extracts_deflate64_archives(tmp_path, kind):
62
+ archive = _write_deflate64_zip(tmp_path / "source.zip", CONTENTS[kind])
63
+ paths = BaseConverter().download_files(
64
+ {str(archive): ["member.txt"]}, cache_folder=str(tmp_path / "cache")
65
+ )
66
+ assert len(paths) == 1
67
+ with open(paths[0][0], "rb") as f:
68
+ assert f.read() == CONTENTS[kind]
@@ -34,6 +34,7 @@ from ..vecorel.hilbert import hilbert_sort_geodataframe
34
34
  from ..vecorel.schemas import Schemas
35
35
  from ..vecorel.typing import Sources
36
36
  from ..vecorel.util import get_fs, name_from_uri, stream_file, suffix_duplicate_ids
37
+ from .deflate64 import deflate64_zip
37
38
  from .flatdict import FlatDict
38
39
 
39
40
  # a source column named `id` moves here when the converter generates its own id,
@@ -399,16 +400,8 @@ class BaseConverter(LoggerMixin):
399
400
 
400
401
  if must_extract:
401
402
  if zipfile.is_zipfile(cache_file):
402
- try:
403
- with zipfile.ZipFile(cache_file, "r") as zip_file:
404
- zip_file.extractall(zip_folder)
405
- except NotImplementedError as e:
406
- if str(e) != "That compression method is not supported":
407
- raise e
408
- import zipfile_deflate64
409
-
410
- with zipfile_deflate64.ZipFile(cache_file, "r") as zip_file:
411
- zip_file.extractall(zip_folder)
403
+ with deflate64_zip(), zipfile.ZipFile(cache_file, "r") as zip_file:
404
+ zip_file.extractall(zip_folder)
412
405
  elif py7zr.is_7zfile(cache_file):
413
406
  with py7zr.SevenZipFile(cache_file, "r") as sz_file:
414
407
  sz_file.extractall(zip_folder)
@@ -0,0 +1,54 @@
1
+ import threading
2
+ import zipfile
3
+ from contextlib import contextmanager
4
+
5
+ import inflate64
6
+
7
+ # Deflate64 ("enhanced deflate", ZIP method 9) is what Windows writes for large archives,
8
+ # and python's zipfile cannot read it. inflate64, a dependency of py7zr, decodes it.
9
+ ZIP_DEFLATED64 = 9
10
+
11
+ _lock = threading.Lock()
12
+ _users = 0
13
+ _original_get_decompressor = zipfile._get_decompressor
14
+
15
+
16
+ class _Deflate64Decompressor:
17
+ """The interface zipfile.ZipExtFile expects of a decompressor other than zlib's
18
+ (Python 3.11 to 3.14): decompress(data) and eof."""
19
+
20
+ def __init__(self):
21
+ self._inflater = inflate64.Inflater()
22
+
23
+ @property
24
+ def eof(self):
25
+ return self._inflater.eof
26
+
27
+ def decompress(self, data):
28
+ return self._inflater.inflate(data)
29
+
30
+
31
+ def _get_decompressor(compress_type):
32
+ if compress_type == ZIP_DEFLATED64:
33
+ return _Deflate64Decompressor()
34
+ return _original_get_decompressor(compress_type)
35
+
36
+
37
+ @contextmanager
38
+ def deflate64_zip():
39
+ """Let zipfile read Deflate64 members for the duration of the block.
40
+
41
+ The hook is process-global, so it is counted: installed by the first block to enter
42
+ and restored by the last to exit, whatever order overlapping blocks exit in."""
43
+ global _users
44
+ with _lock:
45
+ if _users == 0:
46
+ zipfile._get_decompressor = _get_decompressor
47
+ _users += 1
48
+ try:
49
+ yield
50
+ finally:
51
+ with _lock:
52
+ _users -= 1
53
+ if _users == 0:
54
+ zipfile._get_decompressor = _original_get_decompressor
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vecorel-cli
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: CLI tools such as validation and file format conversion for vecorel.
5
5
  Author: Matthias Mohr
6
6
  License-Expression: Apache-2.0
@@ -24,6 +24,7 @@ Requires-Dist: shapely<3.0,>=2.1
24
24
  Requires-Dist: numpy<3.0,>=2.0
25
25
  Requires-Dist: pyarrow<24.0,>=21.0
26
26
  Requires-Dist: py7zr<2.0,>=1.0
27
+ Requires-Dist: inflate64<2.0,>=1.0
27
28
  Requires-Dist: multivolumefile<1.0,>=0.2.3
28
29
  Requires-Dist: fsspec>=2025.7.0
29
30
  Requires-Dist: jsonschema[format]<5.0,>=4.20
@@ -7,6 +7,7 @@ tests/test_converters.py
7
7
  tests/test_create_geojson.py
8
8
  tests/test_create_geoparquet.py
9
9
  tests/test_create_stac.py
10
+ tests/test_deflate64.py
10
11
  tests/test_describe.py
11
12
  tests/test_download.py
12
13
  tests/test_encoding_auto.py
@@ -51,6 +52,7 @@ vecorel_cli/cli/util.py
51
52
  vecorel_cli/conversion/__init__.py
52
53
  vecorel_cli/conversion/admin.py
53
54
  vecorel_cli/conversion/base.py
55
+ vecorel_cli/conversion/deflate64.py
54
56
  vecorel_cli/conversion/duckdb.py
55
57
  vecorel_cli/conversion/flatdict.py
56
58
  vecorel_cli/datasets/__init__.py
@@ -6,6 +6,7 @@ shapely<3.0,>=2.1
6
6
  numpy<3.0,>=2.0
7
7
  pyarrow<24.0,>=21.0
8
8
  py7zr<2.0,>=1.0
9
+ inflate64<2.0,>=1.0
9
10
  multivolumefile<1.0,>=0.2.3
10
11
  fsspec>=2025.7.0
11
12
  jsonschema[format]<5.0,>=4.20
File without changes
File without changes
File without changes