Skip to content

Commit e12365d

Browse files
committed
Misc code cleanup and documentation
1 parent 12c8e7e commit e12365d

File tree

4 files changed

+94
-34
lines changed

4 files changed

+94
-34
lines changed

maxminddb/compat.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import sys
22

3+
# pylint: skip-file
4+
35
is_py2 = sys.version_info[0] == 2
46

57
is_py3_3_or_better = (
@@ -25,12 +27,10 @@ def int_from_bytes(b):
2527
byte_from_int = chr
2628

2729
else:
28-
# XXX -This does apparently slows down the reader by 25 lookups per second
29-
# during the search tree lookup. Figure out alternative
3030
int_from_byte = lambda x: x
3131

3232
FileNotFoundError = FileNotFoundError
3333

3434
int_from_bytes = lambda x: int.from_bytes(x, 'big')
3535

36-
byte_from_int = lambda x: bytes([x])
36+
byte_from_int = lambda x: bytes([x])

maxminddb/decoder.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
1+
"""
2+
maxminddb.decoder
3+
~~~~~~~~~~~~~~~~~
4+
5+
This package contains code for decoding the MaxMind DB data section.
6+
7+
"""
18
from __future__ import unicode_literals
29

3-
from struct import pack, unpack
10+
import struct
411

5-
from .compat import byte_from_int, int_from_bytes
6-
from .errors import InvalidDatabaseError
12+
from maxminddb.compat import byte_from_int, int_from_bytes
13+
from maxminddb.errors import InvalidDatabaseError
714

815

9-
class Decoder(object):
16+
class Decoder(object): # pylint: disable=too-few-public-methods
1017

11-
"""Decodes the data section of the MaxMind DB"""
18+
"""Decoder for the data section of the MaxMind DB"""
1219

1320
def __init__(self, database_buffer, pointer_base=0, pointer_test=False):
21+
"""Created a Decoder for a MaxMind DB
22+
23+
Arguments:
24+
database_buffer -- an mmap'd MaxMind DB file.
25+
pointer_base -- the base number to use when decoding a pointer
26+
pointer_test -- used for internal unit testing of pointer code
27+
"""
1428
self._pointer_test = pointer_test
1529
self._buffer = database_buffer
1630
self._pointer_base = pointer_base
@@ -29,15 +43,19 @@ def _decode_bytes(self, size, offset):
2943
new_offset = offset + size
3044
return self._buffer[offset:new_offset], new_offset
3145

46+
# pylint: disable=no-self-argument
47+
# |-> I am open to better ways of doing this as long as it doesn't involve
48+
# lots of code duplication.
3249
def _decode_packed_type(type_code, type_size, pad=False):
50+
# pylint: disable=protected-access, missing-docstring
3351
def unpack_type(self, size, offset):
3452
if not pad:
3553
self._verify_size(size, type_size)
3654
new_offset = offset + type_size
3755
packed_bytes = self._buffer[offset:new_offset]
3856
if pad:
3957
packed_bytes = packed_bytes.rjust(type_size, b'\x00')
40-
(value,) = unpack(type_code, packed_bytes)
58+
(value,) = struct.unpack(type_code, packed_bytes)
4159
return value, new_offset
4260
return unpack_type
4361

@@ -59,9 +77,9 @@ def _decode_map(self, size, offset):
5977
def _decode_pointer(self, size, offset):
6078
pointer_size = ((size >> 3) & 0x3) + 1
6179
new_offset = offset + pointer_size
62-
b = self._buffer[offset:new_offset]
63-
packed = b if pointer_size == 4 else pack(
64-
b'!c', byte_from_int(size & 0x7)) + b
80+
pointer_bytes = self._buffer[offset:new_offset]
81+
packed = pointer_bytes if pointer_size == 4 else struct.pack(
82+
b'!c', byte_from_int(size & 0x7)) + pointer_bytes
6583
unpacked = int_from_bytes(packed)
6684
pointer = unpacked + self._pointer_base + \
6785
self._pointer_value_offset[pointer_size]
@@ -72,8 +90,8 @@ def _decode_pointer(self, size, offset):
7290

7391
def _decode_uint(self, size, offset):
7492
new_offset = offset + size
75-
b = self._buffer[offset:new_offset]
76-
return int_from_bytes(b), new_offset
93+
uint_bytes = self._buffer[offset:new_offset]
94+
return int_from_bytes(uint_bytes), new_offset
7795

7896
def _decode_utf8_string(self, size, offset):
7997
new_offset = offset + size
@@ -96,8 +114,13 @@ def _decode_utf8_string(self, size, offset):
96114
}
97115

98116
def decode(self, offset):
117+
"""Decode a section of the data section starting at offset
118+
119+
Arguments:
120+
offset -- the location of the data structure to decode
121+
"""
99122
new_offset = offset + 1
100-
(ctrl_byte,) = unpack(b'!B', self._buffer[offset:new_offset])
123+
(ctrl_byte,) = struct.unpack(b'!B', self._buffer[offset:new_offset])
101124
type_num = ctrl_byte >> 5
102125
# Extended type
103126
if not type_num:
@@ -108,7 +131,7 @@ def decode(self, offset):
108131
return self._type_dispatch[type_num](self, size, new_offset)
109132

110133
def _read_extended(self, offset):
111-
(next_byte,) = unpack(b'!B', self._buffer[offset:offset + 1])
134+
(next_byte,) = struct.unpack(b'!B', self._buffer[offset:offset + 1])
112135
type_num = next_byte + 7
113136
if type_num < 7:
114137
raise InvalidDatabaseError(
@@ -134,10 +157,11 @@ def _size_from_ctrl_byte(self, ctrl_byte, offset, type_num):
134157
# Using unpack rather than int_from_bytes as it is about 200 lookups
135158
# per second faster here.
136159
if size == 29:
137-
size = 29 + unpack(b'!B', size_bytes)[0]
160+
size = 29 + struct.unpack(b'!B', size_bytes)[0]
138161
elif size == 30:
139-
size = 285 + unpack(b'!H', size_bytes)[0]
162+
size = 285 + struct.unpack(b'!H', size_bytes)[0]
140163
elif size > 30:
141-
size = unpack(b'!I', size_bytes.rjust(4, b'\x00'))[0] + 65821
164+
size = struct.unpack(
165+
b'!I', size_bytes.rjust(4, b'\x00'))[0] + 65821
142166

143167
return size, offset + bytes_to_read

maxminddb/errors.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
"""This module contains custom errors for the MaxMind DB reader"""
2+
3+
14
class InvalidDatabaseError(RuntimeError):
25

36
"""This error is thrown when unexpected data is found in the database."""

maxminddb/reader.py

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
"""
2+
maxminddb.reader
3+
~~~~~~~~~~~~~~~~
4+
5+
This module contains the pure Python database reader and related classes.
6+
7+
"""
18
from __future__ import unicode_literals
29

310
import mmap
4-
from struct import unpack
11+
import struct
512

6-
from .compat import byte_from_int, int_from_byte, ipaddress
7-
from .decoder import Decoder
8-
from .errors import InvalidDatabaseError
13+
from maxminddb.compat import byte_from_int, int_from_byte, ipaddress
14+
from maxminddb.decoder import Decoder
15+
from maxminddb.errors import InvalidDatabaseError
916

1017

1118
class Reader(object):
@@ -23,8 +30,9 @@ class Reader(object):
2330
def __init__(self, database):
2431
"""Reader for the MaxMind DB file format
2532
26-
The file passed to it must be a valid MaxMind DB file such as a GeoIP2
27-
database file.
33+
Arguments:
34+
database -- A path to a valid MaxMind DB file such as a GeoIP2
35+
database file.
2836
"""
2937
with open(database, 'r+b') as db_file:
3038
self._buffer = mmap.mmap(
@@ -41,25 +49,31 @@ def __init__(self, database):
4149
metadata_start += len(self._METADATA_START_MARKER)
4250
metadata_decoder = Decoder(self._buffer, metadata_start)
4351
(metadata, _) = metadata_decoder.decode(metadata_start)
44-
self._metadata = Metadata(**metadata)
52+
self._metadata = Metadata(**metadata) # pylint: disable=star-args
4553

4654
self._decoder = Decoder(self._buffer, self._metadata.search_tree_size
4755
+ self._DATA_SECTION_SEPARATOR_SIZE)
4856

4957
# XXX - consider making a property
5058
def metadata(self):
59+
"""Return the metadata associated with the MaxMind DB file"""
5160
return self._metadata
5261

5362
# XXX - change to lookup?
5463
def get(self, ip_address):
55-
"""Look up ip_address in the MaxMind DB"""
56-
ip = ipaddress.ip_address(ip_address)
64+
"""Return the record for the ip_address in the MaxMind DB
65+
5766
58-
if ip.version == 6 and self._metadata.ip_version == 4:
67+
Arguments:
68+
ip_address -- an IP address in the standard string notation
69+
"""
70+
address = ipaddress.ip_address(ip_address)
71+
72+
if address.version == 6 and self._metadata.ip_version == 4:
5973
raise ValueError('Error looking up {0}. You attempted to look up '
6074
'an IPv6 address in an IPv4-only database.'.format(
6175
ip_address))
62-
pointer = self._find_address_in_tree(ip)
76+
pointer = self._find_address_in_tree(address)
6377

6478
return self._resolve_data_pointer(pointer) if pointer else None
6579

@@ -93,7 +107,7 @@ def _start_node(self, length):
93107
return self._ipv4_start
94108

95109
node = 0
96-
for i in range(96):
110+
for _ in range(96):
97111
if node >= self._metadata.node_count:
98112
break
99113
node = self._read_node(node, 0)
@@ -108,7 +122,7 @@ def _read_node(self, node_number, index):
108122
offset = base_offset + index * 3
109123
node_bytes = b'\x00' + self._buffer[offset:offset + 3]
110124
elif record_size == 28:
111-
(middle,) = unpack(
125+
(middle,) = struct.unpack(
112126
b'!B', self._buffer[base_offset + 3:base_offset + 4])
113127
if index:
114128
middle &= 0x0F
@@ -123,7 +137,7 @@ def _read_node(self, node_number, index):
123137
else:
124138
raise InvalidDatabaseError(
125139
'Unknown record size: {0}'.format(record_size))
126-
return unpack(b'!I', node_bytes)[0]
140+
return struct.unpack(b'!I', node_bytes)[0]
127141

128142
def _resolve_data_pointer(self, pointer):
129143
resolved = pointer - self._metadata.node_count + \
@@ -137,18 +151,37 @@ def _resolve_data_pointer(self, pointer):
137151
return data
138152

139153
def close(self):
154+
"""Closes the MaxMind DB file and returns the resources to the system"""
140155
self._buffer.close()
141156

142157

143158
class Metadata(object):
144159

160+
"""Metadata for the MaxMind DB reader"""
161+
162+
# pylint: disable=too-many-instance-attributes
145163
def __init__(self, **kwargs):
146-
self.__dict__.update(kwargs)
164+
"""Creates new Metadata object. kwargs are key/value pairs from spec"""
165+
# Although I could just update __dict__, that is less obvious and it
166+
# doesn't work well with static analysis tools and some IDEs
167+
self.node_count = kwargs['node_count']
168+
self.record_size = kwargs['record_size']
169+
self.ip_version = kwargs['ip_version']
170+
self.database_type = kwargs['database_type']
171+
self.languages = kwargs['languages']
172+
self.binary_format_major_version = kwargs[
173+
'binary_format_major_version']
174+
self.binary_format_minor_version = kwargs[
175+
'binary_format_minor_version']
176+
self.build_epoch = kwargs['build_epoch']
177+
self.description = kwargs['description']
147178

148179
@property
149180
def node_byte_size(self):
181+
"""The size of a node in bytes"""
150182
return self.record_size // 4
151183

152184
@property
153185
def search_tree_size(self):
186+
"""The size of the search tree"""
154187
return self.node_count * self.node_byte_size

0 commit comments

Comments
 (0)