`__.
knack-0.6.3/VS2015.sln 0000664 0000000 0000000 00000001656 13511126144 0014173 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.6.3/VS2017.sln 0000664 0000000 0000000 00000001660 13511126144 0014170 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.6.3/docs/ 0000775 0000000 0000000 00000000000 13511126144 0013535 5 ustar 00root root 0000000 0000000 knack-0.6.3/docs/README.md 0000664 0000000 0000000 00000004207 13511126144 0015017 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.6.3/docs/arguments.md 0000664 0000000 0000000 00000010215 13511126144 0016063 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.6.3/docs/cli.md 0000664 0000000 0000000 00000001442 13511126144 0014627 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.6.3/docs/commands.md 0000664 0000000 0000000 00000003366 13511126144 0015670 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.6.3/docs/completion.md 0000664 0000000 0000000 00000004310 13511126144 0016226 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.6.3/docs/config.md 0000664 0000000 0000000 00000001764 13511126144 0015334 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.6.3/docs/events.md 0000664 0000000 0000000 00000001636 13511126144 0015371 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.6.3/docs/help.md 0000664 0000000 0000000 00000007604 13511126144 0015016 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.6.3/docs/logging.md 0000664 0000000 0000000 00000003063 13511126144 0015507 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.
* 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.6.3/docs/output.md 0000664 0000000 0000000 00000001211 13511126144 0015412 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.6.3/docs/prompting.md 0000664 0000000 0000000 00000007530 13511126144 0016103 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.6.3/docs/query.md 0000664 0000000 0000000 00000000203 13511126144 0015217 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.6.3/docs/release-checklist.md 0000664 0000000 0000000 00000002063 13511126144 0017447 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 Travis CI (preferred option)
Once the changes have been merged to master, create a tag on GitHub for that commit.
Follow the format of other releases in the release notes you create on GitHub.
A Travis CI build will be kicked-off that will publish to PyPI.
### Release manually (backup option)
Once the changes have been merged to master, 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.6.3/docs/testing.md 0000664 0000000 0000000 00000011654 13511126144 0015543 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.6.3/docs/validators.md 0000664 0000000 0000000 00000001446 13511126144 0016234 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.6.3/examples/ 0000775 0000000 0000000 00000000000 13511126144 0014423 5 ustar 00root root 0000000 0000000 knack-0.6.3/examples/exapp 0000775 0000000 0000000 00000004614 13511126144 0015473 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.6.3/examples/exapp2 0000664 0000000 0000000 00000005127 13511126144 0015552 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')
g.command('get', 'a_test_command_handler', deprecate_info=g.deprecate(redirect='show', hide='0.1.0'))
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)
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.6.3/examples/test_exapp 0000775 0000000 0000000 00000004171 13511126144 0016530 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.6.3/knack.pyproj 0000664 0000000 0000000 00000011662 13511126144 0015147 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