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
4 changes: 4 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ History
5.2.0
++++++++++++++++++

* IMPORTANT: Python 3.10 or greater is required. If you are using an older
version, please use an earlier release.
* `maxminddb` has been upgraded to 3.0.0. This includes free-threading
support.
* Setuptools has been replaced with the uv build backend for building the
package.

Expand Down
19 changes: 8 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ authors = [
]
dependencies = [
"aiohttp>=3.6.2,<4.0.0",
"maxminddb>=2.7.0,<3.0.0",
"maxminddb>=3.0.0,<4.0.0",
"requests>=2.24.0,<3.0.0",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
readme = "README.rst"
license = "Apache-2.0"
license-files = ["LICENSE"]
Expand All @@ -21,11 +21,11 @@ classifiers = [
"Intended Audience :: System Administrators",
"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",
]
Expand Down Expand Up @@ -65,9 +65,6 @@ Documentation = "https://geoip2.readthedocs.org/"
[tool.ruff.lint]
select = ["ALL"]
ignore = [
# Skip type annotation on **_
"ANN003",

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

Expand All @@ -90,16 +87,16 @@ ignore = [

[tool.ruff.lint.per-file-ignores]
"docs/*" = ["ALL"]
"src/geoip2/{models,records}.py" = [ "D107", "PLR0913" ]
"src/geoip2/{models,records}.py" = [ "ANN401", "D107", "PLR0913" ]
"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 @@ -114,7 +111,7 @@ commands = [

[tool.tox.env.lint]
description = "Code linting"
python = "3.13"
python = "3.14"
dependency_groups = [
"dev",
"lint",
Expand All @@ -126,8 +123,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"]
3 changes: 2 additions & 1 deletion src/geoip2/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
from abc import ABCMeta
from typing import Any


class Model(metaclass=ABCMeta): # noqa: B024
Expand All @@ -17,7 +18,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, PLR0912
def to_dict(self) -> dict[str, Any]: # noqa: C901, PLR0912
"""Return a dict of the object suitable for serialization."""
result = {}
for key, value in self.__dict__.items():
Expand Down
4 changes: 3 additions & 1 deletion src/geoip2/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ class Reader:

def __init__(
self,
fileish: AnyStr | int | os.PathLike | IO,
fileish: (
AnyStr | int | os.PathLike[str] | os.PathLike[bytes] | IO[str] | IO[bytes]
),
locales: Sequence[str] | None = None,
mode: int = MODE_AUTO,
) -> None:
Expand Down
50 changes: 25 additions & 25 deletions src/geoip2/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import ipaddress
from abc import ABCMeta
from ipaddress import IPv4Address, IPv6Address
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import geoip2.records
from geoip2._internal import Model
Expand Down Expand Up @@ -55,15 +55,15 @@ def __init__(
self,
locales: Sequence[str] | None,
*,
continent: dict | None = None,
country: dict | None = None,
continent: dict[str, Any] | None = None,
country: dict[str, Any] | None = None,
ip_address: IPAddress | None = None,
maxmind: dict | None = None,
maxmind: dict[str, Any] | None = None,
prefix_len: int | None = None,
registered_country: dict | None = None,
represented_country: dict | None = None,
traits: dict | None = None,
**_,
registered_country: dict[str, Any] | None = None,
represented_country: dict[str, Any] | None = None,
traits: dict[str, Any] | None = None,
**_: Any,
) -> None:
self._locales = locales
self.continent = geoip2.records.Continent(locales, **(continent or {}))
Expand Down Expand Up @@ -115,19 +115,19 @@ def __init__(
self,
locales: Sequence[str] | None,
*,
city: dict | None = None,
continent: dict | None = None,
country: dict | None = None,
location: dict | None = None,
city: dict[str, Any] | None = None,
continent: dict[str, Any] | None = None,
country: dict[str, Any] | None = None,
location: dict[str, Any] | None = None,
ip_address: IPAddress | None = None,
maxmind: dict | None = None,
postal: dict | None = None,
maxmind: dict[str, Any] | None = None,
postal: dict[str, Any] | None = None,
prefix_len: int | None = None,
registered_country: dict | None = None,
represented_country: dict | None = None,
subdivisions: list[dict] | None = None,
traits: dict | None = None,
**_,
registered_country: dict[str, Any] | None = None,
represented_country: dict[str, Any] | None = None,
subdivisions: list[dict[str, Any]] | None = None,
traits: dict[str, Any] | None = None,
**_: Any,
) -> None:
super().__init__(
locales,
Expand Down Expand Up @@ -260,7 +260,7 @@ def __init__(
is_tor_exit_node: bool = False,
network: str | None = None,
prefix_len: int | None = None,
**_,
**_: Any,
) -> None:
super().__init__(ip_address, network, prefix_len)
self.is_anonymous = is_anonymous
Expand Down Expand Up @@ -304,7 +304,7 @@ def __init__(
network_last_seen: str | None = None,
prefix_len: int | None = None,
provider_name: str | None = None,
**_,
**_: Any,
) -> None:
super().__init__(
is_anonymous=is_anonymous,
Expand Down Expand Up @@ -342,7 +342,7 @@ def __init__(
autonomous_system_organization: str | None = None,
network: str | None = None,
prefix_len: int | None = None,
**_,
**_: Any,
) -> None:
super().__init__(ip_address, network, prefix_len)
self.autonomous_system_number = autonomous_system_number
Expand Down Expand Up @@ -371,7 +371,7 @@ def __init__(
connection_type: str | None = None,
network: str | None = None,
prefix_len: int | None = None,
**_,
**_: Any,
) -> None:
super().__init__(ip_address, network, prefix_len)
self.connection_type = connection_type
Expand All @@ -390,7 +390,7 @@ def __init__(
domain: str | None = None,
network: str | None = None,
prefix_len: int | None = None,
**_,
**_: Any,
) -> None:
super().__init__(ip_address, network, prefix_len)
self.domain = domain
Expand Down Expand Up @@ -429,7 +429,7 @@ def __init__(
organization: str | None = None,
network: str | None = None,
prefix_len: int | None = None,
**_,
**_: Any,
) -> None:
super().__init__(
autonomous_system_number=autonomous_system_number,
Expand Down
24 changes: 12 additions & 12 deletions src/geoip2/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import ipaddress
from abc import ABCMeta
from ipaddress import IPv4Address, IPv6Address
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from geoip2._internal import Model

Expand Down Expand Up @@ -73,7 +73,7 @@ def __init__(
confidence: int | None = None,
geoname_id: int | None = None,
names: dict[str, str] | None = None,
**_,
**_: Any,
) -> None:
self.confidence = confidence
self.geoname_id = geoname_id
Expand Down Expand Up @@ -101,7 +101,7 @@ def __init__(
code: str | None = None,
geoname_id: int | None = None,
names: dict[str, str] | None = None,
**_,
**_: Any,
) -> None:
self.code = code
self.geoname_id = geoname_id
Expand Down Expand Up @@ -138,7 +138,7 @@ def __init__(
is_in_european_union: bool = False,
iso_code: str | None = None,
names: dict[str, str] | None = None,
**_,
**_: Any,
) -> None:
self.confidence = confidence
self.geoname_id = geoname_id
Expand Down Expand Up @@ -171,7 +171,7 @@ def __init__(
iso_code: str | None = None,
names: dict[str, str] | None = None,
type: str | None = None, # noqa: A002
**_,
**_: Any,
) -> None:
self.type = type
super().__init__(
Expand Down Expand Up @@ -238,7 +238,7 @@ def __init__(
metro_code: int | None = None,
population_density: int | None = None,
time_zone: str | None = None,
**_,
**_: Any,
) -> None:
self.average_income = average_income
self.accuracy_radius = accuracy_radius
Expand All @@ -257,7 +257,7 @@ class MaxMind(Record):
calling.
"""

def __init__(self, *, queries_remaining: int | None = None, **_) -> None:
def __init__(self, *, queries_remaining: int | None = None, **_: Any) -> None:
self.queries_remaining = queries_remaining


Expand Down Expand Up @@ -285,7 +285,7 @@ def __init__(
*,
code: str | None = None,
confidence: int | None = None,
**_,
**_: Any,
) -> None:
self.code = code
self.confidence = confidence
Expand Down Expand Up @@ -319,7 +319,7 @@ def __init__(
geoname_id: int | None = None,
iso_code: str | None = None,
names: dict[str, str] | None = None,
**_,
**_: Any,
) -> None:
self.confidence = confidence
self.geoname_id = geoname_id
Expand All @@ -342,7 +342,7 @@ class Subdivisions(tuple): # noqa: SLOT001
def __new__(
cls: type[Self],
locales: Sequence[str] | None,
*subdivisions: dict,
*subdivisions: dict[str, Any],
) -> Self:
"""Create a new Subdivisions instance.

Expand All @@ -367,7 +367,7 @@ def __new__(
def __init__(
self,
locales: Sequence[str] | None,
*_: dict,
*_: dict[str, Any],
) -> None:
"""Initialize the Subdivisions instance."""
self._locales = locales
Expand Down Expand Up @@ -580,7 +580,7 @@ def __init__(
mobile_country_code: str | None = None,
mobile_network_code: str | None = None,
is_anycast: bool = False,
**_,
**_: Any,
) -> None:
self.autonomous_system_number = autonomous_system_number
self.autonomous_system_organization = autonomous_system_organization
Expand Down
3 changes: 1 addition & 2 deletions src/geoip2/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Provides types used internally."""

from ipaddress import IPv4Address, IPv6Address
from typing import Union

IPAddress = Union[str, IPv6Address, IPv4Address]
IPAddress = str | IPv6Address | IPv4Address
5 changes: 4 additions & 1 deletion tests/webservice_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import unittest
from abc import ABC, abstractmethod
from collections import defaultdict
from typing import Callable, ClassVar, cast
from typing import TYPE_CHECKING, ClassVar, cast

import pytest
import pytest_httpserver
Expand All @@ -26,6 +26,9 @@
)
from geoip2.webservice import AsyncClient, Client

if TYPE_CHECKING:
from collections.abc import Callable


class TestBaseClient(unittest.TestCase, ABC):
client: AsyncClient | Client
Expand Down
Loading
Loading