diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 241f2e2..6c5666c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9210f8b..a113d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to the AxonFlow Python SDK will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.0.0] - 2026-03-16 + +### Breaking Changes + +- **Dropped Python 3.9 support.** Python 3.9 reached end-of-life in October 2025. The minimum supported version is now Python 3.10. Users on 3.9 should pin to `axonflow<5.0.0`. + +### Changed + +- Removed `eval_type_backport` dependency (was only required for Python 3.9). +- Modernized type annotations across the codebase: `Optional[X]` → `X | None`, `typing.Callable` → `collections.abc.Callable` (now valid without `from __future__ import annotations` on 3.10+). + +--- + ## [4.2.0] - 2026-03-16 ### Added diff --git a/README.md b/README.md index 1a62204..4ad6bb4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Enterprise AI Governance in 3 Lines of Code. [![PyPI version](https://badge.fury.io/py/axonflow.svg)](https://badge.fury.io/py/axonflow) -[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/) +[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Type hints](https://img.shields.io/badge/type%20hints-mypy-brightgreen.svg)](http://mypy-lang.org/) diff --git a/axonflow/_version.py b/axonflow/_version.py index 74710ae..e8038eb 100644 --- a/axonflow/_version.py +++ b/axonflow/_version.py @@ -1,3 +1,3 @@ """Single source of truth for the AxonFlow SDK version.""" -__version__ = "4.1.0" +__version__ = "5.0.0" diff --git a/axonflow/adapters/langgraph.py b/axonflow/adapters/langgraph.py index 81b6545..5d25822 100644 --- a/axonflow/adapters/langgraph.py +++ b/axonflow/adapters/langgraph.py @@ -34,8 +34,9 @@ import asyncio import json +from collections.abc import Callable from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any from axonflow.exceptions import PolicyViolationError from axonflow.workflow import ( diff --git a/axonflow/interceptors/anthropic.py b/axonflow/interceptors/anthropic.py index ee1672b..f146d64 100644 --- a/axonflow/interceptors/anthropic.py +++ b/axonflow/interceptors/anthropic.py @@ -25,8 +25,9 @@ from __future__ import annotations import asyncio +from collections.abc import Callable from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from axonflow.exceptions import PolicyViolationError from axonflow.interceptors.base import BaseInterceptor diff --git a/axonflow/interceptors/bedrock.py b/axonflow/interceptors/bedrock.py index d6ff3db..9aa30c2 100644 --- a/axonflow/interceptors/bedrock.py +++ b/axonflow/interceptors/bedrock.py @@ -28,8 +28,9 @@ import asyncio import json +from collections.abc import Callable from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from axonflow.exceptions import PolicyViolationError from axonflow.interceptors.base import BaseInterceptor diff --git a/axonflow/interceptors/gemini.py b/axonflow/interceptors/gemini.py index 0da0e67..419f0d1 100644 --- a/axonflow/interceptors/gemini.py +++ b/axonflow/interceptors/gemini.py @@ -22,8 +22,9 @@ from __future__ import annotations import asyncio +from collections.abc import Callable from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from axonflow.exceptions import PolicyViolationError from axonflow.interceptors.base import BaseInterceptor diff --git a/axonflow/interceptors/ollama.py b/axonflow/interceptors/ollama.py index 8b7f461..2082578 100644 --- a/axonflow/interceptors/ollama.py +++ b/axonflow/interceptors/ollama.py @@ -27,8 +27,9 @@ from __future__ import annotations import asyncio +from collections.abc import Callable from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from axonflow.exceptions import PolicyViolationError from axonflow.interceptors.base import BaseInterceptor diff --git a/axonflow/interceptors/openai.py b/axonflow/interceptors/openai.py index 9c419e6..da69a2f 100644 --- a/axonflow/interceptors/openai.py +++ b/axonflow/interceptors/openai.py @@ -24,8 +24,9 @@ from __future__ import annotations import asyncio +from collections.abc import Callable from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from axonflow.exceptions import PolicyViolationError from axonflow.interceptors.base import BaseInterceptor diff --git a/axonflow/masfeat.py b/axonflow/masfeat.py index 6ab40eb..44ddb48 100644 --- a/axonflow/masfeat.py +++ b/axonflow/masfeat.py @@ -11,7 +11,7 @@ from dataclasses import dataclass, field from datetime import datetime from enum import Enum -from typing import Any, Optional +from typing import Any # Python's datetime.fromisoformat requires exactly 6 fractional digits _MICROSECOND_PRECISION = 6 @@ -121,8 +121,8 @@ class Finding: category: str description: str status: FindingStatus - remediation: Optional[str] = None - due_date: Optional[datetime] = None + remediation: str | None = None + due_date: datetime | None = None # =========================================================================== @@ -140,18 +140,18 @@ class AISystemRegistry: system_name: str use_case: AISystemUseCase owner_team: str - customer_impact: Optional[int] - model_complexity: Optional[int] - human_reliance: Optional[int] + customer_impact: int | None + model_complexity: int | None + human_reliance: int | None materiality: MaterialityClassification status: SystemStatus - created_at: Optional[datetime] - updated_at: Optional[datetime] - description: Optional[str] = None - technical_owner: Optional[str] = None - business_owner: Optional[str] = None - metadata: Optional[dict[str, Any]] = None - created_by: Optional[str] = None + created_at: datetime | None + updated_at: datetime | None + description: str | None = None + technical_owner: str | None = None + business_owner: str | None = None + metadata: dict[str, Any] | None = None + created_by: str | None = None @dataclass @@ -181,25 +181,25 @@ class FEATAssessment: system_id: str assessment_type: str status: FEATAssessmentStatus - assessment_date: Optional[datetime] - created_at: Optional[datetime] - updated_at: Optional[datetime] - valid_until: Optional[datetime] = None - fairness_score: Optional[int] = None - ethics_score: Optional[int] = None - accountability_score: Optional[int] = None - transparency_score: Optional[int] = None - overall_score: Optional[int] = None - fairness_details: Optional[dict[str, Any]] = None - ethics_details: Optional[dict[str, Any]] = None - accountability_details: Optional[dict[str, Any]] = None - transparency_details: Optional[dict[str, Any]] = None - findings: Optional[list[Finding]] = None - recommendations: Optional[list[str]] = None - assessors: Optional[list[str]] = None - approved_by: Optional[str] = None - approved_at: Optional[datetime] = None - created_by: Optional[str] = None + assessment_date: datetime | None + created_at: datetime | None + updated_at: datetime | None + valid_until: datetime | None = None + fairness_score: int | None = None + ethics_score: int | None = None + accountability_score: int | None = None + transparency_score: int | None = None + overall_score: int | None = None + fairness_details: dict[str, Any] | None = None + ethics_details: dict[str, Any] | None = None + accountability_details: dict[str, Any] | None = None + transparency_details: dict[str, Any] | None = None + findings: list[Finding] | None = None + recommendations: list[str] | None = None + assessors: list[str] | None = None + approved_by: str | None = None + approved_at: datetime | None = None + created_by: str | None = None # =========================================================================== @@ -216,16 +216,16 @@ class KillSwitch: system_id: str status: KillSwitchStatus auto_trigger_enabled: bool - created_at: Optional[datetime] - updated_at: Optional[datetime] - accuracy_threshold: Optional[float] = None - bias_threshold: Optional[float] = None - error_rate_threshold: Optional[float] = None - triggered_at: Optional[datetime] = None - triggered_by: Optional[str] = None - triggered_reason: Optional[str] = None - restored_at: Optional[datetime] = None - restored_by: Optional[str] = None + created_at: datetime | None + updated_at: datetime | None + accuracy_threshold: float | None = None + bias_threshold: float | None = None + error_rate_threshold: float | None = None + triggered_at: datetime | None = None + triggered_by: str | None = None + triggered_reason: str | None = None + restored_at: datetime | None = None + restored_by: str | None = None @dataclass @@ -235,9 +235,9 @@ class KillSwitchEvent: id: str kill_switch_id: str event_type: KillSwitchEventType - created_at: Optional[datetime] - event_data: Optional[dict[str, Any]] = None - created_by: Optional[str] = None + created_at: datetime | None + event_data: dict[str, Any] | None = None + created_by: str | None = None # =========================================================================== @@ -245,7 +245,7 @@ class KillSwitchEvent: # =========================================================================== -def _parse_datetime(value: Any) -> Optional[datetime]: +def _parse_datetime(value: Any) -> datetime | None: """Parse datetime from API response.""" if value is None: return None @@ -358,7 +358,7 @@ def finding_to_dict(finding: Finding) -> dict[str, Any]: return result -def _parse_findings(data: Optional[list[dict[str, Any]]]) -> Optional[list[Finding]]: +def _parse_findings(data: list[dict[str, Any]] | None) -> list[Finding] | None: """Parse list of findings from API response.""" if data is None: return None diff --git a/axonflow/utils/retry.py b/axonflow/utils/retry.py index 3bd437a..3e81646 100644 --- a/axonflow/utils/retry.py +++ b/axonflow/utils/retry.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Callable, TypeVar +from collections.abc import Callable +from typing import Any, TypeVar from tenacity import ( RetryCallState, diff --git a/pyproject.toml b/pyproject.toml index c3d3803..b1a7969 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "axonflow" -version = "4.1.0" +version = "5.0.0" description = "AxonFlow Python SDK - Enterprise AI Governance in 3 Lines of Code" readme = "README.md" license = {text = "MIT"} @@ -24,7 +24,6 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -32,14 +31,13 @@ classifiers = [ "Topic :: Scientific/Engineering :: Artificial Intelligence", "Typing :: Typed", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "httpx>=0.25.0", "pydantic>=2.0.0", "tenacity>=8.0.0", "structlog>=23.0.0", "cachetools>=5.0.0", - "eval_type_backport>=0.2.0; python_version < '3.10'", ] [project.optional-dependencies] @@ -83,7 +81,7 @@ include = ["axonflow*"] axonflow = ["py.typed"] [tool.ruff] -target-version = "py39" +target-version = "py310" line-length = 100 [tool.ruff.lint] @@ -148,7 +146,7 @@ ignore = [ known-first-party = ["axonflow"] [tool.mypy] -python_version = "3.9" +python_version = "3.10" strict = true warn_return_any = true warn_unused_configs = true