Compare commits

..

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 4da640609c 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>
2026-04-07 23:16:36 +00:00
copilot-swe-agent[bot] d20aad7dc2 Initial plan 2026-04-07 23:07:57 +00:00
Olivier Cervello a8e7ab4540 Bump lxml minimum to 6.0.2 for Python 3.14 compatibility (#2279)
* Bump lxml minimum to 6.0.2 for Python 3.14 compatibility

lxml 5.x fails to build on Python 3.14 due to incompatible pointer
types in Cython-generated C code. lxml 6.0.2 compiles correctly.

Fixes #2266

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update poetry.lock to match pyproject.toml changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Soxoj <31013580+soxoj@users.noreply.github.com>
2026-04-08 00:55:21 +02:00
Soxoj 6db1df2ddb Fix failing test for custom DB path resolution (#2468)
* Fix `--db` bug

* Fix test_resolve_db_path_custom_file to create the file before testing

Agent-Logs-Url: https://github.com/soxoj/maigret/sessions/3ea7b2e8-0565-4fca-8ec2-eff8eb4ee617

Co-authored-by: soxoj <31013580+soxoj@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-08 00:53:57 +02:00
11 changed files with 159 additions and 70 deletions
+53 -1
View File
@@ -82,11 +82,63 @@ id types, sites will be filtered automatically.
ids. Useful for repeated scanning with found known irrelevant usernames.
``--db`` - Load Maigret database from a JSON file or an online, valid,
JSON file.
JSON file. See :ref:`custom-database` below.
``--no-autoupdate`` - Disable the automatic database update check that
runs at startup. The currently cached (or bundled) database is used
as-is.
``--force-update`` - Force a database update check at startup, ignoring
the usual check interval. Implies ``--no-autoupdate`` for the rest of
the run after the explicit update finishes.
``--retries RETRIES`` - Count of attempts to restart temporarily failed
requests.
.. _custom-database:
Using a custom sites database
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``--db`` flag accepts three forms:
1. **HTTP(S) URL** — fetched as-is, e.g.
``--db https://example.com/my_db.json``.
2. **Local file path** — absolute (``--db /tmp/private.json``) or
relative to the current working directory
(``--db LLM/maigret_private_db.json``).
3. **Module-relative path** — kept for backwards compatibility, resolved
against the installed ``maigret/`` package directory (e.g. the
default ``resources/data.json``).
Resolution order for local paths: the path is first tried as given
(absolute or cwd-relative); if that file does not exist, Maigret falls
back to the legacy module-relative resolution. If neither location
contains the file, Maigret exits with an error rather than silently
loading the bundled database.
When ``--db`` points to a custom file, automatic database updates are
skipped — the file is used exactly as provided.
On every run Maigret prints the database it actually loaded, for
example::
[+] Using sites database: /path/to/maigret_private_db.json (6 sites)
If loading the requested database fails for any other reason (corrupt
JSON, missing required keys, …), Maigret prints a warning, falls back
to the bundled database, and reports the fallback explicitly::
[-] Falling back to bundled database: /…/maigret/resources/data.json
[+] Using sites database: /…/maigret/resources/data.json (3154 sites)
A typical invocation against a private database, with auto-update
disabled and all sites scanned, looks like::
python3 -m maigret username \
--db LLM/maigret_private_db.json \
--no-autoupdate -a
Reports
-------
+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
+13 -1
View File
@@ -203,7 +203,19 @@ def resolve_db_path(
if is_url:
return db_file_arg
if not is_default:
return path.join(path.dirname(path.realpath(__file__)), db_file_arg)
# Try the path as-is (absolute or relative to cwd) first.
if path.isfile(db_file_arg):
return path.abspath(db_file_arg)
# Fall back to legacy behavior: resolve relative to the maigret module dir.
module_relative = path.join(path.dirname(path.realpath(__file__)), db_file_arg)
if module_relative != db_file_arg and path.isfile(module_relative):
return module_relative
if module_relative != db_file_arg:
raise FileNotFoundError(
f"Custom database file not found: {db_file_arg!r} "
f"(also tried {module_relative!r})"
)
raise FileNotFoundError(f"Custom database file not found: {db_file_arg!r}")
# Auto-update disabled
if no_autoupdate:
+18 -8
View File
@@ -574,13 +574,17 @@ async def main():
color=not args.no_color,
)
db_file = resolve_db_path(
db_file_arg=args.db_file,
no_autoupdate=args.no_autoupdate or args.force_update,
meta_url=settings.db_update_meta_url,
check_interval_hours=settings.autoupdate_check_interval_hours,
color=not args.no_color,
)
try:
db_file = resolve_db_path(
db_file_arg=args.db_file,
no_autoupdate=args.no_autoupdate or args.force_update,
meta_url=settings.db_update_meta_url,
check_interval_hours=settings.autoupdate_check_interval_hours,
color=not args.no_color,
)
except FileNotFoundError as e:
logger.error(str(e))
sys.exit(2)
if args.top_sites == 0 or args.all_sites:
args.top_sites = sys.maxsize
@@ -597,11 +601,17 @@ async def main():
# Create object with all information about sites we are aware of.
try:
db = MaigretDatabase().load_from_path(db_file)
query_notify.success(f'Using sites database: {db_file} ({len(db.sites)} sites)')
except Exception as e:
logger.warning(f"Failed to load database from {db_file}: {e}")
if db_file != BUNDLED_DB_PATH:
logger.warning("Falling back to bundled database")
query_notify.warning(
f'Falling back to bundled database: {BUNDLED_DB_PATH}'
)
db = MaigretDatabase().load_from_path(BUNDLED_DB_PATH)
query_notify.success(
f'Using sites database: {BUNDLED_DB_PATH} ({len(db.sites)} sites)'
)
else:
raise
get_top_sites_for_id = lambda x: db.ranked_sites_dict(
+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)
+1 -1
View File
@@ -293,7 +293,7 @@
"method": "vimeo"
},
"headers": {
"Authorization": "jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NzU1MDM2ODAsInVzZXJfaWQiOm51bGwsImFwcF9pZCI6NTg0NzksInNjb3BlcyI6InB1YmxpYyIsInRlYW1fdXNlcl9pZCI6bnVsbCwianRpIjoiMWVlMjg4ZTQtZGRkMC00ZWYyLTgyOWYtMDRmMjg3NjI1MTA5In0.FkO1cjuIS9jpn5nxkRWWp-jr0Meh_WUvRP1L46qVhcw"
"Authorization": "jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NzU1Nzk3NjAsInVzZXJfaWQiOm51bGwsImFwcF9pZCI6NTg0NzksInNjb3BlcyI6InB1YmxpYyIsInRlYW1fdXNlcl9pZCI6bnVsbCwianRpIjoiYWQwNWIzMWUtMGU4NC00NDUzLThjZGEtZWFjNDkxNzYwOTVhIn0.Q7VI5NgbZ5rGsmgDQMxa8cKIxWiFwNYa3BLgIBIuj54"
},
"urlProbe": "https://api.vimeo.com/users/{username}?fields=name%2Cgender%2Cbio%2Curi%2Clink%2Cbackground_video%2Clocation_details%2Cpictures%2Cverified%2Cmetadata.public_videos.total%2Cavailable_for_hire%2Ccan_work_remotely%2Cmetadata.connections.videos.total%2Cmetadata.connections.albums.total%2Cmetadata.connections.followers.total%2Cmetadata.connections.following.total%2Cmetadata.public_videos.total%2Cmetadata.connections.vimeo_experts.is_enrolled%2Ctotal_collection_count%2Ccreated_time%2Cprofile_preferences%2Cmembership%2Cclients%2Cskills%2Cproject_types%2Crates%2Ccategories%2Cis_expert%2Cprofile_discovery%2Cwebsites%2Ccontact_emails&fetch_user_profile=1",
"checkType": "status_code",
+3 -3
View File
@@ -1,8 +1,8 @@
{
"version": 1,
"updated_at": "2026-04-07T16:18:18Z",
"sites_count": 3155,
"updated_at": "2026-04-07T22:38:58Z",
"sites_count": 3154,
"min_maigret_version": "0.5.0",
"data_sha256": "279fb90280814cd11dcd711b1b8e6c6a99fefea4ce6ef05c9d64dced6ac795c0",
"data_sha256": "1f1abd85bad2a358e4af1919f2d89d38bf374640e78f6f33b362ac620c55d47f",
"data_url": "https://raw.githubusercontent.com/soxoj/maigret/main/maigret/resources/data.json"
}
+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
+48 -42
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.2.1 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"},
@@ -724,6 +726,7 @@ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {dev = "platform_system == \"Windows\" or sys_platform == \"win32\""}
[[package]]
name = "coverage"
@@ -851,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"},
@@ -924,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"},
@@ -1071,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"},
@@ -2088,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"},
@@ -2261,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"]
@@ -2437,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"},
@@ -2633,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"},
@@ -2669,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"},
@@ -2708,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"},
@@ -2727,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"
@@ -2859,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"},
@@ -3085,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"},
@@ -3148,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"},
@@ -3175,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 = "*"
@@ -3265,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"},
@@ -3353,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"},
@@ -3375,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"},
@@ -3505,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"},
@@ -3518,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"},
@@ -3536,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"},
@@ -3608,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"},
@@ -3789,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 = "edc8e2596a73519ad93c4a1e8c235f95d8070c5ecde2b2d7aba16f58be9e6e0a"
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]
+5 -2
View File
@@ -129,8 +129,11 @@ def test_resolve_db_path_custom_url():
assert result == "https://example.com/db.json"
def test_resolve_db_path_custom_file():
result = resolve_db_path("custom/path.json")
def test_resolve_db_path_custom_file(tmp_path):
custom_db = tmp_path / "custom" / "path.json"
custom_db.parent.mkdir(parents=True)
custom_db.write_text("{}")
result = resolve_db_path(str(custom_db))
assert result.endswith("custom/path.json")