Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down
2 changes: 1 addition & 1 deletion axonflow/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Single source of truth for the AxonFlow SDK version."""

__version__ = "4.1.0"
__version__ = "5.0.0"
3 changes: 2 additions & 1 deletion axonflow/adapters/langgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
3 changes: 2 additions & 1 deletion axonflow/interceptors/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion axonflow/interceptors/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion axonflow/interceptors/gemini.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion axonflow/interceptors/ollama.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion axonflow/interceptors/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 47 additions & 47 deletions axonflow/masfeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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


# ===========================================================================
Expand All @@ -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
Expand Down Expand Up @@ -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


# ===========================================================================
Expand All @@ -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
Expand All @@ -235,17 +235,17 @@ 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


# ===========================================================================
# Helper Functions
# ===========================================================================


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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion axonflow/utils/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 4 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand All @@ -24,22 +24,20 @@ 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",
"Topic :: Software Development :: Libraries :: Python Modules",
"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]
Expand Down Expand Up @@ -83,7 +81,7 @@ include = ["axonflow*"]
axonflow = ["py.typed"]

[tool.ruff]
target-version = "py39"
target-version = "py310"
line-length = 100

[tool.ruff.lint]
Expand Down Expand Up @@ -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
Expand Down
Loading