FastWARC 1.0.0__tar.gz → 1.0.2__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 (58) hide show
  1. fastwarc-1.0.2/FastWARC.egg-info/PKG-INFO +110 -0
  2. {fastwarc-1.0.0 → fastwarc-1.0.2}/FastWARC.egg-info/SOURCES.txt +1 -1
  3. {fastwarc-1.0.0 → fastwarc-1.0.2}/FastWARC.egg-info/requires.txt +1 -1
  4. fastwarc-1.0.2/PKG-INFO +110 -0
  5. fastwarc-1.0.2/README.md +86 -0
  6. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/__init__.py +10 -13
  7. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/__init__.pyi +10 -2
  8. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/cli.py +115 -29
  9. fastwarc-1.0.2/fastwarc/legacy/__init__.pyi +19 -0
  10. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/_shims.py +3 -3
  11. fastwarc-1.0.2/fastwarc/stream_io.pyi +188 -0
  12. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/warc.pyi +114 -70
  13. {fastwarc-1.0.0 → fastwarc-1.0.2}/pyproject.toml +23 -18
  14. fastwarc-1.0.0/FastWARC.egg-info/PKG-INFO +0 -97
  15. fastwarc-1.0.0/PKG-INFO +0 -97
  16. fastwarc-1.0.0/README.md +0 -73
  17. fastwarc-1.0.0/fastwarc/_fastwarc.pyi +0 -35
  18. fastwarc-1.0.0/fastwarc/stream_io.pyi +0 -136
  19. {fastwarc-1.0.0 → fastwarc-1.0.2}/FastWARC.egg-info/dependency_links.txt +0 -0
  20. {fastwarc-1.0.0 → fastwarc-1.0.2}/FastWARC.egg-info/entry_points.txt +0 -0
  21. {fastwarc-1.0.0 → fastwarc-1.0.2}/FastWARC.egg-info/top_level.txt +0 -0
  22. {fastwarc-1.0.0 → fastwarc-1.0.2}/MANIFEST.in +0 -0
  23. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/__init__.pxd +0 -0
  24. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/__init__.py +0 -0
  25. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/stream_io.pxd +0 -0
  26. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/stream_io.pyi +0 -0
  27. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/stream_io.pyx +0 -0
  28. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/tools.pyi +0 -0
  29. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/tools.pyx +0 -0
  30. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/warc.pxd +0 -0
  31. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/warc.pyi +0 -0
  32. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/legacy/warc.pyx +0 -0
  33. {fastwarc-1.0.0 → fastwarc-1.0.2}/fastwarc/py.typed +0 -0
  34. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_common/__init__.pxd +0 -0
  35. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_common/string_util.pxd +0 -0
  36. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/__init__.pxd +0 -0
  37. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/algorithm.pxd +0 -0
  38. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/atomic.pxd +0 -0
  39. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/boost_regex.pxd +0 -0
  40. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/cctype.pxd +0 -0
  41. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/cstdlib.pxd +0 -0
  42. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/cstring.pxd +0 -0
  43. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/dlfcn.pxd +0 -0
  44. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/errno.pxd +0 -0
  45. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/lexbor.pxd +0 -0
  46. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/lz4frame.pxd +0 -0
  47. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/lz4hc.pxd +0 -0
  48. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/pthread.pxd +0 -0
  49. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/re2.pxd +0 -0
  50. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/stdio.pxd +0 -0
  51. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/string_view.pxd +0 -0
  52. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/time.pxd +0 -0
  53. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/uchardet.pxd +0 -0
  54. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/unistd.pxd +0 -0
  55. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/utility.pxd +0 -0
  56. {fastwarc-1.0.0 → fastwarc-1.0.2}/resiliparse_inc/zlib.pxd +0 -0
  57. {fastwarc-1.0.0 → fastwarc-1.0.2}/setup.cfg +0 -0
  58. {fastwarc-1.0.0 → fastwarc-1.0.2}/setup.py +0 -0
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: FastWARC
3
+ Version: 1.0.2
4
+ Summary: A high-performance WARC parsing library written in Rust with bindings for Python.
5
+ Author: Janek Bevendorff
6
+ License-Expression: Apache-2.0
7
+ Project-URL: repository, https://github.com/chatnoir-eu/chatnoir-resiliparse
8
+ Project-URL: homepage, https://resiliparse.chatnoir.eu
9
+ Project-URL: documentation, https://resiliparse.chatnoir.eu
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: brotli
13
+ Requires-Dist: click
14
+ Requires-Dist: tqdm
15
+ Provides-Extra: all
16
+ Requires-Dist: fastwarc[fsspec]; extra == "all"
17
+ Provides-Extra: fsspec
18
+ Requires-Dist: fsspec; extra == "fsspec"
19
+ Requires-Dist: s3fs; extra == "fsspec"
20
+ Provides-Extra: test
21
+ Requires-Dist: fastwarc[all]; extra == "test"
22
+ Requires-Dist: pytest>=7.2.2; extra == "test"
23
+ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
24
+
25
+ # FastWARC (Python)
26
+
27
+ FastWARC is a high-performance WARC parsing library written in Rust with bindings for Python.
28
+ FastWARC supports compressed and uncompressed WARC/1.0 and WARC/1.1 streams. Supported compression algorithms are Gzip,
29
+ Zstd, and LZ4.
30
+
31
+ This package contains the Python bindings for FastWARC.
32
+
33
+ FastWARC belongs to the [ChatNoir Resiliparse toolkit](https://github.com/chatnoir-eu/chatnoir-resiliparse/) for fast
34
+ and robust web data processing.
35
+
36
+ ## Installing FastWARC
37
+
38
+ Pre-built FastWARC binaries for Python can be installed from [PyPi](https://pypi.org/project/FastWARC/):
39
+
40
+ ```bash
41
+ pip install fastwarc
42
+ ```
43
+
44
+ ## Building Python Bindings From Source
45
+
46
+ To be able to build FastWARC, you need the Rust toolchain installed (see above). Then to build directly from the GitHub
47
+ repository, follow these steps:
48
+
49
+ ```bash
50
+ # Clone repository
51
+
52
+ git clone https://github.com/chatnoir-eu/chatnoir-resiliparse.git
53
+ cd chatnoir-resiliparse
54
+
55
+ # Install Rust toolchain (if not already installed)
56
+
57
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
58
+
59
+ # Create a fresh venv first (recommended)
60
+
61
+ python3 -m venv venv && source venv/bin/activate
62
+
63
+ # Option 1: Build and install in editable mode (best for development)
64
+
65
+ python3 -m pip install -e ./fastwarc-py
66
+
67
+ # Option 2 (alternative): Build and install wheels in separate steps (best for redistribution)
68
+
69
+ python3 -m pip wheel -w build ./fastwarc-py
70
+ ls ./build/*.whl | xargs python3 -m pip install
71
+ ```
72
+
73
+ ## Usage Instructions
74
+
75
+ For detailed usage instructions, please consult
76
+ the [FastWARC User Manual](https://resiliparse.chatnoir.eu/en/latest/man/fastwarc.html).
77
+
78
+ ## Cite Us
79
+
80
+ If you use FastWARC, please consider citing our [OSSYM 2021 abstract paper](https://arxiv.org/abs/2112.03103):
81
+
82
+ ```bibtex
83
+ @InProceedings{bevendorff:2021,
84
+ author = {Janek Bevendorff and Martin Potthast and Benno Stein},
85
+ booktitle = {3rd International Symposium on Open Search Technology (OSSYM 2021)},
86
+ editor = {Andreas Wagner and Christian Guetl and Michael Granitzer and Stefan Voigt},
87
+ month = oct,
88
+ publisher = {International Open Search Symposium},
89
+ site = {CERN, Geneva, Switzerland},
90
+ title = {{FastWARC: Optimizing Large-Scale Web Archive Analytics}},
91
+ year = 2021
92
+ }
93
+ ```
94
+
95
+ You can also cite our [ECIR 2018 demo paper](https://webis.de/downloads/publications/papers/bevendorff_2018.pdf):
96
+
97
+ ```bibtex
98
+ @InProceedings{bevendorff:2018,
99
+ address = {Berlin Heidelberg New York},
100
+ author = {Janek Bevendorff and Benno Stein and Matthias Hagen and Martin Potthast},
101
+ booktitle = {Advances in Information Retrieval. 40th European Conference on IR Research (ECIR 2018)},
102
+ editor = {Leif Azzopardi and Allan Hanbury and Gabriella Pasi and Benjamin Piwowarski},
103
+ month = mar,
104
+ publisher = {Springer},
105
+ series = {Lecture Notes in Computer Science},
106
+ site = {Grenoble, France},
107
+ title = {{Elastic ChatNoir: Search Engine for the ClueWeb and the Common Crawl}},
108
+ year = 2018
109
+ }
110
+ ```
@@ -10,13 +10,13 @@ FastWARC.egg-info/requires.txt
10
10
  FastWARC.egg-info/top_level.txt
11
11
  fastwarc/__init__.py
12
12
  fastwarc/__init__.pyi
13
- fastwarc/_fastwarc.pyi
14
13
  fastwarc/cli.py
15
14
  fastwarc/py.typed
16
15
  fastwarc/stream_io.pyi
17
16
  fastwarc/warc.pyi
18
17
  fastwarc/legacy/__init__.pxd
19
18
  fastwarc/legacy/__init__.py
19
+ fastwarc/legacy/__init__.pyi
20
20
  fastwarc/legacy/_shims.py
21
21
  fastwarc/legacy/stream_io.pxd
22
22
  fastwarc/legacy/stream_io.pyi
@@ -7,9 +7,9 @@ fastwarc[fsspec]
7
7
 
8
8
  [fsspec]
9
9
  fsspec
10
+ s3fs
10
11
 
11
12
  [test]
12
13
  fastwarc[all]
13
14
  pytest>=7.2.2
14
15
  pytest-cov>=4.0.0
15
- lz4
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: FastWARC
3
+ Version: 1.0.2
4
+ Summary: A high-performance WARC parsing library written in Rust with bindings for Python.
5
+ Author: Janek Bevendorff
6
+ License-Expression: Apache-2.0
7
+ Project-URL: repository, https://github.com/chatnoir-eu/chatnoir-resiliparse
8
+ Project-URL: homepage, https://resiliparse.chatnoir.eu
9
+ Project-URL: documentation, https://resiliparse.chatnoir.eu
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: brotli
13
+ Requires-Dist: click
14
+ Requires-Dist: tqdm
15
+ Provides-Extra: all
16
+ Requires-Dist: fastwarc[fsspec]; extra == "all"
17
+ Provides-Extra: fsspec
18
+ Requires-Dist: fsspec; extra == "fsspec"
19
+ Requires-Dist: s3fs; extra == "fsspec"
20
+ Provides-Extra: test
21
+ Requires-Dist: fastwarc[all]; extra == "test"
22
+ Requires-Dist: pytest>=7.2.2; extra == "test"
23
+ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
24
+
25
+ # FastWARC (Python)
26
+
27
+ FastWARC is a high-performance WARC parsing library written in Rust with bindings for Python.
28
+ FastWARC supports compressed and uncompressed WARC/1.0 and WARC/1.1 streams. Supported compression algorithms are Gzip,
29
+ Zstd, and LZ4.
30
+
31
+ This package contains the Python bindings for FastWARC.
32
+
33
+ FastWARC belongs to the [ChatNoir Resiliparse toolkit](https://github.com/chatnoir-eu/chatnoir-resiliparse/) for fast
34
+ and robust web data processing.
35
+
36
+ ## Installing FastWARC
37
+
38
+ Pre-built FastWARC binaries for Python can be installed from [PyPi](https://pypi.org/project/FastWARC/):
39
+
40
+ ```bash
41
+ pip install fastwarc
42
+ ```
43
+
44
+ ## Building Python Bindings From Source
45
+
46
+ To be able to build FastWARC, you need the Rust toolchain installed (see above). Then to build directly from the GitHub
47
+ repository, follow these steps:
48
+
49
+ ```bash
50
+ # Clone repository
51
+
52
+ git clone https://github.com/chatnoir-eu/chatnoir-resiliparse.git
53
+ cd chatnoir-resiliparse
54
+
55
+ # Install Rust toolchain (if not already installed)
56
+
57
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
58
+
59
+ # Create a fresh venv first (recommended)
60
+
61
+ python3 -m venv venv && source venv/bin/activate
62
+
63
+ # Option 1: Build and install in editable mode (best for development)
64
+
65
+ python3 -m pip install -e ./fastwarc-py
66
+
67
+ # Option 2 (alternative): Build and install wheels in separate steps (best for redistribution)
68
+
69
+ python3 -m pip wheel -w build ./fastwarc-py
70
+ ls ./build/*.whl | xargs python3 -m pip install
71
+ ```
72
+
73
+ ## Usage Instructions
74
+
75
+ For detailed usage instructions, please consult
76
+ the [FastWARC User Manual](https://resiliparse.chatnoir.eu/en/latest/man/fastwarc.html).
77
+
78
+ ## Cite Us
79
+
80
+ If you use FastWARC, please consider citing our [OSSYM 2021 abstract paper](https://arxiv.org/abs/2112.03103):
81
+
82
+ ```bibtex
83
+ @InProceedings{bevendorff:2021,
84
+ author = {Janek Bevendorff and Martin Potthast and Benno Stein},
85
+ booktitle = {3rd International Symposium on Open Search Technology (OSSYM 2021)},
86
+ editor = {Andreas Wagner and Christian Guetl and Michael Granitzer and Stefan Voigt},
87
+ month = oct,
88
+ publisher = {International Open Search Symposium},
89
+ site = {CERN, Geneva, Switzerland},
90
+ title = {{FastWARC: Optimizing Large-Scale Web Archive Analytics}},
91
+ year = 2021
92
+ }
93
+ ```
94
+
95
+ You can also cite our [ECIR 2018 demo paper](https://webis.de/downloads/publications/papers/bevendorff_2018.pdf):
96
+
97
+ ```bibtex
98
+ @InProceedings{bevendorff:2018,
99
+ address = {Berlin Heidelberg New York},
100
+ author = {Janek Bevendorff and Benno Stein and Matthias Hagen and Martin Potthast},
101
+ booktitle = {Advances in Information Retrieval. 40th European Conference on IR Research (ECIR 2018)},
102
+ editor = {Leif Azzopardi and Allan Hanbury and Gabriella Pasi and Benjamin Piwowarski},
103
+ month = mar,
104
+ publisher = {Springer},
105
+ series = {Lecture Notes in Computer Science},
106
+ site = {Grenoble, France},
107
+ title = {{Elastic ChatNoir: Search Engine for the ClueWeb and the Common Crawl}},
108
+ year = 2018
109
+ }
110
+ ```
@@ -0,0 +1,86 @@
1
+ # FastWARC (Python)
2
+
3
+ FastWARC is a high-performance WARC parsing library written in Rust with bindings for Python.
4
+ FastWARC supports compressed and uncompressed WARC/1.0 and WARC/1.1 streams. Supported compression algorithms are Gzip,
5
+ Zstd, and LZ4.
6
+
7
+ This package contains the Python bindings for FastWARC.
8
+
9
+ FastWARC belongs to the [ChatNoir Resiliparse toolkit](https://github.com/chatnoir-eu/chatnoir-resiliparse/) for fast
10
+ and robust web data processing.
11
+
12
+ ## Installing FastWARC
13
+
14
+ Pre-built FastWARC binaries for Python can be installed from [PyPi](https://pypi.org/project/FastWARC/):
15
+
16
+ ```bash
17
+ pip install fastwarc
18
+ ```
19
+
20
+ ## Building Python Bindings From Source
21
+
22
+ To be able to build FastWARC, you need the Rust toolchain installed (see above). Then to build directly from the GitHub
23
+ repository, follow these steps:
24
+
25
+ ```bash
26
+ # Clone repository
27
+
28
+ git clone https://github.com/chatnoir-eu/chatnoir-resiliparse.git
29
+ cd chatnoir-resiliparse
30
+
31
+ # Install Rust toolchain (if not already installed)
32
+
33
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
34
+
35
+ # Create a fresh venv first (recommended)
36
+
37
+ python3 -m venv venv && source venv/bin/activate
38
+
39
+ # Option 1: Build and install in editable mode (best for development)
40
+
41
+ python3 -m pip install -e ./fastwarc-py
42
+
43
+ # Option 2 (alternative): Build and install wheels in separate steps (best for redistribution)
44
+
45
+ python3 -m pip wheel -w build ./fastwarc-py
46
+ ls ./build/*.whl | xargs python3 -m pip install
47
+ ```
48
+
49
+ ## Usage Instructions
50
+
51
+ For detailed usage instructions, please consult
52
+ the [FastWARC User Manual](https://resiliparse.chatnoir.eu/en/latest/man/fastwarc.html).
53
+
54
+ ## Cite Us
55
+
56
+ If you use FastWARC, please consider citing our [OSSYM 2021 abstract paper](https://arxiv.org/abs/2112.03103):
57
+
58
+ ```bibtex
59
+ @InProceedings{bevendorff:2021,
60
+ author = {Janek Bevendorff and Martin Potthast and Benno Stein},
61
+ booktitle = {3rd International Symposium on Open Search Technology (OSSYM 2021)},
62
+ editor = {Andreas Wagner and Christian Guetl and Michael Granitzer and Stefan Voigt},
63
+ month = oct,
64
+ publisher = {International Open Search Symposium},
65
+ site = {CERN, Geneva, Switzerland},
66
+ title = {{FastWARC: Optimizing Large-Scale Web Archive Analytics}},
67
+ year = 2021
68
+ }
69
+ ```
70
+
71
+ You can also cite our [ECIR 2018 demo paper](https://webis.de/downloads/publications/papers/bevendorff_2018.pdf):
72
+
73
+ ```bibtex
74
+ @InProceedings{bevendorff:2018,
75
+ address = {Berlin Heidelberg New York},
76
+ author = {Janek Bevendorff and Benno Stein and Matthias Hagen and Martin Potthast},
77
+ booktitle = {Advances in Information Retrieval. 40th European Conference on IR Research (ECIR 2018)},
78
+ editor = {Leif Azzopardi and Allan Hanbury and Gabriella Pasi and Benjamin Piwowarski},
79
+ month = mar,
80
+ publisher = {Springer},
81
+ series = {Lecture Notes in Computer Science},
82
+ site = {Grenoble, France},
83
+ title = {{Elastic ChatNoir: Search Engine for the ClueWeb and the Common Crawl}},
84
+ year = 2018
85
+ }
86
+ ```
@@ -13,23 +13,20 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import importlib
16
- import sys
17
16
 
18
17
  _native = importlib.import_module("._fastwarc", __name__)
19
18
  _legacy_shims = importlib.import_module(".legacy._shims", __name__)
20
19
 
21
- from ._fastwarc import * # noqa: F401,F403
20
+ import fastwarc.stream_io as stream_io # noqa: F401
21
+ import fastwarc.warc as warc # noqa: F401
22
22
 
23
- stream_io = _native.stream_io
24
- warc = _native.warc
25
-
26
- sys.modules[__name__ + ".stream_io"] = stream_io
27
- sys.modules[__name__ + ".warc"] = warc
23
+ # Re-export module attributes
24
+ globals().update({attr: getattr(_native, attr) for attr in _native.__all__})
28
25
 
29
26
  # Patch legacy shims onto stream_io
30
27
  for name in _legacy_shims.__all__:
31
28
  setattr(stream_io, name, getattr(_legacy_shims, name))
32
- stream_io.__all__ += getattr(_legacy_shims, "__all__", ())
29
+ stream_io.__all__ += getattr(_legacy_shims, "__all__", ()) # type: ignore[attr-defined]
33
30
 
34
31
  FileStream = _legacy_shims.FileStream
35
32
  GZipStream = _legacy_shims.GZipStream
@@ -39,9 +36,9 @@ StreamError = _legacy_shims.StreamError
39
36
 
40
37
  __all__ = [
41
38
  *_native.__all__,
42
- "FileStream",
43
- "GZipStream",
44
- "LZ4Stream",
45
- "FastWARCError",
46
- "StreamError",
39
+ 'FileStream',
40
+ 'GZipStream',
41
+ 'LZ4Stream',
42
+ 'FastWARCError',
43
+ 'StreamError',
47
44
  ]
@@ -12,8 +12,16 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from ._fastwarc import *
16
- from .legacy import (
15
+ from .warc import ArchiveIterator, WarcRecord, WarcRecordType
16
+ from .stream_io import (
17
+ GzipReader,
18
+ GzipWriter,
19
+ ZstdReader,
20
+ ZstdWriter,
21
+ Lz4Reader,
22
+ Lz4Writer,
23
+
24
+ # Legacy names
17
25
  FileStream,
18
26
  GZipStream,
19
27
  LZ4Stream,
@@ -14,6 +14,7 @@
14
14
 
15
15
  import getpass
16
16
  import importlib
17
+ from enum import IntFlag
17
18
  from itertools import chain
18
19
  import json
19
20
  import os
@@ -24,14 +25,13 @@ import urllib.request
24
25
  import click
25
26
  from tqdm import tqdm
26
27
 
27
- from fastwarc.legacy.stream_io import FileStream, StreamError, FastWARCError, PythonIOStreamAdapter
28
- from fastwarc.legacy.warc import ArchiveIterator, WarcRecordType
29
- from fastwarc.legacy.tools import CompressionAlg, detect_compression_algorithm, wrap_warc_stream, \
30
- recompress_warc_interactive, verify_digests
28
+ from fastwarc.stream_io import *
29
+ from fastwarc.warc import ArchiveIterator, WarcRecordType
30
+ from fastwarc.legacy.tools import CompressionAlg, detect_compression_algorithm, wrap_warc_stream, verify_digests
31
31
 
32
32
 
33
33
  def exception_handler(exctype, value, _):
34
- if exctype == FastWARCError:
34
+ if exctype == OSError:
35
35
  click.echo(str(value), err=True)
36
36
  sys.exit(1)
37
37
 
@@ -56,46 +56,132 @@ def _human_readable_bytes(byte_num):
56
56
  byte_num /= 1024
57
57
 
58
58
 
59
+ class CompressionAlg(IntFlag):
60
+ UNCOMPRESSED = 0
61
+ GZIP = 1
62
+ ZSTD = 2
63
+ LZ4 = 3
64
+ AUTO = 65535
65
+
66
+
67
+ def detect_compression_algorithm(infile):
68
+ """
69
+ Try to detect the used compression algorithm from the given filename.
70
+
71
+ :param infile: filename or file object
72
+ :return: compression algorithm
73
+ """
74
+
75
+ if os.fspath(infile):
76
+ infile = str(infile)
77
+ elif hasattr(infile, 'name'):
78
+ infile = infile.name
79
+ else:
80
+ raise ValueError('Invalid infile type.')
81
+
82
+ if type(infile) is str:
83
+ if infile.endswith('.gz'):
84
+ return CompressionAlg.GZIP
85
+ if infile.endswith('.zst'):
86
+ return CompressionAlg.ZSTD
87
+ if infile.endswith('.lz4'):
88
+ return CompressionAlg.LZ4
89
+ if infile.endswith('.warc'):
90
+ return CompressionAlg.UNCOMPRESSED
91
+
92
+ if not os.path.isfile(infile):
93
+ raise click.UsageError('Could not determine compression algorithm.')
94
+
95
+ with open(infile, 'rb') as infile:
96
+ magic_bytes = infile.read(5)
97
+
98
+ if magic_bytes[:3] == b'\x1F\x8B\x08':
99
+ return CompressionAlg.GZIP
100
+ elif magic_bytes[:4] == b'\x28\xB5\x2F\xFD' or magic_bytes[:4] == b'\x5D\x2A\x4D\x18':
101
+ return CompressionAlg.ZSTD
102
+ elif magic_bytes[:4] == b'\x04\x22\x4D\x18':
103
+ return CompressionAlg.LZ4
104
+ elif magic_bytes == b'WARC/':
105
+ return CompressionAlg.UNCOMPRESSED
106
+
107
+ raise click.FileError('No compression algorithm detected.')
108
+
109
+
110
+ def get_compressor(filename, alg, level):
111
+ if alg == CompressionAlg.AUTO:
112
+ alg = detect_compression_algorithm(filename)
113
+ if alg == CompressionAlg.UNCOMPRESSED:
114
+ return open(filename, 'wb')
115
+ elif alg == CompressionAlg.GZIP:
116
+ return GzipWriter(filename, compression_level=level or 9)
117
+ elif alg == CompressionAlg.ZSTD:
118
+ return ZstdWriter(filename, compression_level=level or 3)
119
+ elif alg == CompressionAlg.LZ4:
120
+ return Lz4Writer(filename) # compression_level currently unsupported
121
+ else:
122
+ raise ValueError(f'Unknown compression algorithm: {alg}')
123
+
124
+
125
+ def get_decompressor(filename, decompress_alg):
126
+ if os.fspath(filename) and not decompress_alg:
127
+ # Use plain filename to avoid Python IO stack
128
+ return str(filename)
129
+
130
+ if decompress_alg:
131
+ alg = decompress_alg
132
+ else:
133
+ alg = detect_compression_algorithm(filename)
134
+ if alg == CompressionAlg.UNCOMPRESSED:
135
+ return open(filename, 'rb')
136
+ if alg == CompressionAlg.GZIP:
137
+ return GzipReader(filename)
138
+ if alg == CompressionAlg.ZSTD:
139
+ return ZstdReader(filename)
140
+ if alg == CompressionAlg.LZ4:
141
+ return Lz4Reader(filename) # compression_level currently unsupported
142
+
143
+ raise ValueError(f'Unknown compression algorithm: {alg}')
144
+
145
+
59
146
  @main.command()
60
147
  @click.argument('infile', type=click.Path(dir_okay=False, exists=True))
61
148
  @click.argument('outfile', type=click.Path(dir_okay=False, exists=False))
62
- @click.option('-c', '--compress-alg', type=click.Choice(['gzip', 'lz4', 'uncompressed', 'auto']),
149
+ @click.option('-c', '--compress-alg', type=click.Choice(['uncompressed', 'gzip', 'zstd', 'lz4', 'auto']),
63
150
  default='auto', show_default=True, help='Compression algorithm to use for output file')
64
- @click.option('-d', '--decompress-alg', type=click.Choice(['gzip', 'lz4', 'uncompressed', 'auto']),
151
+ @click.option('-d', '--decompress-alg', type=click.Choice(['uncompressed', 'gzip', 'zstd', 'lz4', 'auto']),
65
152
  default='auto', show_default=True,
66
153
  help='Decompression algorithm for decoding input file (auto tries to detect based on file extension)')
67
- @click.option('-l', '--compress-level', type=int, default=None, help='Compression level (defaults to max)')
154
+ @click.option('-l', '--compress-level', type=int, default=None, help='Compression level')
68
155
  @click.option('-q', '--quiet', is_flag=True, help='Do not print progress information')
69
156
  def recompress(infile, outfile, compress_alg, decompress_alg, compress_level, quiet):
70
157
  """
71
158
  Recompress a WARC file.
72
159
 
73
- This command allows you to recompress a WARC file if it is uncompressed or not compressed
74
- properly at the record-level if or you want to recompress a GZip WARC as LZ4 or vice versa.
160
+ This command allows you to recompress a WARC file if it is uncompressed, not compressed
161
+ properly at the record-level, or if you want to recompress a Gzip WARC as Zstd or LZ4 or vice versa.
75
162
  """
76
163
 
77
- compress_alg = getattr(CompressionAlg, compress_alg)
78
- decompress_alg = getattr(CompressionAlg, decompress_alg)
164
+ compress_alg = getattr(CompressionAlg, compress_alg.upper())
165
+ decompress_alg = getattr(CompressionAlg, decompress_alg.upper())
79
166
 
80
- comp_args = {}
81
- if compress_level is None:
82
- if compress_alg == CompressionAlg.gzip:
83
- comp_args['compression_level'] = 9
84
- elif compress_alg == CompressionAlg.lz4:
85
- comp_args['compression_level'] = 12
167
+ infile = get_decompressor(infile, decompress_alg if decompress_alg != CompressionAlg.AUTO else None)
168
+ outfile_name = outfile
169
+ outfile = get_compressor(outfile, compress_alg, compress_level)
86
170
 
87
- bytes_written = 0
88
171
  num = 0
89
- start = time.time()
172
+ start = time.monotonic()
90
173
  try:
91
- for _, b in tqdm(recompress_warc_interactive(infile, outfile, decompress_alg, compress_alg, **comp_args),
92
- desc='Recompressing WARC file', unit=' record(s)', leave=False,
93
- disable=quiet, mininterval=0.2):
94
- num += 1
95
- bytes_written += b
174
+ with outfile as out:
175
+ for r in tqdm(ArchiveIterator(infile),
176
+ desc='Recompressing WARC file', unit=' record(s)', leave=False,
177
+ disable=quiet, mininterval=0.3):
178
+ num += 1
179
+ r.write(out)
180
+ if compress_alg != CompressionAlg.UNCOMPRESSED:
181
+ out.finish()
96
182
 
97
- if not quiet:
98
- click.echo('Recompression completed.')
183
+ if not quiet:
184
+ click.echo('Recompression completed.')
99
185
  except KeyboardInterrupt:
100
186
  if not quiet:
101
187
  click.echo('Recompression aborted.')
@@ -103,8 +189,8 @@ def recompress(infile, outfile, compress_alg, decompress_alg, compress_level, qu
103
189
  finally:
104
190
  if not quiet and num > 0:
105
191
  click.echo(f' - Records recompressed: {num}')
106
- click.echo(f' - Bytes written: {_human_readable_bytes(bytes_written)}')
107
- click.echo(f' - Completed in: {time.time() - start:.02f} seconds')
192
+ click.echo(f' - Bytes written: {_human_readable_bytes(os.stat(outfile_name).st_size)}')
193
+ click.echo(f' - Completed in: {time.monotonic() - start:.02f} seconds')
108
194
 
109
195
 
110
196
  @main.command()
@@ -0,0 +1,19 @@
1
+ from .stream_io import (
2
+ FileStream,
3
+ GZipStream,
4
+ LZ4Stream,
5
+ FastWARCError,
6
+ StreamError,
7
+ )
8
+ from .warc import ArchiveIterator, WarcRecord, WarcRecordType
9
+
10
+ __all__ = [
11
+ "FileStream",
12
+ "GZipStream",
13
+ "LZ4Stream",
14
+ "FastWARCError",
15
+ "StreamError",
16
+ "ArchiveIterator",
17
+ "WarcRecord",
18
+ "WarcRecordType",
19
+ ]
@@ -172,7 +172,7 @@ class BytesIOStream:
172
172
 
173
173
  class GZipStream:
174
174
  def __new__(cls, raw_stream, compression_level=9, zlib=False, fsspec_args=None):
175
- from fastwarc.stream_io import GzipReader, GzipWriter
175
+ from ..stream_io import GzipReader, GzipWriter
176
176
  stream = CompressingStream(
177
177
  _reader_factory=lambda: GzipReader(raw_stream, zlib=zlib, fsspec_args=fsspec_args),
178
178
  _writer_factory=lambda: GzipWriter(raw_stream, compression_level=compression_level, zlib=zlib,
@@ -184,7 +184,7 @@ class GZipStream:
184
184
  class LZ4Stream:
185
185
  def __new__(cls, raw_stream, mode='r', compression_level=12,
186
186
  favor_dec_speed=True, fsspec_args=None):
187
- from fastwarc.stream_io import Lz4Reader, Lz4Writer
187
+ from ..stream_io import Lz4Reader, Lz4Writer
188
188
  stream = CompressingStream(
189
189
  _reader_factory=lambda: Lz4Reader(raw_stream, fsspec_args=fsspec_args),
190
190
  _writer_factory=lambda: Lz4Writer(raw_stream, fsspec_args=fsspec_args))
@@ -194,7 +194,7 @@ class LZ4Stream:
194
194
 
195
195
  class BrotliStream:
196
196
  def __new__(cls, raw_stream, quality=11, lgwin=22, lgblock=0, fsspec_args=None):
197
- from fastwarc.stream_io import BrotliReader, BrotliWriter
197
+ from ..stream_io import BrotliReader, BrotliWriter
198
198
  stream = CompressingStream(
199
199
  _reader_factory=lambda: BrotliReader(raw_stream, fsspec_args=fsspec_args),
200
200
  _writer_factory=lambda: BrotliWriter(raw_stream, fsspec_args=fsspec_args))