Skip to content

Commit d288ac5

Browse files
committed
Add memory mode and expose the modes. Closes #10
1 parent 0ee7d9e commit d288ac5

File tree

8 files changed

+209
-62
lines changed

8 files changed

+209
-62
lines changed

HISTORY.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
History
44
-------
55

6+
1.1.0 (2014-12-XX)
7+
++++++++++++++++++
8+
9+
* The pure Python reader now supports an optional file and memory mode in
10+
addition to the existing memory-map mode. If your Python does not provide
11+
the ``mmap`` module, the file mode will be used by default.
12+
* The preferred method for opening a database is now the ``open_database``
13+
function in ``maxminddb``. This function now takes an optional file
14+
``mode``.
15+
616
1.0.0 (2014-09-22)
717
++++++++++++++++++
818

README.rst

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,22 @@ provide `free GeoLite2 databases
4040
<http://dev.maxmind.com/geoip/geoip2/geolite2>`_. These files must be
4141
decompressed with ``gunzip``.
4242

43-
After you have obtained a database and importing the module, you must create a
44-
``Reader`` object, providing the path to the file as the first argument to the
45-
constructor. After doing this, you may call the ``get`` method with an IP
46-
address on the object. This method will return the corresponding values for
47-
the IP address from the database (e.g., a dictionary for GeoIP2/GeoLite2
48-
databases). If the database does not contain a record for that IP address, the
49-
method will return ``None``.
43+
After you have obtained a database and importing the module, call
44+
``open_database`` with a path to the database as the first argument.
45+
Optionally, you may pass a mode as the second arguments. The modes are
46+
exported from ``maxminddb``. Valid modes are:
47+
48+
* MODE_MMAP_EXT - use the C extension with memory map.
49+
* MODE_MMAP - read from memory map. Pure Python.
50+
* MODE_FILE - read database as standard file. Pure Python.
51+
* MODE_MEMORY - load database into memory. Pure Python.
52+
* MODE_AUTO - try MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that order. Default.
53+
54+
The ``open_database`` function returns a ``Reader`` object. To look up an IP
55+
address, use the ``get`` method on this object. The method will return the
56+
corresponding values for the IP address from the database (e.g., a dictionary
57+
for GeoIP2/GeoLite2 databases). If the database does not contain a record for
58+
that IP address, the method will return ``None``.
5059

5160
Example
5261
-------
@@ -55,12 +64,10 @@ Example
5564
5665
>>> import maxminddb
5766
>>>
58-
>>> reader = maxminddb.Reader('GeoLite2-City.mmdb')
67+
>>> reader = maxminddb.open_database('GeoLite2-City.mmdb')
5968
>>> reader.get('1.1.1.1')
6069
{'country': ... }
6170
>>>
62-
>>> # The optional 'close' method will release the resources to the
63-
>>> # system immediately.
6471
>>> reader.close()
6572
6673
Exceptions

maxminddb/__init__.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,42 @@
11
# pylint:disable=C0111
22
import os
33

4+
import maxminddb.reader
5+
46
try:
5-
if os.environ.get('MAXMINDDB_PURE_PYTHON') == '1':
6-
raise ImportError()
7-
# pylint: disable=no-name-in-module
8-
from maxminddb.extension import Reader, InvalidDatabaseError
9-
except ImportError as import_error:
10-
if os.environ.get('MAXMINDDB_PURE_PYTHON') == '0':
11-
raise import_error
12-
from maxminddb.decoder import InvalidDatabaseError
13-
from maxminddb.reader import Reader
7+
import maxminddb.extension
8+
except ImportError:
9+
maxminddb.extension = None
10+
11+
from maxminddb.const import (MODE_AUTO, MODE_MMAP, MODE_MMAP_EXT, MODE_FILE,
12+
MODE_MEMORY)
13+
from maxminddb.decoder import InvalidDatabaseError
14+
15+
16+
def open_database(database, mode=MODE_AUTO):
17+
"""Open a Maxmind DB database
18+
19+
Arguments:
20+
database -- A path to a valid MaxMind DB file such as a GeoIP2
21+
database file.
22+
mode -- mode to open the database with. Valid mode are:
23+
* MODE_MMAP_EXT - use the C extension with memory map.
24+
* MODE_MMAP - read from memory map. Pure Python.
25+
* MODE_FILE - read database as standard file. Pure Python.
26+
* MODE_MEMORY - load database into memory. Pure Python.
27+
* MODE_AUTO - tries MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that
28+
order. Default mode.
29+
"""
30+
if (mode == MODE_AUTO and maxminddb.extension) or mode == MODE_MMAP_EXT:
31+
return maxminddb.extension.Reader(database)
32+
elif mode in (MODE_AUTO, MODE_MMAP, MODE_FILE, MODE_MEMORY):
33+
return maxminddb.reader.Reader(database, mode)
34+
raise ValueError('Unsupported open mode: {}', mode)
35+
1436

37+
def Reader(database): # pylint: disable=invalid-name
38+
"""This exists for backwards compatibility. Use open_database instead"""
39+
open_database(database)
1540

1641
__title__ = 'maxminddb'
1742
__version__ = '1.0.0'

maxminddb/const.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Constants used in the API"""
2+
3+
MODE_AUTO = 0
4+
MODE_MMAP_EXT = 1
5+
MODE_MMAP = 2
6+
MODE_FILE = 4
7+
MODE_MEMORY = 8

maxminddb/extension/maxminddb.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,20 @@ static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list);
4949
# define UNUSED(x) UNUSED_ ## x
5050
#endif
5151

52-
static int Reader_init(PyObject *self, PyObject *args, PyObject *UNUSED(kwds))
52+
static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds)
5353
{
5454
char *filename;
55+
int mode = 0;
5556

56-
if (!PyArg_ParseTuple(args, "s", &filename)) {
57+
static char *kwlist[] = {"database", "mode", NULL};
58+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &filename, &mode)) {
59+
return -1;
60+
}
61+
62+
if (mode != 0 && mode != 1) {
63+
PyErr_Format(PyExc_ValueError, "Unsupported open mode (%i). Only "
64+
"MODE_AUTO and MODE_MMAP_EXT are supported by this extension.",
65+
mode);
5766
return -1;
5867
}
5968

@@ -540,13 +549,21 @@ MOD_INIT(extension){
540549
}
541550
PyModule_AddObject(m, "extension", (PyObject *)&Metadata_Type);
542551

543-
MaxMindDB_error = PyErr_NewException("extension.InvalidDatabaseError", NULL,
544-
NULL);
552+
PyObject* error_mod = PyImport_ImportModule("maxminddb.errors");
553+
if (error_mod == NULL) {
554+
RETURN_MOD_INIT(NULL);
555+
}
556+
557+
MaxMindDB_error = PyObject_GetAttrString(error_mod, "InvalidDatabaseError");
558+
Py_DECREF(error_mod);
559+
545560
if (MaxMindDB_error == NULL) {
546561
RETURN_MOD_INIT(NULL);
547562
}
548563

549564
Py_INCREF(MaxMindDB_error);
565+
566+
/* We primarily add it to the module for backwards compatibility */
550567
PyModule_AddObject(m, "InvalidDatabaseError", MaxMindDB_error);
551568

552569
RETURN_MOD_INIT(m);

maxminddb/file.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ def __getitem__(self, key):
2121
else:
2222
raise TypeError("Invalid argument type.")
2323

24-
def rfind(self, needle, beg):
25-
"""Reverse find needle from beg"""
26-
start = max(0, beg)
24+
def rfind(self, needle, start):
25+
"""Reverse find needle from start"""
2726
self._handle.seek(start)
2827
pos = self._handle.read(self._size - start - 1).rfind(needle)
2928
if pos == -1:

maxminddb/reader.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import struct
1717

1818
from maxminddb.compat import byte_from_int, int_from_byte, ipaddress
19+
from maxminddb.const import MODE_AUTO, MODE_MMAP, MODE_FILE, MODE_MEMORY
1920
from maxminddb.decoder import Decoder
2021
from maxminddb.errors import InvalidDatabaseError
2122
from maxminddb.file import FileBuffer
@@ -33,24 +34,41 @@ class Reader(object):
3334

3435
_ipv4_start = None
3536

36-
def __init__(self, database):
37+
def __init__(self, database, mode=MODE_AUTO):
3738
"""Reader for the MaxMind DB file format
3839
3940
Arguments:
4041
database -- A path to a valid MaxMind DB file such as a GeoIP2
4142
database file.
43+
mode -- mode to open the database with. Valid mode are:
44+
* MODE_MMAP - read from memory map.
45+
* MODE_FILE - read database as standard file.
46+
* MODE_MEMORY - load database into memory.
47+
* MODE_AUTO - tries MODE_MMAP and then MODE_FILE. Default.
4248
"""
43-
if mmap:
49+
if (mode == MODE_AUTO and mmap) or mode == MODE_MMAP:
4450
with open(database, 'rb') as db_file:
4551
self._buffer = mmap.mmap(
4652
db_file.fileno(), 0, access=mmap.ACCESS_READ)
47-
else:
53+
self._buffer_size = self._buffer.size()
54+
elif mode in (MODE_AUTO, MODE_FILE):
4855
self._buffer = FileBuffer(database)
56+
self._buffer_size = self._buffer.size()
57+
elif mode == MODE_MEMORY:
58+
with open(database, 'rb') as db_file:
59+
self._buffer = db_file.read()
60+
self._buffer_size = len(self._buffer)
61+
else:
62+
raise ValueError('Unsupported open mode ({0}). Only MODE_AUTO, '
63+
' MODE_FILE, and MODE_MEMORY are support by the pure Python '
64+
'Reader'.format(mode))
4965

5066
metadata_start = self._buffer.rfind(self._METADATA_START_MARKER,
51-
self._buffer.size() - 128 * 1024)
67+
max(0, self._buffer_size
68+
- 128 * 1024))
5269

5370
if metadata_start == -1:
71+
self.close()
5472
raise InvalidDatabaseError('Error opening database file ({0}). '
5573
'Is this a valid MaxMind DB file?'
5674
''.format(database))
@@ -149,7 +167,7 @@ def _resolve_data_pointer(self, pointer):
149167
resolved = pointer - self._metadata.node_count + \
150168
self._metadata.search_tree_size
151169

152-
if resolved > self._buffer.size():
170+
if resolved > self._buffer_size:
153171
raise InvalidDatabaseError(
154172
"The MaxMind DB file's search tree is corrupt")
155173

@@ -158,7 +176,8 @@ def _resolve_data_pointer(self, pointer):
158176

159177
def close(self):
160178
"""Closes the MaxMind DB file and returns the resources to the system"""
161-
self._buffer.close()
179+
if type(self._buffer) not in (str, bytes):
180+
self._buffer.close()
162181

163182

164183
class Metadata(object):

0 commit comments

Comments
 (0)