Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +7 -0
- .venv/lib/python3.11/site-packages/click/__init__.py +75 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/_compat.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/_termui_impl.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/_textwrap.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/decorators.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/exceptions.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/globals.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/shell_completion.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/testing.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/types.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/__pycache__/utils.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/click/_compat.py +623 -0
- .venv/lib/python3.11/site-packages/click/_textwrap.py +49 -0
- .venv/lib/python3.11/site-packages/click/_winconsole.py +279 -0
- .venv/lib/python3.11/site-packages/click/core.py +0 -0
- .venv/lib/python3.11/site-packages/click/exceptions.py +296 -0
- .venv/lib/python3.11/site-packages/click/formatting.py +301 -0
- .venv/lib/python3.11/site-packages/click/globals.py +67 -0
- .venv/lib/python3.11/site-packages/click/parser.py +531 -0
- .venv/lib/python3.11/site-packages/click/testing.py +483 -0
- .venv/lib/python3.11/site-packages/click/types.py +1093 -0
- .venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/INSTALLER +1 -0
- .venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/METADATA +278 -0
- .venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/RECORD +113 -0
- .venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/WHEEL +4 -0
- .venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/licenses/LICENSE +29 -0
- .venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/INSTALLER +1 -0
- .venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/LICENSE +202 -0
- .venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/METADATA +133 -0
- .venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/RECORD +31 -0
- .venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/WHEEL +5 -0
- .venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/top_level.txt +1 -0
- .venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/INSTALLER +1 -0
- .venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/METADATA +34 -0
- .venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/RECORD +71 -0
- .venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/WHEEL +6 -0
- .venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/top_level.txt +1 -0
- .venv/lib/python3.11/site-packages/opencv_python_headless.libs/libavformat-d296e685.so.59.27.100 +3 -0
- .venv/lib/python3.11/site-packages/opencv_python_headless.libs/libcrypto-8c1ab3ad.so.1.1 +3 -0
- .venv/lib/python3.11/site-packages/opencv_python_headless.libs/libpng16-ef62451c.so.16.44.0 +3 -0
- .venv/lib/python3.11/site-packages/opencv_python_headless.libs/libquadmath-96973f99.so.0.0.0 +3 -0
- .venv/lib/python3.11/site-packages/opencv_python_headless.libs/libswresample-3e7db482.so.4.7.100 +3 -0
- .venv/lib/python3.11/site-packages/opencv_python_headless.libs/libswscale-95ddd674.so.6.7.100 +3 -0
- .venv/lib/python3.11/site-packages/propcache/__init__.py +32 -0
- .venv/lib/python3.11/site-packages/propcache/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/propcache/__pycache__/_helpers.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/propcache/__pycache__/_helpers_py.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/propcache/__pycache__/api.cpython-311.pyc +0 -0
.gitattributes
CHANGED
|
@@ -252,3 +252,10 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/torch/_inductor/_
|
|
| 252 |
.venv/lib/python3.11/site-packages/torchvision.libs/libpng16.7f72a3c5.so.16 filter=lfs diff=lfs merge=lfs -text
|
| 253 |
.venv/lib/python3.11/site-packages/msgspec/_core.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 254 |
.venv/lib/python3.11/site-packages/vllm/_moe_C.abi3.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
.venv/lib/python3.11/site-packages/torchvision.libs/libpng16.7f72a3c5.so.16 filter=lfs diff=lfs merge=lfs -text
|
| 253 |
.venv/lib/python3.11/site-packages/msgspec/_core.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 254 |
.venv/lib/python3.11/site-packages/vllm/_moe_C.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 255 |
+
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libswscale-95ddd674.so.6.7.100 filter=lfs diff=lfs merge=lfs -text
|
| 256 |
+
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libavformat-d296e685.so.59.27.100 filter=lfs diff=lfs merge=lfs -text
|
| 257 |
+
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libpng16-ef62451c.so.16.44.0 filter=lfs diff=lfs merge=lfs -text
|
| 258 |
+
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libcrypto-8c1ab3ad.so.1.1 filter=lfs diff=lfs merge=lfs -text
|
| 259 |
+
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libswresample-3e7db482.so.4.7.100 filter=lfs diff=lfs merge=lfs -text
|
| 260 |
+
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libquadmath-96973f99.so.0.0.0 filter=lfs diff=lfs merge=lfs -text
|
| 261 |
+
.venv/lib/python3.11/site-packages/propcache/_helpers_c.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
.venv/lib/python3.11/site-packages/click/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Click is a simple Python module inspired by the stdlib optparse to make
|
| 3 |
+
writing command line scripts fun. Unlike other modules, it's based
|
| 4 |
+
around a simple API that does not come with too much magic and is
|
| 5 |
+
composable.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from .core import Argument as Argument
|
| 9 |
+
from .core import BaseCommand as BaseCommand
|
| 10 |
+
from .core import Command as Command
|
| 11 |
+
from .core import CommandCollection as CommandCollection
|
| 12 |
+
from .core import Context as Context
|
| 13 |
+
from .core import Group as Group
|
| 14 |
+
from .core import MultiCommand as MultiCommand
|
| 15 |
+
from .core import Option as Option
|
| 16 |
+
from .core import Parameter as Parameter
|
| 17 |
+
from .decorators import argument as argument
|
| 18 |
+
from .decorators import command as command
|
| 19 |
+
from .decorators import confirmation_option as confirmation_option
|
| 20 |
+
from .decorators import group as group
|
| 21 |
+
from .decorators import help_option as help_option
|
| 22 |
+
from .decorators import HelpOption as HelpOption
|
| 23 |
+
from .decorators import make_pass_decorator as make_pass_decorator
|
| 24 |
+
from .decorators import option as option
|
| 25 |
+
from .decorators import pass_context as pass_context
|
| 26 |
+
from .decorators import pass_obj as pass_obj
|
| 27 |
+
from .decorators import password_option as password_option
|
| 28 |
+
from .decorators import version_option as version_option
|
| 29 |
+
from .exceptions import Abort as Abort
|
| 30 |
+
from .exceptions import BadArgumentUsage as BadArgumentUsage
|
| 31 |
+
from .exceptions import BadOptionUsage as BadOptionUsage
|
| 32 |
+
from .exceptions import BadParameter as BadParameter
|
| 33 |
+
from .exceptions import ClickException as ClickException
|
| 34 |
+
from .exceptions import FileError as FileError
|
| 35 |
+
from .exceptions import MissingParameter as MissingParameter
|
| 36 |
+
from .exceptions import NoSuchOption as NoSuchOption
|
| 37 |
+
from .exceptions import UsageError as UsageError
|
| 38 |
+
from .formatting import HelpFormatter as HelpFormatter
|
| 39 |
+
from .formatting import wrap_text as wrap_text
|
| 40 |
+
from .globals import get_current_context as get_current_context
|
| 41 |
+
from .parser import OptionParser as OptionParser
|
| 42 |
+
from .termui import clear as clear
|
| 43 |
+
from .termui import confirm as confirm
|
| 44 |
+
from .termui import echo_via_pager as echo_via_pager
|
| 45 |
+
from .termui import edit as edit
|
| 46 |
+
from .termui import getchar as getchar
|
| 47 |
+
from .termui import launch as launch
|
| 48 |
+
from .termui import pause as pause
|
| 49 |
+
from .termui import progressbar as progressbar
|
| 50 |
+
from .termui import prompt as prompt
|
| 51 |
+
from .termui import secho as secho
|
| 52 |
+
from .termui import style as style
|
| 53 |
+
from .termui import unstyle as unstyle
|
| 54 |
+
from .types import BOOL as BOOL
|
| 55 |
+
from .types import Choice as Choice
|
| 56 |
+
from .types import DateTime as DateTime
|
| 57 |
+
from .types import File as File
|
| 58 |
+
from .types import FLOAT as FLOAT
|
| 59 |
+
from .types import FloatRange as FloatRange
|
| 60 |
+
from .types import INT as INT
|
| 61 |
+
from .types import IntRange as IntRange
|
| 62 |
+
from .types import ParamType as ParamType
|
| 63 |
+
from .types import Path as Path
|
| 64 |
+
from .types import STRING as STRING
|
| 65 |
+
from .types import Tuple as Tuple
|
| 66 |
+
from .types import UNPROCESSED as UNPROCESSED
|
| 67 |
+
from .types import UUID as UUID
|
| 68 |
+
from .utils import echo as echo
|
| 69 |
+
from .utils import format_filename as format_filename
|
| 70 |
+
from .utils import get_app_dir as get_app_dir
|
| 71 |
+
from .utils import get_binary_stream as get_binary_stream
|
| 72 |
+
from .utils import get_text_stream as get_text_stream
|
| 73 |
+
from .utils import open_file as open_file
|
| 74 |
+
|
| 75 |
+
__version__ = "8.1.8"
|
.venv/lib/python3.11/site-packages/click/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (3.72 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/_compat.cpython-311.pyc
ADDED
|
Binary file (28.7 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/_termui_impl.cpython-311.pyc
ADDED
|
Binary file (33.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/_textwrap.cpython-311.pyc
ADDED
|
Binary file (2.63 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/decorators.cpython-311.pyc
ADDED
|
Binary file (26.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/exceptions.cpython-311.pyc
ADDED
|
Binary file (16.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/globals.cpython-311.pyc
ADDED
|
Binary file (3.34 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/shell_completion.cpython-311.pyc
ADDED
|
Binary file (24.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/testing.cpython-311.pyc
ADDED
|
Binary file (26 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/types.cpython-311.pyc
ADDED
|
Binary file (53.7 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (28 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/click/_compat.py
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import codecs
|
| 2 |
+
import io
|
| 3 |
+
import os
|
| 4 |
+
import re
|
| 5 |
+
import sys
|
| 6 |
+
import typing as t
|
| 7 |
+
from weakref import WeakKeyDictionary
|
| 8 |
+
|
| 9 |
+
CYGWIN = sys.platform.startswith("cygwin")
|
| 10 |
+
WIN = sys.platform.startswith("win")
|
| 11 |
+
auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None
|
| 12 |
+
_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _make_text_stream(
|
| 16 |
+
stream: t.BinaryIO,
|
| 17 |
+
encoding: t.Optional[str],
|
| 18 |
+
errors: t.Optional[str],
|
| 19 |
+
force_readable: bool = False,
|
| 20 |
+
force_writable: bool = False,
|
| 21 |
+
) -> t.TextIO:
|
| 22 |
+
if encoding is None:
|
| 23 |
+
encoding = get_best_encoding(stream)
|
| 24 |
+
if errors is None:
|
| 25 |
+
errors = "replace"
|
| 26 |
+
return _NonClosingTextIOWrapper(
|
| 27 |
+
stream,
|
| 28 |
+
encoding,
|
| 29 |
+
errors,
|
| 30 |
+
line_buffering=True,
|
| 31 |
+
force_readable=force_readable,
|
| 32 |
+
force_writable=force_writable,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def is_ascii_encoding(encoding: str) -> bool:
|
| 37 |
+
"""Checks if a given encoding is ascii."""
|
| 38 |
+
try:
|
| 39 |
+
return codecs.lookup(encoding).name == "ascii"
|
| 40 |
+
except LookupError:
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def get_best_encoding(stream: t.IO[t.Any]) -> str:
|
| 45 |
+
"""Returns the default stream encoding if not found."""
|
| 46 |
+
rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
|
| 47 |
+
if is_ascii_encoding(rv):
|
| 48 |
+
return "utf-8"
|
| 49 |
+
return rv
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
| 53 |
+
def __init__(
|
| 54 |
+
self,
|
| 55 |
+
stream: t.BinaryIO,
|
| 56 |
+
encoding: t.Optional[str],
|
| 57 |
+
errors: t.Optional[str],
|
| 58 |
+
force_readable: bool = False,
|
| 59 |
+
force_writable: bool = False,
|
| 60 |
+
**extra: t.Any,
|
| 61 |
+
) -> None:
|
| 62 |
+
self._stream = stream = t.cast(
|
| 63 |
+
t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
|
| 64 |
+
)
|
| 65 |
+
super().__init__(stream, encoding, errors, **extra)
|
| 66 |
+
|
| 67 |
+
def __del__(self) -> None:
|
| 68 |
+
try:
|
| 69 |
+
self.detach()
|
| 70 |
+
except Exception:
|
| 71 |
+
pass
|
| 72 |
+
|
| 73 |
+
def isatty(self) -> bool:
|
| 74 |
+
# https://bitbucket.org/pypy/pypy/issue/1803
|
| 75 |
+
return self._stream.isatty()
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class _FixupStream:
|
| 79 |
+
"""The new io interface needs more from streams than streams
|
| 80 |
+
traditionally implement. As such, this fix-up code is necessary in
|
| 81 |
+
some circumstances.
|
| 82 |
+
|
| 83 |
+
The forcing of readable and writable flags are there because some tools
|
| 84 |
+
put badly patched objects on sys (one such offender are certain version
|
| 85 |
+
of jupyter notebook).
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
def __init__(
|
| 89 |
+
self,
|
| 90 |
+
stream: t.BinaryIO,
|
| 91 |
+
force_readable: bool = False,
|
| 92 |
+
force_writable: bool = False,
|
| 93 |
+
):
|
| 94 |
+
self._stream = stream
|
| 95 |
+
self._force_readable = force_readable
|
| 96 |
+
self._force_writable = force_writable
|
| 97 |
+
|
| 98 |
+
def __getattr__(self, name: str) -> t.Any:
|
| 99 |
+
return getattr(self._stream, name)
|
| 100 |
+
|
| 101 |
+
def read1(self, size: int) -> bytes:
|
| 102 |
+
f = getattr(self._stream, "read1", None)
|
| 103 |
+
|
| 104 |
+
if f is not None:
|
| 105 |
+
return t.cast(bytes, f(size))
|
| 106 |
+
|
| 107 |
+
return self._stream.read(size)
|
| 108 |
+
|
| 109 |
+
def readable(self) -> bool:
|
| 110 |
+
if self._force_readable:
|
| 111 |
+
return True
|
| 112 |
+
x = getattr(self._stream, "readable", None)
|
| 113 |
+
if x is not None:
|
| 114 |
+
return t.cast(bool, x())
|
| 115 |
+
try:
|
| 116 |
+
self._stream.read(0)
|
| 117 |
+
except Exception:
|
| 118 |
+
return False
|
| 119 |
+
return True
|
| 120 |
+
|
| 121 |
+
def writable(self) -> bool:
|
| 122 |
+
if self._force_writable:
|
| 123 |
+
return True
|
| 124 |
+
x = getattr(self._stream, "writable", None)
|
| 125 |
+
if x is not None:
|
| 126 |
+
return t.cast(bool, x())
|
| 127 |
+
try:
|
| 128 |
+
self._stream.write("") # type: ignore
|
| 129 |
+
except Exception:
|
| 130 |
+
try:
|
| 131 |
+
self._stream.write(b"")
|
| 132 |
+
except Exception:
|
| 133 |
+
return False
|
| 134 |
+
return True
|
| 135 |
+
|
| 136 |
+
def seekable(self) -> bool:
|
| 137 |
+
x = getattr(self._stream, "seekable", None)
|
| 138 |
+
if x is not None:
|
| 139 |
+
return t.cast(bool, x())
|
| 140 |
+
try:
|
| 141 |
+
self._stream.seek(self._stream.tell())
|
| 142 |
+
except Exception:
|
| 143 |
+
return False
|
| 144 |
+
return True
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool:
|
| 148 |
+
try:
|
| 149 |
+
return isinstance(stream.read(0), bytes)
|
| 150 |
+
except Exception:
|
| 151 |
+
return default
|
| 152 |
+
# This happens in some cases where the stream was already
|
| 153 |
+
# closed. In this case, we assume the default.
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool:
|
| 157 |
+
try:
|
| 158 |
+
stream.write(b"")
|
| 159 |
+
except Exception:
|
| 160 |
+
try:
|
| 161 |
+
stream.write("")
|
| 162 |
+
return False
|
| 163 |
+
except Exception:
|
| 164 |
+
pass
|
| 165 |
+
return default
|
| 166 |
+
return True
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def _find_binary_reader(stream: t.IO[t.Any]) -> t.Optional[t.BinaryIO]:
|
| 170 |
+
# We need to figure out if the given stream is already binary.
|
| 171 |
+
# This can happen because the official docs recommend detaching
|
| 172 |
+
# the streams to get binary streams. Some code might do this, so
|
| 173 |
+
# we need to deal with this case explicitly.
|
| 174 |
+
if _is_binary_reader(stream, False):
|
| 175 |
+
return t.cast(t.BinaryIO, stream)
|
| 176 |
+
|
| 177 |
+
buf = getattr(stream, "buffer", None)
|
| 178 |
+
|
| 179 |
+
# Same situation here; this time we assume that the buffer is
|
| 180 |
+
# actually binary in case it's closed.
|
| 181 |
+
if buf is not None and _is_binary_reader(buf, True):
|
| 182 |
+
return t.cast(t.BinaryIO, buf)
|
| 183 |
+
|
| 184 |
+
return None
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _find_binary_writer(stream: t.IO[t.Any]) -> t.Optional[t.BinaryIO]:
|
| 188 |
+
# We need to figure out if the given stream is already binary.
|
| 189 |
+
# This can happen because the official docs recommend detaching
|
| 190 |
+
# the streams to get binary streams. Some code might do this, so
|
| 191 |
+
# we need to deal with this case explicitly.
|
| 192 |
+
if _is_binary_writer(stream, False):
|
| 193 |
+
return t.cast(t.BinaryIO, stream)
|
| 194 |
+
|
| 195 |
+
buf = getattr(stream, "buffer", None)
|
| 196 |
+
|
| 197 |
+
# Same situation here; this time we assume that the buffer is
|
| 198 |
+
# actually binary in case it's closed.
|
| 199 |
+
if buf is not None and _is_binary_writer(buf, True):
|
| 200 |
+
return t.cast(t.BinaryIO, buf)
|
| 201 |
+
|
| 202 |
+
return None
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def _stream_is_misconfigured(stream: t.TextIO) -> bool:
|
| 206 |
+
"""A stream is misconfigured if its encoding is ASCII."""
|
| 207 |
+
# If the stream does not have an encoding set, we assume it's set
|
| 208 |
+
# to ASCII. This appears to happen in certain unittest
|
| 209 |
+
# environments. It's not quite clear what the correct behavior is
|
| 210 |
+
# but this at least will force Click to recover somehow.
|
| 211 |
+
return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool:
|
| 215 |
+
"""A stream attribute is compatible if it is equal to the
|
| 216 |
+
desired value or the desired value is unset and the attribute
|
| 217 |
+
has a value.
|
| 218 |
+
"""
|
| 219 |
+
stream_value = getattr(stream, attr, None)
|
| 220 |
+
return stream_value == value or (value is None and stream_value is not None)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def _is_compatible_text_stream(
|
| 224 |
+
stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
| 225 |
+
) -> bool:
|
| 226 |
+
"""Check if a stream's encoding and errors attributes are
|
| 227 |
+
compatible with the desired values.
|
| 228 |
+
"""
|
| 229 |
+
return _is_compat_stream_attr(
|
| 230 |
+
stream, "encoding", encoding
|
| 231 |
+
) and _is_compat_stream_attr(stream, "errors", errors)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def _force_correct_text_stream(
|
| 235 |
+
text_stream: t.IO[t.Any],
|
| 236 |
+
encoding: t.Optional[str],
|
| 237 |
+
errors: t.Optional[str],
|
| 238 |
+
is_binary: t.Callable[[t.IO[t.Any], bool], bool],
|
| 239 |
+
find_binary: t.Callable[[t.IO[t.Any]], t.Optional[t.BinaryIO]],
|
| 240 |
+
force_readable: bool = False,
|
| 241 |
+
force_writable: bool = False,
|
| 242 |
+
) -> t.TextIO:
|
| 243 |
+
if is_binary(text_stream, False):
|
| 244 |
+
binary_reader = t.cast(t.BinaryIO, text_stream)
|
| 245 |
+
else:
|
| 246 |
+
text_stream = t.cast(t.TextIO, text_stream)
|
| 247 |
+
# If the stream looks compatible, and won't default to a
|
| 248 |
+
# misconfigured ascii encoding, return it as-is.
|
| 249 |
+
if _is_compatible_text_stream(text_stream, encoding, errors) and not (
|
| 250 |
+
encoding is None and _stream_is_misconfigured(text_stream)
|
| 251 |
+
):
|
| 252 |
+
return text_stream
|
| 253 |
+
|
| 254 |
+
# Otherwise, get the underlying binary reader.
|
| 255 |
+
possible_binary_reader = find_binary(text_stream)
|
| 256 |
+
|
| 257 |
+
# If that's not possible, silently use the original reader
|
| 258 |
+
# and get mojibake instead of exceptions.
|
| 259 |
+
if possible_binary_reader is None:
|
| 260 |
+
return text_stream
|
| 261 |
+
|
| 262 |
+
binary_reader = possible_binary_reader
|
| 263 |
+
|
| 264 |
+
# Default errors to replace instead of strict in order to get
|
| 265 |
+
# something that works.
|
| 266 |
+
if errors is None:
|
| 267 |
+
errors = "replace"
|
| 268 |
+
|
| 269 |
+
# Wrap the binary stream in a text stream with the correct
|
| 270 |
+
# encoding parameters.
|
| 271 |
+
return _make_text_stream(
|
| 272 |
+
binary_reader,
|
| 273 |
+
encoding,
|
| 274 |
+
errors,
|
| 275 |
+
force_readable=force_readable,
|
| 276 |
+
force_writable=force_writable,
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def _force_correct_text_reader(
|
| 281 |
+
text_reader: t.IO[t.Any],
|
| 282 |
+
encoding: t.Optional[str],
|
| 283 |
+
errors: t.Optional[str],
|
| 284 |
+
force_readable: bool = False,
|
| 285 |
+
) -> t.TextIO:
|
| 286 |
+
return _force_correct_text_stream(
|
| 287 |
+
text_reader,
|
| 288 |
+
encoding,
|
| 289 |
+
errors,
|
| 290 |
+
_is_binary_reader,
|
| 291 |
+
_find_binary_reader,
|
| 292 |
+
force_readable=force_readable,
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def _force_correct_text_writer(
|
| 297 |
+
text_writer: t.IO[t.Any],
|
| 298 |
+
encoding: t.Optional[str],
|
| 299 |
+
errors: t.Optional[str],
|
| 300 |
+
force_writable: bool = False,
|
| 301 |
+
) -> t.TextIO:
|
| 302 |
+
return _force_correct_text_stream(
|
| 303 |
+
text_writer,
|
| 304 |
+
encoding,
|
| 305 |
+
errors,
|
| 306 |
+
_is_binary_writer,
|
| 307 |
+
_find_binary_writer,
|
| 308 |
+
force_writable=force_writable,
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def get_binary_stdin() -> t.BinaryIO:
|
| 313 |
+
reader = _find_binary_reader(sys.stdin)
|
| 314 |
+
if reader is None:
|
| 315 |
+
raise RuntimeError("Was not able to determine binary stream for sys.stdin.")
|
| 316 |
+
return reader
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def get_binary_stdout() -> t.BinaryIO:
|
| 320 |
+
writer = _find_binary_writer(sys.stdout)
|
| 321 |
+
if writer is None:
|
| 322 |
+
raise RuntimeError("Was not able to determine binary stream for sys.stdout.")
|
| 323 |
+
return writer
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def get_binary_stderr() -> t.BinaryIO:
|
| 327 |
+
writer = _find_binary_writer(sys.stderr)
|
| 328 |
+
if writer is None:
|
| 329 |
+
raise RuntimeError("Was not able to determine binary stream for sys.stderr.")
|
| 330 |
+
return writer
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def get_text_stdin(
|
| 334 |
+
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
| 335 |
+
) -> t.TextIO:
|
| 336 |
+
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
| 337 |
+
if rv is not None:
|
| 338 |
+
return rv
|
| 339 |
+
return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def get_text_stdout(
|
| 343 |
+
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
| 344 |
+
) -> t.TextIO:
|
| 345 |
+
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
| 346 |
+
if rv is not None:
|
| 347 |
+
return rv
|
| 348 |
+
return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True)
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
def get_text_stderr(
|
| 352 |
+
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
| 353 |
+
) -> t.TextIO:
|
| 354 |
+
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
| 355 |
+
if rv is not None:
|
| 356 |
+
return rv
|
| 357 |
+
return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True)
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
def _wrap_io_open(
|
| 361 |
+
file: t.Union[str, "os.PathLike[str]", int],
|
| 362 |
+
mode: str,
|
| 363 |
+
encoding: t.Optional[str],
|
| 364 |
+
errors: t.Optional[str],
|
| 365 |
+
) -> t.IO[t.Any]:
|
| 366 |
+
"""Handles not passing ``encoding`` and ``errors`` in binary mode."""
|
| 367 |
+
if "b" in mode:
|
| 368 |
+
return open(file, mode)
|
| 369 |
+
|
| 370 |
+
return open(file, mode, encoding=encoding, errors=errors)
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
def open_stream(
|
| 374 |
+
filename: "t.Union[str, os.PathLike[str]]",
|
| 375 |
+
mode: str = "r",
|
| 376 |
+
encoding: t.Optional[str] = None,
|
| 377 |
+
errors: t.Optional[str] = "strict",
|
| 378 |
+
atomic: bool = False,
|
| 379 |
+
) -> t.Tuple[t.IO[t.Any], bool]:
|
| 380 |
+
binary = "b" in mode
|
| 381 |
+
filename = os.fspath(filename)
|
| 382 |
+
|
| 383 |
+
# Standard streams first. These are simple because they ignore the
|
| 384 |
+
# atomic flag. Use fsdecode to handle Path("-").
|
| 385 |
+
if os.fsdecode(filename) == "-":
|
| 386 |
+
if any(m in mode for m in ["w", "a", "x"]):
|
| 387 |
+
if binary:
|
| 388 |
+
return get_binary_stdout(), False
|
| 389 |
+
return get_text_stdout(encoding=encoding, errors=errors), False
|
| 390 |
+
if binary:
|
| 391 |
+
return get_binary_stdin(), False
|
| 392 |
+
return get_text_stdin(encoding=encoding, errors=errors), False
|
| 393 |
+
|
| 394 |
+
# Non-atomic writes directly go out through the regular open functions.
|
| 395 |
+
if not atomic:
|
| 396 |
+
return _wrap_io_open(filename, mode, encoding, errors), True
|
| 397 |
+
|
| 398 |
+
# Some usability stuff for atomic writes
|
| 399 |
+
if "a" in mode:
|
| 400 |
+
raise ValueError(
|
| 401 |
+
"Appending to an existing file is not supported, because that"
|
| 402 |
+
" would involve an expensive `copy`-operation to a temporary"
|
| 403 |
+
" file. Open the file in normal `w`-mode and copy explicitly"
|
| 404 |
+
" if that's what you're after."
|
| 405 |
+
)
|
| 406 |
+
if "x" in mode:
|
| 407 |
+
raise ValueError("Use the `overwrite`-parameter instead.")
|
| 408 |
+
if "w" not in mode:
|
| 409 |
+
raise ValueError("Atomic writes only make sense with `w`-mode.")
|
| 410 |
+
|
| 411 |
+
# Atomic writes are more complicated. They work by opening a file
|
| 412 |
+
# as a proxy in the same folder and then using the fdopen
|
| 413 |
+
# functionality to wrap it in a Python file. Then we wrap it in an
|
| 414 |
+
# atomic file that moves the file over on close.
|
| 415 |
+
import errno
|
| 416 |
+
import random
|
| 417 |
+
|
| 418 |
+
try:
|
| 419 |
+
perm: t.Optional[int] = os.stat(filename).st_mode
|
| 420 |
+
except OSError:
|
| 421 |
+
perm = None
|
| 422 |
+
|
| 423 |
+
flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
| 424 |
+
|
| 425 |
+
if binary:
|
| 426 |
+
flags |= getattr(os, "O_BINARY", 0)
|
| 427 |
+
|
| 428 |
+
while True:
|
| 429 |
+
tmp_filename = os.path.join(
|
| 430 |
+
os.path.dirname(filename),
|
| 431 |
+
f".__atomic-write{random.randrange(1 << 32):08x}",
|
| 432 |
+
)
|
| 433 |
+
try:
|
| 434 |
+
fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
|
| 435 |
+
break
|
| 436 |
+
except OSError as e:
|
| 437 |
+
if e.errno == errno.EEXIST or (
|
| 438 |
+
os.name == "nt"
|
| 439 |
+
and e.errno == errno.EACCES
|
| 440 |
+
and os.path.isdir(e.filename)
|
| 441 |
+
and os.access(e.filename, os.W_OK)
|
| 442 |
+
):
|
| 443 |
+
continue
|
| 444 |
+
raise
|
| 445 |
+
|
| 446 |
+
if perm is not None:
|
| 447 |
+
os.chmod(tmp_filename, perm) # in case perm includes bits in umask
|
| 448 |
+
|
| 449 |
+
f = _wrap_io_open(fd, mode, encoding, errors)
|
| 450 |
+
af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
|
| 451 |
+
return t.cast(t.IO[t.Any], af), True
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
class _AtomicFile:
|
| 455 |
+
def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None:
|
| 456 |
+
self._f = f
|
| 457 |
+
self._tmp_filename = tmp_filename
|
| 458 |
+
self._real_filename = real_filename
|
| 459 |
+
self.closed = False
|
| 460 |
+
|
| 461 |
+
@property
|
| 462 |
+
def name(self) -> str:
|
| 463 |
+
return self._real_filename
|
| 464 |
+
|
| 465 |
+
def close(self, delete: bool = False) -> None:
|
| 466 |
+
if self.closed:
|
| 467 |
+
return
|
| 468 |
+
self._f.close()
|
| 469 |
+
os.replace(self._tmp_filename, self._real_filename)
|
| 470 |
+
self.closed = True
|
| 471 |
+
|
| 472 |
+
def __getattr__(self, name: str) -> t.Any:
|
| 473 |
+
return getattr(self._f, name)
|
| 474 |
+
|
| 475 |
+
def __enter__(self) -> "_AtomicFile":
|
| 476 |
+
return self
|
| 477 |
+
|
| 478 |
+
def __exit__(self, exc_type: t.Optional[t.Type[BaseException]], *_: t.Any) -> None:
|
| 479 |
+
self.close(delete=exc_type is not None)
|
| 480 |
+
|
| 481 |
+
def __repr__(self) -> str:
|
| 482 |
+
return repr(self._f)
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
def strip_ansi(value: str) -> str:
|
| 486 |
+
return _ansi_re.sub("", value)
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool:
|
| 490 |
+
while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
|
| 491 |
+
stream = stream._stream
|
| 492 |
+
|
| 493 |
+
return stream.__class__.__module__.startswith("ipykernel.")
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
def should_strip_ansi(
|
| 497 |
+
stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None
|
| 498 |
+
) -> bool:
|
| 499 |
+
if color is None:
|
| 500 |
+
if stream is None:
|
| 501 |
+
stream = sys.stdin
|
| 502 |
+
return not isatty(stream) and not _is_jupyter_kernel_output(stream)
|
| 503 |
+
return not color
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
# On Windows, wrap the output streams with colorama to support ANSI
|
| 507 |
+
# color codes.
|
| 508 |
+
# NOTE: double check is needed so mypy does not analyze this on Linux
|
| 509 |
+
if sys.platform.startswith("win") and WIN:
|
| 510 |
+
from ._winconsole import _get_windows_console_stream
|
| 511 |
+
|
| 512 |
+
def _get_argv_encoding() -> str:
|
| 513 |
+
import locale
|
| 514 |
+
|
| 515 |
+
return locale.getpreferredencoding()
|
| 516 |
+
|
| 517 |
+
_ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
| 518 |
+
|
| 519 |
+
def auto_wrap_for_ansi(
|
| 520 |
+
stream: t.TextIO, color: t.Optional[bool] = None
|
| 521 |
+
) -> t.TextIO:
|
| 522 |
+
"""Support ANSI color and style codes on Windows by wrapping a
|
| 523 |
+
stream with colorama.
|
| 524 |
+
"""
|
| 525 |
+
try:
|
| 526 |
+
cached = _ansi_stream_wrappers.get(stream)
|
| 527 |
+
except Exception:
|
| 528 |
+
cached = None
|
| 529 |
+
|
| 530 |
+
if cached is not None:
|
| 531 |
+
return cached
|
| 532 |
+
|
| 533 |
+
import colorama
|
| 534 |
+
|
| 535 |
+
strip = should_strip_ansi(stream, color)
|
| 536 |
+
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
| 537 |
+
rv = t.cast(t.TextIO, ansi_wrapper.stream)
|
| 538 |
+
_write = rv.write
|
| 539 |
+
|
| 540 |
+
def _safe_write(s):
|
| 541 |
+
try:
|
| 542 |
+
return _write(s)
|
| 543 |
+
except BaseException:
|
| 544 |
+
ansi_wrapper.reset_all()
|
| 545 |
+
raise
|
| 546 |
+
|
| 547 |
+
rv.write = _safe_write
|
| 548 |
+
|
| 549 |
+
try:
|
| 550 |
+
_ansi_stream_wrappers[stream] = rv
|
| 551 |
+
except Exception:
|
| 552 |
+
pass
|
| 553 |
+
|
| 554 |
+
return rv
|
| 555 |
+
|
| 556 |
+
else:
|
| 557 |
+
|
| 558 |
+
def _get_argv_encoding() -> str:
|
| 559 |
+
return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding()
|
| 560 |
+
|
| 561 |
+
def _get_windows_console_stream(
|
| 562 |
+
f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
| 563 |
+
) -> t.Optional[t.TextIO]:
|
| 564 |
+
return None
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
def term_len(x: str) -> int:
|
| 568 |
+
return len(strip_ansi(x))
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
def isatty(stream: t.IO[t.Any]) -> bool:
|
| 572 |
+
try:
|
| 573 |
+
return stream.isatty()
|
| 574 |
+
except Exception:
|
| 575 |
+
return False
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
def _make_cached_stream_func(
|
| 579 |
+
src_func: t.Callable[[], t.Optional[t.TextIO]],
|
| 580 |
+
wrapper_func: t.Callable[[], t.TextIO],
|
| 581 |
+
) -> t.Callable[[], t.Optional[t.TextIO]]:
|
| 582 |
+
cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
| 583 |
+
|
| 584 |
+
def func() -> t.Optional[t.TextIO]:
|
| 585 |
+
stream = src_func()
|
| 586 |
+
|
| 587 |
+
if stream is None:
|
| 588 |
+
return None
|
| 589 |
+
|
| 590 |
+
try:
|
| 591 |
+
rv = cache.get(stream)
|
| 592 |
+
except Exception:
|
| 593 |
+
rv = None
|
| 594 |
+
if rv is not None:
|
| 595 |
+
return rv
|
| 596 |
+
rv = wrapper_func()
|
| 597 |
+
try:
|
| 598 |
+
cache[stream] = rv
|
| 599 |
+
except Exception:
|
| 600 |
+
pass
|
| 601 |
+
return rv
|
| 602 |
+
|
| 603 |
+
return func
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
|
| 607 |
+
_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
|
| 608 |
+
_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = {
|
| 612 |
+
"stdin": get_binary_stdin,
|
| 613 |
+
"stdout": get_binary_stdout,
|
| 614 |
+
"stderr": get_binary_stderr,
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
text_streams: t.Mapping[
|
| 618 |
+
str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO]
|
| 619 |
+
] = {
|
| 620 |
+
"stdin": get_text_stdin,
|
| 621 |
+
"stdout": get_text_stdout,
|
| 622 |
+
"stderr": get_text_stderr,
|
| 623 |
+
}
|
.venv/lib/python3.11/site-packages/click/_textwrap.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import textwrap
|
| 2 |
+
import typing as t
|
| 3 |
+
from contextlib import contextmanager
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class TextWrapper(textwrap.TextWrapper):
|
| 7 |
+
def _handle_long_word(
|
| 8 |
+
self,
|
| 9 |
+
reversed_chunks: t.List[str],
|
| 10 |
+
cur_line: t.List[str],
|
| 11 |
+
cur_len: int,
|
| 12 |
+
width: int,
|
| 13 |
+
) -> None:
|
| 14 |
+
space_left = max(width - cur_len, 1)
|
| 15 |
+
|
| 16 |
+
if self.break_long_words:
|
| 17 |
+
last = reversed_chunks[-1]
|
| 18 |
+
cut = last[:space_left]
|
| 19 |
+
res = last[space_left:]
|
| 20 |
+
cur_line.append(cut)
|
| 21 |
+
reversed_chunks[-1] = res
|
| 22 |
+
elif not cur_line:
|
| 23 |
+
cur_line.append(reversed_chunks.pop())
|
| 24 |
+
|
| 25 |
+
@contextmanager
|
| 26 |
+
def extra_indent(self, indent: str) -> t.Iterator[None]:
|
| 27 |
+
old_initial_indent = self.initial_indent
|
| 28 |
+
old_subsequent_indent = self.subsequent_indent
|
| 29 |
+
self.initial_indent += indent
|
| 30 |
+
self.subsequent_indent += indent
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
yield
|
| 34 |
+
finally:
|
| 35 |
+
self.initial_indent = old_initial_indent
|
| 36 |
+
self.subsequent_indent = old_subsequent_indent
|
| 37 |
+
|
| 38 |
+
def indent_only(self, text: str) -> str:
|
| 39 |
+
rv = []
|
| 40 |
+
|
| 41 |
+
for idx, line in enumerate(text.splitlines()):
|
| 42 |
+
indent = self.initial_indent
|
| 43 |
+
|
| 44 |
+
if idx > 0:
|
| 45 |
+
indent = self.subsequent_indent
|
| 46 |
+
|
| 47 |
+
rv.append(f"{indent}{line}")
|
| 48 |
+
|
| 49 |
+
return "\n".join(rv)
|
.venv/lib/python3.11/site-packages/click/_winconsole.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This module is based on the excellent work by Adam Bartoš who
|
| 2 |
+
# provided a lot of what went into the implementation here in
|
| 3 |
+
# the discussion to issue1602 in the Python bug tracker.
|
| 4 |
+
#
|
| 5 |
+
# There are some general differences in regards to how this works
|
| 6 |
+
# compared to the original patches as we do not need to patch
|
| 7 |
+
# the entire interpreter but just work in our little world of
|
| 8 |
+
# echo and prompt.
|
| 9 |
+
import io
|
| 10 |
+
import sys
|
| 11 |
+
import time
|
| 12 |
+
import typing as t
|
| 13 |
+
from ctypes import byref
|
| 14 |
+
from ctypes import c_char
|
| 15 |
+
from ctypes import c_char_p
|
| 16 |
+
from ctypes import c_int
|
| 17 |
+
from ctypes import c_ssize_t
|
| 18 |
+
from ctypes import c_ulong
|
| 19 |
+
from ctypes import c_void_p
|
| 20 |
+
from ctypes import POINTER
|
| 21 |
+
from ctypes import py_object
|
| 22 |
+
from ctypes import Structure
|
| 23 |
+
from ctypes.wintypes import DWORD
|
| 24 |
+
from ctypes.wintypes import HANDLE
|
| 25 |
+
from ctypes.wintypes import LPCWSTR
|
| 26 |
+
from ctypes.wintypes import LPWSTR
|
| 27 |
+
|
| 28 |
+
from ._compat import _NonClosingTextIOWrapper
|
| 29 |
+
|
| 30 |
+
assert sys.platform == "win32"
|
| 31 |
+
import msvcrt # noqa: E402
|
| 32 |
+
from ctypes import windll # noqa: E402
|
| 33 |
+
from ctypes import WINFUNCTYPE # noqa: E402
|
| 34 |
+
|
| 35 |
+
c_ssize_p = POINTER(c_ssize_t)
|
| 36 |
+
|
| 37 |
+
kernel32 = windll.kernel32
|
| 38 |
+
GetStdHandle = kernel32.GetStdHandle
|
| 39 |
+
ReadConsoleW = kernel32.ReadConsoleW
|
| 40 |
+
WriteConsoleW = kernel32.WriteConsoleW
|
| 41 |
+
GetConsoleMode = kernel32.GetConsoleMode
|
| 42 |
+
GetLastError = kernel32.GetLastError
|
| 43 |
+
GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
|
| 44 |
+
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
| 45 |
+
("CommandLineToArgvW", windll.shell32)
|
| 46 |
+
)
|
| 47 |
+
LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32))
|
| 48 |
+
|
| 49 |
+
STDIN_HANDLE = GetStdHandle(-10)
|
| 50 |
+
STDOUT_HANDLE = GetStdHandle(-11)
|
| 51 |
+
STDERR_HANDLE = GetStdHandle(-12)
|
| 52 |
+
|
| 53 |
+
PyBUF_SIMPLE = 0
|
| 54 |
+
PyBUF_WRITABLE = 1
|
| 55 |
+
|
| 56 |
+
ERROR_SUCCESS = 0
|
| 57 |
+
ERROR_NOT_ENOUGH_MEMORY = 8
|
| 58 |
+
ERROR_OPERATION_ABORTED = 995
|
| 59 |
+
|
| 60 |
+
STDIN_FILENO = 0
|
| 61 |
+
STDOUT_FILENO = 1
|
| 62 |
+
STDERR_FILENO = 2
|
| 63 |
+
|
| 64 |
+
EOF = b"\x1a"
|
| 65 |
+
MAX_BYTES_WRITTEN = 32767
|
| 66 |
+
|
| 67 |
+
try:
|
| 68 |
+
from ctypes import pythonapi
|
| 69 |
+
except ImportError:
|
| 70 |
+
# On PyPy we cannot get buffers so our ability to operate here is
|
| 71 |
+
# severely limited.
|
| 72 |
+
get_buffer = None
|
| 73 |
+
else:
|
| 74 |
+
|
| 75 |
+
class Py_buffer(Structure):
|
| 76 |
+
_fields_ = [
|
| 77 |
+
("buf", c_void_p),
|
| 78 |
+
("obj", py_object),
|
| 79 |
+
("len", c_ssize_t),
|
| 80 |
+
("itemsize", c_ssize_t),
|
| 81 |
+
("readonly", c_int),
|
| 82 |
+
("ndim", c_int),
|
| 83 |
+
("format", c_char_p),
|
| 84 |
+
("shape", c_ssize_p),
|
| 85 |
+
("strides", c_ssize_p),
|
| 86 |
+
("suboffsets", c_ssize_p),
|
| 87 |
+
("internal", c_void_p),
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
| 91 |
+
PyBuffer_Release = pythonapi.PyBuffer_Release
|
| 92 |
+
|
| 93 |
+
def get_buffer(obj, writable=False):
|
| 94 |
+
buf = Py_buffer()
|
| 95 |
+
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
|
| 96 |
+
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
buffer_type = c_char * buf.len
|
| 100 |
+
return buffer_type.from_address(buf.buf)
|
| 101 |
+
finally:
|
| 102 |
+
PyBuffer_Release(byref(buf))
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
class _WindowsConsoleRawIOBase(io.RawIOBase):
|
| 106 |
+
def __init__(self, handle):
|
| 107 |
+
self.handle = handle
|
| 108 |
+
|
| 109 |
+
def isatty(self):
|
| 110 |
+
super().isatty()
|
| 111 |
+
return True
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
| 115 |
+
def readable(self):
|
| 116 |
+
return True
|
| 117 |
+
|
| 118 |
+
def readinto(self, b):
|
| 119 |
+
bytes_to_be_read = len(b)
|
| 120 |
+
if not bytes_to_be_read:
|
| 121 |
+
return 0
|
| 122 |
+
elif bytes_to_be_read % 2:
|
| 123 |
+
raise ValueError(
|
| 124 |
+
"cannot read odd number of bytes from UTF-16-LE encoded console"
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
buffer = get_buffer(b, writable=True)
|
| 128 |
+
code_units_to_be_read = bytes_to_be_read // 2
|
| 129 |
+
code_units_read = c_ulong()
|
| 130 |
+
|
| 131 |
+
rv = ReadConsoleW(
|
| 132 |
+
HANDLE(self.handle),
|
| 133 |
+
buffer,
|
| 134 |
+
code_units_to_be_read,
|
| 135 |
+
byref(code_units_read),
|
| 136 |
+
None,
|
| 137 |
+
)
|
| 138 |
+
if GetLastError() == ERROR_OPERATION_ABORTED:
|
| 139 |
+
# wait for KeyboardInterrupt
|
| 140 |
+
time.sleep(0.1)
|
| 141 |
+
if not rv:
|
| 142 |
+
raise OSError(f"Windows error: {GetLastError()}")
|
| 143 |
+
|
| 144 |
+
if buffer[0] == EOF:
|
| 145 |
+
return 0
|
| 146 |
+
return 2 * code_units_read.value
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
| 150 |
+
def writable(self):
|
| 151 |
+
return True
|
| 152 |
+
|
| 153 |
+
@staticmethod
|
| 154 |
+
def _get_error_message(errno):
|
| 155 |
+
if errno == ERROR_SUCCESS:
|
| 156 |
+
return "ERROR_SUCCESS"
|
| 157 |
+
elif errno == ERROR_NOT_ENOUGH_MEMORY:
|
| 158 |
+
return "ERROR_NOT_ENOUGH_MEMORY"
|
| 159 |
+
return f"Windows error {errno}"
|
| 160 |
+
|
| 161 |
+
def write(self, b):
|
| 162 |
+
bytes_to_be_written = len(b)
|
| 163 |
+
buf = get_buffer(b)
|
| 164 |
+
code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
|
| 165 |
+
code_units_written = c_ulong()
|
| 166 |
+
|
| 167 |
+
WriteConsoleW(
|
| 168 |
+
HANDLE(self.handle),
|
| 169 |
+
buf,
|
| 170 |
+
code_units_to_be_written,
|
| 171 |
+
byref(code_units_written),
|
| 172 |
+
None,
|
| 173 |
+
)
|
| 174 |
+
bytes_written = 2 * code_units_written.value
|
| 175 |
+
|
| 176 |
+
if bytes_written == 0 and bytes_to_be_written > 0:
|
| 177 |
+
raise OSError(self._get_error_message(GetLastError()))
|
| 178 |
+
return bytes_written
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class ConsoleStream:
|
| 182 |
+
def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None:
|
| 183 |
+
self._text_stream = text_stream
|
| 184 |
+
self.buffer = byte_stream
|
| 185 |
+
|
| 186 |
+
@property
|
| 187 |
+
def name(self) -> str:
|
| 188 |
+
return self.buffer.name
|
| 189 |
+
|
| 190 |
+
def write(self, x: t.AnyStr) -> int:
|
| 191 |
+
if isinstance(x, str):
|
| 192 |
+
return self._text_stream.write(x)
|
| 193 |
+
try:
|
| 194 |
+
self.flush()
|
| 195 |
+
except Exception:
|
| 196 |
+
pass
|
| 197 |
+
return self.buffer.write(x)
|
| 198 |
+
|
| 199 |
+
def writelines(self, lines: t.Iterable[t.AnyStr]) -> None:
|
| 200 |
+
for line in lines:
|
| 201 |
+
self.write(line)
|
| 202 |
+
|
| 203 |
+
def __getattr__(self, name: str) -> t.Any:
|
| 204 |
+
return getattr(self._text_stream, name)
|
| 205 |
+
|
| 206 |
+
def isatty(self) -> bool:
|
| 207 |
+
return self.buffer.isatty()
|
| 208 |
+
|
| 209 |
+
def __repr__(self):
|
| 210 |
+
return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO:
|
| 214 |
+
text_stream = _NonClosingTextIOWrapper(
|
| 215 |
+
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
| 216 |
+
"utf-16-le",
|
| 217 |
+
"strict",
|
| 218 |
+
line_buffering=True,
|
| 219 |
+
)
|
| 220 |
+
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO:
|
| 224 |
+
text_stream = _NonClosingTextIOWrapper(
|
| 225 |
+
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
|
| 226 |
+
"utf-16-le",
|
| 227 |
+
"strict",
|
| 228 |
+
line_buffering=True,
|
| 229 |
+
)
|
| 230 |
+
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO:
|
| 234 |
+
text_stream = _NonClosingTextIOWrapper(
|
| 235 |
+
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
|
| 236 |
+
"utf-16-le",
|
| 237 |
+
"strict",
|
| 238 |
+
line_buffering=True,
|
| 239 |
+
)
|
| 240 |
+
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = {
|
| 244 |
+
0: _get_text_stdin,
|
| 245 |
+
1: _get_text_stdout,
|
| 246 |
+
2: _get_text_stderr,
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def _is_console(f: t.TextIO) -> bool:
|
| 251 |
+
if not hasattr(f, "fileno"):
|
| 252 |
+
return False
|
| 253 |
+
|
| 254 |
+
try:
|
| 255 |
+
fileno = f.fileno()
|
| 256 |
+
except (OSError, io.UnsupportedOperation):
|
| 257 |
+
return False
|
| 258 |
+
|
| 259 |
+
handle = msvcrt.get_osfhandle(fileno)
|
| 260 |
+
return bool(GetConsoleMode(handle, byref(DWORD())))
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def _get_windows_console_stream(
|
| 264 |
+
f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
| 265 |
+
) -> t.Optional[t.TextIO]:
|
| 266 |
+
if (
|
| 267 |
+
get_buffer is not None
|
| 268 |
+
and encoding in {"utf-16-le", None}
|
| 269 |
+
and errors in {"strict", None}
|
| 270 |
+
and _is_console(f)
|
| 271 |
+
):
|
| 272 |
+
func = _stream_factories.get(f.fileno())
|
| 273 |
+
if func is not None:
|
| 274 |
+
b = getattr(f, "buffer", None)
|
| 275 |
+
|
| 276 |
+
if b is None:
|
| 277 |
+
return None
|
| 278 |
+
|
| 279 |
+
return func(b)
|
.venv/lib/python3.11/site-packages/click/core.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
.venv/lib/python3.11/site-packages/click/exceptions.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import typing as t
|
| 2 |
+
from gettext import gettext as _
|
| 3 |
+
from gettext import ngettext
|
| 4 |
+
|
| 5 |
+
from ._compat import get_text_stderr
|
| 6 |
+
from .globals import resolve_color_default
|
| 7 |
+
from .utils import echo
|
| 8 |
+
from .utils import format_filename
|
| 9 |
+
|
| 10 |
+
if t.TYPE_CHECKING:
|
| 11 |
+
from .core import Command
|
| 12 |
+
from .core import Context
|
| 13 |
+
from .core import Parameter
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _join_param_hints(
|
| 17 |
+
param_hint: t.Optional[t.Union[t.Sequence[str], str]],
|
| 18 |
+
) -> t.Optional[str]:
|
| 19 |
+
if param_hint is not None and not isinstance(param_hint, str):
|
| 20 |
+
return " / ".join(repr(x) for x in param_hint)
|
| 21 |
+
|
| 22 |
+
return param_hint
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class ClickException(Exception):
|
| 26 |
+
"""An exception that Click can handle and show to the user."""
|
| 27 |
+
|
| 28 |
+
#: The exit code for this exception.
|
| 29 |
+
exit_code = 1
|
| 30 |
+
|
| 31 |
+
def __init__(self, message: str) -> None:
|
| 32 |
+
super().__init__(message)
|
| 33 |
+
# The context will be removed by the time we print the message, so cache
|
| 34 |
+
# the color settings here to be used later on (in `show`)
|
| 35 |
+
self.show_color: t.Optional[bool] = resolve_color_default()
|
| 36 |
+
self.message = message
|
| 37 |
+
|
| 38 |
+
def format_message(self) -> str:
|
| 39 |
+
return self.message
|
| 40 |
+
|
| 41 |
+
def __str__(self) -> str:
|
| 42 |
+
return self.message
|
| 43 |
+
|
| 44 |
+
def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None:
|
| 45 |
+
if file is None:
|
| 46 |
+
file = get_text_stderr()
|
| 47 |
+
|
| 48 |
+
echo(
|
| 49 |
+
_("Error: {message}").format(message=self.format_message()),
|
| 50 |
+
file=file,
|
| 51 |
+
color=self.show_color,
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class UsageError(ClickException):
|
| 56 |
+
"""An internal exception that signals a usage error. This typically
|
| 57 |
+
aborts any further handling.
|
| 58 |
+
|
| 59 |
+
:param message: the error message to display.
|
| 60 |
+
:param ctx: optionally the context that caused this error. Click will
|
| 61 |
+
fill in the context automatically in some situations.
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
exit_code = 2
|
| 65 |
+
|
| 66 |
+
def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None:
|
| 67 |
+
super().__init__(message)
|
| 68 |
+
self.ctx = ctx
|
| 69 |
+
self.cmd: t.Optional[Command] = self.ctx.command if self.ctx else None
|
| 70 |
+
|
| 71 |
+
def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None:
|
| 72 |
+
if file is None:
|
| 73 |
+
file = get_text_stderr()
|
| 74 |
+
color = None
|
| 75 |
+
hint = ""
|
| 76 |
+
if (
|
| 77 |
+
self.ctx is not None
|
| 78 |
+
and self.ctx.command.get_help_option(self.ctx) is not None
|
| 79 |
+
):
|
| 80 |
+
hint = _("Try '{command} {option}' for help.").format(
|
| 81 |
+
command=self.ctx.command_path, option=self.ctx.help_option_names[0]
|
| 82 |
+
)
|
| 83 |
+
hint = f"{hint}\n"
|
| 84 |
+
if self.ctx is not None:
|
| 85 |
+
color = self.ctx.color
|
| 86 |
+
echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
|
| 87 |
+
echo(
|
| 88 |
+
_("Error: {message}").format(message=self.format_message()),
|
| 89 |
+
file=file,
|
| 90 |
+
color=color,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class BadParameter(UsageError):
|
| 95 |
+
"""An exception that formats out a standardized error message for a
|
| 96 |
+
bad parameter. This is useful when thrown from a callback or type as
|
| 97 |
+
Click will attach contextual information to it (for instance, which
|
| 98 |
+
parameter it is).
|
| 99 |
+
|
| 100 |
+
.. versionadded:: 2.0
|
| 101 |
+
|
| 102 |
+
:param param: the parameter object that caused this error. This can
|
| 103 |
+
be left out, and Click will attach this info itself
|
| 104 |
+
if possible.
|
| 105 |
+
:param param_hint: a string that shows up as parameter name. This
|
| 106 |
+
can be used as alternative to `param` in cases
|
| 107 |
+
where custom validation should happen. If it is
|
| 108 |
+
a string it's used as such, if it's a list then
|
| 109 |
+
each item is quoted and separated.
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
def __init__(
|
| 113 |
+
self,
|
| 114 |
+
message: str,
|
| 115 |
+
ctx: t.Optional["Context"] = None,
|
| 116 |
+
param: t.Optional["Parameter"] = None,
|
| 117 |
+
param_hint: t.Optional[str] = None,
|
| 118 |
+
) -> None:
|
| 119 |
+
super().__init__(message, ctx)
|
| 120 |
+
self.param = param
|
| 121 |
+
self.param_hint = param_hint
|
| 122 |
+
|
| 123 |
+
def format_message(self) -> str:
|
| 124 |
+
if self.param_hint is not None:
|
| 125 |
+
param_hint = self.param_hint
|
| 126 |
+
elif self.param is not None:
|
| 127 |
+
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
| 128 |
+
else:
|
| 129 |
+
return _("Invalid value: {message}").format(message=self.message)
|
| 130 |
+
|
| 131 |
+
return _("Invalid value for {param_hint}: {message}").format(
|
| 132 |
+
param_hint=_join_param_hints(param_hint), message=self.message
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class MissingParameter(BadParameter):
|
| 137 |
+
"""Raised if click required an option or argument but it was not
|
| 138 |
+
provided when invoking the script.
|
| 139 |
+
|
| 140 |
+
.. versionadded:: 4.0
|
| 141 |
+
|
| 142 |
+
:param param_type: a string that indicates the type of the parameter.
|
| 143 |
+
The default is to inherit the parameter type from
|
| 144 |
+
the given `param`. Valid values are ``'parameter'``,
|
| 145 |
+
``'option'`` or ``'argument'``.
|
| 146 |
+
"""
|
| 147 |
+
|
| 148 |
+
def __init__(
|
| 149 |
+
self,
|
| 150 |
+
message: t.Optional[str] = None,
|
| 151 |
+
ctx: t.Optional["Context"] = None,
|
| 152 |
+
param: t.Optional["Parameter"] = None,
|
| 153 |
+
param_hint: t.Optional[str] = None,
|
| 154 |
+
param_type: t.Optional[str] = None,
|
| 155 |
+
) -> None:
|
| 156 |
+
super().__init__(message or "", ctx, param, param_hint)
|
| 157 |
+
self.param_type = param_type
|
| 158 |
+
|
| 159 |
+
def format_message(self) -> str:
|
| 160 |
+
if self.param_hint is not None:
|
| 161 |
+
param_hint: t.Optional[str] = self.param_hint
|
| 162 |
+
elif self.param is not None:
|
| 163 |
+
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
| 164 |
+
else:
|
| 165 |
+
param_hint = None
|
| 166 |
+
|
| 167 |
+
param_hint = _join_param_hints(param_hint)
|
| 168 |
+
param_hint = f" {param_hint}" if param_hint else ""
|
| 169 |
+
|
| 170 |
+
param_type = self.param_type
|
| 171 |
+
if param_type is None and self.param is not None:
|
| 172 |
+
param_type = self.param.param_type_name
|
| 173 |
+
|
| 174 |
+
msg = self.message
|
| 175 |
+
if self.param is not None:
|
| 176 |
+
msg_extra = self.param.type.get_missing_message(self.param)
|
| 177 |
+
if msg_extra:
|
| 178 |
+
if msg:
|
| 179 |
+
msg += f". {msg_extra}"
|
| 180 |
+
else:
|
| 181 |
+
msg = msg_extra
|
| 182 |
+
|
| 183 |
+
msg = f" {msg}" if msg else ""
|
| 184 |
+
|
| 185 |
+
# Translate param_type for known types.
|
| 186 |
+
if param_type == "argument":
|
| 187 |
+
missing = _("Missing argument")
|
| 188 |
+
elif param_type == "option":
|
| 189 |
+
missing = _("Missing option")
|
| 190 |
+
elif param_type == "parameter":
|
| 191 |
+
missing = _("Missing parameter")
|
| 192 |
+
else:
|
| 193 |
+
missing = _("Missing {param_type}").format(param_type=param_type)
|
| 194 |
+
|
| 195 |
+
return f"{missing}{param_hint}.{msg}"
|
| 196 |
+
|
| 197 |
+
def __str__(self) -> str:
|
| 198 |
+
if not self.message:
|
| 199 |
+
param_name = self.param.name if self.param else None
|
| 200 |
+
return _("Missing parameter: {param_name}").format(param_name=param_name)
|
| 201 |
+
else:
|
| 202 |
+
return self.message
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
class NoSuchOption(UsageError):
|
| 206 |
+
"""Raised if click attempted to handle an option that does not
|
| 207 |
+
exist.
|
| 208 |
+
|
| 209 |
+
.. versionadded:: 4.0
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
def __init__(
|
| 213 |
+
self,
|
| 214 |
+
option_name: str,
|
| 215 |
+
message: t.Optional[str] = None,
|
| 216 |
+
possibilities: t.Optional[t.Sequence[str]] = None,
|
| 217 |
+
ctx: t.Optional["Context"] = None,
|
| 218 |
+
) -> None:
|
| 219 |
+
if message is None:
|
| 220 |
+
message = _("No such option: {name}").format(name=option_name)
|
| 221 |
+
|
| 222 |
+
super().__init__(message, ctx)
|
| 223 |
+
self.option_name = option_name
|
| 224 |
+
self.possibilities = possibilities
|
| 225 |
+
|
| 226 |
+
def format_message(self) -> str:
|
| 227 |
+
if not self.possibilities:
|
| 228 |
+
return self.message
|
| 229 |
+
|
| 230 |
+
possibility_str = ", ".join(sorted(self.possibilities))
|
| 231 |
+
suggest = ngettext(
|
| 232 |
+
"Did you mean {possibility}?",
|
| 233 |
+
"(Possible options: {possibilities})",
|
| 234 |
+
len(self.possibilities),
|
| 235 |
+
).format(possibility=possibility_str, possibilities=possibility_str)
|
| 236 |
+
return f"{self.message} {suggest}"
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
class BadOptionUsage(UsageError):
|
| 240 |
+
"""Raised if an option is generally supplied but the use of the option
|
| 241 |
+
was incorrect. This is for instance raised if the number of arguments
|
| 242 |
+
for an option is not correct.
|
| 243 |
+
|
| 244 |
+
.. versionadded:: 4.0
|
| 245 |
+
|
| 246 |
+
:param option_name: the name of the option being used incorrectly.
|
| 247 |
+
"""
|
| 248 |
+
|
| 249 |
+
def __init__(
|
| 250 |
+
self, option_name: str, message: str, ctx: t.Optional["Context"] = None
|
| 251 |
+
) -> None:
|
| 252 |
+
super().__init__(message, ctx)
|
| 253 |
+
self.option_name = option_name
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
class BadArgumentUsage(UsageError):
|
| 257 |
+
"""Raised if an argument is generally supplied but the use of the argument
|
| 258 |
+
was incorrect. This is for instance raised if the number of values
|
| 259 |
+
for an argument is not correct.
|
| 260 |
+
|
| 261 |
+
.. versionadded:: 6.0
|
| 262 |
+
"""
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
class FileError(ClickException):
|
| 266 |
+
"""Raised if a file cannot be opened."""
|
| 267 |
+
|
| 268 |
+
def __init__(self, filename: str, hint: t.Optional[str] = None) -> None:
|
| 269 |
+
if hint is None:
|
| 270 |
+
hint = _("unknown error")
|
| 271 |
+
|
| 272 |
+
super().__init__(hint)
|
| 273 |
+
self.ui_filename: str = format_filename(filename)
|
| 274 |
+
self.filename = filename
|
| 275 |
+
|
| 276 |
+
def format_message(self) -> str:
|
| 277 |
+
return _("Could not open file {filename!r}: {message}").format(
|
| 278 |
+
filename=self.ui_filename, message=self.message
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
class Abort(RuntimeError):
|
| 283 |
+
"""An internal signalling exception that signals Click to abort."""
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class Exit(RuntimeError):
|
| 287 |
+
"""An exception that indicates that the application should exit with some
|
| 288 |
+
status code.
|
| 289 |
+
|
| 290 |
+
:param code: the status code to exit with.
|
| 291 |
+
"""
|
| 292 |
+
|
| 293 |
+
__slots__ = ("exit_code",)
|
| 294 |
+
|
| 295 |
+
def __init__(self, code: int = 0) -> None:
|
| 296 |
+
self.exit_code: int = code
|
.venv/lib/python3.11/site-packages/click/formatting.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import typing as t
|
| 2 |
+
from contextlib import contextmanager
|
| 3 |
+
from gettext import gettext as _
|
| 4 |
+
|
| 5 |
+
from ._compat import term_len
|
| 6 |
+
from .parser import split_opt
|
| 7 |
+
|
| 8 |
+
# Can force a width. This is used by the test system
|
| 9 |
+
FORCED_WIDTH: t.Optional[int] = None
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]:
|
| 13 |
+
widths: t.Dict[int, int] = {}
|
| 14 |
+
|
| 15 |
+
for row in rows:
|
| 16 |
+
for idx, col in enumerate(row):
|
| 17 |
+
widths[idx] = max(widths.get(idx, 0), term_len(col))
|
| 18 |
+
|
| 19 |
+
return tuple(y for x, y in sorted(widths.items()))
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def iter_rows(
|
| 23 |
+
rows: t.Iterable[t.Tuple[str, str]], col_count: int
|
| 24 |
+
) -> t.Iterator[t.Tuple[str, ...]]:
|
| 25 |
+
for row in rows:
|
| 26 |
+
yield row + ("",) * (col_count - len(row))
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def wrap_text(
|
| 30 |
+
text: str,
|
| 31 |
+
width: int = 78,
|
| 32 |
+
initial_indent: str = "",
|
| 33 |
+
subsequent_indent: str = "",
|
| 34 |
+
preserve_paragraphs: bool = False,
|
| 35 |
+
) -> str:
|
| 36 |
+
"""A helper function that intelligently wraps text. By default, it
|
| 37 |
+
assumes that it operates on a single paragraph of text but if the
|
| 38 |
+
`preserve_paragraphs` parameter is provided it will intelligently
|
| 39 |
+
handle paragraphs (defined by two empty lines).
|
| 40 |
+
|
| 41 |
+
If paragraphs are handled, a paragraph can be prefixed with an empty
|
| 42 |
+
line containing the ``\\b`` character (``\\x08``) to indicate that
|
| 43 |
+
no rewrapping should happen in that block.
|
| 44 |
+
|
| 45 |
+
:param text: the text that should be rewrapped.
|
| 46 |
+
:param width: the maximum width for the text.
|
| 47 |
+
:param initial_indent: the initial indent that should be placed on the
|
| 48 |
+
first line as a string.
|
| 49 |
+
:param subsequent_indent: the indent string that should be placed on
|
| 50 |
+
each consecutive line.
|
| 51 |
+
:param preserve_paragraphs: if this flag is set then the wrapping will
|
| 52 |
+
intelligently handle paragraphs.
|
| 53 |
+
"""
|
| 54 |
+
from ._textwrap import TextWrapper
|
| 55 |
+
|
| 56 |
+
text = text.expandtabs()
|
| 57 |
+
wrapper = TextWrapper(
|
| 58 |
+
width,
|
| 59 |
+
initial_indent=initial_indent,
|
| 60 |
+
subsequent_indent=subsequent_indent,
|
| 61 |
+
replace_whitespace=False,
|
| 62 |
+
)
|
| 63 |
+
if not preserve_paragraphs:
|
| 64 |
+
return wrapper.fill(text)
|
| 65 |
+
|
| 66 |
+
p: t.List[t.Tuple[int, bool, str]] = []
|
| 67 |
+
buf: t.List[str] = []
|
| 68 |
+
indent = None
|
| 69 |
+
|
| 70 |
+
def _flush_par() -> None:
|
| 71 |
+
if not buf:
|
| 72 |
+
return
|
| 73 |
+
if buf[0].strip() == "\b":
|
| 74 |
+
p.append((indent or 0, True, "\n".join(buf[1:])))
|
| 75 |
+
else:
|
| 76 |
+
p.append((indent or 0, False, " ".join(buf)))
|
| 77 |
+
del buf[:]
|
| 78 |
+
|
| 79 |
+
for line in text.splitlines():
|
| 80 |
+
if not line:
|
| 81 |
+
_flush_par()
|
| 82 |
+
indent = None
|
| 83 |
+
else:
|
| 84 |
+
if indent is None:
|
| 85 |
+
orig_len = term_len(line)
|
| 86 |
+
line = line.lstrip()
|
| 87 |
+
indent = orig_len - term_len(line)
|
| 88 |
+
buf.append(line)
|
| 89 |
+
_flush_par()
|
| 90 |
+
|
| 91 |
+
rv = []
|
| 92 |
+
for indent, raw, text in p:
|
| 93 |
+
with wrapper.extra_indent(" " * indent):
|
| 94 |
+
if raw:
|
| 95 |
+
rv.append(wrapper.indent_only(text))
|
| 96 |
+
else:
|
| 97 |
+
rv.append(wrapper.fill(text))
|
| 98 |
+
|
| 99 |
+
return "\n\n".join(rv)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class HelpFormatter:
|
| 103 |
+
"""This class helps with formatting text-based help pages. It's
|
| 104 |
+
usually just needed for very special internal cases, but it's also
|
| 105 |
+
exposed so that developers can write their own fancy outputs.
|
| 106 |
+
|
| 107 |
+
At present, it always writes into memory.
|
| 108 |
+
|
| 109 |
+
:param indent_increment: the additional increment for each level.
|
| 110 |
+
:param width: the width for the text. This defaults to the terminal
|
| 111 |
+
width clamped to a maximum of 78.
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
def __init__(
|
| 115 |
+
self,
|
| 116 |
+
indent_increment: int = 2,
|
| 117 |
+
width: t.Optional[int] = None,
|
| 118 |
+
max_width: t.Optional[int] = None,
|
| 119 |
+
) -> None:
|
| 120 |
+
import shutil
|
| 121 |
+
|
| 122 |
+
self.indent_increment = indent_increment
|
| 123 |
+
if max_width is None:
|
| 124 |
+
max_width = 80
|
| 125 |
+
if width is None:
|
| 126 |
+
width = FORCED_WIDTH
|
| 127 |
+
if width is None:
|
| 128 |
+
width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
|
| 129 |
+
self.width = width
|
| 130 |
+
self.current_indent = 0
|
| 131 |
+
self.buffer: t.List[str] = []
|
| 132 |
+
|
| 133 |
+
def write(self, string: str) -> None:
|
| 134 |
+
"""Writes a unicode string into the internal buffer."""
|
| 135 |
+
self.buffer.append(string)
|
| 136 |
+
|
| 137 |
+
def indent(self) -> None:
|
| 138 |
+
"""Increases the indentation."""
|
| 139 |
+
self.current_indent += self.indent_increment
|
| 140 |
+
|
| 141 |
+
def dedent(self) -> None:
|
| 142 |
+
"""Decreases the indentation."""
|
| 143 |
+
self.current_indent -= self.indent_increment
|
| 144 |
+
|
| 145 |
+
def write_usage(
|
| 146 |
+
self, prog: str, args: str = "", prefix: t.Optional[str] = None
|
| 147 |
+
) -> None:
|
| 148 |
+
"""Writes a usage line into the buffer.
|
| 149 |
+
|
| 150 |
+
:param prog: the program name.
|
| 151 |
+
:param args: whitespace separated list of arguments.
|
| 152 |
+
:param prefix: The prefix for the first line. Defaults to
|
| 153 |
+
``"Usage: "``.
|
| 154 |
+
"""
|
| 155 |
+
if prefix is None:
|
| 156 |
+
prefix = f"{_('Usage:')} "
|
| 157 |
+
|
| 158 |
+
usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
|
| 159 |
+
text_width = self.width - self.current_indent
|
| 160 |
+
|
| 161 |
+
if text_width >= (term_len(usage_prefix) + 20):
|
| 162 |
+
# The arguments will fit to the right of the prefix.
|
| 163 |
+
indent = " " * term_len(usage_prefix)
|
| 164 |
+
self.write(
|
| 165 |
+
wrap_text(
|
| 166 |
+
args,
|
| 167 |
+
text_width,
|
| 168 |
+
initial_indent=usage_prefix,
|
| 169 |
+
subsequent_indent=indent,
|
| 170 |
+
)
|
| 171 |
+
)
|
| 172 |
+
else:
|
| 173 |
+
# The prefix is too long, put the arguments on the next line.
|
| 174 |
+
self.write(usage_prefix)
|
| 175 |
+
self.write("\n")
|
| 176 |
+
indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
|
| 177 |
+
self.write(
|
| 178 |
+
wrap_text(
|
| 179 |
+
args, text_width, initial_indent=indent, subsequent_indent=indent
|
| 180 |
+
)
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
self.write("\n")
|
| 184 |
+
|
| 185 |
+
def write_heading(self, heading: str) -> None:
|
| 186 |
+
"""Writes a heading into the buffer."""
|
| 187 |
+
self.write(f"{'':>{self.current_indent}}{heading}:\n")
|
| 188 |
+
|
| 189 |
+
def write_paragraph(self) -> None:
|
| 190 |
+
"""Writes a paragraph into the buffer."""
|
| 191 |
+
if self.buffer:
|
| 192 |
+
self.write("\n")
|
| 193 |
+
|
| 194 |
+
def write_text(self, text: str) -> None:
|
| 195 |
+
"""Writes re-indented text into the buffer. This rewraps and
|
| 196 |
+
preserves paragraphs.
|
| 197 |
+
"""
|
| 198 |
+
indent = " " * self.current_indent
|
| 199 |
+
self.write(
|
| 200 |
+
wrap_text(
|
| 201 |
+
text,
|
| 202 |
+
self.width,
|
| 203 |
+
initial_indent=indent,
|
| 204 |
+
subsequent_indent=indent,
|
| 205 |
+
preserve_paragraphs=True,
|
| 206 |
+
)
|
| 207 |
+
)
|
| 208 |
+
self.write("\n")
|
| 209 |
+
|
| 210 |
+
def write_dl(
|
| 211 |
+
self,
|
| 212 |
+
rows: t.Sequence[t.Tuple[str, str]],
|
| 213 |
+
col_max: int = 30,
|
| 214 |
+
col_spacing: int = 2,
|
| 215 |
+
) -> None:
|
| 216 |
+
"""Writes a definition list into the buffer. This is how options
|
| 217 |
+
and commands are usually formatted.
|
| 218 |
+
|
| 219 |
+
:param rows: a list of two item tuples for the terms and values.
|
| 220 |
+
:param col_max: the maximum width of the first column.
|
| 221 |
+
:param col_spacing: the number of spaces between the first and
|
| 222 |
+
second column.
|
| 223 |
+
"""
|
| 224 |
+
rows = list(rows)
|
| 225 |
+
widths = measure_table(rows)
|
| 226 |
+
if len(widths) != 2:
|
| 227 |
+
raise TypeError("Expected two columns for definition list")
|
| 228 |
+
|
| 229 |
+
first_col = min(widths[0], col_max) + col_spacing
|
| 230 |
+
|
| 231 |
+
for first, second in iter_rows(rows, len(widths)):
|
| 232 |
+
self.write(f"{'':>{self.current_indent}}{first}")
|
| 233 |
+
if not second:
|
| 234 |
+
self.write("\n")
|
| 235 |
+
continue
|
| 236 |
+
if term_len(first) <= first_col - col_spacing:
|
| 237 |
+
self.write(" " * (first_col - term_len(first)))
|
| 238 |
+
else:
|
| 239 |
+
self.write("\n")
|
| 240 |
+
self.write(" " * (first_col + self.current_indent))
|
| 241 |
+
|
| 242 |
+
text_width = max(self.width - first_col - 2, 10)
|
| 243 |
+
wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
|
| 244 |
+
lines = wrapped_text.splitlines()
|
| 245 |
+
|
| 246 |
+
if lines:
|
| 247 |
+
self.write(f"{lines[0]}\n")
|
| 248 |
+
|
| 249 |
+
for line in lines[1:]:
|
| 250 |
+
self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
|
| 251 |
+
else:
|
| 252 |
+
self.write("\n")
|
| 253 |
+
|
| 254 |
+
@contextmanager
|
| 255 |
+
def section(self, name: str) -> t.Iterator[None]:
|
| 256 |
+
"""Helpful context manager that writes a paragraph, a heading,
|
| 257 |
+
and the indents.
|
| 258 |
+
|
| 259 |
+
:param name: the section name that is written as heading.
|
| 260 |
+
"""
|
| 261 |
+
self.write_paragraph()
|
| 262 |
+
self.write_heading(name)
|
| 263 |
+
self.indent()
|
| 264 |
+
try:
|
| 265 |
+
yield
|
| 266 |
+
finally:
|
| 267 |
+
self.dedent()
|
| 268 |
+
|
| 269 |
+
@contextmanager
|
| 270 |
+
def indentation(self) -> t.Iterator[None]:
|
| 271 |
+
"""A context manager that increases the indentation."""
|
| 272 |
+
self.indent()
|
| 273 |
+
try:
|
| 274 |
+
yield
|
| 275 |
+
finally:
|
| 276 |
+
self.dedent()
|
| 277 |
+
|
| 278 |
+
def getvalue(self) -> str:
|
| 279 |
+
"""Returns the buffer contents."""
|
| 280 |
+
return "".join(self.buffer)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]:
|
| 284 |
+
"""Given a list of option strings this joins them in the most appropriate
|
| 285 |
+
way and returns them in the form ``(formatted_string,
|
| 286 |
+
any_prefix_is_slash)`` where the second item in the tuple is a flag that
|
| 287 |
+
indicates if any of the option prefixes was a slash.
|
| 288 |
+
"""
|
| 289 |
+
rv = []
|
| 290 |
+
any_prefix_is_slash = False
|
| 291 |
+
|
| 292 |
+
for opt in options:
|
| 293 |
+
prefix = split_opt(opt)[0]
|
| 294 |
+
|
| 295 |
+
if prefix == "/":
|
| 296 |
+
any_prefix_is_slash = True
|
| 297 |
+
|
| 298 |
+
rv.append((len(prefix), opt))
|
| 299 |
+
|
| 300 |
+
rv.sort(key=lambda x: x[0])
|
| 301 |
+
return ", ".join(x[1] for x in rv), any_prefix_is_slash
|
.venv/lib/python3.11/site-packages/click/globals.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import typing as t
|
| 2 |
+
from threading import local
|
| 3 |
+
|
| 4 |
+
if t.TYPE_CHECKING:
|
| 5 |
+
import typing_extensions as te
|
| 6 |
+
|
| 7 |
+
from .core import Context
|
| 8 |
+
|
| 9 |
+
_local = local()
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@t.overload
|
| 13 |
+
def get_current_context(silent: "te.Literal[False]" = False) -> "Context": ...
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@t.overload
|
| 17 |
+
def get_current_context(silent: bool = ...) -> t.Optional["Context"]: ...
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def get_current_context(silent: bool = False) -> t.Optional["Context"]:
|
| 21 |
+
"""Returns the current click context. This can be used as a way to
|
| 22 |
+
access the current context object from anywhere. This is a more implicit
|
| 23 |
+
alternative to the :func:`pass_context` decorator. This function is
|
| 24 |
+
primarily useful for helpers such as :func:`echo` which might be
|
| 25 |
+
interested in changing its behavior based on the current context.
|
| 26 |
+
|
| 27 |
+
To push the current context, :meth:`Context.scope` can be used.
|
| 28 |
+
|
| 29 |
+
.. versionadded:: 5.0
|
| 30 |
+
|
| 31 |
+
:param silent: if set to `True` the return value is `None` if no context
|
| 32 |
+
is available. The default behavior is to raise a
|
| 33 |
+
:exc:`RuntimeError`.
|
| 34 |
+
"""
|
| 35 |
+
try:
|
| 36 |
+
return t.cast("Context", _local.stack[-1])
|
| 37 |
+
except (AttributeError, IndexError) as e:
|
| 38 |
+
if not silent:
|
| 39 |
+
raise RuntimeError("There is no active click context.") from e
|
| 40 |
+
|
| 41 |
+
return None
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def push_context(ctx: "Context") -> None:
|
| 45 |
+
"""Pushes a new context to the current stack."""
|
| 46 |
+
_local.__dict__.setdefault("stack", []).append(ctx)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def pop_context() -> None:
|
| 50 |
+
"""Removes the top level from the stack."""
|
| 51 |
+
_local.stack.pop()
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]:
|
| 55 |
+
"""Internal helper to get the default value of the color flag. If a
|
| 56 |
+
value is passed it's returned unchanged, otherwise it's looked up from
|
| 57 |
+
the current context.
|
| 58 |
+
"""
|
| 59 |
+
if color is not None:
|
| 60 |
+
return color
|
| 61 |
+
|
| 62 |
+
ctx = get_current_context(silent=True)
|
| 63 |
+
|
| 64 |
+
if ctx is not None:
|
| 65 |
+
return ctx.color
|
| 66 |
+
|
| 67 |
+
return None
|
.venv/lib/python3.11/site-packages/click/parser.py
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
This module started out as largely a copy paste from the stdlib's
|
| 3 |
+
optparse module with the features removed that we do not need from
|
| 4 |
+
optparse because we implement them in Click on a higher level (for
|
| 5 |
+
instance type handling, help formatting and a lot more).
|
| 6 |
+
|
| 7 |
+
The plan is to remove more and more from here over time.
|
| 8 |
+
|
| 9 |
+
The reason this is a different module and not optparse from the stdlib
|
| 10 |
+
is that there are differences in 2.x and 3.x about the error messages
|
| 11 |
+
generated and optparse in the stdlib uses gettext for no good reason
|
| 12 |
+
and might cause us issues.
|
| 13 |
+
|
| 14 |
+
Click uses parts of optparse written by Gregory P. Ward and maintained
|
| 15 |
+
by the Python Software Foundation. This is limited to code in parser.py.
|
| 16 |
+
|
| 17 |
+
Copyright 2001-2006 Gregory P. Ward. All rights reserved.
|
| 18 |
+
Copyright 2002-2006 Python Software Foundation. All rights reserved.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
# This code uses parts of optparse written by Gregory P. Ward and
|
| 22 |
+
# maintained by the Python Software Foundation.
|
| 23 |
+
# Copyright 2001-2006 Gregory P. Ward
|
| 24 |
+
# Copyright 2002-2006 Python Software Foundation
|
| 25 |
+
import typing as t
|
| 26 |
+
from collections import deque
|
| 27 |
+
from gettext import gettext as _
|
| 28 |
+
from gettext import ngettext
|
| 29 |
+
|
| 30 |
+
from .exceptions import BadArgumentUsage
|
| 31 |
+
from .exceptions import BadOptionUsage
|
| 32 |
+
from .exceptions import NoSuchOption
|
| 33 |
+
from .exceptions import UsageError
|
| 34 |
+
|
| 35 |
+
if t.TYPE_CHECKING:
|
| 36 |
+
import typing_extensions as te
|
| 37 |
+
|
| 38 |
+
from .core import Argument as CoreArgument
|
| 39 |
+
from .core import Context
|
| 40 |
+
from .core import Option as CoreOption
|
| 41 |
+
from .core import Parameter as CoreParameter
|
| 42 |
+
|
| 43 |
+
V = t.TypeVar("V")
|
| 44 |
+
|
| 45 |
+
# Sentinel value that indicates an option was passed as a flag without a
|
| 46 |
+
# value but is not a flag option. Option.consume_value uses this to
|
| 47 |
+
# prompt or use the flag_value.
|
| 48 |
+
_flag_needs_value = object()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _unpack_args(
|
| 52 |
+
args: t.Sequence[str], nargs_spec: t.Sequence[int]
|
| 53 |
+
) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]:
|
| 54 |
+
"""Given an iterable of arguments and an iterable of nargs specifications,
|
| 55 |
+
it returns a tuple with all the unpacked arguments at the first index
|
| 56 |
+
and all remaining arguments as the second.
|
| 57 |
+
|
| 58 |
+
The nargs specification is the number of arguments that should be consumed
|
| 59 |
+
or `-1` to indicate that this position should eat up all the remainders.
|
| 60 |
+
|
| 61 |
+
Missing items are filled with `None`.
|
| 62 |
+
"""
|
| 63 |
+
args = deque(args)
|
| 64 |
+
nargs_spec = deque(nargs_spec)
|
| 65 |
+
rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = []
|
| 66 |
+
spos: t.Optional[int] = None
|
| 67 |
+
|
| 68 |
+
def _fetch(c: "te.Deque[V]") -> t.Optional[V]:
|
| 69 |
+
try:
|
| 70 |
+
if spos is None:
|
| 71 |
+
return c.popleft()
|
| 72 |
+
else:
|
| 73 |
+
return c.pop()
|
| 74 |
+
except IndexError:
|
| 75 |
+
return None
|
| 76 |
+
|
| 77 |
+
while nargs_spec:
|
| 78 |
+
nargs = _fetch(nargs_spec)
|
| 79 |
+
|
| 80 |
+
if nargs is None:
|
| 81 |
+
continue
|
| 82 |
+
|
| 83 |
+
if nargs == 1:
|
| 84 |
+
rv.append(_fetch(args))
|
| 85 |
+
elif nargs > 1:
|
| 86 |
+
x = [_fetch(args) for _ in range(nargs)]
|
| 87 |
+
|
| 88 |
+
# If we're reversed, we're pulling in the arguments in reverse,
|
| 89 |
+
# so we need to turn them around.
|
| 90 |
+
if spos is not None:
|
| 91 |
+
x.reverse()
|
| 92 |
+
|
| 93 |
+
rv.append(tuple(x))
|
| 94 |
+
elif nargs < 0:
|
| 95 |
+
if spos is not None:
|
| 96 |
+
raise TypeError("Cannot have two nargs < 0")
|
| 97 |
+
|
| 98 |
+
spos = len(rv)
|
| 99 |
+
rv.append(None)
|
| 100 |
+
|
| 101 |
+
# spos is the position of the wildcard (star). If it's not `None`,
|
| 102 |
+
# we fill it with the remainder.
|
| 103 |
+
if spos is not None:
|
| 104 |
+
rv[spos] = tuple(args)
|
| 105 |
+
args = []
|
| 106 |
+
rv[spos + 1 :] = reversed(rv[spos + 1 :])
|
| 107 |
+
|
| 108 |
+
return tuple(rv), list(args)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def split_opt(opt: str) -> t.Tuple[str, str]:
|
| 112 |
+
first = opt[:1]
|
| 113 |
+
if first.isalnum():
|
| 114 |
+
return "", opt
|
| 115 |
+
if opt[1:2] == first:
|
| 116 |
+
return opt[:2], opt[2:]
|
| 117 |
+
return first, opt[1:]
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str:
|
| 121 |
+
if ctx is None or ctx.token_normalize_func is None:
|
| 122 |
+
return opt
|
| 123 |
+
prefix, opt = split_opt(opt)
|
| 124 |
+
return f"{prefix}{ctx.token_normalize_func(opt)}"
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def split_arg_string(string: str) -> t.List[str]:
|
| 128 |
+
"""Split an argument string as with :func:`shlex.split`, but don't
|
| 129 |
+
fail if the string is incomplete. Ignores a missing closing quote or
|
| 130 |
+
incomplete escape sequence and uses the partial token as-is.
|
| 131 |
+
|
| 132 |
+
.. code-block:: python
|
| 133 |
+
|
| 134 |
+
split_arg_string("example 'my file")
|
| 135 |
+
["example", "my file"]
|
| 136 |
+
|
| 137 |
+
split_arg_string("example my\\")
|
| 138 |
+
["example", "my"]
|
| 139 |
+
|
| 140 |
+
:param string: String to split.
|
| 141 |
+
"""
|
| 142 |
+
import shlex
|
| 143 |
+
|
| 144 |
+
lex = shlex.shlex(string, posix=True)
|
| 145 |
+
lex.whitespace_split = True
|
| 146 |
+
lex.commenters = ""
|
| 147 |
+
out = []
|
| 148 |
+
|
| 149 |
+
try:
|
| 150 |
+
for token in lex:
|
| 151 |
+
out.append(token)
|
| 152 |
+
except ValueError:
|
| 153 |
+
# Raised when end-of-string is reached in an invalid state. Use
|
| 154 |
+
# the partial token as-is. The quote or escape character is in
|
| 155 |
+
# lex.state, not lex.token.
|
| 156 |
+
out.append(lex.token)
|
| 157 |
+
|
| 158 |
+
return out
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
class Option:
|
| 162 |
+
def __init__(
|
| 163 |
+
self,
|
| 164 |
+
obj: "CoreOption",
|
| 165 |
+
opts: t.Sequence[str],
|
| 166 |
+
dest: t.Optional[str],
|
| 167 |
+
action: t.Optional[str] = None,
|
| 168 |
+
nargs: int = 1,
|
| 169 |
+
const: t.Optional[t.Any] = None,
|
| 170 |
+
):
|
| 171 |
+
self._short_opts = []
|
| 172 |
+
self._long_opts = []
|
| 173 |
+
self.prefixes: t.Set[str] = set()
|
| 174 |
+
|
| 175 |
+
for opt in opts:
|
| 176 |
+
prefix, value = split_opt(opt)
|
| 177 |
+
if not prefix:
|
| 178 |
+
raise ValueError(f"Invalid start character for option ({opt})")
|
| 179 |
+
self.prefixes.add(prefix[0])
|
| 180 |
+
if len(prefix) == 1 and len(value) == 1:
|
| 181 |
+
self._short_opts.append(opt)
|
| 182 |
+
else:
|
| 183 |
+
self._long_opts.append(opt)
|
| 184 |
+
self.prefixes.add(prefix)
|
| 185 |
+
|
| 186 |
+
if action is None:
|
| 187 |
+
action = "store"
|
| 188 |
+
|
| 189 |
+
self.dest = dest
|
| 190 |
+
self.action = action
|
| 191 |
+
self.nargs = nargs
|
| 192 |
+
self.const = const
|
| 193 |
+
self.obj = obj
|
| 194 |
+
|
| 195 |
+
@property
|
| 196 |
+
def takes_value(self) -> bool:
|
| 197 |
+
return self.action in ("store", "append")
|
| 198 |
+
|
| 199 |
+
def process(self, value: t.Any, state: "ParsingState") -> None:
|
| 200 |
+
if self.action == "store":
|
| 201 |
+
state.opts[self.dest] = value # type: ignore
|
| 202 |
+
elif self.action == "store_const":
|
| 203 |
+
state.opts[self.dest] = self.const # type: ignore
|
| 204 |
+
elif self.action == "append":
|
| 205 |
+
state.opts.setdefault(self.dest, []).append(value) # type: ignore
|
| 206 |
+
elif self.action == "append_const":
|
| 207 |
+
state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
|
| 208 |
+
elif self.action == "count":
|
| 209 |
+
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
|
| 210 |
+
else:
|
| 211 |
+
raise ValueError(f"unknown action '{self.action}'")
|
| 212 |
+
state.order.append(self.obj)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
class Argument:
|
| 216 |
+
def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1):
|
| 217 |
+
self.dest = dest
|
| 218 |
+
self.nargs = nargs
|
| 219 |
+
self.obj = obj
|
| 220 |
+
|
| 221 |
+
def process(
|
| 222 |
+
self,
|
| 223 |
+
value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]],
|
| 224 |
+
state: "ParsingState",
|
| 225 |
+
) -> None:
|
| 226 |
+
if self.nargs > 1:
|
| 227 |
+
assert value is not None
|
| 228 |
+
holes = sum(1 for x in value if x is None)
|
| 229 |
+
if holes == len(value):
|
| 230 |
+
value = None
|
| 231 |
+
elif holes != 0:
|
| 232 |
+
raise BadArgumentUsage(
|
| 233 |
+
_("Argument {name!r} takes {nargs} values.").format(
|
| 234 |
+
name=self.dest, nargs=self.nargs
|
| 235 |
+
)
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
if self.nargs == -1 and self.obj.envvar is not None and value == ():
|
| 239 |
+
# Replace empty tuple with None so that a value from the
|
| 240 |
+
# environment may be tried.
|
| 241 |
+
value = None
|
| 242 |
+
|
| 243 |
+
state.opts[self.dest] = value # type: ignore
|
| 244 |
+
state.order.append(self.obj)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
class ParsingState:
|
| 248 |
+
def __init__(self, rargs: t.List[str]) -> None:
|
| 249 |
+
self.opts: t.Dict[str, t.Any] = {}
|
| 250 |
+
self.largs: t.List[str] = []
|
| 251 |
+
self.rargs = rargs
|
| 252 |
+
self.order: t.List[CoreParameter] = []
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
class OptionParser:
|
| 256 |
+
"""The option parser is an internal class that is ultimately used to
|
| 257 |
+
parse options and arguments. It's modelled after optparse and brings
|
| 258 |
+
a similar but vastly simplified API. It should generally not be used
|
| 259 |
+
directly as the high level Click classes wrap it for you.
|
| 260 |
+
|
| 261 |
+
It's not nearly as extensible as optparse or argparse as it does not
|
| 262 |
+
implement features that are implemented on a higher level (such as
|
| 263 |
+
types or defaults).
|
| 264 |
+
|
| 265 |
+
:param ctx: optionally the :class:`~click.Context` where this parser
|
| 266 |
+
should go with.
|
| 267 |
+
"""
|
| 268 |
+
|
| 269 |
+
def __init__(self, ctx: t.Optional["Context"] = None) -> None:
|
| 270 |
+
#: The :class:`~click.Context` for this parser. This might be
|
| 271 |
+
#: `None` for some advanced use cases.
|
| 272 |
+
self.ctx = ctx
|
| 273 |
+
#: This controls how the parser deals with interspersed arguments.
|
| 274 |
+
#: If this is set to `False`, the parser will stop on the first
|
| 275 |
+
#: non-option. Click uses this to implement nested subcommands
|
| 276 |
+
#: safely.
|
| 277 |
+
self.allow_interspersed_args: bool = True
|
| 278 |
+
#: This tells the parser how to deal with unknown options. By
|
| 279 |
+
#: default it will error out (which is sensible), but there is a
|
| 280 |
+
#: second mode where it will ignore it and continue processing
|
| 281 |
+
#: after shifting all the unknown options into the resulting args.
|
| 282 |
+
self.ignore_unknown_options: bool = False
|
| 283 |
+
|
| 284 |
+
if ctx is not None:
|
| 285 |
+
self.allow_interspersed_args = ctx.allow_interspersed_args
|
| 286 |
+
self.ignore_unknown_options = ctx.ignore_unknown_options
|
| 287 |
+
|
| 288 |
+
self._short_opt: t.Dict[str, Option] = {}
|
| 289 |
+
self._long_opt: t.Dict[str, Option] = {}
|
| 290 |
+
self._opt_prefixes = {"-", "--"}
|
| 291 |
+
self._args: t.List[Argument] = []
|
| 292 |
+
|
| 293 |
+
def add_option(
|
| 294 |
+
self,
|
| 295 |
+
obj: "CoreOption",
|
| 296 |
+
opts: t.Sequence[str],
|
| 297 |
+
dest: t.Optional[str],
|
| 298 |
+
action: t.Optional[str] = None,
|
| 299 |
+
nargs: int = 1,
|
| 300 |
+
const: t.Optional[t.Any] = None,
|
| 301 |
+
) -> None:
|
| 302 |
+
"""Adds a new option named `dest` to the parser. The destination
|
| 303 |
+
is not inferred (unlike with optparse) and needs to be explicitly
|
| 304 |
+
provided. Action can be any of ``store``, ``store_const``,
|
| 305 |
+
``append``, ``append_const`` or ``count``.
|
| 306 |
+
|
| 307 |
+
The `obj` can be used to identify the option in the order list
|
| 308 |
+
that is returned from the parser.
|
| 309 |
+
"""
|
| 310 |
+
opts = [normalize_opt(opt, self.ctx) for opt in opts]
|
| 311 |
+
option = Option(obj, opts, dest, action=action, nargs=nargs, const=const)
|
| 312 |
+
self._opt_prefixes.update(option.prefixes)
|
| 313 |
+
for opt in option._short_opts:
|
| 314 |
+
self._short_opt[opt] = option
|
| 315 |
+
for opt in option._long_opts:
|
| 316 |
+
self._long_opt[opt] = option
|
| 317 |
+
|
| 318 |
+
def add_argument(
|
| 319 |
+
self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1
|
| 320 |
+
) -> None:
|
| 321 |
+
"""Adds a positional argument named `dest` to the parser.
|
| 322 |
+
|
| 323 |
+
The `obj` can be used to identify the option in the order list
|
| 324 |
+
that is returned from the parser.
|
| 325 |
+
"""
|
| 326 |
+
self._args.append(Argument(obj, dest=dest, nargs=nargs))
|
| 327 |
+
|
| 328 |
+
def parse_args(
|
| 329 |
+
self, args: t.List[str]
|
| 330 |
+
) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]:
|
| 331 |
+
"""Parses positional arguments and returns ``(values, args, order)``
|
| 332 |
+
for the parsed options and arguments as well as the leftover
|
| 333 |
+
arguments if there are any. The order is a list of objects as they
|
| 334 |
+
appear on the command line. If arguments appear multiple times they
|
| 335 |
+
will be memorized multiple times as well.
|
| 336 |
+
"""
|
| 337 |
+
state = ParsingState(args)
|
| 338 |
+
try:
|
| 339 |
+
self._process_args_for_options(state)
|
| 340 |
+
self._process_args_for_args(state)
|
| 341 |
+
except UsageError:
|
| 342 |
+
if self.ctx is None or not self.ctx.resilient_parsing:
|
| 343 |
+
raise
|
| 344 |
+
return state.opts, state.largs, state.order
|
| 345 |
+
|
| 346 |
+
def _process_args_for_args(self, state: ParsingState) -> None:
|
| 347 |
+
pargs, args = _unpack_args(
|
| 348 |
+
state.largs + state.rargs, [x.nargs for x in self._args]
|
| 349 |
+
)
|
| 350 |
+
|
| 351 |
+
for idx, arg in enumerate(self._args):
|
| 352 |
+
arg.process(pargs[idx], state)
|
| 353 |
+
|
| 354 |
+
state.largs = args
|
| 355 |
+
state.rargs = []
|
| 356 |
+
|
| 357 |
+
def _process_args_for_options(self, state: ParsingState) -> None:
|
| 358 |
+
while state.rargs:
|
| 359 |
+
arg = state.rargs.pop(0)
|
| 360 |
+
arglen = len(arg)
|
| 361 |
+
# Double dashes always handled explicitly regardless of what
|
| 362 |
+
# prefixes are valid.
|
| 363 |
+
if arg == "--":
|
| 364 |
+
return
|
| 365 |
+
elif arg[:1] in self._opt_prefixes and arglen > 1:
|
| 366 |
+
self._process_opts(arg, state)
|
| 367 |
+
elif self.allow_interspersed_args:
|
| 368 |
+
state.largs.append(arg)
|
| 369 |
+
else:
|
| 370 |
+
state.rargs.insert(0, arg)
|
| 371 |
+
return
|
| 372 |
+
|
| 373 |
+
# Say this is the original argument list:
|
| 374 |
+
# [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
|
| 375 |
+
# ^
|
| 376 |
+
# (we are about to process arg(i)).
|
| 377 |
+
#
|
| 378 |
+
# Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
|
| 379 |
+
# [arg0, ..., arg(i-1)] (any options and their arguments will have
|
| 380 |
+
# been removed from largs).
|
| 381 |
+
#
|
| 382 |
+
# The while loop will usually consume 1 or more arguments per pass.
|
| 383 |
+
# If it consumes 1 (eg. arg is an option that takes no arguments),
|
| 384 |
+
# then after _process_arg() is done the situation is:
|
| 385 |
+
#
|
| 386 |
+
# largs = subset of [arg0, ..., arg(i)]
|
| 387 |
+
# rargs = [arg(i+1), ..., arg(N-1)]
|
| 388 |
+
#
|
| 389 |
+
# If allow_interspersed_args is false, largs will always be
|
| 390 |
+
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
|
| 391 |
+
# not a very interesting subset!
|
| 392 |
+
|
| 393 |
+
def _match_long_opt(
|
| 394 |
+
self, opt: str, explicit_value: t.Optional[str], state: ParsingState
|
| 395 |
+
) -> None:
|
| 396 |
+
if opt not in self._long_opt:
|
| 397 |
+
from difflib import get_close_matches
|
| 398 |
+
|
| 399 |
+
possibilities = get_close_matches(opt, self._long_opt)
|
| 400 |
+
raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
|
| 401 |
+
|
| 402 |
+
option = self._long_opt[opt]
|
| 403 |
+
if option.takes_value:
|
| 404 |
+
# At this point it's safe to modify rargs by injecting the
|
| 405 |
+
# explicit value, because no exception is raised in this
|
| 406 |
+
# branch. This means that the inserted value will be fully
|
| 407 |
+
# consumed.
|
| 408 |
+
if explicit_value is not None:
|
| 409 |
+
state.rargs.insert(0, explicit_value)
|
| 410 |
+
|
| 411 |
+
value = self._get_value_from_state(opt, option, state)
|
| 412 |
+
|
| 413 |
+
elif explicit_value is not None:
|
| 414 |
+
raise BadOptionUsage(
|
| 415 |
+
opt, _("Option {name!r} does not take a value.").format(name=opt)
|
| 416 |
+
)
|
| 417 |
+
|
| 418 |
+
else:
|
| 419 |
+
value = None
|
| 420 |
+
|
| 421 |
+
option.process(value, state)
|
| 422 |
+
|
| 423 |
+
def _match_short_opt(self, arg: str, state: ParsingState) -> None:
|
| 424 |
+
stop = False
|
| 425 |
+
i = 1
|
| 426 |
+
prefix = arg[0]
|
| 427 |
+
unknown_options = []
|
| 428 |
+
|
| 429 |
+
for ch in arg[1:]:
|
| 430 |
+
opt = normalize_opt(f"{prefix}{ch}", self.ctx)
|
| 431 |
+
option = self._short_opt.get(opt)
|
| 432 |
+
i += 1
|
| 433 |
+
|
| 434 |
+
if not option:
|
| 435 |
+
if self.ignore_unknown_options:
|
| 436 |
+
unknown_options.append(ch)
|
| 437 |
+
continue
|
| 438 |
+
raise NoSuchOption(opt, ctx=self.ctx)
|
| 439 |
+
if option.takes_value:
|
| 440 |
+
# Any characters left in arg? Pretend they're the
|
| 441 |
+
# next arg, and stop consuming characters of arg.
|
| 442 |
+
if i < len(arg):
|
| 443 |
+
state.rargs.insert(0, arg[i:])
|
| 444 |
+
stop = True
|
| 445 |
+
|
| 446 |
+
value = self._get_value_from_state(opt, option, state)
|
| 447 |
+
|
| 448 |
+
else:
|
| 449 |
+
value = None
|
| 450 |
+
|
| 451 |
+
option.process(value, state)
|
| 452 |
+
|
| 453 |
+
if stop:
|
| 454 |
+
break
|
| 455 |
+
|
| 456 |
+
# If we got any unknown options we recombine the string of the
|
| 457 |
+
# remaining options and re-attach the prefix, then report that
|
| 458 |
+
# to the state as new larg. This way there is basic combinatorics
|
| 459 |
+
# that can be achieved while still ignoring unknown arguments.
|
| 460 |
+
if self.ignore_unknown_options and unknown_options:
|
| 461 |
+
state.largs.append(f"{prefix}{''.join(unknown_options)}")
|
| 462 |
+
|
| 463 |
+
def _get_value_from_state(
|
| 464 |
+
self, option_name: str, option: Option, state: ParsingState
|
| 465 |
+
) -> t.Any:
|
| 466 |
+
nargs = option.nargs
|
| 467 |
+
|
| 468 |
+
if len(state.rargs) < nargs:
|
| 469 |
+
if option.obj._flag_needs_value:
|
| 470 |
+
# Option allows omitting the value.
|
| 471 |
+
value = _flag_needs_value
|
| 472 |
+
else:
|
| 473 |
+
raise BadOptionUsage(
|
| 474 |
+
option_name,
|
| 475 |
+
ngettext(
|
| 476 |
+
"Option {name!r} requires an argument.",
|
| 477 |
+
"Option {name!r} requires {nargs} arguments.",
|
| 478 |
+
nargs,
|
| 479 |
+
).format(name=option_name, nargs=nargs),
|
| 480 |
+
)
|
| 481 |
+
elif nargs == 1:
|
| 482 |
+
next_rarg = state.rargs[0]
|
| 483 |
+
|
| 484 |
+
if (
|
| 485 |
+
option.obj._flag_needs_value
|
| 486 |
+
and isinstance(next_rarg, str)
|
| 487 |
+
and next_rarg[:1] in self._opt_prefixes
|
| 488 |
+
and len(next_rarg) > 1
|
| 489 |
+
):
|
| 490 |
+
# The next arg looks like the start of an option, don't
|
| 491 |
+
# use it as the value if omitting the value is allowed.
|
| 492 |
+
value = _flag_needs_value
|
| 493 |
+
else:
|
| 494 |
+
value = state.rargs.pop(0)
|
| 495 |
+
else:
|
| 496 |
+
value = tuple(state.rargs[:nargs])
|
| 497 |
+
del state.rargs[:nargs]
|
| 498 |
+
|
| 499 |
+
return value
|
| 500 |
+
|
| 501 |
+
def _process_opts(self, arg: str, state: ParsingState) -> None:
|
| 502 |
+
explicit_value = None
|
| 503 |
+
# Long option handling happens in two parts. The first part is
|
| 504 |
+
# supporting explicitly attached values. In any case, we will try
|
| 505 |
+
# to long match the option first.
|
| 506 |
+
if "=" in arg:
|
| 507 |
+
long_opt, explicit_value = arg.split("=", 1)
|
| 508 |
+
else:
|
| 509 |
+
long_opt = arg
|
| 510 |
+
norm_long_opt = normalize_opt(long_opt, self.ctx)
|
| 511 |
+
|
| 512 |
+
# At this point we will match the (assumed) long option through
|
| 513 |
+
# the long option matching code. Note that this allows options
|
| 514 |
+
# like "-foo" to be matched as long options.
|
| 515 |
+
try:
|
| 516 |
+
self._match_long_opt(norm_long_opt, explicit_value, state)
|
| 517 |
+
except NoSuchOption:
|
| 518 |
+
# At this point the long option matching failed, and we need
|
| 519 |
+
# to try with short options. However there is a special rule
|
| 520 |
+
# which says, that if we have a two character options prefix
|
| 521 |
+
# (applies to "--foo" for instance), we do not dispatch to the
|
| 522 |
+
# short option code and will instead raise the no option
|
| 523 |
+
# error.
|
| 524 |
+
if arg[:2] not in self._opt_prefixes:
|
| 525 |
+
self._match_short_opt(arg, state)
|
| 526 |
+
return
|
| 527 |
+
|
| 528 |
+
if not self.ignore_unknown_options:
|
| 529 |
+
raise
|
| 530 |
+
|
| 531 |
+
state.largs.append(arg)
|
.venv/lib/python3.11/site-packages/click/testing.py
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
import io
|
| 3 |
+
import os
|
| 4 |
+
import shlex
|
| 5 |
+
import shutil
|
| 6 |
+
import sys
|
| 7 |
+
import tempfile
|
| 8 |
+
import typing as t
|
| 9 |
+
from types import TracebackType
|
| 10 |
+
|
| 11 |
+
from . import _compat
|
| 12 |
+
from . import formatting
|
| 13 |
+
from . import termui
|
| 14 |
+
from . import utils
|
| 15 |
+
from ._compat import _find_binary_reader
|
| 16 |
+
|
| 17 |
+
if t.TYPE_CHECKING:
|
| 18 |
+
from .core import BaseCommand
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class EchoingStdin:
|
| 22 |
+
def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None:
|
| 23 |
+
self._input = input
|
| 24 |
+
self._output = output
|
| 25 |
+
self._paused = False
|
| 26 |
+
|
| 27 |
+
def __getattr__(self, x: str) -> t.Any:
|
| 28 |
+
return getattr(self._input, x)
|
| 29 |
+
|
| 30 |
+
def _echo(self, rv: bytes) -> bytes:
|
| 31 |
+
if not self._paused:
|
| 32 |
+
self._output.write(rv)
|
| 33 |
+
|
| 34 |
+
return rv
|
| 35 |
+
|
| 36 |
+
def read(self, n: int = -1) -> bytes:
|
| 37 |
+
return self._echo(self._input.read(n))
|
| 38 |
+
|
| 39 |
+
def read1(self, n: int = -1) -> bytes:
|
| 40 |
+
return self._echo(self._input.read1(n)) # type: ignore
|
| 41 |
+
|
| 42 |
+
def readline(self, n: int = -1) -> bytes:
|
| 43 |
+
return self._echo(self._input.readline(n))
|
| 44 |
+
|
| 45 |
+
def readlines(self) -> t.List[bytes]:
|
| 46 |
+
return [self._echo(x) for x in self._input.readlines()]
|
| 47 |
+
|
| 48 |
+
def __iter__(self) -> t.Iterator[bytes]:
|
| 49 |
+
return iter(self._echo(x) for x in self._input)
|
| 50 |
+
|
| 51 |
+
def __repr__(self) -> str:
|
| 52 |
+
return repr(self._input)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@contextlib.contextmanager
|
| 56 |
+
def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]:
|
| 57 |
+
if stream is None:
|
| 58 |
+
yield
|
| 59 |
+
else:
|
| 60 |
+
stream._paused = True
|
| 61 |
+
yield
|
| 62 |
+
stream._paused = False
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class _NamedTextIOWrapper(io.TextIOWrapper):
|
| 66 |
+
def __init__(
|
| 67 |
+
self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any
|
| 68 |
+
) -> None:
|
| 69 |
+
super().__init__(buffer, **kwargs)
|
| 70 |
+
self._name = name
|
| 71 |
+
self._mode = mode
|
| 72 |
+
|
| 73 |
+
@property
|
| 74 |
+
def name(self) -> str:
|
| 75 |
+
return self._name
|
| 76 |
+
|
| 77 |
+
@property
|
| 78 |
+
def mode(self) -> str:
|
| 79 |
+
return self._mode
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def make_input_stream(
|
| 83 |
+
input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]], charset: str
|
| 84 |
+
) -> t.BinaryIO:
|
| 85 |
+
# Is already an input stream.
|
| 86 |
+
if hasattr(input, "read"):
|
| 87 |
+
rv = _find_binary_reader(t.cast(t.IO[t.Any], input))
|
| 88 |
+
|
| 89 |
+
if rv is not None:
|
| 90 |
+
return rv
|
| 91 |
+
|
| 92 |
+
raise TypeError("Could not find binary reader for input stream.")
|
| 93 |
+
|
| 94 |
+
if input is None:
|
| 95 |
+
input = b""
|
| 96 |
+
elif isinstance(input, str):
|
| 97 |
+
input = input.encode(charset)
|
| 98 |
+
|
| 99 |
+
return io.BytesIO(input)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class Result:
|
| 103 |
+
"""Holds the captured result of an invoked CLI script."""
|
| 104 |
+
|
| 105 |
+
def __init__(
|
| 106 |
+
self,
|
| 107 |
+
runner: "CliRunner",
|
| 108 |
+
stdout_bytes: bytes,
|
| 109 |
+
stderr_bytes: t.Optional[bytes],
|
| 110 |
+
return_value: t.Any,
|
| 111 |
+
exit_code: int,
|
| 112 |
+
exception: t.Optional[BaseException],
|
| 113 |
+
exc_info: t.Optional[
|
| 114 |
+
t.Tuple[t.Type[BaseException], BaseException, TracebackType]
|
| 115 |
+
] = None,
|
| 116 |
+
):
|
| 117 |
+
#: The runner that created the result
|
| 118 |
+
self.runner = runner
|
| 119 |
+
#: The standard output as bytes.
|
| 120 |
+
self.stdout_bytes = stdout_bytes
|
| 121 |
+
#: The standard error as bytes, or None if not available
|
| 122 |
+
self.stderr_bytes = stderr_bytes
|
| 123 |
+
#: The value returned from the invoked command.
|
| 124 |
+
#:
|
| 125 |
+
#: .. versionadded:: 8.0
|
| 126 |
+
self.return_value = return_value
|
| 127 |
+
#: The exit code as integer.
|
| 128 |
+
self.exit_code = exit_code
|
| 129 |
+
#: The exception that happened if one did.
|
| 130 |
+
self.exception = exception
|
| 131 |
+
#: The traceback
|
| 132 |
+
self.exc_info = exc_info
|
| 133 |
+
|
| 134 |
+
@property
|
| 135 |
+
def output(self) -> str:
|
| 136 |
+
"""The (standard) output as unicode string."""
|
| 137 |
+
return self.stdout
|
| 138 |
+
|
| 139 |
+
@property
|
| 140 |
+
def stdout(self) -> str:
|
| 141 |
+
"""The standard output as unicode string."""
|
| 142 |
+
return self.stdout_bytes.decode(self.runner.charset, "replace").replace(
|
| 143 |
+
"\r\n", "\n"
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
@property
|
| 147 |
+
def stderr(self) -> str:
|
| 148 |
+
"""The standard error as unicode string."""
|
| 149 |
+
if self.stderr_bytes is None:
|
| 150 |
+
raise ValueError("stderr not separately captured")
|
| 151 |
+
return self.stderr_bytes.decode(self.runner.charset, "replace").replace(
|
| 152 |
+
"\r\n", "\n"
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
def __repr__(self) -> str:
|
| 156 |
+
exc_str = repr(self.exception) if self.exception else "okay"
|
| 157 |
+
return f"<{type(self).__name__} {exc_str}>"
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class CliRunner:
|
| 161 |
+
"""The CLI runner provides functionality to invoke a Click command line
|
| 162 |
+
script for unittesting purposes in a isolated environment. This only
|
| 163 |
+
works in single-threaded systems without any concurrency as it changes the
|
| 164 |
+
global interpreter state.
|
| 165 |
+
|
| 166 |
+
:param charset: the character set for the input and output data.
|
| 167 |
+
:param env: a dictionary with environment variables for overriding.
|
| 168 |
+
:param echo_stdin: if this is set to `True`, then reading from stdin writes
|
| 169 |
+
to stdout. This is useful for showing examples in
|
| 170 |
+
some circumstances. Note that regular prompts
|
| 171 |
+
will automatically echo the input.
|
| 172 |
+
:param mix_stderr: if this is set to `False`, then stdout and stderr are
|
| 173 |
+
preserved as independent streams. This is useful for
|
| 174 |
+
Unix-philosophy apps that have predictable stdout and
|
| 175 |
+
noisy stderr, such that each may be measured
|
| 176 |
+
independently
|
| 177 |
+
"""
|
| 178 |
+
|
| 179 |
+
def __init__(
|
| 180 |
+
self,
|
| 181 |
+
charset: str = "utf-8",
|
| 182 |
+
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
| 183 |
+
echo_stdin: bool = False,
|
| 184 |
+
mix_stderr: bool = True,
|
| 185 |
+
) -> None:
|
| 186 |
+
self.charset = charset
|
| 187 |
+
self.env: t.Mapping[str, t.Optional[str]] = env or {}
|
| 188 |
+
self.echo_stdin = echo_stdin
|
| 189 |
+
self.mix_stderr = mix_stderr
|
| 190 |
+
|
| 191 |
+
def get_default_prog_name(self, cli: "BaseCommand") -> str:
|
| 192 |
+
"""Given a command object it will return the default program name
|
| 193 |
+
for it. The default is the `name` attribute or ``"root"`` if not
|
| 194 |
+
set.
|
| 195 |
+
"""
|
| 196 |
+
return cli.name or "root"
|
| 197 |
+
|
| 198 |
+
def make_env(
|
| 199 |
+
self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None
|
| 200 |
+
) -> t.Mapping[str, t.Optional[str]]:
|
| 201 |
+
"""Returns the environment overrides for invoking a script."""
|
| 202 |
+
rv = dict(self.env)
|
| 203 |
+
if overrides:
|
| 204 |
+
rv.update(overrides)
|
| 205 |
+
return rv
|
| 206 |
+
|
| 207 |
+
@contextlib.contextmanager
|
| 208 |
+
def isolation(
|
| 209 |
+
self,
|
| 210 |
+
input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,
|
| 211 |
+
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
| 212 |
+
color: bool = False,
|
| 213 |
+
) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]:
|
| 214 |
+
"""A context manager that sets up the isolation for invoking of a
|
| 215 |
+
command line tool. This sets up stdin with the given input data
|
| 216 |
+
and `os.environ` with the overrides from the given dictionary.
|
| 217 |
+
This also rebinds some internals in Click to be mocked (like the
|
| 218 |
+
prompt functionality).
|
| 219 |
+
|
| 220 |
+
This is automatically done in the :meth:`invoke` method.
|
| 221 |
+
|
| 222 |
+
:param input: the input stream to put into sys.stdin.
|
| 223 |
+
:param env: the environment overrides as dictionary.
|
| 224 |
+
:param color: whether the output should contain color codes. The
|
| 225 |
+
application can still override this explicitly.
|
| 226 |
+
|
| 227 |
+
.. versionchanged:: 8.0
|
| 228 |
+
``stderr`` is opened with ``errors="backslashreplace"``
|
| 229 |
+
instead of the default ``"strict"``.
|
| 230 |
+
|
| 231 |
+
.. versionchanged:: 4.0
|
| 232 |
+
Added the ``color`` parameter.
|
| 233 |
+
"""
|
| 234 |
+
bytes_input = make_input_stream(input, self.charset)
|
| 235 |
+
echo_input = None
|
| 236 |
+
|
| 237 |
+
old_stdin = sys.stdin
|
| 238 |
+
old_stdout = sys.stdout
|
| 239 |
+
old_stderr = sys.stderr
|
| 240 |
+
old_forced_width = formatting.FORCED_WIDTH
|
| 241 |
+
formatting.FORCED_WIDTH = 80
|
| 242 |
+
|
| 243 |
+
env = self.make_env(env)
|
| 244 |
+
|
| 245 |
+
bytes_output = io.BytesIO()
|
| 246 |
+
|
| 247 |
+
if self.echo_stdin:
|
| 248 |
+
bytes_input = echo_input = t.cast(
|
| 249 |
+
t.BinaryIO, EchoingStdin(bytes_input, bytes_output)
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
sys.stdin = text_input = _NamedTextIOWrapper(
|
| 253 |
+
bytes_input, encoding=self.charset, name="<stdin>", mode="r"
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
if self.echo_stdin:
|
| 257 |
+
# Force unbuffered reads, otherwise TextIOWrapper reads a
|
| 258 |
+
# large chunk which is echoed early.
|
| 259 |
+
text_input._CHUNK_SIZE = 1 # type: ignore
|
| 260 |
+
|
| 261 |
+
sys.stdout = _NamedTextIOWrapper(
|
| 262 |
+
bytes_output, encoding=self.charset, name="<stdout>", mode="w"
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
bytes_error = None
|
| 266 |
+
if self.mix_stderr:
|
| 267 |
+
sys.stderr = sys.stdout
|
| 268 |
+
else:
|
| 269 |
+
bytes_error = io.BytesIO()
|
| 270 |
+
sys.stderr = _NamedTextIOWrapper(
|
| 271 |
+
bytes_error,
|
| 272 |
+
encoding=self.charset,
|
| 273 |
+
name="<stderr>",
|
| 274 |
+
mode="w",
|
| 275 |
+
errors="backslashreplace",
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
@_pause_echo(echo_input) # type: ignore
|
| 279 |
+
def visible_input(prompt: t.Optional[str] = None) -> str:
|
| 280 |
+
sys.stdout.write(prompt or "")
|
| 281 |
+
val = text_input.readline().rstrip("\r\n")
|
| 282 |
+
sys.stdout.write(f"{val}\n")
|
| 283 |
+
sys.stdout.flush()
|
| 284 |
+
return val
|
| 285 |
+
|
| 286 |
+
@_pause_echo(echo_input) # type: ignore
|
| 287 |
+
def hidden_input(prompt: t.Optional[str] = None) -> str:
|
| 288 |
+
sys.stdout.write(f"{prompt or ''}\n")
|
| 289 |
+
sys.stdout.flush()
|
| 290 |
+
return text_input.readline().rstrip("\r\n")
|
| 291 |
+
|
| 292 |
+
@_pause_echo(echo_input) # type: ignore
|
| 293 |
+
def _getchar(echo: bool) -> str:
|
| 294 |
+
char = sys.stdin.read(1)
|
| 295 |
+
|
| 296 |
+
if echo:
|
| 297 |
+
sys.stdout.write(char)
|
| 298 |
+
|
| 299 |
+
sys.stdout.flush()
|
| 300 |
+
return char
|
| 301 |
+
|
| 302 |
+
default_color = color
|
| 303 |
+
|
| 304 |
+
def should_strip_ansi(
|
| 305 |
+
stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None
|
| 306 |
+
) -> bool:
|
| 307 |
+
if color is None:
|
| 308 |
+
return not default_color
|
| 309 |
+
return not color
|
| 310 |
+
|
| 311 |
+
old_visible_prompt_func = termui.visible_prompt_func
|
| 312 |
+
old_hidden_prompt_func = termui.hidden_prompt_func
|
| 313 |
+
old__getchar_func = termui._getchar
|
| 314 |
+
old_should_strip_ansi = utils.should_strip_ansi # type: ignore
|
| 315 |
+
old__compat_should_strip_ansi = _compat.should_strip_ansi
|
| 316 |
+
termui.visible_prompt_func = visible_input
|
| 317 |
+
termui.hidden_prompt_func = hidden_input
|
| 318 |
+
termui._getchar = _getchar
|
| 319 |
+
utils.should_strip_ansi = should_strip_ansi # type: ignore
|
| 320 |
+
_compat.should_strip_ansi = should_strip_ansi
|
| 321 |
+
|
| 322 |
+
old_env = {}
|
| 323 |
+
try:
|
| 324 |
+
for key, value in env.items():
|
| 325 |
+
old_env[key] = os.environ.get(key)
|
| 326 |
+
if value is None:
|
| 327 |
+
try:
|
| 328 |
+
del os.environ[key]
|
| 329 |
+
except Exception:
|
| 330 |
+
pass
|
| 331 |
+
else:
|
| 332 |
+
os.environ[key] = value
|
| 333 |
+
yield (bytes_output, bytes_error)
|
| 334 |
+
finally:
|
| 335 |
+
for key, value in old_env.items():
|
| 336 |
+
if value is None:
|
| 337 |
+
try:
|
| 338 |
+
del os.environ[key]
|
| 339 |
+
except Exception:
|
| 340 |
+
pass
|
| 341 |
+
else:
|
| 342 |
+
os.environ[key] = value
|
| 343 |
+
sys.stdout = old_stdout
|
| 344 |
+
sys.stderr = old_stderr
|
| 345 |
+
sys.stdin = old_stdin
|
| 346 |
+
termui.visible_prompt_func = old_visible_prompt_func
|
| 347 |
+
termui.hidden_prompt_func = old_hidden_prompt_func
|
| 348 |
+
termui._getchar = old__getchar_func
|
| 349 |
+
utils.should_strip_ansi = old_should_strip_ansi # type: ignore
|
| 350 |
+
_compat.should_strip_ansi = old__compat_should_strip_ansi
|
| 351 |
+
formatting.FORCED_WIDTH = old_forced_width
|
| 352 |
+
|
| 353 |
+
def invoke(
|
| 354 |
+
self,
|
| 355 |
+
cli: "BaseCommand",
|
| 356 |
+
args: t.Optional[t.Union[str, t.Sequence[str]]] = None,
|
| 357 |
+
input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,
|
| 358 |
+
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
| 359 |
+
catch_exceptions: bool = True,
|
| 360 |
+
color: bool = False,
|
| 361 |
+
**extra: t.Any,
|
| 362 |
+
) -> Result:
|
| 363 |
+
"""Invokes a command in an isolated environment. The arguments are
|
| 364 |
+
forwarded directly to the command line script, the `extra` keyword
|
| 365 |
+
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
| 366 |
+
the command.
|
| 367 |
+
|
| 368 |
+
This returns a :class:`Result` object.
|
| 369 |
+
|
| 370 |
+
:param cli: the command to invoke
|
| 371 |
+
:param args: the arguments to invoke. It may be given as an iterable
|
| 372 |
+
or a string. When given as string it will be interpreted
|
| 373 |
+
as a Unix shell command. More details at
|
| 374 |
+
:func:`shlex.split`.
|
| 375 |
+
:param input: the input data for `sys.stdin`.
|
| 376 |
+
:param env: the environment overrides.
|
| 377 |
+
:param catch_exceptions: Whether to catch any other exceptions than
|
| 378 |
+
``SystemExit``.
|
| 379 |
+
:param extra: the keyword arguments to pass to :meth:`main`.
|
| 380 |
+
:param color: whether the output should contain color codes. The
|
| 381 |
+
application can still override this explicitly.
|
| 382 |
+
|
| 383 |
+
.. versionchanged:: 8.0
|
| 384 |
+
The result object has the ``return_value`` attribute with
|
| 385 |
+
the value returned from the invoked command.
|
| 386 |
+
|
| 387 |
+
.. versionchanged:: 4.0
|
| 388 |
+
Added the ``color`` parameter.
|
| 389 |
+
|
| 390 |
+
.. versionchanged:: 3.0
|
| 391 |
+
Added the ``catch_exceptions`` parameter.
|
| 392 |
+
|
| 393 |
+
.. versionchanged:: 3.0
|
| 394 |
+
The result object has the ``exc_info`` attribute with the
|
| 395 |
+
traceback if available.
|
| 396 |
+
"""
|
| 397 |
+
exc_info = None
|
| 398 |
+
with self.isolation(input=input, env=env, color=color) as outstreams:
|
| 399 |
+
return_value = None
|
| 400 |
+
exception: t.Optional[BaseException] = None
|
| 401 |
+
exit_code = 0
|
| 402 |
+
|
| 403 |
+
if isinstance(args, str):
|
| 404 |
+
args = shlex.split(args)
|
| 405 |
+
|
| 406 |
+
try:
|
| 407 |
+
prog_name = extra.pop("prog_name")
|
| 408 |
+
except KeyError:
|
| 409 |
+
prog_name = self.get_default_prog_name(cli)
|
| 410 |
+
|
| 411 |
+
try:
|
| 412 |
+
return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
|
| 413 |
+
except SystemExit as e:
|
| 414 |
+
exc_info = sys.exc_info()
|
| 415 |
+
e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code)
|
| 416 |
+
|
| 417 |
+
if e_code is None:
|
| 418 |
+
e_code = 0
|
| 419 |
+
|
| 420 |
+
if e_code != 0:
|
| 421 |
+
exception = e
|
| 422 |
+
|
| 423 |
+
if not isinstance(e_code, int):
|
| 424 |
+
sys.stdout.write(str(e_code))
|
| 425 |
+
sys.stdout.write("\n")
|
| 426 |
+
e_code = 1
|
| 427 |
+
|
| 428 |
+
exit_code = e_code
|
| 429 |
+
|
| 430 |
+
except Exception as e:
|
| 431 |
+
if not catch_exceptions:
|
| 432 |
+
raise
|
| 433 |
+
exception = e
|
| 434 |
+
exit_code = 1
|
| 435 |
+
exc_info = sys.exc_info()
|
| 436 |
+
finally:
|
| 437 |
+
sys.stdout.flush()
|
| 438 |
+
stdout = outstreams[0].getvalue()
|
| 439 |
+
if self.mix_stderr:
|
| 440 |
+
stderr = None
|
| 441 |
+
else:
|
| 442 |
+
stderr = outstreams[1].getvalue() # type: ignore
|
| 443 |
+
|
| 444 |
+
return Result(
|
| 445 |
+
runner=self,
|
| 446 |
+
stdout_bytes=stdout,
|
| 447 |
+
stderr_bytes=stderr,
|
| 448 |
+
return_value=return_value,
|
| 449 |
+
exit_code=exit_code,
|
| 450 |
+
exception=exception,
|
| 451 |
+
exc_info=exc_info, # type: ignore
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
@contextlib.contextmanager
|
| 455 |
+
def isolated_filesystem(
|
| 456 |
+
self, temp_dir: t.Optional[t.Union[str, "os.PathLike[str]"]] = None
|
| 457 |
+
) -> t.Iterator[str]:
|
| 458 |
+
"""A context manager that creates a temporary directory and
|
| 459 |
+
changes the current working directory to it. This isolates tests
|
| 460 |
+
that affect the contents of the CWD to prevent them from
|
| 461 |
+
interfering with each other.
|
| 462 |
+
|
| 463 |
+
:param temp_dir: Create the temporary directory under this
|
| 464 |
+
directory. If given, the created directory is not removed
|
| 465 |
+
when exiting.
|
| 466 |
+
|
| 467 |
+
.. versionchanged:: 8.0
|
| 468 |
+
Added the ``temp_dir`` parameter.
|
| 469 |
+
"""
|
| 470 |
+
cwd = os.getcwd()
|
| 471 |
+
dt = tempfile.mkdtemp(dir=temp_dir)
|
| 472 |
+
os.chdir(dt)
|
| 473 |
+
|
| 474 |
+
try:
|
| 475 |
+
yield dt
|
| 476 |
+
finally:
|
| 477 |
+
os.chdir(cwd)
|
| 478 |
+
|
| 479 |
+
if temp_dir is None:
|
| 480 |
+
try:
|
| 481 |
+
shutil.rmtree(dt)
|
| 482 |
+
except OSError:
|
| 483 |
+
pass
|
.venv/lib/python3.11/site-packages/click/types.py
ADDED
|
@@ -0,0 +1,1093 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import stat
|
| 3 |
+
import sys
|
| 4 |
+
import typing as t
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from gettext import gettext as _
|
| 7 |
+
from gettext import ngettext
|
| 8 |
+
|
| 9 |
+
from ._compat import _get_argv_encoding
|
| 10 |
+
from ._compat import open_stream
|
| 11 |
+
from .exceptions import BadParameter
|
| 12 |
+
from .utils import format_filename
|
| 13 |
+
from .utils import LazyFile
|
| 14 |
+
from .utils import safecall
|
| 15 |
+
|
| 16 |
+
if t.TYPE_CHECKING:
|
| 17 |
+
import typing_extensions as te
|
| 18 |
+
|
| 19 |
+
from .core import Context
|
| 20 |
+
from .core import Parameter
|
| 21 |
+
from .shell_completion import CompletionItem
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class ParamType:
|
| 25 |
+
"""Represents the type of a parameter. Validates and converts values
|
| 26 |
+
from the command line or Python into the correct type.
|
| 27 |
+
|
| 28 |
+
To implement a custom type, subclass and implement at least the
|
| 29 |
+
following:
|
| 30 |
+
|
| 31 |
+
- The :attr:`name` class attribute must be set.
|
| 32 |
+
- Calling an instance of the type with ``None`` must return
|
| 33 |
+
``None``. This is already implemented by default.
|
| 34 |
+
- :meth:`convert` must convert string values to the correct type.
|
| 35 |
+
- :meth:`convert` must accept values that are already the correct
|
| 36 |
+
type.
|
| 37 |
+
- It must be able to convert a value if the ``ctx`` and ``param``
|
| 38 |
+
arguments are ``None``. This can occur when converting prompt
|
| 39 |
+
input.
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
is_composite: t.ClassVar[bool] = False
|
| 43 |
+
arity: t.ClassVar[int] = 1
|
| 44 |
+
|
| 45 |
+
#: the descriptive name of this type
|
| 46 |
+
name: str
|
| 47 |
+
|
| 48 |
+
#: if a list of this type is expected and the value is pulled from a
|
| 49 |
+
#: string environment variable, this is what splits it up. `None`
|
| 50 |
+
#: means any whitespace. For all parameters the general rule is that
|
| 51 |
+
#: whitespace splits them up. The exception are paths and files which
|
| 52 |
+
#: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
|
| 53 |
+
#: Windows).
|
| 54 |
+
envvar_list_splitter: t.ClassVar[t.Optional[str]] = None
|
| 55 |
+
|
| 56 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 57 |
+
"""Gather information that could be useful for a tool generating
|
| 58 |
+
user-facing documentation.
|
| 59 |
+
|
| 60 |
+
Use :meth:`click.Context.to_info_dict` to traverse the entire
|
| 61 |
+
CLI structure.
|
| 62 |
+
|
| 63 |
+
.. versionadded:: 8.0
|
| 64 |
+
"""
|
| 65 |
+
# The class name without the "ParamType" suffix.
|
| 66 |
+
param_type = type(self).__name__.partition("ParamType")[0]
|
| 67 |
+
param_type = param_type.partition("ParameterType")[0]
|
| 68 |
+
|
| 69 |
+
# Custom subclasses might not remember to set a name.
|
| 70 |
+
if hasattr(self, "name"):
|
| 71 |
+
name = self.name
|
| 72 |
+
else:
|
| 73 |
+
name = param_type
|
| 74 |
+
|
| 75 |
+
return {"param_type": param_type, "name": name}
|
| 76 |
+
|
| 77 |
+
def __call__(
|
| 78 |
+
self,
|
| 79 |
+
value: t.Any,
|
| 80 |
+
param: t.Optional["Parameter"] = None,
|
| 81 |
+
ctx: t.Optional["Context"] = None,
|
| 82 |
+
) -> t.Any:
|
| 83 |
+
if value is not None:
|
| 84 |
+
return self.convert(value, param, ctx)
|
| 85 |
+
|
| 86 |
+
def get_metavar(self, param: "Parameter") -> t.Optional[str]:
|
| 87 |
+
"""Returns the metavar default for this param if it provides one."""
|
| 88 |
+
|
| 89 |
+
def get_missing_message(self, param: "Parameter") -> t.Optional[str]:
|
| 90 |
+
"""Optionally might return extra information about a missing
|
| 91 |
+
parameter.
|
| 92 |
+
|
| 93 |
+
.. versionadded:: 2.0
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
def convert(
|
| 97 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 98 |
+
) -> t.Any:
|
| 99 |
+
"""Convert the value to the correct type. This is not called if
|
| 100 |
+
the value is ``None`` (the missing value).
|
| 101 |
+
|
| 102 |
+
This must accept string values from the command line, as well as
|
| 103 |
+
values that are already the correct type. It may also convert
|
| 104 |
+
other compatible types.
|
| 105 |
+
|
| 106 |
+
The ``param`` and ``ctx`` arguments may be ``None`` in certain
|
| 107 |
+
situations, such as when converting prompt input.
|
| 108 |
+
|
| 109 |
+
If the value cannot be converted, call :meth:`fail` with a
|
| 110 |
+
descriptive message.
|
| 111 |
+
|
| 112 |
+
:param value: The value to convert.
|
| 113 |
+
:param param: The parameter that is using this type to convert
|
| 114 |
+
its value. May be ``None``.
|
| 115 |
+
:param ctx: The current context that arrived at this value. May
|
| 116 |
+
be ``None``.
|
| 117 |
+
"""
|
| 118 |
+
return value
|
| 119 |
+
|
| 120 |
+
def split_envvar_value(self, rv: str) -> t.Sequence[str]:
|
| 121 |
+
"""Given a value from an environment variable this splits it up
|
| 122 |
+
into small chunks depending on the defined envvar list splitter.
|
| 123 |
+
|
| 124 |
+
If the splitter is set to `None`, which means that whitespace splits,
|
| 125 |
+
then leading and trailing whitespace is ignored. Otherwise, leading
|
| 126 |
+
and trailing splitters usually lead to empty items being included.
|
| 127 |
+
"""
|
| 128 |
+
return (rv or "").split(self.envvar_list_splitter)
|
| 129 |
+
|
| 130 |
+
def fail(
|
| 131 |
+
self,
|
| 132 |
+
message: str,
|
| 133 |
+
param: t.Optional["Parameter"] = None,
|
| 134 |
+
ctx: t.Optional["Context"] = None,
|
| 135 |
+
) -> "t.NoReturn":
|
| 136 |
+
"""Helper method to fail with an invalid value message."""
|
| 137 |
+
raise BadParameter(message, ctx=ctx, param=param)
|
| 138 |
+
|
| 139 |
+
def shell_complete(
|
| 140 |
+
self, ctx: "Context", param: "Parameter", incomplete: str
|
| 141 |
+
) -> t.List["CompletionItem"]:
|
| 142 |
+
"""Return a list of
|
| 143 |
+
:class:`~click.shell_completion.CompletionItem` objects for the
|
| 144 |
+
incomplete value. Most types do not provide completions, but
|
| 145 |
+
some do, and this allows custom types to provide custom
|
| 146 |
+
completions as well.
|
| 147 |
+
|
| 148 |
+
:param ctx: Invocation context for this command.
|
| 149 |
+
:param param: The parameter that is requesting completion.
|
| 150 |
+
:param incomplete: Value being completed. May be empty.
|
| 151 |
+
|
| 152 |
+
.. versionadded:: 8.0
|
| 153 |
+
"""
|
| 154 |
+
return []
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class CompositeParamType(ParamType):
|
| 158 |
+
is_composite = True
|
| 159 |
+
|
| 160 |
+
@property
|
| 161 |
+
def arity(self) -> int: # type: ignore
|
| 162 |
+
raise NotImplementedError()
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
class FuncParamType(ParamType):
|
| 166 |
+
def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
|
| 167 |
+
self.name: str = func.__name__
|
| 168 |
+
self.func = func
|
| 169 |
+
|
| 170 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 171 |
+
info_dict = super().to_info_dict()
|
| 172 |
+
info_dict["func"] = self.func
|
| 173 |
+
return info_dict
|
| 174 |
+
|
| 175 |
+
def convert(
|
| 176 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 177 |
+
) -> t.Any:
|
| 178 |
+
try:
|
| 179 |
+
return self.func(value)
|
| 180 |
+
except ValueError:
|
| 181 |
+
try:
|
| 182 |
+
value = str(value)
|
| 183 |
+
except UnicodeError:
|
| 184 |
+
value = value.decode("utf-8", "replace")
|
| 185 |
+
|
| 186 |
+
self.fail(value, param, ctx)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
class UnprocessedParamType(ParamType):
|
| 190 |
+
name = "text"
|
| 191 |
+
|
| 192 |
+
def convert(
|
| 193 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 194 |
+
) -> t.Any:
|
| 195 |
+
return value
|
| 196 |
+
|
| 197 |
+
def __repr__(self) -> str:
|
| 198 |
+
return "UNPROCESSED"
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class StringParamType(ParamType):
|
| 202 |
+
name = "text"
|
| 203 |
+
|
| 204 |
+
def convert(
|
| 205 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 206 |
+
) -> t.Any:
|
| 207 |
+
if isinstance(value, bytes):
|
| 208 |
+
enc = _get_argv_encoding()
|
| 209 |
+
try:
|
| 210 |
+
value = value.decode(enc)
|
| 211 |
+
except UnicodeError:
|
| 212 |
+
fs_enc = sys.getfilesystemencoding()
|
| 213 |
+
if fs_enc != enc:
|
| 214 |
+
try:
|
| 215 |
+
value = value.decode(fs_enc)
|
| 216 |
+
except UnicodeError:
|
| 217 |
+
value = value.decode("utf-8", "replace")
|
| 218 |
+
else:
|
| 219 |
+
value = value.decode("utf-8", "replace")
|
| 220 |
+
return value
|
| 221 |
+
return str(value)
|
| 222 |
+
|
| 223 |
+
def __repr__(self) -> str:
|
| 224 |
+
return "STRING"
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
class Choice(ParamType):
|
| 228 |
+
"""The choice type allows a value to be checked against a fixed set
|
| 229 |
+
of supported values. All of these values have to be strings.
|
| 230 |
+
|
| 231 |
+
You should only pass a list or tuple of choices. Other iterables
|
| 232 |
+
(like generators) may lead to surprising results.
|
| 233 |
+
|
| 234 |
+
The resulting value will always be one of the originally passed choices
|
| 235 |
+
regardless of ``case_sensitive`` or any ``ctx.token_normalize_func``
|
| 236 |
+
being specified.
|
| 237 |
+
|
| 238 |
+
See :ref:`choice-opts` for an example.
|
| 239 |
+
|
| 240 |
+
:param case_sensitive: Set to false to make choices case
|
| 241 |
+
insensitive. Defaults to true.
|
| 242 |
+
"""
|
| 243 |
+
|
| 244 |
+
name = "choice"
|
| 245 |
+
|
| 246 |
+
def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None:
|
| 247 |
+
self.choices = choices
|
| 248 |
+
self.case_sensitive = case_sensitive
|
| 249 |
+
|
| 250 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 251 |
+
info_dict = super().to_info_dict()
|
| 252 |
+
info_dict["choices"] = self.choices
|
| 253 |
+
info_dict["case_sensitive"] = self.case_sensitive
|
| 254 |
+
return info_dict
|
| 255 |
+
|
| 256 |
+
def get_metavar(self, param: "Parameter") -> str:
|
| 257 |
+
choices_str = "|".join(self.choices)
|
| 258 |
+
|
| 259 |
+
# Use curly braces to indicate a required argument.
|
| 260 |
+
if param.required and param.param_type_name == "argument":
|
| 261 |
+
return f"{{{choices_str}}}"
|
| 262 |
+
|
| 263 |
+
# Use square braces to indicate an option or optional argument.
|
| 264 |
+
return f"[{choices_str}]"
|
| 265 |
+
|
| 266 |
+
def get_missing_message(self, param: "Parameter") -> str:
|
| 267 |
+
return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices))
|
| 268 |
+
|
| 269 |
+
def convert(
|
| 270 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 271 |
+
) -> t.Any:
|
| 272 |
+
# Match through normalization and case sensitivity
|
| 273 |
+
# first do token_normalize_func, then lowercase
|
| 274 |
+
# preserve original `value` to produce an accurate message in
|
| 275 |
+
# `self.fail`
|
| 276 |
+
normed_value = value
|
| 277 |
+
normed_choices = {choice: choice for choice in self.choices}
|
| 278 |
+
|
| 279 |
+
if ctx is not None and ctx.token_normalize_func is not None:
|
| 280 |
+
normed_value = ctx.token_normalize_func(value)
|
| 281 |
+
normed_choices = {
|
| 282 |
+
ctx.token_normalize_func(normed_choice): original
|
| 283 |
+
for normed_choice, original in normed_choices.items()
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
if not self.case_sensitive:
|
| 287 |
+
normed_value = normed_value.casefold()
|
| 288 |
+
normed_choices = {
|
| 289 |
+
normed_choice.casefold(): original
|
| 290 |
+
for normed_choice, original in normed_choices.items()
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
if normed_value in normed_choices:
|
| 294 |
+
return normed_choices[normed_value]
|
| 295 |
+
|
| 296 |
+
choices_str = ", ".join(map(repr, self.choices))
|
| 297 |
+
self.fail(
|
| 298 |
+
ngettext(
|
| 299 |
+
"{value!r} is not {choice}.",
|
| 300 |
+
"{value!r} is not one of {choices}.",
|
| 301 |
+
len(self.choices),
|
| 302 |
+
).format(value=value, choice=choices_str, choices=choices_str),
|
| 303 |
+
param,
|
| 304 |
+
ctx,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
def __repr__(self) -> str:
|
| 308 |
+
return f"Choice({list(self.choices)})"
|
| 309 |
+
|
| 310 |
+
def shell_complete(
|
| 311 |
+
self, ctx: "Context", param: "Parameter", incomplete: str
|
| 312 |
+
) -> t.List["CompletionItem"]:
|
| 313 |
+
"""Complete choices that start with the incomplete value.
|
| 314 |
+
|
| 315 |
+
:param ctx: Invocation context for this command.
|
| 316 |
+
:param param: The parameter that is requesting completion.
|
| 317 |
+
:param incomplete: Value being completed. May be empty.
|
| 318 |
+
|
| 319 |
+
.. versionadded:: 8.0
|
| 320 |
+
"""
|
| 321 |
+
from click.shell_completion import CompletionItem
|
| 322 |
+
|
| 323 |
+
str_choices = map(str, self.choices)
|
| 324 |
+
|
| 325 |
+
if self.case_sensitive:
|
| 326 |
+
matched = (c for c in str_choices if c.startswith(incomplete))
|
| 327 |
+
else:
|
| 328 |
+
incomplete = incomplete.lower()
|
| 329 |
+
matched = (c for c in str_choices if c.lower().startswith(incomplete))
|
| 330 |
+
|
| 331 |
+
return [CompletionItem(c) for c in matched]
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
class DateTime(ParamType):
|
| 335 |
+
"""The DateTime type converts date strings into `datetime` objects.
|
| 336 |
+
|
| 337 |
+
The format strings which are checked are configurable, but default to some
|
| 338 |
+
common (non-timezone aware) ISO 8601 formats.
|
| 339 |
+
|
| 340 |
+
When specifying *DateTime* formats, you should only pass a list or a tuple.
|
| 341 |
+
Other iterables, like generators, may lead to surprising results.
|
| 342 |
+
|
| 343 |
+
The format strings are processed using ``datetime.strptime``, and this
|
| 344 |
+
consequently defines the format strings which are allowed.
|
| 345 |
+
|
| 346 |
+
Parsing is tried using each format, in order, and the first format which
|
| 347 |
+
parses successfully is used.
|
| 348 |
+
|
| 349 |
+
:param formats: A list or tuple of date format strings, in the order in
|
| 350 |
+
which they should be tried. Defaults to
|
| 351 |
+
``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
|
| 352 |
+
``'%Y-%m-%d %H:%M:%S'``.
|
| 353 |
+
"""
|
| 354 |
+
|
| 355 |
+
name = "datetime"
|
| 356 |
+
|
| 357 |
+
def __init__(self, formats: t.Optional[t.Sequence[str]] = None):
|
| 358 |
+
self.formats: t.Sequence[str] = formats or [
|
| 359 |
+
"%Y-%m-%d",
|
| 360 |
+
"%Y-%m-%dT%H:%M:%S",
|
| 361 |
+
"%Y-%m-%d %H:%M:%S",
|
| 362 |
+
]
|
| 363 |
+
|
| 364 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 365 |
+
info_dict = super().to_info_dict()
|
| 366 |
+
info_dict["formats"] = self.formats
|
| 367 |
+
return info_dict
|
| 368 |
+
|
| 369 |
+
def get_metavar(self, param: "Parameter") -> str:
|
| 370 |
+
return f"[{'|'.join(self.formats)}]"
|
| 371 |
+
|
| 372 |
+
def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]:
|
| 373 |
+
try:
|
| 374 |
+
return datetime.strptime(value, format)
|
| 375 |
+
except ValueError:
|
| 376 |
+
return None
|
| 377 |
+
|
| 378 |
+
def convert(
|
| 379 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 380 |
+
) -> t.Any:
|
| 381 |
+
if isinstance(value, datetime):
|
| 382 |
+
return value
|
| 383 |
+
|
| 384 |
+
for format in self.formats:
|
| 385 |
+
converted = self._try_to_convert_date(value, format)
|
| 386 |
+
|
| 387 |
+
if converted is not None:
|
| 388 |
+
return converted
|
| 389 |
+
|
| 390 |
+
formats_str = ", ".join(map(repr, self.formats))
|
| 391 |
+
self.fail(
|
| 392 |
+
ngettext(
|
| 393 |
+
"{value!r} does not match the format {format}.",
|
| 394 |
+
"{value!r} does not match the formats {formats}.",
|
| 395 |
+
len(self.formats),
|
| 396 |
+
).format(value=value, format=formats_str, formats=formats_str),
|
| 397 |
+
param,
|
| 398 |
+
ctx,
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
def __repr__(self) -> str:
|
| 402 |
+
return "DateTime"
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
class _NumberParamTypeBase(ParamType):
|
| 406 |
+
_number_class: t.ClassVar[t.Type[t.Any]]
|
| 407 |
+
|
| 408 |
+
def convert(
|
| 409 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 410 |
+
) -> t.Any:
|
| 411 |
+
try:
|
| 412 |
+
return self._number_class(value)
|
| 413 |
+
except ValueError:
|
| 414 |
+
self.fail(
|
| 415 |
+
_("{value!r} is not a valid {number_type}.").format(
|
| 416 |
+
value=value, number_type=self.name
|
| 417 |
+
),
|
| 418 |
+
param,
|
| 419 |
+
ctx,
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
class _NumberRangeBase(_NumberParamTypeBase):
|
| 424 |
+
def __init__(
|
| 425 |
+
self,
|
| 426 |
+
min: t.Optional[float] = None,
|
| 427 |
+
max: t.Optional[float] = None,
|
| 428 |
+
min_open: bool = False,
|
| 429 |
+
max_open: bool = False,
|
| 430 |
+
clamp: bool = False,
|
| 431 |
+
) -> None:
|
| 432 |
+
self.min = min
|
| 433 |
+
self.max = max
|
| 434 |
+
self.min_open = min_open
|
| 435 |
+
self.max_open = max_open
|
| 436 |
+
self.clamp = clamp
|
| 437 |
+
|
| 438 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 439 |
+
info_dict = super().to_info_dict()
|
| 440 |
+
info_dict.update(
|
| 441 |
+
min=self.min,
|
| 442 |
+
max=self.max,
|
| 443 |
+
min_open=self.min_open,
|
| 444 |
+
max_open=self.max_open,
|
| 445 |
+
clamp=self.clamp,
|
| 446 |
+
)
|
| 447 |
+
return info_dict
|
| 448 |
+
|
| 449 |
+
def convert(
|
| 450 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 451 |
+
) -> t.Any:
|
| 452 |
+
import operator
|
| 453 |
+
|
| 454 |
+
rv = super().convert(value, param, ctx)
|
| 455 |
+
lt_min: bool = self.min is not None and (
|
| 456 |
+
operator.le if self.min_open else operator.lt
|
| 457 |
+
)(rv, self.min)
|
| 458 |
+
gt_max: bool = self.max is not None and (
|
| 459 |
+
operator.ge if self.max_open else operator.gt
|
| 460 |
+
)(rv, self.max)
|
| 461 |
+
|
| 462 |
+
if self.clamp:
|
| 463 |
+
if lt_min:
|
| 464 |
+
return self._clamp(self.min, 1, self.min_open) # type: ignore
|
| 465 |
+
|
| 466 |
+
if gt_max:
|
| 467 |
+
return self._clamp(self.max, -1, self.max_open) # type: ignore
|
| 468 |
+
|
| 469 |
+
if lt_min or gt_max:
|
| 470 |
+
self.fail(
|
| 471 |
+
_("{value} is not in the range {range}.").format(
|
| 472 |
+
value=rv, range=self._describe_range()
|
| 473 |
+
),
|
| 474 |
+
param,
|
| 475 |
+
ctx,
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
return rv
|
| 479 |
+
|
| 480 |
+
def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
|
| 481 |
+
"""Find the valid value to clamp to bound in the given
|
| 482 |
+
direction.
|
| 483 |
+
|
| 484 |
+
:param bound: The boundary value.
|
| 485 |
+
:param dir: 1 or -1 indicating the direction to move.
|
| 486 |
+
:param open: If true, the range does not include the bound.
|
| 487 |
+
"""
|
| 488 |
+
raise NotImplementedError
|
| 489 |
+
|
| 490 |
+
def _describe_range(self) -> str:
|
| 491 |
+
"""Describe the range for use in help text."""
|
| 492 |
+
if self.min is None:
|
| 493 |
+
op = "<" if self.max_open else "<="
|
| 494 |
+
return f"x{op}{self.max}"
|
| 495 |
+
|
| 496 |
+
if self.max is None:
|
| 497 |
+
op = ">" if self.min_open else ">="
|
| 498 |
+
return f"x{op}{self.min}"
|
| 499 |
+
|
| 500 |
+
lop = "<" if self.min_open else "<="
|
| 501 |
+
rop = "<" if self.max_open else "<="
|
| 502 |
+
return f"{self.min}{lop}x{rop}{self.max}"
|
| 503 |
+
|
| 504 |
+
def __repr__(self) -> str:
|
| 505 |
+
clamp = " clamped" if self.clamp else ""
|
| 506 |
+
return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
class IntParamType(_NumberParamTypeBase):
|
| 510 |
+
name = "integer"
|
| 511 |
+
_number_class = int
|
| 512 |
+
|
| 513 |
+
def __repr__(self) -> str:
|
| 514 |
+
return "INT"
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
class IntRange(_NumberRangeBase, IntParamType):
|
| 518 |
+
"""Restrict an :data:`click.INT` value to a range of accepted
|
| 519 |
+
values. See :ref:`ranges`.
|
| 520 |
+
|
| 521 |
+
If ``min`` or ``max`` are not passed, any value is accepted in that
|
| 522 |
+
direction. If ``min_open`` or ``max_open`` are enabled, the
|
| 523 |
+
corresponding boundary is not included in the range.
|
| 524 |
+
|
| 525 |
+
If ``clamp`` is enabled, a value outside the range is clamped to the
|
| 526 |
+
boundary instead of failing.
|
| 527 |
+
|
| 528 |
+
.. versionchanged:: 8.0
|
| 529 |
+
Added the ``min_open`` and ``max_open`` parameters.
|
| 530 |
+
"""
|
| 531 |
+
|
| 532 |
+
name = "integer range"
|
| 533 |
+
|
| 534 |
+
def _clamp( # type: ignore
|
| 535 |
+
self, bound: int, dir: "te.Literal[1, -1]", open: bool
|
| 536 |
+
) -> int:
|
| 537 |
+
if not open:
|
| 538 |
+
return bound
|
| 539 |
+
|
| 540 |
+
return bound + dir
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
class FloatParamType(_NumberParamTypeBase):
|
| 544 |
+
name = "float"
|
| 545 |
+
_number_class = float
|
| 546 |
+
|
| 547 |
+
def __repr__(self) -> str:
|
| 548 |
+
return "FLOAT"
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
class FloatRange(_NumberRangeBase, FloatParamType):
|
| 552 |
+
"""Restrict a :data:`click.FLOAT` value to a range of accepted
|
| 553 |
+
values. See :ref:`ranges`.
|
| 554 |
+
|
| 555 |
+
If ``min`` or ``max`` are not passed, any value is accepted in that
|
| 556 |
+
direction. If ``min_open`` or ``max_open`` are enabled, the
|
| 557 |
+
corresponding boundary is not included in the range.
|
| 558 |
+
|
| 559 |
+
If ``clamp`` is enabled, a value outside the range is clamped to the
|
| 560 |
+
boundary instead of failing. This is not supported if either
|
| 561 |
+
boundary is marked ``open``.
|
| 562 |
+
|
| 563 |
+
.. versionchanged:: 8.0
|
| 564 |
+
Added the ``min_open`` and ``max_open`` parameters.
|
| 565 |
+
"""
|
| 566 |
+
|
| 567 |
+
name = "float range"
|
| 568 |
+
|
| 569 |
+
def __init__(
|
| 570 |
+
self,
|
| 571 |
+
min: t.Optional[float] = None,
|
| 572 |
+
max: t.Optional[float] = None,
|
| 573 |
+
min_open: bool = False,
|
| 574 |
+
max_open: bool = False,
|
| 575 |
+
clamp: bool = False,
|
| 576 |
+
) -> None:
|
| 577 |
+
super().__init__(
|
| 578 |
+
min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
|
| 579 |
+
)
|
| 580 |
+
|
| 581 |
+
if (min_open or max_open) and clamp:
|
| 582 |
+
raise TypeError("Clamping is not supported for open bounds.")
|
| 583 |
+
|
| 584 |
+
def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
|
| 585 |
+
if not open:
|
| 586 |
+
return bound
|
| 587 |
+
|
| 588 |
+
# Could use Python 3.9's math.nextafter here, but clamping an
|
| 589 |
+
# open float range doesn't seem to be particularly useful. It's
|
| 590 |
+
# left up to the user to write a callback to do it if needed.
|
| 591 |
+
raise RuntimeError("Clamping is not supported for open bounds.")
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
class BoolParamType(ParamType):
|
| 595 |
+
name = "boolean"
|
| 596 |
+
|
| 597 |
+
def convert(
|
| 598 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 599 |
+
) -> t.Any:
|
| 600 |
+
if value in {False, True}:
|
| 601 |
+
return bool(value)
|
| 602 |
+
|
| 603 |
+
norm = value.strip().lower()
|
| 604 |
+
|
| 605 |
+
if norm in {"1", "true", "t", "yes", "y", "on"}:
|
| 606 |
+
return True
|
| 607 |
+
|
| 608 |
+
if norm in {"0", "false", "f", "no", "n", "off"}:
|
| 609 |
+
return False
|
| 610 |
+
|
| 611 |
+
self.fail(
|
| 612 |
+
_("{value!r} is not a valid boolean.").format(value=value), param, ctx
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
def __repr__(self) -> str:
|
| 616 |
+
return "BOOL"
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
class UUIDParameterType(ParamType):
|
| 620 |
+
name = "uuid"
|
| 621 |
+
|
| 622 |
+
def convert(
|
| 623 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 624 |
+
) -> t.Any:
|
| 625 |
+
import uuid
|
| 626 |
+
|
| 627 |
+
if isinstance(value, uuid.UUID):
|
| 628 |
+
return value
|
| 629 |
+
|
| 630 |
+
value = value.strip()
|
| 631 |
+
|
| 632 |
+
try:
|
| 633 |
+
return uuid.UUID(value)
|
| 634 |
+
except ValueError:
|
| 635 |
+
self.fail(
|
| 636 |
+
_("{value!r} is not a valid UUID.").format(value=value), param, ctx
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
def __repr__(self) -> str:
|
| 640 |
+
return "UUID"
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
class File(ParamType):
|
| 644 |
+
"""Declares a parameter to be a file for reading or writing. The file
|
| 645 |
+
is automatically closed once the context tears down (after the command
|
| 646 |
+
finished working).
|
| 647 |
+
|
| 648 |
+
Files can be opened for reading or writing. The special value ``-``
|
| 649 |
+
indicates stdin or stdout depending on the mode.
|
| 650 |
+
|
| 651 |
+
By default, the file is opened for reading text data, but it can also be
|
| 652 |
+
opened in binary mode or for writing. The encoding parameter can be used
|
| 653 |
+
to force a specific encoding.
|
| 654 |
+
|
| 655 |
+
The `lazy` flag controls if the file should be opened immediately or upon
|
| 656 |
+
first IO. The default is to be non-lazy for standard input and output
|
| 657 |
+
streams as well as files opened for reading, `lazy` otherwise. When opening a
|
| 658 |
+
file lazily for reading, it is still opened temporarily for validation, but
|
| 659 |
+
will not be held open until first IO. lazy is mainly useful when opening
|
| 660 |
+
for writing to avoid creating the file until it is needed.
|
| 661 |
+
|
| 662 |
+
Files can also be opened atomically in which case all writes go into a
|
| 663 |
+
separate file in the same folder and upon completion the file will
|
| 664 |
+
be moved over to the original location. This is useful if a file
|
| 665 |
+
regularly read by other users is modified.
|
| 666 |
+
|
| 667 |
+
See :ref:`file-args` for more information.
|
| 668 |
+
|
| 669 |
+
.. versionchanged:: 2.0
|
| 670 |
+
Added the ``atomic`` parameter.
|
| 671 |
+
"""
|
| 672 |
+
|
| 673 |
+
name = "filename"
|
| 674 |
+
envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
|
| 675 |
+
|
| 676 |
+
def __init__(
|
| 677 |
+
self,
|
| 678 |
+
mode: str = "r",
|
| 679 |
+
encoding: t.Optional[str] = None,
|
| 680 |
+
errors: t.Optional[str] = "strict",
|
| 681 |
+
lazy: t.Optional[bool] = None,
|
| 682 |
+
atomic: bool = False,
|
| 683 |
+
) -> None:
|
| 684 |
+
self.mode = mode
|
| 685 |
+
self.encoding = encoding
|
| 686 |
+
self.errors = errors
|
| 687 |
+
self.lazy = lazy
|
| 688 |
+
self.atomic = atomic
|
| 689 |
+
|
| 690 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 691 |
+
info_dict = super().to_info_dict()
|
| 692 |
+
info_dict.update(mode=self.mode, encoding=self.encoding)
|
| 693 |
+
return info_dict
|
| 694 |
+
|
| 695 |
+
def resolve_lazy_flag(self, value: "t.Union[str, os.PathLike[str]]") -> bool:
|
| 696 |
+
if self.lazy is not None:
|
| 697 |
+
return self.lazy
|
| 698 |
+
if os.fspath(value) == "-":
|
| 699 |
+
return False
|
| 700 |
+
elif "w" in self.mode:
|
| 701 |
+
return True
|
| 702 |
+
return False
|
| 703 |
+
|
| 704 |
+
def convert(
|
| 705 |
+
self,
|
| 706 |
+
value: t.Union[str, "os.PathLike[str]", t.IO[t.Any]],
|
| 707 |
+
param: t.Optional["Parameter"],
|
| 708 |
+
ctx: t.Optional["Context"],
|
| 709 |
+
) -> t.IO[t.Any]:
|
| 710 |
+
if _is_file_like(value):
|
| 711 |
+
return value
|
| 712 |
+
|
| 713 |
+
value = t.cast("t.Union[str, os.PathLike[str]]", value)
|
| 714 |
+
|
| 715 |
+
try:
|
| 716 |
+
lazy = self.resolve_lazy_flag(value)
|
| 717 |
+
|
| 718 |
+
if lazy:
|
| 719 |
+
lf = LazyFile(
|
| 720 |
+
value, self.mode, self.encoding, self.errors, atomic=self.atomic
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
if ctx is not None:
|
| 724 |
+
ctx.call_on_close(lf.close_intelligently)
|
| 725 |
+
|
| 726 |
+
return t.cast(t.IO[t.Any], lf)
|
| 727 |
+
|
| 728 |
+
f, should_close = open_stream(
|
| 729 |
+
value, self.mode, self.encoding, self.errors, atomic=self.atomic
|
| 730 |
+
)
|
| 731 |
+
|
| 732 |
+
# If a context is provided, we automatically close the file
|
| 733 |
+
# at the end of the context execution (or flush out). If a
|
| 734 |
+
# context does not exist, it's the caller's responsibility to
|
| 735 |
+
# properly close the file. This for instance happens when the
|
| 736 |
+
# type is used with prompts.
|
| 737 |
+
if ctx is not None:
|
| 738 |
+
if should_close:
|
| 739 |
+
ctx.call_on_close(safecall(f.close))
|
| 740 |
+
else:
|
| 741 |
+
ctx.call_on_close(safecall(f.flush))
|
| 742 |
+
|
| 743 |
+
return f
|
| 744 |
+
except OSError as e:
|
| 745 |
+
self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
|
| 746 |
+
|
| 747 |
+
def shell_complete(
|
| 748 |
+
self, ctx: "Context", param: "Parameter", incomplete: str
|
| 749 |
+
) -> t.List["CompletionItem"]:
|
| 750 |
+
"""Return a special completion marker that tells the completion
|
| 751 |
+
system to use the shell to provide file path completions.
|
| 752 |
+
|
| 753 |
+
:param ctx: Invocation context for this command.
|
| 754 |
+
:param param: The parameter that is requesting completion.
|
| 755 |
+
:param incomplete: Value being completed. May be empty.
|
| 756 |
+
|
| 757 |
+
.. versionadded:: 8.0
|
| 758 |
+
"""
|
| 759 |
+
from click.shell_completion import CompletionItem
|
| 760 |
+
|
| 761 |
+
return [CompletionItem(incomplete, type="file")]
|
| 762 |
+
|
| 763 |
+
|
| 764 |
+
def _is_file_like(value: t.Any) -> "te.TypeGuard[t.IO[t.Any]]":
|
| 765 |
+
return hasattr(value, "read") or hasattr(value, "write")
|
| 766 |
+
|
| 767 |
+
|
| 768 |
+
class Path(ParamType):
|
| 769 |
+
"""The ``Path`` type is similar to the :class:`File` type, but
|
| 770 |
+
returns the filename instead of an open file. Various checks can be
|
| 771 |
+
enabled to validate the type of file and permissions.
|
| 772 |
+
|
| 773 |
+
:param exists: The file or directory needs to exist for the value to
|
| 774 |
+
be valid. If this is not set to ``True``, and the file does not
|
| 775 |
+
exist, then all further checks are silently skipped.
|
| 776 |
+
:param file_okay: Allow a file as a value.
|
| 777 |
+
:param dir_okay: Allow a directory as a value.
|
| 778 |
+
:param readable: if true, a readable check is performed.
|
| 779 |
+
:param writable: if true, a writable check is performed.
|
| 780 |
+
:param executable: if true, an executable check is performed.
|
| 781 |
+
:param resolve_path: Make the value absolute and resolve any
|
| 782 |
+
symlinks. A ``~`` is not expanded, as this is supposed to be
|
| 783 |
+
done by the shell only.
|
| 784 |
+
:param allow_dash: Allow a single dash as a value, which indicates
|
| 785 |
+
a standard stream (but does not open it). Use
|
| 786 |
+
:func:`~click.open_file` to handle opening this value.
|
| 787 |
+
:param path_type: Convert the incoming path value to this type. If
|
| 788 |
+
``None``, keep Python's default, which is ``str``. Useful to
|
| 789 |
+
convert to :class:`pathlib.Path`.
|
| 790 |
+
|
| 791 |
+
.. versionchanged:: 8.1
|
| 792 |
+
Added the ``executable`` parameter.
|
| 793 |
+
|
| 794 |
+
.. versionchanged:: 8.0
|
| 795 |
+
Allow passing ``path_type=pathlib.Path``.
|
| 796 |
+
|
| 797 |
+
.. versionchanged:: 6.0
|
| 798 |
+
Added the ``allow_dash`` parameter.
|
| 799 |
+
"""
|
| 800 |
+
|
| 801 |
+
envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
|
| 802 |
+
|
| 803 |
+
def __init__(
|
| 804 |
+
self,
|
| 805 |
+
exists: bool = False,
|
| 806 |
+
file_okay: bool = True,
|
| 807 |
+
dir_okay: bool = True,
|
| 808 |
+
writable: bool = False,
|
| 809 |
+
readable: bool = True,
|
| 810 |
+
resolve_path: bool = False,
|
| 811 |
+
allow_dash: bool = False,
|
| 812 |
+
path_type: t.Optional[t.Type[t.Any]] = None,
|
| 813 |
+
executable: bool = False,
|
| 814 |
+
):
|
| 815 |
+
self.exists = exists
|
| 816 |
+
self.file_okay = file_okay
|
| 817 |
+
self.dir_okay = dir_okay
|
| 818 |
+
self.readable = readable
|
| 819 |
+
self.writable = writable
|
| 820 |
+
self.executable = executable
|
| 821 |
+
self.resolve_path = resolve_path
|
| 822 |
+
self.allow_dash = allow_dash
|
| 823 |
+
self.type = path_type
|
| 824 |
+
|
| 825 |
+
if self.file_okay and not self.dir_okay:
|
| 826 |
+
self.name: str = _("file")
|
| 827 |
+
elif self.dir_okay and not self.file_okay:
|
| 828 |
+
self.name = _("directory")
|
| 829 |
+
else:
|
| 830 |
+
self.name = _("path")
|
| 831 |
+
|
| 832 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 833 |
+
info_dict = super().to_info_dict()
|
| 834 |
+
info_dict.update(
|
| 835 |
+
exists=self.exists,
|
| 836 |
+
file_okay=self.file_okay,
|
| 837 |
+
dir_okay=self.dir_okay,
|
| 838 |
+
writable=self.writable,
|
| 839 |
+
readable=self.readable,
|
| 840 |
+
allow_dash=self.allow_dash,
|
| 841 |
+
)
|
| 842 |
+
return info_dict
|
| 843 |
+
|
| 844 |
+
def coerce_path_result(
|
| 845 |
+
self, value: "t.Union[str, os.PathLike[str]]"
|
| 846 |
+
) -> "t.Union[str, bytes, os.PathLike[str]]":
|
| 847 |
+
if self.type is not None and not isinstance(value, self.type):
|
| 848 |
+
if self.type is str:
|
| 849 |
+
return os.fsdecode(value)
|
| 850 |
+
elif self.type is bytes:
|
| 851 |
+
return os.fsencode(value)
|
| 852 |
+
else:
|
| 853 |
+
return t.cast("os.PathLike[str]", self.type(value))
|
| 854 |
+
|
| 855 |
+
return value
|
| 856 |
+
|
| 857 |
+
def convert(
|
| 858 |
+
self,
|
| 859 |
+
value: "t.Union[str, os.PathLike[str]]",
|
| 860 |
+
param: t.Optional["Parameter"],
|
| 861 |
+
ctx: t.Optional["Context"],
|
| 862 |
+
) -> "t.Union[str, bytes, os.PathLike[str]]":
|
| 863 |
+
rv = value
|
| 864 |
+
|
| 865 |
+
is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
|
| 866 |
+
|
| 867 |
+
if not is_dash:
|
| 868 |
+
if self.resolve_path:
|
| 869 |
+
# os.path.realpath doesn't resolve symlinks on Windows
|
| 870 |
+
# until Python 3.8. Use pathlib for now.
|
| 871 |
+
import pathlib
|
| 872 |
+
|
| 873 |
+
rv = os.fsdecode(pathlib.Path(rv).resolve())
|
| 874 |
+
|
| 875 |
+
try:
|
| 876 |
+
st = os.stat(rv)
|
| 877 |
+
except OSError:
|
| 878 |
+
if not self.exists:
|
| 879 |
+
return self.coerce_path_result(rv)
|
| 880 |
+
self.fail(
|
| 881 |
+
_("{name} {filename!r} does not exist.").format(
|
| 882 |
+
name=self.name.title(), filename=format_filename(value)
|
| 883 |
+
),
|
| 884 |
+
param,
|
| 885 |
+
ctx,
|
| 886 |
+
)
|
| 887 |
+
|
| 888 |
+
if not self.file_okay and stat.S_ISREG(st.st_mode):
|
| 889 |
+
self.fail(
|
| 890 |
+
_("{name} {filename!r} is a file.").format(
|
| 891 |
+
name=self.name.title(), filename=format_filename(value)
|
| 892 |
+
),
|
| 893 |
+
param,
|
| 894 |
+
ctx,
|
| 895 |
+
)
|
| 896 |
+
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
| 897 |
+
self.fail(
|
| 898 |
+
_("{name} {filename!r} is a directory.").format(
|
| 899 |
+
name=self.name.title(), filename=format_filename(value)
|
| 900 |
+
),
|
| 901 |
+
param,
|
| 902 |
+
ctx,
|
| 903 |
+
)
|
| 904 |
+
|
| 905 |
+
if self.readable and not os.access(rv, os.R_OK):
|
| 906 |
+
self.fail(
|
| 907 |
+
_("{name} {filename!r} is not readable.").format(
|
| 908 |
+
name=self.name.title(), filename=format_filename(value)
|
| 909 |
+
),
|
| 910 |
+
param,
|
| 911 |
+
ctx,
|
| 912 |
+
)
|
| 913 |
+
|
| 914 |
+
if self.writable and not os.access(rv, os.W_OK):
|
| 915 |
+
self.fail(
|
| 916 |
+
_("{name} {filename!r} is not writable.").format(
|
| 917 |
+
name=self.name.title(), filename=format_filename(value)
|
| 918 |
+
),
|
| 919 |
+
param,
|
| 920 |
+
ctx,
|
| 921 |
+
)
|
| 922 |
+
|
| 923 |
+
if self.executable and not os.access(value, os.X_OK):
|
| 924 |
+
self.fail(
|
| 925 |
+
_("{name} {filename!r} is not executable.").format(
|
| 926 |
+
name=self.name.title(), filename=format_filename(value)
|
| 927 |
+
),
|
| 928 |
+
param,
|
| 929 |
+
ctx,
|
| 930 |
+
)
|
| 931 |
+
|
| 932 |
+
return self.coerce_path_result(rv)
|
| 933 |
+
|
| 934 |
+
def shell_complete(
|
| 935 |
+
self, ctx: "Context", param: "Parameter", incomplete: str
|
| 936 |
+
) -> t.List["CompletionItem"]:
|
| 937 |
+
"""Return a special completion marker that tells the completion
|
| 938 |
+
system to use the shell to provide path completions for only
|
| 939 |
+
directories or any paths.
|
| 940 |
+
|
| 941 |
+
:param ctx: Invocation context for this command.
|
| 942 |
+
:param param: The parameter that is requesting completion.
|
| 943 |
+
:param incomplete: Value being completed. May be empty.
|
| 944 |
+
|
| 945 |
+
.. versionadded:: 8.0
|
| 946 |
+
"""
|
| 947 |
+
from click.shell_completion import CompletionItem
|
| 948 |
+
|
| 949 |
+
type = "dir" if self.dir_okay and not self.file_okay else "file"
|
| 950 |
+
return [CompletionItem(incomplete, type=type)]
|
| 951 |
+
|
| 952 |
+
|
| 953 |
+
class Tuple(CompositeParamType):
|
| 954 |
+
"""The default behavior of Click is to apply a type on a value directly.
|
| 955 |
+
This works well in most cases, except for when `nargs` is set to a fixed
|
| 956 |
+
count and different types should be used for different items. In this
|
| 957 |
+
case the :class:`Tuple` type can be used. This type can only be used
|
| 958 |
+
if `nargs` is set to a fixed number.
|
| 959 |
+
|
| 960 |
+
For more information see :ref:`tuple-type`.
|
| 961 |
+
|
| 962 |
+
This can be selected by using a Python tuple literal as a type.
|
| 963 |
+
|
| 964 |
+
:param types: a list of types that should be used for the tuple items.
|
| 965 |
+
"""
|
| 966 |
+
|
| 967 |
+
def __init__(self, types: t.Sequence[t.Union[t.Type[t.Any], ParamType]]) -> None:
|
| 968 |
+
self.types: t.Sequence[ParamType] = [convert_type(ty) for ty in types]
|
| 969 |
+
|
| 970 |
+
def to_info_dict(self) -> t.Dict[str, t.Any]:
|
| 971 |
+
info_dict = super().to_info_dict()
|
| 972 |
+
info_dict["types"] = [t.to_info_dict() for t in self.types]
|
| 973 |
+
return info_dict
|
| 974 |
+
|
| 975 |
+
@property
|
| 976 |
+
def name(self) -> str: # type: ignore
|
| 977 |
+
return f"<{' '.join(ty.name for ty in self.types)}>"
|
| 978 |
+
|
| 979 |
+
@property
|
| 980 |
+
def arity(self) -> int: # type: ignore
|
| 981 |
+
return len(self.types)
|
| 982 |
+
|
| 983 |
+
def convert(
|
| 984 |
+
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
|
| 985 |
+
) -> t.Any:
|
| 986 |
+
len_type = len(self.types)
|
| 987 |
+
len_value = len(value)
|
| 988 |
+
|
| 989 |
+
if len_value != len_type:
|
| 990 |
+
self.fail(
|
| 991 |
+
ngettext(
|
| 992 |
+
"{len_type} values are required, but {len_value} was given.",
|
| 993 |
+
"{len_type} values are required, but {len_value} were given.",
|
| 994 |
+
len_value,
|
| 995 |
+
).format(len_type=len_type, len_value=len_value),
|
| 996 |
+
param=param,
|
| 997 |
+
ctx=ctx,
|
| 998 |
+
)
|
| 999 |
+
|
| 1000 |
+
return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
|
| 1001 |
+
|
| 1002 |
+
|
| 1003 |
+
def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType:
|
| 1004 |
+
"""Find the most appropriate :class:`ParamType` for the given Python
|
| 1005 |
+
type. If the type isn't provided, it can be inferred from a default
|
| 1006 |
+
value.
|
| 1007 |
+
"""
|
| 1008 |
+
guessed_type = False
|
| 1009 |
+
|
| 1010 |
+
if ty is None and default is not None:
|
| 1011 |
+
if isinstance(default, (tuple, list)):
|
| 1012 |
+
# If the default is empty, ty will remain None and will
|
| 1013 |
+
# return STRING.
|
| 1014 |
+
if default:
|
| 1015 |
+
item = default[0]
|
| 1016 |
+
|
| 1017 |
+
# A tuple of tuples needs to detect the inner types.
|
| 1018 |
+
# Can't call convert recursively because that would
|
| 1019 |
+
# incorrectly unwind the tuple to a single type.
|
| 1020 |
+
if isinstance(item, (tuple, list)):
|
| 1021 |
+
ty = tuple(map(type, item))
|
| 1022 |
+
else:
|
| 1023 |
+
ty = type(item)
|
| 1024 |
+
else:
|
| 1025 |
+
ty = type(default)
|
| 1026 |
+
|
| 1027 |
+
guessed_type = True
|
| 1028 |
+
|
| 1029 |
+
if isinstance(ty, tuple):
|
| 1030 |
+
return Tuple(ty)
|
| 1031 |
+
|
| 1032 |
+
if isinstance(ty, ParamType):
|
| 1033 |
+
return ty
|
| 1034 |
+
|
| 1035 |
+
if ty is str or ty is None:
|
| 1036 |
+
return STRING
|
| 1037 |
+
|
| 1038 |
+
if ty is int:
|
| 1039 |
+
return INT
|
| 1040 |
+
|
| 1041 |
+
if ty is float:
|
| 1042 |
+
return FLOAT
|
| 1043 |
+
|
| 1044 |
+
if ty is bool:
|
| 1045 |
+
return BOOL
|
| 1046 |
+
|
| 1047 |
+
if guessed_type:
|
| 1048 |
+
return STRING
|
| 1049 |
+
|
| 1050 |
+
if __debug__:
|
| 1051 |
+
try:
|
| 1052 |
+
if issubclass(ty, ParamType):
|
| 1053 |
+
raise AssertionError(
|
| 1054 |
+
f"Attempted to use an uninstantiated parameter type ({ty})."
|
| 1055 |
+
)
|
| 1056 |
+
except TypeError:
|
| 1057 |
+
# ty is an instance (correct), so issubclass fails.
|
| 1058 |
+
pass
|
| 1059 |
+
|
| 1060 |
+
return FuncParamType(ty)
|
| 1061 |
+
|
| 1062 |
+
|
| 1063 |
+
#: A dummy parameter type that just does nothing. From a user's
|
| 1064 |
+
#: perspective this appears to just be the same as `STRING` but
|
| 1065 |
+
#: internally no string conversion takes place if the input was bytes.
|
| 1066 |
+
#: This is usually useful when working with file paths as they can
|
| 1067 |
+
#: appear in bytes and unicode.
|
| 1068 |
+
#:
|
| 1069 |
+
#: For path related uses the :class:`Path` type is a better choice but
|
| 1070 |
+
#: there are situations where an unprocessed type is useful which is why
|
| 1071 |
+
#: it is is provided.
|
| 1072 |
+
#:
|
| 1073 |
+
#: .. versionadded:: 4.0
|
| 1074 |
+
UNPROCESSED = UnprocessedParamType()
|
| 1075 |
+
|
| 1076 |
+
#: A unicode string parameter type which is the implicit default. This
|
| 1077 |
+
#: can also be selected by using ``str`` as type.
|
| 1078 |
+
STRING = StringParamType()
|
| 1079 |
+
|
| 1080 |
+
#: An integer parameter. This can also be selected by using ``int`` as
|
| 1081 |
+
#: type.
|
| 1082 |
+
INT = IntParamType()
|
| 1083 |
+
|
| 1084 |
+
#: A floating point value parameter. This can also be selected by using
|
| 1085 |
+
#: ``float`` as type.
|
| 1086 |
+
FLOAT = FloatParamType()
|
| 1087 |
+
|
| 1088 |
+
#: A boolean parameter. This is the default for boolean flags. This can
|
| 1089 |
+
#: also be selected by using ``bool`` as a type.
|
| 1090 |
+
BOOL = BoolParamType()
|
| 1091 |
+
|
| 1092 |
+
#: A UUID parameter.
|
| 1093 |
+
UUID = UUIDParameterType()
|
.venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: fsspec
|
| 3 |
+
Version: 2025.2.0
|
| 4 |
+
Summary: File-system specification
|
| 5 |
+
Project-URL: Changelog, https://filesystem-spec.readthedocs.io/en/latest/changelog.html
|
| 6 |
+
Project-URL: Documentation, https://filesystem-spec.readthedocs.io/en/latest/
|
| 7 |
+
Project-URL: Homepage, https://github.com/fsspec/filesystem_spec
|
| 8 |
+
Maintainer-email: Martin Durant <mdurant@anaconda.com>
|
| 9 |
+
License: BSD 3-Clause License
|
| 10 |
+
|
| 11 |
+
Copyright (c) 2018, Martin Durant
|
| 12 |
+
All rights reserved.
|
| 13 |
+
|
| 14 |
+
Redistribution and use in source and binary forms, with or without
|
| 15 |
+
modification, are permitted provided that the following conditions are met:
|
| 16 |
+
|
| 17 |
+
* Redistributions of source code must retain the above copyright notice, this
|
| 18 |
+
list of conditions and the following disclaimer.
|
| 19 |
+
|
| 20 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 21 |
+
this list of conditions and the following disclaimer in the documentation
|
| 22 |
+
and/or other materials provided with the distribution.
|
| 23 |
+
|
| 24 |
+
* Neither the name of the copyright holder nor the names of its
|
| 25 |
+
contributors may be used to endorse or promote products derived from
|
| 26 |
+
this software without specific prior written permission.
|
| 27 |
+
|
| 28 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 29 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 30 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 31 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 32 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 33 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 34 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 35 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 36 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 37 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 38 |
+
License-File: LICENSE
|
| 39 |
+
Keywords: file
|
| 40 |
+
Classifier: Development Status :: 4 - Beta
|
| 41 |
+
Classifier: Intended Audience :: Developers
|
| 42 |
+
Classifier: License :: OSI Approved :: BSD License
|
| 43 |
+
Classifier: Operating System :: OS Independent
|
| 44 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 45 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 46 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 47 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 48 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 49 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 50 |
+
Requires-Python: >=3.8
|
| 51 |
+
Provides-Extra: abfs
|
| 52 |
+
Requires-Dist: adlfs; extra == 'abfs'
|
| 53 |
+
Provides-Extra: adl
|
| 54 |
+
Requires-Dist: adlfs; extra == 'adl'
|
| 55 |
+
Provides-Extra: arrow
|
| 56 |
+
Requires-Dist: pyarrow>=1; extra == 'arrow'
|
| 57 |
+
Provides-Extra: dask
|
| 58 |
+
Requires-Dist: dask; extra == 'dask'
|
| 59 |
+
Requires-Dist: distributed; extra == 'dask'
|
| 60 |
+
Provides-Extra: dev
|
| 61 |
+
Requires-Dist: pre-commit; extra == 'dev'
|
| 62 |
+
Requires-Dist: ruff; extra == 'dev'
|
| 63 |
+
Provides-Extra: doc
|
| 64 |
+
Requires-Dist: numpydoc; extra == 'doc'
|
| 65 |
+
Requires-Dist: sphinx; extra == 'doc'
|
| 66 |
+
Requires-Dist: sphinx-design; extra == 'doc'
|
| 67 |
+
Requires-Dist: sphinx-rtd-theme; extra == 'doc'
|
| 68 |
+
Requires-Dist: yarl; extra == 'doc'
|
| 69 |
+
Provides-Extra: dropbox
|
| 70 |
+
Requires-Dist: dropbox; extra == 'dropbox'
|
| 71 |
+
Requires-Dist: dropboxdrivefs; extra == 'dropbox'
|
| 72 |
+
Requires-Dist: requests; extra == 'dropbox'
|
| 73 |
+
Provides-Extra: entrypoints
|
| 74 |
+
Provides-Extra: full
|
| 75 |
+
Requires-Dist: adlfs; extra == 'full'
|
| 76 |
+
Requires-Dist: aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'full'
|
| 77 |
+
Requires-Dist: dask; extra == 'full'
|
| 78 |
+
Requires-Dist: distributed; extra == 'full'
|
| 79 |
+
Requires-Dist: dropbox; extra == 'full'
|
| 80 |
+
Requires-Dist: dropboxdrivefs; extra == 'full'
|
| 81 |
+
Requires-Dist: fusepy; extra == 'full'
|
| 82 |
+
Requires-Dist: gcsfs; extra == 'full'
|
| 83 |
+
Requires-Dist: libarchive-c; extra == 'full'
|
| 84 |
+
Requires-Dist: ocifs; extra == 'full'
|
| 85 |
+
Requires-Dist: panel; extra == 'full'
|
| 86 |
+
Requires-Dist: paramiko; extra == 'full'
|
| 87 |
+
Requires-Dist: pyarrow>=1; extra == 'full'
|
| 88 |
+
Requires-Dist: pygit2; extra == 'full'
|
| 89 |
+
Requires-Dist: requests; extra == 'full'
|
| 90 |
+
Requires-Dist: s3fs; extra == 'full'
|
| 91 |
+
Requires-Dist: smbprotocol; extra == 'full'
|
| 92 |
+
Requires-Dist: tqdm; extra == 'full'
|
| 93 |
+
Provides-Extra: fuse
|
| 94 |
+
Requires-Dist: fusepy; extra == 'fuse'
|
| 95 |
+
Provides-Extra: gcs
|
| 96 |
+
Requires-Dist: gcsfs; extra == 'gcs'
|
| 97 |
+
Provides-Extra: git
|
| 98 |
+
Requires-Dist: pygit2; extra == 'git'
|
| 99 |
+
Provides-Extra: github
|
| 100 |
+
Requires-Dist: requests; extra == 'github'
|
| 101 |
+
Provides-Extra: gs
|
| 102 |
+
Requires-Dist: gcsfs; extra == 'gs'
|
| 103 |
+
Provides-Extra: gui
|
| 104 |
+
Requires-Dist: panel; extra == 'gui'
|
| 105 |
+
Provides-Extra: hdfs
|
| 106 |
+
Requires-Dist: pyarrow>=1; extra == 'hdfs'
|
| 107 |
+
Provides-Extra: http
|
| 108 |
+
Requires-Dist: aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'http'
|
| 109 |
+
Provides-Extra: libarchive
|
| 110 |
+
Requires-Dist: libarchive-c; extra == 'libarchive'
|
| 111 |
+
Provides-Extra: oci
|
| 112 |
+
Requires-Dist: ocifs; extra == 'oci'
|
| 113 |
+
Provides-Extra: s3
|
| 114 |
+
Requires-Dist: s3fs; extra == 's3'
|
| 115 |
+
Provides-Extra: sftp
|
| 116 |
+
Requires-Dist: paramiko; extra == 'sftp'
|
| 117 |
+
Provides-Extra: smb
|
| 118 |
+
Requires-Dist: smbprotocol; extra == 'smb'
|
| 119 |
+
Provides-Extra: ssh
|
| 120 |
+
Requires-Dist: paramiko; extra == 'ssh'
|
| 121 |
+
Provides-Extra: test
|
| 122 |
+
Requires-Dist: aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'test'
|
| 123 |
+
Requires-Dist: numpy; extra == 'test'
|
| 124 |
+
Requires-Dist: pytest; extra == 'test'
|
| 125 |
+
Requires-Dist: pytest-asyncio!=0.22.0; extra == 'test'
|
| 126 |
+
Requires-Dist: pytest-benchmark; extra == 'test'
|
| 127 |
+
Requires-Dist: pytest-cov; extra == 'test'
|
| 128 |
+
Requires-Dist: pytest-mock; extra == 'test'
|
| 129 |
+
Requires-Dist: pytest-recording; extra == 'test'
|
| 130 |
+
Requires-Dist: pytest-rerunfailures; extra == 'test'
|
| 131 |
+
Requires-Dist: requests; extra == 'test'
|
| 132 |
+
Provides-Extra: test-downstream
|
| 133 |
+
Requires-Dist: aiobotocore<3.0.0,>=2.5.4; extra == 'test-downstream'
|
| 134 |
+
Requires-Dist: dask[dataframe,test]; extra == 'test-downstream'
|
| 135 |
+
Requires-Dist: moto[server]<5,>4; extra == 'test-downstream'
|
| 136 |
+
Requires-Dist: pytest-timeout; extra == 'test-downstream'
|
| 137 |
+
Requires-Dist: xarray; extra == 'test-downstream'
|
| 138 |
+
Provides-Extra: test-full
|
| 139 |
+
Requires-Dist: adlfs; extra == 'test-full'
|
| 140 |
+
Requires-Dist: aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'test-full'
|
| 141 |
+
Requires-Dist: cloudpickle; extra == 'test-full'
|
| 142 |
+
Requires-Dist: dask; extra == 'test-full'
|
| 143 |
+
Requires-Dist: distributed; extra == 'test-full'
|
| 144 |
+
Requires-Dist: dropbox; extra == 'test-full'
|
| 145 |
+
Requires-Dist: dropboxdrivefs; extra == 'test-full'
|
| 146 |
+
Requires-Dist: fastparquet; extra == 'test-full'
|
| 147 |
+
Requires-Dist: fusepy; extra == 'test-full'
|
| 148 |
+
Requires-Dist: gcsfs; extra == 'test-full'
|
| 149 |
+
Requires-Dist: jinja2; extra == 'test-full'
|
| 150 |
+
Requires-Dist: kerchunk; extra == 'test-full'
|
| 151 |
+
Requires-Dist: libarchive-c; extra == 'test-full'
|
| 152 |
+
Requires-Dist: lz4; extra == 'test-full'
|
| 153 |
+
Requires-Dist: notebook; extra == 'test-full'
|
| 154 |
+
Requires-Dist: numpy; extra == 'test-full'
|
| 155 |
+
Requires-Dist: ocifs; extra == 'test-full'
|
| 156 |
+
Requires-Dist: pandas; extra == 'test-full'
|
| 157 |
+
Requires-Dist: panel; extra == 'test-full'
|
| 158 |
+
Requires-Dist: paramiko; extra == 'test-full'
|
| 159 |
+
Requires-Dist: pyarrow; extra == 'test-full'
|
| 160 |
+
Requires-Dist: pyarrow>=1; extra == 'test-full'
|
| 161 |
+
Requires-Dist: pyftpdlib; extra == 'test-full'
|
| 162 |
+
Requires-Dist: pygit2; extra == 'test-full'
|
| 163 |
+
Requires-Dist: pytest; extra == 'test-full'
|
| 164 |
+
Requires-Dist: pytest-asyncio!=0.22.0; extra == 'test-full'
|
| 165 |
+
Requires-Dist: pytest-benchmark; extra == 'test-full'
|
| 166 |
+
Requires-Dist: pytest-cov; extra == 'test-full'
|
| 167 |
+
Requires-Dist: pytest-mock; extra == 'test-full'
|
| 168 |
+
Requires-Dist: pytest-recording; extra == 'test-full'
|
| 169 |
+
Requires-Dist: pytest-rerunfailures; extra == 'test-full'
|
| 170 |
+
Requires-Dist: python-snappy; extra == 'test-full'
|
| 171 |
+
Requires-Dist: requests; extra == 'test-full'
|
| 172 |
+
Requires-Dist: smbprotocol; extra == 'test-full'
|
| 173 |
+
Requires-Dist: tqdm; extra == 'test-full'
|
| 174 |
+
Requires-Dist: urllib3; extra == 'test-full'
|
| 175 |
+
Requires-Dist: zarr; extra == 'test-full'
|
| 176 |
+
Requires-Dist: zstandard; extra == 'test-full'
|
| 177 |
+
Provides-Extra: tqdm
|
| 178 |
+
Requires-Dist: tqdm; extra == 'tqdm'
|
| 179 |
+
Description-Content-Type: text/markdown
|
| 180 |
+
|
| 181 |
+
# filesystem_spec
|
| 182 |
+
|
| 183 |
+
[](https://pypi.python.org/pypi/fsspec/)
|
| 184 |
+
[](https://anaconda.org/conda-forge/fsspec)
|
| 185 |
+

|
| 186 |
+
[](https://filesystem-spec.readthedocs.io/en/latest/?badge=latest)
|
| 187 |
+
|
| 188 |
+
A specification for pythonic filesystems.
|
| 189 |
+
|
| 190 |
+
## Install
|
| 191 |
+
|
| 192 |
+
```bash
|
| 193 |
+
pip install fsspec
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
would install the base fsspec. Various optionally supported features might require specification of custom
|
| 197 |
+
extra require, e.g. `pip install fsspec[ssh]` will install dependencies for `ssh` backends support.
|
| 198 |
+
Use `pip install fsspec[full]` for installation of all known extra dependencies.
|
| 199 |
+
|
| 200 |
+
Up-to-date package also provided through conda-forge distribution:
|
| 201 |
+
|
| 202 |
+
```bash
|
| 203 |
+
conda install -c conda-forge fsspec
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
## Purpose
|
| 208 |
+
|
| 209 |
+
To produce a template or specification for a file-system interface, that specific implementations should follow,
|
| 210 |
+
so that applications making use of them can rely on a common behaviour and not have to worry about the specific
|
| 211 |
+
internal implementation decisions with any given backend. Many such implementations are included in this package,
|
| 212 |
+
or in sister projects such as `s3fs` and `gcsfs`.
|
| 213 |
+
|
| 214 |
+
In addition, if this is well-designed, then additional functionality, such as a key-value store or FUSE
|
| 215 |
+
mounting of the file-system implementation may be available for all implementations "for free".
|
| 216 |
+
|
| 217 |
+
## Documentation
|
| 218 |
+
|
| 219 |
+
Please refer to [RTD](https://filesystem-spec.readthedocs.io/en/latest/?badge=latest)
|
| 220 |
+
|
| 221 |
+
## Develop
|
| 222 |
+
|
| 223 |
+
fsspec uses GitHub Actions for CI. Environment files can be found
|
| 224 |
+
in the "ci/" directory. Note that the main environment is called "py38",
|
| 225 |
+
but it is expected that the version of python installed be adjustable at
|
| 226 |
+
CI runtime. For local use, pick a version suitable for you.
|
| 227 |
+
|
| 228 |
+
```bash
|
| 229 |
+
# For a new environment (mamba / conda).
|
| 230 |
+
mamba create -n fsspec -c conda-forge python=3.9 -y
|
| 231 |
+
conda activate fsspec
|
| 232 |
+
|
| 233 |
+
# Standard dev install with docs and tests.
|
| 234 |
+
pip install -e ".[dev,doc,test]"
|
| 235 |
+
|
| 236 |
+
# Full tests except for downstream
|
| 237 |
+
pip install s3fs
|
| 238 |
+
pip uninstall s3fs
|
| 239 |
+
pip install -e .[dev,doc,test_full]
|
| 240 |
+
pip install s3fs --no-deps
|
| 241 |
+
pytest -v
|
| 242 |
+
|
| 243 |
+
# Downstream tests.
|
| 244 |
+
sh install_s3fs.sh
|
| 245 |
+
# Windows powershell.
|
| 246 |
+
install_s3fs.sh
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
### Testing
|
| 250 |
+
|
| 251 |
+
Tests can be run in the dev environment, if activated, via ``pytest fsspec``.
|
| 252 |
+
|
| 253 |
+
The full fsspec suite requires a system-level docker, docker-compose, and fuse
|
| 254 |
+
installation. If only making changes to one backend implementation, it is
|
| 255 |
+
not generally necessary to run all tests locally.
|
| 256 |
+
|
| 257 |
+
It is expected that contributors ensure that any change to fsspec does not
|
| 258 |
+
cause issues or regressions for either other fsspec-related packages such
|
| 259 |
+
as gcsfs and s3fs, nor for downstream users of fsspec. The "downstream" CI
|
| 260 |
+
run and corresponding environment file run a set of tests from the dask
|
| 261 |
+
test suite, and very minimal tests against pandas and zarr from the
|
| 262 |
+
test_downstream.py module in this repo.
|
| 263 |
+
|
| 264 |
+
### Code Formatting
|
| 265 |
+
|
| 266 |
+
fsspec uses [Black](https://black.readthedocs.io/en/stable) to ensure
|
| 267 |
+
a consistent code format throughout the project.
|
| 268 |
+
Run ``black fsspec`` from the root of the filesystem_spec repository to
|
| 269 |
+
auto-format your code. Additionally, many editors have plugins that will apply
|
| 270 |
+
``black`` as you edit files. ``black`` is included in the ``tox`` environments.
|
| 271 |
+
|
| 272 |
+
Optionally, you may wish to setup [pre-commit hooks](https://pre-commit.com) to
|
| 273 |
+
automatically run ``black`` when you make a git commit.
|
| 274 |
+
Run ``pre-commit install --install-hooks`` from the root of the
|
| 275 |
+
filesystem_spec repository to setup pre-commit hooks. ``black`` will now be run
|
| 276 |
+
before you commit, reformatting any changed files. You can format without
|
| 277 |
+
committing via ``pre-commit run`` or skip these checks with ``git commit
|
| 278 |
+
--no-verify``.
|
.venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fsspec-2025.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
fsspec-2025.2.0.dist-info/METADATA,sha256=GqrJBTBVJNmkbcEfTOamzq2KqYYbc9vRM0jpSWTZT04,11747
|
| 3 |
+
fsspec-2025.2.0.dist-info/RECORD,,
|
| 4 |
+
fsspec-2025.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
| 5 |
+
fsspec-2025.2.0.dist-info/licenses/LICENSE,sha256=LcNUls5TpzB5FcAIqESq1T53K0mzTN0ARFBnaRQH7JQ,1513
|
| 6 |
+
fsspec/__init__.py,sha256=l9MJaNNV2d4wKpCtMvXDr55n92DkdrAayGy3F9ICjzk,1998
|
| 7 |
+
fsspec/__pycache__/__init__.cpython-311.pyc,,
|
| 8 |
+
fsspec/__pycache__/_version.cpython-311.pyc,,
|
| 9 |
+
fsspec/__pycache__/archive.cpython-311.pyc,,
|
| 10 |
+
fsspec/__pycache__/asyn.cpython-311.pyc,,
|
| 11 |
+
fsspec/__pycache__/caching.cpython-311.pyc,,
|
| 12 |
+
fsspec/__pycache__/callbacks.cpython-311.pyc,,
|
| 13 |
+
fsspec/__pycache__/compression.cpython-311.pyc,,
|
| 14 |
+
fsspec/__pycache__/config.cpython-311.pyc,,
|
| 15 |
+
fsspec/__pycache__/conftest.cpython-311.pyc,,
|
| 16 |
+
fsspec/__pycache__/core.cpython-311.pyc,,
|
| 17 |
+
fsspec/__pycache__/dircache.cpython-311.pyc,,
|
| 18 |
+
fsspec/__pycache__/exceptions.cpython-311.pyc,,
|
| 19 |
+
fsspec/__pycache__/fuse.cpython-311.pyc,,
|
| 20 |
+
fsspec/__pycache__/generic.cpython-311.pyc,,
|
| 21 |
+
fsspec/__pycache__/gui.cpython-311.pyc,,
|
| 22 |
+
fsspec/__pycache__/json.cpython-311.pyc,,
|
| 23 |
+
fsspec/__pycache__/mapping.cpython-311.pyc,,
|
| 24 |
+
fsspec/__pycache__/parquet.cpython-311.pyc,,
|
| 25 |
+
fsspec/__pycache__/registry.cpython-311.pyc,,
|
| 26 |
+
fsspec/__pycache__/spec.cpython-311.pyc,,
|
| 27 |
+
fsspec/__pycache__/transaction.cpython-311.pyc,,
|
| 28 |
+
fsspec/__pycache__/utils.cpython-311.pyc,,
|
| 29 |
+
fsspec/_version.py,sha256=IE7d_vZlkju9WTb8xdQYMiqPyQOYnfC9HN9w8nHfkrY,417
|
| 30 |
+
fsspec/archive.py,sha256=vM6t_lgV6lBWbBYwpm3S4ofBQFQxUPr5KkDQrrQcQro,2411
|
| 31 |
+
fsspec/asyn.py,sha256=rsnCsFUmBZmKJqg9m-IDWInoQtE4wV0rGDZEXZwuU3c,36500
|
| 32 |
+
fsspec/caching.py,sha256=oHVy9zpy4Oqk5f1t3-Q31bbw0tsmfddGGKLJs__OdKA,32790
|
| 33 |
+
fsspec/callbacks.py,sha256=BDIwLzK6rr_0V5ch557fSzsivCElpdqhXr5dZ9Te-EE,9210
|
| 34 |
+
fsspec/compression.py,sha256=jCSUMJu-zSNyrusnHT0wKXgOd1tTJR6vM126i5SR5Zc,4865
|
| 35 |
+
fsspec/config.py,sha256=LF4Zmu1vhJW7Je9Q-cwkRc3xP7Rhyy7Xnwj26Z6sv2g,4279
|
| 36 |
+
fsspec/conftest.py,sha256=fVfx-NLrH_OZS1TIpYNoPzM7efEcMoL62reHOdYeFCA,1245
|
| 37 |
+
fsspec/core.py,sha256=bn-y3Mn9q8Gh3Ng_yAIDfIjyysQ95tuK78RlhlrqTb4,23828
|
| 38 |
+
fsspec/dircache.py,sha256=YzogWJrhEastHU7vWz-cJiJ7sdtLXFXhEpInGKd4EcM,2717
|
| 39 |
+
fsspec/exceptions.py,sha256=pauSLDMxzTJMOjvX1WEUK0cMyFkrFxpWJsyFywav7A8,331
|
| 40 |
+
fsspec/fuse.py,sha256=Q-3NOOyLqBfYa4Db5E19z_ZY36zzYHtIs1mOUasItBQ,10177
|
| 41 |
+
fsspec/generic.py,sha256=AFbo-mHBt5QJV1Aplg5CJuUiiJ4bNQhcKRuwkZJdWac,13761
|
| 42 |
+
fsspec/gui.py,sha256=xBnHL2-r0LVwhDAtnHoPpXts7jd4Z32peawCJiI-7lI,13975
|
| 43 |
+
fsspec/implementations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 44 |
+
fsspec/implementations/__pycache__/__init__.cpython-311.pyc,,
|
| 45 |
+
fsspec/implementations/__pycache__/arrow.cpython-311.pyc,,
|
| 46 |
+
fsspec/implementations/__pycache__/asyn_wrapper.cpython-311.pyc,,
|
| 47 |
+
fsspec/implementations/__pycache__/cache_mapper.cpython-311.pyc,,
|
| 48 |
+
fsspec/implementations/__pycache__/cache_metadata.cpython-311.pyc,,
|
| 49 |
+
fsspec/implementations/__pycache__/cached.cpython-311.pyc,,
|
| 50 |
+
fsspec/implementations/__pycache__/dask.cpython-311.pyc,,
|
| 51 |
+
fsspec/implementations/__pycache__/data.cpython-311.pyc,,
|
| 52 |
+
fsspec/implementations/__pycache__/dbfs.cpython-311.pyc,,
|
| 53 |
+
fsspec/implementations/__pycache__/dirfs.cpython-311.pyc,,
|
| 54 |
+
fsspec/implementations/__pycache__/ftp.cpython-311.pyc,,
|
| 55 |
+
fsspec/implementations/__pycache__/git.cpython-311.pyc,,
|
| 56 |
+
fsspec/implementations/__pycache__/github.cpython-311.pyc,,
|
| 57 |
+
fsspec/implementations/__pycache__/http.cpython-311.pyc,,
|
| 58 |
+
fsspec/implementations/__pycache__/jupyter.cpython-311.pyc,,
|
| 59 |
+
fsspec/implementations/__pycache__/libarchive.cpython-311.pyc,,
|
| 60 |
+
fsspec/implementations/__pycache__/local.cpython-311.pyc,,
|
| 61 |
+
fsspec/implementations/__pycache__/memory.cpython-311.pyc,,
|
| 62 |
+
fsspec/implementations/__pycache__/reference.cpython-311.pyc,,
|
| 63 |
+
fsspec/implementations/__pycache__/sftp.cpython-311.pyc,,
|
| 64 |
+
fsspec/implementations/__pycache__/smb.cpython-311.pyc,,
|
| 65 |
+
fsspec/implementations/__pycache__/tar.cpython-311.pyc,,
|
| 66 |
+
fsspec/implementations/__pycache__/webhdfs.cpython-311.pyc,,
|
| 67 |
+
fsspec/implementations/__pycache__/zip.cpython-311.pyc,,
|
| 68 |
+
fsspec/implementations/arrow.py,sha256=721Dikne_lV_0tlgk9jyKmHL6W-5MT0h2LKGvOYQTPI,8623
|
| 69 |
+
fsspec/implementations/asyn_wrapper.py,sha256=gmLy2voDAH9KRxhvd24UDPiOqX_NCK-3JY9rMX7R6Is,2935
|
| 70 |
+
fsspec/implementations/cache_mapper.py,sha256=W4wlxyPxZbSp9ItJ0pYRVBMh6bw9eFypgP6kUYuuiI4,2421
|
| 71 |
+
fsspec/implementations/cache_metadata.py,sha256=pcOJYcBQY5OaC7Yhw0F3wjg08QLYApGmoISCrbs59ks,8511
|
| 72 |
+
fsspec/implementations/cached.py,sha256=KA6c4jqrGeeg8WNPLsh8FkL3KeRAQtGLzKw18vSF1CI,32820
|
| 73 |
+
fsspec/implementations/dask.py,sha256=CXZbJzIVOhKV8ILcxuy3bTvcacCueAbyQxmvAkbPkrk,4466
|
| 74 |
+
fsspec/implementations/data.py,sha256=LDLczxRh8h7x39Zjrd-GgzdQHr78yYxDlrv2C9Uxb5E,1658
|
| 75 |
+
fsspec/implementations/dbfs.py,sha256=XwpotuS_ncz3XK1dkUteww9GnTja7HoY91c0m4GUfwI,15092
|
| 76 |
+
fsspec/implementations/dirfs.py,sha256=ymakitNNQ07tW76EShyw3rC9RvIDHl4gtuOhE_h1vUg,12032
|
| 77 |
+
fsspec/implementations/ftp.py,sha256=sorsczLp_2J3ukONsbZY-11sRZP6H5a3V7XXf6o6ip0,11936
|
| 78 |
+
fsspec/implementations/git.py,sha256=4SElW9U5d3k3_ITlvUAx59Yk7XLNRTqkGa2C3hCUkWM,3754
|
| 79 |
+
fsspec/implementations/github.py,sha256=eAn1kJ7VeWR6gVoVRLBYclF_rQDXSJU-xzMXpvPQWqs,8002
|
| 80 |
+
fsspec/implementations/http.py,sha256=d7G7_pRTMHouKE42lvRNHqB5u4XQi0dm4wb-6U_IiF4,29361
|
| 81 |
+
fsspec/implementations/jupyter.py,sha256=B2uj7OEm7yIk-vRSsO37_ND0t0EBvn4B-Su43ibN4Pg,3811
|
| 82 |
+
fsspec/implementations/libarchive.py,sha256=5_I2DiLXwQ1JC8x-K7jXu-tBwhO9dj7tFLnb0bTnVMQ,7102
|
| 83 |
+
fsspec/implementations/local.py,sha256=YvR9b2MndSQIHszAMUkFvN65eWVbIfoGJJjAeS43ZS4,15259
|
| 84 |
+
fsspec/implementations/memory.py,sha256=cLNrK9wk97sl4Tre9uVDXWj6mEHvvVVIgaVgNA5KVIg,10527
|
| 85 |
+
fsspec/implementations/reference.py,sha256=1VbyjAxq_8xHSQo2UV4ohuuoSAreB3OY4vjK05DnHsY,48646
|
| 86 |
+
fsspec/implementations/sftp.py,sha256=fMY9XZcmpjszQ2tCqO_TPaJesaeD_Dv7ptYzgUPGoO0,5631
|
| 87 |
+
fsspec/implementations/smb.py,sha256=5fhu8h06nOLBPh2c48aT7WBRqh9cEcbIwtyu06wTjec,15236
|
| 88 |
+
fsspec/implementations/tar.py,sha256=dam78Tp_CozybNqCY2JYgGBS3Uc9FuJUAT9oB0lolOs,4111
|
| 89 |
+
fsspec/implementations/webhdfs.py,sha256=G9wGywj7BkZk4Mu9zXu6HaDlEqX4F8Gw1i4k46CP_-o,16769
|
| 90 |
+
fsspec/implementations/zip.py,sha256=9LBMHPft2OutJl2Ft-r9u_z3GptLkc2n91ur2A3bCbg,6072
|
| 91 |
+
fsspec/json.py,sha256=65sQ0Y7mTj33u_Y4IId5up4abQ3bAel4E4QzbKMiQSg,3826
|
| 92 |
+
fsspec/mapping.py,sha256=m2ndB_gtRBXYmNJg0Ie1-BVR75TFleHmIQBzC-yWhjU,8343
|
| 93 |
+
fsspec/parquet.py,sha256=6ibAmG527L5JNFS0VO8BDNlxHdA3bVYqdByeiFgpUVM,19448
|
| 94 |
+
fsspec/registry.py,sha256=QFyMiUV6fnksETJuapNplf6YjkNRIdHSOyd95IqPZe8,11473
|
| 95 |
+
fsspec/spec.py,sha256=l7ZEbgLsnrFuS-yrGl9re6ia1Yts1_10RqGV_mT-5P8,76032
|
| 96 |
+
fsspec/tests/abstract/__init__.py,sha256=4xUJrv7gDgc85xAOz1p-V_K1hrsdMWTSa0rviALlJk8,10181
|
| 97 |
+
fsspec/tests/abstract/__pycache__/__init__.cpython-311.pyc,,
|
| 98 |
+
fsspec/tests/abstract/__pycache__/common.cpython-311.pyc,,
|
| 99 |
+
fsspec/tests/abstract/__pycache__/copy.cpython-311.pyc,,
|
| 100 |
+
fsspec/tests/abstract/__pycache__/get.cpython-311.pyc,,
|
| 101 |
+
fsspec/tests/abstract/__pycache__/mv.cpython-311.pyc,,
|
| 102 |
+
fsspec/tests/abstract/__pycache__/open.cpython-311.pyc,,
|
| 103 |
+
fsspec/tests/abstract/__pycache__/pipe.cpython-311.pyc,,
|
| 104 |
+
fsspec/tests/abstract/__pycache__/put.cpython-311.pyc,,
|
| 105 |
+
fsspec/tests/abstract/common.py,sha256=1GQwNo5AONzAnzZj0fWgn8NJPLXALehbsuGxS3FzWVU,4973
|
| 106 |
+
fsspec/tests/abstract/copy.py,sha256=gU5-d97U3RSde35Vp4RxPY4rWwL744HiSrJ8IBOp9-8,19967
|
| 107 |
+
fsspec/tests/abstract/get.py,sha256=vNR4HztvTR7Cj56AMo7_tx7TeYz1Jgr_2Wb8Lv-UiBY,20755
|
| 108 |
+
fsspec/tests/abstract/mv.py,sha256=k8eUEBIrRrGMsBY5OOaDXdGnQUKGwDIfQyduB6YD3Ns,1982
|
| 109 |
+
fsspec/tests/abstract/open.py,sha256=Fi2PBPYLbRqysF8cFm0rwnB41kMdQVYjq8cGyDXp3BU,329
|
| 110 |
+
fsspec/tests/abstract/pipe.py,sha256=LFzIrLCB5GLXf9rzFKJmE8AdG7LQ_h4bJo70r8FLPqM,402
|
| 111 |
+
fsspec/tests/abstract/put.py,sha256=7aih17OKB_IZZh1Mkq1eBDIjobhtMQmI8x-Pw-S_aZk,21201
|
| 112 |
+
fsspec/transaction.py,sha256=xliRG6U2Zf3khG4xcw9WiB-yAoqJSHEGK_VjHOdtgo0,2398
|
| 113 |
+
fsspec/utils.py,sha256=A11t25RnpiQ30RO6xeR0Qqlu3fGj8bnc40jg08tlYSI,22980
|
.venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: hatchling 1.27.0
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
.venv/lib/python3.11/site-packages/fsspec-2025.2.0.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 3-Clause License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2018, Martin Durant
|
| 4 |
+
All rights reserved.
|
| 5 |
+
|
| 6 |
+
Redistribution and use in source and binary forms, with or without
|
| 7 |
+
modification, are permitted provided that the following conditions are met:
|
| 8 |
+
|
| 9 |
+
* Redistributions of source code must retain the above copyright notice, this
|
| 10 |
+
list of conditions and the following disclaimer.
|
| 11 |
+
|
| 12 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 13 |
+
this list of conditions and the following disclaimer in the documentation
|
| 14 |
+
and/or other materials provided with the distribution.
|
| 15 |
+
|
| 16 |
+
* Neither the name of the copyright holder nor the names of its
|
| 17 |
+
contributors may be used to endorse or promote products derived from
|
| 18 |
+
this software without specific prior written permission.
|
| 19 |
+
|
| 20 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 21 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 22 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 23 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 24 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 25 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 26 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 27 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 28 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 29 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
.venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
.venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.2
|
| 2 |
+
Name: importlib_metadata
|
| 3 |
+
Version: 8.6.1
|
| 4 |
+
Summary: Read metadata from Python packages
|
| 5 |
+
Author-email: "Jason R. Coombs" <jaraco@jaraco.com>
|
| 6 |
+
Project-URL: Source, https://github.com/python/importlib_metadata
|
| 7 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 8 |
+
Classifier: Intended Audience :: Developers
|
| 9 |
+
Classifier: License :: OSI Approved :: Apache Software License
|
| 10 |
+
Classifier: Programming Language :: Python :: 3
|
| 11 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 12 |
+
Requires-Python: >=3.9
|
| 13 |
+
Description-Content-Type: text/x-rst
|
| 14 |
+
License-File: LICENSE
|
| 15 |
+
Requires-Dist: zipp>=3.20
|
| 16 |
+
Requires-Dist: typing-extensions>=3.6.4; python_version < "3.8"
|
| 17 |
+
Provides-Extra: test
|
| 18 |
+
Requires-Dist: pytest!=8.1.*,>=6; extra == "test"
|
| 19 |
+
Requires-Dist: importlib_resources>=1.3; python_version < "3.9" and extra == "test"
|
| 20 |
+
Requires-Dist: packaging; extra == "test"
|
| 21 |
+
Requires-Dist: pyfakefs; extra == "test"
|
| 22 |
+
Requires-Dist: flufl.flake8; extra == "test"
|
| 23 |
+
Requires-Dist: pytest-perf>=0.9.2; extra == "test"
|
| 24 |
+
Requires-Dist: jaraco.test>=5.4; extra == "test"
|
| 25 |
+
Provides-Extra: doc
|
| 26 |
+
Requires-Dist: sphinx>=3.5; extra == "doc"
|
| 27 |
+
Requires-Dist: jaraco.packaging>=9.3; extra == "doc"
|
| 28 |
+
Requires-Dist: rst.linker>=1.9; extra == "doc"
|
| 29 |
+
Requires-Dist: furo; extra == "doc"
|
| 30 |
+
Requires-Dist: sphinx-lint; extra == "doc"
|
| 31 |
+
Requires-Dist: jaraco.tidelift>=1.4; extra == "doc"
|
| 32 |
+
Provides-Extra: perf
|
| 33 |
+
Requires-Dist: ipython; extra == "perf"
|
| 34 |
+
Provides-Extra: check
|
| 35 |
+
Requires-Dist: pytest-checkdocs>=2.4; extra == "check"
|
| 36 |
+
Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check"
|
| 37 |
+
Provides-Extra: cover
|
| 38 |
+
Requires-Dist: pytest-cov; extra == "cover"
|
| 39 |
+
Provides-Extra: enabler
|
| 40 |
+
Requires-Dist: pytest-enabler>=2.2; extra == "enabler"
|
| 41 |
+
Provides-Extra: type
|
| 42 |
+
Requires-Dist: pytest-mypy; extra == "type"
|
| 43 |
+
|
| 44 |
+
.. image:: https://img.shields.io/pypi/v/importlib_metadata.svg
|
| 45 |
+
:target: https://pypi.org/project/importlib_metadata
|
| 46 |
+
|
| 47 |
+
.. image:: https://img.shields.io/pypi/pyversions/importlib_metadata.svg
|
| 48 |
+
|
| 49 |
+
.. image:: https://github.com/python/importlib_metadata/actions/workflows/main.yml/badge.svg
|
| 50 |
+
:target: https://github.com/python/importlib_metadata/actions?query=workflow%3A%22tests%22
|
| 51 |
+
:alt: tests
|
| 52 |
+
|
| 53 |
+
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
|
| 54 |
+
:target: https://github.com/astral-sh/ruff
|
| 55 |
+
:alt: Ruff
|
| 56 |
+
|
| 57 |
+
.. image:: https://readthedocs.org/projects/importlib-metadata/badge/?version=latest
|
| 58 |
+
:target: https://importlib-metadata.readthedocs.io/en/latest/?badge=latest
|
| 59 |
+
|
| 60 |
+
.. image:: https://img.shields.io/badge/skeleton-2024-informational
|
| 61 |
+
:target: https://blog.jaraco.com/skeleton
|
| 62 |
+
|
| 63 |
+
.. image:: https://tidelift.com/badges/package/pypi/importlib-metadata
|
| 64 |
+
:target: https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=readme
|
| 65 |
+
|
| 66 |
+
Library to access the metadata for a Python package.
|
| 67 |
+
|
| 68 |
+
This package supplies third-party access to the functionality of
|
| 69 |
+
`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_
|
| 70 |
+
including improvements added to subsequent Python versions.
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
Compatibility
|
| 74 |
+
=============
|
| 75 |
+
|
| 76 |
+
New features are introduced in this third-party library and later merged
|
| 77 |
+
into CPython. The following table indicates which versions of this library
|
| 78 |
+
were contributed to different versions in the standard library:
|
| 79 |
+
|
| 80 |
+
.. list-table::
|
| 81 |
+
:header-rows: 1
|
| 82 |
+
|
| 83 |
+
* - importlib_metadata
|
| 84 |
+
- stdlib
|
| 85 |
+
* - 7.0
|
| 86 |
+
- 3.13
|
| 87 |
+
* - 6.5
|
| 88 |
+
- 3.12
|
| 89 |
+
* - 4.13
|
| 90 |
+
- 3.11
|
| 91 |
+
* - 4.6
|
| 92 |
+
- 3.10
|
| 93 |
+
* - 1.4
|
| 94 |
+
- 3.8
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
Usage
|
| 98 |
+
=====
|
| 99 |
+
|
| 100 |
+
See the `online documentation <https://importlib-metadata.readthedocs.io/>`_
|
| 101 |
+
for usage details.
|
| 102 |
+
|
| 103 |
+
`Finder authors
|
| 104 |
+
<https://docs.python.org/3/reference/import.html#finders-and-loaders>`_ can
|
| 105 |
+
also add support for custom package installers. See the above documentation
|
| 106 |
+
for details.
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
Caveats
|
| 110 |
+
=======
|
| 111 |
+
|
| 112 |
+
This project primarily supports third-party packages installed by PyPA
|
| 113 |
+
tools (or other conforming packages). It does not support:
|
| 114 |
+
|
| 115 |
+
- Packages in the stdlib.
|
| 116 |
+
- Packages installed without metadata.
|
| 117 |
+
|
| 118 |
+
Project details
|
| 119 |
+
===============
|
| 120 |
+
|
| 121 |
+
* Project home: https://github.com/python/importlib_metadata
|
| 122 |
+
* Report bugs at: https://github.com/python/importlib_metadata/issues
|
| 123 |
+
* Code hosting: https://github.com/python/importlib_metadata
|
| 124 |
+
* Documentation: https://importlib-metadata.readthedocs.io/
|
| 125 |
+
|
| 126 |
+
For Enterprise
|
| 127 |
+
==============
|
| 128 |
+
|
| 129 |
+
Available as part of the Tidelift Subscription.
|
| 130 |
+
|
| 131 |
+
This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.
|
| 132 |
+
|
| 133 |
+
`Learn more <https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=referral&utm_campaign=github>`_.
|
.venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
importlib_metadata-8.6.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
importlib_metadata-8.6.1.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
| 3 |
+
importlib_metadata-8.6.1.dist-info/METADATA,sha256=F24ATbamOm1kp40IQGodtMLWMrH-fVEksdySlZk_KAY,4738
|
| 4 |
+
importlib_metadata-8.6.1.dist-info/RECORD,,
|
| 5 |
+
importlib_metadata-8.6.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
| 6 |
+
importlib_metadata-8.6.1.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
|
| 7 |
+
importlib_metadata/__init__.py,sha256=-Sk7aVqfmzLecdjSOpLKo1P_PegQanR__HsMMyEq0PI,35853
|
| 8 |
+
importlib_metadata/__pycache__/__init__.cpython-311.pyc,,
|
| 9 |
+
importlib_metadata/__pycache__/_adapters.cpython-311.pyc,,
|
| 10 |
+
importlib_metadata/__pycache__/_collections.cpython-311.pyc,,
|
| 11 |
+
importlib_metadata/__pycache__/_compat.cpython-311.pyc,,
|
| 12 |
+
importlib_metadata/__pycache__/_functools.cpython-311.pyc,,
|
| 13 |
+
importlib_metadata/__pycache__/_itertools.cpython-311.pyc,,
|
| 14 |
+
importlib_metadata/__pycache__/_meta.cpython-311.pyc,,
|
| 15 |
+
importlib_metadata/__pycache__/_text.cpython-311.pyc,,
|
| 16 |
+
importlib_metadata/__pycache__/diagnose.cpython-311.pyc,,
|
| 17 |
+
importlib_metadata/_adapters.py,sha256=9Y3FAlZuoo8pOMVLnKXm5Xx6hKgsdUQOXF5SkiDGqWo,3784
|
| 18 |
+
importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
|
| 19 |
+
importlib_metadata/_compat.py,sha256=VC5ZDLlT-BcshauCShdFJvMNLntJJfZzNK1meGa-enw,1313
|
| 20 |
+
importlib_metadata/_functools.py,sha256=bSbAqC9-2niWM9364FYBx9GWtetnJEfo4mdLv8uMl7c,2895
|
| 21 |
+
importlib_metadata/_itertools.py,sha256=nMvp9SfHAQ_JYwK4L2i64lr3GRXGlYlikGTVzWbys_E,5351
|
| 22 |
+
importlib_metadata/_meta.py,sha256=JzuqMG4za5MoaBPCPv61c26fUBdQPZ4by3pbaQA_E_o,1823
|
| 23 |
+
importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
|
| 24 |
+
importlib_metadata/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 25 |
+
importlib_metadata/compat/__pycache__/__init__.cpython-311.pyc,,
|
| 26 |
+
importlib_metadata/compat/__pycache__/py311.cpython-311.pyc,,
|
| 27 |
+
importlib_metadata/compat/__pycache__/py39.cpython-311.pyc,,
|
| 28 |
+
importlib_metadata/compat/py311.py,sha256=uqm-K-uohyj1042TH4a9Er_I5o7667DvulcD-gC_fSA,608
|
| 29 |
+
importlib_metadata/compat/py39.py,sha256=cPkMv6-0ilK-0Jw_Tkn0xYbOKJZc4WJKQHow0c2T44w,1102
|
| 30 |
+
importlib_metadata/diagnose.py,sha256=nkSRMiowlmkhLYhKhvCg9glmt_11Cox-EmLzEbqYTa8,379
|
| 31 |
+
importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
.venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (75.8.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
.venv/lib/python3.11/site-packages/importlib_metadata-8.6.1.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
importlib_metadata
|
.venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/METADATA
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: oauth2client
|
| 3 |
+
Version: 4.1.3
|
| 4 |
+
Summary: OAuth 2.0 client library
|
| 5 |
+
Home-page: http://github.com/google/oauth2client/
|
| 6 |
+
Author: Google Inc.
|
| 7 |
+
Author-email: jonwayne+oauth2client@google.com
|
| 8 |
+
License: Apache 2.0
|
| 9 |
+
Keywords: google oauth 2.0 http client
|
| 10 |
+
Platform: UNKNOWN
|
| 11 |
+
Classifier: Programming Language :: Python :: 2
|
| 12 |
+
Classifier: Programming Language :: Python :: 2.7
|
| 13 |
+
Classifier: Programming Language :: Python :: 3
|
| 14 |
+
Classifier: Programming Language :: Python :: 3.4
|
| 15 |
+
Classifier: Programming Language :: Python :: 3.5
|
| 16 |
+
Classifier: Development Status :: 7 - Inactive
|
| 17 |
+
Classifier: Intended Audience :: Developers
|
| 18 |
+
Classifier: License :: OSI Approved :: Apache Software License
|
| 19 |
+
Classifier: Operating System :: POSIX
|
| 20 |
+
Classifier: Topic :: Internet :: WWW/HTTP
|
| 21 |
+
Requires-Dist: httplib2 (>=0.9.1)
|
| 22 |
+
Requires-Dist: pyasn1 (>=0.1.7)
|
| 23 |
+
Requires-Dist: pyasn1-modules (>=0.0.5)
|
| 24 |
+
Requires-Dist: rsa (>=3.1.4)
|
| 25 |
+
Requires-Dist: six (>=1.6.1)
|
| 26 |
+
|
| 27 |
+
oauth2client is a client library for OAuth 2.0.
|
| 28 |
+
|
| 29 |
+
Note: oauth2client is now deprecated. No more features will be added to the
|
| 30 |
+
libraries and the core team is turning down support. We recommend you use
|
| 31 |
+
`google-auth <https://google-auth.readthedocs.io>`__ and
|
| 32 |
+
`oauthlib <http://oauthlib.readthedocs.io/>`__.
|
| 33 |
+
|
| 34 |
+
|
.venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/RECORD
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
oauth2client-4.1.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
oauth2client-4.1.3.dist-info/METADATA,sha256=JZWZEBXJhRlFYMHmep-HdaeRsY1jWD35JiWPYzSGdF4,1247
|
| 3 |
+
oauth2client-4.1.3.dist-info/RECORD,,
|
| 4 |
+
oauth2client-4.1.3.dist-info/WHEEL,sha256=gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk,110
|
| 5 |
+
oauth2client-4.1.3.dist-info/top_level.txt,sha256=omHPlSNz2suNBxXgtIrjYmhEGUmNTYAfPQ5lwQ69Nlc,13
|
| 6 |
+
oauth2client/__init__.py,sha256=TBa120J9aG9Gcu6t2PZYaitkua7M3k_vzkedK8ABJA0,1002
|
| 7 |
+
oauth2client/__pycache__/__init__.cpython-311.pyc,,
|
| 8 |
+
oauth2client/__pycache__/_helpers.cpython-311.pyc,,
|
| 9 |
+
oauth2client/__pycache__/_openssl_crypt.cpython-311.pyc,,
|
| 10 |
+
oauth2client/__pycache__/_pkce.cpython-311.pyc,,
|
| 11 |
+
oauth2client/__pycache__/_pure_python_crypt.cpython-311.pyc,,
|
| 12 |
+
oauth2client/__pycache__/_pycrypto_crypt.cpython-311.pyc,,
|
| 13 |
+
oauth2client/__pycache__/client.cpython-311.pyc,,
|
| 14 |
+
oauth2client/__pycache__/clientsecrets.cpython-311.pyc,,
|
| 15 |
+
oauth2client/__pycache__/crypt.cpython-311.pyc,,
|
| 16 |
+
oauth2client/__pycache__/file.cpython-311.pyc,,
|
| 17 |
+
oauth2client/__pycache__/service_account.cpython-311.pyc,,
|
| 18 |
+
oauth2client/__pycache__/tools.cpython-311.pyc,,
|
| 19 |
+
oauth2client/__pycache__/transport.cpython-311.pyc,,
|
| 20 |
+
oauth2client/_helpers.py,sha256=SamYkoAl9AJFpX4VDSFs36X69tdUmqzMwQAWuR-Khr8,10852
|
| 21 |
+
oauth2client/_openssl_crypt.py,sha256=SsdLrYE36SkJRpgyPl70cf9nQC2-HyBHJcEYSsG_tl8,4556
|
| 22 |
+
oauth2client/_pkce.py,sha256=XJaILUCZmTEI8wPBHQMRPo8NenOt2qYG4L7Dqrn57BI,2198
|
| 23 |
+
oauth2client/_pure_python_crypt.py,sha256=WllaU6FHNgqv9L_cGVmfcZbvgrnXn68O5buuvUrZx_w,6367
|
| 24 |
+
oauth2client/_pycrypto_crypt.py,sha256=hDYgYMc0Gh2rRA35C6YsQ16rYH_xhJaWBEEyJnfsvRo,4118
|
| 25 |
+
oauth2client/client.py,sha256=vguAS807amedrYxcdXaE3Y3FiuX9LdBXAavhjkQF1jE,83289
|
| 26 |
+
oauth2client/clientsecrets.py,sha256=ahMPXRucWfHbt7BdZynQrij_9HXy_tPBEfKfCKgjld8,5281
|
| 27 |
+
oauth2client/contrib/__init__.py,sha256=RgS87vH1KJhMUFArK1-bICLabNB6kcwM_Lx_mEkO45o,220
|
| 28 |
+
oauth2client/contrib/__pycache__/__init__.cpython-311.pyc,,
|
| 29 |
+
oauth2client/contrib/__pycache__/_appengine_ndb.cpython-311.pyc,,
|
| 30 |
+
oauth2client/contrib/__pycache__/_metadata.cpython-311.pyc,,
|
| 31 |
+
oauth2client/contrib/__pycache__/appengine.cpython-311.pyc,,
|
| 32 |
+
oauth2client/contrib/__pycache__/devshell.cpython-311.pyc,,
|
| 33 |
+
oauth2client/contrib/__pycache__/dictionary_storage.cpython-311.pyc,,
|
| 34 |
+
oauth2client/contrib/__pycache__/flask_util.cpython-311.pyc,,
|
| 35 |
+
oauth2client/contrib/__pycache__/gce.cpython-311.pyc,,
|
| 36 |
+
oauth2client/contrib/__pycache__/keyring_storage.cpython-311.pyc,,
|
| 37 |
+
oauth2client/contrib/__pycache__/multiprocess_file_storage.cpython-311.pyc,,
|
| 38 |
+
oauth2client/contrib/__pycache__/sqlalchemy.cpython-311.pyc,,
|
| 39 |
+
oauth2client/contrib/__pycache__/xsrfutil.cpython-311.pyc,,
|
| 40 |
+
oauth2client/contrib/_appengine_ndb.py,sha256=8l4erxmgtdK5mRrDu_NJaaWyU_o_fyvjFyCOPElvNtQ,5381
|
| 41 |
+
oauth2client/contrib/_metadata.py,sha256=Y-TwnItsOb1o2Abt1b3-yP5com3VrrUmwEeL-B3qUNs,4181
|
| 42 |
+
oauth2client/contrib/appengine.py,sha256=bPV7qY8GbK73qVVbOXVjumAI-K6img-C-dovWy8si9Q,33941
|
| 43 |
+
oauth2client/contrib/devshell.py,sha256=BdM3eX6nvR4J0cXb-nPx-VjrEqhQRlvNH1Z14R8gYgc,4991
|
| 44 |
+
oauth2client/contrib/dictionary_storage.py,sha256=TRxniVD5VzQUD0gUQ2fzyzlJAtMsaEoYOMkbZfklgqU,2278
|
| 45 |
+
oauth2client/contrib/django_util/__init__.py,sha256=HSuW5HW60Oty5uivnQ1KoRUD4g88NQCFsyYPOwOntNw,18152
|
| 46 |
+
oauth2client/contrib/django_util/__pycache__/__init__.cpython-311.pyc,,
|
| 47 |
+
oauth2client/contrib/django_util/__pycache__/apps.cpython-311.pyc,,
|
| 48 |
+
oauth2client/contrib/django_util/__pycache__/decorators.cpython-311.pyc,,
|
| 49 |
+
oauth2client/contrib/django_util/__pycache__/models.cpython-311.pyc,,
|
| 50 |
+
oauth2client/contrib/django_util/__pycache__/signals.cpython-311.pyc,,
|
| 51 |
+
oauth2client/contrib/django_util/__pycache__/site.cpython-311.pyc,,
|
| 52 |
+
oauth2client/contrib/django_util/__pycache__/storage.cpython-311.pyc,,
|
| 53 |
+
oauth2client/contrib/django_util/__pycache__/views.cpython-311.pyc,,
|
| 54 |
+
oauth2client/contrib/django_util/apps.py,sha256=S8wE_F8BJkyl0MKZfdB1wyIq39Zf4-rZwwCOhZR3k3E,1213
|
| 55 |
+
oauth2client/contrib/django_util/decorators.py,sha256=6caUSK4jvZz15jE2GL0XXC2o3zuRQoqEuup5KfvdRAw,5564
|
| 56 |
+
oauth2client/contrib/django_util/models.py,sha256=Q_bp__GHZNJ2tjIY8nuE4Z4bDE7O2JqAwiZtKLHZzrA,2742
|
| 57 |
+
oauth2client/contrib/django_util/signals.py,sha256=8RXmCgWXc9A5Se-ELQ-ny2uvCknBdQxB4fSpFo0ye70,1050
|
| 58 |
+
oauth2client/contrib/django_util/site.py,sha256=ywnit7c6MXN-MUhAB0T3IgLg0xa75dmrUGqke9zfJW8,960
|
| 59 |
+
oauth2client/contrib/django_util/storage.py,sha256=7XnyxjHydqEzBQUYhJRGzb0bqpmTS3b6iyF0wQODDCU,3070
|
| 60 |
+
oauth2client/contrib/django_util/views.py,sha256=lMlLk2yE6cs_O-OCiMxjyAGpdrAtnoajs6a1BUXN-U4,6785
|
| 61 |
+
oauth2client/contrib/flask_util.py,sha256=N9NhLOD7u4ZerJ1oOha8nj8uVZ_nNleqmndoTSvgd0M,19413
|
| 62 |
+
oauth2client/contrib/gce.py,sha256=lmcesVXQuiRk8LWg9wk9UsNpV_a4bmv1RVU3Q86MjK8,5431
|
| 63 |
+
oauth2client/contrib/keyring_storage.py,sha256=F4r96naObAPaJwrT_oZKCXu_b4GKBRASq21ND55EjrY,3091
|
| 64 |
+
oauth2client/contrib/multiprocess_file_storage.py,sha256=BIotaqL2rrruGIJbivZT1JG8Q_viL26RpFxL6b-ocUI,11679
|
| 65 |
+
oauth2client/contrib/sqlalchemy.py,sha256=nUHF369AFDth3a0G0MhovK1YSy7J1ETNioZrvVKbw_Q,5246
|
| 66 |
+
oauth2client/contrib/xsrfutil.py,sha256=KEQmQ29rkUkZldwVZ3QFfaX91prJOZLjvZP_PoygMBU,3470
|
| 67 |
+
oauth2client/crypt.py,sha256=7DjFdzNK4aeQ1FhXfwysf_iV6KVLnvVvMVIz6hckjoI,8447
|
| 68 |
+
oauth2client/file.py,sha256=YmIp9Ut4AVXHCKzZMzRoQHdk6unyCjfQcht_8brd4KU,2730
|
| 69 |
+
oauth2client/service_account.py,sha256=35g1easZKkx8kMcc7EJtRvR46JAz5WxyYJB7jNk4Tc0,28195
|
| 70 |
+
oauth2client/tools.py,sha256=8Oy5jSnvkLdxs88Xkoe7Nvy98536A7k0JYhDbE0yuNc,9069
|
| 71 |
+
oauth2client/transport.py,sha256=-RrkwCG1EtD6V68jMk_RhW2YMImjQXmPYRoJ3igKchY,10367
|
.venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: bdist_wheel (0.31.1)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py2-none-any
|
| 5 |
+
Tag: py3-none-any
|
| 6 |
+
|
.venv/lib/python3.11/site-packages/oauth2client-4.1.3.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
oauth2client
|
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libavformat-d296e685.so.59.27.100
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:71bbf605de259ea96b5cd8149c81a2cbb328d8a0c5e81389570177d508a3b4be
|
| 3 |
+
size 2571489
|
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libcrypto-8c1ab3ad.so.1.1
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d562e9ddc5621cad39f1d4a989fb233e6ebd59178b2a54fe06e8bc23a7389600
|
| 3 |
+
size 3481345
|
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libpng16-ef62451c.so.16.44.0
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:39e236462d06ff259b3d6b94eeda6c16311a139e5cc5a30a035f6ae8631d6319
|
| 3 |
+
size 1105201
|
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libquadmath-96973f99.so.0.0.0
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:934c22ded0e7d169c4d4678876c96051adf3d94545da962f60b41659b075da3b
|
| 3 |
+
size 247609
|
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libswresample-3e7db482.so.4.7.100
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e1870006793d8396a4aa7a6db3e228aa0a30ae4781a639787bc1dc78f76bc220
|
| 3 |
+
size 132417
|
.venv/lib/python3.11/site-packages/opencv_python_headless.libs/libswscale-95ddd674.so.6.7.100
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:72155ab5149130621f244a9c22259713208f25e0a5b295957479b9d3bc185be1
|
| 3 |
+
size 619945
|
.venv/lib/python3.11/site-packages/propcache/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""propcache: An accelerated property cache for Python classes."""
|
| 2 |
+
|
| 3 |
+
from typing import TYPE_CHECKING
|
| 4 |
+
|
| 5 |
+
_PUBLIC_API = ("cached_property", "under_cached_property")
|
| 6 |
+
|
| 7 |
+
__version__ = "0.2.1"
|
| 8 |
+
__all__ = ()
|
| 9 |
+
|
| 10 |
+
# Imports have moved to `propcache.api` in 0.2.0+.
|
| 11 |
+
# This module is now a facade for the API.
|
| 12 |
+
if TYPE_CHECKING:
|
| 13 |
+
from .api import cached_property as cached_property # noqa: F401
|
| 14 |
+
from .api import under_cached_property as under_cached_property # noqa: F401
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _import_facade(attr: str) -> object:
|
| 18 |
+
"""Import the public API from the `api` module."""
|
| 19 |
+
if attr in _PUBLIC_API:
|
| 20 |
+
from . import api # pylint: disable=import-outside-toplevel
|
| 21 |
+
|
| 22 |
+
return getattr(api, attr)
|
| 23 |
+
raise AttributeError(f"module '{__package__}' has no attribute '{attr}'")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _dir_facade() -> list[str]:
|
| 27 |
+
"""Include the public API in the module's dir() output."""
|
| 28 |
+
return [*_PUBLIC_API, *globals().keys()]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
__getattr__ = _import_facade
|
| 32 |
+
__dir__ = _dir_facade
|
.venv/lib/python3.11/site-packages/propcache/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.44 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/propcache/__pycache__/_helpers.cpython-311.pyc
ADDED
|
Binary file (1.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/propcache/__pycache__/_helpers_py.cpython-311.pyc
ADDED
|
Binary file (3.45 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/propcache/__pycache__/api.cpython-311.pyc
ADDED
|
Binary file (359 Bytes). View file
|
|
|