diff --git a/sdks/python/README.md b/sdks/python/README.md index 57cbd56..5d1bc3c 100644 --- a/sdks/python/README.md +++ b/sdks/python/README.md @@ -8,7 +8,7 @@ Official Python SDK for the [Elydora](https://elydora.com) tamper-evident audit pip install elydora ``` -Requires Python 3.9+. +Requires Python 3.10+. ## Quick Start diff --git a/sdks/python/elydora/types.py b/sdks/python/elydora/types.py index 5692c82..185fdce 100644 --- a/sdks/python/elydora/types.py +++ b/sdks/python/elydora/types.py @@ -1,6 +1,6 @@ """Type definitions matching the Elydora backend API contract. -All types use TypedDict for structural typing, compatible with Python 3.9+. +All types use TypedDict for structural typing, compatible with Python 3.10+. """ import sys diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index fef010f..81ccbae 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=68.0", "wheel"] +requires = ["setuptools>=77.0.3", "wheel"] build-backend = "setuptools.build_meta" [project] @@ -7,24 +7,24 @@ name = "elydora" dynamic = ["version"] description = "Python SDK for the Elydora tamper-evident audit platform" readme = "README.md" -license = {text = "MIT"} -requires-python = ">=3.9" +license = "MIT" +requires-python = ">=3.10" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Typing :: Typed", ] dependencies = [ - "requests>=2.28.0", - "aiohttp>=3.8.0", - "cryptography>=41.0.0", + "requests>=2.33.0", + "aiohttp>=3.14.1", + "urllib3>=2.7.0", + "cryptography>=48.0.1", "tomlkit>=0.15.1,<1.0.0", "typing_extensions>=4.0.0;python_version<'3.11'", ] @@ -34,8 +34,8 @@ elydora = "elydora.cli:main" [project.optional-dependencies] dev = [ - "pytest>=7.0", - "pytest-asyncio>=0.21", + "pytest>=9.0.3", + "pytest-asyncio>=1.4.0", "mypy>=1.0", ] diff --git a/sdks/python/tests/test_package_metadata.py b/sdks/python/tests/test_package_metadata.py new file mode 100644 index 0000000..173767a --- /dev/null +++ b/sdks/python/tests/test_package_metadata.py @@ -0,0 +1,80 @@ +from pathlib import Path + +from packaging.requirements import Requirement +from packaging.version import Version +import tomlkit + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] + + +def _pyproject() -> dict[str, object]: + return tomlkit.parse((PROJECT_ROOT / "pyproject.toml").read_text(encoding="utf-8")) + + +def _project_metadata() -> dict[str, object]: + return _pyproject()["project"] + + +def _requirements(entries: object) -> dict[str, Requirement]: + assert isinstance(entries, list) + requirements = (Requirement(str(entry)) for entry in entries) + return {requirement.name: requirement for requirement in requirements} + + +def _assert_safe_floor( + requirement: Requirement, + vulnerable: str, + fixed: str, +) -> None: + assert Version(vulnerable) not in requirement.specifier + assert Version(fixed) in requirement.specifier + + +def test_python_support_matches_maintained_runtime_line() -> None: + project = _project_metadata() + + assert project["requires-python"] == ">=3.10" + assert "Programming Language :: Python :: 3.9" not in project["classifiers"] + assert "Programming Language :: Python :: 3.14" in project["classifiers"] + + +def test_license_uses_current_packaging_metadata() -> None: + document = _pyproject() + project = document["project"] + assert isinstance(project, dict) + build_system = document["build-system"] + assert isinstance(build_system, dict) + build_requirements = _requirements(build_system["requires"]) + + assert project["license"] == "MIT" + assert all( + not str(classifier).startswith("License ::") + for classifier in project["classifiers"] + ) + assert Version("77.0.3") in build_requirements["setuptools"].specifier + + +def test_dependency_floors_exclude_known_vulnerable_releases() -> None: + project = _project_metadata() + runtime = _requirements(project["dependencies"]) + optional = project["optional-dependencies"] + assert isinstance(optional, dict) + development = _requirements(optional["dev"]) + + _assert_safe_floor(runtime["requests"], "2.32.5", "2.33.0") + _assert_safe_floor(runtime["aiohttp"], "3.13.5", "3.14.1") + _assert_safe_floor(runtime["urllib3"], "2.6.3", "2.7.0") + _assert_safe_floor(runtime["cryptography"], "41.0.0", "48.0.1") + _assert_safe_floor(development["pytest"], "8.4.2", "9.0.3") + + +def test_async_test_runner_supports_pytest_nine() -> None: + project = _project_metadata() + optional = project["optional-dependencies"] + assert isinstance(optional, dict) + development = _requirements(optional["dev"]) + + requirement = development["pytest-asyncio"] + assert Version("1.3.0") not in requirement.specifier + assert Version("1.4.0") in requirement.specifier