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/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
env: [3.9, "3.10", 3.11, 3.12, 3.13]
env: ["3.10", 3.11, 3.12, 3.13, 3.14]
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v5
Expand Down
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ History
3.2.0
++++++++++++++++++

* IMPORTANT: Python 3.10 or greater is required. If you are using an older
version, please use an earlier release.
* Setuptools has been replaced with the uv build backend for building the
package.
* Added ``securepay`` to the ``/payment/processor`` validation.
Expand Down
18 changes: 8 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies = [
"typing-extensions>=4.13.2",
"voluptuous",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
readme = "README.rst"
license = "Apache-2.0"
license-files = ["LICENSE"]
Expand All @@ -23,11 +23,11 @@ classifiers = [
"Intended Audience :: Developers",
"Programming Language :: Python",
"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",
"Topic :: Internet",
"Topic :: Internet :: Proxy Servers",
"Topic :: Internet :: WWW/HTTP",
Expand Down Expand Up @@ -67,9 +67,6 @@ source-include = [
[tool.ruff.lint]
select = ["ALL"]
ignore = [
# Skip type annotation on **_
"ANN003",

# Redundant as the formatter handles missing trailing commas.
"COM812",

Expand All @@ -95,16 +92,17 @@ ignorelist = ["id"]

[tool.ruff.lint.per-file-ignores]
"docs/*" = ["ALL"]
"src/minfraud/models.py" = [ "PLR0913" ]
"src/minfraud/models.py" = ["ANN401", "PLR0913"]
"src/minfraud/webservice.py" = ["ANN401"]
"tests/*" = ["ANN201", "D"]

[tool.tox]
env_list = [
"3.9",
"3.10",
"3.11",
"3.12",
"3.13",
"3.14",
"lint",
]
skip_missing_interpreters = false
Expand All @@ -119,7 +117,7 @@ commands = [

[tool.tox.env.lint]
description = "Code linting"
python = "3.13"
python = "3.14"
dependency_groups = [
"dev",
"lint",
Expand All @@ -131,8 +129,8 @@ commands = [
]

[tool.tox.gh.python]
"3.13" = ["3.13", "lint"]
"3.14" = ["3.14", "lint"]
"3.13" = ["3.13"]
"3.12" = ["3.12"]
"3.11" = ["3.11"]
"3.10" = ["3.10"]
"3.9" = ["3.9"]
100 changes: 50 additions & 50 deletions src/minfraud/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import json
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import geoip2.models
import geoip2.records
Expand All @@ -23,7 +23,7 @@ def __hash__(self) -> int:
# This is not particularly efficient, but I don't expect it to be used much.
return hash(json.dumps(self.to_dict(), sort_keys=True))

def to_dict(self) -> dict: # noqa: C901
def to_dict(self) -> dict[str, Any]: # noqa: C901
"""Return a dict of the object suitable for serialization."""
result = {}
for key, value in self.__dict__.items():
Expand Down Expand Up @@ -100,7 +100,7 @@ class GeoIP2Location(geoip2.records.Location):
`RFC 3339 <https://tools.ietf.org/html/rfc3339>`_. For instance, the
local time in Boston might be returned as 2015-04-27T19:17:24-04:00."""

def __init__(self, *args, **kwargs) -> None: # noqa: ANN002
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize a GeoIP2Location instance."""
self.local_time = kwargs.get("local_time")
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -131,11 +131,11 @@ def __init__(
self,
locales: Sequence[str] | None,
*,
country: dict | None = None,
location: dict | None = None,
country: dict[str, Any] | None = None,
location: dict[str, Any] | None = None,
risk: float | None = None,
risk_reasons: list[dict] | None = None,
**kwargs,
risk_reasons: list[dict[str, Any]] | None = None,
**kwargs: Any,
) -> None:
"""Initialize an IPAddress instance."""
# For raw attribute
Expand All @@ -161,7 +161,7 @@ class ScoreIPAddress(_Serializable):
"""This field contains the risk associated with the IP address. The value
ranges from 0.01 to 99. A higher score indicates a higher risk."""

def __init__(self, *, risk: float | None = None, **_) -> None:
def __init__(self, *, risk: float | None = None, **_: Any) -> None:
"""Initialize a ScoreIPAddress instance."""
self.risk = risk

Expand Down Expand Up @@ -197,7 +197,7 @@ def __init__(
matches_provided_name: bool | None = None,
phone_number: str | None = None,
matches_provided_phone_number: bool | None = None,
**_,
**_: Any,
) -> None:
"""Initialize an Issuer instance."""
self.name = name
Expand Down Expand Up @@ -239,7 +239,7 @@ def __init__(
id: str | None = None,
last_seen: str | None = None,
local_time: str | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a Device instance."""
self.confidence = confidence
Expand Down Expand Up @@ -277,7 +277,7 @@ def __init__(
action: str | None = None,
reason: str | None = None,
rule_label: str | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a Disposition instance."""
self.action = action
Expand All @@ -293,7 +293,7 @@ class EmailDomain(_Serializable):
was first seen by MaxMind. This is expressed using the ISO 8601 date
format."""

def __init__(self, *, first_seen: str | None = None, **_) -> None:
def __init__(self, *, first_seen: str | None = None, **_: Any) -> None:
"""Initialize an EmailDomain instance."""
self.first_seen = first_seen

Expand Down Expand Up @@ -325,7 +325,7 @@ class Email(_Serializable):

def __init__(
self,
domain: dict | None = None,
domain: dict[str, Any] | None = None,
first_seen: str | None = None,
is_disposable: bool | None = None, # noqa: FBT001
is_free: bool | None = None, # noqa: FBT001
Expand Down Expand Up @@ -378,7 +378,7 @@ class CreditCard(_Serializable):

def __init__(
self,
issuer: dict | None = None,
issuer: dict[str, Any] | None = None,
country: str | None = None,
brand: str | None = None,
is_business: bool | None = None, # noqa: FBT001
Expand Down Expand Up @@ -432,7 +432,7 @@ def __init__(
longitude: float | None = None,
distance_to_ip_location: int | None = None,
is_in_ip_country: bool | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a BillingAddress instance."""
self.is_postal_in_city = is_postal_in_city
Expand Down Expand Up @@ -487,7 +487,7 @@ def __init__(
is_in_ip_country: bool | None = None,
is_high_risk: bool | None = None,
distance_to_billing_address: int | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a ShippingAddress instance."""
self.is_postal_in_city = is_postal_in_city
Expand Down Expand Up @@ -538,7 +538,7 @@ def __init__(
matches_postal: bool | None = None,
network_operator: str | None = None,
number_type: str | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a Phone instance."""
self.country = country
Expand Down Expand Up @@ -573,7 +573,7 @@ def __init__(
code: str | None = None,
warning: str | None = None,
input_pointer: str | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a ServiceWarning instance."""
self.code = code
Expand Down Expand Up @@ -717,7 +717,7 @@ def __init__(
shipping_address: float | None = None,
shipping_address_distance_to_ip_location: float | None = None,
time_of_day: float | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a Subscores instance."""
self.avs_result = avs_result
Expand Down Expand Up @@ -831,7 +831,7 @@ def __init__(
*,
code: str | None = None,
reason: str | None = None,
**_,
**_: Any,
) -> None:
"""Initialize a Reason instance."""
self.code = code
Expand All @@ -855,8 +855,8 @@ def __init__(
self,
*,
multiplier: float,
reasons: list | None = None,
**_,
reasons: list[dict[str, Any]] | None = None,
**_: Any,
) -> None:
"""Initialize a RiskScoreReason instance."""
self.multiplier = multiplier
Expand Down Expand Up @@ -948,23 +948,23 @@ def __init__(
self,
locales: Sequence[str],
*,
billing_address: dict | None = None,
billing_phone: dict | None = None,
credit_card: dict | None = None,
disposition: dict | None = None,
billing_address: dict[str, Any] | None = None,
billing_phone: dict[str, Any] | None = None,
credit_card: dict[str, Any] | None = None,
disposition: dict[str, Any] | None = None,
funds_remaining: float,
device: dict | None = None,
email: dict | None = None,
device: dict[str, Any] | None = None,
email: dict[str, Any] | None = None,
id: str,
ip_address: dict | None = None,
ip_address: dict[str, Any] | None = None,
queries_remaining: int,
risk_score: float,
shipping_address: dict | None = None,
shipping_phone: dict | None = None,
subscores: dict | None = None,
warnings: list[dict] | None = None,
risk_score_reasons: list[dict] | None = None,
**_,
shipping_address: dict[str, Any] | None = None,
shipping_phone: dict[str, Any] | None = None,
subscores: dict[str, Any] | None = None,
warnings: list[dict[str, Any]] | None = None,
risk_score_reasons: list[dict[str, Any]] | None = None,
**_: Any,
) -> None:
"""Initialize a Factors instance."""
self.billing_address = BillingAddress(**(billing_address or {}))
Expand Down Expand Up @@ -1056,21 +1056,21 @@ def __init__(
self,
locales: Sequence[str],
*,
billing_address: dict | None = None,
billing_phone: dict | None = None,
credit_card: dict | None = None,
device: dict | None = None,
disposition: dict | None = None,
email: dict | None = None,
billing_address: dict[str, Any] | None = None,
billing_phone: dict[str, Any] | None = None,
credit_card: dict[str, Any] | None = None,
device: dict[str, Any] | None = None,
disposition: dict[str, Any] | None = None,
email: dict[str, Any] | None = None,
funds_remaining: float,
id: str,
ip_address: dict | None = None,
ip_address: dict[str, Any] | None = None,
queries_remaining: int,
risk_score: float,
shipping_address: dict | None = None,
shipping_phone: dict | None = None,
warnings: list[dict] | None = None,
**_,
shipping_address: dict[str, Any] | None = None,
shipping_phone: dict[str, Any] | None = None,
warnings: list[dict[str, Any]] | None = None,
**_: Any,
) -> None:
"""Initialize an Insights instance."""
self.billing_address = BillingAddress(**(billing_address or {}))
Expand Down Expand Up @@ -1128,14 +1128,14 @@ class Score(_Serializable):
def __init__(
self,
*,
disposition: dict | None = None,
disposition: dict[str, Any] | None = None,
funds_remaining: float,
id: str,
ip_address: dict | None = None,
ip_address: dict[str, Any] | None = None,
queries_remaining: int,
risk_score: float,
warnings: list[dict] | None = None,
**_,
warnings: list[dict[str, Any]] | None = None,
**_: Any,
) -> None:
"""Initialize a Score instance."""
self.disposition = Disposition(**(disposition or {}))
Expand Down
5 changes: 3 additions & 2 deletions src/minfraud/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import urllib.parse
import uuid
from decimal import Decimal
from typing import Any as AnyType

from email_validator import validate_email
from voluptuous import (
Expand Down Expand Up @@ -327,7 +328,7 @@ def _uri(s: str) -> str:
return s


validate_transaction = Schema(
validate_transaction: Schema = Schema(
{
"account": {
"user_id": str,
Expand Down Expand Up @@ -459,7 +460,7 @@ def _validate_at_least_one_identifier_field(report: dict) -> bool:
return True


def validate_report(report: dict) -> bool:
def validate_report(report: dict[str, AnyType]) -> bool:
"""Validate minFraud Transaction Report fields."""
_validate_report_schema(report)
_validate_at_least_one_identifier_field(report)
Expand Down
Loading
Loading