FastWARC 1.0.7__tar.gz → 1.0.9__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 (63) hide show
  1. {fastwarc-1.0.7 → fastwarc-1.0.9}/FastWARC.egg-info/PKG-INFO +2 -2
  2. {fastwarc-1.0.7 → fastwarc-1.0.9}/FastWARC.egg-info/SOURCES.txt +10 -0
  3. {fastwarc-1.0.7 → fastwarc-1.0.9}/FastWARC.egg-info/requires.txt +1 -1
  4. {fastwarc-1.0.7 → fastwarc-1.0.9}/MANIFEST.in +1 -1
  5. {fastwarc-1.0.7 → fastwarc-1.0.9}/PKG-INFO +2 -2
  6. fastwarc-1.0.9/fastwarc/Cargo.toml +28 -0
  7. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/__init__.pyi +2 -1
  8. fastwarc-1.0.9/fastwarc/build.rs +18 -0
  9. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/_shims.py +4 -2
  10. fastwarc-1.0.9/fastwarc/src/lib.rs +116 -0
  11. fastwarc-1.0.9/fastwarc/src/stream_io/brotli.rs +148 -0
  12. fastwarc-1.0.9/fastwarc/src/stream_io/chunked.rs +140 -0
  13. fastwarc-1.0.9/fastwarc/src/stream_io/gzip.rs +161 -0
  14. fastwarc-1.0.9/fastwarc/src/stream_io/lz4.rs +145 -0
  15. fastwarc-1.0.9/fastwarc/src/stream_io/mod.rs +712 -0
  16. fastwarc-1.0.9/fastwarc/src/stream_io/zstd.rs +206 -0
  17. fastwarc-1.0.9/fastwarc/src/warc.rs +1712 -0
  18. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/warc.pyi +62 -16
  19. {fastwarc-1.0.7 → fastwarc-1.0.9}/pyproject.toml +8 -3
  20. {fastwarc-1.0.7 → fastwarc-1.0.9}/setup.py +56 -13
  21. {fastwarc-1.0.7 → fastwarc-1.0.9}/FastWARC.egg-info/dependency_links.txt +0 -0
  22. {fastwarc-1.0.7 → fastwarc-1.0.9}/FastWARC.egg-info/entry_points.txt +0 -0
  23. {fastwarc-1.0.7 → fastwarc-1.0.9}/FastWARC.egg-info/top_level.txt +0 -0
  24. {fastwarc-1.0.7 → fastwarc-1.0.9}/README.md +0 -0
  25. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/__init__.py +0 -0
  26. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/cli.py +0 -0
  27. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/__init__.pxd +0 -0
  28. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/__init__.py +0 -0
  29. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/__init__.pyi +0 -0
  30. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/stream_io.pxd +0 -0
  31. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/stream_io.pyi +0 -0
  32. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/stream_io.pyx +0 -0
  33. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/tools.pyi +0 -0
  34. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/tools.pyx +0 -0
  35. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/warc.pxd +0 -0
  36. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/warc.pyi +0 -0
  37. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/legacy/warc.pyx +0 -0
  38. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/py.typed +0 -0
  39. {fastwarc-1.0.7 → fastwarc-1.0.9}/fastwarc/stream_io.pyi +0 -0
  40. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_common/__init__.pxd +0 -0
  41. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_common/string_util.pxd +0 -0
  42. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/__init__.pxd +0 -0
  43. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/algorithm.pxd +0 -0
  44. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/atomic.pxd +0 -0
  45. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/boost_regex.pxd +0 -0
  46. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/cctype.pxd +0 -0
  47. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/cstdlib.pxd +0 -0
  48. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/cstring.pxd +0 -0
  49. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/dlfcn.pxd +0 -0
  50. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/errno.pxd +0 -0
  51. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/lexbor.pxd +0 -0
  52. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/lz4frame.pxd +0 -0
  53. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/lz4hc.pxd +0 -0
  54. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/pthread.pxd +0 -0
  55. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/re2.pxd +0 -0
  56. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/stdio.pxd +0 -0
  57. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/string_view.pxd +0 -0
  58. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/time.pxd +0 -0
  59. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/uchardet.pxd +0 -0
  60. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/unistd.pxd +0 -0
  61. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/utility.pxd +0 -0
  62. {fastwarc-1.0.7 → fastwarc-1.0.9}/resiliparse_inc/zlib.pxd +0 -0
  63. {fastwarc-1.0.7 → fastwarc-1.0.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastWARC
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary: The world's fastest WARC parsing library written in Rust with bindings for Python.
5
5
  Author: Janek Bevendorff
6
6
  License-Expression: Apache-2.0
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: brotli
13
13
  Requires-Dist: click
14
14
  Requires-Dist: tqdm
15
- Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
15
+ Requires-Dist: typing_extensions>=4.15; python_version < "3.13"
16
16
  Provides-Extra: all
17
17
  Requires-Dist: fastwarc[fsspec]; extra == "all"
18
18
  Provides-Extra: fsspec
@@ -8,8 +8,10 @@ FastWARC.egg-info/dependency_links.txt
8
8
  FastWARC.egg-info/entry_points.txt
9
9
  FastWARC.egg-info/requires.txt
10
10
  FastWARC.egg-info/top_level.txt
11
+ fastwarc/Cargo.toml
11
12
  fastwarc/__init__.py
12
13
  fastwarc/__init__.pyi
14
+ fastwarc/build.rs
13
15
  fastwarc/cli.py
14
16
  fastwarc/py.typed
15
17
  fastwarc/stream_io.pyi
@@ -26,6 +28,14 @@ fastwarc/legacy/tools.pyx
26
28
  fastwarc/legacy/warc.pxd
27
29
  fastwarc/legacy/warc.pyi
28
30
  fastwarc/legacy/warc.pyx
31
+ fastwarc/src/lib.rs
32
+ fastwarc/src/warc.rs
33
+ fastwarc/src/stream_io/brotli.rs
34
+ fastwarc/src/stream_io/chunked.rs
35
+ fastwarc/src/stream_io/gzip.rs
36
+ fastwarc/src/stream_io/lz4.rs
37
+ fastwarc/src/stream_io/mod.rs
38
+ fastwarc/src/stream_io/zstd.rs
29
39
  resiliparse_common/__init__.pxd
30
40
  resiliparse_common/string_util.pxd
31
41
  resiliparse_inc/__init__.pxd
@@ -3,7 +3,7 @@ click
3
3
  tqdm
4
4
 
5
5
  [:python_version < "3.13"]
6
- typing_extensions>=4.5
6
+ typing_extensions>=4.15
7
7
 
8
8
  [all]
9
9
  fastwarc[fsspec]
@@ -1,4 +1,4 @@
1
- recursive-include fastwarc *.pyx *.pxd *.h
1
+ recursive-include fastwarc *.rs Cargo.toml Cargo.lock *.pyx *.pxd *.h
2
2
  recursive-exclude fastwarc *.cpp
3
3
  graft resiliparse_common
4
4
  graft resiliparse_inc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastWARC
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary: The world's fastest WARC parsing library written in Rust with bindings for Python.
5
5
  Author: Janek Bevendorff
6
6
  License-Expression: Apache-2.0
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: brotli
13
13
  Requires-Dist: click
14
14
  Requires-Dist: tqdm
15
- Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
15
+ Requires-Dist: typing_extensions>=4.15; python_version < "3.13"
16
16
  Provides-Extra: all
17
17
  Requires-Dist: fastwarc[fsspec]; extra == "all"
18
18
  Provides-Extra: fsspec
@@ -0,0 +1,28 @@
1
+ [package]
2
+ name = "fastwarc-py"
3
+ edition = "2024"
4
+ publish = false
5
+
6
+ [lib]
7
+ name = "fastwarc"
8
+ path = "src/lib.rs"
9
+ crate-type = [
10
+ "cdylib",
11
+ ]
12
+
13
+ [dependencies]
14
+ fastwarc = "1.0.9"
15
+ pyo3 = "0.28"
16
+
17
+ [dependencies.time]
18
+ version = "0.3"
19
+ features = [
20
+ "formatting",
21
+ "parsing",
22
+ "macros",
23
+ ]
24
+
25
+ [build-dependencies]
26
+ pyo3-build-config = "0.28"
27
+
28
+ [workspace]
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from .warc import ArchiveIterator, WarcRecord, WarcRecordType
15
+ from .warc import ArchiveIterator, WarcHeader, WarcRecord, WarcRecordType
16
16
  from .stream_io import (
17
17
  GzipReader,
18
18
  GzipWriter,
@@ -31,6 +31,7 @@ from .stream_io import (
31
31
 
32
32
  __all__ = [
33
33
  'ArchiveIterator',
34
+ 'WarcHeader',
34
35
  'WarcRecord',
35
36
  'WarcRecordType',
36
37
  'GzipReader',
@@ -0,0 +1,18 @@
1
+ // Copyright 2026 Janek Bevendorff
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ fn main() {
16
+ // https://pyo3.rs/v0.28.3/building-and-distribution.html?utm_source=chatgpt.com#macos
17
+ pyo3_build_config::add_extension_module_link_args();
18
+ }
@@ -14,9 +14,11 @@
14
14
 
15
15
  # Legacy shims for making the new Rust extension work with the old type names
16
16
 
17
- try:
17
+ import sys
18
+
19
+ if sys.version_info >= (3, 13):
18
20
  from warnings import deprecated
19
- except ImportError:
21
+ else:
20
22
  from typing_extensions import deprecated
21
23
 
22
24
  __all__ = [
@@ -0,0 +1,116 @@
1
+ // Copyright 2026 Janek Bevendorff
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ mod stream_io;
16
+ mod warc;
17
+
18
+ use pyo3::prelude::*;
19
+ use pyo3::types::PyModule;
20
+
21
+ #[pymodule]
22
+ pub mod _fastwarc {
23
+ use super::*;
24
+
25
+ #[pymodule_init]
26
+ pub fn __init__(m: &Bound<'_, PyModule>) -> PyResult<()> {
27
+ let py = m.py();
28
+
29
+ // Load submodules.
30
+ // Don't add with add_submodule(), otherwise they end up in __all__.
31
+ let stream_io = pyo3::wrap_pymodule!(_stream_io)(py);
32
+ let stream_io = stream_io.bind(py);
33
+
34
+ let warc = pyo3::wrap_pymodule!(_warc)(py);
35
+ let warc = warc.bind(py);
36
+
37
+ // Register submodules to make them importable.
38
+ let sys = PyModule::import(py, "sys")?;
39
+ let modules = sys.getattr("modules")?;
40
+ modules.set_item("fastwarc.stream_io", stream_io)?;
41
+ modules.set_item("fastwarc.warc", warc)?;
42
+
43
+ // Top-level exports
44
+ m.add("WarcRecordType", warc.getattr("WarcRecordType")?)?;
45
+ m.add("WarcHeader", warc.getattr("WarcHeader")?)?;
46
+ m.add("WarcRecord", warc.getattr("WarcRecord")?)?;
47
+ m.add("ArchiveIterator", warc.getattr("ArchiveIterator")?)?;
48
+
49
+ m.add("GzipReader", stream_io.getattr("GzipReader")?)?;
50
+ m.add("GzipWriter", stream_io.getattr("GzipWriter")?)?;
51
+ m.add("Lz4Reader", stream_io.getattr("Lz4Reader")?)?;
52
+ m.add("Lz4Writer", stream_io.getattr("Lz4Writer")?)?;
53
+ m.add("ZstdReader", stream_io.getattr("ZstdReader")?)?;
54
+ m.add("ZstdWriter", stream_io.getattr("ZstdWriter")?)?;
55
+
56
+ Ok(())
57
+ }
58
+ }
59
+
60
+ #[pymodule(name = "stream_io", module = "fastwarc")]
61
+ pub mod _stream_io {
62
+ #[pymodule_export]
63
+ pub use crate::stream_io::{WarcReaderPy, WarcWriterPy};
64
+
65
+ #[pymodule_export]
66
+ pub use crate::stream_io::gzip::{GzipReaderPy, GzipWriterPy};
67
+
68
+ #[pymodule_export]
69
+ pub use crate::stream_io::zstd::{
70
+ ZstdReaderPy, ZstdWriterPy, zstd_train_dictionary_from_continuous, zstd_train_dictionary_from_files,
71
+ zstd_train_dictionary_from_samples,
72
+ };
73
+
74
+ #[pymodule_export]
75
+ pub use crate::stream_io::lz4::{Lz4ReaderPy, Lz4WriterPy};
76
+
77
+ #[pymodule_export]
78
+ pub use crate::stream_io::brotli::{BrotliReaderPy, BrotliWriterPy};
79
+
80
+ #[pymodule_export]
81
+ pub use crate::stream_io::chunked::{ChunkedReaderPy, ChunkedWriterPy};
82
+ }
83
+
84
+ #[pymodule(name = "warc", module = "fastwarc")]
85
+ pub mod _warc {
86
+ use super::*;
87
+
88
+ #[pymodule_init]
89
+ fn __init__(m: &Bound<'_, PyModule>) -> PyResult<()> {
90
+ // Re-export enum members as individual constants.
91
+ m.add("warcinfo", WarcRecordTypePy::warcinfo)?;
92
+ m.add("response", WarcRecordTypePy::response)?;
93
+ m.add("resource", WarcRecordTypePy::resource)?;
94
+ m.add("request", WarcRecordTypePy::request)?;
95
+ m.add("metadata", WarcRecordTypePy::metadata)?;
96
+ m.add("revisit", WarcRecordTypePy::revisit)?;
97
+ m.add("conversion", WarcRecordTypePy::conversion)?;
98
+ m.add("continuation", WarcRecordTypePy::continuation)?;
99
+ m.add("unknown", WarcRecordTypePy::unknown)?;
100
+ m.add("no_type", WarcRecordTypePy::no_type)?;
101
+ m.add("any_type", WarcRecordTypePy::any_type)?;
102
+
103
+ // Legacy name
104
+ m.add("WarcHeaderMap", m.getattr("HeaderMap")?)?;
105
+
106
+ Ok(())
107
+ }
108
+
109
+ #[pymodule_export]
110
+ pub use crate::warc::{
111
+ ArchiveIteratorPy, HeaderMapPy, WarcHeaderPy, WarcRecordPayloadReaderPy, WarcRecordPy, WarcRecordTypePy,
112
+ has_block_digest_py, has_content_length_gte_py, has_content_length_lte_py, has_payload_digest_py,
113
+ has_record_type_py, has_valid_block_digest_py, has_valid_payload_digest_py, is_concurrent_py, is_http_py,
114
+ is_warc_10_py, is_warc_11_py,
115
+ };
116
+ }
@@ -0,0 +1,148 @@
1
+ // Copyright 2026 Janek Bevendorff
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ use super::impl_macros::*;
16
+ use crate::stream_io::{WarcReaderPy, WarcWriterPy, wrap_reader_stream, wrap_writer_stream};
17
+ use fastwarc::stream_io::brotli::{self, BrotliWriterOptions};
18
+ use fastwarc::stream_io::traits::{WarcRead, WarcWrite};
19
+ use pyo3::exceptions::PyValueError;
20
+ use pyo3::prelude::*;
21
+ use pyo3::types::PyBytes;
22
+ use std::io::{self, Read, Seek, Write};
23
+ use std::sync::Mutex;
24
+
25
+ /// Brotli reader.
26
+ ///
27
+ /// :param inner: raw input stream, file-like object, file name, or URL
28
+ /// :param buffer_size: input buffer size
29
+ /// :param fsspec_args: arguments for :mod:`fsspec`, or ``False`` to disable it
30
+ #[pyclass(name = "BrotliReader", module = "fastwarc.stream_io", extends = WarcReaderPy, subclass)]
31
+ pub struct BrotliReaderPy {
32
+ pub(crate) inner: Mutex<Option<Box<dyn WarcRead + Send>>>,
33
+ }
34
+
35
+ // noinspection DuplicatedCode
36
+ #[pymethods]
37
+ impl BrotliReaderPy {
38
+ #[new]
39
+ #[pyo3(signature = (inner, buffer_size=64 << 10, fsspec_args=None))]
40
+ pub fn __new__(
41
+ py: Python<'_>,
42
+ inner: Py<PyAny>,
43
+ buffer_size: usize,
44
+ fsspec_args: Option<Py<PyAny>>,
45
+ ) -> PyResult<PyClassInitializer<Self>> {
46
+ let options = brotli::BrotliReaderOptions { capacity: buffer_size };
47
+ let inner = wrap_reader_stream(
48
+ py,
49
+ inner,
50
+ fsspec_args,
51
+ |reader| -> io::Result<Box<dyn WarcRead + Send>> {
52
+ Ok(Box::new(brotli::BrotliReader::with_options(reader, options)))
53
+ },
54
+ |path| Ok(Box::new(brotli::BrotliReader::from_path_with_options(path, options)?)),
55
+ )?;
56
+ Ok(PyClassInitializer::from(WarcReaderPy::__new__()).add_subclass(Self {
57
+ inner: Mutex::new(Some(inner)),
58
+ }))
59
+ }
60
+
61
+ #[pyo3(signature = (size=-1))]
62
+ pub fn read<'py>(&self, py: Python<'py>, size: i128) -> PyResult<Bound<'py, PyBytes>> {
63
+ impl_reader_read!(self, py, size)
64
+ }
65
+
66
+ #[pyo3(signature = (offset, whence=0))]
67
+ pub fn seek(&self, offset: i128, whence: u8) -> PyResult<u64> {
68
+ impl_reader_seek!(self, offset, whence, seek)
69
+ }
70
+
71
+ #[pyo3(signature = (offset, whence=0))]
72
+ pub fn inner_seek(&self, offset: i128, whence: u8) -> PyResult<u64> {
73
+ impl_reader_seek!(self, offset, whence, inner_seek)
74
+ }
75
+
76
+ pub fn tell(&self) -> PyResult<u64> {
77
+ forward_fn_call!(self, stream_position)
78
+ }
79
+
80
+ pub fn inner_tell(&self) -> PyResult<u64> {
81
+ forward_fn_call!(self, inner_stream_position)
82
+ }
83
+
84
+ #[allow(clippy::needless_question_mark)]
85
+ pub fn frame_start_position(&mut self) -> io::Result<Option<u64>> {
86
+ forward_fn_call!(self, frame_start_position)
87
+ }
88
+
89
+ pub fn close(&self) -> PyResult<()> {
90
+ impl_reader_close!(self)
91
+ }
92
+ }
93
+
94
+ /// Brotli writer.
95
+ ///
96
+ /// :param inner: raw output stream, file-like object, file name, or URL
97
+ /// :param buffer_size: compression buffer size
98
+ /// :param fsspec_args: arguments for :mod:`fsspec`, or ``False`` to disable it
99
+ #[pyclass(name = "BrotliWriter", module = "fastwarc.stream_io", extends = WarcWriterPy, subclass)]
100
+ pub struct BrotliWriterPy {
101
+ pub(crate) inner: Mutex<Option<Box<dyn WarcWrite + Send>>>,
102
+ }
103
+
104
+ // noinspection DuplicatedCode
105
+ #[pymethods]
106
+ impl BrotliWriterPy {
107
+ #[new]
108
+ #[pyo3(signature = (inner, buffer_size=8192, fsspec_args=None,))]
109
+ pub fn __new__(
110
+ py: Python<'_>,
111
+ inner: Py<PyAny>,
112
+ buffer_size: usize,
113
+ fsspec_args: Option<Py<PyAny>>,
114
+ ) -> PyResult<PyClassInitializer<Self>> {
115
+ let options = brotli::BrotliWriterOptions {
116
+ capacity: buffer_size,
117
+ ..BrotliWriterOptions::default()
118
+ };
119
+ let inner = wrap_writer_stream(
120
+ py,
121
+ inner,
122
+ fsspec_args,
123
+ |reader| -> io::Result<Box<dyn WarcWrite + Send>> {
124
+ Ok(Box::new(brotli::BrotliWriter::with_options(reader, options)))
125
+ },
126
+ |path| Ok(Box::new(brotli::BrotliWriter::from_path_with_options(path, options)?)),
127
+ )?;
128
+ Ok(PyClassInitializer::from(WarcWriterPy::__new__()).add_subclass(Self {
129
+ inner: Mutex::new(Some(inner)),
130
+ }))
131
+ }
132
+
133
+ pub fn write(&self, data: &[u8]) -> PyResult<usize> {
134
+ impl_writer_write!(self, data)
135
+ }
136
+
137
+ pub fn flush(&self) -> PyResult<()> {
138
+ forward_fn_call!(self, flush)
139
+ }
140
+
141
+ pub fn finish(&self) -> PyResult<()> {
142
+ forward_fn_call!(self, finish)
143
+ }
144
+
145
+ pub fn close(&self) -> PyResult<()> {
146
+ impl_writer_close!(self)
147
+ }
148
+ }
@@ -0,0 +1,140 @@
1
+ // Copyright 2026 Janek Bevendorff
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ use super::impl_macros::*;
16
+ use crate::stream_io::{WarcReaderPy, WarcWriterPy, wrap_reader_stream, wrap_writer_stream};
17
+ use fastwarc::stream_io::chunked;
18
+ use fastwarc::stream_io::traits::{WarcRead, WarcWrite};
19
+ use pyo3::exceptions::PyValueError;
20
+ use pyo3::prelude::*;
21
+ use pyo3::types::PyBytes;
22
+ use std::io::{self, Read, Seek, Write};
23
+ use std::sync::Mutex;
24
+
25
+ /// HTTP chunked-transfer reader.
26
+ ///
27
+ /// :param inner: raw input stream, file-like object, file name, or URL
28
+ /// :param buffer_size: input buffer size
29
+ /// :param fsspec_args: arguments for :mod:`fsspec`, or ``False`` to disable it
30
+ #[pyclass(name = "ChunkedReader", module = "fastwarc.stream_io", extends = WarcReaderPy, subclass)]
31
+ pub struct ChunkedReaderPy {
32
+ pub(crate) inner: Mutex<Option<Box<dyn WarcRead + Send>>>,
33
+ }
34
+
35
+ // noinspection DuplicatedCode
36
+ #[pymethods]
37
+ impl ChunkedReaderPy {
38
+ #[new]
39
+ #[pyo3(signature = (inner, buffer_size=4096, fsspec_args=None))]
40
+ pub fn __new__(
41
+ py: Python<'_>,
42
+ inner: Py<PyAny>,
43
+ buffer_size: usize,
44
+ fsspec_args: Option<Py<PyAny>>,
45
+ ) -> PyResult<PyClassInitializer<Self>> {
46
+ let options = chunked::ChunkedReaderOptions { capacity: buffer_size };
47
+ let inner = wrap_reader_stream(
48
+ py,
49
+ inner,
50
+ fsspec_args,
51
+ |reader| -> io::Result<Box<dyn WarcRead + Send>> {
52
+ Ok(Box::new(chunked::ChunkedReader::with_options(reader, options)))
53
+ },
54
+ |path| Ok(Box::new(chunked::ChunkedReader::from_path_with_options(path, options)?)),
55
+ )?;
56
+ Ok(PyClassInitializer::from(WarcReaderPy::__new__()).add_subclass(Self {
57
+ inner: Mutex::new(Some(inner)),
58
+ }))
59
+ }
60
+
61
+ #[pyo3(signature = (size=-1))]
62
+ pub fn read<'py>(&self, py: Python<'py>, size: i128) -> PyResult<Bound<'py, PyBytes>> {
63
+ impl_reader_read!(self, py, size)
64
+ }
65
+
66
+ #[pyo3(signature = (offset, whence=0))]
67
+ pub fn seek(&self, offset: i128, whence: u8) -> PyResult<u64> {
68
+ impl_reader_seek!(self, offset, whence, seek)
69
+ }
70
+
71
+ #[pyo3(signature = (offset, whence=0))]
72
+ pub fn inner_seek(&self, offset: i128, whence: u8) -> PyResult<u64> {
73
+ impl_reader_seek!(self, offset, whence, inner_seek)
74
+ }
75
+
76
+ pub fn tell(&self) -> PyResult<u64> {
77
+ forward_fn_call!(self, stream_position)
78
+ }
79
+
80
+ pub fn inner_tell(&self) -> PyResult<u64> {
81
+ forward_fn_call!(self, inner_stream_position)
82
+ }
83
+
84
+ pub fn close(&self) -> PyResult<()> {
85
+ impl_reader_close!(self)
86
+ }
87
+ }
88
+
89
+ /// HTTP chunked-transfer writer.
90
+ ///
91
+ /// :param inner: raw output stream, file-like object, file name, or URL
92
+ /// :param min_chunk_size: minimum chunk size
93
+ /// :param fsspec_args: arguments for :mod:`fsspec`, or ``False`` to disable it
94
+ #[pyclass(name = "ChunkedWriter", module = "fastwarc.stream_io", extends = WarcWriterPy, subclass)]
95
+ pub struct ChunkedWriterPy {
96
+ pub(crate) inner: Mutex<Option<Box<dyn WarcWrite + Send>>>,
97
+ }
98
+
99
+ // noinspection DuplicatedCode
100
+ #[pymethods]
101
+ impl ChunkedWriterPy {
102
+ #[new]
103
+ #[pyo3(signature = (inner, min_chunk_size=1024, fsspec_args=None,))]
104
+ pub fn __new__(
105
+ py: Python<'_>,
106
+ inner: Py<PyAny>,
107
+ min_chunk_size: usize,
108
+ fsspec_args: Option<Py<PyAny>>,
109
+ ) -> PyResult<PyClassInitializer<Self>> {
110
+ let options = chunked::ChunkedWriterOptions { min_chunk_size };
111
+ let inner = wrap_writer_stream(
112
+ py,
113
+ inner,
114
+ fsspec_args,
115
+ |reader| -> io::Result<Box<dyn WarcWrite + Send>> {
116
+ Ok(Box::new(chunked::ChunkedWriter::with_options(reader, options)))
117
+ },
118
+ |path| Ok(Box::new(chunked::ChunkedWriter::from_path_with_options(path, options)?)),
119
+ )?;
120
+ Ok(PyClassInitializer::from(WarcWriterPy::__new__()).add_subclass(Self {
121
+ inner: Mutex::new(Some(inner)),
122
+ }))
123
+ }
124
+
125
+ pub fn write(&self, data: &[u8]) -> PyResult<usize> {
126
+ impl_writer_write!(self, data)
127
+ }
128
+
129
+ pub fn flush(&self) -> PyResult<()> {
130
+ forward_fn_call!(self, flush)
131
+ }
132
+
133
+ pub fn finish(&self) -> PyResult<()> {
134
+ forward_fn_call!(self, finish)
135
+ }
136
+
137
+ pub fn close(&self) -> PyResult<()> {
138
+ impl_writer_close!(self)
139
+ }
140
+ }