1414
1515
1616"""Tools to parse and validate a MongoDB URI."""
17+ import warnings
1718
1819from bson .py3compat import PY3 , iteritems , string_type
1920
2223else :
2324 from urllib import unquote_plus
2425
25- from pymongo .common import validate as _validate
26+ from pymongo .common import ( validate as _validate , get_validated_options )
2627from pymongo .errors import ConfigurationError , InvalidURI
2728
2829
@@ -129,6 +130,8 @@ def parse_host(entity, default_port=DEFAULT_PORT):
129130 port = default_port
130131 if entity [0 ] == '[' :
131132 host , port = parse_ipv6_literal_host (entity , default_port )
133+ elif entity .endswith (".sock" ):
134+ return entity , default_port
132135 elif entity .find (':' ) != - 1 :
133136 if entity .count (':' ) > 1 :
134137 raise ValueError ("Reserved characters such as ':' must be "
@@ -137,8 +140,9 @@ def parse_host(entity, default_port=DEFAULT_PORT):
137140 "and ']' according to RFC 2732." )
138141 host , port = host .split (':' , 1 )
139142 if isinstance (port , string_type ):
140- if not port .isdigit ():
141- raise ValueError ("Port number must be an integer." )
143+ if not port .isdigit () or int (port ) > 65535 or int (port ) <= 0 :
144+ raise ValueError ("Port must be an integer between 0 and 65535: %s"
145+ % (port ,))
142146 port = int (port )
143147
144148 # Normalize hostname to lowercase, since DNS is case-insensitive:
@@ -148,15 +152,23 @@ def parse_host(entity, default_port=DEFAULT_PORT):
148152 return host .lower (), port
149153
150154
151- def validate_options (opts ):
155+ def validate_options (opts , warn = False ):
152156 """Validates and normalizes options passed in a MongoDB URI.
153157
154- Returns a new dictionary of validated and normalized options.
158+ Returns a new dictionary of validated and normalized options. If warn is
159+ False then errors will be thrown for invalid options, otherwise they will
160+ be ignored and a warning will be issued.
155161
156162 :Parameters:
157163 - `opts`: A dict of MongoDB URI options.
164+ - `warn` (optional): If ``True`` then warnigns will be logged and
165+ invalid options will be ignored. Otherwise invalid options will
166+ cause errors.
158167 """
159- return dict ([_validate (opt , val ) for opt , val in iteritems (opts )])
168+ if warn :
169+ return get_validated_options (opts )
170+ else :
171+ return dict ([_validate (opt , val ) for opt , val in iteritems (opts )])
160172
161173
162174def _parse_options (opts , delim ):
@@ -172,11 +184,21 @@ def _parse_options(opts, delim):
172184 # str(option) to ensure that a unicode URI results in plain 'str'
173185 # option names. 'normalized' is then suitable to be passed as
174186 # kwargs in all Python versions.
175- options [str (key )] = val
187+ if str (key ) in options :
188+ warnings .warn ("Duplicate URI option %s" % (str (key ),))
189+ options [str (key )] = unquote_plus (val )
190+
191+ # Special case for deprecated options
192+ if "wtimeout" in options :
193+ if "wtimeoutMS" in options :
194+ options .pop ("wtimeout" )
195+ warnings .warn ("Option wtimeout is deprecated, use 'wtimeoutMS'"
196+ " instead" )
197+
176198 return options
177199
178200
179- def split_options (opts , validate = True ):
201+ def split_options (opts , validate = True , warn = False ):
180202 """Takes the options portion of a MongoDB URI, validates each option
181203 and returns the options in a dictionary.
182204
@@ -202,7 +224,7 @@ def split_options(opts, validate=True):
202224 raise InvalidURI ("MongoDB URI options are key=value pairs." )
203225
204226 if validate :
205- return validate_options (options )
227+ return validate_options (options , warn )
206228 return options
207229
208230
@@ -232,7 +254,7 @@ def split_hosts(hosts, default_port=DEFAULT_PORT):
232254 return nodes
233255
234256
235- def parse_uri (uri , default_port = DEFAULT_PORT , validate = True ):
257+ def parse_uri (uri , default_port = DEFAULT_PORT , validate = True , warn = False ):
236258 """Parse and validate a MongoDB URI.
237259
238260 Returns a dict of the form::
@@ -252,6 +274,13 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
252274 for a host in the URI.
253275 - `validate`: If ``True`` (the default), validate and normalize all
254276 options.
277+ - `warn` (optional): When validating, if ``True`` then will warn
278+ the user then ignore any invalid options or values. If ``False``,
279+ validation will error when options are unsupported or values are
280+ invalid.
281+
282+ .. versionchanged:: 3.1
283+ ``warn`` added so invalid options can be ignored.
255284 """
256285 if not uri .startswith (SCHEME ):
257286 raise InvalidURI ("Invalid URI scheme: URI "
@@ -262,7 +291,6 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
262291 if not scheme_free :
263292 raise InvalidURI ("Must provide at least one hostname or IP." )
264293
265- nodes = None
266294 user = None
267295 passwd = None
268296 dbase = None
@@ -272,11 +300,14 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
272300 # Check for unix domain sockets in the uri
273301 if '.sock' in scheme_free :
274302 host_part , _ , path_part = _rpartition (scheme_free , '/' )
275- try :
276- parse_uri ('%s%s' % (SCHEME , host_part ))
277- except (ConfigurationError , InvalidURI ):
278- host_part = scheme_free
303+ if not host_part :
304+ host_part = path_part
279305 path_part = ""
306+ if '/' in host_part :
307+ raise InvalidURI ("Any '/' in a unix domain socket must be"
308+ " URL encoded: %s" % host_part )
309+ host_part = unquote_plus (host_part )
310+ path_part = unquote_plus (path_part )
280311 else :
281312 host_part , _ , path_part = _partition (scheme_free , '/' )
282313
@@ -302,7 +333,12 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
302333 dbase , collection = dbase .split ('.' , 1 )
303334
304335 if opts :
305- options = split_options (opts , validate )
336+ options = split_options (opts , validate , warn )
337+
338+ if dbase is not None :
339+ dbase = unquote_plus (dbase )
340+ if collection is not None :
341+ collection = unquote_plus (collection )
306342
307343 return {
308344 'nodelist' : nodes ,
0 commit comments