Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/utils.py: 23%

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

241 statements  

1from __future__ import annotations 

2 

3import io 

4import mimetypes 

5import os 

6import pkgutil 

7import re 

8import sys 

9import typing as t 

10import unicodedata 

11from datetime import datetime 

12from time import time 

13from urllib.parse import quote 

14from zlib import adler32 

15 

16from markupsafe import escape 

17 

18from ._internal import _DictAccessorProperty 

19from ._internal import _missing 

20from ._internal import _TAccessorValue 

21from .datastructures import Headers 

22from .exceptions import NotFound 

23from .exceptions import RequestedRangeNotSatisfiable 

24from .security import _windows_device_files 

25from .security import safe_join 

26from .wsgi import wrap_file 

27 

28if t.TYPE_CHECKING: 

29 from _typeshed.wsgi import WSGIEnvironment 

30 

31 from .wrappers.request import Request 

32 from .wrappers.response import Response 

33 

34_T = t.TypeVar("_T") 

35 

36_entity_re = re.compile(r"&([^;]+);") 

37_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9_.-]") 

38 

39 

40class cached_property(property, t.Generic[_T]): 

41 """A :func:`property` that is only evaluated once. Subsequent access 

42 returns the cached value. Setting the property sets the cached 

43 value. Deleting the property clears the cached value, accessing it 

44 again will evaluate it again. 

45 

46 .. code-block:: python 

47 

48 class Example: 

49 @cached_property 

50 def value(self): 

51 # calculate something important here 

52 return 42 

53 

54 e = Example() 

55 e.value # evaluates 

56 e.value # uses cache 

57 e.value = 16 # sets cache 

58 del e.value # clears cache 

59 

60 If the class defines ``__slots__``, it must add ``_cache_{name}`` as 

61 a slot. Alternatively, it can add ``__dict__``, but that's usually 

62 not desirable. 

63 

64 .. versionchanged:: 2.1 

65 Works with ``__slots__``. 

66 

67 .. versionchanged:: 2.0 

68 ``del obj.name`` clears the cached value. 

69 """ 

70 

71 def __init__( 

72 self, 

73 fget: t.Callable[[t.Any], _T], 

74 name: str | None = None, 

75 doc: str | None = None, 

76 ) -> None: 

77 super().__init__(fget, doc=doc) 

78 self.__name__ = name or fget.__name__ 

79 self.slot_name = f"_cache_{self.__name__}" 

80 self.__module__ = fget.__module__ 

81 

82 def __set__(self, obj: object, value: _T) -> None: 

83 if hasattr(obj, "__dict__"): 

84 obj.__dict__[self.__name__] = value 

85 else: 

86 setattr(obj, self.slot_name, value) 

87 

88 def __get__(self, obj: object, type: type = None) -> _T: # type: ignore 

89 if obj is None: 

90 return self # type: ignore 

91 

92 obj_dict = getattr(obj, "__dict__", None) 

93 

94 if obj_dict is not None: 

95 value: _T = obj_dict.get(self.__name__, _missing) 

96 else: 

97 value = getattr(obj, self.slot_name, _missing) # type: ignore[arg-type] 

98 

99 if value is _missing: 

100 value = self.fget(obj) # type: ignore 

101 

102 if obj_dict is not None: 

103 obj.__dict__[self.__name__] = value 

104 else: 

105 setattr(obj, self.slot_name, value) 

106 

107 return value 

108 

109 def __delete__(self, obj: object) -> None: 

110 if hasattr(obj, "__dict__"): 

111 del obj.__dict__[self.__name__] 

112 else: 

113 setattr(obj, self.slot_name, _missing) 

114 

115 

116class environ_property(_DictAccessorProperty[_TAccessorValue]): 

117 """Maps request attributes to environment variables. This works not only 

118 for the Werkzeug request object, but also any other class with an 

119 environ attribute: 

120 

121 >>> class Test(object): 

122 ... environ = {'key': 'value'} 

123 ... test = environ_property('key') 

124 >>> var = Test() 

125 >>> var.test 

126 'value' 

127 

128 If you pass it a second value it's used as default if the key does not 

129 exist, the third one can be a converter that takes a value and converts 

130 it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value 

131 is used. If no default value is provided `None` is used. 

132 

133 Per default the property is read only. You have to explicitly enable it 

134 by passing ``read_only=False`` to the constructor. 

135 """ 

136 

137 read_only = True 

138 

139 def lookup(self, obj: Request) -> WSGIEnvironment: 

140 return obj.environ 

141 

142 

143class header_property(_DictAccessorProperty[_TAccessorValue]): 

144 """Like `environ_property` but for headers.""" 

145 

146 def lookup(self, obj: Request | Response) -> Headers: # type: ignore[override] 

147 return obj.headers 

148 

149 

150# https://cgit.freedesktop.org/xdg/shared-mime-info/tree/freedesktop.org.xml.in 

151# https://www.iana.org/assignments/media-types/media-types.xhtml 

152# Types listed in the XDG mime info that have a charset in the IANA registration. 

153_charset_mimetypes = { 

154 "application/ecmascript", 

155 "application/javascript", 

156 "application/sql", 

157 "application/xml", 

158 "application/xml-dtd", 

159 "application/xml-external-parsed-entity", 

160} 

161 

162 

163def get_content_type(mimetype: str, charset: str) -> str: 

164 """Returns the full content type string with charset for a mimetype. 

165 

166 If the mimetype represents text, the charset parameter will be 

167 appended, otherwise the mimetype is returned unchanged. 

168 

169 :param mimetype: The mimetype to be used as content type. 

170 :param charset: The charset to be appended for text mimetypes. 

171 :return: The content type. 

172 

173 .. versionchanged:: 0.15 

174 Any type that ends with ``+xml`` gets a charset, not just those 

175 that start with ``application/``. Known text types such as 

176 ``application/javascript`` are also given charsets. 

177 """ 

178 if ( 

179 mimetype.startswith("text/") 

180 or mimetype in _charset_mimetypes 

181 or mimetype.endswith("+xml") 

182 ): 

183 mimetype += f"; charset={charset}" 

184 

185 return mimetype 

186 

187 

188def secure_filename(filename: str) -> str: 

189 r"""Pass it a filename and it will return a secure version of it. This 

190 filename can then safely be stored on a regular file system and passed 

191 to :func:`os.path.join`. The filename returned is an ASCII only string 

192 for maximum portability. 

193 

194 On windows systems the function also makes sure that the file is not 

195 named after one of the special device files. 

196 

197 >>> secure_filename("My cool movie.mov") 

198 'My_cool_movie.mov' 

199 >>> secure_filename("../../../etc/passwd") 

200 'etc_passwd' 

201 >>> secure_filename('i contain cool \xfcml\xe4uts.txt') 

202 'i_contain_cool_umlauts.txt' 

203 

204 The function might return an empty filename. It's your responsibility 

205 to ensure that the filename is unique and that you abort or 

206 generate a random filename if the function returned an empty one. 

207 

208 .. versionadded:: 0.5 

209 

210 :param filename: the filename to secure 

211 """ 

212 filename = unicodedata.normalize("NFKD", filename) 

213 filename = filename.encode("ascii", "ignore").decode("ascii") 

214 

215 for sep in os.sep, os.path.altsep: 

216 if sep: 

217 filename = filename.replace(sep, " ") 

218 filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip( 

219 "._" 

220 ) 

221 

222 # on nt a couple of special files are present in each folder. We 

223 # have to ensure that the target file is not such a filename. In 

224 # this case we prepend an underline 

225 if ( 

226 os.name == "nt" 

227 and filename 

228 and filename.split(".")[0].upper() in _windows_device_files 

229 ): 

230 filename = f"_{filename}" 

231 

232 return filename 

233 

234 

235def redirect( 

236 location: str, code: int = 303, Response: type[Response] | None = None 

237) -> Response: 

238 """Create a response that redirects the client to the target location. 

239 

240 The default ``303 See Other`` status code instructs the client to make a 

241 ``GET`` request to the target, regardless of what method the current request 

242 is. This produces the correct result for the common use cases: page redirects 

243 and form success. The status codes you're likely to use are: 

244 

245 - ``303 See Other`` always uses a ``GET`` request. 

246 - ``307 Temporary Redirect`` preserves the current method. 

247 - ``308 Permanent Redirect`` preserves the current method, and instructs 

248 the client to permanently apply the result. This is hard to undo once 

249 you've sent it, so be sure the permanence is what you want. 

250 

251 Two older codes, ``302 Found`` and ``301 Moved Permanently``, are 

252 superseded by ``307`` and ``308`` respectively. They were not consistently 

253 implemented by clients, which tend to switch ``POST`` to ``GET`` but 

254 preserve other methods. Prefer using ``303``, ``307``, and ``308`` to get 

255 the exact behavior you intend. Other ``3xx`` codes are either not defined or 

256 have specific use cases. 

257 

258 :param location: The URL to redirect to. The client will interpret a 

259 relative URL (without the host) as relative to the host it's accessing. 

260 :param code: The redirect status code. This affects how the client issues 

261 the next request. Defaults to ``303``. 

262 :param Response: The response class. Defaults to 

263 :class:`werkzeug.wrappers.Response`. 

264 

265 .. versionchanged:: 3.2 

266 ``code`` defaults to 303 instead of 302. 

267 

268 .. versionchanged:: 0.10 

269 Added the ``response`` parameter. 

270 

271 .. versionchanged:: 0.6 

272 ``location`` can contain Unicode characters. 

273 """ 

274 if Response is None: 

275 from .wrappers import Response 

276 

277 html_location = escape(location) 

278 response = Response( # type: ignore[misc] 

279 "<!doctype html>\n" 

280 "<html lang=en>\n" 

281 "<title>Redirecting...</title>\n" 

282 "<h1>Redirecting...</h1>\n" 

283 "<p>You should be redirected automatically to the target URL: " 

284 f'<a href="{html_location}">{html_location}</a>. If not, click the link.\n', 

285 code, 

286 mimetype="text/html", 

287 ) 

288 response.headers["Location"] = location 

289 return response 

290 

291 

292def append_slash_redirect(environ: WSGIEnvironment, code: int = 308) -> Response: 

293 """Redirect to the current URL with a slash appended. 

294 

295 If the current URL is ``/user/42``, the redirect URL will be 

296 ``42/``. When joined to the current URL during response 

297 processing or by the browser, this will produce ``/user/42/``. 

298 

299 The behavior is undefined if the path ends with a slash already. If 

300 called unconditionally on a URL, it may produce a redirect loop. 

301 

302 :param environ: Use the path and query from this WSGI environment 

303 to produce the redirect URL. 

304 :param code: the status code for the redirect. 

305 

306 .. versionchanged:: 2.1 

307 Produce a relative URL that only modifies the last segment. 

308 Relevant when the current path has multiple segments. 

309 

310 .. versionchanged:: 2.1 

311 The default status code is 308 instead of 301. This preserves 

312 the request method and body. 

313 """ 

314 tail = environ["PATH_INFO"].rpartition("/")[2] 

315 

316 if not tail: 

317 new_path = "./" 

318 else: 

319 new_path = f"{tail}/" 

320 

321 query_string = environ.get("QUERY_STRING") 

322 

323 if query_string: 

324 new_path = f"{new_path}?{query_string}" 

325 

326 return redirect(new_path, code) 

327 

328 

329def send_file( 

330 path_or_file: os.PathLike[str] | str | t.IO[bytes], 

331 environ: WSGIEnvironment, 

332 mimetype: str | None = None, 

333 as_attachment: bool = False, 

334 download_name: str | None = None, 

335 conditional: bool = True, 

336 etag: bool | str = True, 

337 last_modified: datetime | int | float | None = None, 

338 max_age: None | (int | t.Callable[[str | None], int | None]) = None, 

339 use_x_sendfile: bool = False, 

340 response_class: type[Response] | None = None, 

341 _root_path: os.PathLike[str] | str | None = None, 

342) -> Response: 

343 """Send the contents of a file to the client. 

344 

345 The first argument can be a file path or a file-like object. Paths 

346 are preferred in most cases because Werkzeug can manage the file and 

347 get extra information from the path. Passing a file-like object 

348 requires that the file is opened in binary mode, and is mostly 

349 useful when building a file in memory with :class:`io.BytesIO`. 

350 

351 Never pass file paths provided by a user. The path is assumed to be 

352 trusted, so a user could craft a path to access a file you didn't 

353 intend. Use :func:`send_from_directory` to safely serve user-provided paths. 

354 

355 If the WSGI server sets a ``file_wrapper`` in ``environ``, it is 

356 used, otherwise Werkzeug's built-in wrapper is used. Alternatively, 

357 if the HTTP server supports ``X-Sendfile``, ``use_x_sendfile=True`` 

358 will tell the server to send the given path, which is much more 

359 efficient than reading it in Python. 

360 

361 :param path_or_file: The path to the file to send, relative to the 

362 current working directory if a relative path is given. 

363 Alternatively, a file-like object opened in binary mode. Make 

364 sure the file pointer is seeked to the start of the data. 

365 :param environ: The WSGI environ for the current request. 

366 :param mimetype: The MIME type to send for the file. If not 

367 provided, it will try to detect it from the file name. 

368 :param as_attachment: Indicate to a browser that it should offer to 

369 save the file instead of displaying it. 

370 :param download_name: The default name browsers will use when saving 

371 the file. Defaults to the passed file name. 

372 :param conditional: Enable conditional and range responses based on 

373 request headers. Requires passing a file path and ``environ``. 

374 :param etag: Calculate an ETag for the file, which requires passing 

375 a file path. Can also be a string to use instead. 

376 :param last_modified: The last modified time to send for the file, 

377 in seconds. If not provided, it will try to detect it from the 

378 file path. 

379 :param max_age: How long the client should cache the file, in 

380 seconds. If set, ``Cache-Control`` will be ``public``, otherwise 

381 it will be ``no-cache`` to prefer conditional caching. 

382 :param use_x_sendfile: Set the ``X-Sendfile`` header to let the 

383 server to efficiently send the file. Requires support from the 

384 HTTP server. Requires passing a file path. 

385 :param response_class: Build the response using this class. Defaults 

386 to :class:`~werkzeug.wrappers.Response`. 

387 :param _root_path: Do not use. For internal use only. Use 

388 :func:`send_from_directory` to safely send files under a path. 

389 

390 .. versionchanged:: 2.0.2 

391 ``send_file`` only sets a detected ``Content-Encoding`` if 

392 ``as_attachment`` is disabled. 

393 

394 .. versionadded:: 2.0 

395 Adapted from Flask's implementation. 

396 

397 .. versionchanged:: 2.0 

398 ``download_name`` replaces Flask's ``attachment_filename`` 

399 parameter. If ``as_attachment=False``, it is passed with 

400 ``Content-Disposition: inline`` instead. 

401 

402 .. versionchanged:: 2.0 

403 ``max_age`` replaces Flask's ``cache_timeout`` parameter. 

404 ``conditional`` is enabled and ``max_age`` is not set by 

405 default. 

406 

407 .. versionchanged:: 2.0 

408 ``etag`` replaces Flask's ``add_etags`` parameter. It can be a 

409 string to use instead of generating one. 

410 

411 .. versionchanged:: 2.0 

412 If an encoding is returned when guessing ``mimetype`` from 

413 ``download_name``, set the ``Content-Encoding`` header. 

414 """ 

415 if response_class is None: 

416 from .wrappers import Response 

417 

418 response_class = Response 

419 

420 path: str | None = None 

421 file: t.IO[bytes] | None = None 

422 size: int | None = None 

423 mtime: float | None = None 

424 headers = Headers() 

425 

426 if isinstance(path_or_file, (os.PathLike, str)) or hasattr( 

427 path_or_file, "__fspath__" 

428 ): 

429 path_or_file = t.cast("os.PathLike[str] | str", path_or_file) 

430 

431 # Flask will pass app.root_path, allowing its send_file wrapper 

432 # to not have to deal with paths. 

433 if _root_path is not None: 

434 path = os.path.join(_root_path, path_or_file) 

435 else: 

436 path = os.path.abspath(path_or_file) 

437 

438 stat = os.stat(path) 

439 size = stat.st_size 

440 mtime = stat.st_mtime 

441 else: 

442 file = path_or_file 

443 

444 if download_name is None and path is not None: 

445 download_name = os.path.basename(path) 

446 

447 if mimetype is None: 

448 if download_name is None: 

449 raise TypeError( 

450 "Unable to detect the MIME type because a file name is" 

451 " not available. Either set 'download_name', pass a" 

452 " path instead of a file, or set 'mimetype'." 

453 ) 

454 

455 mimetype, encoding = mimetypes.guess_type(download_name) 

456 

457 if mimetype is None: 

458 mimetype = "application/octet-stream" 

459 

460 # Don't send encoding for attachments, it causes browsers to 

461 # save decompress tar.gz files. 

462 if encoding is not None and not as_attachment: 

463 headers.set("Content-Encoding", encoding) 

464 

465 if download_name is not None: 

466 try: 

467 download_name.encode("ascii") 

468 except UnicodeEncodeError: 

469 simple = unicodedata.normalize("NFKD", download_name) 

470 simple = simple.encode("ascii", "ignore").decode("ascii") 

471 # safe = RFC 5987 attr-char 

472 quoted = quote(download_name, safe="!#$&+-.^_`|~") 

473 names = {"filename": simple, "filename*": f"UTF-8''{quoted}"} 

474 else: 

475 names = {"filename": download_name} 

476 

477 value = "attachment" if as_attachment else "inline" 

478 headers.set("Content-Disposition", value, **names) 

479 elif as_attachment: 

480 raise TypeError( 

481 "No name provided for attachment. Either set" 

482 " 'download_name' or pass a path instead of a file." 

483 ) 

484 

485 if use_x_sendfile and path is not None: 

486 headers["X-Sendfile"] = path 

487 data = None 

488 else: 

489 if file is None: 

490 file = open(path, "rb") # type: ignore 

491 elif isinstance(file, io.BytesIO): 

492 size = file.getbuffer().nbytes 

493 elif isinstance(file, io.TextIOBase): 

494 raise ValueError("Files must be opened in binary mode or use BytesIO.") 

495 

496 data = wrap_file(environ, file) 

497 

498 rv = response_class( 

499 data, mimetype=mimetype, headers=headers, direct_passthrough=True 

500 ) 

501 

502 if size is not None: 

503 rv.content_length = size 

504 

505 if last_modified is not None: 

506 rv.last_modified = last_modified # type: ignore 

507 elif mtime is not None: 

508 rv.last_modified = mtime # type: ignore 

509 

510 rv.cache_control.no_cache = True 

511 

512 # Flask will pass app.get_send_file_max_age, allowing its send_file 

513 # wrapper to not have to deal with paths. 

514 if callable(max_age): 

515 max_age = max_age(path) 

516 

517 if max_age is not None: 

518 if max_age > 0: 

519 rv.cache_control.no_cache = None 

520 rv.cache_control.public = True 

521 

522 rv.cache_control.max_age = max_age 

523 rv.expires = int(time() + max_age) # type: ignore 

524 

525 if isinstance(etag, str): 

526 rv.set_etag(etag) 

527 elif etag and path is not None: 

528 check = adler32(path.encode()) & 0xFFFFFFFF 

529 rv.set_etag(f"{mtime}-{size}-{check}") 

530 

531 if conditional: 

532 try: 

533 rv = rv.make_conditional(environ, accept_ranges=True, complete_length=size) 

534 except RequestedRangeNotSatisfiable: 

535 if file is not None: 

536 file.close() 

537 

538 raise 

539 

540 # Some x-sendfile implementations incorrectly ignore the 304 

541 # status code and send the file anyway. 

542 if rv.status_code == 304: 

543 rv.headers.pop("X-Sendfile", None) 

544 

545 return rv 

546 

547 

548def send_from_directory( 

549 directory: os.PathLike[str] | str, 

550 path: os.PathLike[str] | str, 

551 environ: WSGIEnvironment, 

552 **kwargs: t.Any, 

553) -> Response: 

554 """Send a file from within a directory using :func:`send_file`. 

555 

556 This is a secure way to serve files from a folder, such as static 

557 files or uploads. Uses :func:`~werkzeug.security.safe_join` to 

558 ensure the path coming from the client is not maliciously crafted to 

559 point outside the specified directory. 

560 

561 If the final path does not point to an existing regular file, 

562 returns a 404 :exc:`~werkzeug.exceptions.NotFound` error. 

563 

564 :param directory: The directory that ``path`` must be located under. This *must not* 

565 be a value provided by the client, otherwise it becomes insecure. 

566 :param path: The path to the file to send, relative to ``directory``. This is the 

567 part of the path provided by the client, which is checked for security. 

568 :param environ: The WSGI environ for the current request. 

569 :param kwargs: Arguments to pass to :func:`send_file`. 

570 

571 .. versionadded:: 2.0 

572 Adapted from Flask's implementation. 

573 """ 

574 path_str = safe_join(os.fspath(directory), os.fspath(path)) 

575 

576 if path_str is None: 

577 raise NotFound() 

578 

579 # Flask will pass app.root_path, allowing its send_from_directory 

580 # wrapper to not have to deal with paths. 

581 if "_root_path" in kwargs: 

582 path_str = os.path.join(kwargs["_root_path"], path_str) 

583 

584 if not os.path.isfile(path_str): 

585 raise NotFound() 

586 

587 return send_file(path_str, environ, **kwargs) 

588 

589 

590def import_string(import_name: str, silent: bool = False) -> t.Any: 

591 """Imports an object based on a string. This is useful if you want to 

592 use import paths as endpoints or something similar. An import path can 

593 be specified either in dotted notation (``xml.sax.saxutils.escape``) 

594 or with a colon as object delimiter (``xml.sax.saxutils:escape``). 

595 

596 If `silent` is True the return value will be `None` if the import fails. 

597 

598 :param import_name: the dotted name for the object to import. 

599 :param silent: if set to `True` import errors are ignored and 

600 `None` is returned instead. 

601 :return: imported object 

602 """ 

603 import_name = import_name.replace(":", ".") 

604 try: 

605 try: 

606 __import__(import_name) 

607 except ImportError: 

608 if "." not in import_name: 

609 raise 

610 else: 

611 return sys.modules[import_name] 

612 

613 module_name, obj_name = import_name.rsplit(".", 1) 

614 module = __import__(module_name, globals(), locals(), [obj_name]) 

615 try: 

616 return getattr(module, obj_name) 

617 except AttributeError as e: 

618 raise ImportError(e) from None 

619 

620 except ImportError as e: 

621 if not silent: 

622 raise ImportStringError(import_name, e).with_traceback( 

623 sys.exc_info()[2] 

624 ) from None 

625 

626 return None 

627 

628 

629def find_modules( 

630 import_path: str, include_packages: bool = False, recursive: bool = False 

631) -> t.Iterator[str]: 

632 """Finds all the modules below a package. This can be useful to 

633 automatically import all views / controllers so that their metaclasses / 

634 function decorators have a chance to register themselves on the 

635 application. 

636 

637 Packages are not returned unless `include_packages` is `True`. This can 

638 also recursively list modules but in that case it will import all the 

639 packages to get the correct load path of that module. 

640 

641 :param import_path: the dotted name for the package to find child modules. 

642 :param include_packages: set to `True` if packages should be returned, too. 

643 :param recursive: set to `True` if recursion should happen. 

644 :return: generator 

645 """ 

646 module = import_string(import_path) 

647 path = getattr(module, "__path__", None) 

648 if path is None: 

649 raise ValueError(f"{import_path!r} is not a package") 

650 basename = f"{module.__name__}." 

651 for _importer, modname, ispkg in pkgutil.iter_modules(path): 

652 modname = basename + modname 

653 if ispkg: 

654 if include_packages: 

655 yield modname 

656 if recursive: 

657 yield from find_modules(modname, include_packages, True) 

658 else: 

659 yield modname 

660 

661 

662class ImportStringError(ImportError): 

663 """Provides information about a failed :func:`import_string` attempt.""" 

664 

665 #: String in dotted notation that failed to be imported. 

666 import_name: str 

667 #: Wrapped exception. 

668 exception: BaseException 

669 

670 def __init__(self, import_name: str, exception: BaseException) -> None: 

671 self.import_name = import_name 

672 self.exception = exception 

673 msg = import_name 

674 name = "" 

675 tracked = [] 

676 for part in import_name.replace(":", ".").split("."): 

677 name = f"{name}.{part}" if name else part 

678 imported = import_string(name, silent=True) 

679 if imported: 

680 tracked.append((name, getattr(imported, "__file__", None))) 

681 else: 

682 track = [f"- {n!r} found in {i!r}." for n, i in tracked] 

683 track.append(f"- {name!r} not found.") 

684 track_str = "\n".join(track) 

685 msg = ( 

686 f"import_string() failed for {import_name!r}. Possible reasons" 

687 f" are:\n\n" 

688 "- missing __init__.py in a package;\n" 

689 "- package or module path not included in sys.path;\n" 

690 "- duplicated package or module name taking precedence in" 

691 " sys.path;\n" 

692 "- missing module, class, function or variable;\n\n" 

693 f"Debugged import:\n\n{track_str}\n\n" 

694 f"Original exception:\n\n{type(exception).__name__}: {exception}" 

695 ) 

696 break 

697 

698 super().__init__(msg) 

699 

700 def __repr__(self) -> str: 

701 return f"<{type(self).__name__}({self.import_name!r}, {self.exception!r})>"