Make xhtml2pdf and python-bidi optional dependencies for PDF support

python-bidi >= 0.5.0 requires Rust/maturin to build from source, which
fails on platforms like Termux (Android) that lack a Rust toolchain.

Since PDF report generation is an optional feature (--pdf flag), move
xhtml2pdf (which depends on python-bidi, arabic-reshaper, reportlab)
to an optional extras group. Users can install PDF support with:
  pip install maigret[pdf]

Also add graceful error handling in save_pdf_report() when xhtml2pdf
is not installed, with a clear message about how to install it.

Agent-Logs-Url: https://github.com/soxoj/maigret/sessions/ab19aa96-7175-47bd-af88-34b3106269e9

Co-authored-by: soxoj <31013580+soxoj@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-07 23:16:36 +00:00
committed by GitHub
parent d20aad7dc2
commit 4da640609c
5 changed files with 65 additions and 54 deletions
+3
View File
@@ -56,6 +56,9 @@ If you encounter frequent false positive results, we recommend installing the la
# install from pypi
pip3 install maigret
# install with PDF report support
pip3 install maigret[pdf]
# usage
maigret username
+7 -1
View File
@@ -79,7 +79,13 @@ def save_pdf_report(filename: str, context: dict):
filled_template = template.render(**context)
# moved here to speed up the launch of Maigret
from xhtml2pdf import pisa # type: ignore[import-untyped]
try:
from xhtml2pdf import pisa # type: ignore[import-untyped]
except ImportError:
raise ImportError(
"PDF report generation requires the 'xhtml2pdf' package. "
"Install it with: pip install maigret[pdf]"
)
with open(filename, "w+b") as f:
pisa.pisaDocument(io.StringIO(filled_template), dest=f, default_css=css)
+4 -6
View File
@@ -8,7 +8,6 @@
aiodns>=3.0.0
aiohttp>=3.8.6
aiohttp-socks>=0.7.1
arabic-reshaper~=3.0.0
async-timeout
attrs>=22.2.0
certifi>=2023.7.22
@@ -24,9 +23,7 @@ MarkupSafe
mock>=4.0.3
multidict
pycountry>=22.3.5
PyPDF2>=3.0.1
PySocks>=1.7.1
python-bidi>=0.4.2
requests
requests-futures>=1.0.0
six>=1.16.0
@@ -37,11 +34,12 @@ torrequest>=0.1.0
tqdm
typing-extensions
webencodings>=0.5.1
svglib
xhtml2pdf~=0.2.11
XMind>=1.2.0
yarl
networkx
pyvis>=0.2.1
reportlab
cloudscraper>=1.2.71
# Optional PDF dependencies (install separately for PDF report support):
# xhtml2pdf~=0.2.11
# arabic-reshaper~=3.0.0
# reportlab
Generated
+47 -42
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.3.3 and should not be changed by hand.
[[package]]
name = "about-time"
@@ -234,9 +234,10 @@ graphemeu = "0.7.2"
name = "arabic-reshaper"
version = "3.0.0"
description = "Reconstruct Arabic sentences to be used in applications that do not support Arabic"
optional = false
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "arabic_reshaper-3.0.0-py3-none-any.whl", hash = "sha256:3f71d5034bb694204a239a6f1ebcf323ac3c5b059de02259235e2016a1a5e2dc"},
{file = "arabic_reshaper-3.0.0.tar.gz", hash = "sha256:ffcd13ba5ec007db71c072f5b23f420da92ac7f268512065d49e790e62237099"},
@@ -267,9 +268,10 @@ tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"]
name = "asn1crypto"
version = "1.5.1"
description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
optional = false
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
{file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
@@ -852,9 +854,10 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
name = "cryptography"
version = "46.0.6"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
optional = true
python-versions = "!=3.9.0,!=3.9.1,>=3.8"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8"},
{file = "cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30"},
@@ -925,9 +928,10 @@ test-randomorder = ["pytest-randomly"]
name = "cssselect2"
version = "0.7.0"
description = "CSS selectors for Python ElementTree"
optional = false
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"},
{file = "cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a"},
@@ -1072,9 +1076,10 @@ dotenv = ["python-dotenv"]
name = "freetype-py"
version = "2.5.1"
description = "Freetype python bindings"
optional = false
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "freetype-py-2.5.1.zip", hash = "sha256:cfe2686a174d0dd3d71a9d8ee9bf6a2c23f5872385cf8ce9f24af83d076e2fbd"},
{file = "freetype_py-2.5.1-py3-none-macosx_10_9_universal2.whl", hash = "sha256:d01ded2557694f06aa0413f3400c0c0b2b5ebcaabeef7aaf3d756be44f51e90b"},
@@ -2089,9 +2094,10 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"]
name = "oscrypto"
version = "1.3.0"
description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD."
optional = false
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"},
{file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"},
@@ -2262,6 +2268,7 @@ files = [
{file = "pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e"},
{file = "pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4"},
]
markers = {main = "extra == \"pdf\""}
[package.extras]
docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
@@ -2438,9 +2445,10 @@ tests = ["pytest"]
name = "pycairo"
version = "1.29.0"
description = "Python interface for cairo"
optional = false
optional = true
python-versions = ">=3.10"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "pycairo-1.29.0-cp310-cp310-win32.whl", hash = "sha256:96c67e6caba72afd285c2372806a0175b1aa2f4537aa88fb4d9802d726effcd1"},
{file = "pycairo-1.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:65bddd944aee9f7d7d72821b1c87e97593856617c2820a78d589d66aa8afbd08"},
@@ -2634,9 +2642,10 @@ windows-terminal = ["colorama (>=0.4.6)"]
name = "pyhanko"
version = "0.25.3"
description = "Tools for stamping and signing PDF files"
optional = false
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "pyHanko-0.25.3-py3-none-any.whl", hash = "sha256:d66ec499f057191df100f322c2fd22949057a9b0d981f4e75bc077c1a817497f"},
{file = "pyhanko-0.25.3.tar.gz", hash = "sha256:e879fd44e20f4b7726e75c62e8c7b0c41ea41f8fa5bda626bc7d206ae3d30dec"},
@@ -2670,9 +2679,10 @@ xmp = ["defusedxml (>=0.7.1,<0.8.0)"]
name = "pyhanko-certvalidator"
version = "0.26.5"
description = "Validates X.509 certificates and paths; forked from wbond/certvalidator"
optional = false
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "pyhanko_certvalidator-0.26.5-py3-none-any.whl", hash = "sha256:86a56df420bfb273ba881826b76245a53b2bd039fea7a7826231dbe76d761a8a"},
{file = "pyhanko_certvalidator-0.26.5.tar.gz", hash = "sha256:800f5a7744d23870a5203cb38007689902c79c44e7374dab0c9b02e1b1a89bd4"},
@@ -2709,9 +2719,10 @@ diagrams = ["jinja2", "railroad-diagrams"]
name = "pypdf"
version = "6.9.2"
description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
optional = false
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "pypdf-6.9.2-py3-none-any.whl", hash = "sha256:662cf29bcb419a36a1365232449624ab40b7c2d0cfc28e54f42eeecd1fd7e844"},
{file = "pypdf-6.9.2.tar.gz", hash = "sha256:7f850faf2b0d4ab936582c05da32c52214c2b089d61a316627b5bfb5b0dab46c"},
@@ -2728,25 +2739,6 @@ docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"]
full = ["Pillow (>=8.0.0)", "cryptography"]
image = ["Pillow (>=8.0.0)"]
[[package]]
name = "pypdf2"
version = "3.0.1"
description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "PyPDF2-3.0.1.tar.gz", hash = "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440"},
{file = "pypdf2-3.0.1-py3-none-any.whl", hash = "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928"},
]
[package.extras]
crypto = ["PyCryptodome"]
dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "wheel"]
docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"]
full = ["Pillow", "PyCryptodome"]
image = ["Pillow"]
[[package]]
name = "pysocks"
version = "1.7.1"
@@ -2860,9 +2852,10 @@ pytest = ">=7.4,<8.2.2 || >8.2.2"
name = "python-bidi"
version = "0.6.7"
description = "Python Bidi layout wrapping the Rust crate unicode-bidi"
optional = false
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "python_bidi-0.6.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:94dbfd6a6ec0ae64b5262290bf014d6063f9ac8688bda9ec668dc175378d2c80"},
{file = "python_bidi-0.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8274ff02d447cca026ba00f56070ba15f95e184b2d028ee0e4b6c9813d2aaf9"},
@@ -3086,9 +3079,10 @@ networkx = ">=1.11"
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
@@ -3149,9 +3143,10 @@ files = [
name = "qrcode"
version = "8.0"
description = "QR Code image generator"
optional = false
optional = true
python-versions = "<4.0,>=3.9"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "qrcode-8.0-py3-none-any.whl", hash = "sha256:9fc05f03305ad27a709eb742cf3097fa19e6f6f93bb9e2f039c0979190f6f1b1"},
{file = "qrcode-8.0.tar.gz", hash = "sha256:025ce2b150f7fe4296d116ee9bad455a6643ab4f6e7dce541613a4758cbce347"},
@@ -3176,6 +3171,7 @@ files = [
{file = "reportlab-4.4.10-py3-none-any.whl", hash = "sha256:5abc815746ae2bc44e7ff25db96814f921349ca814c992c7eac3c26029bf7c24"},
{file = "reportlab-4.4.10.tar.gz", hash = "sha256:5cbbb34ac3546039d0086deb2938cdec06b12da3cdb836e813258eb33cd28487"},
]
markers = {main = "extra == \"pdf\""}
[package.dependencies]
charset-normalizer = "*"
@@ -3266,9 +3262,10 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
name = "rlpycairo"
version = "0.4.0"
description = "Plugin backend renderer for reportlab.graphics.renderPM"
optional = false
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "rlpycairo-0.4.0-py3-none-any.whl", hash = "sha256:3ce83825d5761c03bc3571c7db12a336ad51417e63189e3512d11b8922576aa9"},
{file = "rlpycairo-0.4.0.tar.gz", hash = "sha256:07c2c3c47828e83d9c09657a54ecbcd1a97aac9dc199780234456d3473faadc7"},
@@ -3354,9 +3351,10 @@ files = [
name = "svglib"
version = "1.6.0"
description = "A pure-Python library for reading and converting SVG"
optional = false
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "svglib-1.6.0-py3-none-any.whl", hash = "sha256:9aea8e2e81cbbf9c844460e4c7dc90e0a06aea7983bc201975ccd279d7b2d194"},
{file = "svglib-1.6.0.tar.gz", hash = "sha256:4c38a274a744ef0d1677f55d5d62fc0fb798819f813e52872a796e615741733d"},
@@ -3376,9 +3374,10 @@ dev = ["mypy (>=1.18.1)", "pre-commit (>=4.3.0)", "pytest (>=8.3.5)", "pytest-co
name = "tinycss2"
version = "1.4.0"
description = "A tiny CSS parser"
optional = false
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"},
{file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"},
@@ -3506,10 +3505,10 @@ files = [
name = "tzdata"
version = "2024.2"
description = "Provider of IANA time zone data"
optional = false
optional = true
python-versions = ">=2"
groups = ["main"]
markers = "platform_system == \"Windows\""
markers = "extra == \"pdf\" and platform_system == \"Windows\""
files = [
{file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
{file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
@@ -3519,9 +3518,10 @@ files = [
name = "tzlocal"
version = "5.2"
description = "tzinfo object for the local timezone"
optional = false
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"},
{file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"},
@@ -3537,9 +3537,10 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)
name = "uritools"
version = "4.0.3"
description = "URI parsing, classification and composition"
optional = false
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "uritools-4.0.3-py3-none-any.whl", hash = "sha256:bae297d090e69a0451130ffba6f2f1c9477244aa0a5543d66aed2d9f77d0dd9c"},
{file = "uritools-4.0.3.tar.gz", hash = "sha256:ee06a182a9c849464ce9d5fa917539aacc8edd2a4924d1b7aabeeecabcae3bc2"},
@@ -3609,9 +3610,10 @@ watchdog = ["watchdog (>=2.3)"]
name = "xhtml2pdf"
version = "0.2.17"
description = "PDF generator using HTML and CSS"
optional = false
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"pdf\""
files = [
{file = "xhtml2pdf-0.2.17-py3-none-any.whl", hash = "sha256:61a7ecac829fed518f7dbcb916e9d56bea6e521e02e54644b3d0ca33f0658315"},
{file = "xhtml2pdf-0.2.17.tar.gz", hash = "sha256:09ddbc31aa0e38a16f2f3cb73be89af5f7c968c17a564afdd685d280e39c526d"},
@@ -3790,7 +3792,10 @@ idna = ">=2.0"
multidict = ">=4.0"
propcache = ">=0.2.1"
[extras]
pdf = ["xhtml2pdf"]
[metadata]
lock-version = "2.1"
python-versions = "^3.10"
content-hash = "64280126055cdc808ace9bcaa01a1aa2ed2c1ee8892e4acf6a093441168535c8"
content-hash = "c7cc6e5d313cef228fbc78ee078748e94b65339a667f8e9fb5d396ce582a0793"
+4 -5
View File
@@ -34,7 +34,6 @@ python = "^3.10"
aiodns = ">=3,<5"
aiohttp = "^3.12.14"
aiohttp-socks = ">=0.10.1,<0.12.0"
arabic-reshaper = "^3.0.0"
async-timeout = "^5.0.1"
attrs = ">=25.3,<27.0"
certifi = ">=2025.6.15,<2027.0.0"
@@ -50,9 +49,7 @@ MarkupSafe = "^3.0.2"
mock = "^5.1.0"
multidict = "^6.6.3"
pycountry = ">=24.6.1,<27.0.0"
PyPDF2 = "^3.0.1"
PySocks = "^1.7.1"
python-bidi = "^0.6.3"
requests = "^2.32.4"
requests-futures = "^1.0.2"
requests-toolbelt = "^1.0.0"
@@ -64,17 +61,19 @@ torrequest = "^0.1.0"
alive_progress = "^3.2.0"
typing-extensions = "^4.14.1"
webencodings = "^0.5.1"
xhtml2pdf = "^0.2.11"
XMind = "^1.2.0"
yarl = "^1.20.1"
networkx = "^2.6.3"
pyvis = "^0.3.2"
reportlab = "^4.4.3"
cloudscraper = "^1.2.71"
flask = {extras = ["async"], version = "^3.1.1"}
asgiref = "^3.9.1"
platformdirs = "^4.3.8"
curl-cffi = ">=0.14,<1.0"
xhtml2pdf = {version = "^0.2.11", optional = true}
[tool.poetry.extras]
pdf = ["xhtml2pdf"]
[tool.poetry.group.dev.dependencies]