Using a numeric identifier for value selection in click.Choice - python-click

The Click package allows a range of values to be selected from a list using the click.Choice method.
In my case the values are relatively long strings, so using:
choice_names = [u'Vulnerable BMC (IPMI)', u'IoT Vulnerability', u'SMBv1', u'BadHTTPStatus', u'Compromised']
#click.option('--category', prompt='\nPlease enter the category of incident.\n\n -- Options:\n{}\n\n'.format(
format_choices(choice_names)), type=click.Choice(choice_names))
will list the values as:
-> Vulnerable BMC (IPMI)
-> IoT Vulnerability
-> SMBv1
-> BadHTTPStatus
-> Compromised
This requires the user to enter the full string, which is inconvenient. Does Click provide a functionality to select a value using only a numeric identifier? So, the above options could be listed as:
-> Vulnerable BMC (IPMI) [1]
-> IoT Vulnerability [2]
-> SMBv1 [3]
-> BadHTTPStatus [4]
-> Compromised [5]
and to select the first option, the user would need to enter 1. This could be possible by defining a custom validation function, but I couldn't find any existing functionality offered by Click.

I came up with this:
class ChoiceOption(click.Option):
def __init__(self, param_decls=None, **attrs):
click.Option.__init__(self, param_decls, **attrs)
if not isinstance(self.type, click.Choice):
raise Exception('ChoiceOption type arg must be click.Choice')
if self.prompt:
prompt_text = '{}:\n{}\n'.format(
self.prompt,
'\n'.join(f'{idx: >4}: {c}' for idx, c in enumerate(self.type.choices, start=1))
)
self.prompt = prompt_text
def process_prompt_value(self, ctx, value, prompt_type):
if value is not None:
index = prompt_type(value, self, ctx)
return self.type.choices[index - 1]
def prompt_for_value(self, ctx):
# Calculate the default before prompting anything to be stable.
default = self.get_default(ctx)
prompt_type = click.IntRange(min=1, max=len(self.type.choices))
return click.prompt(
self.prompt, default=default, type=prompt_type,
hide_input=self.hide_input, show_choices=False,
confirmation_prompt=self.confirmation_prompt,
value_proc=lambda x: self.process_prompt_value(ctx, x, prompt_type))
#click.command()
#click.option('--hash-type', prompt='Hash', type=click.Choice(['MD5', 'SHA1'], case_sensitive=False), cls=ChoiceOption)
def cli(**kwargs):
print(kwargs)
Result:
> cli --help
Usage: cli [OPTIONS]
Options:
--hash-type [MD5|SHA1]
--help Show this message and exit.
> cli --hash-type MD5
{'hash_type': 'MD5'}
> cli
Hash:
1: MD5
2: SHA1
: 4
Error: 4 is not in the valid range of 1 to 2.
Hash:
1: MD5
2: SHA1
: 2
{'hash_type': 'SHA1'}
Edit May 25, 2020:
I recently came across questionary and integrated it with click
import click
import questionary
class QuestionaryOption(click.Option):
def __init__(self, param_decls=None, **attrs):
click.Option.__init__(self, param_decls, **attrs)
if not isinstance(self.type, click.Choice):
raise Exception('ChoiceOption type arg must be click.Choice')
def prompt_for_value(self, ctx):
val = questionary.select(self.prompt, choices=self.type.choices).unsafe_ask()
return val
#click.command()
#click.option('--hash-type', prompt='Hash', type=click.Choice(['MD5', 'SHA1'], case_sensitive=False), cls=QuestionaryOption)
def cli(**kwargs):
print(kwargs)
if __name__ == "__main__":
cli()

Since Click does not seem to provide a functionality of this kind, this custom validation function fulfills the purpose:
def validate_choice(ctx, param, value):
# Check if the passed value is an integer.
try:
index = int(value) - 1
# Return the value at the given index.
try:
return choice_names[index]
# If the index does not exist.
except IndexError:
click.echo('Please select a valid index.')
# If the value is of a different type, for example, String.
except (TypeError, ValueError):
# Return the value if it exists in the list of choices.
if value in choice_names:
return value
else:
click.echo('Please select a valid value from the choices {}.'.format(choice_names))
# Prompt the user for an input.
value = click.prompt(param.prompt)
return validate_choice(ctx, param, value)
#click.option('--category', prompt='\nPlease enter the category.\n\n -- Options:\n{}\n\n'.format(choice_names),
help='Category of the incident', callback=validate_category)
This allows a user to select a choice either by entering the choice name or by entering the index value. In case an invalid value is entered, the user is prompted again for an input.

Related

How to do a case insensitive match for command line arguments in scala?

I'm working on a command line tool written in Scala which is executed as:
sbt "run --customerAccount 1234567"
Now, I wish to make this flexible to accept "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT ...you get the drift
Here's what the code looks like:
lazy val OptionsParser: OptionParser[Args] = new scopt.OptionParser[Args]("scopt") {
head(
"XML Generator",
"Creates XML for testing"
)
help("help").text(s"Prints this usage message. $envUsage")
opt[String]('c', "customerAccount")
.text("Required: Please provide customer account number as -c 12334 or --customerAccount 12334")
.required()
.action { (cust, args) =>
assert(cust.nonEmpty, "cust is REQUIRED!!")
args.copy(cust = cust)
}
}
I assume the opt[String]('c', "customerAccount") does the pattern matching from the command line and will match with "customerAccount" - how do I get this to match with "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT? What exactly does the args.copy (cust = cust) do?
I apologize if the questions seem too basic. I'm incredibly new to Scala, have worked in Java and Python earlier so sometimes I find the syntax a little hard to understand as well.
You'd normally be parsing the args with code like:
OptionsParser.parse(args, Args())
So if you want case-insensitivity, probably the easiest way is to canonicalize the case of args with something like
val canonicalized = args.map(_.toLowerCase)
OptionsParser.parse(canonicalized, Args())
Or, if you for instance wanted to only canonicalize args starting with -- and before a bare --:
val canonicalized =
args.foldLeft(false -> List.empty[String]) { (state, arg) =>
val (afterDashes, result) = state
if (afterDashes) true -> (arg :: result) // pass through unchanged
else {
if (arg == "==") true -> (arg :: result) // move to afterDash state & pass through
else {
if (arg.startsWith("--")) false -> (arg.toLowerCase :: result)
else false -> (arg :: result) // pass through unchanged
}
}
}
._2 // Extract the result
.reverse // Reverse it back into the original order (if building up a sequence, your first choice should be to build a list in reversed order and reverse at the end)
OptionsParser.parse(canonicalized, Args())
Re the second question, since Args is (almost certainly) a case class, it has a copy method which constructs a new object with (most likely, depending on usage) different values for its fields. So
args.copy(cust = cust)
creates a new Args object, where:
the value of the cust field in that object is the value of the cust variable in that block (this is basically a somewhat clever hack that works with named method arguments)
every other field's value is taken from args

Store generated dynamic unique ID and parse to next test case

I have keyword groovy which allowed me to generate a dynamic unique ID for test data purpose.
package kw
import java.text.SimpleDateFormat
import com.kms.katalon.core.annotation.Keyword
class dynamicId {
//TIME STAMP
String timeStamp() {
return new SimpleDateFormat('ddMMyyyyhhmmss').format(new Date())
}
//Generate Random Number
Integer getRandomNumber(int min, int max) {
return ((Math.floor(Math.random() * ((max - min) + 1))) as int) + min
}
/**
* Generate a unique key and return value to service
*/
#Keyword
String getUniqueId() {
String prodName = (Integer.toString(getRandomNumber(1, 99))) + timeStamp()
return prodName
}
}
Then I have a couple of API test cases as below:
test case 1:
POST test data by calling the keyword. this test case works well.
the dynamic unique ID is being posted and stored in the Database.
partial test case
//test data using dynamic Id
NewId = CustomKeywords.'kw.dynamicId.getUniqueId'()
println('....DO' + NewId)
GlobalVariable.DynamicId = NewId
//test data to simulate Ingest Service sends Dispense Order to Dispense Order Service.
def incomingDOInfo = '{"Operation":"Add","Msg":{"id":"'+GlobalVariable.DynamicId+'"}'
now, test case 2 served as a verification test case.
where I need to verify the dynamic unique ID can be retrieved by GET API (GET back data by ID, this ID should matched the one being POSTED).
how do I store the generated dynamic unique ID once generated from test case 1?
i have the "println('....DO' + NewId)" in Test Case 1, but i have no idea how to use it and put it in test case 2.
which method should I use to get back the generated dynamic unique ID?
updated Test Case 2 with the suggestion, it works well.
def dispenseOrderId = GlobalVariable.DynamicId
'Check data'
getDispenseOrder(dispenseOrderId)
def getDispenseOrder(def dispenseOrderId){
def response = WS.sendRequestAndVerify(findTestObject('Object Repository/Web Service Request/ApiDispenseorderByDispenseOrderIdGet', [('dispenseOrderId') : dispenseOrderId, ('SiteHostName') : GlobalVariable.SiteHostName, , ('SitePort') : GlobalVariable.SitePort]))
println(response.statusCode)
println(response.responseText)
WS.verifyResponseStatusCode(response, 200)
println(response.responseText)
//convert to json format and verify result
def dojson = new JsonSlurper().parseText(new String(response.responseText))
println('response text: \n' + JsonOutput.prettyPrint(JsonOutput.toJson(dojson)))
assertThat(dojson.dispenseOrderId).isEqualTo(dispenseOrderId)
assertThat(dojson.state).isEqualTo("NEW")
}
====================
updated post to try #2 suggestion, works
TC2
//retrieve the dynamic ID generated at previous test case
def file = new File("C:/DynamicId.txt")
//Modify this to match test data at test case "IncomingDOFromIngest"
def dispenseOrderId = file.text
'Check posted DO data from DO service'
getDispenseOrder(dispenseOrderId)
def getDispenseOrder(def dispenseOrderId){
def response = WS.sendRequestAndVerify(findTestObject('Object Repository/Web Service Request/ApiDispenseorderByDispenseOrderIdGet', [('dispenseOrderId') : dispenseOrderId, ('SiteHostName') : GlobalVariable.SiteHostName, , ('SitePort') : GlobalVariable.SitePort]))
println(response.statusCode)
println(response.responseText)
WS.verifyResponseStatusCode(response, 200)
println(response.responseText)
}
There are multiple ways of doing that that I can think of.
1. Store the value od dynamic ID in a GlobalVariable
If you are running Test Case 1 (TC1) and TC2 in a test suite, you can use the global variable for inter-storage.
You are already doing this in the TC1:
GlobalVariable.DynamicId = NewId
Now, this will only work if TC1 and TC2 are running as a part of the same test suite. That is because GlobalVariables are reset to default on the teardown of the test suite or the teardown of a test case when a single test case is run.
Let us say you retrieved the GET response and put it in a response variable.
assert response.equals(GlobalVariable.DynamicId)
2. Store the value od dynamic ID on the filesystem
This method will work even if you run the test cases separately (i.e. not in a test suite).
You can use file system for permanently storing the ID value to a file. There are various Groovy mehods to help you with that.
Here's an example on how to store the ID to a text file c:/path-to/variable.txt:
def file = new File("c:/path-to/variable.txt")
file.newWriter().withWriter { it << NewID }
println file.text
The TC2 needs this assertion (adjust according to your needs):
def file = new File("c:/path-to/variable.txt")
assert response.equals(file.text)
Make sure you defined file in TC2, as well.
3. Return the ID value at the end of TC1 and use it as an input to TC2
This also presupposes TC1 and TC2 are in the same test suite. You return the value of the ID with
return NewId
and then use as an input parameter for TC2.
4. Use test listeners
This is the same thing as the first solution, you just use test listeners to create a temporary holding variable that will be active during the test suite run.

How to return multiple variables from one test case to another using Katalon

TC1: 01_UserManagement/Login
String u = WebUI.getAttribute(findTestObject('SignInPage/txt_username'), 'placeholder')
WebUI.setText(findTestObject('SignInPage/txt_username'), username)
String p = WebUI.getAttribute(findTestObject('SignInPage/txt_password'), 'placeholder')
CustomKeywords.'com.fcm.utilities.ClearTextField.ClearText'(findTestObject('SignInPage/txt_password'))
WebUI.setText(findTestObject('SignInPage/txt_password'), password)
WebUI.click(findTestObject('SignInPage/btn_signinButton'))
Map map = [:]
map.put('inlinetextofusername',u)
map.put('inlinetextofpassword',p)
map.each{ k, v -> println "${k}:${v}" }
return map;
TestCase2:
Map TC_1_called = WebUI.callTestCase(findTestCase('01_UserManagement/Login'), [('username') : 'Anna', ('password') : 'Analyst_2017',('inlinetextofusername'):'',('inlinetextofpassword'):''],
FailureHandling.STOP_ON_FAILURE)
println(TC_1_called[inlinetextofusername])
println(TC_1_called[inlinetextofpassword]
I am getting following error:-
12-11-2017 04:31:40 PM - [ERROR] - Test Cases/01_UserManagement/Logout FAILED because (of) Variable 'inlinetextofusername' is not defined for test case.
How to take the values which is stored in Map in Test case 1 and to use in Test Case 2.
Map TC_1_called = WebUI.callTestCase(findTestCase('01_UserManagement/Login'), [('username') : 'Anna', ('password') : 'Analyst_2017',('map'):''],
FailureHandling.STOP_ON_FAILURE)
This returns values..
If you want to pass values from one test case to another in Katalon Studio, you should pass these values from Test Case A to Test Case B
Imagine you have these test cases Log in test and Dashboard test and you need to pass username and email from Log in testto Dashboard test
Go to script mode in Log in test and at the end of script add this line of code
WebUI.callTestCase( findTestCase('Test Cases/dashboard test') , [('username'): username, ('email'):email] )
This will pass local variables username and email from Log in test script to dashboard test
Notice: before applying this method, you should create test case variables to the second test case dashboard test from variables tab beside script tab in the lower section of test case editor.
Hope this comes in handy
I have issue on passing variable that has a random value to another test case.
Solution: I created a Global Variable with empty string then I set that Global Variable on my testCase_1 with a random value. I can now call that Global Variable to my testCase_2 without calling testCase_1. But you must execute testCase_1 first on your test suite.
testCase_1:
GlobalVariable.randomString = yourRandomValue
testCase_2:
String variableFromTestCase_1 = GlobalVariable.randomString
Screenshot

Python pass variable from class in one file to another file

i have following code in two files:
operation1.py
class App_Name():
def __init__(self):
self.type_option = ""
def Intro(self):
self.type_option = input("Chose one option: ")
...
start = App_Name()
start.Intro()
menu.py
from operation1 import App_name
aP = App_Name()
if aP.type_option == 1:
do smth
elif aP.type.type_option == 2:
do smth 2
If i type 1, i expect to run commands from first if condition. When i try to print App_name.type_option it seems to be empty. How can i pass value of aP.type_option to menu.py?
start and aP are 2 different instances. Since type_option is bound to one instance, start.type_option contains the input (as string), whereas aP.type_option contains the empty string you set in the __init__ method.
Remove start instanciation in your operation1 module or you'll be prompted when importing it!
Then fix menu.py as follows:
from operation1 import App_name
aP = App_Name()
aP.Intro()
if aP.type_option == "1":
do smth
elif aP.type.type_option == "2":
do smth 2
(note that comparison must be done against strings because Python 3 input returns strings, doesn't evaluate literals like python 2 input did)

Coffeescript isn't empty object [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
is object empty?
update: (id, data) ->
toUpdate = #find(id)
if toUpdate isnt {}
console.log "hi mom"
console.log toUpdate
toUpdate.setProperty(key, value) for own key, value of data
return toUpdate
find:(id) ->
result = record for record in #storage when record.id is id
return result or {}
Given the following Mocha tests
describe '#update', ->
it 'should return an updated record from a given id and data when the record exists', ->
boogie = createData()
archive = new Archive("Dog")
dog = archive.create(boogie)
result = archive.update(1, {name:"Chompie", age:1})
result.name.should.eql "Chompie"
result.age.should.eql 1
result.emotion.should.eql dog.emotion
it 'should return an updated record from a given id and data when the record does not exist', ->
boogie = createData()
archive = new Archive("Dog")
dog = archive.create(boogie)
result = archive.update(50, {name:"Chompie", age:1})
result.should.not.exist
The result is
Archive #update should return an updated record from a given id and data when the record exists: hi mom
{ id: 1,
validationStrategies: {},
name: 'Boogie',
age: 2,
emotion: 'happy' }
✓ Archive #update should return an updated record from a given id and data when the record exists: 1ms
Archive #update should return empty when the record does not exist: hi mom
{}
✖ 1 of 13 tests failed:
1) Archive #update should return empty when the record does not exist:
TypeError: Object #<Object> has no method 'setProperty'
...surprising, isnt it?
CoffeeScript's is (AKA ==) is just JavaScript's === and isnt (AKA !=) is just JavaScript's !==. So your condition:
if toUpdate isnt {}
will always be true since toUpdate and the object literal {} will never be the same object.
However, if #find could return a known "empty" object that was available in a constant, then you could use isnt:
EMPTY = {}
find: ->
# ...
EMPTY
and later:
if toUpdate isnt EMPTY
#...
For example, consider this simple code:
a = { }
b = { }
console.log("a is b: #{a is b}")
console.log("a isnt b: #{a isnt b}")
That will give you this in your console:
a is b: false
a isnt b: true
But this:
class C
EMPTY = { }
find: -> EMPTY
check: -> console.log("#find() == EMPTY: #{#find() == EMPTY}")
(new C).check()
will say:
#find() == EMPTY: true
Demo: http://jsfiddle.net/ambiguous/7JGdq/
So you need another way to check if toUpdate isn't empty. You could count the properties in toUpdate:
if (k for own k of toUpdate).length isnt 0
or you could use the special EMTPY constant approach outlined above. There are various other ways to check for an empty object, Ricardo Tomasi​ has suggested a few:
Underscore offers _.isEmpty which is basically the for loop approach with some special case handling and a short circuit.
Underscore also offers _.values so you could look at _(toUpdate).values().length. This calls map internally and that will be the native map function if available.
You could even go through JSON using JSON.stringify(toUpdate) is '{}', this seems a bit fragile to me and rather round about.
You could use Object.keys instead of the for loop: Object.keys(toUpdate).length isnt 0. keys isn't supported everywhere though but it will work with Node, up-to-date non-IE browsers, and IE9+.
Sugar also has Object.isEmpty and jQuery has $.isEmptyObject.
A short-circuiting for loop appears to be the quickest way to check emptiness:
(obj) ->
for k of toUpdate
return true
false
That assumes that you don't need own to avoid iterating over the wrong things. But given that this is just a test suite and that an emptiness test almost certainly won't be a bottle neck in your code, I'd go with whichever of Underscore, Sugar, or jQuery you have (if you need portability and have to deal with the usual browser nonsense), Object.keys(x).length if you know it will be available, and (k for own k of toUpdate).length if you don't have the libraries and have to deal with browser nonsense and aren't certain that toUpdate will be a simple object.