diff --git a/pyproject.toml b/pyproject.toml index ee237d5b3..5d3107575 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "Crawl4AI" dynamic = ["version"] description = "🚀🤖 Crawl4AI: Open-source LLM Friendly Web Crawler & scraper" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.10,<3.15" license = "Apache-2.0" authors = [ {name = "Unclecode", email = "unclecode@kidocode.com"} diff --git a/setup.py b/setup.py index 4d6e0575a..58cb6abd6 100644 --- a/setup.py +++ b/setup.py @@ -61,5 +61,5 @@ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ], - python_requires=">=3.10", + python_requires=">=3.10,<3.15", ) diff --git a/tests/test_python_requires_metadata.py b/tests/test_python_requires_metadata.py new file mode 100644 index 000000000..876744bbb --- /dev/null +++ b/tests/test_python_requires_metadata.py @@ -0,0 +1,45 @@ +import ast +from pathlib import Path + + +EXPECTED_REQUIRES_PYTHON = ">=3.10,<3.15" +ROOT = Path(__file__).resolve().parents[1] + + +def _read_pyproject_requires_python() -> str: + in_project_section = False + + for line in (ROOT / "pyproject.toml").read_text(encoding="utf-8").splitlines(): + stripped = line.strip() + if stripped == "[project]": + in_project_section = True + continue + if in_project_section and stripped.startswith("["): + break + if in_project_section and stripped.startswith("requires-python"): + return ast.literal_eval(stripped.split("=", 1)[1].strip()) + + raise AssertionError("pyproject.toml is missing [project].requires-python") + + +def _read_setup_python_requires() -> str: + tree = ast.parse((ROOT / "setup.py").read_text(encoding="utf-8")) + + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + if not isinstance(node.func, ast.Name) or node.func.id != "setup": + continue + for keyword in node.keywords: + if keyword.arg == "python_requires": + return ast.literal_eval(keyword.value) + + raise AssertionError("setup.py is missing setup(python_requires=...)") + + +def test_project_metadata_caps_future_python_versions() -> None: + assert _read_pyproject_requires_python() == EXPECTED_REQUIRES_PYTHON + + +def test_legacy_setup_py_matches_project_python_requirement() -> None: + assert _read_setup_python_requires() == EXPECTED_REQUIRES_PYTHON