Skip to content

Switch to ruff #204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 23, 2025
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
22 changes: 8 additions & 14 deletions .github/workflows/address-sanitizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,18 @@ jobs:
submodules: true
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.13
- name: Install the latest version of uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # 6.0.1

- name: Install dependencies
- name: Install packages
run: |
python -m pip install --upgrade pip
pip install pytest setuptools wheel

- name: Install
run: pip install .
env:
CFLAGS: "-Werror -Wall -Wextra"
MAXMINDDB_REQUIRE_EXTENSION: 1
sudo apt-get update
sudo apt-get -y install libasan6

- name: Test
run: pytest
run: uv run pytest
env:
CFLAGS: "-Werror -Wall -Wextra"
LD_PRELOAD: libasan.so.6
MAXMINDDB_REQUIRE_EXTENSION: 1
MM_FORCE_EXT_TESTS: 1
9 changes: 2 additions & 7 deletions .github/workflows/clang-analyzer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,8 @@ jobs:
- name: Install clang-tools
run: sudo apt install clang-tools

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.13

- name: Install dependencies
run: python -m pip install uv
- name: Install the latest version of uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # 6.0.1

- name: Build and run analyzer
# We exclude extension/libmaxminddb/ as libmaxminddb has its own workflow
Expand Down
60 changes: 23 additions & 37 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,26 @@ jobs:
security-events: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
submodules: true
persist-credentials: false

# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
# Override language selection by uncommenting this and choosing your languages
with:
languages: python, cpp

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl

# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language

- name: Install dependencies
run: python -m pip install uv

- run: uv build
env:
MAXMINDDB_REQUIRE_EXTENSION: 1

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
submodules: true
persist-credentials: false
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: python, cpp

- name: Install the latest version of uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # 6.0.1

- run: uv build
env:
MAXMINDDB_REQUIRE_EXTENSION: 1

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@ docs/html
env/
MANIFEST
maxminddb.egg-info/
pylint.txt
valgrind-python.supp
violations.pyflakes.txt
4 changes: 3 additions & 1 deletion examples/benchmark.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/python
"""Basic benchmark for maxminddb."""

import argparse
import random
Expand All @@ -20,6 +21,7 @@


def lookup_ip_address() -> None:
"""Look up the IP."""
ip = socket.inet_ntoa(struct.pack("!L", random.getrandbits(32)))
reader.get(str(ip))

Expand All @@ -30,4 +32,4 @@ def lookup_ip_address() -> None:
number=args.count,
)

print(f"{int(args.count / elapsed):,}", "lookups per second")
print(f"{int(args.count / elapsed):,}", "lookups per second") # noqa: T201
40 changes: 22 additions & 18 deletions maxminddb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Module for reading MaxMind DB files."""

# pylint:disable=C0111
import os
from typing import IO, AnyStr, Union, cast
from __future__ import annotations

from typing import IO, TYPE_CHECKING, AnyStr, cast

from .const import (
MODE_AUTO,
Expand All @@ -15,8 +15,10 @@
from .decoder import InvalidDatabaseError
from .reader import Reader

if TYPE_CHECKING:
import os

try:
# pylint: disable=import-self
from . import extension as _extension
except ImportError:
_extension = None # type: ignore[assignment]
Expand All @@ -36,22 +38,22 @@


def open_database(
database: Union[AnyStr, int, os.PathLike, IO],
database: AnyStr | int | os.PathLike | IO,
mode: int = MODE_AUTO,
) -> Reader:
"""Open a MaxMind DB database.

Arguments:
database -- A path to a valid MaxMind DB file such as a GeoIP2 database
file, or a file descriptor in the case of MODE_FD.
mode -- mode to open the database with. Valid mode are:
* MODE_MMAP_EXT - use the C extension with memory map.
* MODE_MMAP - read from memory map. Pure Python.
* MODE_FILE - read database as standard file. Pure Python.
* MODE_MEMORY - load database into memory. Pure Python.
* MODE_FD - the param passed via database is a file descriptor, not
a path. This mode implies MODE_MEMORY.
* MODE_AUTO - tries MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that
database: A path to a valid MaxMind DB file such as a GeoIP2 database
file, or a file descriptor in the case of MODE_FD.
mode: mode to open the database with. Valid mode are:
* MODE_MMAP_EXT - use the C extension with memory map.
* MODE_MMAP - read from memory map. Pure Python.
* MODE_FILE - read database as standard file. Pure Python.
* MODE_MEMORY - load database into memory. Pure Python.
* MODE_FD - the param passed via database is a file descriptor, not
a path. This mode implies MODE_MEMORY.
* MODE_AUTO - tries MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that
order. Default mode.

"""
Expand All @@ -63,7 +65,8 @@ def open_database(
MODE_MMAP,
MODE_MMAP_EXT,
):
raise ValueError(f"Unsupported open mode: {mode}")
msg = f"Unsupported open mode: {mode}"
raise ValueError(msg)

has_extension = _extension and hasattr(_extension, "Reader")
use_extension = has_extension if mode == MODE_AUTO else mode == MODE_MMAP_EXT
Expand All @@ -72,15 +75,16 @@ def open_database(
return Reader(database, mode)

if not has_extension:
msg = "MODE_MMAP_EXT requires the maxminddb.extension module to be available"
raise ValueError(
"MODE_MMAP_EXT requires the maxminddb.extension module to be available",
msg,
)

# The C type exposes the same API as the Python Reader, so for type
# checking purposes, pretend it is one. (Ideally this would be a subclass
# of, or share a common parent class with, the Python Reader
# implementation.)
return cast(Reader, _extension.Reader(database, mode))
return cast("Reader", _extension.Reader(database, mode))


__title__ = "maxminddb"
Expand Down
39 changes: 22 additions & 17 deletions maxminddb/decoder.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
"""Decoder for the MaxMind DB data section."""

import struct
from typing import Union, cast
from typing import ClassVar, Union, cast

try:
# pylint: disable=unused-import
import mmap
except ImportError:
# pylint: disable=invalid-name
mmap = None # type: ignore
mmap = None # type: ignore[assignment]


from maxminddb.errors import InvalidDatabaseError
from maxminddb.file import FileBuffer
from maxminddb.types import Record


class Decoder: # pylint: disable=too-few-public-methods
class Decoder:
"""Decoder for the data section of the MaxMind DB."""

def __init__(
self,
database_buffer: Union[FileBuffer, "mmap.mmap", bytes],
pointer_base: int = 0,
pointer_test: bool = False,
pointer_test: bool = False, # noqa: FBT001, FBT002
) -> None:
"""Created a Decoder for a MaxMind DB.
"""Create a Decoder for a MaxMind DB.

Arguments:
database_buffer -- an mmap'd MaxMind DB file.
pointer_base -- the base number to use when decoding a pointer
pointer_test -- used for internal unit testing of pointer code
database_buffer: an mmap'd MaxMind DB file.
pointer_base: the base number to use when decoding a pointer
pointer_test: used for internal unit testing of pointer code

"""
self._pointer_test = pointer_test
Expand Down Expand Up @@ -116,7 +114,7 @@ def _decode_utf8_string(self, size: int, offset: int) -> tuple[str, int]:
new_offset = offset + size
return self._buffer[offset:new_offset].decode("utf-8"), new_offset

_type_decoder = {
_type_decoder: ClassVar = {
1: _decode_pointer,
2: _decode_utf8_string,
3: _decode_double,
Expand All @@ -136,7 +134,7 @@ def decode(self, offset: int) -> tuple[Record, int]:
"""Decode a section of the data section starting at offset.

Arguments:
offset -- the location of the data structure to decode
offset: the location of the data structure to decode

"""
new_offset = offset + 1
Expand All @@ -149,8 +147,9 @@ def decode(self, offset: int) -> tuple[Record, int]:
try:
decoder = self._type_decoder[type_num]
except KeyError as ex:
msg = f"Unexpected type number ({type_num}) encountered"
raise InvalidDatabaseError(
f"Unexpected type number ({type_num}) encountered",
msg,
) from ex

(size, new_offset) = self._size_from_ctrl_byte(ctrl_byte, new_offset, type_num)
Expand All @@ -160,18 +159,24 @@ def _read_extended(self, offset: int) -> tuple[int, int]:
next_byte = self._buffer[offset]
type_num = next_byte + 7
if type_num < 7:
raise InvalidDatabaseError(
msg = (
"Something went horribly wrong in the decoder. An "
f"extended type resolved to a type number < 8 ({type_num})",
f"extended type resolved to a type number < 8 ({type_num})"
)
raise InvalidDatabaseError(
msg,
)
return type_num, offset + 1

@staticmethod
def _verify_size(expected: int, actual: int) -> None:
if expected != actual:
raise InvalidDatabaseError(
msg = (
"The MaxMind DB file's data section contains bad data "
"(unknown data type or corrupt data)",
"(unknown data type or corrupt data)"
)
raise InvalidDatabaseError(
msg,
)

def _size_from_ctrl_byte(
Expand Down
Loading
Loading