1=============================================================================== 2vistir: Setup / utilities which most projects eventually need 3=============================================================================== 4 5.. image:: https://img.shields.io/pypi/v/vistir.svg 6 :target: https://pypi.python.org/pypi/vistir 7 8.. image:: https://img.shields.io/pypi/l/vistir.svg 9 :target: https://pypi.python.org/pypi/vistir 10 11.. image:: https://travis-ci.com/sarugaku/vistir.svg?branch=master 12 :target: https://travis-ci.com/sarugaku/vistir 13 14.. image:: https://dev.azure.com/sarugaku/vistir/_apis/build/status/Vistir%20Build%20Pipeline?branchName=master 15 :target: https://dev.azure.com/sarugaku/vistir/_build/latest?definitionId=2&branchName=master 16 17.. image:: https://img.shields.io/pypi/pyversions/vistir.svg 18 :target: https://pypi.python.org/pypi/vistir 19 20.. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg 21 :target: https://saythanks.io/to/techalchemy 22 23.. image:: https://readthedocs.org/projects/vistir/badge/?version=latest 24 :target: https://vistir.readthedocs.io/en/latest/?badge=latest 25 :alt: Documentation Status 26 27 28 Installation 29================= 30 31Install from `PyPI`_: 32 33 :: 34 35 $ pipenv install vistir 36 37Install from `Github`_: 38 39 :: 40 41 $ pipenv install -e git+https://github.com/sarugaku/vistir.git#egg=vistir 42 43 44.. _PyPI: https://www.pypi.org/project/vistir 45.. _Github: https://github.com/sarugaku/vistir 46 47 48.. _`Summary`: 49 50 Summary 51=========== 52 53**vistir** is a library full of utility functions designed to make life easier. Here are 54some of the places where these functions are used: 55 56 * `pipenv`_ 57 * `requirementslib`_ 58 * `pip-tools`_ 59 * `passa`_ 60 * `pythonfinder`_ 61 62.. _passa: https://github.com/sarugaku/passa 63.. _pipenv: https://github.com/pypa/pipenv 64.. _pip-tools: https://github.com/jazzband/pip-tools 65.. _requirementslib: https://github.com/sarugaku/requirementslib 66.. _pythonfinder: https://github.com/sarugaku/pythonfinder 67 68 69.. _`Usage`: 70 71 Usage 72========== 73 74Importing a utility 75-------------------- 76 77You can import utilities directly from **vistir**: 78 79.. code:: python 80 81 from vistir import cd 82 cd('/path/to/somedir'): 83 do_stuff_in('somedir') 84 85 86.. _`Functionality`: 87 88 Functionality 89================== 90 91**vistir** provides several categories of functionality, including: 92 93 * Backports 94 * Compatibility Shims 95 * Context Managers 96 * Miscellaneous Utilities 97 * Path Utilities 98 99.. note:: 100 101 The backports should be imported via :mod:`~vistir.compat` which will provide the 102 native versions of the backported items if possible. 103 104 105 Compatibility Shims 106----------------------- 107 108Shims are provided for full API compatibility from python 2.7 through 3.7 for the following: 109 110 * :func:`weakref.finalize` 111 * :func:`functools.partialmethod` (via :func:`~vistir.backports.functools.partialmethod`) 112 * :class:`tempfile.TemporaryDirectory` (via :class:`~vistir.backports.tempfile.TemporaryDirectory`) 113 * :class:`tempfile.NamedTemporaryFile` (via :class:`~vistir.backports.tempfile.NamedTemporaryFile`) 114 * :class:`~vistir.compat.Path` 115 * :func:`~vistir.compat.get_terminal_size` 116 * :class:`~vistir.compat.JSONDecodeError` 117 * :exc:`~vistir.compat.ResourceWarning` 118 * :exc:`~vistir.compat.FileNotFoundError` 119 * :exc:`~vistir.compat.PermissionError` 120 * :exc:`~vistir.compat.IsADirectoryError` 121 122The following additional function is provided for encoding strings to the filesystem 123defualt encoding: 124 125 * :func:`~vistir.compat.fs_str` 126 * :func:`~vistir.compat.to_native_string` 127 * :func:`~vistir.compat.fs_encode` 128 * :func:`vistir.compat.fs_decode` 129 130 131 Context Managers 132-------------------- 133 134**vistir** provides the following context managers as utility contexts: 135 136 * :func:`~vistir.contextmanagers.atomic_open_for_write` 137 * :func:`~vistir.contextmanagers.cd` 138 * :func:`~vistir.contextmanagers.open_file` 139 * :func:`~vistir.contextmanagers.replaced_stream` 140 * :func:`~vistir.contextmanagers.replaced_streams` 141 * :func:`~vistir.contextmanagers.spinner` 142 * :func:`~vistir.contextmanagers.temp_environ` 143 * :func:`~vistir.contextmanagers.temp_path` 144 145 146.. _`atomic_open_for_write`: 147 148**atomic_open_for_write** 149/////////////////////////// 150 151This context manager ensures that a file only gets overwritten if the contents can be 152successfully written in its place. If you open a file for writing and then fail in the 153middle under normal circumstances, your original file is already gone. 154 155.. code:: python 156 157 >>> fn = "test_file.txt" 158 >>> with open(fn, "w") as fh: 159 fh.write("this is some test text") 160 >>> read_test_file() 161 this is some test text 162 >>> def raise_exception_while_writing(filename): 163 with vistir.contextmanagers.atomic_open_for_write(filename) as fh: 164 fh.write("Overwriting all the text from before with even newer text") 165 raise RuntimeError("But did it get overwritten now?") 166 >>> raise_exception_while_writing(fn) 167 Traceback (most recent call last): 168 ... 169 RuntimeError: But did it get overwritten now? 170 >>> read_test_file() 171 writing some new text 172 173 174.. _`cd`: 175 176**cd** 177/////// 178 179A context manager for temporarily changing the working directory. 180 181 182.. code:: python 183 184 >>> os.path.abspath(os.curdir) 185 '/tmp/test' 186 >>> with vistir.contextmanagers.cd('/tmp/vistir_test'): 187 print(os.path.abspath(os.curdir)) 188 /tmp/vistir_test 189 190 191.. _`open_file`: 192 193**open_file** 194/////////////// 195 196A context manager for streaming file contents, either local or remote. It is recommended 197to pair this with an iterator which employs a sensible chunk size. 198 199 200.. code:: python 201 202 >>> filecontents = b"" 203 with vistir.contextmanagers.open_file("https://norvig.com/big.txt") as fp: 204 for chunk in iter(lambda: fp.read(16384), b""): 205 filecontents.append(chunk) 206 >>> import io 207 >>> import shutil 208 >>> filecontents = io.BytesIO(b"") 209 >>> with vistir.contextmanagers.open_file("https://norvig.com/big.txt") as fp: 210 shutil.copyfileobj(fp, filecontents) 211 212 213.. _`replaced_stream`: 214 215**replaced_stream** 216//////////////////// 217 218A context manager to temporarily swap out *stream_name* with a stream wrapper. This will 219capture the stream output and prevent it from being written as normal. 220 221 222.. code-block:: python 223 224 >>> orig_stdout = sys.stdout 225 >>> with replaced_stream("stdout") as stdout: 226 ... sys.stdout.write("hello") 227 ... assert stdout.getvalue() == "hello" 228 229 >>> sys.stdout.write("hello") 230 'hello' 231 232 233.. _`replaced_streams`: 234 235**replaced_streams** 236///////////////////// 237 238 239Temporarily replaces both *sys.stdout* and *sys.stderr* and captures anything written 240to these respective targets. 241 242 243.. code-block:: python 244 245 >>> import sys 246 >>> with vistir.contextmanagers.replaced_streams() as streams: 247 >>> stdout, stderr = streams 248 >>> sys.stderr.write("test") 249 >>> sys.stdout.write("hello") 250 >>> assert stdout.getvalue() == "hello" 251 >>> assert stderr.getvalue() == "test" 252 253 >>> stdout.getvalue() 254 'hello' 255 256 >>> stderr.getvalue() 257 'test' 258 259 260.. _`spinner`: 261 262**spinner** 263//////////// 264 265A context manager for wrapping some actions with a threaded, interrupt-safe spinner. The 266spinner is fully compatible with all terminals (you can use ``bouncingBar`` on non-utf8 267terminals) and will allow you to update the text of the spinner itself by simply setting 268``spinner.text`` or write lines to the screen above the spinner by using 269``spinner.write(line)``. Success text can be indicated using ``spinner.ok("Text")`` and 270failure text can be indicated with ``spinner.fail("Fail text")``. 271 272.. code:: python 273 274 >>> lines = ["a", "b"] 275 >>> with vistir.contextmanagers.spinner(spinner_name="dots", text="Running...", handler_map={}, nospin=False) as sp: 276 for line in lines: 277 sp.write(line + "\n") 278 while some_variable = some_queue.pop(): 279 sp.text = "Consuming item: %s" % some_variable 280 if success_condition: 281 sp.ok("Succeeded!") 282 else: 283 sp.fail("Failed!") 284 285 286.. _`temp_environ`: 287 288**temp_environ** 289///////////////// 290 291Sets a temporary environment context to freely manipulate :data:`os.environ` which will 292be reset upon exiting the context. 293 294 295.. code:: python 296 297 >>> os.environ['MY_KEY'] = "test" 298 >>> os.environ['MY_KEY'] 299 'test' 300 >>> with vistir.contextmanagers.temp_environ(): 301 os.environ['MY_KEY'] = "another thing" 302 print("New key: %s" % os.environ['MY_KEY']) 303 New key: another thing 304 >>> os.environ['MY_KEY'] 305 'test' 306 307 308.. _`temp_path`: 309 310**temp_path** 311////////////// 312 313Sets a temporary environment context to freely manipulate :data:`sys.path` which will 314be reset upon exiting the context. 315 316 317.. code:: python 318 319 >>> path_from_virtualenv = load_path("/path/to/venv/bin/python") 320 >>> print(sys.path) 321 ['/home/user/.pyenv/versions/3.7.0/bin', '/home/user/.pyenv/versions/3.7.0/lib/python37.zip', '/home/user/.pyenv/versions/3.7.0/lib/python3.7', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'] 322 >>> with temp_path(): 323 sys.path = path_from_virtualenv 324 # Running in the context of the path above 325 run(["pip", "install", "stuff"]) 326 >>> print(sys.path) 327 ['/home/user/.pyenv/versions/3.7.0/bin', '/home/user/.pyenv/versions/3.7.0/lib/python37.zip', '/home/user/.pyenv/versions/3.7.0/lib/python3.7', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'] 328 329 330 Miscellaneous Utilities 331-------------------------- 332 333The following Miscellaneous utilities are available as helper methods: 334 335 * :func:`~vistir.misc.shell_escape` 336 * :func:`~vistir.misc.unnest` 337 * :func:`~vistir.misc.dedup` 338 * :func:`~vistir.misc.run` 339 * :func:`~vistir.misc.load_path` 340 * :func:`~vistir.misc.partialclass` 341 * :func:`~vistir.misc.to_text` 342 * :func:`~vistir.misc.to_bytes` 343 * :func:`~vistir.misc.divide` 344 * :func:`~vistir.misc.take` 345 * :func:`~vistir.misc.chunked` 346 * :func:`~vistir.misc.decode_for_output` 347 * :func:`~vistir.misc.get_canonical_encoding_name` 348 * :func:`~vistir.misc.get_wrapped_stream` 349 * :class:`~vistir.misc.StreamWrapper` 350 * :func:`~vistir.misc.get_text_stream` 351 * :func:`~vistir.misc.replace_with_text_stream` 352 * :func:`~vistir.misc.get_text_stdin` 353 * :func:`~vistir.misc.get_text_stdout` 354 * :func:`~vistir.misc.get_text_stderr` 355 * :func:`~vistir.misc.echo` 356 357 358.. _`shell_escape`: 359 360**shell_escape** 361///////////////// 362 363Escapes a string for use as shell input when passing *shell=True* to :func:`os.Popen`. 364 365.. code:: python 366 367 >>> vistir.misc.shell_escape("/tmp/test/test script.py hello") 368 '/tmp/test/test script.py hello' 369 370 371.. _`unnest`: 372 373**unnest** 374/////////// 375 376Unnests nested iterables into a flattened one. 377 378.. code:: python 379 380 >>> nested_iterable = (1234, (3456, 4398345, (234234)), (2396, (23895750, 9283798, 29384, (289375983275, 293759, 2347, (2098, 7987, 27599))))) 381 >>> list(vistir.misc.unnest(nested_iterable)) 382 [1234, 3456, 4398345, 234234, 2396, 23895750, 9283798, 29384, 289375983275, 293759, 2347, 2098, 7987, 27599] 383 384 385.. _`dedup`: 386 387**dedup** 388////////// 389 390Deduplicates an iterable (like a :class:`set`, but preserving order). 391 392.. code:: python 393 394 >>> iterable = ["repeatedval", "uniqueval", "repeatedval", "anotherval", "somethingelse"] 395 >>> list(vistir.misc.dedup(iterable)) 396 ['repeatedval', 'uniqueval', 'anotherval', 'somethingelse'] 397 398.. _`run`: 399 400**run** 401//////// 402 403Runs the given command using :func:`subprocess.Popen` and passing sane defaults. 404 405.. code:: python 406 407 >>> out, err = vistir.run(["cat", "/proc/version"]) 408 >>> out 409 'Linux version 4.15.0-27-generic (buildd@lgw01-amd64-044) (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #29-Ubuntu SMP Wed Jul 11 08:21:57 UTC 2018' 410 411 412.. _`load_path`: 413 414**load_path** 415////////////// 416 417Load the :data:`sys.path` from the given python executable's environment as json. 418 419.. code:: python 420 421 >>> load_path("/home/user/.virtualenvs/requirementslib-5MhGuG3C/bin/python") 422 ['', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python37.zip', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7/lib-dynload', '/home/user/.pyenv/versions/3.7.0/lib/python3.7', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7/site-packages', '/home/user/git/requirementslib/src'] 423 424 425.. _`partialclass`: 426 427**partialclass** 428///////////////// 429 430Create a partially instantiated class. 431 432.. code:: python 433 434 >>> source = partialclass(Source, url="https://pypi.org/simple") 435 >>> new_source = source(name="pypi") 436 >>> new_source 437 <__main__.Source object at 0x7f23af189b38> 438 >>> new_source.__dict__ 439 {'url': 'https://pypi.org/simple', 'verify_ssl': True, 'name': 'pypi'} 440 441 442.. _`to_text`: 443 444**to_text** 445//////////// 446 447Convert arbitrary text-formattable input to text while handling errors. 448 449.. code:: python 450 451 >>> vistir.misc.to_text(b"these are bytes") 452 'these are bytes' 453 454 455.. _`to_bytes`: 456 457**to_bytes** 458///////////// 459 460Converts arbitrary byte-convertable input to bytes while handling errors. 461 462.. code:: python 463 464 >>> vistir.misc.to_bytes("this is some text") 465 b'this is some text' 466 >>> vistir.misc.to_bytes(u"this is some text") 467 b'this is some text' 468 469 470.. _`chunked`: 471 472**chunked** 473//////////// 474 475Splits an iterable up into groups *of the specified length*, per `more itertools`_. Returns an iterable. 476 477This example will create groups of chunk size **5**, which means there will be *6 groups*. 478 479.. code-block:: python 480 481 >>> chunked_iterable = vistir.misc.chunked(5, range(30)) 482 >>> for chunk in chunked_iterable: 483 ... add_to_some_queue(chunk) 484 485.. _more itertools: https://more-itertools.readthedocs.io/en/latest/api.html#grouping 486 487 488.. _`take`: 489 490**take** 491///////// 492 493Take elements from the supplied iterable without consuming it. 494 495.. code-block:: python 496 497 >>> iterable = range(30) 498 >>> first_10 = take(10, iterable) 499 >>> [i for i in first_10] 500 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 501 502 >>> [i for i in iterable] 503 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] 504 505 506.. _`divide`: 507 508**divide** 509//////////// 510 511Splits an iterable up into the *specified number of groups*, per `more itertools`_. Returns an iterable. 512 513.. code-block:: python 514 515 >>> iterable = range(30) 516 >>> groups = [] 517 >>> for grp in vistir.misc.divide(3, iterable): 518 ... groups.append(grp) 519 >>> groups 520 [<tuple_iterator object at 0x7fb7966006a0>, <tuple_iterator object at 0x7fb796652780>, <tuple_iterator object at 0x7fb79650a2b0>] 521 522 523.. _more itertools: https://more-itertools.readthedocs.io/en/latest/api.html#grouping 524 525 526.. _`decode_for_output`: 527 528**decode_for_output** 529////////////////////// 530 531Converts an arbitrary text input to output which is encoded for printing to terminal 532outputs using the system preferred locale using ``locale.getpreferredencoding(False)`` 533with some additional hackery on linux systems. 534 535.. code:: python 536 537 >>> vistir.misc.decode_for_output(u"Some text") 538 "some default locale encoded text" 539 540 541.. _`get_canonical_encoding_name`: 542 543**get_canonical_encoding_name** 544//////////////////////////////// 545 546Given an encoding name, get the canonical name from a codec lookup. 547 548.. code-block:: python 549 550 >>> vistir.misc.get_canonical_encoding_name("utf8") 551 "utf-8" 552 553 554.. _`get_wrapped_stream`: 555 556**get_wrapped_stream** 557////////////////////// 558 559Given a stream, wrap it in a `StreamWrapper` instance and return the wrapped stream. 560 561.. code-block:: python 562 563 >>> stream = sys.stdout 564 >>> wrapped_stream = vistir.misc.get_wrapped_stream(sys.stdout) 565 566 567.. _`StreamWrapper`: 568 569**StreamWrapper** 570////////////////// 571 572A stream wrapper and compatibility class for handling wrapping file-like stream objects 573which may be used in place of ``sys.stdout`` and other streams. 574 575.. code-block:: python 576 577 >>> wrapped_stream = vistir.misc.StreamWrapper(sys.stdout, encoding="utf-8", errors="replace", line_buffering=True) 578 >>> wrapped_stream = vistir.misc.StreamWrapper(io.StringIO(), encoding="utf-8", errors="replace", line_buffering=True) 579 580 581.. _`get_text_stream`: 582 583**get_text_stream** 584//////////////////// 585 586An implementation of the **StreamWrapper** for the purpose of wrapping **sys.stdin** or **sys.stdout**. 587 588On Windows, this returns the appropriate handle to the requested output stream. 589 590.. code-block:: python 591 592 >>> text_stream = vistir.misc.get_text_stream("stdout") 593 >>> sys.stdout = text_stream 594 >>> sys.stdin = vistir.misc.get_text_stream("stdin") 595 >>> vistir.misc.echo(u"\0499", fg="green") 596 ҙ 597 598 599.. _`replace_with_text_stream`: 600 601**replace_with_text_stream** 602///////////////////////////// 603 604Given a text stream name, replaces the text stream with a **StreamWrapper** instance. 605 606 607.. code-block:: python 608 609 >>> vistir.misc.replace_with_text_stream("stdout") 610 611Once invoked, the standard stream in question is replaced with the required wrapper, 612turning it into a ``TextIOWrapper`` compatible stream (which ensures that unicode 613characters can be written to it). 614 615 616.. _`get_text_stdin`: 617 618**get_text_stdin** 619/////////////////// 620 621A helper function for calling **get_text_stream("stdin")**. 622 623 624.. _`get_text_stdout`: 625 626**get_text_stdout** 627//////////////////// 628 629A helper function for calling **get_text_stream("stdout")**. 630 631 632.. _`get_text_stderr`: 633 634**get_text_stderr** 635//////////////////// 636 637A helper function for calling **get_text_stream("stderr")**. 638 639 640.. _`echo`: 641 642**echo** 643///////// 644 645Writes colored, stream-compatible output to the desired handle (``sys.stdout`` by default). 646 647.. code-block:: python 648 649 >>> vistir.misc.echo("some text", fg="green", bg="black", style="bold", err=True) # write to stderr 650 some text 651 >>> vistir.misc.echo("some other text", fg="cyan", bg="white", style="underline") # write to stdout 652 some other text 653 654 655 Path Utilities 656------------------ 657 658**vistir** provides utilities for interacting with filesystem paths: 659 660 * :func:`vistir.path.normalize_path` 661 * :func:`vistir.path.is_in_path` 662 * :func:`vistir.path.get_converted_relative_path` 663 * :func:`vistir.path.handle_remove_readonly` 664 * :func:`vistir.path.is_file_url` 665 * :func:`vistir.path.is_readonly_path` 666 * :func:`vistir.path.is_valid_url` 667 * :func:`vistir.path.mkdir_p` 668 * :func:`vistir.path.ensure_mkdir_p` 669 * :func:`vistir.path.create_tracked_tempdir` 670 * :func:`vistir.path.create_tracked_tempfile` 671 * :func:`vistir.path.path_to_url` 672 * :func:`vistir.path.rmtree` 673 * :func:`vistir.path.safe_expandvars` 674 * :func:`vistir.path.set_write_bit` 675 * :func:`vistir.path.url_to_path` 676 * :func:`vistir.path.walk_up` 677 678 679.. _`normalize_path`: 680 681**normalize_path** 682////////////////// 683 684Return a case-normalized absolute variable-expanded path. 685 686 687.. code:: python 688 689 >>> vistir.path.normalize_path("~/${USER}") 690 /home/user/user 691 692 693.. _`is_in_path`: 694 695**is_in_path** 696////////////// 697 698Determine if the provided full path is in the given parent root. 699 700 701.. code:: python 702 703 >>> vistir.path.is_in_path("~/.pyenv/versions/3.7.1/bin/python", "${PYENV_ROOT}/versions") 704 True 705 706 707.. _`get_converted_relative_path`: 708 709**get_converted_relative_path** 710//////////////////////////////// 711 712Convert the supplied path to a relative path (relative to :data:`os.curdir`) 713 714 715.. code:: python 716 717 >>> os.chdir('/home/user/code/myrepo/myfolder') 718 >>> vistir.path.get_converted_relative_path('/home/user/code/file.zip') 719 './../../file.zip' 720 >>> vistir.path.get_converted_relative_path('/home/user/code/myrepo/myfolder/mysubfolder') 721 './mysubfolder' 722 >>> vistir.path.get_converted_relative_path('/home/user/code/myrepo/myfolder') 723 '.' 724 725 726.. _`handle_remove_readonly`: 727 728**handle_remove_readonly** 729/////////////////////////// 730 731Error handler for shutil.rmtree. 732 733Windows source repo folders are read-only by default, so this error handler attempts to 734set them as writeable and then proceed with deletion. 735 736This function will call check :func:`vistir.path.is_readonly_path` before attempting to 737call :func:`vistir.path.set_write_bit` on the target path and try again. 738 739 740.. _`is_file_url`: 741 742**is_file_url** 743//////////////// 744 745Checks whether the given url is a properly formatted ``file://`` uri. 746 747.. code:: python 748 749 >>> vistir.path.is_file_url('file:///home/user/somefile.zip') 750 True 751 >>> vistir.path.is_file_url('/home/user/somefile.zip') 752 False 753 754 755.. _`is_readonly_path`: 756 757**is_readonly_path** 758///////////////////// 759 760Check if a provided path exists and is readonly by checking for ``bool(path.stat & stat.S_IREAD) and not os.access(path, os.W_OK)`` 761 762.. code:: python 763 764 >>> vistir.path.is_readonly_path('/etc/passwd') 765 True 766 >>> vistir.path.is_readonly_path('/home/user/.bashrc') 767 False 768 769 770.. _`is_valid_url`: 771 772**is_valid_url** 773///////////////// 774 775Checks whether a URL is valid and parseable by checking for the presence of a scheme and 776a netloc. 777 778.. code:: python 779 780 >>> vistir.path.is_valid_url("https://google.com") 781 True 782 >>> vistir.path.is_valid_url("/home/user/somefile") 783 False 784 785 786.. _`mkdir_p`: 787 788**mkdir_p** 789///////////// 790 791Recursively creates the target directory and all of its parents if they do not 792already exist. Fails silently if they do. 793 794.. code:: python 795 796 >>> os.mkdir('/tmp/test_dir') 797 >>> os.listdir('/tmp/test_dir') 798 [] 799 >>> vistir.path.mkdir_p('/tmp/test_dir/child/subchild/subsubchild') 800 >>> os.listdir('/tmp/test_dir/child/subchild') 801 ['subsubchild'] 802 803 804.. _`ensure_mkdir_p`: 805 806**ensure_mkdir_p** 807/////////////////// 808 809A decorator which ensures that the caller function's return value is created as a 810directory on the filesystem. 811 812.. code:: python 813 814 >>> @ensure_mkdir_p 815 def return_fake_value(path): 816 return path 817 >>> return_fake_value('/tmp/test_dir') 818 >>> os.listdir('/tmp/test_dir') 819 [] 820 >>> return_fake_value('/tmp/test_dir/child/subchild/subsubchild') 821 >>> os.listdir('/tmp/test_dir/child/subchild') 822 ['subsubchild'] 823 824 825.. _`create_tracked_tempdir`: 826 827**create_tracked_tempdir** 828//////////////////////////// 829 830Creates a tracked temporary directory using :class:`~vistir.path.TemporaryDirectory`, but does 831not remove the directory when the return value goes out of scope, instead registers a 832handler to cleanup on program exit. 833 834.. code:: python 835 836 >>> temp_dir = vistir.path.create_tracked_tempdir(prefix="test_dir") 837 >>> assert temp_dir.startswith("test_dir") 838 True 839 >>> with vistir.path.create_tracked_tempdir(prefix="test_dir") as temp_dir: 840 with io.open(os.path.join(temp_dir, "test_file.txt"), "w") as fh: 841 fh.write("this is a test") 842 >>> os.listdir(temp_dir) 843 844 845.. _`create_tracked_tempfile`: 846 847**create_tracked_tempfile** 848//////////////////////////// 849 850Creates a tracked temporary file using ``vistir.compat.NamedTemporaryFile``, but creates 851a ``weakref.finalize`` call which will detach on garbage collection to close and delete 852the file. 853 854.. code:: python 855 856 >>> temp_file = vistir.path.create_tracked_tempfile(prefix="requirements", suffix="txt") 857 >>> temp_file.write("some\nstuff") 858 >>> exit() 859 860 861.. _`path_to_url`: 862 863**path_to_url** 864//////////////// 865 866Convert the supplied local path to a file uri. 867 868.. code:: python 869 870 >>> path_to_url("/home/user/code/myrepo/myfile.zip") 871 'file:///home/user/code/myrepo/myfile.zip' 872 873 874.. _`rmtree`: 875 876**rmtree** 877/////////// 878 879Stand-in for :func:`~shutil.rmtree` with additional error-handling. 880 881This version of `rmtree` handles read-only paths, especially in the case of index files 882written by certain source control systems. 883 884.. code:: python 885 886 >>> vistir.path.rmtree('/tmp/test_dir') 887 >>> [d for d in os.listdir('/tmp') if 'test_dir' in d] 888 [] 889 890.. note:: 891 892 Setting `ignore_errors=True` may cause this to silently fail to delete the path 893 894 895.. _`safe_expandvars`: 896 897**safe_expandvars** 898//////////////////// 899 900Call :func:`os.path.expandvars` if value is a string, otherwise do nothing. 901 902.. code:: python 903 904 >>> os.environ['TEST_VAR'] = "MY_TEST_VALUE" 905 >>> vistir.path.safe_expandvars("https://myuser:${TEST_VAR}@myfakewebsite.com") 906 'https://myuser:MY_TEST_VALUE@myfakewebsite.com' 907 908 909.. _`set_write_bit`: 910 911**set_write_bit** 912////////////////// 913 914Set read-write permissions for the current user on the target path. Fail silently 915if the path doesn't exist. 916 917.. code:: python 918 919 >>> vistir.path.set_write_bit('/path/to/some/file') 920 >>> with open('/path/to/some/file', 'w') as fh: 921 fh.write("test text!") 922 923 924.. _`url_to_path`: 925 926**url_to_path** 927//////////////// 928 929Convert a valid file url to a local filesystem path. Follows logic taken from pip. 930 931.. code:: python 932 933 >>> vistir.path.url_to_path("file:///home/user/somefile.zip") 934 '/home/user/somefile.zip' 935