66 GeoIP(R) is a registered trademark of MaxMind, LLC of Boston, Massachusetts.
77
88 For IP-based geolocation, this module requires the GeoLite Country and City
9- datasets, in binary format (CSV will not work!). The datasets may be
9+ datasets, in binary format (CSV will not work!). The datasets may be
1010 downloaded from MaxMind at http://www.maxmind.com/download/geoip/database/.
1111 Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
1212 corresponding to settings.GEOIP_PATH. See the GeoIP docstring and examples
3434 >>> g.lat_lon('salon.com')
3535 (37.789798736572266, -122.39420318603516)
3636 >>> g.lon_lat('uh.edu')
37- (-95.415199279785156, 29.77549934387207)
37+ (-95.415199279785156, 29.77549934387207)
3838 >>> g.geos('24.124.1.80').wkt
3939 'POINT (-95.2087020874023438 39.0392990112304688)'
4040"""
4545if not settings .configured : settings .configure ()
4646
4747# Creating the settings dictionary with any settings, if needed.
48- GEOIP_SETTINGS = dict ((key , getattr (settings , key ))
48+ GEOIP_SETTINGS = dict ((key , getattr (settings , key ))
4949 for key in ('GEOIP_PATH' , 'GEOIP_LIBRARY_PATH' , 'GEOIP_COUNTRY' , 'GEOIP_CITY' )
5050 if hasattr (settings , key ))
5151lib_path = GEOIP_SETTINGS .get ('GEOIP_LIBRARY_PATH' , None )
@@ -83,8 +83,17 @@ class GeoIPRecord(Structure):
8383 ('postal_code' , c_char_p ),
8484 ('latitude' , c_float ),
8585 ('longitude' , c_float ),
86+ # TODO: In 1.4.6 this changed from `int dma_code;` to
87+ # `union {int metro_code; int dma_code;};`. Change
88+ # to a `ctypes.Union` in to accomodate in future when
89+ # pre-1.4.6 versions are no longer distributed.
8690 ('dma_code' , c_int ),
8791 ('area_code' , c_int ),
92+ # TODO: The following structure fields were added in 1.4.3 --
93+ # uncomment these fields when sure previous versions are no
94+ # longer distributed by package maintainers.
95+ #('charset', c_int),
96+ #('continent_code', c_char_p),
8897 ]
8998class GeoIPTag (Structure ): pass
9099
@@ -99,9 +108,12 @@ def record_output(func):
99108rec_by_addr = record_output (lgeoip .GeoIP_record_by_addr )
100109rec_by_name = record_output (lgeoip .GeoIP_record_by_name )
101110
102- # For opening up GeoIP databases .
111+ # For opening & closing GeoIP database files .
103112geoip_open = lgeoip .GeoIP_open
104113geoip_open .restype = DBTYPE
114+ geoip_close = lgeoip .GeoIP_delete
115+ geoip_close .argtypes = [DBTYPE ]
116+ geoip_close .restype = None
105117
106118# String output routines.
107119def string_output (func ):
@@ -136,6 +148,12 @@ class GeoIP(object):
136148 GEOIP_CHECK_CACHE = 2
137149 GEOIP_INDEX_CACHE = 4
138150 cache_options = dict ((opt , None ) for opt in (0 , 1 , 2 , 4 ))
151+ _city_file = ''
152+ _country_file = ''
153+
154+ # Initially, pointers to GeoIP file references are NULL.
155+ _city = None
156+ _country = None
139157
140158 def __init__ (self , path = None , cache = 0 , country = None , city = None ):
141159 """
@@ -174,43 +192,42 @@ def __init__(self, path=None, cache=0, country=None, city=None):
174192 if not isinstance (path , basestring ):
175193 raise TypeError ('Invalid path type: %s' % type (path ).__name__ )
176194
177- cntry_ptr , city_ptr = (None , None )
178195 if os .path .isdir (path ):
179- # Getting the country and city files using the settings
180- # dictionary. If no settings are provided, default names
181- # are assigned.
182- country = os .path .join (path , country or GEOIP_SETTINGS .get ('GEOIP_COUNTRY' , 'GeoIP.dat' ))
183- city = os .path .join (path , city or GEOIP_SETTINGS .get ('GEOIP_CITY' , 'GeoLiteCity.dat' ))
196+ # Constructing the GeoIP database filenames using the settings
197+ # dictionary. If the database files for the GeoLite country
198+ # and/or city datasets exist, then try and open them.
199+ country_db = os .path .join (path , country or GEOIP_SETTINGS .get ('GEOIP_COUNTRY' , 'GeoIP.dat' ))
200+ if os .path .isfile (country_db ):
201+ self ._country = geoip_open (country_db , cache )
202+ self ._country_file = country_db
203+
204+ city_db = os .path .join (path , city or GEOIP_SETTINGS .get ('GEOIP_CITY' , 'GeoLiteCity.dat' ))
205+ if os .path .isfile (city_db ):
206+ self ._city = geoip_open (city_db , cache )
207+ self ._city_file = city_db
184208 elif os .path .isfile (path ):
185209 # Otherwise, some detective work will be needed to figure
186210 # out whether the given database path is for the GeoIP country
187211 # or city databases.
188212 ptr = geoip_open (path , cache )
189213 info = geoip_dbinfo (ptr )
190214 if lite_regex .match (info ):
191- # GeoLite City database.
192- city , city_ptr = path , ptr
215+ # GeoLite City database detected.
216+ self ._city = ptr
217+ self ._city_file = path
193218 elif free_regex .match (info ):
194- # GeoIP Country database.
195- country , cntry_ptr = path , ptr
219+ # GeoIP Country database detected.
220+ self ._country = ptr
221+ self ._country_file = path
196222 else :
197223 raise GeoIPException ('Unable to recognize database edition: %s' % info )
198224 else :
199225 raise GeoIPException ('GeoIP path must be a valid file or directory.' )
200-
201- # `_init_db` does the dirty work.
202- self ._init_db (country , cache , '_country' , cntry_ptr )
203- self ._init_db (city , cache , '_city' , city_ptr )
204-
205- def _init_db (self , db_file , cache , attname , ptr = None ):
206- "Helper routine for setting GeoIP ctypes database properties."
207- if ptr :
208- # Pointer already retrieved.
209- pass
210- elif os .path .isfile (db_file or '' ):
211- ptr = geoip_open (db_file , cache )
212- setattr (self , attname , ptr )
213- setattr (self , '%s_file' % attname , db_file )
226+
227+ def __del__ (self ):
228+ # Cleaning any GeoIP file handles lying around.
229+ if self ._country : geoip_close (self ._country )
230+ if self ._city : geoip_close (self ._city )
214231
215232 def _check_query (self , query , country = False , city = False , city_or_country = False ):
216233 "Helper routine for checking the query and database availability."
@@ -219,11 +236,11 @@ def _check_query(self, query, country=False, city=False, city_or_country=False):
219236 raise TypeError ('GeoIP query must be a string, not type %s' % type (query ).__name__ )
220237
221238 # Extra checks for the existence of country and city databases.
222- if city_or_country and self ._country is None and self ._city is None :
239+ if city_or_country and not ( self ._country or self ._city ) :
223240 raise GeoIPException ('Invalid GeoIP country and city data files.' )
224- elif country and self ._country is None :
241+ elif country and not self ._country :
225242 raise GeoIPException ('Invalid GeoIP country data file: %s' % self ._country_file )
226- elif city and self ._city is None :
243+ elif city and not self ._city :
227244 raise GeoIPException ('Invalid GeoIP city data file: %s' % self ._city_file )
228245
229246 def city (self , query ):
@@ -247,7 +264,7 @@ def city(self, query):
247264 return dict ((tup [0 ], getattr (record , tup [0 ])) for tup in record ._fields_ )
248265 else :
249266 return None
250-
267+
251268 def country_code (self , query ):
252269 "Returns the country code for the given IP Address or FQDN."
253270 self ._check_query (query , city_or_country = True )
@@ -268,12 +285,12 @@ def country_name(self, query):
268285
269286 def country (self , query ):
270287 """
271- Returns a dictonary with with the country code and name when given an
288+ Returns a dictonary with with the country code and name when given an
272289 IP address or a Fully Qualified Domain Name (FQDN). For example, both
273290 '24.124.1.80' and 'djangoproject.com' are valid parameters.
274291 """
275292 # Returning the country code and name
276- return {'country_code' : self .country_code (query ),
293+ return {'country_code' : self .country_code (query ),
277294 'country_name' : self .country_name (query ),
278295 }
279296
@@ -318,7 +335,7 @@ def city_info(self):
318335 ci = geoip_dbinfo (self ._city )
319336 return ci
320337 city_info = property (city_info )
321-
338+
322339 def info (self ):
323340 "Returns information about all GeoIP databases in use."
324341 return 'Country:\n \t %s\n City:\n \t %s' % (self .country_info , self .city_info )
0 commit comments