scraper-rust 0.5.1__tar.gz → 0.7.0__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 (61) hide show
  1. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/.github/workflows/release.yml +43 -72
  2. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/.github/workflows/tests.yml +3 -6
  3. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/AGENTS.md +13 -14
  4. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/Cargo.lock +345 -389
  5. scraper_rust-0.7.0/Cargo.toml +48 -0
  6. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/PKG-INFO +20 -8
  7. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/README.md +18 -6
  8. scraper_rust-0.7.0/benchmarks/README.md +54 -0
  9. scraper_rust-0.7.0/benchmarks/common.rs +122 -0
  10. scraper_rust-0.7.0/benchmarks/parse_scaling.rs +38 -0
  11. scraper_rust-0.7.0/benchmarks/sync_async.rs +128 -0
  12. scraper_rust-0.7.0/build.rs +3 -0
  13. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/docs/api.md +1 -1
  14. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/docs/async-api.md +2 -0
  15. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/justfile +3 -0
  16. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/pyproject.toml +2 -2
  17. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/scraper_rs/__init__.py +1 -1
  18. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/scraper_rs/asyncio.py +11 -11
  19. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/async_core.rs +9 -5
  20. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/document.rs +26 -35
  21. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/element.rs +0 -18
  22. scraper_rust-0.7.0/src/html5_dict.rs +151 -0
  23. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/lib.rs +4 -2
  24. scraper_rust-0.7.0/src/prettify.rs +279 -0
  25. scraper_rust-0.7.0/src/selectors.rs +47 -0
  26. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/tests.rs +11 -12
  27. scraper_rust-0.7.0/src/text.rs +89 -0
  28. scraper_rust-0.7.0/src/tl_dom.rs +313 -0
  29. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/xpath.rs +10 -14
  30. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/tests/test_scraper.py +56 -2
  31. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/uv.lock +44 -44
  32. scraper_rust-0.5.1/Cargo.toml +0 -31
  33. scraper_rust-0.5.1/benchmarks/README.md +0 -440
  34. scraper_rust-0.5.1/benchmarks/bench_parse_memory.py +0 -523
  35. scraper_rust-0.5.1/benchmarks/bench_sync_async.py +0 -309
  36. scraper_rust-0.5.1/benchmarks/bench_vs_markupever.py +0 -273
  37. scraper_rust-0.5.1/src/html5_dict.rs +0 -493
  38. scraper_rust-0.5.1/src/prettify.rs +0 -207
  39. scraper_rust-0.5.1/src/selectors.rs +0 -85
  40. scraper_rust-0.5.1/src/text.rs +0 -76
  41. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/.github/workflows/benchmark.yml +0 -0
  42. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/.github/workflows/bump-version.yml +0 -0
  43. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/.gitignore +0 -0
  44. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/LICENSE +0 -0
  45. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/docs/README.md +0 -0
  46. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/docs/architecture.md +0 -0
  47. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/docs/development.md +0 -0
  48. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/docs/limits-and-errors.md +0 -0
  49. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/examples/demo.py +0 -0
  50. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/examples/demo_async_document.py +0 -0
  51. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/examples/demo_asyncio.py +0 -0
  52. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/examples/demo_prettify_url.py +0 -0
  53. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/py.typed +0 -0
  54. /scraper_rust-0.5.1/scraper_rs.pyi → /scraper_rust-0.7.0/scraper_rs/__init__.pyi +0 -0
  55. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/scraper_rs/asyncio.pyi +0 -0
  56. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/cache.rs +0 -0
  57. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/functions.rs +0 -0
  58. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/limits.rs +0 -0
  59. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/src/runtime.rs +0 -0
  60. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/tests/test_asyncio.py +0 -0
  61. {scraper_rust-0.5.1 → scraper_rust-0.7.0}/tests/test_performance.py +0 -0
@@ -82,6 +82,7 @@ jobs:
82
82
  with:
83
83
  name: wheels-${{ matrix.name }}
84
84
  path: dist/*.whl
85
+ if-no-files-found: error
85
86
 
86
87
  - name: Upload wheels to release
87
88
  uses: softprops/action-gh-release@v2
@@ -91,71 +92,6 @@ jobs:
91
92
  env:
92
93
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
93
94
 
94
- build-free-threaded-wheels:
95
- name: Build free-threaded wheels on ${{ matrix.name }} (Python ${{ matrix.python-version }})
96
- runs-on: ${{ matrix.runner }}
97
- strategy:
98
- fail-fast: false
99
- matrix:
100
- python-version: ['3.13t', '3.14t']
101
- include:
102
- - name: linux-x86_64
103
- runner: ubuntu-latest
104
- platform: linux
105
- - name: linux-arm64
106
- runner: ubuntu-24.04-arm
107
- platform: linux
108
- - name: windows
109
- runner: windows-latest
110
- platform: windows
111
- - name: macos
112
- runner: macos-latest
113
- platform: macos
114
-
115
- steps:
116
- - name: Checkout repository
117
- uses: actions/checkout@v6
118
-
119
- - name: Set up Python
120
- uses: actions/setup-python@v6
121
- with:
122
- python-version: ${{ matrix.python-version }}
123
- allow-prereleases: true
124
-
125
- - name: Install zig (for Linux manylinux builds)
126
- if: matrix.platform == 'linux'
127
- uses: goto-bus-stop/setup-zig@v2
128
- with:
129
- version: 0.11.0
130
-
131
- - name: Build free-threaded wheels (Linux - using zig for manylinux)
132
- if: matrix.platform == 'linux'
133
- uses: PyO3/maturin-action@v1
134
- with:
135
- command: build
136
- args: --release --strip --out dist --zig --interpreter python
137
-
138
- - name: Build free-threaded wheels (Windows/macOS)
139
- if: matrix.platform == 'windows' || matrix.platform == 'macos'
140
- uses: PyO3/maturin-action@v1
141
- with:
142
- command: build
143
- args: --release --strip --out dist
144
-
145
- - name: Upload free-threaded wheels as artifacts
146
- uses: actions/upload-artifact@v7
147
- with:
148
- name: wheels-free-threaded-${{ matrix.name }}-${{ matrix.python-version }}
149
- path: dist/*.whl
150
-
151
- - name: Upload free-threaded wheels to release
152
- uses: softprops/action-gh-release@v2
153
- with:
154
- files: dist/*.whl
155
- fail_on_unmatched_files: true
156
- env:
157
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
158
-
159
95
  build-sdist:
160
96
  name: Build source distribution
161
97
  runs-on: ubuntu-latest
@@ -174,6 +110,7 @@ jobs:
174
110
  with:
175
111
  name: sdist
176
112
  path: dist/*.tar.gz
113
+ if-no-files-found: error
177
114
 
178
115
  - name: Upload sdist to release
179
116
  uses: softprops/action-gh-release@v2
@@ -186,7 +123,7 @@ jobs:
186
123
  publish-to-pypi:
187
124
  name: Publish to PyPI
188
125
  runs-on: ubuntu-latest
189
- needs: [build-wheels, build-free-threaded-wheels, build-sdist]
126
+ needs: [build-wheels, build-sdist]
190
127
  environment:
191
128
  name: pypi
192
129
  url: https://pypi.org/p/scraper-rust
@@ -208,14 +145,48 @@ jobs:
208
145
  pattern: 'sdist'
209
146
  merge-multiple: true
210
147
 
211
- - name: List all files to be uploaded
148
+ - name: Validate distributions
212
149
  run: |
213
- echo "Files in dist directory:"
214
150
  ls -lah dist/
215
- echo "Total wheel files:"
216
- ls -1 dist/*.whl | wc -l
217
- echo "All wheel files:"
218
- ls -1 dist/*.whl
151
+ python - <<'PY'
152
+ from pathlib import Path
153
+ import struct
154
+ import tarfile
155
+ import zipfile
156
+
157
+ dist = Path("dist")
158
+ wheels = sorted(dist.glob("*.whl"))
159
+ sdists = sorted(dist.glob("*.tar.gz"))
160
+
161
+ if len(wheels) != 4:
162
+ raise SystemExit(f"Expected 4 platform wheels, found {len(wheels)}")
163
+ if len(sdists) != 1:
164
+ raise SystemExit(f"Expected 1 source distribution, found {len(sdists)}")
165
+
166
+ for wheel in wheels:
167
+ data = wheel.read_bytes()
168
+ eocd = data.rfind(b"PK\x05\x06")
169
+ if eocd < 0 or eocd + 22 > len(data):
170
+ raise SystemExit(f"{wheel}: missing or truncated ZIP end record")
171
+
172
+ comment_length = struct.unpack_from("<H", data, eocd + 20)[0]
173
+ archive_end = eocd + 22 + comment_length
174
+ if comment_length:
175
+ raise SystemExit(f"{wheel}: ZIP comments are not accepted by PyPI")
176
+ if archive_end != len(data):
177
+ trailing = len(data) - archive_end
178
+ raise SystemExit(f"{wheel}: contains {trailing} trailing bytes")
179
+
180
+ with zipfile.ZipFile(wheel) as archive:
181
+ bad_member = archive.testzip()
182
+ if bad_member is not None:
183
+ raise SystemExit(f"{wheel}: corrupt member {bad_member}")
184
+
185
+ with tarfile.open(sdists[0], mode="r:gz") as archive:
186
+ archive.getmembers()
187
+
188
+ print(f"Validated {len(wheels)} wheels and {len(sdists)} source distribution")
189
+ PY
219
190
 
220
191
  - name: Publish to PyPI
221
192
  uses: pypa/gh-action-pypi-publish@release/v1
@@ -16,7 +16,8 @@ jobs:
16
16
  strategy:
17
17
  fail-fast: false
18
18
  matrix:
19
- python-version: ${{ fromJSON(github.event.inputs.python-version != '' && format('["{0}"]', github.event.inputs.python-version) || '["3.10","3.11","3.12","3.13","3.13t","3.14","3.14t"]') }}
19
+ # PyO3 0.29 supports free-threaded CPython starting with 3.14.
20
+ python-version: ${{ fromJSON(github.event.inputs.python-version != '' && format('["{0}"]', github.event.inputs.python-version) || '["3.10","3.11","3.12","3.13","3.14","3.14t"]') }}
20
21
 
21
22
  steps:
22
23
  - name: Checkout repository
@@ -44,11 +45,7 @@ jobs:
44
45
  - name: Build extension module
45
46
  run: |
46
47
  source .venv/bin/activate
47
- if [[ "${{ matrix.python-version }}" == "3.13t" || "${{ matrix.python-version }}" == "3.14t" ]]; then
48
- maturin develop --release --locked
49
- else
50
- maturin develop --release --locked
51
- fi
48
+ maturin develop --release --locked
52
49
 
53
50
  - name: Run tests
54
51
  run: |
@@ -4,7 +4,7 @@ This file contains essential information for Large Language Models (LLMs) to eff
4
4
 
5
5
  ## Project Overview
6
6
 
7
- **scraper-rs** is a Python library providing high-performance HTML parsing and CSS/XPath selection through Python bindings to the Rust `scraper` crate. It uses PyO3 to create Python bindings for Rust code, offering zero Python-side parsing overhead.
7
+ **scraper-rs** is a Python library providing high-performance HTML parsing and CSS/XPath selection through Python bindings to Rust HTML tooling. It uses PyO3 to create Python bindings for Rust code, offering zero Python-side parsing overhead.
8
8
 
9
9
  - **Repository**: https://github.com/RustedBytes/scraper-rs
10
10
  - **Package Name**: `scraper-rust` (PyPI)
@@ -17,14 +17,13 @@ This file contains essential information for Large Language Models (LLMs) to eff
17
17
 
18
18
  ### Core Technologies
19
19
  - **Rust** (2024 edition)
20
- - `scraper` v0.27.0 from `rust-scraper/scraper` with the `atomic` feature - HTML parsing and CSS selection
21
20
  - `xee-xpath` v0.1.5 - XPath support
22
- - `html5ever` v0.39.0 - parse-tree dictionary helpers
21
+ - `rustedbytes-tl` v0.2.0 with `std` feature - HTML parsing, CSS selection, and parse-tree dictionary helpers
23
22
  - `pyo3` v0.28.3 with `abi3-py310`; the `extension-module` feature is enabled through the package build config
24
23
  - `pyo3-async-runtimes` v0.28 with `tokio-runtime` feature
25
24
  - `tokio` v1 (rt, macros) for async wrappers
26
25
  - **Python** 3.10+
27
- - Type annotations and stub files (`scraper_rs.pyi`, `py.typed`)
26
+ - Type annotations and stub files (`__init__.pyi`, `py.typed`)
28
27
  - Async support via `asyncio` module
29
28
  - **Build System**
30
29
  - `maturin` - Build PyO3 wheels
@@ -60,8 +59,9 @@ scraper-rs/
60
59
  │ ├── demo_asyncio.py # Async usage examples
61
60
  │ ├── demo_async_document.py
62
61
  │ └── demo_prettify_url.py
63
- ├── scraper_rs/ # Python package directory (async wrappers + import glue)
64
- ├── scraper_rs.pyi # Type stub file
62
+ ├── scraper_rs/
63
+ ├── __init__.pyi # Type stub file
64
+ │ └── asyncio.pyi # async wrappers
65
65
  ├── py.typed # PEP 561 marker for type support
66
66
  ├── Cargo.toml # Rust package configuration
67
67
  ├── pyproject.toml # Python package configuration
@@ -78,7 +78,7 @@ scraper-rs/
78
78
  - **`src/functions.rs`**: Top-level helpers: `parse()`, `parse_document()`, `parse_fragment()`, `prettify()`, `select()`, `select_first()`, `first()`, `xpath()`, `xpath_first()`
79
79
  - **`src/selectors.rs`**, **`src/xpath.rs`**, **`src/limits.rs`**: selector caches, XPath conversion, size limit enforcement, and UTF-8-safe truncation
80
80
 
81
- - **`scraper_rs.pyi`**: Type stubs for IDE support and type checking
81
+ - **`__init__.pyi`**: Type stubs for IDE support and type checking
82
82
 
83
83
  - **`Cargo.toml`**: Rust crate configuration
84
84
  - `crate-type = ["cdylib"]` for Python extension module
@@ -241,7 +241,7 @@ Module: `scraper_rs.asyncio`
241
241
  - Release build optimizations in `Cargo.toml` for smaller binary size
242
242
 
243
243
  ### Python Code
244
- - Type annotations required (uses `scraper_rs.pyi` stub file)
244
+ - Type annotations required (uses `__init__.pyi` stub file)
245
245
  - Follow PEP 8 style guidelines
246
246
  - Use descriptive variable names
247
247
  - Include docstrings for public functions
@@ -259,7 +259,7 @@ Module: `scraper_rs.asyncio`
259
259
  ### Adding a New Method to Document
260
260
  1. Add Rust implementation in `src/document.rs` under `impl Document`
261
261
  2. Add `#[pymethods]` annotation if needed
262
- 3. Update `scraper_rs.pyi` with type signature
262
+ 3. Update `__init__.pyi` with type signature
263
263
  4. Add tests in `tests/test_scraper.py`
264
264
  5. If it should be async, update `scraper_rs/asyncio.py` and `scraper_rs/asyncio.pyi`
265
265
  6. Update README.md with usage example
@@ -270,7 +270,7 @@ Module: `scraper_rs.asyncio`
270
270
  1. Add Rust function in `src/functions.rs` with `#[pyfunction]`
271
271
  2. Register with `m.add_function(wrap_pyfunction!(function_name, m)?)?` in `scraper_rs` module
272
272
  3. Export in `__all__` if applicable
273
- 4. Update `scraper_rs.pyi`
273
+ 4. Update `__init__.pyi`
274
274
  5. Add async wrapper in `scraper_rs/asyncio.py` and `scraper_rs/asyncio.pyi` if needed
275
275
  6. Add tests
276
276
  7. Update documentation
@@ -297,7 +297,7 @@ See `.github/workflows/release.yml` and `.github/workflows/bump-version.yml`
297
297
  4. **UTF-8 safety**: Truncation must respect character boundaries
298
298
  5. **Async wrappers**: Public async wrappers live in `scraper_rs/asyncio.py`; keep their stubs and tests aligned with sync behavior
299
299
  6. **Rust modules are split by responsibility**: `src/lib.rs` is the module entry point; add core behavior to the focused module that owns it
300
- 7. **Type stubs are critical**: Always update `scraper_rs.pyi` when changing APIs
300
+ 7. **Type stubs are critical**: Always update `__init__.pyi` when changing APIs
301
301
  8. **Tests are comprehensive**: Follow existing test patterns in `tests/`
302
302
  9. **Build artifacts**: `target/` and `.venv/` should not be committed
303
303
  10. **Use `just` commands**: They handle common workflows correctly
@@ -308,9 +308,8 @@ See `.github/workflows/release.yml` and `.github/workflows/bump-version.yml`
308
308
  - `pyo3 = { version = "0.28.3", features = ["abi3-py310"] }`
309
309
  - `pyo3-async-runtimes = { version = "0.28", features = ["tokio-runtime"] }`
310
310
  - `tokio = { version = "1", features = ["rt", "macros"] }`
311
- - `scraper = { version = "0.27.0", git = "https://github.com/rust-scraper/scraper", features = ["atomic"], rev = "9c1eff304e45a8bccb463968268ce6758703e821" }`
312
311
  - `xee-xpath = "0.1.5"`
313
- - `html5ever = "0.39.0"`
312
+ - `rustedbytes-tl = { version = "0.2.0", features = ["std"] }`
314
313
 
315
314
  ### Python Dependencies (pyproject.toml)
316
315
  **Runtime**: None (self-contained binary wheel)
@@ -339,7 +338,7 @@ See `.github/workflows/release.yml` and `.github/workflows/bump-version.yml`
339
338
  ## Resources
340
339
 
341
340
  - **PyO3 Documentation**: https://pyo3.rs/
342
- - **scraper crate**: https://docs.rs/scraper/
341
+ - **rustedbytes-tl crate**: https://crates.io/crates/rustedbytes-tl
343
342
  - **Maturin Guide**: https://www.maturin.rs/
344
343
  - **Project README**: `README.md`
345
344
  - **Example Usage**: `examples/demo.py`