`__.
knack-0.12.0/SECURITY.md 0000664 0000000 0000000 00000005305 14644175146 0014471 0 ustar 00root root 0000000 0000000
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
knack-0.12.0/VS2015.sln 0000664 0000000 0000000 00000001656 14644175146 0014263 0 ustar 00root root 0000000 0000000
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "knack", "knack.pyproj", "{27802D2F-7F88-44E9-9818-C960569098A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{27802D2F-7F88-44E9-9818-C960569098A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27802D2F-7F88-44E9-9818-C960569098A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87C87E54-C86A-44F2-96F7-D282A01692A9}
EndGlobalSection
EndGlobal
knack-0.12.0/VS2017.sln 0000664 0000000 0000000 00000001660 14644175146 0014260 0 ustar 00root root 0000000 0000000
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2002
MinimumVisualStudioVersion = 10.0.40219.1
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "knack", "knack.pyproj", "{27802D2F-7F88-44E9-9818-C960569098A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{27802D2F-7F88-44E9-9818-C960569098A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27802D2F-7F88-44E9-9818-C960569098A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87C87E54-C86A-44F2-96F7-D282A01692A9}
EndGlobalSection
EndGlobal
knack-0.12.0/azure-pipeline.yml 0000664 0000000 0000000 00000004246 14644175146 0016357 0 ustar 00root root 0000000 0000000 resources:
- repo: self
trigger:
batch: true
branches:
include:
- '*'
pr:
branches:
include:
- '*'
jobs:
- job: AutomationTest
timeoutInMinutes: 20
pool:
name: 'pool-ubuntu-2004'
strategy:
matrix:
Python38:
python.version: '3.8'
tox_env: 'py38'
Python39:
python.version: '3.9'
tox_env: 'py39'
Python310:
python.version: '3.10'
tox_env: 'py310'
Python311:
python.version: '3.11'
tox_env: 'py311'
Python312:
python.version: '3.12'
tox_env: 'py312'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python $(python.version)'
inputs:
versionSpec: '$(python.version)'
- bash: pip install --upgrade pip tox
displayName: 'Install pip and tox'
- bash: |
set -ev
tox
displayName: 'Run Test'
env:
TOXENV: $(tox_env)
- job: BuildPythonWheel
condition: succeeded()
pool:
name: 'pool-ubuntu-2004'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.12
- bash: |
set -ev
: "${BUILD_STAGINGDIRECTORY:?BUILD_STAGINGDIRECTORY environment variable not set}"
: "${BUILD_SOURCESDIRECTORY:=$(cd $(dirname $0); cd ../../; pwd)}"
cd "${BUILD_SOURCESDIRECTORY}"
echo "Build knack"
pip install -U pip setuptools wheel
python setup.py bdist_wheel -d "${BUILD_STAGINGDIRECTORY}"
python setup.py sdist -d "${BUILD_STAGINGDIRECTORY}"
displayName: Build Wheel
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact: pypi'
inputs:
TargetPath: $(Build.ArtifactStagingDirectory)
ArtifactName: pypi
- task: DownloadPipelineArtifact@1
displayName: 'Download PyPI Packages'
inputs:
TargetPath: '$(Build.ArtifactStagingDirectory)/pypi'
artifactName: pypi
- bash: |
set -ev
cd $BUILD_ARTIFACTSTAGINGDIRECTORY/pypi
pwd
ls -la
displayName: Test Build Wheel
knack-0.12.0/docs/ 0000775 0000000 0000000 00000000000 14644175146 0013625 5 ustar 00root root 0000000 0000000 knack-0.12.0/docs/README.md 0000664 0000000 0000000 00000004207 14644175146 0015107 0 ustar 00root root 0000000 0000000 Documentation
=============
CLI Patterns
------------
- Be consistent with POSIX tools.
- CLI success comes from ease and predictability of use so be consistent.
- Support Piping and output direction to chain commands together.
- Work with GREP, AWK, JQ and other common tools and commands.
- Support productivity features like tab completion and parameter value completion.
* Commands should follow a "[noun] [noun] [verb]" pattern.
* *For nouns that only support a single verb, the command should be named as a single hyphenated verb-noun pair.*
* Commands should support all output types (be consistent).
* *Exceptions are okay if only a 'raw' format makes sense e.g. XML.*
* Commands and arguments should have descriptions.
* *Include examples for the less straightforward commands.*
* Commands should return an object or dictionary, not strings/bools/etc.; `logging.info(“Upload of myfile.txt successful”)` **NOT** ~~`return “Upload successful”`~~.
- Log to ERROR or WARNING for user messages; don't use `print()` function (by default it goes to STDOUT).
- STDOUT vs. STDERR: STDOUT is used for actual command output. Everything else to STDERR (e.g. log/status/error messages).
Doc Sections
------------
- [CLI](cli.md) - Provides the entry point.
- [Commands](commands.md) - Provides logic to register and load commands.
- [Arguments](arguments.md) - Provides logic to register and load command arguments.
- [Validators](validators.md) - Provides logic to valid or transform command arguments.
- [Events](events.md) - Provides an extensible events module that you can hook into.
- [Config](config.md) - Provides user-configurable options back by environment variables or config files.
- [Logging](logging.md) - Provides consistent logging.
- [Completion](completion.md) - Provides tab completion support.
- [Prompting](prompting.md) - Provides a consistent user-prompting experience.
- [Help](help.md) - Provides command/argument help.
- [Output](output.md) - Provides output options and displays command output.
- [Query](query.md) - Provides JMESPath query support.
- [Testing](testing.md) - Provides a framework to test your commands.
knack-0.12.0/docs/arguments.md 0000664 0000000 0000000 00000010215 14644175146 0016153 0 ustar 00root root 0000000 0000000 Arguments
=========
Arguments are stored in the `CLICommandsLoader` as an `ArgumentRegistry`.
**Customizing Arguments**
There are a number of customizations that you can make to the arguments of a command that alter their behavior within the CLI. To modify/enhance your command arguments, use `ArgumentsContext`.
- `dest` - This string is the name of the parameter you wish to modify, as specified in the function signature.
- `scope` - This string is the level at which your customizations are applied. For example, consider the case where you have commands `mycli mypackage command1` and `mycli mypackage command2`, which both have a parameter `my_param`.
```Python
with ArgumentsContext(self, 'mypackage') as ac:
ac.argument('my_param', ...) # applies to both command1 and command2
```
But
```Python
with ArgumentsContext(self, 'mypackage command1') as ac:
ac.argument('my_param', ...) # applies to command1 but not command2
```
Like CSS rules, modifications are applied in order from generic to specific.
```Python
with ArgumentsContext(self, 'mypackage') as ac:
ac.argument('my_param', ...) # applies to both command1 and command2
with ArgumentsContext(self, 'mypackage command1') as ac:
ac.argument('my_param', ...) # applies to command1 but not command2 # command2 inherits and build upon the previous changes
```
- `arg_type` - An instance of the `CLIArgumentType` class. This essentially serves as a named, reusable packaging of the `kwargs` that modify your command's argument. It is useful when you want to reuse an argument definition, but is generally not required. It is most commonly used for name type parameters.
- `kwargs` - Most likely, you will simply specify keyword arguments in `ArgumentsContext.argument` that will accomplish what you need. Any `kwargs` specified will override or extend the definition in `arg_type`, if provided.
The following keyword arguments are supported:
- `options_list` - By default, your argument will be exposed as an option in hyphenated form (ex: `my_param` becomes `--my-param`). If you would like to change the option string without changing the parameter name, and/or add a short option, specify the `options_list` kwarg. This is a tuple of two string values, one for a standard option string, and the other for an optional short string. (Ex: `options_list=('--myparam', '-m')`)
- `validator` - The name of a callable that takes the function namespace as a parameter. Allows you to perform any custom logic or validation on the entire namespace prior to command execution. Validators are executed after argument parsing, and thus after `type` and `action` have been applied. However, because the order in which validators are executed is random, you should not have multiple validators modifying the same parameter within the namespace.
- `completer` - The name of a callable that takes the following parameters `(prefix, action, parsed_args, **kwargs)` and returns a list of completion values.
Additionally, the following `kwargs`, supported by argparse, are supported as well:
- `nargs` - See https://docs.python.org/3/library/argparse.html#nargs
- `action` - See https://docs.python.org/3/library/argparse.html#action
- `const` - See https://docs.python.org/3/library/argparse.html#const
- `default` - See https://docs.python.org/3/library/argparse.html#default. Note that the default value is inferred from the parameter's default value in the function signature. If specified, this will override that value.
- `type` - See https://docs.python.org/3/library/argparse.html#type
- `choices` - See https://docs.python.org/3/library/argparse.html#choices. If specified this will also serve as a value completer for people using tab completion.
- `required` - See https://docs.python.org/3/library/argparse.html#required. Note that this value is inferred from the function signature depending on whether or not the parameter has a default value. If specified, this will override that value.
- `help` - See https://docs.python.org/3/library/argparse.html#help. Generally, you should avoid adding help text in this way, instead opting to create a help file as described above.
- `metavar` - See https://docs.python.org/3/library/argparse.html#metavar
knack-0.12.0/docs/cli.md 0000664 0000000 0000000 00000001442 14644175146 0014717 0 ustar 00root root 0000000 0000000 CLI
===
CLI provides the entry point.
The CLI object is used as a context, `cli_ctx`, that is passed around throughout the application. You will see this context, `cli_ctx`, referenced frequently.
We recommend specifying `cli_name`, `config_dir` and `config_env_var_prefix`.
For example:
`cli_name` - Name of CLI. Typically the executable name.
`config_dir` - Path to config dir. e.g. `os.path.expanduser(os.path.join('~', '.myconfig'))`
`config_env_var_prefix` - A prefix for environment variables used in config e.g. `CLI_`.
Use the `invoke()` method to invoke commands.
For example:
```Python
mycli = CLI(commands_loader_cls=MyCommandsLoader)
exit_code = mycli.invoke(sys.argv[1:])
```
How do I?
---------
### Show my own version info ###
Subclass `CLI` and override `get_cli_version()`.
knack-0.12.0/docs/commands.md 0000664 0000000 0000000 00000003366 14644175146 0015760 0 ustar 00root root 0000000 0000000 Commands
========
The commands loader contains a command table.
A command table is a dictionary from command name to a `CLICommand` instance.
**Writing a Command**
Write your command as a simple function, specifying your arguments as the parameter names.
When choosing names, it is recommended that you look at similar commands and follow those naming conventions to take advantage of any aliasing that may already be in place.
If you specify a default value in your function signature, this will flag the argument as optional and will automatically display the default value in the help text for the command. Any parameters that do not have a default value are required and will automatically appear in help with the [Required] label. The required and default behaviors for arguments can be overridden if needed with the `ArgumentsContext` function but this is not generally needed.
There are a few different ways to register commands (see the examples directory for working samples).
Typically, you would use `CommandGroup` to register commands.
For example:
```Python
def hello_command_handler():
return ['hello', 'world']
class MyCommandsLoader(CLICommandsLoader):
def load_command_table(self, args):
with CommandGroup(__name__, self, 'hello', '__main__#{}') as g:
g.command('world', 'hello_command_handler')
return OrderedDict(self.command_table)
mycli = CLI(cli_name='mycli', commands_loader_cls=MyCommandsLoader)
exit_code = mycli.invoke(sys.argv[1:])
```
You can also provide your own command class to the CLICommandsLoader like so:
```Python
class MyCommandsLoader(CLICommandsLoader):
def __init__(self, cli_ctx=None):
super(MyCommandsLoader, self).__init__(cli_ctx=cli_ctx, command_cls=MyCustomCLICommand)
```
knack-0.12.0/docs/completion.md 0000664 0000000 0000000 00000004310 14644175146 0016316 0 ustar 00root root 0000000 0000000 # Completion #
Tab completion is provided by [argcomplete](http://pypi.python.org/pypi/argcomplete).
In your environment, you can enable it with `eval "$(register-python-argcomplete CLI_NAME)"`.
You will then get tab completion for all command names, command arguments and global arguments.
## How to ship tab completion support with your pip package ##
With the PyPI package of your CLI, you can include a shell script.
For example:
`mycli.completion.sh`
```Bash
case $SHELL in
*/zsh)
echo 'Enabling ZSH compatibility mode';
autoload bashcompinit && bashcompinit
;;
*/bash)
;;
*)
esac
eval "$(register-python-argcomplete mycli)"
```
In your `setup.py` file, include this script so it is included in your package.
```Python
setup(
scripts=['mycli.completion.sh', ...],
)
```
Once your CLI has been installed with `pip`, instruct your users to source your completion file.
```Bash
source mycli.completion.sh
```
## How to ship tab completion support with your other installers ##
The method above will not work for other installers as `register-python-argcomplete` is a command that gets enabled when `argcomplete` is installed with pip.
`register-python-argcomplete` is a command that produces a shell script that you can consume directly; you can see this with running `register-python-argcomplete --no-defaults mycli`.
We directly use the output of the above command.
`mycli.completion`
```Bash
_python_argcomplete() {
local IFS=$'\013'
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_SUPPRESS_SPACE=$SUPPRESS_SPACE \
"$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) )
if [[ $? != 0 ]]; then
unset COMPREPLY
elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "$COMPREPLY" =~ [=/:]$ ]]; then
compopt -o nospace
fi
}
complete -o nospace -F _python_argcomplete "mycli"
```
Ship the above file and include it as part of each installer.
knack-0.12.0/docs/config.md 0000664 0000000 0000000 00000001764 14644175146 0015424 0 ustar 00root root 0000000 0000000 Config
======
The config system is used for user-configurable options.
They are backed by environment variables and config files.
Here are the layers of config, with each layer overriding the layer below it:
| Config hierarchy |
|-----------------------|
| Command line argument |
| Environment variable |
| Config file |
Use the `config_dir` and `config_env_var_prefix` options in the constructor of `CLI` to set the config directory and environment variable prefix.
Here's an example:
Let's assume `config_dir=~/.myconfig` and `config_env_var_prefix='CLI'`.
Environment variable format `export PREFIX_SECTION_OPTION=value`.
Config file format:
```
[section]
option = value
```
So to set the output type of commands, a user can set the environment variable `CLI_CORE_OUTPUT` or specify the section and option in the config file.
The environment variable will override the config file.
Lastly, some configurations (like output type) can be specified on a command-by-command basis also.
knack-0.12.0/docs/events.md 0000664 0000000 0000000 00000001636 14644175146 0015461 0 ustar 00root root 0000000 0000000 Events
======
An extensible event framework is built-in.
Things to keep in mind:
- The order of event handler calls is not guaranteed.
- Event handlers cannot return anything. However, they can modify the arguments they receive.
Register for an event
---------------------
To register for an event, get the context, `cli_ctx`, and call `register_event()`.
When an event is raised, the first argument is the context, `cli_ctx` and `kwargs` is any keyword arguments passed in by the raiser of the event (so this is event specific).
```Python
def event_handler(cli_ctx, **kwargs):
print(kwargs)
self.cli_ctx.register_event(EVENT_NAME, event_handler)
```
Raise your own events
---------------------
The framework has some events built-in.
For the full list of events, see [events](../knack/events.py).
You can also add your own events.
```Python
self.cli_ctx.raise_event(EVENT_NAME, arg1=arg1, arg2=arg2, ...)
```
knack-0.12.0/docs/help.md 0000664 0000000 0000000 00000007604 14644175146 0015106 0 ustar 00root root 0000000 0000000 # Help #
Help authoring for commands is done in a number of places. The YAML-based system is the recommended way to update command and group help text.
Command help starts with the docstring text on the handler, if available. Code can specify values that replace the docstring contents. YAML is the final override for help content and is the recommended way for authoring command and group help. Note that group help can only be authored via YAML.
Here are the layers of help, with each layer overriding the layer below it:
| Help Display |
|----------------|
| YAML Authoring |
| Code Specified |
| Docstring |
The YAML syntax is described [here](http://www.yaml.org/spec/1.2/spec.html "here").
Authoring note: it is not recommended to use the product code to author command/group help--YAML is the recommended way (see above). This information is provided for completeness and may be useful for fixing small typos in existing help text.
### Example YAML help ###
This is example YAML help for the command `mycli hello world`.
from knack.help_files import helps
helps['hello world'] = """
type: command
short-summary: Say hello to the world.
long-summary: Longer summary of saying hello.
parameters:
- name: --language -l
type: string
short-summary: 'Language to say hello in'
long-summary: |
Longer summary with newlines preserved. Preserving newlines is helpful for paragraph breaks.
populator-commands:
- mycli hello languages
- These indicate where values can be retrieved for input to this command
- name: --another-parameter
short-summary: These parameter names must match what is shown in the command's CLI help output, including abbreviation.
examples:
- name: Document a parameter that doesn't exist
text: >
You will get an error when you show help for the command stating there is an extra parameter.
- name: Collapse whitespace in YAML
text: >
The > character collapses multiple lines into a single line, which is good for on-screen wrapping.
"""
You can also document groups using the same format.
helps['hello'] = """
type: group
short-summary: Commands to say hello
long-summary: Longer summary of the hello group
examples:
- name: Example name
text: Description
"""
# Tips to write effective help for your command
- Make sure the doc contains all the details that someone unfamiliar with the API needs to use the command.
- Examples are worth a thousand words. Provide examples that cover common use cases.
- Don't use "etc". Sometimes it makes sense to spell out a list completely. Sometimes it works to say "like ..." instead of "..., etc".
- The short summary for a group should start with "Commands to...".
- Use active voice. For example, say "Update web app configurations" instead of "Updates web app configurations" or "Updating web app configurations".
- Don't use highly formal language. If you imagine that another dev sat down with you and you were telling him what he needs to know to use the command, that's exactly what you need to write, in those words.
# Testing Authored Help #
To verify the YAML help is correctly formatted, the command/group's help command must be executed at runtime. For example, to verify `mycli hello world`, run the command `mycli hello world -h` and verify the text.
Runtime is also when help authoring errors will be reported, such as documenting a parameter that doesn't exist. Errors will only show when the CLI help is executed, so verifying the CLI help is required to ensure your authoring is correct.
knack-0.12.0/docs/logging.md 0000664 0000000 0000000 00000003224 14644175146 0015576 0 ustar 00root root 0000000 0000000 Logging
=======
| Log Level | Usage |
|-------------|---------------------------------------------------------------------------------------------|
| Critical | A serious error, program may be unable to continue running. |
| Error | Serious problem, software has not been able to perform some function. |
| Warning | Something you want to draw the attention of the user to. Software still working as expected |
| Info | Confirmation that things are working as expected. |
| Debug | Detailed information, useful for diagnostics. |
- By default, log messages Warning and above are shown to the user.
- `--verbose` - This flag changes the logging level to Info and above.
- `--debug` - This flag changes the logging level to Debug and above.
- `--only-show-errors` - This flag changes the logging level to Error only, suppressing Warning.
* All log messages go to STDERR (not STDOUT)
* Log to Error or Warning for user messages instead of using the `print()` function
* If file logging has been enabled by the user, full Debug logs are saved to rotating log files.
* File logging is enabled if section=logging, option=enable_log_file is set in config (see [config](config.md)).
Get the logger
--------------
```Python
from knack.log import get_logger
logger = get_logger(__name__)
```
See [Python Logger documentation](https://docs.python.org/3/library/logging.html#logging.Logger.debug) for how to format log messages.
knack-0.12.0/docs/output.md 0000664 0000000 0000000 00000001211 14644175146 0015502 0 ustar 00root root 0000000 0000000 Output
======
In general, all commands produce an output object that can be converted to any of the available output types by the CLI core.
In other words, commands are output type independent.
Supported output types:
- JSON (human readable, can handle complex objects, useful for queries.
- JSON colored
- Table (human readable format)
- TSV (great for *nix scripting e.g. with awk, grep, etc.)
Table and TSV format can't display nested objects so a user can use the `--query` argument to select the properties they want to display.
The `table_transformer` is available when registering a command to define how it should look in table output.
knack-0.12.0/docs/prompting.md 0000664 0000000 0000000 00000007530 14644175146 0016173 0 ustar 00root root 0000000 0000000 Prompting
=========
We provide some utilities for prompting during command execution.
Use this sparingly and provide non-interactive ways to specify such arguments.
Each of these utility methods does a TTY check and raises a `NoTTYException`.
Handle this appropriately in each case.
Examples
--------
**A basic message prompt**
```Python
from knack.prompting import prompt
```
Prompt for any user input.
```Python
>>> username = prompt('What is your name? ')
What is your name? Jonathon
>>> username
'Jonathon'
```
If you provide a help string, the user can type '?' to request this help string. All the prompting types support this functionality.
```Python
>>> username = prompt('What is your name? ', help_string='The name you prefer to be known by.')
What is your name? ?
The name you prefer to be known by.
What is your name? Jon
>>> username
'Jon'
```
**Integer based prompt**
```Python
from knack.prompting import prompt_int
```
It's straightforward to get the number entered.
```Python
>>> number = prompt_int('How many do you want to create? ')
How many do you want to create? 10
>>> number
10
```
It has built-in checks that the user has entered an integer.
```Python
>>> number = prompt_int('How many do you want to create? ')
How many do you want to create? hello
hello is not a valid number
How many do you want to create?
is not a valid number
How many do you want to create? ten
ten is not a valid number
How many do you want to create? 10
>>> number
10
```
**Password prompts**
```Python
from knack.prompting import prompt_pass
```
This is a simple password prompt. As you can see, the entered password is not printed to the screen but is saved in the variable.
```Python
>>> userpassword = prompt_pass()
Password:
>>> userpassword
'password123!@#'
```
You can change the prompt message with the `msg` argument.
```Python
>>> secret = prompt_pass(msg='Client secret: ')
Client secret:
>>> secret
'm#@$%453edf'
```
If you're requesting a new password from the user, use the `confirm` argument.
```Python
>>> userpassword = prompt_pass(msg='New resource password: ', confirm=True)
New resource password:
Confirm New resource password:
>>> userpassword
'mysimplepassword'
```
If the passwords don't, the user will get a warning message and be required to enter again.
```Python
>>> userpassword = prompt_pass(msg='New resource password: ', confirm=True)
New resource password:
Confirm New resource password:
Passwords do not match.
New resource password:
```
**Boolean prompts**
```Python
from knack.prompting import prompt_y_n
```
```Python
>>> response = prompt_y_n('Do you agree to this? ')
Do you agree to this? (y/n): y
>>> response
True
```
```Python
>>> response = prompt_y_n('Do you agree to this? ')
Do you agree to this? (y/n): n
>>> response
False
```
A default value can be provided if a user does not specify one.
```Python
>>> prompt_y_n('Do you agree to this? ', default='y')
Do you agree to this? (Y/n):
True
```
We also have a similar prompt for True/False:
```Python
from knack.prompting import prompt_t_f
```
**Choice list prompts**
```Python
from knack.prompting import prompt_choice_list
```
Prompt the user to choose from a choice list.
You will be given the index of the list item the user selected.
```Python
>>> a_list = ['size A', 'size B', 'size C']
>>> choice_index = prompt_choice_list('Default output type? ', a_list)
Default output type?
[1] size A
[2] size B
[3] size C
Please enter a choice [1]: 3
>>> choice_index
2
```
You can also provide a list with descriptions.
```Python
>>> a_list = [{'name':'size A', 'desc':'A smaller size'}, {'name':'size B', 'desc':'An average size'}, {'name':'size C', 'desc':'A bigger size'}]
>>> choice_index = prompt_choice_list('Default output type? ', a_list)
Default output type?
[1] size A - A smaller size
[2] size B - An average size
[3] size C - A bigger size
Please enter a choice [1]: 2
```
knack-0.12.0/docs/query.md 0000664 0000000 0000000 00000000203 14644175146 0015307 0 ustar 00root root 0000000 0000000 Query
=====
Query support is provided through [JMESPath](http://jmespath.org).
This allows filter and project of command output.
knack-0.12.0/docs/release-checklist.md 0000664 0000000 0000000 00000002141 14644175146 0017534 0 ustar 00root root 0000000 0000000 # Release Checklist
## A Maintainer's Guide to Releasing Knack
All releases will be of the form X.Y.Z where X is the major version number, Y is the minor version
number and Z is the patch release number. This project strictly follows
[semantic versioning](http://semver.org/) so following this step is critical.
Modify the version number in `setup.py` and create a pull request for the changes.
### Release with Azure DevOps (preferred option)
Once the changes have been merged to `dev`, create a tag on GitHub for that commit.
Follow the format of other releases in the release notes you create on GitHub.
Visit [Knack Release](https://dev.azure.com/azure-sdk/internal/_release?definitionId=83) to publish to PyPI.
### Release manually (backup option)
Once the changes have been merged to `dev`, continue with the rest of the release.
```
git clone https://github.com/microsoft/knack
cd knack
python setup.py sdist bdist_wheel
```
```
pip install twine
```
```
export TWINE_REPOSITORY_URL=https://upload.pypi.org/legacy/
export TWINE_USERNAME=A_USERNAME
export TWINE_PASSWORD=A_SECRET
twine upload dist/*
```
knack-0.12.0/docs/testing.md 0000664 0000000 0000000 00000011654 14644175146 0015633 0 ustar 00root root 0000000 0000000 Command Testing
===============
## Overview
There are two types of automated tests you can add. They are the [unit tests](https://en.wikipedia.org/wiki/Unit_testing) and the [integration tests](https://en.wikipedia.org/wiki/Integration_testing).
For unit tests, we support unit tests written in the forms of both standard [unittest](https://docs.python.org/3/library/unittest.html) and [nosetest](http://nose.readthedocs.io/en/latest/writing_tests.html).
For integration tests, we provide `ScenarioTest` to support [VCR.py](https://vcrpy.readthedocs.io/en/latest/) based replayable tests.
## About replayable tests
HTTP communication is captured and recorded, the integration tests can be replay in automation without a live testing environment.
We rely on [VCR.py](https://vcrpy.readthedocs.io/en/latest/) to record and replay HTTP communications. On top of the VCR.py, we build `ScenarioTest` to facilitate authoring tests.
## Authoring Scenario Tests
The `ScenarioTest` class is the preferred base class for and VCR based test cases from now on.
### Sample 1. Basic fixture
```Python
from knack.testsdk import ScenarioTest
class TestMyScenarios(ScenarioTest):
def __init__(self, method_name):
super(TestMyScenarios, self).__init__(mycli, method_name)
def test_abc_list(self):
self.cmd('abc list')
```
Note:
1. When the test is run without recording file, the test will be run under live mode. A recording file will be created at `recording/.yaml`
2. Wrap the command in `self.cmd` method. It will assert the exit code of the command to be zero.
3. All the functions and classes your need for writing tests are included in `knack.testsdk` namespace. It is recommended __not__ to reference the sub-namespace to avoid breaking changes.
### Sample 2. Validate the return value in JSON
``` Python
class TestMyScenarios(ScenarioTest):
def __init__(self, method_name):
super(TestMyScenarios, self).__init__(mycli, method_name)
def test_abc_list(self):
result_list = self.cmd('abc list').get_output_in_json()
assert len(result_list) > 0
```
Note:
1. The return value of `self.cmd` is an instance of class `ExecutionResult`. It has the exit code and stdout as its properties.
2. `get_output_in_json` deserialize the output to a JSON object
Tip:
1. Don't make any rigid assertions based on any assumptions which may not stand in a live test environment.
### Sample 3. Validate the return JSON value using JMESPath
``` Python
from knack.testsdk import ScenarioTest, JMESPathCheck
class TestMyScenarios(ScenarioTest):
def __init__(self, method_name):
super(TestMyScenarios, self).__init__(mycli, method_name)
def test_abc_list(self):
self.cmd('abc list', checks=[JMESPathCheck('length(@)', 26)])
```
Note:
1. What is JMESPath? [JMESPath is a query language for JSON](http://jmespath.org/)
2. If a command is return value in JSON, multiple JMESPath based check can be added to the checks list to validate the result.
3. In addition to the `JMESPatchCheck`, there are other checks list `NoneCheck` which validate the output is `None`. The check mechanism is extensible. Any callable accept `ExecutionResult` can act as a check.
## Recording Tests
### Record test for the first time
After the test is executed, a recording file will be generated at `recording/.yaml`. The recording file will be created no matter the test pass or not. The behavior makes it easy for you to find issues when a test fails. If you make changes to the test, delete the recording and rerun the test, a new recording file will be regenerated.
It is a good practice to add a recording file to the local git cache, which makes it easy to diff the different versions of recording to detect issues or changes.
Once the recording file is generated, execute the test again. This time the test will run in playback mode. The execution is offline.
If the replay passes, you can commit the tests as well as recordings.
### Run test live
When the recording file is missing, the test framework will execute the test in live mode. You can force tests to be run live by set following environment variable:
```
export CLI_TEST_RUN_LIVE='True'
```
Also, you can author tests which are for live test only. Just derive the test class from `LiveTest`.
## Test Issues
Here are some common issues that occur when authoring tests that you should be aware of.
- **Non-deterministic results**: If you find that a test will pass on some playbacks but fail on others, there are a couple possible things to check:
1. check if your command makes use of concurrency.
2. check your parameter aliasing (particularly if it complains that a required parameter is missing that you know is there)
- **Paths**: When including paths in your tests as parameter values, always wrap them in double quotes. While this isn't necessary when running from the command line (depending on your shell environment), it will likely cause issues with the test framework.
knack-0.12.0/docs/validators.md 0000664 0000000 0000000 00000001446 14644175146 0016324 0 ustar 00root root 0000000 0000000 Validators
==========
Validators allow you to validate or transform command arguments just before execution.
Knack supports both command-level and argument-level validators.
If a command has a command-level validator, then any argument-level validators that would ordinarily be applied are ignored.
i.e. A command can have at most command validator or many argument level validators.
**Command-level Validators**
Command-level validators can operate on any arguments on the command. This is useful when you need to control the validation sequence.
**Argument-level Validators**
Argument-level validators should only operate on a single argument.
The order argument-level validators are executed in is not guaranteed so don't use multiple argument-level validators that rely on the same arguments.
knack-0.12.0/examples/ 0000775 0000000 0000000 00000000000 14644175146 0014513 5 ustar 00root root 0000000 0000000 knack-0.12.0/examples/exapp 0000775 0000000 0000000 00000004560 14644175146 0015563 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
""" User registers commands with CommandGroups """
import os
import sys
from collections import OrderedDict
from knack import CLI
from knack.commands import CLICommandsLoader, CommandGroup
from knack.arguments import ArgumentsContext
from knack.help import CLIHelp
from knack.help_files import helps
cli_name = os.path.basename(__file__)
helps['abc'] = """
type: group
short-summary: Manage the alphabet of words.
"""
helps['abc list'] = """
type: command
short-summary: List the alphabet.
examples:
- name: It's pretty straightforward.
text: {cli_name} abc list
""".format(cli_name=cli_name)
def a_test_command_handler():
return [{'a': 1, 'b': 1234}, {'a': 3, 'b': 4}]
def abc_list_command_handler():
import string
return list(string.ascii_lowercase)
def hello_command_handler(myarg=None, abc=None):
return ['hello', 'world', myarg, abc]
WELCOME_MESSAGE = r"""
_____ _ _____
/ ____| | |_ _|
| | | | | |
| | | | | |
| |____| |____ _| |_
\_____|______|_____|
Welcome to the cool new CLI!
"""
class MyCLIHelp(CLIHelp):
def __init__(self, cli_ctx=None):
super(MyCLIHelp, self).__init__(cli_ctx=cli_ctx,
privacy_statement='My privacy statement.',
welcome_message=WELCOME_MESSAGE)
class MyCommandsLoader(CLICommandsLoader):
def load_command_table(self, args):
with CommandGroup(self, 'hello', '__main__#{}') as g:
g.command('world', 'hello_command_handler', confirmation=True)
with CommandGroup(self, 'abc', '__main__#{}') as g:
g.command('list', 'abc_list_command_handler')
g.command('show', 'a_test_command_handler')
return super(MyCommandsLoader, self).load_command_table(args)
def load_arguments(self, command):
with ArgumentsContext(self, 'hello world') as ac:
ac.argument('myarg', type=int, default=100)
super(MyCommandsLoader, self).load_arguments(command)
mycli = CLI(cli_name=cli_name,
config_dir=os.path.expanduser(os.path.join('~', '.{}'.format(cli_name))),
config_env_var_prefix=cli_name,
commands_loader_cls=MyCommandsLoader,
help_cls=MyCLIHelp)
exit_code = mycli.invoke(sys.argv[1:])
sys.exit(exit_code)
knack-0.12.0/examples/exapp2 0000664 0000000 0000000 00000016152 14644175146 0015642 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
""" User registers commands with CommandGroups """
import os
import sys
from collections import OrderedDict
from knack import CLI
from knack.commands import CLICommandsLoader, CommandGroup
from knack.arguments import ArgumentsContext
from knack.help import CLIHelp
from knack.help_files import helps
cli_name = os.path.basename(__file__)
helps['abc'] = """
type: group
short-summary: Manage the alphabet of words.
"""
helps['abc list'] = """
type: command
short-summary: List the alphabet.
examples:
- name: It's pretty straightforward.
text: {cli_name} abc list
""".format(cli_name=cli_name)
helps['abc first'] = """
type: command
short-summary: List the first several letters in the alphabet.
examples:
- name: Show the list of abc
text: {cli_name} abc first --number 3
""".format(cli_name=cli_name)
helps['abc last'] = """
type: command
short-summary: List the last several letters in the alphabet.
examples:
- name: Show the list of xyz
text: {cli_name} abc last --number 3
""".format(cli_name=cli_name)
helps['ga'] = """
type: group
short-summary: A general available command group
"""
helps['pre'] = """
type: group
short-summary: A preview command group
"""
helps['exp'] = """
type: group
short-summary: An experimental command group
"""
helps['demo'] = """
type: group
short-summary: A command group for demos.
"""
helps['demo arg'] = """
type: group
short-summary: A command showing how to use arguments.
"""
def abc_show_command_handler():
"""
Show a JSON mapping of letters to their ASCII values
"""
import string
lower = {}
for ch in string.ascii_lowercase:
lower[ch] = ord(ch)
upper = {}
for ch in string.ascii_uppercase:
upper[ch] = ord(ch)
return {"lowercase": lower, "uppercase": upper}
def abc_list_command_handler():
import string
return list(string.ascii_lowercase)
def abc_first_command_handler(number=5):
import string
return list(string.ascii_lowercase)[0:number]
def abc_last_command_handler(number=5):
import string
return list(string.ascii_lowercase)[-number:]
def range_command_handler(start=0, end=5):
"""
Get a list of natural numbers from start to end
:param start: the lower bound
:param end: the higher bound
:return:
"""
return list(range(int(start), int(end) + 1))
def sample_json_handler():
"""
Get a sample JSON string
"""
# https://docs.microsoft.com/en-us/rest/api/resources/subscriptions/list#examples
result = {
"id": "/subscriptions/291bba3f-e0a5-47bc-a099-3bdcb2a50a05",
"subscriptionId": "291bba3f-e0a5-47bc-a099-3bdcb2a50a05",
"tenantId": "31c75423-32d6-4322-88b7-c478bdde4858",
"displayName": "Example Subscription",
"state": "Enabled",
"subscriptionPolicies": {
"locationPlacementId": "Internal_2014-09-01",
"quotaId": "Internal_2014-09-01",
"spendingLimit": "Off"
},
"authorizationSource": "RoleBased",
"managedByTenants": [
{
"tenantId": "8f70baf1-1f6e-46a2-a1ff-238dac1ebfb7"
}
]
}
return result
def sample_logger_handler():
"""
Print logs to stderr.
"""
print("""This is a demo for logging. The logging level can be controlled with:
--only-show-errors: Show ERROR logs and above
: Show WARNING logs and above
--verbose: Show INFO logs and above
--debug: Show DEBUG logs and above""")
from knack.log import get_logger
logger = get_logger(__name__)
logger.debug("This is a debug log entry.")
logger.info("This is a info log entry.")
logger.warning("This is a warning log entry.")
logger.error("This is a error log entry.")
logger.critical("This is a critical log entry.")
def hello_command_handler(greetings=None):
"""
Say "Hello World!" and my warm greetings
:param greetings: My warm greetings
"""
return ['Hello World!', greetings]
def demo_arg_handler(move=None):
if move:
print("Your move was: {}".format(move))
return
print("Nothing to do.")
WELCOME_MESSAGE = r"""
_____ _ _____
/ ____| | |_ _|
| | | | | |
| | | | | |
| |____| |____ _| |_
\_____|______|_____|
Welcome to the cool new CLI!
"""
class MyCLIHelp(CLIHelp):
def __init__(self, cli_ctx=None):
super(MyCLIHelp, self).__init__(cli_ctx=cli_ctx,
privacy_statement='My privacy statement.',
welcome_message=WELCOME_MESSAGE)
class MyCommandsLoader(CLICommandsLoader):
def load_command_table(self, args):
with CommandGroup(self, '', '__main__#{}') as g:
g.command('hello', 'hello_command_handler', confirmation=True)
g.command('sample-json', 'sample_json_handler')
g.command('sample-logger', 'sample_logger_handler')
with CommandGroup(self, 'abc', '__main__#{}') as g:
g.command('list', 'abc_list_command_handler')
g.command('show', 'abc_show_command_handler')
g.command('get', 'abc_show_command_handler', deprecate_info=g.deprecate(redirect='show', hide='1.0.0'))
g.command('first', 'abc_first_command_handler', is_preview=True)
g.command('last', 'abc_last_command_handler', is_experimental=True)
# A GA command group
with CommandGroup(self, 'ga', '__main__#{}') as g:
g.command('range', 'range_command_handler')
# A preview command group
with CommandGroup(self, 'pre', '__main__#{}', is_preview=True) as g:
g.command('first', 'abc_first_command_handler', is_preview=True)
g.command('range', 'range_command_handler')
# An experimental command group
with CommandGroup(self, 'exp', '__main__#{}', is_experimental=True) as g:
g.command('range', 'range_command_handler')
# A deprecated command group
with CommandGroup(self, 'dep', '__main__#{}', deprecate_info=g.deprecate(redirect='ga', hide='1.0.0')) as g:
g.command('range', 'range_command_handler')
with CommandGroup(self, 'demo', '__main__#{}') as g:
g.command('arg', 'demo_arg_handler')
return super(MyCommandsLoader, self).load_command_table(args)
def load_arguments(self, command):
with ArgumentsContext(self, 'ga range') as ac:
ac.argument('start', type=int, is_preview=True)
ac.argument('end', type=int, is_experimental=True)
with ArgumentsContext(self, 'demo arg') as ac:
ac.argument('move', choices=['rock', 'paper', 'scissors'])
super(MyCommandsLoader, self).load_arguments(command)
class MyCLI(CLI):
def get_cli_version(self):
return '0.1.0'
mycli = MyCLI(cli_name=cli_name,
config_dir=os.path.expanduser(os.path.join('~', '.{}'.format(cli_name))),
config_env_var_prefix=cli_name,
commands_loader_cls=MyCommandsLoader,
help_cls=MyCLIHelp)
exit_code = mycli.invoke(sys.argv[1:])
sys.exit(exit_code)
knack-0.12.0/examples/test_exapp 0000775 0000000 0000000 00000004171 14644175146 0016620 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
""" Create ScenarioTests from a CLI you've created. """
import os
import sys
import unittest
from collections import OrderedDict
from knack import CLI
from knack.commands import CLICommandsLoader, CommandGroup
from knack.arguments import ArgumentsContext
# DEFINE MY CLI
def a_test_command_handler():
return [{'a': 1, 'b': 1234}, {'a': 3, 'b': 4}]
def abc_list_command_handler():
import string
return list(string.ascii_lowercase)
def hello_command_handler(myarg=None, abc=None):
return ['hello', 'world', myarg, abc]
class MyCommandsLoader(CLICommandsLoader):
def load_command_table(self, args):
with CommandGroup(self, 'hello', '__main__#{}') as g:
g.command('world', 'hello_command_handler', confirmation=True)
with CommandGroup(self, 'abc', '__main__#{}') as g:
g.command('list', 'abc_list_command_handler')
g.command('show', 'a_test_command_handler')
return super(MyCommandsLoader, self).load_command_table(args)
def load_arguments(self, command):
with ArgumentsContext(self, 'hello world') as ac:
ac.argument('myarg', type=int, default=100)
super(MyCommandsLoader, self).load_arguments(command)
name = 'exapp4'
mycli = CLI(cli_name=name,
config_dir=os.path.expanduser(os.path.join('~', '.{}'.format(name))),
config_env_var_prefix=name,
commands_loader_cls=MyCommandsLoader)
# END OF - DEFINE MY CLI
# DEFINE MY TESTS
from knack.testsdk import ScenarioTest, JMESPathCheck
class TestMyScenarios(ScenarioTest):
def __init__(self, method_name):
super(TestMyScenarios, self).__init__(mycli, method_name)
def test_hello_world_yes(self):
self.cmd('hello world --yes', checks=[
JMESPathCheck('length(@)', 4)
])
def test_abc_list(self):
self.cmd('abc list', checks=[
JMESPathCheck('length(@)', 26)
])
def test_abc_show(self):
self.cmd('abc show', checks=[
JMESPathCheck('length(@)', 2),
])
# END OF - DEFINE MY TESTS
if __name__ == '__main__':
unittest.main()
knack-0.12.0/knack.pyproj 0000664 0000000 0000000 00000011662 14644175146 0015237 0 ustar 00root root 0000000 0000000
Debug
2.0
{27802d2f-7f88-44e9-9818-c960569098a6}
knack\__init__.py
.
.
{888888a0-9f3d-457c-b088-3a5042f75d52}
Standard Python launcher
MSBuild|env2|$(MSBuildProjectFullPath)
10.0
Code
env2
2.7
env2 (Python 2.7 (32-bit))
Scripts\python.exe
Scripts\pythonw.exe
PYTHONPATH
X86
{52196499-2eb9-4ba7-924a-ca67b294886b}
2.7
env2 (Python 2.7)
Scripts\python.exe
Scripts\pythonw.exe
Lib\
PYTHONPATH
X86
env3
3.6
env3 (Python 3.6 (64-bit))
Scripts\python.exe
Scripts\pythonw.exe
PYTHONPATH
X64