diff options
author | robot-contrib <robot-contrib@yandex-team.com> | 2023-10-19 17:11:31 +0300 |
---|---|---|
committer | robot-contrib <robot-contrib@yandex-team.com> | 2023-10-19 18:26:04 +0300 |
commit | b9fe236a503791a3a7b37d4ef5f466225218996c (patch) | |
tree | c2f80019399b393ddf0450d0f91fc36478af8bea | |
parent | 44dd27d0a2ae37c80d97a95581951d1d272bd7df (diff) | |
download | ydb-b9fe236a503791a3a7b37d4ef5f466225218996c.tar.gz |
Update contrib/python/traitlets/py3 to 5.11.2
61 files changed, 4480 insertions, 226 deletions
diff --git a/contrib/python/argcomplete/py2/Authors.rst b/contrib/python/argcomplete/py2/Authors.rst new file mode 100644 index 0000000000..3c0c589908 --- /dev/null +++ b/contrib/python/argcomplete/py2/Authors.rst @@ -0,0 +1 @@ +Andrey Kislyuk <kislyuk@gmail.com> diff --git a/contrib/python/argcomplete/py2/LICENSE.rst b/contrib/python/argcomplete/py2/LICENSE.rst new file mode 100644 index 0000000000..f433b1a53f --- /dev/null +++ b/contrib/python/argcomplete/py2/LICENSE.rst @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/contrib/python/argcomplete/py2/README.rst b/contrib/python/argcomplete/py2/README.rst new file mode 100644 index 0000000000..86c8d44f5c --- /dev/null +++ b/contrib/python/argcomplete/py2/README.rst @@ -0,0 +1,367 @@ +argcomplete - Bash tab completion for argparse +============================================== +*Tab complete all the things!* + +Argcomplete provides easy, extensible command line tab completion of arguments for your Python script. + +It makes two assumptions: + +* You're using bash as your shell (limited support for zsh, fish, and tcsh is available) +* You're using `argparse <http://docs.python.org/3/library/argparse.html>`_ to manage your command line arguments/options + +Argcomplete is particularly useful if your program has lots of options or subparsers, and if your program can +dynamically suggest completions for your argument/option values (for example, if the user is browsing resources over +the network). + +Installation +------------ +:: + + pip3 install argcomplete + activate-global-python-argcomplete + +See `Activating global completion`_ below for details about the second step (or if it reports an error). + +Refresh your bash environment (start a new shell or ``source /etc/profile``). + +Synopsis +-------- +Python code (e.g. ``my-awesome-script``): + +.. code-block:: python + + #!/usr/bin/env python + # PYTHON_ARGCOMPLETE_OK + import argcomplete, argparse + parser = argparse.ArgumentParser() + ... + argcomplete.autocomplete(parser) + args = parser.parse_args() + ... + +Shellcode (only necessary if global completion is not activated - see `Global completion`_ below), to be put in e.g. ``.bashrc``:: + + eval "$(register-python-argcomplete my-awesome-script)" + +argcomplete.autocomplete(*parser*) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method is the entry point to the module. It must be called **after** ArgumentParser construction is complete, but +**before** the ``ArgumentParser.parse_args()`` method is called. The method looks for an environment variable that the +completion hook shellcode sets, and if it's there, collects completions, prints them to the output stream (fd 8 by +default), and exits. Otherwise, it returns to the caller immediately. + +.. admonition:: Side effects + + Argcomplete gets completions by running your program. It intercepts the execution flow at the moment + ``argcomplete.autocomplete()`` is called. After sending completions, it exits using ``exit_method`` (``os._exit`` + by default). This means if your program has any side effects that happen before ``argcomplete`` is called, those + side effects will happen every time the user presses ``<TAB>`` (although anything your program prints to stdout or + stderr will be suppressed). For this reason it's best to construct the argument parser and call + ``argcomplete.autocomplete()`` as early as possible in your execution flow. + +.. admonition:: Performance + + If the program takes a long time to get to the point where ``argcomplete.autocomplete()`` is called, the tab completion + process will feel sluggish, and the user may lose confidence in it. So it's also important to minimize the startup time + of the program up to that point (for example, by deferring initialization or importing of large modules until after + parsing options). + +Specifying completers +--------------------- +You can specify custom completion functions for your options and arguments. Two styles are supported: callable and +readline-style. Callable completers are simpler. They are called with the following keyword arguments: + +* ``prefix``: The prefix text of the last word before the cursor on the command line. + For dynamic completers, this can be used to reduce the work required to generate possible completions. +* ``action``: The ``argparse.Action`` instance that this completer was called for. +* ``parser``: The ``argparse.ArgumentParser`` instance that the action was taken by. +* ``parsed_args``: The result of argument parsing so far (the ``argparse.Namespace`` args object normally returned by + ``ArgumentParser.parse_args()``). + +Completers should return their completions as a list of strings. An example completer for names of environment +variables might look like this: + +.. code-block:: python + + def EnvironCompleter(**kwargs): + return os.environ + +To specify a completer for an argument or option, set the ``completer`` attribute of its associated action. An easy +way to do this at definition time is: + +.. code-block:: python + + from argcomplete.completers import EnvironCompleter + + parser = argparse.ArgumentParser() + parser.add_argument("--env-var1").completer = EnvironCompleter + parser.add_argument("--env-var2").completer = EnvironCompleter + argcomplete.autocomplete(parser) + +If you specify the ``choices`` keyword for an argparse option or argument (and don't specify a completer), it will be +used for completions. + +A completer that is initialized with a set of all possible choices of values for its action might look like this: + +.. code-block:: python + + class ChoicesCompleter(object): + def __init__(self, choices): + self.choices = choices + + def __call__(self, **kwargs): + return self.choices + +The following two ways to specify a static set of choices are equivalent for completion purposes: + +.. code-block:: python + + from argcomplete.completers import ChoicesCompleter + + parser.add_argument("--protocol", choices=('http', 'https', 'ssh', 'rsync', 'wss')) + parser.add_argument("--proto").completer=ChoicesCompleter(('http', 'https', 'ssh', 'rsync', 'wss')) + +Note that if you use the ``choices=<completions>`` option, argparse will show +all these choices in the ``--help`` output by default. To prevent this, set +``metavar`` (like ``parser.add_argument("--protocol", metavar="PROTOCOL", +choices=('http', 'https', 'ssh', 'rsync', 'wss'))``). + +The following `script <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ uses +``parsed_args`` and `Requests <http://python-requests.org/>`_ to query GitHub for publicly known members of an +organization and complete their names, then prints the member description: + +.. code-block:: python + + #!/usr/bin/env python + # PYTHON_ARGCOMPLETE_OK + import argcomplete, argparse, requests, pprint + + def github_org_members(prefix, parsed_args, **kwargs): + resource = "https://api.github.com/orgs/{org}/members".format(org=parsed_args.organization) + return (member['login'] for member in requests.get(resource).json() if member['login'].startswith(prefix)) + + parser = argparse.ArgumentParser() + parser.add_argument("--organization", help="GitHub organization") + parser.add_argument("--member", help="GitHub member").completer = github_org_members + + argcomplete.autocomplete(parser) + args = parser.parse_args() + + pprint.pprint(requests.get("https://api.github.com/users/{m}".format(m=args.member)).json()) + +`Try it <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ like this:: + + ./describe_github_user.py --organization heroku --member <TAB> + +If you have a useful completer to add to the `completer library +<https://github.com/kislyuk/argcomplete/blob/master/argcomplete/completers.py>`_, send a pull request! + +Readline-style completers +~~~~~~~~~~~~~~~~~~~~~~~~~ +The readline_ module defines a completer protocol in rlcompleter_. Readline-style completers are also supported by +argcomplete, so you can use the same completer object both in an interactive readline-powered shell and on the bash +command line. For example, you can use the readline-style completer provided by IPython_ to get introspective +completions like you would get in the IPython shell: + +.. _readline: http://docs.python.org/3/library/readline.html +.. _rlcompleter: http://docs.python.org/3/library/rlcompleter.html#completer-objects +.. _IPython: http://ipython.org/ + +.. code-block:: python + + import IPython + parser.add_argument("--python-name").completer = IPython.core.completer.Completer() + +``argcomplete.CompletionFinder.rl_complete`` can also be used to plug in an argparse parser as a readline completer. + +Printing warnings in completers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Normal stdout/stderr output is suspended when argcomplete runs. Sometimes, though, when the user presses ``<TAB>``, it's +appropriate to print information about why completions generation failed. To do this, use ``warn``: + +.. code-block:: python + + from argcomplete import warn + + def AwesomeWebServiceCompleter(prefix, **kwargs): + if login_failed: + warn("Please log in to Awesome Web Service to use autocompletion") + return completions + +Using a custom completion validator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +By default, argcomplete validates your completions by checking if they start with the prefix given to the completer. You +can override this validation check by supplying the ``validator`` keyword to ``argcomplete.autocomplete()``: + +.. code-block:: python + + def my_validator(current_input, keyword_to_check_against): + # Pass through ALL options even if they don't all start with 'current_input' + return True + + argcomplete.autocomplete(parser, validator=my_validator) + +Global completion +----------------- +In global completion mode, you don't have to register each argcomplete-capable executable separately. Instead, bash +will look for the string **PYTHON_ARGCOMPLETE_OK** in the first 1024 bytes of any executable that it's running +completion for, and if it's found, follow the rest of the argcomplete protocol as described above. + +Additionally, completion is activated for scripts run as ``python <script>`` and ``python -m <module>``. +This also works for alternate Python versions (e.g. ``python3`` and ``pypy``), as long as that version of Python has +argcomplete installed. + +.. admonition:: Bash version compatibility + + Global completion requires bash support for ``complete -D``, which was introduced in bash 4.2. On OS X or older Linux + systems, you will need to update bash to use this feature. Check the version of the running copy of bash with + ``echo $BASH_VERSION``. On OS X, install bash via `Homebrew <http://brew.sh/>`_ (``brew install bash``), add + ``/usr/local/bin/bash`` to ``/etc/shells``, and run ``chsh`` to change your shell. + + Global completion is not currently compatible with zsh. + +.. note:: If you use setuptools/distribute ``scripts`` or ``entry_points`` directives to package your module, + argcomplete will follow the wrapper scripts to their destination and look for ``PYTHON_ARGCOMPLETE_OK`` in the + destination code. + +If you choose not to use global completion, or ship a bash completion module that depends on argcomplete, you must +register your script explicitly using ``eval "$(register-python-argcomplete my-awesome-script)"``. Standard bash +completion registration roules apply: namely, the script name is passed directly to ``complete``, meaning it is only tab +completed when invoked exactly as it was registered. In the above example, ``my-awesome-script`` must be on the path, +and the user must be attempting to complete it by that name. The above line alone would **not** allow you to complete +``./my-awesome-script``, or ``/path/to/my-awesome-script``. + + +Activating global completion +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The script ``activate-global-python-argcomplete`` will try to install the file +``bash_completion.d/python-argcomplete`` (`see on GitHub`_) into an appropriate location on your system +(``/etc/bash_completion.d/`` or ``~/.bash_completion.d/``). If it +fails, but you know the correct location of your bash completion scripts directory, you can specify it with ``--dest``:: + + activate-global-python-argcomplete --dest=/path/to/bash_completion.d + +Otherwise, you can redirect its shellcode output into a file:: + + activate-global-python-argcomplete --dest=- > file + +The file's contents should then be sourced in e.g. ``~/.bashrc``. + +.. _`see on GitHub`: https://github.com/kislyuk/argcomplete/blob/master/argcomplete/bash_completion.d/python-argcomplete + +Zsh Support +------------ +To activate completions for zsh you need to have ``bashcompinit`` enabled in zsh:: + + autoload -U bashcompinit + bashcompinit + +Afterwards you can enable completion for your scripts with ``register-python-argcomplete``:: + + eval "$(register-python-argcomplete my-awesome-script)" + +Tcsh Support +------------ +To activate completions for tcsh use:: + + eval `register-python-argcomplete --shell tcsh my-awesome-script` + +The ``python-argcomplete-tcsh`` script provides completions for tcsh. +The following is an example of the tcsh completion syntax for +``my-awesome-script`` emitted by ``register-python-argcomplete``:: + + complete my-awesome-script 'p@*@`python-argcomplete-tcsh my-awesome-script`@' + +Fish Support +------------ +To activate completions for fish use:: + + register-python-argcomplete --shell fish my-awesome-script | source + +or create new completion file, e.g:: + + register-python-argcomplete --shell fish my-awesome-script > ~/.config/fish/completions/my-awesome-script.fish + +Completion Description For Fish +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +By default help string is added as completion description. + +.. image:: docs/fish_help_string.png + +You can disable this feature by removing ``_ARGCOMPLETE_DFS`` variable, e.g:: + + register-python-argcomplete --shell fish my-awesome-script | grep -v _ARGCOMPLETE_DFS | . + +Git Bash Support +---------------- +Due to limitations of file descriptor inheritance on Windows, +Git Bash not supported out of the box. You can opt in to using +temporary files instead of file descriptors for for IPC +by setting the environment variable ``ARGCOMPLETE_USE_TEMPFILES``, +e.g. by adding ``export ARGCOMPLETE_USE_TEMPFILES=1`` to ``~/.bashrc``. + +For full support, consider using Bash with the +Windows Subsystem for Linux (WSL). + +External argcomplete script +--------------------------- +To register an argcomplete script for an arbitrary name, the ``--external-argcomplete-script`` argument of the ``register-python-argcomplete`` script can be used:: + + eval "$(register-python-argcomplete --external-argcomplete-script /path/to/script arbitrary-name)" + +This allows, for example, to use the auto completion functionality of argcomplete for an application not written in Python. +The command line interface of this program must be additionally implemented in a Python script with argparse and argcomplete and whenever the application is called the registered external argcomplete script is used for auto completion. + +This option can also be used in combination with the other supported shells. + +Python Support +-------------- +Argcomplete requires Python 2.7 or 3.5+. + +Common Problems +--------------- +If global completion is not completing your script, bash may have registered a +default completion function:: + + $ complete | grep my-awesome-script + complete -F _minimal my-awesome-script + +You can fix this by restarting your shell, or by running +``complete -r my-awesome-script``. + +Debugging +--------- +Set the ``_ARC_DEBUG`` variable in your shell to enable verbose debug output every time argcomplete runs. This will +disrupt the command line composition state of your terminal, but make it possible to see the internal state of the +completer if it encounters problems. + +Acknowledgments +--------------- +Inspired and informed by the optcomplete_ module by Martin Blais. + +.. _optcomplete: http://pypi.python.org/pypi/optcomplete + +Links +----- +* `Project home page (GitHub) <https://github.com/kislyuk/argcomplete>`_ +* `Documentation <https://kislyuk.github.io/argcomplete/>`_ +* `Package distribution (PyPI) <https://pypi.python.org/pypi/argcomplete>`_ +* `Change log <https://github.com/kislyuk/argcomplete/blob/master/Changes.rst>`_ +* `xontrib-argcomplete <https://github.com/anki-code/xontrib-argcomplete>`_ - support argcomplete in `xonsh <https://github.com/xonsh/xonsh>`_ shell + +Bugs +~~~~ +Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/kislyuk/argcomplete/issues>`_. + +License +------- +Licensed under the terms of the `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. + +.. image:: https://github.com/kislyuk/argcomplete/workflows/Python%20package/badge.svg + :target: https://github.com/kislyuk/argcomplete/actions +.. image:: https://codecov.io/github/kislyuk/argcomplete/coverage.svg?branch=master + :target: https://codecov.io/github/kislyuk/argcomplete?branch=master +.. image:: https://img.shields.io/pypi/v/argcomplete.svg + :target: https://pypi.python.org/pypi/argcomplete +.. image:: https://img.shields.io/pypi/l/argcomplete.svg + :target: https://pypi.python.org/pypi/argcomplete diff --git a/contrib/python/argcomplete/py3/.dist-info/METADATA b/contrib/python/argcomplete/py3/.dist-info/METADATA new file mode 100644 index 0000000000..7d995caedb --- /dev/null +++ b/contrib/python/argcomplete/py3/.dist-info/METADATA @@ -0,0 +1,351 @@ +Metadata-Version: 2.1 +Name: argcomplete +Version: 3.1.2 +Summary: Bash tab completion for argparse +Home-page: https://github.com/kislyuk/argcomplete +Author: Andrey Kislyuk +Author-email: kislyuk@gmail.com +License: Apache Software License +Project-URL: Documentation, https://kislyuk.github.io/argcomplete +Project-URL: Source Code, https://github.com/kislyuk/argcomplete +Project-URL: Issue Tracker, https://github.com/kislyuk/argcomplete/issues +Project-URL: Change Log, https://github.com/kislyuk/argcomplete/blob/master/Changes.rst +Platform: MacOS X +Platform: Posix +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Development Status :: 5 - Production/Stable +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Shells +Classifier: Topic :: Terminals +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +License-File: NOTICE +Requires-Dist: importlib-metadata <7,>=0.23 ; python_version < "3.8" +Provides-Extra: test +Requires-Dist: coverage ; extra == 'test' +Requires-Dist: pexpect ; extra == 'test' +Requires-Dist: wheel ; extra == 'test' +Requires-Dist: ruff ; extra == 'test' +Requires-Dist: mypy ; extra == 'test' + +argcomplete - Bash/zsh tab completion for argparse +================================================== +*Tab complete all the things!* + +Argcomplete provides easy, extensible command line tab completion of arguments for your Python application. + +It makes two assumptions: + +* You're using bash or zsh as your shell +* You're using `argparse <http://docs.python.org/3/library/argparse.html>`_ to manage your command line arguments/options + +Argcomplete is particularly useful if your program has lots of options or subparsers, and if your program can +dynamically suggest completions for your argument/option values (for example, if the user is browsing resources over +the network). + +Installation +------------ +:: + + pip install argcomplete + activate-global-python-argcomplete + +See `Activating global completion`_ below for details about the second step. + +Refresh your shell environment (start a new shell). + +Synopsis +-------- +Add the ``PYTHON_ARGCOMPLETE_OK`` marker and a call to ``argcomplete.autocomplete()`` to your Python application as +follows: + +.. code-block:: python + + #!/usr/bin/env python + # PYTHON_ARGCOMPLETE_OK + import argcomplete, argparse + parser = argparse.ArgumentParser() + ... + argcomplete.autocomplete(parser) + args = parser.parse_args() + ... + +Register your Python application with your shell's completion framework by running ``register-python-argcomplete``:: + + eval "$(register-python-argcomplete my-python-app)" + +Quotes are significant; the registration will fail without them. See `Global completion`_ below for a way to enable +argcomplete generally without registering each application individually. + +argcomplete.autocomplete(*parser*) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method is the entry point to the module. It must be called **after** ArgumentParser construction is complete, but +**before** the ``ArgumentParser.parse_args()`` method is called. The method looks for an environment variable that the +completion hook shellcode sets, and if it's there, collects completions, prints them to the output stream (fd 8 by +default), and exits. Otherwise, it returns to the caller immediately. + +.. admonition:: Side effects + + Argcomplete gets completions by running your program. It intercepts the execution flow at the moment + ``argcomplete.autocomplete()`` is called. After sending completions, it exits using ``exit_method`` (``os._exit`` + by default). This means if your program has any side effects that happen before ``argcomplete`` is called, those + side effects will happen every time the user presses ``<TAB>`` (although anything your program prints to stdout or + stderr will be suppressed). For this reason it's best to construct the argument parser and call + ``argcomplete.autocomplete()`` as early as possible in your execution flow. + +.. admonition:: Performance + + If the program takes a long time to get to the point where ``argcomplete.autocomplete()`` is called, the tab completion + process will feel sluggish, and the user may lose confidence in it. So it's also important to minimize the startup time + of the program up to that point (for example, by deferring initialization or importing of large modules until after + parsing options). + +Specifying completers +--------------------- +You can specify custom completion functions for your options and arguments. Two styles are supported: callable and +readline-style. Callable completers are simpler. They are called with the following keyword arguments: + +* ``prefix``: The prefix text of the last word before the cursor on the command line. + For dynamic completers, this can be used to reduce the work required to generate possible completions. +* ``action``: The ``argparse.Action`` instance that this completer was called for. +* ``parser``: The ``argparse.ArgumentParser`` instance that the action was taken by. +* ``parsed_args``: The result of argument parsing so far (the ``argparse.Namespace`` args object normally returned by + ``ArgumentParser.parse_args()``). + +Completers can return their completions as an iterable of strings or a mapping (dict) of strings to their +descriptions (zsh will display the descriptions as context help alongside completions). An example completer for names +of environment variables might look like this: + +.. code-block:: python + + def EnvironCompleter(**kwargs): + return os.environ + +To specify a completer for an argument or option, set the ``completer`` attribute of its associated action. An easy +way to do this at definition time is: + +.. code-block:: python + + from argcomplete.completers import EnvironCompleter + + parser = argparse.ArgumentParser() + parser.add_argument("--env-var1").completer = EnvironCompleter + parser.add_argument("--env-var2").completer = EnvironCompleter + argcomplete.autocomplete(parser) + +If you specify the ``choices`` keyword for an argparse option or argument (and don't specify a completer), it will be +used for completions. + +A completer that is initialized with a set of all possible choices of values for its action might look like this: + +.. code-block:: python + + class ChoicesCompleter(object): + def __init__(self, choices): + self.choices = choices + + def __call__(self, **kwargs): + return self.choices + +The following two ways to specify a static set of choices are equivalent for completion purposes: + +.. code-block:: python + + from argcomplete.completers import ChoicesCompleter + + parser.add_argument("--protocol", choices=('http', 'https', 'ssh', 'rsync', 'wss')) + parser.add_argument("--proto").completer=ChoicesCompleter(('http', 'https', 'ssh', 'rsync', 'wss')) + +Note that if you use the ``choices=<completions>`` option, argparse will show +all these choices in the ``--help`` output by default. To prevent this, set +``metavar`` (like ``parser.add_argument("--protocol", metavar="PROTOCOL", +choices=('http', 'https', 'ssh', 'rsync', 'wss'))``). + +The following `script <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ uses +``parsed_args`` and `Requests <http://python-requests.org/>`_ to query GitHub for publicly known members of an +organization and complete their names, then prints the member description: + +.. code-block:: python + + #!/usr/bin/env python + # PYTHON_ARGCOMPLETE_OK + import argcomplete, argparse, requests, pprint + + def github_org_members(prefix, parsed_args, **kwargs): + resource = "https://api.github.com/orgs/{org}/members".format(org=parsed_args.organization) + return (member['login'] for member in requests.get(resource).json() if member['login'].startswith(prefix)) + + parser = argparse.ArgumentParser() + parser.add_argument("--organization", help="GitHub organization") + parser.add_argument("--member", help="GitHub member").completer = github_org_members + + argcomplete.autocomplete(parser) + args = parser.parse_args() + + pprint.pprint(requests.get("https://api.github.com/users/{m}".format(m=args.member)).json()) + +`Try it <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ like this:: + + ./describe_github_user.py --organization heroku --member <TAB> + +If you have a useful completer to add to the `completer library +<https://github.com/kislyuk/argcomplete/blob/master/argcomplete/completers.py>`_, send a pull request! + +Readline-style completers +~~~~~~~~~~~~~~~~~~~~~~~~~ +The readline_ module defines a completer protocol in rlcompleter_. Readline-style completers are also supported by +argcomplete, so you can use the same completer object both in an interactive readline-powered shell and on the command +line. For example, you can use the readline-style completer provided by IPython_ to get introspective completions like +you would get in the IPython shell: + +.. _readline: http://docs.python.org/3/library/readline.html +.. _rlcompleter: http://docs.python.org/3/library/rlcompleter.html#completer-objects +.. _IPython: http://ipython.org/ + +.. code-block:: python + + import IPython + parser.add_argument("--python-name").completer = IPython.core.completer.Completer() + +``argcomplete.CompletionFinder.rl_complete`` can also be used to plug in an argparse parser as a readline completer. + +Printing warnings in completers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Normal stdout/stderr output is suspended when argcomplete runs. Sometimes, though, when the user presses ``<TAB>``, it's +appropriate to print information about why completions generation failed. To do this, use ``warn``: + +.. code-block:: python + + from argcomplete import warn + + def AwesomeWebServiceCompleter(prefix, **kwargs): + if login_failed: + warn("Please log in to Awesome Web Service to use autocompletion") + return completions + +Using a custom completion validator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +By default, argcomplete validates your completions by checking if they start with the prefix given to the completer. You +can override this validation check by supplying the ``validator`` keyword to ``argcomplete.autocomplete()``: + +.. code-block:: python + + def my_validator(completion_candidate, current_input): + """Complete non-prefix substring matches.""" + return current_input in completion_candidate + + argcomplete.autocomplete(parser, validator=my_validator) + +Global completion +----------------- +In global completion mode, you don't have to register each argcomplete-capable executable separately. Instead, the shell +will look for the string **PYTHON_ARGCOMPLETE_OK** in the first 1024 bytes of any executable that it's running +completion for, and if it's found, follow the rest of the argcomplete protocol as described above. + +Additionally, completion is activated for scripts run as ``python <script>`` and ``python -m <module>``. If you're using +multiple Python versions on the same system, the version being used to run the script must have argcomplete installed. + +.. admonition:: Bash version compatibility + + When using bash, global completion requires bash support for ``complete -D``, which was introduced in bash 4.2. Since + Mac OS ships with an outdated version of Bash (3.2), you can either use zsh or install a newer version of bash using + `Homebrew <http://brew.sh/>`_ (``brew install bash`` - you will also need to add ``/usr/local/bin/bash`` to + ``/etc/shells``, and run ``chsh`` to change your shell). You can check the version of the running copy of bash with + ``echo $BASH_VERSION``. + +.. note:: If you use setuptools/distribute ``scripts`` or ``entry_points`` directives to package your module, + argcomplete will follow the wrapper scripts to their destination and look for ``PYTHON_ARGCOMPLETE_OK`` in the + destination code. + +If you choose not to use global completion, or ship a completion module that depends on argcomplete, you must register +your script explicitly using ``eval "$(register-python-argcomplete my-python-app)"``. Standard completion module +registration rules apply: namely, the script name is passed directly to ``complete``, meaning it is only tab completed +when invoked exactly as it was registered. In the above example, ``my-python-app`` must be on the path, and the user +must be attempting to complete it by that name. The above line alone would **not** allow you to complete +``./my-python-app``, or ``/path/to/my-python-app``. + +Activating global completion +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The script ``activate-global-python-argcomplete`` installs the global completion script +`bash_completion.d/_python-argcomplete <https://github.com/kislyuk/argcomplete/blob/master/argcomplete/bash_completion.d/_python-argcomplete>`_ +into an appropriate location on your system for both bash and zsh. The specific location depends on your platform and +whether you installed argcomplete system-wide using ``sudo`` or locally (into your user's home directory). + +Zsh Support +----------- +Argcomplete supports zsh. On top of plain completions like in bash, zsh allows you to see argparse help strings as +completion descriptions. All shellcode included with argcomplete is compatible with both bash and zsh, so the same +completer commands ``activate-global-python-argcomplete`` and ``eval "$(register-python-argcomplete my-python-app)"`` +work for zsh as well. + +Python Support +-------------- +Argcomplete requires Python 3.7+. + +Support for other shells +------------------------ +Argcomplete maintainers provide support only for the bash and zsh shells on Linux and MacOS. For resources related to +other shells and platforms, including fish, tcsh, xonsh, powershell, and Windows, please see the +`contrib <https://github.com/kislyuk/argcomplete/tree/develop/contrib>`_ directory. + +Common Problems +--------------- +If global completion is not completing your script, bash may have registered a default completion function:: + + $ complete | grep my-python-app + complete -F _minimal my-python-app + +You can fix this by restarting your shell, or by running ``complete -r my-python-app``. + +Debugging +--------- +Set the ``_ARC_DEBUG`` variable in your shell to enable verbose debug output every time argcomplete runs. This will +disrupt the command line composition state of your terminal, but make it possible to see the internal state of the +completer if it encounters problems. + +Acknowledgments +--------------- +Inspired and informed by the optcomplete_ module by Martin Blais. + +.. _optcomplete: http://pypi.python.org/pypi/optcomplete + +Links +----- +* `Project home page (GitHub) <https://github.com/kislyuk/argcomplete>`_ +* `Documentation <https://kislyuk.github.io/argcomplete/>`_ +* `Package distribution (PyPI) <https://pypi.python.org/pypi/argcomplete>`_ +* `Change log <https://github.com/kislyuk/argcomplete/blob/master/Changes.rst>`_ + +Bugs +~~~~ +Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/kislyuk/argcomplete/issues>`_. + +License +------- +Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the +`Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE +files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License. + +.. image:: https://github.com/kislyuk/argcomplete/workflows/Python%20package/badge.svg + :target: https://github.com/kislyuk/argcomplete/actions +.. image:: https://codecov.io/github/kislyuk/argcomplete/coverage.svg?branch=master + :target: https://codecov.io/github/kislyuk/argcomplete?branch=master +.. image:: https://img.shields.io/pypi/v/argcomplete.svg + :target: https://pypi.python.org/pypi/argcomplete +.. image:: https://img.shields.io/pypi/l/argcomplete.svg + :target: https://pypi.python.org/pypi/argcomplete diff --git a/contrib/python/argcomplete/py3/.dist-info/top_level.txt b/contrib/python/argcomplete/py3/.dist-info/top_level.txt new file mode 100644 index 0000000000..e60624ffee --- /dev/null +++ b/contrib/python/argcomplete/py3/.dist-info/top_level.txt @@ -0,0 +1 @@ +argcomplete diff --git a/contrib/python/argcomplete/py3/Authors.rst b/contrib/python/argcomplete/py3/Authors.rst new file mode 100644 index 0000000000..3c0c589908 --- /dev/null +++ b/contrib/python/argcomplete/py3/Authors.rst @@ -0,0 +1 @@ +Andrey Kislyuk <kislyuk@gmail.com> diff --git a/contrib/python/argcomplete/py3/LICENSE.rst b/contrib/python/argcomplete/py3/LICENSE.rst new file mode 100644 index 0000000000..f433b1a53f --- /dev/null +++ b/contrib/python/argcomplete/py3/LICENSE.rst @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/contrib/python/argcomplete/py3/NOTICE b/contrib/python/argcomplete/py3/NOTICE new file mode 100644 index 0000000000..0f13a0709a --- /dev/null +++ b/contrib/python/argcomplete/py3/NOTICE @@ -0,0 +1,4 @@ +argcomplete is a free open source library that integrates Python applications with Bash and Zsh shell completion. +The argcomplete project is staffed by volunteers. If you are using this library in a for-profit project, please +contribute to argcomplete development and maintenance using the "Sponsor" button on the argcomplete GitHub project page, +https://github.com/kislyuk/argcomplete. diff --git a/contrib/python/argcomplete/py3/README.rst b/contrib/python/argcomplete/py3/README.rst new file mode 100644 index 0000000000..aaf9e1a122 --- /dev/null +++ b/contrib/python/argcomplete/py3/README.rst @@ -0,0 +1,306 @@ +argcomplete - Bash/zsh tab completion for argparse +================================================== +*Tab complete all the things!* + +Argcomplete provides easy, extensible command line tab completion of arguments for your Python application. + +It makes two assumptions: + +* You're using bash or zsh as your shell +* You're using `argparse <http://docs.python.org/3/library/argparse.html>`_ to manage your command line arguments/options + +Argcomplete is particularly useful if your program has lots of options or subparsers, and if your program can +dynamically suggest completions for your argument/option values (for example, if the user is browsing resources over +the network). + +Installation +------------ +:: + + pip install argcomplete + activate-global-python-argcomplete + +See `Activating global completion`_ below for details about the second step. + +Refresh your shell environment (start a new shell). + +Synopsis +-------- +Add the ``PYTHON_ARGCOMPLETE_OK`` marker and a call to ``argcomplete.autocomplete()`` to your Python application as +follows: + +.. code-block:: python + + #!/usr/bin/env python + # PYTHON_ARGCOMPLETE_OK + import argcomplete, argparse + parser = argparse.ArgumentParser() + ... + argcomplete.autocomplete(parser) + args = parser.parse_args() + ... + +Register your Python application with your shell's completion framework by running ``register-python-argcomplete``:: + + eval "$(register-python-argcomplete my-python-app)" + +Quotes are significant; the registration will fail without them. See `Global completion`_ below for a way to enable +argcomplete generally without registering each application individually. + +argcomplete.autocomplete(*parser*) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method is the entry point to the module. It must be called **after** ArgumentParser construction is complete, but +**before** the ``ArgumentParser.parse_args()`` method is called. The method looks for an environment variable that the +completion hook shellcode sets, and if it's there, collects completions, prints them to the output stream (fd 8 by +default), and exits. Otherwise, it returns to the caller immediately. + +.. admonition:: Side effects + + Argcomplete gets completions by running your program. It intercepts the execution flow at the moment + ``argcomplete.autocomplete()`` is called. After sending completions, it exits using ``exit_method`` (``os._exit`` + by default). This means if your program has any side effects that happen before ``argcomplete`` is called, those + side effects will happen every time the user presses ``<TAB>`` (although anything your program prints to stdout or + stderr will be suppressed). For this reason it's best to construct the argument parser and call + ``argcomplete.autocomplete()`` as early as possible in your execution flow. + +.. admonition:: Performance + + If the program takes a long time to get to the point where ``argcomplete.autocomplete()`` is called, the tab completion + process will feel sluggish, and the user may lose confidence in it. So it's also important to minimize the startup time + of the program up to that point (for example, by deferring initialization or importing of large modules until after + parsing options). + +Specifying completers +--------------------- +You can specify custom completion functions for your options and arguments. Two styles are supported: callable and +readline-style. Callable completers are simpler. They are called with the following keyword arguments: + +* ``prefix``: The prefix text of the last word before the cursor on the command line. + For dynamic completers, this can be used to reduce the work required to generate possible completions. +* ``action``: The ``argparse.Action`` instance that this completer was called for. +* ``parser``: The ``argparse.ArgumentParser`` instance that the action was taken by. +* ``parsed_args``: The result of argument parsing so far (the ``argparse.Namespace`` args object normally returned by + ``ArgumentParser.parse_args()``). + +Completers can return their completions as an iterable of strings or a mapping (dict) of strings to their +descriptions (zsh will display the descriptions as context help alongside completions). An example completer for names +of environment variables might look like this: + +.. code-block:: python + + def EnvironCompleter(**kwargs): + return os.environ + +To specify a completer for an argument or option, set the ``completer`` attribute of its associated action. An easy +way to do this at definition time is: + +.. code-block:: python + + from argcomplete.completers import EnvironCompleter + + parser = argparse.ArgumentParser() + parser.add_argument("--env-var1").completer = EnvironCompleter + parser.add_argument("--env-var2").completer = EnvironCompleter + argcomplete.autocomplete(parser) + +If you specify the ``choices`` keyword for an argparse option or argument (and don't specify a completer), it will be +used for completions. + +A completer that is initialized with a set of all possible choices of values for its action might look like this: + +.. code-block:: python + + class ChoicesCompleter(object): + def __init__(self, choices): + self.choices = choices + + def __call__(self, **kwargs): + return self.choices + +The following two ways to specify a static set of choices are equivalent for completion purposes: + +.. code-block:: python + + from argcomplete.completers import ChoicesCompleter + + parser.add_argument("--protocol", choices=('http', 'https', 'ssh', 'rsync', 'wss')) + parser.add_argument("--proto").completer=ChoicesCompleter(('http', 'https', 'ssh', 'rsync', 'wss')) + +Note that if you use the ``choices=<completions>`` option, argparse will show +all these choices in the ``--help`` output by default. To prevent this, set +``metavar`` (like ``parser.add_argument("--protocol", metavar="PROTOCOL", +choices=('http', 'https', 'ssh', 'rsync', 'wss'))``). + +The following `script <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ uses +``parsed_args`` and `Requests <http://python-requests.org/>`_ to query GitHub for publicly known members of an +organization and complete their names, then prints the member description: + +.. code-block:: python + + #!/usr/bin/env python + # PYTHON_ARGCOMPLETE_OK + import argcomplete, argparse, requests, pprint + + def github_org_members(prefix, parsed_args, **kwargs): + resource = "https://api.github.com/orgs/{org}/members".format(org=parsed_args.organization) + return (member['login'] for member in requests.get(resource).json() if member['login'].startswith(prefix)) + + parser = argparse.ArgumentParser() + parser.add_argument("--organization", help="GitHub organization") + parser.add_argument("--member", help="GitHub member").completer = github_org_members + + argcomplete.autocomplete(parser) + args = parser.parse_args() + + pprint.pprint(requests.get("https://api.github.com/users/{m}".format(m=args.member)).json()) + +`Try it <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ like this:: + + ./describe_github_user.py --organization heroku --member <TAB> + +If you have a useful completer to add to the `completer library +<https://github.com/kislyuk/argcomplete/blob/master/argcomplete/completers.py>`_, send a pull request! + +Readline-style completers +~~~~~~~~~~~~~~~~~~~~~~~~~ +The readline_ module defines a completer protocol in rlcompleter_. Readline-style completers are also supported by +argcomplete, so you can use the same completer object both in an interactive readline-powered shell and on the command +line. For example, you can use the readline-style completer provided by IPython_ to get introspective completions like +you would get in the IPython shell: + +.. _readline: http://docs.python.org/3/library/readline.html +.. _rlcompleter: http://docs.python.org/3/library/rlcompleter.html#completer-objects +.. _IPython: http://ipython.org/ + +.. code-block:: python + + import IPython + parser.add_argument("--python-name").completer = IPython.core.completer.Completer() + +``argcomplete.CompletionFinder.rl_complete`` can also be used to plug in an argparse parser as a readline completer. + +Printing warnings in completers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Normal stdout/stderr output is suspended when argcomplete runs. Sometimes, though, when the user presses ``<TAB>``, it's +appropriate to print information about why completions generation failed. To do this, use ``warn``: + +.. code-block:: python + + from argcomplete import warn + + def AwesomeWebServiceCompleter(prefix, **kwargs): + if login_failed: + warn("Please log in to Awesome Web Service to use autocompletion") + return completions + +Using a custom completion validator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +By default, argcomplete validates your completions by checking if they start with the prefix given to the completer. You +can override this validation check by supplying the ``validator`` keyword to ``argcomplete.autocomplete()``: + +.. code-block:: python + + def my_validator(completion_candidate, current_input): + """Complete non-prefix substring matches.""" + return current_input in completion_candidate + + argcomplete.autocomplete(parser, validator=my_validator) + +Global completion +----------------- +In global completion mode, you don't have to register each argcomplete-capable executable separately. Instead, the shell +will look for the string **PYTHON_ARGCOMPLETE_OK** in the first 1024 bytes of any executable that it's running +completion for, and if it's found, follow the rest of the argcomplete protocol as described above. + +Additionally, completion is activated for scripts run as ``python <script>`` and ``python -m <module>``. If you're using +multiple Python versions on the same system, the version being used to run the script must have argcomplete installed. + +.. admonition:: Bash version compatibility + + When using bash, global completion requires bash support for ``complete -D``, which was introduced in bash 4.2. Since + Mac OS ships with an outdated version of Bash (3.2), you can either use zsh or install a newer version of bash using + `Homebrew <http://brew.sh/>`_ (``brew install bash`` - you will also need to add ``/usr/local/bin/bash`` to + ``/etc/shells``, and run ``chsh`` to change your shell). You can check the version of the running copy of bash with + ``echo $BASH_VERSION``. + +.. note:: If you use setuptools/distribute ``scripts`` or ``entry_points`` directives to package your module, + argcomplete will follow the wrapper scripts to their destination and look for ``PYTHON_ARGCOMPLETE_OK`` in the + destination code. + +If you choose not to use global completion, or ship a completion module that depends on argcomplete, you must register +your script explicitly using ``eval "$(register-python-argcomplete my-python-app)"``. Standard completion module +registration rules apply: namely, the script name is passed directly to ``complete``, meaning it is only tab completed +when invoked exactly as it was registered. In the above example, ``my-python-app`` must be on the path, and the user +must be attempting to complete it by that name. The above line alone would **not** allow you to complete +``./my-python-app``, or ``/path/to/my-python-app``. + +Activating global completion +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The script ``activate-global-python-argcomplete`` installs the global completion script +`bash_completion.d/_python-argcomplete <https://github.com/kislyuk/argcomplete/blob/master/argcomplete/bash_completion.d/_python-argcomplete>`_ +into an appropriate location on your system for both bash and zsh. The specific location depends on your platform and +whether you installed argcomplete system-wide using ``sudo`` or locally (into your user's home directory). + +Zsh Support +----------- +Argcomplete supports zsh. On top of plain completions like in bash, zsh allows you to see argparse help strings as +completion descriptions. All shellcode included with argcomplete is compatible with both bash and zsh, so the same +completer commands ``activate-global-python-argcomplete`` and ``eval "$(register-python-argcomplete my-python-app)"`` +work for zsh as well. + +Python Support +-------------- +Argcomplete requires Python 3.7+. + +Support for other shells +------------------------ +Argcomplete maintainers provide support only for the bash and zsh shells on Linux and MacOS. For resources related to +other shells and platforms, including fish, tcsh, xonsh, powershell, and Windows, please see the +`contrib <https://github.com/kislyuk/argcomplete/tree/develop/contrib>`_ directory. + +Common Problems +--------------- +If global completion is not completing your script, bash may have registered a default completion function:: + + $ complete | grep my-python-app + complete -F _minimal my-python-app + +You can fix this by restarting your shell, or by running ``complete -r my-python-app``. + +Debugging +--------- +Set the ``_ARC_DEBUG`` variable in your shell to enable verbose debug output every time argcomplete runs. This will +disrupt the command line composition state of your terminal, but make it possible to see the internal state of the +completer if it encounters problems. + +Acknowledgments +--------------- +Inspired and informed by the optcomplete_ module by Martin Blais. + +.. _optcomplete: http://pypi.python.org/pypi/optcomplete + +Links +----- +* `Project home page (GitHub) <https://github.com/kislyuk/argcomplete>`_ +* `Documentation <https://kislyuk.github.io/argcomplete/>`_ +* `Package distribution (PyPI) <https://pypi.python.org/pypi/argcomplete>`_ +* `Change log <https://github.com/kislyuk/argcomplete/blob/master/Changes.rst>`_ + +Bugs +~~~~ +Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/kislyuk/argcomplete/issues>`_. + +License +------- +Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the +`Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE +files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License. + +.. image:: https://github.com/kislyuk/argcomplete/workflows/Python%20package/badge.svg + :target: https://github.com/kislyuk/argcomplete/actions +.. image:: https://codecov.io/github/kislyuk/argcomplete/coverage.svg?branch=master + :target: https://codecov.io/github/kislyuk/argcomplete?branch=master +.. image:: https://img.shields.io/pypi/v/argcomplete.svg + :target: https://pypi.python.org/pypi/argcomplete +.. image:: https://img.shields.io/pypi/l/argcomplete.svg + :target: https://pypi.python.org/pypi/argcomplete diff --git a/contrib/python/argcomplete/py3/argcomplete/__init__.py b/contrib/python/argcomplete/py3/argcomplete/__init__.py new file mode 100644 index 0000000000..9bd90f1f56 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. +# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info. + +from . import completers +from .completers import ChoicesCompleter, DirectoriesCompleter, EnvironCompleter, FilesCompleter, SuppressCompleter +from .exceptions import ArgcompleteException +from .finders import CompletionFinder, ExclusiveCompletionFinder, safe_actions +from .io import debug, mute_stderr, warn +from .lexers import split_line +from .shell_integration import shellcode + +autocomplete = CompletionFinder() +autocomplete.__doc__ = """ Use this to access argcomplete. See :meth:`argcomplete.CompletionFinder.__call__()`. """ diff --git a/contrib/python/argcomplete/py3/argcomplete/_check_console_script.py b/contrib/python/argcomplete/py3/argcomplete/_check_console_script.py new file mode 100644 index 0000000000..ed1e3b34f4 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/_check_console_script.py @@ -0,0 +1,73 @@ +""" +Utility for locating the module (or package's __init__.py) +associated with a given console_script name +and verifying it contains the PYTHON_ARGCOMPLETE_OK marker. + +Such scripts are automatically generated and cannot contain +the marker themselves, so we defer to the containing module or package. + +For more information on setuptools console_scripts, see +https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation + +Intended to be invoked by argcomplete's global completion function. +""" +import os +import sys + +try: + from importlib.metadata import entry_points as importlib_entry_points + from importlib.metadata import EntryPoint + use_entry_points_backport = False +except ImportError: + from importlib_metadata import entry_points as importlib_entry_points # type:ignore + from importlib_metadata import EntryPoint # type:ignore + use_entry_points_backport = True + +from ._check_module import ArgcompleteMarkerNotFound, find +from typing import Iterable + + +def main(): + # Argument is the full path to the console script. + script_path = sys.argv[1] + + # Find the module and function names that correspond to this + # assuming it is actually a console script. + name = os.path.basename(script_path) + + entry_points : Iterable[EntryPoint] = importlib_entry_points() # type:ignore + + # The importlib_metadata backport returns a tuple of entry point objects + # whereas the official library returns a SelectableGroups object + # Python 3.12+ behaves like the importlib_metadata backport + if not use_entry_points_backport and sys.version_info < (3, 12): + entry_points = entry_points["console_scripts"] # type:ignore + + entry_points = [ep for ep in entry_points \ + if ep.name == name and ep.group == "console_scripts" ] # type:ignore + + if not entry_points: + raise ArgcompleteMarkerNotFound("no entry point found matching script") + entry_point = entry_points[0] + module_name, function_name = entry_point.value.split(":", 1) + + # Check this looks like the script we really expected. + with open(script_path) as f: + script = f.read() + if "from {} import {}".format(module_name, function_name) not in script: + raise ArgcompleteMarkerNotFound("does not appear to be a console script") + if "sys.exit({}())".format(function_name) not in script: + raise ArgcompleteMarkerNotFound("does not appear to be a console script") + + # Look for the argcomplete marker in the script it imports. + with open(find(module_name, return_package=True)) as f: + head = f.read(1024) + if "PYTHON_ARGCOMPLETE_OK" not in head: + raise ArgcompleteMarkerNotFound("marker not found") + + +if __name__ == "__main__": + try: + main() + except ArgcompleteMarkerNotFound as e: + sys.exit(str(e)) diff --git a/contrib/python/argcomplete/py3/argcomplete/_check_module.py b/contrib/python/argcomplete/py3/argcomplete/_check_module.py new file mode 100644 index 0000000000..4958f0267f --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/_check_module.py @@ -0,0 +1,89 @@ +""" +Utility for locating a module (or package's __main__.py) with a given name +and verifying it contains the PYTHON_ARGCOMPLETE_OK marker. + +The module name should be specified in a form usable with `python -m`. + +Intended to be invoked by argcomplete's global completion function. +""" +import os +import sys +import tokenize + +try: + from importlib.util import find_spec # type:ignore +except ImportError: + import typing as t + from collections import namedtuple + from imp import find_module + + ModuleSpec = namedtuple("ModuleSpec", ["origin", "has_location", "submodule_search_locations"]) + + def find_spec( # type:ignore + name: str, + package: t.Optional[str] = None, + ) -> t.Optional[ModuleSpec]: + """Minimal implementation as required by `find`.""" + try: + f, path, _ = find_module(name) + except ImportError: + return None + has_location = path is not None + if f is None: + return ModuleSpec(None, has_location, [path]) + f.close() + return ModuleSpec(path, has_location, None) + + +class ArgcompleteMarkerNotFound(RuntimeError): + pass + + +def find(name, return_package=False): + names = name.split(".") + spec = find_spec(names[0]) + if spec is None: + raise ArgcompleteMarkerNotFound('no module named "{}"'.format(names[0])) + if not spec.has_location: + raise ArgcompleteMarkerNotFound("cannot locate file") + if spec.submodule_search_locations is None: + if len(names) != 1: + raise ArgcompleteMarkerNotFound("{} is not a package".format(names[0])) + return spec.origin + if len(spec.submodule_search_locations) != 1: + raise ArgcompleteMarkerNotFound("expecting one search location") + path = os.path.join(spec.submodule_search_locations[0], *names[1:]) + if os.path.isdir(path): + filename = "__main__.py" + if return_package: + filename = "__init__.py" + return os.path.join(path, filename) + else: + return path + ".py" + + +def main(): + try: + name = sys.argv[1] + except IndexError: + raise ArgcompleteMarkerNotFound("missing argument on the command line") + + filename = find(name) + + try: + fp = tokenize.open(filename) + except OSError: + raise ArgcompleteMarkerNotFound("cannot open file") + + with fp: + head = fp.read(1024) + + if "PYTHON_ARGCOMPLETE_OK" not in head: + raise ArgcompleteMarkerNotFound("marker not found") + + +if __name__ == "__main__": + try: + main() + except ArgcompleteMarkerNotFound as e: + sys.exit(str(e)) diff --git a/contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete b/contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete new file mode 100644 index 0000000000..4c7edda3c3 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/bash_completion.d/_python-argcomplete @@ -0,0 +1,236 @@ +#compdef -P * + +# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. +# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info. + +# Note: both the leading underscore in the name of this file and the first line (compdef) are required by zsh + +# Copy of __expand_tilde_by_ref from bash-completion +# ZSH implementation added +__python_argcomplete_expand_tilde_by_ref () { + if [ -n "${ZSH_VERSION-}" ]; then + if [ "${(P)1[1]}" = "~" ]; then + eval $1="${(P)1/#\~/$HOME}"; + fi + else + if [ "${!1:0:1}" = "~" ]; then + if [ "${!1}" != "${!1//\/}" ]; then + eval $1="${!1/%\/*}"/'${!1#*/}'; + else + eval $1="${!1}"; + fi; + fi + fi +} + +# Run something, muting output or redirecting it to the debug stream +# depending on the value of _ARC_DEBUG. +# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC. +__python_argcomplete_run() { + if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then + __python_argcomplete_run_inner "$@" + return + fi + local tmpfile="$(mktemp)" + _ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@" + local code=$? + cat "$tmpfile" + rm "$tmpfile" + return $code +} + +__python_argcomplete_run_inner() { + if [[ -z "${_ARC_DEBUG-}" ]]; then + "$@" 8>&1 9>&2 1>/dev/null 2>&1 + else + "$@" 8>&1 9>&2 1>&9 2>&1 + fi +} + +__python_argcomplete_upshift_bash_rematch() { + if [[ -z "${ZSH_VERSION-}" ]]; then + _BASH_REMATCH=( "" "${BASH_REMATCH[@]}" ) + else + _BASH_REMATCH=( "${BASH_REMATCH[@]}" ) + fi +} + +# This function scans the beginning of an executable file provided as the first +# argument ($1) for certain indicators, specified by the second argument ($2), +# or the "target". There are three possible targets: "interpreter", +# "magic_string", and "easy_install". If the target is "interpreter", the +# function matches the interpreter line, alongside any optional interpreter +# arguments. If the target is "magic_string", a match is attempted for the +# "PYTHON_ARGCOMPLETE_OK" magic string, indicating that the file should be run +# to get completions. If the target is "easy_install", the function matches either +# "PBR Generated" or any of the "EASY-INSTALL" scripts (either SCRIPT, +# ENTRY-SCRIPT, or DEV-SCRIPT). In all cases, only the first kilobyte of +# the file is searched. The regex matches are returned in BASH_REMATCH, +# indexed starting at 1, regardless of the shell in use. +__python_argcomplete_scan_head() { + local file="$1" + local target="$2" + + if [[ -n "${ZSH_VERSION-}" ]]; then + read -r -k 1024 -u 0 < "$file"; + else + read -r -N 1024 < "$file" + fi + + # Since ZSH does not support a -n option, we + # trim all characters after the first line in both shells + if [[ "$target" = "interpreter" ]]; then + read -r <<< "$REPLY" + fi + + local regex + + case "$target" in + magic_string) regex='PYTHON_ARGCOMPLETE_OK' ;; + easy_install) regex="(PBR Generated)|(EASY-INSTALL-(SCRIPT|ENTRY-SCRIPT|DEV-SCRIPT))" ;; + asdf) regex="asdf exec " ;; + interpreter) regex='^#!(.*)$' ;; + esac + + local ret="" + if [[ "$REPLY" =~ $regex ]]; then + ret=1 + fi + + __python_argcomplete_upshift_bash_rematch + + [[ -n $ret ]] +} + +__python_argcomplete_scan_head_noerr() { + __python_argcomplete_scan_head "$@" 2>/dev/null +} + +__python_argcomplete_which() { + if [[ -n "${ZSH_VERSION-}" ]]; then + whence -p "$@" + else + type -P "$@" + fi +} + +_python_argcomplete_global() { + + if [[ -n "${ZSH_VERSION-}" ]]; then + # Store result of a regex match in the + # BASH_REMATCH variable rather than MATCH + setopt local_options BASH_REMATCH + fi + + # 1-based version of BASH_REMATCH. Modifying BASH_REMATCH + # directly causes older versions of Bash to exit + local _BASH_REMATCH=""; + + local executable="" + + # req_argv contains the arguments to the completion + # indexed from 1 (regardless of the shell.) In Bash, + # the zeroth index is empty + local req_argv=() + + if [[ -z "${ZSH_VERSION-}" ]]; then + executable=$1 + req_argv=( "" "${COMP_WORDS[@]:1}" ) + __python_argcomplete_expand_tilde_by_ref executable + else + # TODO: check if we should call _default or use a different condition here + if [[ "$service" != "-default-" ]]; then + return + fi + executable="${words[1]}" + req_argv=( "${words[@]:1}" ) + fi + + local ARGCOMPLETE=0 + if [[ "$executable" == python* ]] || [[ "$executable" == pypy* ]]; then + if [[ "${req_argv[1]}" == -m ]]; then + if __python_argcomplete_run "$executable" -m argcomplete._check_module "${req_argv[2]}"; then + ARGCOMPLETE=3 + else + return + fi + fi + if [[ $ARGCOMPLETE == 0 ]]; then + local potential_path="${req_argv[1]}" + __python_argcomplete_expand_tilde_by_ref potential_path + if [[ -f "$potential_path" ]] && __python_argcomplete_scan_head_noerr "$potential_path" magic_string; then + req_argv[1]="$potential_path" # not expanded in __python_argcomplete_run + ARGCOMPLETE=2 + else + return + fi + fi + elif __python_argcomplete_which "$executable" >/dev/null 2>&1; then + local SCRIPT_NAME=$(__python_argcomplete_which "$executable") + __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" interpreter + if (__python_argcomplete_which pyenv && [[ "$SCRIPT_NAME" = $(pyenv root)/shims/* ]]) >/dev/null 2>&1; then + local SCRIPT_NAME=$(pyenv which "$executable") + fi + if (__python_argcomplete_which asdf && __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" asdf) >/dev/null 2>&1; then + local SCRIPT_NAME=$(asdf which "$executable") + fi + if __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" magic_string; then + ARGCOMPLETE=1 + elif __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" interpreter; then + __python_argcomplete_upshift_bash_rematch + local interpreter="${_BASH_REMATCH[2]}" + + if [[ -n "${ZSH_VERSION-}" ]]; then + interpreter=($=interpreter) + else + interpreter=($interpreter) + fi + + if (__python_argcomplete_scan_head_noerr "$SCRIPT_NAME" easy_install \ + && "${interpreter[@]}" "$(__python_argcomplete_which python-argcomplete-check-easy-install-script)" "$SCRIPT_NAME") >/dev/null 2>&1; then + ARGCOMPLETE=1 + elif __python_argcomplete_run "${interpreter[@]}" -m argcomplete._check_console_script "$SCRIPT_NAME"; then + ARGCOMPLETE=1 + fi + fi + fi + + if [[ $ARGCOMPLETE != 0 ]]; then + local IFS=$'\013' + if [[ -n "${ZSH_VERSION-}" ]]; then + local completions + completions=($(IFS="$IFS" \ + COMP_LINE="$BUFFER" \ + COMP_POINT="$CURSOR" \ + _ARGCOMPLETE=$ARGCOMPLETE \ + _ARGCOMPLETE_SHELL="zsh" \ + _ARGCOMPLETE_SUPPRESS_SPACE=1 \ + __python_argcomplete_run "$executable" "${(@)req_argv[1, ${ARGCOMPLETE}-1]}")) + _describe "$executable" completions + else + COMPREPLY=($(IFS="$IFS" \ + COMP_LINE="$COMP_LINE" \ + COMP_POINT="$COMP_POINT" \ + COMP_TYPE="$COMP_TYPE" \ + _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \ + _ARGCOMPLETE=$ARGCOMPLETE \ + _ARGCOMPLETE_SHELL="bash" \ + _ARGCOMPLETE_SUPPRESS_SPACE=1 \ + __python_argcomplete_run "$executable" "${req_argv[@]:1:${ARGCOMPLETE}-1}")) + if [[ $? != 0 ]]; then + unset COMPREPLY + elif [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then + compopt -o nospace + fi + fi + elif [[ -n "${ZSH_VERSION-}" ]]; then + _default + else + type -t _completion_loader | grep -q 'function' && _completion_loader "$@" + fi +} +if [[ -z "${ZSH_VERSION-}" ]]; then + complete -o default -o bashdefault -D -F _python_argcomplete_global +else + compdef _python_argcomplete_global -P '*' +fi diff --git a/contrib/python/argcomplete/py3/argcomplete/completers.py b/contrib/python/argcomplete/py3/argcomplete/completers.py new file mode 100644 index 0000000000..d89cdbfbc3 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/completers.py @@ -0,0 +1,124 @@ +# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. +# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info. + +import argparse +import os +import subprocess + + +def _call(*args, **kwargs): + # TODO: replace "universal_newlines" with "text" once 3.6 support is dropped + kwargs["universal_newlines"] = True + try: + return subprocess.check_output(*args, **kwargs).splitlines() + except subprocess.CalledProcessError: + return [] + + +class BaseCompleter: + """ + This is the base class that all argcomplete completers should subclass. + """ + + def __call__( + self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace + ): + raise NotImplementedError("This method should be implemented by a subclass.") + + +class ChoicesCompleter(BaseCompleter): + def __init__(self, choices): + self.choices = choices + + def _convert(self, choice): + if not isinstance(choice, str): + choice = str(choice) + return choice + + def __call__(self, **kwargs): + return (self._convert(c) for c in self.choices) + + +EnvironCompleter = ChoicesCompleter(os.environ) + + +class FilesCompleter(BaseCompleter): + """ + File completer class, optionally takes a list of allowed extensions + """ + + def __init__(self, allowednames=(), directories=True): + # Fix if someone passes in a string instead of a list + if isinstance(allowednames, (str, bytes)): + allowednames = [allowednames] + + self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames] + self.directories = directories + + def __call__(self, prefix, **kwargs): + completion = [] + if self.allowednames: + if self.directories: + files = _call(["bash", "-c", "compgen -A directory -- '{p}'".format(p=prefix)]) + completion += [f + "/" for f in files] + for x in self.allowednames: + completion += _call(["bash", "-c", "compgen -A file -X '!*.{0}' -- '{p}'".format(x, p=prefix)]) + else: + completion += _call(["bash", "-c", "compgen -A file -- '{p}'".format(p=prefix)]) + anticomp = _call(["bash", "-c", "compgen -A directory -- '{p}'".format(p=prefix)]) + completion = list(set(completion) - set(anticomp)) + + if self.directories: + completion += [f + "/" for f in anticomp] + return completion + + +class _FilteredFilesCompleter(BaseCompleter): + def __init__(self, predicate): + """ + Create the completer + + A predicate accepts as its only argument a candidate path and either + accepts it or rejects it. + """ + assert predicate, "Expected a callable predicate" + self.predicate = predicate + + def __call__(self, prefix, **kwargs): + """ + Provide completions on prefix + """ + target_dir = os.path.dirname(prefix) + try: + names = os.listdir(target_dir or ".") + except Exception: + return # empty iterator + incomplete_part = os.path.basename(prefix) + # Iterate on target_dir entries and filter on given predicate + for name in names: + if not name.startswith(incomplete_part): + continue + candidate = os.path.join(target_dir, name) + if not self.predicate(candidate): + continue + yield candidate + "/" if os.path.isdir(candidate) else candidate + + +class DirectoriesCompleter(_FilteredFilesCompleter): + def __init__(self): + _FilteredFilesCompleter.__init__(self, predicate=os.path.isdir) + + +class SuppressCompleter(BaseCompleter): + """ + A completer used to suppress the completion of specific arguments + """ + + def __init__(self): + pass + + def suppress(self): + """ + Decide if the completion should be suppressed + """ + return True diff --git a/contrib/python/argcomplete/py3/argcomplete/exceptions.py b/contrib/python/argcomplete/py3/argcomplete/exceptions.py new file mode 100644 index 0000000000..077b1850d2 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/exceptions.py @@ -0,0 +1,2 @@ +class ArgcompleteException(Exception): + "Exception raised when the shell argument completion process fails." diff --git a/contrib/python/argcomplete/py3/argcomplete/finders.py b/contrib/python/argcomplete/py3/argcomplete/finders.py new file mode 100644 index 0000000000..bf8e0e25f8 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/finders.py @@ -0,0 +1,590 @@ +# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the +# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE +# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License. +# See https://github.com/kislyuk/argcomplete for more info. + +import argparse +import os +import sys +from collections.abc import Mapping +from typing import Callable, Dict, List, Optional, Sequence, Union + +from . import io as _io +from .completers import ChoicesCompleter, FilesCompleter, SuppressCompleter +from .io import debug, mute_stderr +from .lexers import split_line +from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied + +safe_actions = { + argparse._StoreAction, + argparse._StoreConstAction, + argparse._StoreTrueAction, + argparse._StoreFalseAction, + argparse._AppendAction, + argparse._AppendConstAction, + argparse._CountAction, +} + + +def default_validator(completion, prefix): + return completion.startswith(prefix) + + +class CompletionFinder(object): + """ + Inherit from this class if you wish to override any of the stages below. Otherwise, use + ``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as + :meth:`CompletionFinder.__call__()`. + """ + + def __init__( + self, + argument_parser=None, + always_complete_options=True, + exclude=None, + validator=None, + print_suppressed=False, + default_completer=FilesCompleter(), + append_space=None, + ): + self._parser = argument_parser + self.always_complete_options = always_complete_options + self.exclude = exclude + if validator is None: + validator = default_validator + self.validator = validator + self.print_suppressed = print_suppressed + self.completing = False + self._display_completions: Dict[str, str] = {} + self.default_completer = default_completer + if append_space is None: + append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1" + self.append_space = append_space + + def __call__( + self, + argument_parser: argparse.ArgumentParser, + always_complete_options: Union[bool, str] = True, + exit_method: Callable = os._exit, + output_stream=None, + exclude: Optional[Sequence[str]] = None, + validator: Optional[Callable] = None, + print_suppressed: bool = False, + append_space: Optional[bool] = None, + default_completer=FilesCompleter(), + ): + """ + :param argument_parser: The argument parser to autocomplete on + :param always_complete_options: + Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not + been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be + suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options + with no long variant will be suggested. If ``short``, short options and long options with no short variant + will be suggested. + :param exit_method: + Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to + perform a normal exit that calls exit handlers, use :meth:`sys.exit`. + :param exclude: List of strings representing options to be omitted from autocompletion + :param validator: + Function to filter all completions through before returning (called with two string arguments, completion + and prefix; return value is evaluated as a boolean) + :param print_suppressed: + Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set. + :param append_space: + Whether to append a space to unique matches. The default is ``True``. + + .. note:: + If you are not subclassing CompletionFinder to override its behaviors, + use :meth:`argcomplete.autocomplete()` directly. It has the same signature as this method. + + Produces tab completions for ``argument_parser``. See module docs for more info. + + Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be + added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or + their execution is otherwise desirable. + """ + self.__init__( # type: ignore + argument_parser, + always_complete_options=always_complete_options, + exclude=exclude, + validator=validator, + print_suppressed=print_suppressed, + append_space=append_space, + default_completer=default_completer, + ) + + if "_ARGCOMPLETE" not in os.environ: + # not an argument completion invocation + return + + try: + _io.debug_stream = os.fdopen(9, "w") + except Exception: + _io.debug_stream = sys.stderr + debug() + + if output_stream is None: + filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME") + if filename is not None: + debug("Using output file {}".format(filename)) + output_stream = open(filename, "w") + + if output_stream is None: + try: + output_stream = os.fdopen(8, "w") + except Exception: + debug("Unable to open fd 8 for writing, quitting") + exit_method(1) + + ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013") + if len(ifs) != 1: + debug("Invalid value for IFS, quitting [{v}]".format(v=ifs)) + exit_method(1) + + dfs = os.environ.get("_ARGCOMPLETE_DFS") + if dfs and len(dfs) != 1: + debug("Invalid value for DFS, quitting [{v}]".format(v=dfs)) + exit_method(1) + + comp_line = os.environ["COMP_LINE"] + comp_point = int(os.environ["COMP_POINT"]) + + cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point) + + # _ARGCOMPLETE is set by the shell script to tell us where comp_words + # should start, based on what we're completing. + # 1: <script> [args] + # 2: python <script> [args] + # 3: python -m <module> [args] + start = int(os.environ["_ARGCOMPLETE"]) - 1 + comp_words = comp_words[start:] + + if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix: + # Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser. + comp_words.append(cword_prefix.split("=", 1)[0]) + + debug( + "\nLINE: {!r}".format(comp_line), + "\nPOINT: {!r}".format(comp_point), + "\nPREQUOTE: {!r}".format(cword_prequote), + "\nPREFIX: {!r}".format(cword_prefix), + "\nSUFFIX: {!r}".format(cword_suffix), + "\nWORDS:", + comp_words, + ) + + completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos) + + if dfs: + display_completions = { + key: value.replace(ifs, " ") if value else "" for key, value in self._display_completions.items() + } + completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions] + + if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh": + completions = [f"{c}:{self._display_completions.get(c)}" for c in completions] + + debug("\nReturning completions:", completions) + output_stream.write(ifs.join(completions)) + output_stream.flush() + _io.debug_stream.flush() + exit_method(0) + + def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos): + active_parsers = self._patch_argument_parser() + + parsed_args = argparse.Namespace() + self.completing = True + + try: + debug("invoking parser with", comp_words[1:]) + with mute_stderr(): + a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args) + debug("parsed args:", a) + except BaseException as e: + debug("\nexception", type(e), str(e), "while parsing args") + + self.completing = False + + if "--" in comp_words: + self.always_complete_options = False + + completions = self.collect_completions(active_parsers, parsed_args, cword_prefix) + completions = self.filter_completions(completions) + completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos) + return completions + + def _patch_argument_parser(self): + """ + Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and + all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser + figure out which subparsers need to be activated (then recursively monkey-patch those). + We save all active ArgumentParsers to extract all their possible option names later. + """ + self.active_parsers: List[argparse.ArgumentParser] = [] + self.visited_positionals: List[argparse.Action] = [] + + completer = self + + def patch(parser): + completer.visited_positionals.append(parser) + completer.active_parsers.append(parser) + + if isinstance(parser, IntrospectiveArgumentParser): + return + + classname = "MonkeyPatchedIntrospectiveArgumentParser" + + parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {}) + + for action in parser._actions: + if hasattr(action, "_orig_class"): + continue + + # TODO: accomplish this with super + class IntrospectAction(action.__class__): # type: ignore + def __call__(self, parser, namespace, values, option_string=None): + debug("Action stub called on", self) + debug("\targs:", parser, namespace, values, option_string) + debug("\torig class:", self._orig_class) + debug("\torig callable:", self._orig_callable) + + if not completer.completing: + self._orig_callable(parser, namespace, values, option_string=option_string) + elif issubclass(self._orig_class, argparse._SubParsersAction): + debug("orig class is a subparsers action: patching and running it") + patch(self._name_parser_map[values[0]]) + self._orig_callable(parser, namespace, values, option_string=option_string) + elif self._orig_class in safe_actions: + if not self.option_strings: + completer.visited_positionals.append(self) + + self._orig_callable(parser, namespace, values, option_string=option_string) + + action._orig_class = action.__class__ + action._orig_callable = action.__call__ + action.__class__ = IntrospectAction + + patch(self._parser) + + debug("Active parsers:", self.active_parsers) + debug("Visited positionals:", self.visited_positionals) + + return self.active_parsers + + def _get_subparser_completions(self, parser, cword_prefix): + aliases_by_parser: Dict[argparse.ArgumentParser, List[str]] = {} + for key in parser.choices.keys(): + p = parser.choices[key] + aliases_by_parser.setdefault(p, []).append(key) + + for action in parser._get_subactions(): + for alias in aliases_by_parser[parser.choices[action.dest]]: + if alias.startswith(cword_prefix): + self._display_completions[alias] = action.help or "" + + completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)] + return completions + + def _include_options(self, action, cword_prefix): + if len(cword_prefix) > 0 or self.always_complete_options is True: + return [opt for opt in action.option_strings if opt.startswith(cword_prefix)] + long_opts = [opt for opt in action.option_strings if len(opt) > 2] + short_opts = [opt for opt in action.option_strings if len(opt) <= 2] + if self.always_complete_options == "long": + return long_opts if long_opts else short_opts + elif self.always_complete_options == "short": + return short_opts if short_opts else long_opts + return [] + + def _get_option_completions(self, parser, cword_prefix): + for action in parser._actions: + if action.option_strings: + for option_string in action.option_strings: + if option_string.startswith(cword_prefix): + self._display_completions[option_string] = action.help or "" + + option_completions = [] + for action in parser._actions: + if not self.print_suppressed: + completer = getattr(action, "completer", None) + if isinstance(completer, SuppressCompleter) and completer.suppress(): + continue + if action.help == argparse.SUPPRESS: + continue + if not self._action_allowed(action, parser): + continue + if not isinstance(action, argparse._SubParsersAction): + option_completions += self._include_options(action, cword_prefix) + return option_completions + + @staticmethod + def _action_allowed(action, parser): + # Logic adapted from take_action in ArgumentParser._parse_known_args + # (members are saved by vendor._argparse.IntrospectiveArgumentParser) + for conflict_action in parser._action_conflicts.get(action, []): + if conflict_action in parser._seen_non_default_actions: + return False + return True + + def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions): + debug("Active actions (L={l}): {a}".format(l=len(parser.active_actions), a=parser.active_actions)) + + isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars + optional_prefix = "" + greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)] + if greedy_actions: + assert len(greedy_actions) == 1, "expect at most 1 greedy action" + # This means the action will fail to parse if the word under the cursor is not given + # to it, so give it exclusive control over completions (flush previous completions) + debug("Resetting completions because", greedy_actions[0], "must consume the next argument") + self._display_completions = {} + completions = [] + elif isoptional: + if "=" in cword_prefix: + # Special case for when the current word is "--optional=PARTIAL_VALUE". + # The completer runs on PARTIAL_VALUE. The prefix is added back to the completions + # (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic). + optional_prefix, _, cword_prefix = cword_prefix.partition("=") + else: + # Only run completers if current word does not start with - (is not an optional) + return completions + + complete_remaining_positionals = False + # Use the single greedy action (if there is one) or all active actions. + for active_action in greedy_actions or parser.active_actions: + if not active_action.option_strings: # action is a positional + if action_is_open(active_action): + # Any positional arguments after this may slide down into this action + # if more arguments are added (since the user may not be done yet), + # so it is extremely difficult to tell which completers to run. + # Running all remaining completers will probably show more than the user wants + # but it also guarantees we won't miss anything. + complete_remaining_positionals = True + if not complete_remaining_positionals: + if action_is_satisfied(active_action) and not action_is_open(active_action): + debug("Skipping", active_action) + continue + + debug("Activating completion for", active_action, active_action._orig_class) + # completer = getattr(active_action, "completer", DefaultCompleter()) + completer = getattr(active_action, "completer", None) + + if completer is None: + if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction): + completer = ChoicesCompleter(active_action.choices) + elif not isinstance(active_action, argparse._SubParsersAction): + completer = self.default_completer + + if completer: + if isinstance(completer, SuppressCompleter) and completer.suppress(): + continue + + if callable(completer): + completer_output = completer( + prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args + ) + if isinstance(completer_output, Mapping): + for completion, description in completer_output.items(): + if self.validator(completion, cword_prefix): + completions.append(completion) + self._display_completions[completion] = description + else: + for completion in completer_output: + if self.validator(completion, cword_prefix): + completions.append(completion) + if isinstance(completer, ChoicesCompleter): + self._display_completions[completion] = active_action.help or "" + else: + self._display_completions[completion] = "" + else: + debug("Completer is not callable, trying the readline completer protocol instead") + for i in range(9999): + next_completion = completer.complete(cword_prefix, i) # type: ignore + if next_completion is None: + break + if self.validator(next_completion, cword_prefix): + self._display_completions[next_completion] = "" + completions.append(next_completion) + if optional_prefix: + completions = [optional_prefix + "=" + completion for completion in completions] + debug("Completions:", completions) + return completions + + def collect_completions( + self, active_parsers: List[argparse.ArgumentParser], parsed_args: argparse.Namespace, cword_prefix: str + ) -> List[str]: + """ + Visits the active parsers and their actions, executes their completers or introspects them to collect their + option strings. Returns the resulting completions as a list of strings. + + This method is exposed for overriding in subclasses; there is no need to use it directly. + """ + completions: List[str] = [] + + debug("all active parsers:", active_parsers) + active_parser = active_parsers[-1] + debug("active_parser:", active_parser) + if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars): + completions += self._get_option_completions(active_parser, cword_prefix) + debug("optional options:", completions) + + next_positional = self._get_next_positional() + debug("next_positional:", next_positional) + + if isinstance(next_positional, argparse._SubParsersAction): + completions += self._get_subparser_completions(next_positional, cword_prefix) + + completions = self._complete_active_option( + active_parser, next_positional, cword_prefix, parsed_args, completions + ) + debug("active options:", completions) + debug("display completions:", self._display_completions) + + return completions + + def _get_next_positional(self): + """ + Get the next positional action if it exists. + """ + active_parser = self.active_parsers[-1] + last_positional = self.visited_positionals[-1] + + all_positionals = active_parser._get_positional_actions() + if not all_positionals: + return None + + if active_parser == last_positional: + return all_positionals[0] + + i = 0 + for i in range(len(all_positionals)): + if all_positionals[i] == last_positional: + break + + if i + 1 < len(all_positionals): + return all_positionals[i + 1] + + return None + + def filter_completions(self, completions: List[str]) -> List[str]: + """ + De-duplicates completions and excludes those specified by ``exclude``. + Returns the filtered completions as a list. + + This method is exposed for overriding in subclasses; there is no need to use it directly. + """ + filtered_completions = [] + for completion in completions: + if self.exclude is not None: + if completion in self.exclude: + continue + if completion not in filtered_completions: + filtered_completions.append(completion) + return filtered_completions + + def quote_completions( + self, completions: List[str], cword_prequote: str, last_wordbreak_pos: Optional[int] + ) -> List[str]: + """ + If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes + occurrences of that quote character in the completions, and adds the quote to the beginning of each completion. + Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of + completions before the first colon if (``COMP_WORDBREAKS``) contains a colon. + + If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``), + adds a space after the completion. + + This method is exposed for overriding in subclasses; there is no need to use it directly. + """ + special_chars = "\\" + # If the word under the cursor was quoted, escape the quote char. + # Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars. + if cword_prequote == "": + # Bash mangles completions which contain characters in COMP_WORDBREAKS. + # This workaround has the same effect as __ltrim_colon_completions in bash_completion + # (extended to characters other than the colon). + if last_wordbreak_pos: + completions = [c[last_wordbreak_pos + 1 :] for c in completions] + special_chars += "();<>|&!`$* \t\n\"'" + elif cword_prequote == '"': + special_chars += '"`$!' + + if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"): + # tcsh and fish escapes special characters itself. + special_chars = "" + elif cword_prequote == "'": + # Nothing can be escaped in single quotes, so we need to close + # the string, escape the single quote, then open a new string. + special_chars = "" + completions = [c.replace("'", r"'\''") for c in completions] + + # PowerShell uses ` as escape character. + if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell": + escape_char = '`' + special_chars = special_chars.replace('`', '') + else: + escape_char = "\\" + for char in special_chars: + completions = [c.replace(char, escape_char + char) for c in completions] + + if self.append_space: + # Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete. + # Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character. + # This code is retained for environments where this isn't done natively. + continuation_chars = "=/:" + if len(completions) == 1 and completions[0][-1] not in continuation_chars: + if cword_prequote == "": + completions[0] += " " + + return completions + + def rl_complete(self, text, state): + """ + Alternate entry point for using the argcomplete completer in a readline-based REPL. See also + `rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_. + Usage: + + .. code-block:: python + + import argcomplete, argparse, readline + parser = argparse.ArgumentParser() + ... + completer = argcomplete.CompletionFinder(parser) + readline.set_completer_delims("") + readline.set_completer(completer.rl_complete) + readline.parse_and_bind("tab: complete") + result = input("prompt> ") + """ + if state == 0: + cword_prequote, cword_prefix, cword_suffix, comp_words, first_colon_pos = split_line(text) + comp_words.insert(0, sys.argv[0]) + matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos) + self._rl_matches = [text + match[len(cword_prefix) :] for match in matches] + + if state < len(self._rl_matches): + return self._rl_matches[state] + else: + return None + + def get_display_completions(self): + """ + This function returns a mapping of option names to their help strings for displaying to the user. + """ + return self._display_completions + + +class ExclusiveCompletionFinder(CompletionFinder): + @staticmethod + def _action_allowed(action, parser): + if not CompletionFinder._action_allowed(action, parser): + return False + + append_classes = (argparse._AppendAction, argparse._AppendConstAction) + if action._orig_class in append_classes: + return True + + if action not in parser._seen_non_default_actions: + return True + + return False diff --git a/contrib/python/argcomplete/py3/argcomplete/io.py b/contrib/python/argcomplete/py3/argcomplete/io.py new file mode 100644 index 0000000000..df2c4f2032 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/io.py @@ -0,0 +1,42 @@ +import contextlib +import os +import sys + +_DEBUG = "_ARC_DEBUG" in os.environ + +debug_stream = sys.stderr + + +def debug(*args): + if _DEBUG: + print(file=debug_stream, *args) + + +@contextlib.contextmanager +def mute_stdout(): + stdout = sys.stdout + sys.stdout = open(os.devnull, "w") + try: + yield + finally: + sys.stdout = stdout + + +@contextlib.contextmanager +def mute_stderr(): + stderr = sys.stderr + sys.stderr = open(os.devnull, "w") + try: + yield + finally: + sys.stderr.close() + sys.stderr = stderr + + +def warn(*args): + """ + Prints **args** to standard error when running completions. This will interrupt the user's command line interaction; + use it to indicate an error condition that is preventing your completer from working. + """ + print(file=debug_stream) + print(file=debug_stream, *args) diff --git a/contrib/python/argcomplete/py3/argcomplete/lexers.py b/contrib/python/argcomplete/py3/argcomplete/lexers.py new file mode 100644 index 0000000000..72c378b54d --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/lexers.py @@ -0,0 +1,57 @@ +import os + +from .exceptions import ArgcompleteException +from .io import debug +from .packages import _shlex + + +def split_line(line, point=None): + if point is None: + point = len(line) + line = line[:point] + lexer = _shlex.shlex(line, posix=True) + lexer.whitespace_split = True + lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "") + words = [] + + def split_word(word): + # TODO: make this less ugly + point_in_word = len(word) + point - lexer.instream.tell() + if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace: + point_in_word += 1 + if point_in_word > len(word): + debug("In trailing whitespace") + words.append(word) + word = "" + prefix, suffix = word[:point_in_word], word[point_in_word:] + prequote = "" + # posix + if lexer.state is not None and lexer.state in lexer.quotes: + prequote = lexer.state + # non-posix + # if len(prefix) > 0 and prefix[0] in lexer.quotes: + # prequote, prefix = prefix[0], prefix[1:] + + return prequote, prefix, suffix, words, lexer.last_wordbreak_pos + + while True: + try: + word = lexer.get_token() + if word == lexer.eof: + # TODO: check if this is ever unsafe + # raise ArgcompleteException("Unexpected end of input") + return "", "", "", words, None + if lexer.instream.tell() >= point: + debug("word", word, "split, lexer state: '{s}'".format(s=lexer.state)) + return split_word(word) + words.append(word) + except ValueError: + debug("word", lexer.token, "split (lexer stopped, state: '{s}')".format(s=lexer.state)) + if lexer.instream.tell() >= point: + return split_word(lexer.token) + else: + msg = ( + "Unexpected internal state. " + "Please report this bug at https://github.com/kislyuk/argcomplete/issues." + ) + raise ArgcompleteException(msg) diff --git a/contrib/python/argcomplete/py3/argcomplete/packages/__init__.py b/contrib/python/argcomplete/py3/argcomplete/packages/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/packages/__init__.py diff --git a/contrib/python/argcomplete/py3/argcomplete/packages/_argparse.py b/contrib/python/argcomplete/py3/argcomplete/packages/_argparse.py new file mode 100644 index 0000000000..7a8b4fc821 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/packages/_argparse.py @@ -0,0 +1,337 @@ +# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the +# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE +# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License. +# See https://github.com/kislyuk/argcomplete for more info. + +# This file contains argparse introspection utilities used in the course of argcomplete execution. + +from argparse import ( + ONE_OR_MORE, + OPTIONAL, + PARSER, + REMAINDER, + SUPPRESS, + ZERO_OR_MORE, + Action, + ArgumentError, + ArgumentParser, + _get_action_name, + _SubParsersAction, +) +from gettext import gettext +from typing import Dict, List, Set, Tuple + +_num_consumed_args: Dict[Action, int] = {} + + +def action_is_satisfied(action): + '''Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.''' + num_consumed_args = _num_consumed_args.get(action, 0) + + if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]: + return True + if action.nargs == ONE_OR_MORE: + return num_consumed_args >= 1 + if action.nargs == PARSER: + # Not sure what this should be, but this previously always returned False + # so at least this won't break anything that wasn't already broken. + return False + if action.nargs is None: + return num_consumed_args == 1 + + assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs + return num_consumed_args == action.nargs + + +def action_is_open(action): + '''Returns True if action could consume more arguments (i.e., its pattern is open).''' + num_consumed_args = _num_consumed_args.get(action, 0) + + if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]: + return True + if action.nargs == OPTIONAL or action.nargs is None: + return num_consumed_args == 0 + + assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs + return num_consumed_args < action.nargs + + +def action_is_greedy(action, isoptional=False): + '''Returns True if action will necessarily consume the next argument. + isoptional indicates whether the argument is an optional (starts with -). + ''' + num_consumed_args = _num_consumed_args.get(action, 0) + + if action.option_strings: + if not isoptional and not action_is_satisfied(action): + return True + return action.nargs == REMAINDER + else: + return action.nargs == REMAINDER and num_consumed_args >= 1 + + +class IntrospectiveArgumentParser(ArgumentParser): + '''The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3), + except for the lines that contain the string "Added by argcomplete". + ''' + + def _parse_known_args(self, arg_strings, namespace): + _num_consumed_args.clear() # Added by argcomplete + self._argcomplete_namespace = namespace + self.active_actions: List[Action] = [] # Added by argcomplete + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts: Dict[Action, List[Action]] = {} + self._action_conflicts = action_conflicts # Added by argcomplete + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1 :]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions: Set[Action] = set() + seen_non_default_actions: Set[Action] = set() + self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = gettext('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS or isinstance(action, _SubParsersAction): + try: + action(self, namespace, argument_values, option_string) + except BaseException: + # Begin added by argcomplete + # When a subparser action is taken and fails due to incomplete arguments, it does not merge the + # contents of its parsed namespace into the parent namespace. Do that here to allow completers to + # access the partially parsed arguments for the subparser. + if isinstance(action, _SubParsersAction): + subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace + for key, value in vars(subnamespace).items(): + setattr(namespace, key, value) + # End added by argcomplete + raise + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples: List[Tuple[Action, List[str], str]] = [] + while True: + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + char = option_string[0] + option_string = char + explicit_arg[0] + new_explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + explicit_arg = new_explicit_arg + else: + msg = gettext('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = gettext('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + self.active_actions = [action] # Added by argcomplete + _num_consumed_args[action] = 0 # Added by argcomplete + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + + # Begin added by argcomplete + # If the pattern is not open (e.g. no + at the end), remove the action from active actions (since + # it wouldn't be able to consume any more args) + _num_consumed_args[action] = len(args) + if not action_is_open(action): + self.active_actions.remove(action) + # End added by argcomplete + + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): # Added by argcomplete + self.active_actions.append(action) # Added by argcomplete + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index : start_index + arg_count] + start_index += arg_count + _num_consumed_args[action] = len(args) # Added by argcomplete + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts) :] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + # consume any Positionals preceding the next option + next_option_string_index = min([index for index in option_string_indices if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # if we didn't use all the Positional objects, there were too few + # arg strings supplied. + + if positionals: + self.active_actions.append(positionals[0]) # Added by argcomplete + self.error(gettext('too few arguments')) + + # make sure all required actions were present + for action in self._actions: + if action.required: + if action not in seen_actions: + name = _get_action_name(action) + self.error(gettext('argument %s is required') % name) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [ + str(_get_action_name(action)) for action in group._group_actions if action.help is not SUPPRESS + ] + msg = gettext('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras diff --git a/contrib/python/argcomplete/py3/argcomplete/packages/_shlex.py b/contrib/python/argcomplete/py3/argcomplete/packages/_shlex.py new file mode 100644 index 0000000000..613feb2597 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/packages/_shlex.py @@ -0,0 +1,310 @@ +# This copy of shlex.py from Python 3.6 is distributed with argcomplete. +# It contains only the shlex class, with modifications as noted. + +"""A lexical analyzer class for simple shell-like syntaxes.""" + +# Module and documentation by Eric S. Raymond, 21 Dec 1998 +# Input stacking and error message cleanup added by ESR, March 2000 +# push_source() and pop_source() made explicit by ESR, January 2001. +# Posix compliance, split(), string arguments, and +# iterator interface by Gustavo Niemeyer, April 2003. +# changes to tokenize more like Posix shells by Vinay Sajip, July 2016. + +import os +import sys +from collections import deque +from io import StringIO +from typing import Optional + + +class shlex: + "A lexical analyzer class for simple shell-like syntaxes." + + def __init__(self, instream=None, infile=None, posix=False, punctuation_chars=False): + # Modified by argcomplete: 2/3 compatibility + if isinstance(instream, str): + instream = StringIO(instream) + if instream is not None: + self.instream = instream + self.infile = infile + else: + self.instream = sys.stdin + self.infile = None + self.posix = posix + if posix: + self.eof = None + else: + self.eof = '' + self.commenters = '#' + self.wordchars = 'abcdfeghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' + # Modified by argcomplete: 2/3 compatibility + # if self.posix: + # self.wordchars += ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' + # 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ') + self.whitespace = ' \t\r\n' + self.whitespace_split = False + self.quotes = '\'"' + self.escape = '\\' + self.escapedquotes = '"' + self.state: Optional[str] = ' ' + self.pushback: deque = deque() + self.lineno = 1 + self.debug = 0 + self.token = '' + self.filestack: deque = deque() + self.source = None + if not punctuation_chars: + punctuation_chars = '' + elif punctuation_chars is True: + punctuation_chars = '();<>|&' + self.punctuation_chars = punctuation_chars + if punctuation_chars: + # _pushback_chars is a push back queue used by lookahead logic + self._pushback_chars: deque = deque() + # these chars added because allowed in file names, args, wildcards + self.wordchars += '~-./*?=' + # remove any punctuation chars from wordchars + t = self.wordchars.maketrans(dict.fromkeys(punctuation_chars)) + self.wordchars = self.wordchars.translate(t) + + # Modified by argcomplete: Record last wordbreak position + self.last_wordbreak_pos = None + self.wordbreaks = '' + + def push_token(self, tok): + "Push a token onto the stack popped by the get_token method" + if self.debug >= 1: + print("shlex: pushing token " + repr(tok)) + self.pushback.appendleft(tok) + + def push_source(self, newstream, newfile=None): + "Push an input source onto the lexer's input source stack." + # Modified by argcomplete: 2/3 compatibility + if isinstance(newstream, str): + newstream = StringIO(newstream) + self.filestack.appendleft((self.infile, self.instream, self.lineno)) + self.infile = newfile + self.instream = newstream + self.lineno = 1 + if self.debug: + if newfile is not None: + print('shlex: pushing to file %s' % (self.infile,)) + else: + print('shlex: pushing to stream %s' % (self.instream,)) + + def pop_source(self): + "Pop the input source stack." + self.instream.close() + (self.infile, self.instream, self.lineno) = self.filestack.popleft() + if self.debug: + print('shlex: popping to %s, line %d' % (self.instream, self.lineno)) + self.state = ' ' + + def get_token(self): + "Get a token from the input stream (or from stack if it's nonempty)" + if self.pushback: + tok = self.pushback.popleft() + if self.debug >= 1: + print("shlex: popping token " + repr(tok)) + return tok + # No pushback. Get a token. + raw = self.read_token() + # Handle inclusions + if self.source is not None: + while raw == self.source: + spec = self.sourcehook(self.read_token()) + if spec: + (newfile, newstream) = spec + self.push_source(newstream, newfile) + raw = self.get_token() + # Maybe we got EOF instead? + while raw == self.eof: + if not self.filestack: + return self.eof + else: + self.pop_source() + raw = self.get_token() + # Neither inclusion nor EOF + if self.debug >= 1: + if raw != self.eof: + print("shlex: token=" + repr(raw)) + else: + print("shlex: token=EOF") + return raw + + def read_token(self): + quoted = False + escapedstate = ' ' + while True: + if self.punctuation_chars and self._pushback_chars: + nextchar = self._pushback_chars.pop() + else: + nextchar = self.instream.read(1) + if nextchar == '\n': + self.lineno += 1 + if self.debug >= 3: + print("shlex: in state %r I see character: %r" % (self.state, nextchar)) + if self.state is None: + self.token = '' # past end of file + break + elif self.state == ' ': + if not nextchar: + self.state = None # end of file + break + elif nextchar in self.whitespace: + if self.debug >= 2: + print("shlex: I see whitespace in whitespace state") + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif nextchar in self.commenters: + self.instream.readline() + self.lineno += 1 + elif self.posix and nextchar in self.escape: + escapedstate = 'a' + self.state = nextchar + elif nextchar in self.wordchars: + self.token = nextchar + self.state = 'a' + elif nextchar in self.punctuation_chars: + self.token = nextchar + self.state = 'c' + elif nextchar in self.quotes: + if not self.posix: + self.token = nextchar + self.state = nextchar + elif self.whitespace_split: + self.token = nextchar + self.state = 'a' + else: + self.token = nextchar + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif self.state in self.quotes: + quoted = True + if not nextchar: # end of file + if self.debug >= 2: + print("shlex: I see EOF in quotes state") + # XXX what error should be raised here? + raise ValueError("No closing quotation") + if nextchar == self.state: + if not self.posix: + self.token += nextchar + self.state = ' ' + break + else: + self.state = 'a' + elif self.posix and nextchar in self.escape and self.state in self.escapedquotes: + escapedstate = self.state + self.state = nextchar + else: + self.token += nextchar + elif self.state in self.escape: + if not nextchar: # end of file + if self.debug >= 2: + print("shlex: I see EOF in escape state") + # XXX what error should be raised here? + raise ValueError("No escaped character") + # In posix shells, only the quote itself or the escape + # character may be escaped within quotes. + if escapedstate in self.quotes and nextchar != self.state and nextchar != escapedstate: + self.token += self.state + self.token += nextchar + self.state = escapedstate + elif self.state in ('a', 'c'): + if not nextchar: + self.state = None # end of file + break + elif nextchar in self.whitespace: + if self.debug >= 2: + print("shlex: I see whitespace in word state") + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif nextchar in self.commenters: + self.instream.readline() + self.lineno += 1 + if self.posix: + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif self.posix and nextchar in self.quotes: + self.state = nextchar + elif self.posix and nextchar in self.escape: + escapedstate = 'a' + self.state = nextchar + elif self.state == 'c': + if nextchar in self.punctuation_chars: + self.token += nextchar + else: + if nextchar not in self.whitespace: + self._pushback_chars.append(nextchar) + self.state = ' ' + break + elif nextchar in self.wordchars or nextchar in self.quotes or self.whitespace_split: + self.token += nextchar + # Modified by argcomplete: Record last wordbreak position + if nextchar in self.wordbreaks: + self.last_wordbreak_pos = len(self.token) - 1 + else: + if self.punctuation_chars: + self._pushback_chars.append(nextchar) + else: + self.pushback.appendleft(nextchar) + if self.debug >= 2: + print("shlex: I see punctuation in word state") + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + result: Optional[str] = self.token + self.token = '' + if self.posix and not quoted and result == '': + result = None + if self.debug > 1: + if result: + print("shlex: raw token=" + repr(result)) + else: + print("shlex: raw token=EOF") + # Modified by argcomplete: Record last wordbreak position + if self.state == ' ': + self.last_wordbreak_pos = None + return result + + def sourcehook(self, newfile): + "Hook called on a filename to be sourced." + if newfile[0] == '"': + newfile = newfile[1:-1] + # This implements cpp-like semantics for relative-path inclusion. + # Modified by argcomplete: 2/3 compatibility + if isinstance(self.infile, str) and not os.path.isabs(newfile): + newfile = os.path.join(os.path.dirname(self.infile), newfile) + return (newfile, open(newfile, "r")) + + def error_leader(self, infile=None, lineno=None): + "Emit a C-compiler-like, Emacs-friendly error-message leader." + if infile is None: + infile = self.infile + if lineno is None: + lineno = self.lineno + return "\"%s\", line %d: " % (infile, lineno) + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token == self.eof: + raise StopIteration + return token + + # Modified by argcomplete: 2/3 compatibility + next = __next__ diff --git a/contrib/python/argcomplete/py3/argcomplete/py.typed b/contrib/python/argcomplete/py3/argcomplete/py.typed new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/py.typed diff --git a/contrib/python/argcomplete/py3/argcomplete/shell_integration.py b/contrib/python/argcomplete/py3/argcomplete/shell_integration.py new file mode 100644 index 0000000000..74bede645a --- /dev/null +++ b/contrib/python/argcomplete/py3/argcomplete/shell_integration.py @@ -0,0 +1,183 @@ +# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the +# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE +# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License. +# See https://github.com/kislyuk/argcomplete for more info. + +from shlex import quote + +bashcode = r""" +# Run something, muting output or redirecting it to the debug stream +# depending on the value of _ARC_DEBUG. +# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC. +__python_argcomplete_run() { + if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then + __python_argcomplete_run_inner "$@" + return + fi + local tmpfile="$(mktemp)" + _ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@" + local code=$? + cat "$tmpfile" + rm "$tmpfile" + return $code +} + +__python_argcomplete_run_inner() { + if [[ -z "${_ARC_DEBUG-}" ]]; then + "$@" 8>&1 9>&2 1>/dev/null 2>&1 + else + "$@" 8>&1 9>&2 1>&9 2>&1 + fi +} + +_python_argcomplete%(function_suffix)s() { + local IFS=$'\013' + if [[ -n "${ZSH_VERSION-}" ]]; then + local completions + completions=($(IFS="$IFS" \ + COMP_LINE="$BUFFER" \ + COMP_POINT="$CURSOR" \ + _ARGCOMPLETE=1 \ + _ARGCOMPLETE_SHELL="zsh" \ + _ARGCOMPLETE_SUPPRESS_SPACE=1 \ + __python_argcomplete_run "${words[1]}") ) + _describe "${words[1]}" completions -o nosort + else + local SUPPRESS_SPACE=0 + if compopt +o nospace 2> /dev/null; then + SUPPRESS_SPACE=1 + fi + COMPREPLY=($(IFS="$IFS" \ + COMP_LINE="$COMP_LINE" \ + COMP_POINT="$COMP_POINT" \ + COMP_TYPE="$COMP_TYPE" \ + _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \ + _ARGCOMPLETE=1 \ + _ARGCOMPLETE_SHELL="bash" \ + _ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \ + __python_argcomplete_run "%(argcomplete_script)s")) + if [[ $? != 0 ]]; then + unset COMPREPLY + elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then + compopt -o nospace + fi + fi +} +if [[ -z "${ZSH_VERSION-}" ]]; then + complete %(complete_opts)s -F _python_argcomplete%(function_suffix)s %(executables)s +else + compdef _python_argcomplete%(function_suffix)s %(executables)s +fi +""" + +tcshcode = """\ +complete "%(executable)s" 'p@*@`python-argcomplete-tcsh "%(argcomplete_script)s"`@' ; +""" + +fishcode = r""" +function __fish_%(function_name)s_complete + set -x _ARGCOMPLETE 1 + set -x _ARGCOMPLETE_DFS \t + set -x _ARGCOMPLETE_IFS \n + set -x _ARGCOMPLETE_SUPPRESS_SPACE 1 + set -x _ARGCOMPLETE_SHELL fish + set -x COMP_LINE (commandline -p) + set -x COMP_POINT (string length (commandline -cp)) + set -x COMP_TYPE + if set -q _ARC_DEBUG + %(argcomplete_script)s 8>&1 9>&2 1>&9 2>&1 + else + %(argcomplete_script)s 8>&1 9>&2 1>/dev/null 2>&1 + end +end +complete %(completion_arg)s %(executable)s -f -a '(__fish_%(function_name)s_complete)' +""" + +powershell_code = r""" +Register-ArgumentCompleter -Native -CommandName %(executable)s -ScriptBlock { + param($commandName, $wordToComplete, $cursorPosition) + $completion_file = New-TemporaryFile + $env:ARGCOMPLETE_USE_TEMPFILES = 1 + $env:_ARGCOMPLETE_STDOUT_FILENAME = $completion_file + $env:COMP_LINE = $wordToComplete + $env:COMP_POINT = $cursorPosition + $env:_ARGCOMPLETE = 1 + $env:_ARGCOMPLETE_SUPPRESS_SPACE = 0 + $env:_ARGCOMPLETE_IFS = "`n" + $env:_ARGCOMPLETE_SHELL = "powershell" + %(argcomplete_script)s 2>&1 | Out-Null + + Get-Content $completion_file | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) + } + Remove-Item $completion_file, Env:\_ARGCOMPLETE_STDOUT_FILENAME, Env:\ARGCOMPLETE_USE_TEMPFILES, Env:\COMP_LINE, Env:\COMP_POINT, Env:\_ARGCOMPLETE, Env:\_ARGCOMPLETE_SUPPRESS_SPACE, Env:\_ARGCOMPLETE_IFS, Env:\_ARGCOMPLETE_SHELL +} +""" # noqa: E501 + +shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code} + + +def shellcode(executables, use_defaults=True, shell="bash", complete_arguments=None, argcomplete_script=None): + """ + Provide the shell code required to register a python executable for use with the argcomplete module. + + :param list(str) executables: Executables to be completed (when invoked exactly with this name) + :param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated + (affects bash only) + :param str shell: Name of the shell to output code for + :param complete_arguments: Arguments to call complete with (affects bash only) + :type complete_arguments: list(str) or None + :param argcomplete_script: Script to call complete with, if not the executable to complete. + If supplied, will be used to complete *all* passed executables. + :type argcomplete_script: str or None + """ + + if complete_arguments is None: + complete_options = "-o nospace -o default -o bashdefault" if use_defaults else "-o nospace -o bashdefault" + else: + complete_options = " ".join(complete_arguments) + + if shell == "bash" or shell == "zsh": + quoted_executables = [quote(i) for i in executables] + executables_list = " ".join(quoted_executables) + script = argcomplete_script + if script: + function_suffix = "_" + script + else: + script = "$1" + function_suffix = "" + code = bashcode % dict( + complete_opts=complete_options, + executables=executables_list, + argcomplete_script=script, + function_suffix=function_suffix, + ) + elif shell == "fish": + code = "" + for executable in executables: + script = argcomplete_script or executable + completion_arg = "--path" if "/" in executable else "--command" # use path for absolute paths + function_name = executable.replace("/", "_") # / not allowed in function name + + code += fishcode % dict( + executable=executable, + argcomplete_script=script, + completion_arg=completion_arg, + function_name=function_name, + ) + elif shell == "powershell": + code = "" + for executable in executables: + script = argcomplete_script or executable + code += powershell_code % dict(executable=executable, argcomplete_script=script) + + else: + code = "" + for executable in executables: + script = argcomplete_script + # If no script was specified, default to the executable being completed. + if not script: + script = executable + code += shell_codes.get(shell, "") % dict(executable=executable, argcomplete_script=script) + + return code diff --git a/contrib/python/argcomplete/py3/ya.make b/contrib/python/argcomplete/py3/ya.make new file mode 100644 index 0000000000..eebc255d35 --- /dev/null +++ b/contrib/python/argcomplete/py3/ya.make @@ -0,0 +1,35 @@ +# Generated by devtools/yamaker (pypi). + +PY3_LIBRARY() + +VERSION(3.1.2) + +LICENSE(Apache-2.0) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + argcomplete/__init__.py + argcomplete/_check_console_script.py + argcomplete/_check_module.py + argcomplete/completers.py + argcomplete/exceptions.py + argcomplete/finders.py + argcomplete/io.py + argcomplete/lexers.py + argcomplete/packages/__init__.py + argcomplete/packages/_argparse.py + argcomplete/packages/_shlex.py + argcomplete/shell_integration.py +) + +RESOURCE_FILES( + PREFIX contrib/python/argcomplete/py3/ + .dist-info/METADATA + .dist-info/top_level.txt + argcomplete/bash_completion.d/_python-argcomplete + argcomplete/py.typed +) + +END() diff --git a/contrib/python/argcomplete/ya.make b/contrib/python/argcomplete/ya.make new file mode 100644 index 0000000000..1692542636 --- /dev/null +++ b/contrib/python/argcomplete/ya.make @@ -0,0 +1,18 @@ +PY23_LIBRARY() + +LICENSE(Service-Py23-Proxy) + +IF (PYTHON2) + PEERDIR(contrib/python/argcomplete/py2) +ELSE() + PEERDIR(contrib/python/argcomplete/py3) +ENDIF() + +NO_LINT() + +END() + +RECURSE( + py2 + py3 +) diff --git a/contrib/python/traitlets/py3/.dist-info/METADATA b/contrib/python/traitlets/py3/.dist-info/METADATA index 5ef59abe28..fb63430a4c 100644 --- a/contrib/python/traitlets/py3/.dist-info/METADATA +++ b/contrib/python/traitlets/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: traitlets -Version: 5.10.1 +Version: 5.11.2 Summary: Traitlets Python configuration system Project-URL: Homepage, https://github.com/ipython/traitlets Author-email: IPython Development Team <ipython-dev@python.org> diff --git a/contrib/python/traitlets/py3/tests/__init__.py b/contrib/python/traitlets/py3/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/traitlets/py3/tests/__init__.py diff --git a/contrib/python/traitlets/py3/traitlets/tests/_warnings.py b/contrib/python/traitlets/py3/tests/_warnings.py index e3c3a0ac6d..e3c3a0ac6d 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/_warnings.py +++ b/contrib/python/traitlets/py3/tests/_warnings.py diff --git a/contrib/python/traitlets/py3/tests/config/__init__.py b/contrib/python/traitlets/py3/tests/config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/traitlets/py3/tests/config/__init__.py diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py b/contrib/python/traitlets/py3/tests/config/test_application.py index 084ff6f032..610cafc3cd 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py +++ b/contrib/python/traitlets/py3/tests/config/test_application.py @@ -601,7 +601,7 @@ class TestApplication(TestCase): with self.assertRaises(SyntaxError): app.load_config_file(name, path=[td]) - def test_subcommands_instanciation(self): + def test_subcommands_instantiation(self): """Try all ways to specify how to create sub-apps.""" app = Root.instance() app.parse_command_line(["sub1"]) @@ -695,7 +695,7 @@ def test_cli_multi_scalar(caplog): class Root(Application): subcommands = { - "sub1": ("__tests__.config.tests.test_application.Sub1", "import string"), + "sub1": ("__tests__.config.test_application.Sub1", "import string"), } diff --git a/contrib/python/traitlets/py3/tests/config/test_argcomplete.py b/contrib/python/traitlets/py3/tests/config/test_argcomplete.py new file mode 100644 index 0000000000..52ed6d2bb2 --- /dev/null +++ b/contrib/python/traitlets/py3/tests/config/test_argcomplete.py @@ -0,0 +1,219 @@ +""" +Tests for argcomplete handling by traitlets.config.application.Application +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import io +import os +import typing as t + +import pytest + +argcomplete = pytest.importorskip("argcomplete") + +from traitlets import Unicode +from traitlets.config.application import Application +from traitlets.config.configurable import Configurable +from traitlets.config.loader import KVArgParseConfigLoader + + +class ArgcompleteApp(Application): + """Override loader to pass through kwargs for argcomplete testing""" + + argcomplete_kwargs: t.Dict[str, t.Any] + + def __init__(self, *args, **kwargs): + # For subcommands, inherit argcomplete_kwargs from parent app + parent = kwargs.get("parent") + super().__init__(*args, **kwargs) + if parent: + argcomplete_kwargs = getattr(parent, "argcomplete_kwargs", None) + if argcomplete_kwargs: + self.argcomplete_kwargs = argcomplete_kwargs + + def _create_loader(self, argv, aliases, flags, classes): + loader = KVArgParseConfigLoader( + argv, aliases, flags, classes=classes, log=self.log, subcommands=self.subcommands + ) + loader._argcomplete_kwargs = self.argcomplete_kwargs # type: ignore[attr-defined] + return loader + + +class SubApp1(ArgcompleteApp): + pass + + +class SubApp2(ArgcompleteApp): + @classmethod + def get_subapp_instance(cls, app: Application) -> Application: + app.clear_instance() # since Application is singleton, need to clear main app + return cls.instance(parent=app) # type: ignore[no-any-return] + + +class MainApp(ArgcompleteApp): + subcommands = { + "subapp1": (SubApp1, "First subapp"), + "subapp2": (SubApp2.get_subapp_instance, "Second subapp"), + } + + +class CustomError(Exception): + """Helper for exit hook for testing argcomplete""" + + @classmethod + def exit(cls, code): + raise cls(str(code)) + + +class TestArgcomplete: + IFS = "\013" + COMP_WORDBREAKS = " \t\n\"'><=;|&(:" + + @pytest.fixture + def argcomplete_on(self, mocker): + """Mostly borrowed from argcomplete's unit test fixtures + + Set up environment variables to mimic those passed by argcomplete + """ + _old_environ = os.environ + os.environ = os.environ.copy() # type: ignore[assignment] + os.environ["_ARGCOMPLETE"] = "1" + os.environ["_ARC_DEBUG"] = "yes" + os.environ["IFS"] = self.IFS + os.environ["_ARGCOMPLETE_COMP_WORDBREAKS"] = self.COMP_WORDBREAKS + + # argcomplete==2.0.0 always calls fdopen(9, "w") to open a debug stream, + # however this could conflict with file descriptors used by pytest + # and lead to obscure errors. Since we are not looking at debug stream + # in these tests, just mock this fdopen call out. + mocker.patch("os.fdopen") + try: + yield + finally: + os.environ = _old_environ + + def run_completer( + self, + app: ArgcompleteApp, + command: str, + point: t.Union[str, int, None] = None, + **kwargs: t.Any, + ) -> t.List[str]: + """Mostly borrowed from argcomplete's unit tests + + Modified to take an application instead of an ArgumentParser + + Command is the current command being completed and point is the index + into the command where the completion is triggered. + """ + if point is None: + point = str(len(command)) + # Flushing tempfile was leading to CI failures with Bad file descriptor, not sure why. + # Fortunately we can just write to a StringIO instead. + # print("Writing completions to temp file with mode=", write_mode) + # from tempfile import TemporaryFile + # with TemporaryFile(mode=write_mode) as t: + strio = io.StringIO() + os.environ["COMP_LINE"] = command + os.environ["COMP_POINT"] = str(point) + + with pytest.raises(CustomError) as cm: + app.argcomplete_kwargs = dict( + output_stream=strio, exit_method=CustomError.exit, **kwargs + ) + app.initialize() + + if str(cm.value) != "0": + raise RuntimeError(f"Unexpected exit code {cm.value}") + out = strio.getvalue() + return out.split(self.IFS) + + def test_complete_simple_app(self, argcomplete_on): + app = ArgcompleteApp() + expected = [ + '--help', + '--debug', + '--show-config', + '--show-config-json', + '--log-level', + '--Application.', + '--ArgcompleteApp.', + ] + assert set(self.run_completer(app, "app --")) == set(expected) + + # completing class traits + assert set(self.run_completer(app, "app --App")) > { + '--Application.show_config', + '--Application.log_level', + '--Application.log_format', + } + + def test_complete_custom_completers(self, argcomplete_on): + app = ArgcompleteApp() + # test pre-defined completers for Bool/Enum + assert set(self.run_completer(app, "app --Application.log_level=")) > {"DEBUG", "INFO"} + assert set(self.run_completer(app, "app --ArgcompleteApp.show_config ")) == { + "0", + "1", + "true", + "false", + } + + # test custom completer and mid-command completions + class CustomCls(Configurable): + val = Unicode().tag( + config=True, argcompleter=argcomplete.completers.ChoicesCompleter(["foo", "bar"]) + ) + + class CustomApp(ArgcompleteApp): + classes = [CustomCls] + aliases = {("v", "val"): "CustomCls.val"} + + app = CustomApp() + assert self.run_completer(app, "app --val ") == ["foo", "bar"] + assert self.run_completer(app, "app --val=") == ["foo", "bar"] + assert self.run_completer(app, "app -v ") == ["foo", "bar"] + assert self.run_completer(app, "app -v=") == ["foo", "bar"] + assert self.run_completer(app, "app --CustomCls.val ") == ["foo", "bar"] + assert self.run_completer(app, "app --CustomCls.val=") == ["foo", "bar"] + completions = self.run_completer(app, "app --val= abc xyz", point=10) + # fixed in argcomplete >= 2.0 to return latter below + assert completions == ["--val=foo", "--val=bar"] or completions == ["foo", "bar"] + assert self.run_completer(app, "app --val --log-level=", point=10) == ["foo", "bar"] + + def test_complete_subcommands(self, argcomplete_on): + app = MainApp() + assert set(self.run_completer(app, "app ")) >= {"subapp1", "subapp2"} + assert set(self.run_completer(app, "app sub")) == {"subapp1", "subapp2"} + assert set(self.run_completer(app, "app subapp1")) == {"subapp1"} + + def test_complete_subcommands_subapp1(self, argcomplete_on): + # subcommand handling modifies _ARGCOMPLETE env var global state, so + # only can test one completion per unit test + app = MainApp() + try: + assert set(self.run_completer(app, "app subapp1 --Sub")) > { + '--SubApp1.show_config', + '--SubApp1.log_level', + '--SubApp1.log_format', + } + finally: + SubApp1.clear_instance() + + def test_complete_subcommands_subapp2(self, argcomplete_on): + app = MainApp() + try: + assert set(self.run_completer(app, "app subapp2 --")) > { + '--Application.', + '--SubApp2.', + } + finally: + SubApp2.clear_instance() + + def test_complete_subcommands_main(self, argcomplete_on): + app = MainApp() + completions = set(self.run_completer(app, "app --")) + assert completions > {'--Application.', '--MainApp.'} + assert "--SubApp1." not in completions and "--SubApp2." not in completions diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py b/contrib/python/traitlets/py3/tests/config/test_configurable.py index 357aede78a..f6499ea29d 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py +++ b/contrib/python/traitlets/py3/tests/config/test_configurable.py @@ -8,6 +8,7 @@ from unittest import TestCase from pytest import mark +from .._warnings import expected_warnings from traitlets.config.application import Application from traitlets.config.configurable import Configurable, LoggingConfigurable, SingletonConfigurable from traitlets.config.loader import Config @@ -26,8 +27,6 @@ from traitlets.traitlets import ( ) from traitlets.utils.warnings import _deprecations_shown -from traitlets.tests._warnings import expected_warnings - class MyConfigurable(Configurable): a = Integer(1, help="The integer a.").tag(config=True) diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py b/contrib/python/traitlets/py3/tests/config/test_loader.py index 3a1f96120f..3a1f96120f 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py +++ b/contrib/python/traitlets/py3/tests/config/test_loader.py diff --git a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py b/contrib/python/traitlets/py3/tests/test_traitlets.py index dab1f6ddc7..62fa726f19 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py +++ b/contrib/python/traitlets/py3/tests/test_traitlets.py @@ -62,7 +62,7 @@ from traitlets import ( ) from traitlets.utils import cast_unicode -from traitlets.tests._warnings import expected_warnings +from ._warnings import expected_warnings def change_dict(*ordered_values): diff --git a/contrib/python/traitlets/py3/tests/test_traitlets_docstring.py b/contrib/python/traitlets/py3/tests/test_traitlets_docstring.py new file mode 100644 index 0000000000..700199108f --- /dev/null +++ b/contrib/python/traitlets/py3/tests/test_traitlets_docstring.py @@ -0,0 +1,84 @@ +"""Tests for traitlets.traitlets.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +# +from traitlets import Dict, Instance, Integer, Unicode, Union +from traitlets.config import Configurable + + +def test_handle_docstring(): + class SampleConfigurable(Configurable): + pass + + class TraitTypesSampleConfigurable(Configurable): + """TraitTypesSampleConfigurable docstring""" + + trait_integer = Integer( + help="""trait_integer help text""", + config=True, + ) + trait_integer_nohelp = Integer( + config=True, + ) + trait_integer_noconfig = Integer( + help="""trait_integer_noconfig help text""", + ) + + trait_unicode = Unicode( + help="""trait_unicode help text""", + config=True, + ) + trait_unicode_nohelp = Unicode( + config=True, + ) + trait_unicode_noconfig = Unicode( + help="""trait_unicode_noconfig help text""", + ) + + trait_dict = Dict( + help="""trait_dict help text""", + config=True, + ) + trait_dict_nohelp = Dict( + config=True, + ) + trait_dict_noconfig = Dict( + help="""trait_dict_noconfig help text""", + ) + + trait_instance = Instance( + klass=SampleConfigurable, + help="""trait_instance help text""", + config=True, + ) + trait_instance_nohelp = Instance( + klass=SampleConfigurable, + config=True, + ) + trait_instance_noconfig = Instance( + klass=SampleConfigurable, + help="""trait_instance_noconfig help text""", + ) + + trait_union = Union( + [Integer(), Unicode()], + help="""trait_union help text""", + config=True, + ) + trait_union_nohelp = Union( + [Integer(), Unicode()], + config=True, + ) + trait_union_noconfig = Union( + [Integer(), Unicode()], + help="""trait_union_noconfig help text""", + ) + + base_names = SampleConfigurable().trait_names() + for name in TraitTypesSampleConfigurable().trait_names(): + if name in base_names: + continue + doc = getattr(TraitTypesSampleConfigurable, name).__doc__ + if "nohelp" not in name: + assert doc == f"{name} help text" diff --git a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets_enum.py b/contrib/python/traitlets/py3/tests/test_traitlets_enum.py index c39007e8a0..c39007e8a0 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets_enum.py +++ b/contrib/python/traitlets/py3/tests/test_traitlets_enum.py diff --git a/contrib/python/traitlets/py3/tests/test_typing.py b/contrib/python/traitlets/py3/tests/test_typing.py new file mode 100644 index 0000000000..2b4073ecf7 --- /dev/null +++ b/contrib/python/traitlets/py3/tests/test_typing.py @@ -0,0 +1,395 @@ +from __future__ import annotations + +import typing + +import pytest + +from traitlets import ( + Any, + Bool, + CInt, + Dict, + HasTraits, + Instance, + Int, + List, + Set, + TCPAddress, + Type, + Unicode, + Union, + default, + observe, + validate, +) +from traitlets.config import Config + +if not typing.TYPE_CHECKING: + + def reveal_type(*args, **kwargs): + pass + + +# mypy: disallow-untyped-calls + + +class Foo: + def __init__(self, c): + self.c = c + + +@pytest.mark.mypy_testing +def mypy_decorator_typing(): + class T(HasTraits): + foo = Unicode("").tag(config=True) + + @default("foo") + def _default_foo(self) -> str: + return "hi" + + @observe("foo") + def _foo_observer(self, change: typing.Any) -> bool: + return True + + @validate("foo") + def _foo_validate(self, commit: typing.Any) -> bool: + return True + + t = T() + reveal_type(t.foo) # R: builtins.str + reveal_type(t._foo_observer) # R: Any + reveal_type(t._foo_validate) # R: Any + + +@pytest.mark.mypy_testing +def mypy_config_typing(): + c = Config( + { + "ExtractOutputPreprocessor": {"enabled": True}, + } + ) + reveal_type(c) # R: traitlets.config.loader.Config + + +@pytest.mark.mypy_testing +def mypy_union_typing(): + class T(HasTraits): + style = Union( + [Unicode("default"), Type(klass=object)], + help="Name of the pygments style to use", + default_value="hi", + ).tag(config=True) + + t = T() + reveal_type(Union("foo")) # R: traitlets.traitlets.Union + reveal_type(Union("").tag(sync=True)) # R: traitlets.traitlets.Union + reveal_type(Union(None, allow_none=True)) # R: traitlets.traitlets.Union + reveal_type(Union(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Union + reveal_type(T.style) # R: traitlets.traitlets.Union + reveal_type(t.style) # R: Any + + +@pytest.mark.mypy_testing +def mypy_list_typing(): + class T(HasTraits): + latex_command = List( + ["xelatex", "{filename}", "-quiet"], help="Shell command used to compile latex." + ).tag(config=True) + + t = T() + reveal_type(List("foo")) # R: traitlets.traitlets.List + reveal_type(List("").tag(sync=True)) # R: traitlets.traitlets.List + reveal_type(List(None, allow_none=True)) # R: traitlets.traitlets.List + reveal_type(List(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.List + reveal_type(T.latex_command) # R: traitlets.traitlets.List + reveal_type(t.latex_command) # R: builtins.list[Any] + + +@pytest.mark.mypy_testing +def mypy_dict_typing(): + class T(HasTraits): + foo = Dict({}, help="Shell command used to compile latex.").tag(config=True) + + t = T() + reveal_type(Dict("foo")) # R: traitlets.traitlets.Dict + reveal_type(Dict("").tag(sync=True)) # R: traitlets.traitlets.Dict + reveal_type(Dict(None, allow_none=True)) # R: traitlets.traitlets.Dict + reveal_type(Dict(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Dict + reveal_type(T.foo) # R: traitlets.traitlets.Dict + reveal_type(t.foo) # R: builtins.dict[Any, Any] + + +@pytest.mark.mypy_testing +def mypy_type_typing(): + class KernelSpec: + item = Unicode("foo") + + class KernelSpecManager(HasTraits): + """A manager for kernel specs.""" + + kernel_spec_class = Type( + KernelSpec, + config=True, + help="""The kernel spec class. This is configurable to allow + subclassing of the KernelSpecManager for customized behavior. + """, + ) + other_class = Type("foo.bar.baz") + + t = KernelSpecManager() + reveal_type(t.kernel_spec_class) # R: def () -> tests.test_typing.KernelSpec@124 + reveal_type(t.kernel_spec_class()) # R: tests.test_typing.KernelSpec@124 + reveal_type(t.kernel_spec_class().item) # R: builtins.str + reveal_type(t.other_class) # R: builtins.type + reveal_type(t.other_class()) # R: Any + + +@pytest.mark.mypy_testing +def mypy_unicode_typing(): + class T(HasTraits): + export_format = Unicode( + allow_none=False, + help="""The export format to be used, either one of the built-in formats + or a dotted object name that represents the import path for an + ``Exporter`` class""", + ).tag(config=True) + + t = T() + reveal_type( + Unicode( # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]] + "foo" + ) + ) + reveal_type( + Unicode( # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]] + "" + ).tag( + sync=True + ) + ) + reveal_type( + Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]] + None, allow_none=True + ) + ) + reveal_type( + Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]] + None, allow_none=True + ).tag( + sync=True + ) + ) + reveal_type( + T.export_format # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]] + ) + reveal_type(t.export_format) # R: builtins.str + + +@pytest.mark.mypy_testing +def mypy_set_typing(): + class T(HasTraits): + remove_cell_tags = Set( + Unicode(), + default_value=[], + help=( + "Tags indicating which cells are to be removed," + "matches tags in ``cell.metadata.tags``." + ), + ).tag(config=True) + + safe_output_keys = Set( + config=True, + default_value={ + "metadata", # Not a mimetype per-se, but expected and safe. + "text/plain", + "text/latex", + "application/json", + "image/png", + "image/jpeg", + }, + help="Cell output mimetypes to render without modification", + ) + + t = T() + reveal_type(Set("foo")) # R: traitlets.traitlets.Set + reveal_type(Set("").tag(sync=True)) # R: traitlets.traitlets.Set + reveal_type(Set(None, allow_none=True)) # R: traitlets.traitlets.Set + reveal_type(Set(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Set + reveal_type(T.remove_cell_tags) # R: traitlets.traitlets.Set + reveal_type(t.remove_cell_tags) # R: builtins.set[Any] + reveal_type(T.safe_output_keys) # R: traitlets.traitlets.Set + reveal_type(t.safe_output_keys) # R: builtins.set[Any] + + +@pytest.mark.mypy_testing +def mypy_any_typing(): + class T(HasTraits): + attributes = Any( + config=True, + default_value={ + "a": ["href", "title"], + "abbr": ["title"], + "acronym": ["title"], + }, + help="Allowed HTML tag attributes", + ) + + t = T() + reveal_type(Any("foo")) # R: traitlets.traitlets.Any + reveal_type(Any("").tag(sync=True)) # R: traitlets.traitlets.Any + reveal_type(Any(None, allow_none=True)) # R: traitlets.traitlets.Any + reveal_type(Any(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Any + reveal_type(T.attributes) # R: traitlets.traitlets.Any + reveal_type(t.attributes) # R: Any + + +@pytest.mark.mypy_testing +def mypy_bool_typing(): + class T(HasTraits): + b = Bool(True).tag(sync=True) + ob = Bool(None, allow_none=True).tag(sync=True) + + t = T() + reveal_type( + Bool(True) # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]] + ) + reveal_type( + Bool( # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]] + True + ).tag(sync=True) + ) + reveal_type( + Bool( # R: traitlets.traitlets.Bool[Union[builtins.bool, None], Union[builtins.bool, builtins.int, None]] + None, allow_none=True + ) + ) + reveal_type( + Bool( # R: traitlets.traitlets.Bool[Union[builtins.bool, None], Union[builtins.bool, builtins.int, None]] + None, allow_none=True + ).tag( + sync=True + ) + ) + reveal_type( + T.b # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]] + ) + reveal_type(t.b) # R: builtins.bool + reveal_type(t.ob) # R: Union[builtins.bool, None] + reveal_type( + T.b # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]] + ) + reveal_type( + T.ob # R: traitlets.traitlets.Bool[Union[builtins.bool, None], Union[builtins.bool, builtins.int, None]] + ) + # we would expect this to be Optional[Union[bool, int]], but... + t.b = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Union[bool, int]") [assignment] + t.b = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[bool, int]") [assignment] + + +@pytest.mark.mypy_testing +def mypy_int_typing(): + class T(HasTraits): + i: Int[int, int] = Int(42).tag(sync=True) + oi: Int[int | None, int | None] = Int(42, allow_none=True).tag(sync=True) + + t = T() + reveal_type(Int(True)) # R: traitlets.traitlets.Int[builtins.int, builtins.int] + reveal_type(Int(True).tag(sync=True)) # R: traitlets.traitlets.Int[builtins.int, builtins.int] + reveal_type( + Int( # R: traitlets.traitlets.Int[Union[builtins.int, None], Union[builtins.int, None]] + None, allow_none=True + ) + ) + reveal_type( + Int( # R: traitlets.traitlets.Int[Union[builtins.int, None], Union[builtins.int, None]] + None, allow_none=True + ).tag(sync=True) + ) + reveal_type(T.i) # R: traitlets.traitlets.Int[builtins.int, builtins.int] + reveal_type(t.i) # R: builtins.int + reveal_type(t.oi) # R: Union[builtins.int, None] + reveal_type(T.i) # R: traitlets.traitlets.Int[builtins.int, builtins.int] + reveal_type( + T.oi # R: traitlets.traitlets.Int[Union[builtins.int, None], Union[builtins.int, None]] + ) + t.i = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] + t.i = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment] + t.i = 1.2 # E: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] + + +@pytest.mark.mypy_testing +def mypy_cint_typing(): + class T(HasTraits): + i = CInt(42).tag(sync=True) + oi = CInt(42, allow_none=True).tag(sync=True) + + t = T() + reveal_type(CInt(42)) # R: traitlets.traitlets.CInt[builtins.int, Any] + reveal_type(CInt(42).tag(sync=True)) # R: traitlets.traitlets.CInt[builtins.int, Any] + reveal_type( + CInt(None, allow_none=True) # R: traitlets.traitlets.CInt[Union[builtins.int, None], Any] + ) + reveal_type( + CInt( # R: traitlets.traitlets.CInt[Union[builtins.int, None], Any] + None, allow_none=True + ).tag(sync=True) + ) + reveal_type(T.i) # R: traitlets.traitlets.CInt[builtins.int, Any] + reveal_type(t.i) # R: builtins.int + reveal_type(t.oi) # R: Union[builtins.int, None] + reveal_type(T.i) # R: traitlets.traitlets.CInt[builtins.int, Any] + reveal_type(T.oi) # R: traitlets.traitlets.CInt[Union[builtins.int, None], Any] + + +@pytest.mark.mypy_testing +def mypy_tcp_typing(): + class T(HasTraits): + tcp = TCPAddress() + otcp = TCPAddress(None, allow_none=True) + + t = T() + reveal_type(t.tcp) # R: Tuple[builtins.str, builtins.int] + reveal_type( + T.tcp # R: traitlets.traitlets.TCPAddress[Tuple[builtins.str, builtins.int], Tuple[builtins.str, builtins.int]] + ) + reveal_type( + T.tcp.tag( # R:traitlets.traitlets.TCPAddress[Tuple[builtins.str, builtins.int], Tuple[builtins.str, builtins.int]] + sync=True + ) + ) + reveal_type(t.otcp) # R: Union[Tuple[builtins.str, builtins.int], None] + reveal_type( + T.otcp # R: traitlets.traitlets.TCPAddress[Union[Tuple[builtins.str, builtins.int], None], Union[Tuple[builtins.str, builtins.int], None]] + ) + reveal_type( + T.otcp.tag( # R: traitlets.traitlets.TCPAddress[Union[Tuple[builtins.str, builtins.int], None], Union[Tuple[builtins.str, builtins.int], None]] + sync=True + ) + ) + t.tcp = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Tuple[str, int]") [assignment] + t.otcp = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Optional[Tuple[str, int]]") [assignment] + t.tcp = None # E: Incompatible types in assignment (expression has type "None", variable has type "Tuple[str, int]") [assignment] + + +@pytest.mark.mypy_testing +def mypy_instance_typing(): + class T(HasTraits): + inst = Instance(Foo) + oinst = Instance(Foo, allow_none=True) + oinst_string = Instance("Foo", allow_none=True) + + t = T() + reveal_type(t.inst) # R: tests.test_typing.Foo + reveal_type(T.inst) # R: traitlets.traitlets.Instance[tests.test_typing.Foo] + reveal_type(T.inst.tag(sync=True)) # R: traitlets.traitlets.Instance[tests.test_typing.Foo] + reveal_type(t.oinst) # R: Union[tests.test_typing.Foo, None] + reveal_type(t.oinst_string) # R: Union[Any, None] + reveal_type(T.oinst) # R: traitlets.traitlets.Instance[Union[tests.test_typing.Foo, None]] + reveal_type( + T.oinst.tag( # R: traitlets.traitlets.Instance[Union[tests.test_typing.Foo, None]] + sync=True + ) + ) + t.inst = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Foo") [assignment] + t.oinst = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Optional[Foo]") [assignment] + t.inst = None # E: Incompatible types in assignment (expression has type "None", variable has type "Foo") [assignment] diff --git a/contrib/python/traitlets/py3/tests/utils/__init__.py b/contrib/python/traitlets/py3/tests/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/contrib/python/traitlets/py3/tests/utils/__init__.py diff --git a/contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py b/contrib/python/traitlets/py3/tests/utils/test_bunch.py index 223124d7d5..223124d7d5 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/tests/test_bunch.py +++ b/contrib/python/traitlets/py3/tests/utils/test_bunch.py diff --git a/contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py b/contrib/python/traitlets/py3/tests/utils/test_decorators.py index 5410c20137..d6bf8414e5 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/tests/test_decorators.py +++ b/contrib/python/traitlets/py3/tests/utils/test_decorators.py @@ -1,7 +1,7 @@ from inspect import Parameter, signature from unittest import TestCase -from traitlets.traitlets import HasTraits, Int, Unicode +from traitlets import HasTraits, Int, Unicode from traitlets.utils.decorators import signature_has_traits diff --git a/contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py b/contrib/python/traitlets/py3/tests/utils/test_importstring.py index 8ce28add41..8ce28add41 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/tests/test_importstring.py +++ b/contrib/python/traitlets/py3/tests/utils/test_importstring.py diff --git a/contrib/python/traitlets/py3/tests/ya.make b/contrib/python/traitlets/py3/tests/ya.make index 6a5cd7cf46..6ffd29993d 100644 --- a/contrib/python/traitlets/py3/tests/ya.make +++ b/contrib/python/traitlets/py3/tests/ya.make @@ -1,20 +1,27 @@ PY3TEST() PEERDIR( + contrib/python/argcomplete contrib/python/traitlets + contrib/python/pytest-mock ) -SRCDIR(contrib/python/traitlets/py3/traitlets) - TEST_SRCS( - config/tests/test_application.py - config/tests/test_configurable.py - config/tests/test_loader.py - tests/test_traitlets.py - tests/test_traitlets_enum.py - utils/tests/test_bunch.py - utils/tests/test_decorators.py - utils/tests/test_importstring.py + __init__.py + _warnings.py + config/__init__.py + config/test_application.py + config/test_argcomplete.py + config/test_configurable.py + config/test_loader.py + test_traitlets.py + test_traitlets_docstring.py + test_traitlets_enum.py + test_typing.py + utils/__init__.py + utils/test_bunch.py + utils/test_decorators.py + utils/test_importstring.py ) NO_LINT() diff --git a/contrib/python/traitlets/py3/traitlets/__init__.py b/contrib/python/traitlets/py3/traitlets/__init__.py index 96ebe57f1b..2641c443e9 100644 --- a/contrib/python/traitlets/py3/traitlets/__init__.py +++ b/contrib/python/traitlets/py3/traitlets/__init__.py @@ -1,4 +1,6 @@ """Traitlets Python configuration system""" +import typing as _t + from . import traitlets from ._version import __version__, version_info from .traitlets import * @@ -19,7 +21,7 @@ __all__ = [ class Sentinel(traitlets.Sentinel): # type:ignore[name-defined] - def __init__(self, *args, **kwargs): + def __init__(self, *args: _t.Any, **kwargs: _t.Any) -> None: super().__init__(*args, **kwargs) warn( """ diff --git a/contrib/python/traitlets/py3/traitlets/_version.py b/contrib/python/traitlets/py3/traitlets/_version.py index d477cb8ce1..5fbb9a2599 100644 --- a/contrib/python/traitlets/py3/traitlets/_version.py +++ b/contrib/python/traitlets/py3/traitlets/_version.py @@ -5,7 +5,7 @@ import re from typing import List # Version string must appear intact for hatch versioning -__version__ = "5.10.1" +__version__ = "5.11.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)" diff --git a/contrib/python/traitlets/py3/traitlets/config/application.py b/contrib/python/traitlets/py3/traitlets/config/application.py index fb185f0ae5..5993b4c7f2 100644 --- a/contrib/python/traitlets/py3/traitlets/config/application.py +++ b/contrib/python/traitlets/py3/traitlets/config/application.py @@ -2,7 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - +from __future__ import annotations import functools import json @@ -17,7 +17,6 @@ from contextlib import suppress from copy import deepcopy from logging.config import dictConfig from textwrap import dedent -from typing import Any, Callable, TypeVar, cast from traitlets.config.configurable import Configurable, SingletonConfigurable from traitlets.config.loader import ( @@ -40,6 +39,7 @@ from traitlets.traitlets import ( observe, observe_compat, ) +from traitlets.utils.bunch import Bunch from traitlets.utils.nested_update import nested_update from traitlets.utils.text import indent, wrap_paragraphs @@ -95,7 +95,11 @@ else: IS_PYTHONW = sys.executable and sys.executable.endswith("pythonw.exe") -T = TypeVar("T", bound=Callable[..., Any]) +T = t.TypeVar("T", bound=t.Callable[..., t.Any]) +AnyLogger = t.Union[logging.Logger, logging.LoggerAdapter] +StrDict = t.Dict[str, t.Any] +ArgvType = t.Optional[t.List[str]] +ClassesType = t.List[t.Type[Configurable]] def catch_config_error(method: T) -> T: @@ -108,7 +112,7 @@ def catch_config_error(method: T) -> T: """ @functools.wraps(method) - def inner(app, *args, **kwargs): + def inner(app: Application, *args: t.Any, **kwargs: t.Any) -> t.Any: try: return method(app, *args, **kwargs) except (TraitError, ArgumentError) as e: @@ -116,7 +120,7 @@ def catch_config_error(method: T) -> T: app.log.debug("Config at the time: %s", app.config) app.exit(1) - return cast(T, inner) + return t.cast(T, inner) class ApplicationError(Exception): @@ -136,7 +140,7 @@ class LevelFormatter(logging.Formatter): highlevel_limit = logging.WARN highlevel_format = " %(levelname)s |" - def format(self, record): + def format(self, record: logging.LogRecord) -> str: if record.levelno >= self.highlevel_limit: record.highlevel = self.highlevel_format % record.__dict__ else: @@ -149,35 +153,29 @@ class Application(SingletonConfigurable): # The name of the application, will usually match the name of the command # line application - name: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode("application") + name: str | Unicode[str, str | bytes] = Unicode("application") # The description of the application that is printed at the beginning # of the help. - description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( - "This is an application." - ) + description: str | Unicode[str, str | bytes] = Unicode("This is an application.") # default section descriptions - option_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( - option_description - ) - keyvalue_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( - keyvalue_description - ) - subcommand_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( - subcommand_description - ) + option_description: str | Unicode[str, str | bytes] = Unicode(option_description) + keyvalue_description: str | Unicode[str, str | bytes] = Unicode(keyvalue_description) + subcommand_description: str | Unicode[str, str | bytes] = Unicode(subcommand_description) python_config_loader_class = PyFileConfigLoader json_config_loader_class = JSONFileConfigLoader # The usage and example string that goes at the end of the help string. - examples: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode() + examples: str | Unicode[str, str | bytes] = Unicode() # A sequence of Configurable subclasses whose config=True attributes will # be exposed at the command line. - classes: t.List[t.Type[t.Any]] = [] + classes: ClassesType = [] - def _classes_inc_parents(self, classes=None): + def _classes_inc_parents( + self, classes: ClassesType | None = None + ) -> t.Generator[type[Configurable], None, None]: """Iterate through configurable classes, including configurable parents :param classes: @@ -198,18 +196,16 @@ class Application(SingletonConfigurable): yield parent # The version string of this application. - version: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode("0.0") + version: str | Unicode[str, str | bytes] = Unicode("0.0") # the argv used to initialize the application - argv: t.Union[t.List[str], List] = List() + argv = List() # Whether failing to load config files should prevent startup - raise_config_file_errors: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool( - TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR - ) + raise_config_file_errors = Bool(TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR) # The log level for the application - log_level: t.Union[str, int, Enum[t.Any, t.Any]] = Enum( + log_level = Enum( (0, 10, 20, 30, 40, 50, "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"), default_value=logging.WARN, help="Set the log level by value or name.", @@ -217,16 +213,16 @@ class Application(SingletonConfigurable): _log_formatter_cls = LevelFormatter - log_datefmt: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( + log_datefmt = Unicode( "%Y-%m-%d %H:%M:%S", help="The date format used by logging formatters for %(asctime)s" ).tag(config=True) - log_format: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( + log_format = Unicode( "[%(name)s]%(highlevel)s %(message)s", help="The Logging format template", ).tag(config=True) - def get_default_logging_config(self): + def get_default_logging_config(self) -> StrDict: """Return the base logging configuration. The default is to log to stderr using a StreamHandler, if no default @@ -239,7 +235,7 @@ class Application(SingletonConfigurable): control of logging. """ - config: t.Dict[str, t.Any] = { + config: StrDict = { "version": 1, "handlers": { "console": { @@ -278,7 +274,7 @@ class Application(SingletonConfigurable): return config @observe("log_datefmt", "log_format", "log_level", "logging_config") - def _observe_logging_change(self, change): + def _observe_logging_change(self, change: Bunch) -> None: # convert log level strings to ints log_level = self.log_level if isinstance(log_level, str): @@ -286,10 +282,10 @@ class Application(SingletonConfigurable): self._configure_logging() @observe("log", type="default") - def _observe_logging_default(self, change): + def _observe_logging_default(self, change: Bunch) -> None: self._configure_logging() - def _configure_logging(self): + def _configure_logging(self) -> None: config = self.get_default_logging_config() nested_update(config, self.logging_config or {}) dictConfig(config) @@ -297,7 +293,7 @@ class Application(SingletonConfigurable): self._logging_configured = True @default("log") - def _log_default(self): + def _log_default(self) -> AnyLogger: """Start logging for this application.""" log = logging.getLogger(self.__class__.__name__) log.propagate = False @@ -366,17 +362,13 @@ class Application(SingletonConfigurable): #: Values might be like "Class.trait" strings of two-tuples: (Class.trait, help-text), # or just the "Class.trait" string, in which case the help text is inferred from the # corresponding trait - aliases: t.Dict[t.Union[str, t.Tuple[str, ...]], t.Union[str, t.Tuple[str, str]]] = { - "log-level": "Application.log_level" - } + aliases: StrDict = {"log-level": "Application.log_level"} # flags for loading Configurables or store_const style flags # flags are loaded from this dict by '--key' flags # this must be a dict of two-tuples, the first element being the Config/dict # and the second being the help string for the flag - flags: t.Dict[ - t.Union[str, t.Tuple[str, ...]], t.Tuple[t.Union[t.Dict[str, t.Any], Config], str] - ] = { + flags: StrDict = { "debug": ( { "Application": { @@ -408,12 +400,12 @@ class Application(SingletonConfigurable): # this must be a dict of two-tuples, # the first element being the application class/import string # and the second being the help string for the subcommand - subcommands: t.Union[t.Dict[str, t.Tuple[t.Any, str]], Dict] = Dict() + subcommands: dict[str, t.Any] | Dict = Dict() # parse_command_line will initialize a subapp, if requested subapp = Instance("traitlets.config.application.Application", allow_none=True) # extra command-line arguments that don't set config values - extra_args: t.Union[t.List[str], List] = List(Unicode()) + extra_args = List(Unicode()) cli_config = Instance( Config, @@ -428,20 +420,20 @@ class Application(SingletonConfigurable): _loaded_config_files = List() - show_config: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool( + show_config = Bool( help="Instead of starting the Application, dump configuration to stdout" ).tag(config=True) - show_config_json: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool( + show_config_json = Bool( help="Instead of starting the Application, dump configuration to stdout (as JSON)" ).tag(config=True) @observe("show_config_json") - def _show_config_json_changed(self, change): + def _show_config_json_changed(self, change: Bunch) -> None: self.show_config = change.new @observe("show_config") - def _show_config_changed(self, change): + def _show_config_changed(self, change: Bunch) -> None: if change.new: self._save_start = self.start self.start = self.start_show_config # type:ignore[method-assign] @@ -460,27 +452,28 @@ class Application(SingletonConfigurable): @observe("config") @observe_compat - def _config_changed(self, change): + def _config_changed(self, change: Bunch) -> None: super()._config_changed(change) self.log.debug("Config changed: %r", change.new) @catch_config_error - def initialize(self, argv=None): + def initialize(self, argv: ArgvType = None) -> None: """Do the basic steps to configure me. Override in subclasses. """ self.parse_command_line(argv) - def start(self): + def start(self) -> None: """Start the app mainloop. Override in subclasses. """ if self.subapp is not None: + assert isinstance(self.subapp, Application) return self.subapp.start() - def start_show_config(self): + def start_show_config(self) -> None: """start function used when show_config is True""" config = self.config.copy() # exclude show_config flags from displayed config @@ -507,28 +500,28 @@ class Application(SingletonConfigurable): if not class_config: continue print(classname) - pformat_kwargs: t.Dict[str, t.Any] = dict(indent=4, compact=True) + pformat_kwargs: StrDict = dict(indent=4, compact=True) for traitname in sorted(class_config): value = class_config[traitname] print(f" .{traitname} = {pprint.pformat(value, **pformat_kwargs)}") - def print_alias_help(self): + def print_alias_help(self) -> None: """Print the alias parts of the help.""" print("\n".join(self.emit_alias_help())) - def emit_alias_help(self): + def emit_alias_help(self) -> t.Generator[str, None, None]: """Yield the lines for alias part of the help.""" if not self.aliases: return - classdict = {} + classdict: dict[str, type[Configurable]] = {} for cls in self.classes: # include all parents (up to, but excluding Configurable) in available names for c in cls.mro()[:-3]: - classdict[c.__name__] = c + classdict[c.__name__] = t.cast(t.Type[Configurable], c) - fhelp: t.Optional[str] + fhelp: str | None for alias, longname in self.aliases.items(): try: if isinstance(longname, tuple): @@ -540,27 +533,26 @@ class Application(SingletonConfigurable): cls = classdict[classname] trait = cls.class_traits(config=True)[traitname] - fhelp = cls.class_get_trait_help(trait, helptext=fhelp).splitlines() + fhelp_lines = cls.class_get_trait_help(trait, helptext=fhelp).splitlines() if not isinstance(alias, tuple): - alias = (alias,) + alias = (alias,) # type:ignore[assignment] alias = sorted(alias, key=len) # type:ignore[assignment] alias = ", ".join(("--%s" if len(m) > 1 else "-%s") % m for m in alias) # reformat first line - assert fhelp is not None - fhelp[0] = fhelp[0].replace("--" + longname, alias) # type:ignore - yield from fhelp + fhelp_lines[0] = fhelp_lines[0].replace("--" + longname, alias) + yield from fhelp_lines yield indent("Equivalent to: [--%s]" % longname) except Exception as ex: self.log.error("Failed collecting help-message for alias %r, due to: %s", alias, ex) raise - def print_flag_help(self): + def print_flag_help(self) -> None: """Print the flag part of the help.""" print("\n".join(self.emit_flag_help())) - def emit_flag_help(self): + def emit_flag_help(self) -> t.Generator[str, None, None]: """Yield the lines for the flag part of the help.""" if not self.flags: return @@ -568,7 +560,7 @@ class Application(SingletonConfigurable): for flags, (cfg, fhelp) in self.flags.items(): try: if not isinstance(flags, tuple): - flags = (flags,) + flags = (flags,) # type:ignore[assignment] flags = sorted(flags, key=len) # type:ignore[assignment] flags = ", ".join(("--%s" if len(m) > 1 else "-%s") % m for m in flags) yield flags @@ -584,11 +576,11 @@ class Application(SingletonConfigurable): self.log.error("Failed collecting help-message for flag %r, due to: %s", flags, ex) raise - def print_options(self): + def print_options(self) -> None: """Print the options part of the help.""" print("\n".join(self.emit_options_help())) - def emit_options_help(self): + def emit_options_help(self) -> t.Generator[str, None, None]: """Yield the lines for the options part of the help.""" if not self.flags and not self.aliases: return @@ -603,11 +595,11 @@ class Application(SingletonConfigurable): yield from self.emit_alias_help() yield "" - def print_subcommands(self): + def print_subcommands(self) -> None: """Print the subcommand part of the help.""" print("\n".join(self.emit_subcommands_help())) - def emit_subcommands_help(self): + def emit_subcommands_help(self) -> t.Generator[str, None, None]: """Yield the lines for the subcommand part of the help.""" if not self.subcommands: return @@ -624,7 +616,7 @@ class Application(SingletonConfigurable): yield indent(dedent(help.strip())) yield "" - def emit_help_epilogue(self, classes): + def emit_help_epilogue(self, classes: bool) -> t.Generator[str, None, None]: """Yield the very bottom lines of the help message. If classes=False (the default), print `--help-all` msg. @@ -633,14 +625,14 @@ class Application(SingletonConfigurable): yield "To see all available configurables, use `--help-all`." yield "" - def print_help(self, classes=False): + def print_help(self, classes: bool = False) -> None: """Print the help for each Configurable class in self.classes. If classes=False (the default), only flags and aliases are printed. """ print("\n".join(self.emit_help(classes=classes))) - def emit_help(self, classes=False): + def emit_help(self, classes: bool = False) -> t.Generator[str, None, None]: """Yield the help-lines for each Configurable class in self.classes. If classes=False (the default), only flags and aliases are printed. @@ -665,28 +657,28 @@ class Application(SingletonConfigurable): yield from self.emit_help_epilogue(classes) - def document_config_options(self): + def document_config_options(self) -> str: """Generate rST format documentation for the config options this application Returns a multiline string. """ return "\n".join(c.class_config_rst_doc() for c in self._classes_inc_parents()) - def print_description(self): + def print_description(self) -> None: """Print the application description.""" print("\n".join(self.emit_description())) - def emit_description(self): + def emit_description(self) -> t.Generator[str, None, None]: """Yield lines with the application description.""" for p in wrap_paragraphs(self.description or self.__doc__ or ""): yield p yield "" - def print_examples(self): + def print_examples(self) -> None: """Print usage and examples (see `emit_examples()`).""" print("\n".join(self.emit_examples())) - def emit_examples(self): + def emit_examples(self) -> t.Generator[str, None, None]: """Yield lines with the usage and examples. This usage string goes at the end of the command line help string @@ -699,12 +691,12 @@ class Application(SingletonConfigurable): yield indent(dedent(self.examples.strip())) yield "" - def print_version(self): + def print_version(self) -> None: """Print the version string.""" print(self.version) @catch_config_error - def initialize_subcommand(self, subc, argv=None): + def initialize_subcommand(self, subc: str, argv: ArgvType = None) -> None: """Initialize a subcommand with argv.""" val = self.subcommands.get(subc) assert val is not None @@ -726,9 +718,9 @@ class Application(SingletonConfigurable): raise AssertionError("Invalid mappings for subcommand '%s'!" % subc) # ... and finally initialize subapp. - self.subapp.initialize(argv) + self.subapp.initialize(argv) # type:ignore[union-attr] - def flatten_flags(self): + def flatten_flags(self) -> tuple[dict[str, t.Any], dict[str, t.Any]]: """Flatten flags and aliases for loaders, so cl-args override as expected. This prevents issues such as an alias pointing to InteractiveShell, @@ -751,11 +743,11 @@ class Application(SingletonConfigurable): mro_tree[parent.__name__].append(clsname) # flatten aliases, which have the form: # { 'alias' : 'Class.trait' } - aliases: t.Dict[str, str] = {} + aliases: dict[str, str] = {} for alias, longname in self.aliases.items(): if isinstance(longname, tuple): longname, _ = longname - cls, trait = longname.split(".", 1) # type:ignore + cls, trait = longname.split(".", 1) children = mro_tree[cls] # type:ignore[index] if len(children) == 1: # exactly one descendent, promote alias @@ -769,8 +761,8 @@ class Application(SingletonConfigurable): # { 'key' : ({'Cls' : {'trait' : value}}, 'help')} flags = {} for key, (flagdict, help) in self.flags.items(): - newflag: t.Dict[t.Any, t.Any] = {} - for cls, subdict in flagdict.items(): # type:ignore + newflag: dict[t.Any, t.Any] = {} + for cls, subdict in flagdict.items(): children = mro_tree[cls] # type:ignore[index] # exactly one descendent, promote flag section if len(children) == 1: @@ -782,18 +774,24 @@ class Application(SingletonConfigurable): newflag[cls] = subdict if not isinstance(key, tuple): - key = (key,) + key = (key,) # type:ignore[assignment] for k in key: flags[k] = (newflag, help) return flags, aliases - def _create_loader(self, argv, aliases, flags, classes): + def _create_loader( + self, + argv: list[str] | None, + aliases: StrDict, + flags: StrDict, + classes: ClassesType | None, + ) -> KVArgParseConfigLoader: return KVArgParseConfigLoader( argv, aliases, flags, classes=classes, log=self.log, subcommands=self.subcommands ) @classmethod - def _get_sys_argv(cls, check_argcomplete: bool = False) -> t.List[str]: + def _get_sys_argv(cls, check_argcomplete: bool = False) -> list[str]: """Get `sys.argv` or equivalent from `argcomplete` `argcomplete`'s strategy is to call the python script with no arguments, @@ -819,7 +817,7 @@ class Application(SingletonConfigurable): return sys.argv @classmethod - def _handle_argcomplete_for_subcommand(cls): + def _handle_argcomplete_for_subcommand(cls) -> None: """Helper for `argcomplete` to recognize `traitlets` subcommands `argcomplete` does not know that `traitlets` has already consumed subcommands, @@ -839,7 +837,7 @@ class Application(SingletonConfigurable): pass @catch_config_error - def parse_command_line(self, argv=None): + def parse_command_line(self, argv: ArgvType = None) -> None: """Parse the command line arguments.""" assert not isinstance(argv, str) if argv is None: @@ -877,7 +875,7 @@ class Application(SingletonConfigurable): # flatten flags&aliases, so cl-args get appropriate priority: flags, aliases = self.flatten_flags() - classes = tuple(self._classes_with_config_traits()) + classes = list(self._classes_with_config_traits()) loader = self._create_loader(argv, aliases, flags, classes=classes) try: self.cli_config = deepcopy(loader.load_config()) @@ -890,13 +888,18 @@ class Application(SingletonConfigurable): self.extra_args = loader.extra_args @classmethod - def _load_config_files(cls, basefilename, path=None, log=None, raise_config_file_errors=False): + def _load_config_files( + cls, + basefilename: str, + path: str | t.Sequence[str | None] | None, + log: AnyLogger | None = None, + raise_config_file_errors: bool = False, + ) -> t.Generator[t.Any, None, None]: """Load config files (py,json) by filename and path. yield each config object in turn. """ - - if not isinstance(path, list): + if isinstance(path, str) or path is None: path = [path] for current in reversed(path): # path list is in descending priority order, so load files backwards: @@ -904,8 +907,8 @@ class Application(SingletonConfigurable): if log: log.debug("Looking for %s in %s", basefilename, current or os.getcwd()) jsonloader = cls.json_config_loader_class(basefilename + ".json", path=current, log=log) - loaded: t.List[t.Any] = [] - filenames: t.List[str] = [] + loaded: list[t.Any] = [] + filenames: list[str] = [] for loader in [pyloader, jsonloader]: config = None try: @@ -941,12 +944,14 @@ class Application(SingletonConfigurable): filenames.append(loader.full_filename) @property - def loaded_config_files(self): + def loaded_config_files(self) -> list[str]: """Currently loaded configuration files""" return self._loaded_config_files[:] @catch_config_error - def load_config_file(self, filename, path=None): + def load_config_file( + self, filename: str, path: str | t.Sequence[str | None] | None = None + ) -> None: """Load config files by filename and path.""" filename, ext = os.path.splitext(filename) new_config = Config() @@ -965,7 +970,9 @@ class Application(SingletonConfigurable): new_config.merge(self.cli_config) self.update_config(new_config) - def _classes_with_config_traits(self, classes=None): + def _classes_with_config_traits( + self, classes: ClassesType | None = None + ) -> t.Generator[type[Configurable], None, None]: """ Yields only classes with configurable traits, and their subclasses. @@ -987,7 +994,7 @@ class Application(SingletonConfigurable): for cls in self._classes_inc_parents(classes) ) - def is_any_parent_included(cls): + def is_any_parent_included(cls: t.Any) -> bool: return any(b in cls_to_config and cls_to_config[b] for b in cls.__bases__) # Mark "empty" classes for inclusion if their parents own-traits, @@ -1005,7 +1012,7 @@ class Application(SingletonConfigurable): if inc_yes: yield cl - def generate_config_file(self, classes=None): + def generate_config_file(self, classes: ClassesType | None = None) -> str: """generate default config file from Configurables""" lines = ["# Configuration file for %s." % self.name] lines.append("") @@ -1017,7 +1024,7 @@ class Application(SingletonConfigurable): lines.append(cls.class_config_section(config_classes)) return "\n".join(lines) - def close_handlers(self): + def close_handlers(self) -> None: if getattr(self, "_logging_configured", False): # don't attempt to close handlers unless they have been opened # (note accessing self.log.handlers will create handlers if they @@ -1027,16 +1034,16 @@ class Application(SingletonConfigurable): handler.close() self._logging_configured = False - def exit(self, exit_status=0): + def exit(self, exit_status: int | str | None = 0) -> None: self.log.debug("Exiting application: %s" % self.name) self.close_handlers() sys.exit(exit_status) - def __del__(self): + def __del__(self) -> None: self.close_handlers() @classmethod - def launch_instance(cls, argv=None, **kwargs): + def launch_instance(cls, argv: ArgvType = None, **kwargs: t.Any) -> None: """Launch a global instance of this Application If a global instance already exists, this reinitializes and starts it @@ -1054,7 +1061,7 @@ default_aliases = Application.aliases default_flags = Application.flags -def boolean_flag(name, configurable, set_help="", unset_help=""): +def boolean_flag(name: str, configurable: str, set_help: str = "", unset_help: str = "") -> StrDict: """Helper for building basic --trait, --no-trait flags. Parameters @@ -1085,7 +1092,7 @@ def boolean_flag(name, configurable, set_help="", unset_help=""): return {name: (setter, set_help), "no-" + name: (unsetter, unset_help)} -def get_config(): +def get_config() -> Config: """Get the config object for the global Application instance, if there is one otherwise return an empty config object diff --git a/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py b/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py index ee1e51b492..82112aaf6b 100644 --- a/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py +++ b/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py @@ -15,7 +15,7 @@ except ImportError: # This module and its utility methods are written to not crash even # if argcomplete is not installed. class StubModule: - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> t.Any: if not attr.startswith("__"): raise ModuleNotFoundError("No module named 'argcomplete'") raise AttributeError(f"argcomplete stub module has no attribute '{attr}'") @@ -63,7 +63,7 @@ def get_argcomplete_cwords() -> t.Optional[t.List[str]]: return comp_words -def increment_argcomplete_index(): +def increment_argcomplete_index() -> None: """Assumes ``$_ARGCOMPLETE`` is set and `argcomplete` is importable Increment the index pointed to by ``$_ARGCOMPLETE``, which is used to @@ -122,7 +122,7 @@ class ExtendedCompletionFinder(CompletionFinder): ] return matched_completions - def inject_class_to_parser(self, cls): + def inject_class_to_parser(self, cls: t.Any) -> None: """Add dummy arguments to our ArgumentParser for the traits of this class The argparse-based loader currently does not actually add any class traits to diff --git a/contrib/python/traitlets/py3/traitlets/config/configurable.py b/contrib/python/traitlets/py3/traitlets/config/configurable.py index f448e696b8..77b4214e45 100644 --- a/contrib/python/traitlets/py3/traitlets/config/configurable.py +++ b/contrib/python/traitlets/py3/traitlets/config/configurable.py @@ -2,7 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - +from __future__ import annotations import logging import typing as t @@ -15,12 +15,14 @@ from traitlets.traitlets import ( Dict, HasTraits, Instance, + TraitType, default, observe, observe_compat, validate, ) from traitlets.utils import warnings +from traitlets.utils.bunch import Bunch from traitlets.utils.text import indent, wrap_paragraphs from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key @@ -29,6 +31,11 @@ from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key # Helper classes for Configurables # ----------------------------------------------------------------------------- +if t.TYPE_CHECKING: + LoggerType = t.Union[logging.Logger, logging.LoggerAdapter[t.Any]] +else: + LoggerType = t.Any + class ConfigurableError(Exception): pass @@ -87,7 +94,7 @@ class Configurable(HasTraits): # record traits set by config config_override_names = set() - def notice_config_override(change): + def notice_config_override(change: Bunch) -> None: """Record traits set by both config and kwargs. They will need to be overridden again after loading config. @@ -120,7 +127,7 @@ class Configurable(HasTraits): # ------------------------------------------------------------------------- @classmethod - def section_names(cls): + def section_names(cls) -> list[str]: """return section names as a list""" return [ c.__name__ @@ -128,7 +135,7 @@ class Configurable(HasTraits): if issubclass(c, Configurable) and issubclass(cls, c) ] - def _find_my_config(self, cfg): + def _find_my_config(self, cfg: Config) -> t.Any: """extract my config from a global Config object will construct a Config object of only the config values that apply to me @@ -153,7 +160,9 @@ class Configurable(HasTraits): my_config.merge(c[sname]) return my_config - def _load_config(self, cfg, section_names=None, traits=None): + def _load_config( + self, cfg: Config, section_names: list[str] | None = None, traits: list[str] | None = None + ) -> None: """load traits from a Config object""" if traits is None: @@ -187,7 +196,7 @@ class Configurable(HasTraits): warn = self.log.warning else: - def warn(msg): + def warn(msg: t.Any) -> None: return warnings.warn(msg, UserWarning, stacklevel=9) matches = get_close_matches(name, traits) @@ -203,7 +212,7 @@ class Configurable(HasTraits): @observe("config") @observe_compat - def _config_changed(self, change): + def _config_changed(self, change: Bunch) -> None: """Update all the class traits having ``config=True`` in metadata. For any class trait with a ``config`` metadata attribute that is @@ -219,7 +228,7 @@ class Configurable(HasTraits): section_names = self.section_names() self._load_config(change.new, traits=traits, section_names=section_names) - def update_config(self, config): + def update_config(self, config: Config) -> None: """Update config and load the new values""" # traitlets prior to 4.2 created a copy of self.config in order to trigger change events. # Some projects (IPython < 5) relied upon one side effect of this, @@ -236,7 +245,7 @@ class Configurable(HasTraits): # DO NOT trigger full trait-change @classmethod - def class_get_help(cls, inst=None): + def class_get_help(cls, inst: HasTraits | None = None) -> str: """Get the help string for this class in ReST format. If `inst` is given, its current trait values will be used in place of @@ -253,7 +262,12 @@ class Configurable(HasTraits): return "\n".join(final_help) @classmethod - def class_get_trait_help(cls, trait, inst=None, helptext=None): + def class_get_trait_help( + cls, + trait: TraitType[t.Any, t.Any], + inst: HasTraits | None = None, + helptext: str | None = None, + ) -> str: """Get the helptext string for a single trait. :param inst: @@ -291,7 +305,7 @@ class Configurable(HasTraits): lines.append(indent("Choices: %s" % trait.info())) if inst is not None: - lines.append(indent(f"Current: {getattr(inst, trait.name)!r}")) + lines.append(indent(f"Current: {getattr(inst, trait.name or '')!r}")) else: try: dvr = trait.default_value_repr() @@ -305,12 +319,14 @@ class Configurable(HasTraits): return "\n".join(lines) @classmethod - def class_print_help(cls, inst=None): + def class_print_help(cls, inst: HasTraits | None = None) -> None: """Get the help string for a single trait and print it.""" print(cls.class_get_help(inst)) @classmethod - def _defining_class(cls, trait, classes): + def _defining_class( + cls, trait: TraitType[t.Any, t.Any], classes: t.Sequence[type[HasTraits]] + ) -> type[Configurable]: """Get the class that defines a trait For reducing redundant help output in config files. @@ -338,7 +354,7 @@ class Configurable(HasTraits): return defining_cls @classmethod - def class_config_section(cls, classes=None): + def class_config_section(cls, classes: t.Sequence[type[HasTraits]] | None = None) -> str: """Get the config section for this class. Parameters @@ -348,7 +364,7 @@ class Configurable(HasTraits): Used to reduce redundant information. """ - def c(s): + def c(s: str) -> str: """return a commented, wrapped block.""" s = "\n\n".join(wrap_paragraphs(s, 78)) @@ -398,7 +414,7 @@ class Configurable(HasTraits): return "\n".join(lines) @classmethod - def class_config_rst_doc(cls): + def class_config_rst_doc(cls) -> str: """Generate rST documentation for this class' config options. Excludes traits defined on parent classes. @@ -447,10 +463,10 @@ class LoggingConfigurable(Configurable): is to get the logger from the currently running Application. """ - log = Any(help="Logger or LoggerAdapter instance") + log = Any(help="Logger or LoggerAdapter instance", allow_none=False) @validate("log") - def _validate_log(self, proposal): + def _validate_log(self, proposal: Bunch) -> LoggerType: if not isinstance(proposal.value, (logging.Logger, logging.LoggerAdapter)): # warn about unsupported type, but be lenient to allow for duck typing warnings.warn( @@ -459,18 +475,18 @@ class LoggingConfigurable(Configurable): UserWarning, stacklevel=2, ) - return proposal.value + return proposal.value # type:ignore[no-any-return] @default("log") - def _log_default(self): + def _log_default(self) -> LoggerType: if isinstance(self.parent, LoggingConfigurable): assert self.parent is not None - return self.parent.log + return t.cast(logging.Logger, self.parent.log) from traitlets import log return log.get_logger() - def _get_log_handler(self): + def _get_log_handler(self) -> logging.Handler | None: """Return the default Handler Returns None if none can be found @@ -478,13 +494,16 @@ class LoggingConfigurable(Configurable): Deprecated, this now returns the first log handler which may or may not be the default one. """ - logger = self.log - if isinstance(logger, logging.LoggerAdapter): - logger = logger.logger + if not self.log: + return None + logger = self.log if isinstance(self.log, logging.Logger) else self.log.logger if not getattr(logger, "handlers", None): # no handlers attribute or empty handlers list return None - return logger.handlers[0] + return logger.handlers[0] # type:ignore[no-any-return] + + +CT = t.TypeVar('CT', bound='SingletonConfigurable') class SingletonConfigurable(LoggingConfigurable): @@ -498,7 +517,7 @@ class SingletonConfigurable(LoggingConfigurable): _instance = None @classmethod - def _walk_mro(cls): + def _walk_mro(cls) -> t.Generator[type[SingletonConfigurable], None, None]: """Walk the cls.mro() for parent classes that are also singletons For use in instance() @@ -513,7 +532,7 @@ class SingletonConfigurable(LoggingConfigurable): yield subclass @classmethod - def clear_instance(cls): + def clear_instance(cls) -> None: """unset _instance for this class and singleton parents.""" if not cls.initialized(): return @@ -524,7 +543,7 @@ class SingletonConfigurable(LoggingConfigurable): subclass._instance = None @classmethod - def instance(cls, *args, **kwargs): + def instance(cls: type[CT], *args: t.Any, **kwargs: t.Any) -> CT: """Returns a global instance of this class. This method create a new instance if none have previously been created @@ -568,6 +587,6 @@ class SingletonConfigurable(LoggingConfigurable): ) @classmethod - def initialized(cls): + def initialized(cls) -> bool: """Has an instance been created?""" return hasattr(cls, "_instance") and cls._instance is not None diff --git a/contrib/python/traitlets/py3/traitlets/config/loader.py b/contrib/python/traitlets/py3/traitlets/config/loader.py index 34d62e5a50..437c8c17cd 100644 --- a/contrib/python/traitlets/py3/traitlets/config/loader.py +++ b/contrib/python/traitlets/py3/traitlets/config/loader.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +# ruff: noqa: ANN201, ANN001, ANN204, ANN102, ANN003, ANN206, ANN002 from __future__ import annotations import argparse @@ -50,10 +51,10 @@ class ArgumentError(ConfigLoaderError): class _Sentinel: - def __repr__(self): + def __repr__(self) -> str: return "<Sentinel deprecated>" - def __str__(self): + def __str__(self) -> str: return "<deprecated>" @@ -208,7 +209,7 @@ class LazyConfigValue(HasTraits): d["inserts"] = self._inserts return d - def __repr__(self): + def __repr__(self) -> str: if self._value is not None: return f"<{self.__class__.__name__} value={self._value!r}>" else: @@ -294,7 +295,7 @@ class Config(dict): # type:ignore[type-arg] collisions[section][key] = f"{mine[key]!r} ignored, using {theirs[key]!r}" return collisions - def __contains__(self, key): + def __contains__(self, key: t.Any) -> bool: # allow nested contains of the form `"Section.key" in config` if "." in key: first, remainder = key.split(".", 1) @@ -344,7 +345,7 @@ class Config(dict): # type:ignore[type-arg] else: raise - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: t.Any) -> None: if _is_section_key(key): if not isinstance(value, Config): raise ValueError( @@ -361,7 +362,7 @@ class Config(dict): # type:ignore[type-arg] except KeyError as e: raise AttributeError(e) from e - def __setattr__(self, key, value): + def __setattr__(self, key: str, value: t.Any) -> None: if key.startswith("__"): return dict.__setattr__(self, key, value) try: @@ -369,7 +370,7 @@ class Config(dict): # type:ignore[type-arg] except KeyError as e: raise AttributeError(e) from e - def __delattr__(self, key): + def __delattr__(self, key: str) -> None: if key.startswith("__"): return dict.__delattr__(self, key) try: @@ -420,7 +421,7 @@ class DeferredConfigString(str, DeferredConfig): # this will raise a more informative error when config is loaded. return s - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}({self._super_repr()})" @@ -462,7 +463,7 @@ class DeferredConfigList(list, DeferredConfig): # type:ignore[type-arg] # this will raise a more informative error when config is loaded. return src - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}({self._super_repr()})" @@ -749,7 +750,7 @@ class _DefaultOptionDict(dict): # type:ignore[type-arg] metavar=key.lstrip("-"), ) - def __contains__(self, key): + def __contains__(self, key: t.Any) -> bool: if "=" in key: return False if super().__contains__(key): @@ -785,7 +786,6 @@ class _KVArgParser(argparse.ArgumentParser): # type aliases -Flags = t.Union[str, t.Tuple[str, ...]] SubcommandsDict = t.Dict[str, t.Any] @@ -797,8 +797,8 @@ class ArgParseConfigLoader(CommandLineConfigLoader): def __init__( self, argv: list[str] | None = None, - aliases: dict[Flags, str] | None = None, - flags: dict[Flags, str] | None = None, + aliases: dict[str, str] | None = None, + flags: dict[str, str] | None = None, log: t.Any = None, classes: list[type[t.Any]] | None = None, subcommands: SubcommandsDict | None = None, @@ -915,7 +915,7 @@ class ArgParseConfigLoader(CommandLineConfigLoader): if alias in self.flags: continue if not isinstance(alias, tuple): - alias = (alias,) + alias = (alias,) # type:ignore[assignment] for al in alias: if len(al) == 1: unpacked_aliases["-" + al] = "--" + alias_target diff --git a/contrib/python/traitlets/py3/traitlets/config/manager.py b/contrib/python/traitlets/py3/traitlets/config/manager.py index 728cd2f22c..9102544e50 100644 --- a/contrib/python/traitlets/py3/traitlets/config/manager.py +++ b/contrib/python/traitlets/py3/traitlets/config/manager.py @@ -2,15 +2,18 @@ """ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + import errno import json import os +from typing import Any from traitlets.config import LoggingConfigurable from traitlets.traitlets import Unicode -def recursive_update(target, new): +def recursive_update(target: dict[Any, Any], new: dict[Any, Any]) -> None: """Recursively update one dictionary using another. None values will delete their keys. @@ -39,17 +42,17 @@ class BaseJSONConfigManager(LoggingConfigurable): config_dir = Unicode(".") - def ensure_config_dir_exists(self): + def ensure_config_dir_exists(self) -> None: try: os.makedirs(self.config_dir, 0o755) except OSError as e: if e.errno != errno.EEXIST: raise - def file_name(self, section_name): + def file_name(self, section_name: str) -> str: return os.path.join(self.config_dir, section_name + ".json") - def get(self, section_name): + def get(self, section_name: str) -> Any: """Retrieve the config data for the specified section. Returns the data as a dictionary, or an empty dictionary if the file @@ -62,7 +65,7 @@ class BaseJSONConfigManager(LoggingConfigurable): else: return {} - def set(self, section_name, data): + def set(self, section_name: str, data: Any) -> None: """Store the given config data.""" filename = self.file_name(section_name) self.ensure_config_dir_exists() @@ -71,7 +74,7 @@ class BaseJSONConfigManager(LoggingConfigurable): with f: json.dump(data, f, indent=2) - def update(self, section_name, new_data): + def update(self, section_name: str, new_data: Any) -> Any: """Modify the config section by recursively updating it with new_data. Returns the modified config data as a dictionary. diff --git a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py index a69d89f9e4..300c0a0b03 100644 --- a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py +++ b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py @@ -32,6 +32,7 @@ The generated rST syntax looks like this:: Cross reference like this: :configtrait:`Application.log_datefmt`. """ +# ruff: noqa: ANN201, ANN001, ANN204, ANN102, ANN003, ANN206, ANN002 from collections import defaultdict from textwrap import dedent diff --git a/contrib/python/traitlets/py3/traitlets/log.py b/contrib/python/traitlets/py3/traitlets/log.py index 468c7c3c24..d90a9c5284 100644 --- a/contrib/python/traitlets/py3/traitlets/log.py +++ b/contrib/python/traitlets/py3/traitlets/log.py @@ -5,11 +5,12 @@ from __future__ import annotations import logging +from typing import Any -_logger: logging.Logger | None = None +_logger: logging.Logger | logging.LoggerAdapter[Any] | None = None -def get_logger() -> logging.Logger: +def get_logger() -> logging.Logger | logging.LoggerAdapter[Any]: """Grab the global logger instance. If a global Application is instantiated, grab its logger. diff --git a/contrib/python/traitlets/py3/traitlets/tests/utils.py b/contrib/python/traitlets/py3/traitlets/tests/utils.py index c39d86942a..9552a5c786 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/utils.py +++ b/contrib/python/traitlets/py3/traitlets/tests/utils.py @@ -1,20 +1,23 @@ +from __future__ import annotations + import sys from subprocess import PIPE, Popen -import os +from typing import Any, Sequence -def get_output_error_code(cmd): +def get_output_error_code(cmd: str | Sequence[str]) -> tuple[str, str, Any]: """Get stdout, stderr, and exit code from running a command""" + import os env = os.environ.copy() env["Y_PYTHON_ENTRY_POINT"] = ":main" p = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) # noqa out, err = p.communicate() - out = out.decode("utf8", "replace") # type:ignore - err = err.decode("utf8", "replace") # type:ignore - return out, err, p.returncode + out_str = out.decode("utf8", "replace") + err_str = err.decode("utf8", "replace") + return out_str, err_str, p.returncode -def check_help_output(pkg, subcommand=None): +def check_help_output(pkg: str, subcommand: Sequence[str] | None = None) -> tuple[str, str]: """test that `python -m PKG [subcommand] -h` works""" cmd = [sys.executable, "-m", pkg] if subcommand: @@ -28,7 +31,7 @@ def check_help_output(pkg, subcommand=None): return out, err -def check_help_all_output(pkg, subcommand=None): +def check_help_all_output(pkg: str, subcommand: Sequence[str] | None = None) -> tuple[str, str]: """test that `python -m PKG --help-all` works""" cmd = [sys.executable, "-m", pkg] if subcommand: diff --git a/contrib/python/traitlets/py3/traitlets/traitlets.py b/contrib/python/traitlets/py3/traitlets/traitlets.py index 036f51aac1..50d6face52 100644 --- a/contrib/python/traitlets/py3/traitlets/traitlets.py +++ b/contrib/python/traitlets/py3/traitlets/traitlets.py @@ -38,6 +38,9 @@ Inheritance diagram: # # Adapted from enthought.traits, Copyright (c) Enthought, Inc., # also under the terms of the Modified BSD License. + +# ruff: noqa: ANN001, ANN204, ANN201, ANN003, ANN206, ANN002 + from __future__ import annotations import contextlib @@ -213,16 +216,16 @@ def parse_notifier_name(names: Sentinel | str | t.Iterable[Sentinel | str]) -> t class _SimpleTest: - def __init__(self, value): + def __init__(self, value: t.Any) -> None: self.value = value - def __call__(self, test): - return test == self.value + def __call__(self, test: t.Any) -> bool: + return bool(test == self.value) - def __repr__(self): + def __repr__(self) -> str: return "<SimpleTest(%r)" % self.value - def __str__(self): + def __str__(self) -> str: return self.__repr__() @@ -294,7 +297,7 @@ class link: self.link() - def link(self): + def link(self) -> None: try: setattr( self.target[0], @@ -334,7 +337,7 @@ class link: f"Broken link {self}: the target value changed while updating " "the source." ) - def unlink(self): + def unlink(self) -> None: self.source[0].unobserve(self._update_target, names=self.source[1]) self.target[0].unobserve(self._update_source, names=self.target[1]) @@ -378,7 +381,7 @@ class directional_link: self.source, self.target = source, target self.link() - def link(self): + def link(self) -> None: try: setattr( self.target[0], @@ -402,7 +405,7 @@ class directional_link: with self._busy_updating(): setattr(self.target[0], self.target[1], self._transform(change.new)) - def unlink(self): + def unlink(self) -> None: self.source[0].unobserve(self._update, names=self.source[1]) @@ -1123,7 +1126,7 @@ def observe(*names: Sentinel | str, type: str = "change") -> ObserveHandler: return ObserveHandler(names, type=type) -def observe_compat(func): +def observe_compat(func: FuncT) -> FuncT: """Backward-compatibility shim decorator for observers Use with: @@ -1137,9 +1140,11 @@ def observe_compat(func): Allows adoption of new observer API without breaking subclasses that override and super. """ - def compatible_observer(self, change_or_name, old=Undefined, new=Undefined): + def compatible_observer( + self: t.Any, change_or_name: str, old: t.Any = Undefined, new: t.Any = Undefined + ) -> t.Any: if isinstance(change_or_name, dict): - change = change_or_name + change = Bunch(change_or_name) else: clsname = self.__class__.__name__ warn( @@ -1156,7 +1161,7 @@ def observe_compat(func): ) return func(self, change) - return compatible_observer + return t.cast(FuncT, compatible_observer) def validate(*names: Sentinel | str) -> ValidateHandler: @@ -2027,7 +2032,7 @@ class Type(ClassBasedTraitType[G, S]): @t.overload def __init__( - self: Type[object, object], + self: Type[type, type], default_value: Sentinel | None | str = ..., klass: None | str = ..., allow_none: Literal[False] = ..., @@ -2040,8 +2045,8 @@ class Type(ClassBasedTraitType[G, S]): @t.overload def __init__( - self: Type[object | None, object | None], - default_value: S | Sentinel | None | str = ..., + self: Type[type | None, type | None], + default_value: Sentinel | None | str = ..., klass: None | str = ..., allow_none: Literal[True] = ..., read_only: bool | None = ..., @@ -2054,7 +2059,7 @@ class Type(ClassBasedTraitType[G, S]): @t.overload def __init__( self: Type[S, S], - default_value: S | Sentinel | str = ..., + default_value: S = ..., klass: type[S] = ..., allow_none: Literal[False] = ..., read_only: bool | None = ..., @@ -2067,7 +2072,7 @@ class Type(ClassBasedTraitType[G, S]): @t.overload def __init__( self: Type[S | None, S | None], - default_value: S | Sentinel | None | str = ..., + default_value: S | None = ..., klass: type[S] = ..., allow_none: Literal[True] = ..., read_only: bool | None = ..., diff --git a/contrib/python/traitlets/py3/traitlets/utils/__init__.py b/contrib/python/traitlets/py3/traitlets/utils/__init__.py index dfec4ee322..e8ee7f9856 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/__init__.py +++ b/contrib/python/traitlets/py3/traitlets/utils/__init__.py @@ -1,15 +1,18 @@ +from __future__ import annotations + import os import pathlib +from typing import Sequence # vestigal things from IPython_genutils. -def cast_unicode(s, encoding="utf-8"): +def cast_unicode(s: str | bytes, encoding: str = "utf-8") -> str: if isinstance(s, bytes): return s.decode(encoding, "replace") return s -def filefind(filename, path_dirs=None): +def filefind(filename: str, path_dirs: Sequence[str] | None = None) -> str: """Find a file by looking through a sequence of paths. This iterates through a sequence of paths looking for a file and returns @@ -65,7 +68,7 @@ def filefind(filename, path_dirs=None): raise OSError(f"File {filename!r} does not exist in any of the search paths: {path_dirs!r}") -def expand_path(s): +def expand_path(s: str) -> str: """Expand $VARS and ~names in a string, like a shell :Examples: diff --git a/contrib/python/traitlets/py3/traitlets/utils/bunch.py b/contrib/python/traitlets/py3/traitlets/utils/bunch.py index 6b3fffeb12..498563e0b5 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/bunch.py +++ b/contrib/python/traitlets/py3/traitlets/utils/bunch.py @@ -5,21 +5,24 @@ attribute-access of items on a dict. # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + +from typing import Any class Bunch(dict): # type:ignore[type-arg] """A dict with attribute-access""" - def __getattr__(self, key): + def __getattr__(self, key: str) -> Any: try: return self.__getitem__(key) except KeyError as e: raise AttributeError(key) from e - def __setattr__(self, key, value): + def __setattr__(self, key: str, value: Any) -> None: self.__setitem__(key, value) - def __dir__(self): + def __dir__(self) -> list[str]: # py2-compat: can't use super because dict doesn't have __dir__ names = dir({}) names.extend(self.keys()) diff --git a/contrib/python/traitlets/py3/traitlets/utils/decorators.py b/contrib/python/traitlets/py3/traitlets/utils/decorators.py index a59e8167b0..dedbaad193 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/decorators.py +++ b/contrib/python/traitlets/py3/traitlets/utils/decorators.py @@ -2,12 +2,12 @@ import copy from inspect import Parameter, Signature, signature -from typing import Type, TypeVar +from typing import Any, Type, TypeVar from ..traitlets import HasTraits, Undefined -def _get_default(value): +def _get_default(value: Any) -> Any: """Get default argument value, given the trait default value.""" return Parameter.empty if value == Undefined else value diff --git a/contrib/python/traitlets/py3/traitlets/utils/descriptions.py b/contrib/python/traitlets/py3/traitlets/utils/descriptions.py index 232eb0e728..c068ecdba5 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/descriptions.py +++ b/contrib/python/traitlets/py3/traitlets/utils/descriptions.py @@ -1,9 +1,18 @@ +from __future__ import annotations + import inspect import re import types +from typing import Any -def describe(article, value, name=None, verbose=False, capital=False): +def describe( + article: str | None, + value: Any, + name: str | None = None, + verbose: bool = False, + capital: bool = False, +) -> str: """Return string that describes a value Parameters @@ -110,7 +119,7 @@ def describe(article, value, name=None, verbose=False, capital=False): ) -def _prefix(value): +def _prefix(value: Any) -> str: if isinstance(value, types.MethodType): name = describe(None, value.__self__, verbose=True) + "." else: @@ -122,7 +131,7 @@ def _prefix(value): return name -def class_of(value): +def class_of(value: Any) -> Any: """Returns a string of the value's type with an indefinite article. For example 'an Image' or 'a PlotValue'. @@ -133,7 +142,7 @@ def class_of(value): return class_of(type(value)) -def add_article(name, definite=False, capital=False): +def add_article(name: str, definite: bool = False, capital: bool = False) -> str: """Returns the string with a prepended article. The input does not need to begin with a charater. @@ -164,7 +173,7 @@ def add_article(name, definite=False, capital=False): return result -def repr_type(obj): +def repr_type(obj: Any) -> str: """Return a string representation of a value and its type for readable error messages. diff --git a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py index e2b1f235c8..7cbc82659c 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/getargspec.py +++ b/contrib/python/traitlets/py3/traitlets/utils/getargspec.py @@ -7,14 +7,14 @@ :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ - import inspect from functools import partial +from typing import Any # Unmodified from sphinx below this line -def getargspec(func): +def getargspec(func: Any) -> inspect.FullArgSpec: """Like inspect.getargspec but supports functools.partial as well.""" if inspect.ismethod(func): func = func.__func__ diff --git a/contrib/python/traitlets/py3/traitlets/utils/nested_update.py b/contrib/python/traitlets/py3/traitlets/utils/nested_update.py index 7f09e171a3..37e2d27cd2 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/nested_update.py +++ b/contrib/python/traitlets/py3/traitlets/utils/nested_update.py @@ -1,8 +1,9 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from typing import Any, Dict -def nested_update(this, that): +def nested_update(this: Dict[Any, Any], that: Dict[Any, Any]) -> Dict[Any, Any]: """Merge two nested dictionaries. Effectively a recursive ``dict.update``. diff --git a/contrib/python/traitlets/py3/traitlets/utils/text.py b/contrib/python/traitlets/py3/traitlets/utils/text.py index c7d49edece..72ad98fc2a 100644 --- a/contrib/python/traitlets/py3/traitlets/utils/text.py +++ b/contrib/python/traitlets/py3/traitlets/utils/text.py @@ -9,7 +9,7 @@ from textwrap import indent as _indent from typing import List -def indent(val): +def indent(val: str) -> str: res = _indent(val, " ") return res diff --git a/contrib/python/traitlets/py3/ya.make b/contrib/python/traitlets/py3/ya.make index 2d9cfeae1b..ff8ebca152 100644 --- a/contrib/python/traitlets/py3/ya.make +++ b/contrib/python/traitlets/py3/ya.make @@ -4,7 +4,7 @@ PY3_LIBRARY() PROVIDES(python_traitlets) -VERSION(5.10.1) +VERSION(5.11.2) LICENSE(BSD-3-Clause) @@ -23,7 +23,6 @@ PY_SRCS( traitlets/config/sphinxdoc.py traitlets/log.py traitlets/tests/__init__.py - traitlets/tests/_warnings.py traitlets/tests/utils.py traitlets/traitlets.py traitlets/utils/__init__.py |