Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rfc3986/_mixin.py: 67%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

137 statements  

1"""Module containing the implementation of the URIMixin class.""" 

2 

3import typing as t 

4import warnings 

5 

6from . import exceptions as exc 

7from . import misc 

8from . import normalizers 

9from . import uri 

10from . import validators 

11from ._typing_compat import Self as _Self 

12 

13 

14class _AuthorityInfo(t.TypedDict): 

15 """A typed dict for the authority info triple: userinfo, host, and port.""" 

16 

17 userinfo: t.Optional[str] 

18 host: t.Optional[str] 

19 port: t.Optional[str] 

20 

21 

22class URIMixin: 

23 """Mixin with all shared methods for URIs and IRIs.""" 

24 

25 if t.TYPE_CHECKING: 

26 scheme: t.Optional[str] 

27 authority: t.Optional[str] 

28 path: t.Optional[str] 

29 query: t.Optional[str] 

30 fragment: t.Optional[str] 

31 encoding: str 

32 

33 def authority_info(self) -> _AuthorityInfo: 

34 """Return a dictionary with the ``userinfo``, ``host``, and ``port``. 

35 

36 If the authority is not valid, it will raise a 

37 :class:`~rfc3986.exceptions.InvalidAuthority` Exception. 

38 

39 :returns: 

40 ``{'userinfo': 'username:password', 'host': 'www.example.com', 

41 'port': '80'}`` 

42 :rtype: dict 

43 :raises rfc3986.exceptions.InvalidAuthority: 

44 If the authority is not ``None`` and can not be parsed. 

45 """ 

46 if not self.authority: 

47 return {"userinfo": None, "host": None, "port": None} 

48 

49 match = self._match_subauthority() 

50 

51 if match is None: 

52 # In this case, we have an authority that was parsed from the URI 

53 # Reference, but it cannot be further parsed by our 

54 # misc.SUBAUTHORITY_MATCHER. In this case it must not be a valid 

55 # authority. 

56 raise exc.InvalidAuthority(self.authority.encode(self.encoding)) 

57 

58 # We had a match, now let's ensure that it is actually a valid host 

59 # address if it is IPv4 

60 matches = match.groupdict() 

61 host = matches.get("host") 

62 

63 if ( 

64 host 

65 and misc.IPv4_MATCHER.match(host) 

66 and not validators.valid_ipv4_host_address(host) 

67 ): 

68 # If we have a host, it appears to be IPv4 and it does not have 

69 # valid bytes, it is an InvalidAuthority. 

70 raise exc.InvalidAuthority(self.authority.encode(self.encoding)) 

71 

72 return matches 

73 

74 def _match_subauthority(self) -> t.Optional[t.Match[str]]: 

75 return misc.SUBAUTHORITY_MATCHER.match(self.authority) 

76 

77 @property 

78 def _validator(self) -> validators.Validator: 

79 v = getattr(self, "_cached_validator", None) 

80 if v is not None: 

81 return v 

82 self._cached_validator = validators.Validator().require_presence_of( 

83 "scheme" 

84 ) 

85 return self._cached_validator 

86 

87 @property 

88 def host(self) -> t.Optional[str]: 

89 """If present, a string representing the host.""" 

90 try: 

91 authority = self.authority_info() 

92 except exc.InvalidAuthority: 

93 return None 

94 return authority["host"] 

95 

96 @property 

97 def port(self) -> t.Optional[str]: 

98 """If present, the port extracted from the authority.""" 

99 try: 

100 authority = self.authority_info() 

101 except exc.InvalidAuthority: 

102 return None 

103 return authority["port"] 

104 

105 @property 

106 def userinfo(self) -> t.Optional[str]: 

107 """If present, the userinfo extracted from the authority.""" 

108 try: 

109 authority = self.authority_info() 

110 except exc.InvalidAuthority: 

111 return None 

112 return authority["userinfo"] 

113 

114 def is_absolute(self) -> bool: 

115 """Determine if this URI Reference is an absolute URI. 

116 

117 See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation. 

118 

119 :returns: ``True`` if it is an absolute URI, ``False`` otherwise. 

120 :rtype: bool 

121 """ 

122 return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit())) 

123 

124 def is_valid(self, **kwargs: bool) -> bool: 

125 """Determine if the URI is valid. 

126 

127 .. deprecated:: 1.1.0 

128 

129 Use the :class:`~rfc3986.validators.Validator` object instead. 

130 

131 :param bool require_scheme: Set to ``True`` if you wish to require the 

132 presence of the scheme component. 

133 :param bool require_authority: Set to ``True`` if you wish to require 

134 the presence of the authority component. 

135 :param bool require_path: Set to ``True`` if you wish to require the 

136 presence of the path component. 

137 :param bool require_query: Set to ``True`` if you wish to require the 

138 presence of the query component. 

139 :param bool require_fragment: Set to ``True`` if you wish to require 

140 the presence of the fragment component. 

141 :returns: ``True`` if the URI is valid. ``False`` otherwise. 

142 :rtype: bool 

143 """ 

144 warnings.warn( 

145 "Please use rfc3986.validators.Validator instead. " 

146 "This method will be eventually removed.", 

147 DeprecationWarning, 

148 ) 

149 validators = [ 

150 (self.scheme_is_valid, kwargs.get("require_scheme", False)), 

151 (self.authority_is_valid, kwargs.get("require_authority", False)), 

152 (self.path_is_valid, kwargs.get("require_path", False)), 

153 (self.query_is_valid, kwargs.get("require_query", False)), 

154 (self.fragment_is_valid, kwargs.get("require_fragment", False)), 

155 ] 

156 return all(v(r) for v, r in validators) 

157 

158 def authority_is_valid(self, require: bool = False) -> bool: 

159 """Determine if the authority component is valid. 

160 

161 .. deprecated:: 1.1.0 

162 

163 Use the :class:`~rfc3986.validators.Validator` object instead. 

164 

165 :param bool require: 

166 Set to ``True`` to require the presence of this component. 

167 :returns: 

168 ``True`` if the authority is valid. ``False`` otherwise. 

169 :rtype: 

170 bool 

171 """ 

172 warnings.warn( 

173 "Please use rfc3986.validators.Validator instead. " 

174 "This method will be eventually removed.", 

175 DeprecationWarning, 

176 ) 

177 try: 

178 self.authority_info() 

179 except exc.InvalidAuthority: 

180 return False 

181 

182 return validators.authority_is_valid( 

183 self.authority, 

184 host=self.host, 

185 require=require, 

186 ) 

187 

188 def scheme_is_valid(self, require: bool = False) -> bool: 

189 """Determine if the scheme component is valid. 

190 

191 .. deprecated:: 1.1.0 

192 

193 Use the :class:`~rfc3986.validators.Validator` object instead. 

194 

195 :param str require: Set to ``True`` to require the presence of this 

196 component. 

197 :returns: ``True`` if the scheme is valid. ``False`` otherwise. 

198 :rtype: bool 

199 """ 

200 warnings.warn( 

201 "Please use rfc3986.validators.Validator instead. " 

202 "This method will be eventually removed.", 

203 DeprecationWarning, 

204 ) 

205 return validators.scheme_is_valid(self.scheme, require) 

206 

207 def path_is_valid(self, require: bool = False) -> bool: 

208 """Determine if the path component is valid. 

209 

210 .. deprecated:: 1.1.0 

211 

212 Use the :class:`~rfc3986.validators.Validator` object instead. 

213 

214 :param str require: Set to ``True`` to require the presence of this 

215 component. 

216 :returns: ``True`` if the path is valid. ``False`` otherwise. 

217 :rtype: bool 

218 """ 

219 warnings.warn( 

220 "Please use rfc3986.validators.Validator instead. " 

221 "This method will be eventually removed.", 

222 DeprecationWarning, 

223 ) 

224 return validators.path_is_valid(self.path, require) 

225 

226 def query_is_valid(self, require: bool = False) -> bool: 

227 """Determine if the query component is valid. 

228 

229 .. deprecated:: 1.1.0 

230 

231 Use the :class:`~rfc3986.validators.Validator` object instead. 

232 

233 :param str require: Set to ``True`` to require the presence of this 

234 component. 

235 :returns: ``True`` if the query is valid. ``False`` otherwise. 

236 :rtype: bool 

237 """ 

238 warnings.warn( 

239 "Please use rfc3986.validators.Validator instead. " 

240 "This method will be eventually removed.", 

241 DeprecationWarning, 

242 ) 

243 return validators.query_is_valid(self.query, require) 

244 

245 def fragment_is_valid(self, require: bool = False) -> bool: 

246 """Determine if the fragment component is valid. 

247 

248 .. deprecated:: 1.1.0 

249 

250 Use the Validator object instead. 

251 

252 :param str require: Set to ``True`` to require the presence of this 

253 component. 

254 :returns: ``True`` if the fragment is valid. ``False`` otherwise. 

255 :rtype: bool 

256 """ 

257 warnings.warn( 

258 "Please use rfc3986.validators.Validator instead. " 

259 "This method will be eventually removed.", 

260 DeprecationWarning, 

261 ) 

262 return validators.fragment_is_valid(self.fragment, require) 

263 

264 def normalized_equality(self, other_ref: "uri.URIReference") -> bool: 

265 """Compare this URIReference to another URIReference. 

266 

267 :param URIReference other_ref: (required), The reference with which 

268 we're comparing. 

269 :returns: ``True`` if the references are equal, ``False`` otherwise. 

270 :rtype: bool 

271 """ 

272 return tuple(self.normalize()) == tuple(other_ref.normalize()) 

273 

274 def resolve_with( # noqa: C901 

275 self, 

276 base_uri: t.Union[str, "uri.URIReference"], 

277 strict: bool = False, 

278 ) -> _Self: 

279 """Use an absolute URI Reference to resolve this relative reference. 

280 

281 Assuming this is a relative reference that you would like to resolve, 

282 use the provided base URI to resolve it. 

283 

284 See http://tools.ietf.org/html/rfc3986#section-5 for more information. 

285 

286 :param base_uri: Either a string or URIReference. It must be an 

287 absolute URI or it will raise an exception. 

288 :returns: A new URIReference which is the result of resolving this 

289 reference using ``base_uri``. 

290 :rtype: :class:`URIReference` 

291 :raises rfc3986.exceptions.ResolutionError: 

292 If the ``base_uri`` does not at least have a scheme. 

293 """ 

294 if not isinstance(base_uri, URIMixin): 

295 base_uri = type(self).from_string(base_uri) 

296 

297 if t.TYPE_CHECKING: 

298 base_uri = t.cast(uri.URIReference, base_uri) 

299 

300 try: 

301 self._validator.validate(base_uri) 

302 except exc.ValidationError: 

303 raise exc.ResolutionError(base_uri) 

304 

305 # This is optional per 

306 # http://tools.ietf.org/html/rfc3986#section-5.2.1 

307 base_uri = base_uri.normalize() 

308 

309 # The reference we're resolving 

310 resolving = self 

311 

312 if not strict and resolving.scheme == base_uri.scheme: 

313 resolving = resolving.copy_with(scheme=None) 

314 

315 # http://tools.ietf.org/html/rfc3986#page-32 

316 if resolving.scheme is not None: 

317 target = resolving.copy_with( 

318 path=normalizers.normalize_path(resolving.path) 

319 ) 

320 else: 

321 if resolving.authority is not None: 

322 target = resolving.copy_with( 

323 scheme=base_uri.scheme, 

324 path=normalizers.normalize_path(resolving.path), 

325 ) 

326 else: 

327 if resolving.path is None: 

328 if resolving.query is not None: 

329 query = resolving.query 

330 else: 

331 query = base_uri.query 

332 target = resolving.copy_with( 

333 scheme=base_uri.scheme, 

334 authority=base_uri.authority, 

335 path=base_uri.path, 

336 query=query, 

337 ) 

338 else: 

339 if resolving.path.startswith("/"): 

340 path = normalizers.normalize_path(resolving.path) 

341 else: 

342 path = normalizers.normalize_path( 

343 misc.merge_paths(base_uri, resolving.path) 

344 ) 

345 target = resolving.copy_with( 

346 scheme=base_uri.scheme, 

347 authority=base_uri.authority, 

348 path=path, 

349 query=resolving.query, 

350 ) 

351 return target 

352 

353 def unsplit(self) -> str: 

354 """Create a URI string from the components. 

355 

356 :returns: The URI Reference reconstituted as a string. 

357 :rtype: str 

358 """ 

359 # See http://tools.ietf.org/html/rfc3986#section-5.3 

360 result_list: list[str] = [] 

361 if self.scheme: 

362 result_list.extend([self.scheme, ":"]) 

363 if self.authority: 

364 result_list.extend(["//", self.authority]) 

365 if self.path: 

366 result_list.append(self.path) 

367 if self.query is not None: 

368 result_list.extend(["?", self.query]) 

369 if self.fragment is not None: 

370 result_list.extend(["#", self.fragment]) 

371 return "".join(result_list) 

372 

373 def copy_with( 

374 self, 

375 scheme: t.Optional[str] = misc.UseExisting, 

376 authority: t.Optional[str] = misc.UseExisting, 

377 path: t.Optional[str] = misc.UseExisting, 

378 query: t.Optional[str] = misc.UseExisting, 

379 fragment: t.Optional[str] = misc.UseExisting, 

380 ) -> _Self: 

381 """Create a copy of this reference with the new components. 

382 

383 :param str scheme: 

384 (optional) The scheme to use for the new reference. 

385 :param str authority: 

386 (optional) The authority to use for the new reference. 

387 :param str path: 

388 (optional) The path to use for the new reference. 

389 :param str query: 

390 (optional) The query to use for the new reference. 

391 :param str fragment: 

392 (optional) The fragment to use for the new reference. 

393 :returns: 

394 New URIReference with provided components. 

395 :rtype: 

396 URIReference 

397 """ 

398 attributes = { 

399 "scheme": scheme, 

400 "authority": authority, 

401 "path": path, 

402 "query": query, 

403 "fragment": fragment, 

404 } 

405 for key, value in list(attributes.items()): 

406 if value is misc.UseExisting: 

407 del attributes[key] 

408 uri: _Self = self._replace(**attributes) 

409 uri.encoding = self.encoding 

410 return uri