Skip to content

Conversation

oschwald
Copy link
Member

  • Implement PEP 489 multi-phase initialization for C extension
  • Add missing iter type hint
  • Modernize setup.py for Python 3.10+
  • Convert Metadata class to dataclass
  • Make Metadata class readonly/immutable
  • Convert MODE constants to IntEnum
  • Improve type annotations for Record types and Decoder
  • Remove Jython support
  • Use single source of truth for version from pyproject.toml

oschwald and others added 9 commits October 14, 2025 13:31
This commit modernizes the C extension to use PEP 489 multi-phase
initialization, enabling proper subinterpreter support and module
isolation for Python 3.12+.

Key changes:

Module State Management:
- Added maxminddb_state struct to store per-module state
- Implemented get_maxminddb_state() helpers to access module state
- Added module lifecycle functions (traverse, clear, free)

Type Conversion:
- Converted Reader_Type, Metadata_Type, and ReaderIter_Type from
  static types to heap types using PyType_FromModuleAndSpec()
- Created PyType_Spec definitions for all three types
- Removed static global type declarations

State Threading:
- Updated all functions to access module state instead of globals:
  - Reader_init(), get_record(), Reader_metadata()
  - Reader_iter(), ReaderIter_next()
  - from_entry_data_list(), from_map(), from_array()
- Removed static global variables for MaxMindDB_error and
  ipaddress_ip_network

Module Initialization:
- Implemented maxminddb_exec() to initialize module state
- Added module slots declaring:
  - Multi-phase initialization support (Py_mod_exec)
  - Multiple interpreter support (Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED)
  - GIL-free operation when available (Py_MOD_GIL_NOT_USED)
- Updated PyModuleDef with m_size, m_slots, and GC functions
- Simplified PyInit_extension() to use PyModuleDef_Init()

Testing:
- Updated test regex to handle fully qualified type names in error
  messages (heap types include module path)
- All 278 tests pass

Benefits:
- Supports Python 3.12+ isolated subinterpreters
- Enables multiple independent module instances
- Complements existing free-threading support (PEP 703)
- Follows modern Python C API best practices
- Future-proof for Python 3.14's InterpreterPoolExecutor

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace sys.exc_info() with modern exception handling using 'from' clause
- Use text mode when opening files instead of binary mode with manual decode
- Update BuildFailed to accept exception parameter for proper chaining

These are internal improvements with no user-visible changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Convert the pure Python `maxminddb.reader.Metadata` class to use
@DataClass(kw_only=True) for cleaner, more maintainable code.

Changes:
- Added dataclass import and decorator
- Removed manual __init__ method (15+ lines of boilerplate)
- Removed custom __repr__ method
- Preserved @Property methods for node_byte_size and search_tree_size
- All functionality remains identical

Breaking change: The __repr__ format changes from
`maxminddb.reader.Metadata(...)` to `Metadata(...)`. This is
documented in HISTORY.rst.

The C extension's Metadata class is unchanged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add frozen=True to the Metadata dataclass to make all attributes
readonly after creation. This prevents accidental modification of
metadata that represents the immutable properties of a database file.

Changes:
- Added frozen=True to @DataClass decorator
- Updated HISTORY.rst to document the breaking change
- Noted that C extension Metadata has always been readonly

Benefits:
- Consistent behavior between pure Python and C extension
- Prevents bugs from accidental metadata modification
- Better represents the immutable nature of database metadata

Breaking change: Attempting to modify Metadata attributes after
creation will now raise an AttributeError. This brings the pure
Python implementation into consistency with the C extension, which
has always had readonly attributes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Convert the MODE_* constants to an IntEnum class for better type
safety, IDE support, and more descriptive string representations.

Changes:
- Created Mode IntEnum with AUTO, MMAP_EXT, MMAP, FILE, MEMORY, FD members
- Added comprehensive documentation for the Mode class and each member
- Maintained backward compatibility by exporting old-style constants
  (MODE_AUTO, MODE_FILE, etc.) as aliases to enum members
- Added __all__ to explicitly define the public API

Benefits:
- Better IDE autocomplete and type hints
- Descriptive repr: <Mode.FILE: 4> instead of just 4
- Access to .name and .value attributes
- Full backward compatibility (IntEnum is int-compatible)
- Modern Python 3.10+ idiom
- Inline documentation for each mode visible in source and IDEs

Backward compatibility: All existing code continues to work unchanged.
Both Mode.FILE and MODE_FILE can be used interchangeably, and all
numeric comparisons work as before since IntEnum is int-compatible.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Convert RecordList and RecordDict from class-based type definitions to
explicit TypeAlias declarations. These types are only used for type
annotations and were never instantiated as actual classes, so using
TypeAlias makes their purpose more explicit and follows modern Python
typing conventions.

Add explicit type annotation to Decoder._type_decoder ClassVar. This
provides better type checking for the decoder function map by fully
specifying the callable signature.

These are internal changes with no user-visible effects.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Remove Jython-specific code from setup.py. Jython does not support
Python 3.10+, making this code unreachable given the package's minimum
Python version requirement.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace hardcoded module metadata in maxminddb/__init__.py with
importlib.metadata.version() to get the version from pyproject.toml.
This eliminates duplication and makes pyproject.toml the single source
of truth for package metadata.

Changes:
- Remove __title__, __author__, __license__, and __copyright__ from
  __init__.py (redundant with pyproject.toml)
- Replace hardcoded __version__ with importlib.metadata.version()
- Remove version reading from setup.py (setuptools gets it from
  pyproject.toml automatically)
- Update dev-bin/release.sh to only update pyproject.toml version

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@oschwald oschwald force-pushed the greg/eng-3216-pep489 branch from 34a31d9 to 5648f66 Compare October 14, 2025 21:11
The old constants (``MODE_AUTO``, ``MODE_FILE``, etc.) remain available for
backward compatibility and are now aliases to the enum members. This provides
better IDE support and type safety while maintaining full backward
compatibility. You can now use either ``Mode.FILE`` or ``MODE_FILE`` - both
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we want to update the readme to suggest the former style?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you change that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gah, sorry, too many issues going at once. Pushed.

Updated the README to suggest using the new Mode enum style (e.g.,
Mode.FILE) instead of the old constant style (e.g., MODE_FILE). The old
constants remain available for backward compatibility, but the enum
provides better IDE support and type safety.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@horgh horgh merged commit f0edc90 into main Oct 14, 2025
90 checks passed
@horgh horgh deleted the greg/eng-3216-pep489 branch October 14, 2025 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants