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