"Pythonic" equivalent for handling switch and multiple string compares - conditional-statements

Alright, so my title sucked. An example works better:
input = 'check yahoo.com'
I want to parse input, using the first word as the "command", and the rest of the string as a parameter. Here's the simple version of how my non-Pythonic mind is coding it:
if len(input) > 0:
a = input.split(' ')
if a[0] == 'check':
if len(a) > 1:
do_check(a[1])
elif a[0] == 'search':
if len(a) > 1:
do_search(a[1])
I like Python because it makes normally complicated things into rather simple things. I'm not too experienced with it, and I am fairly sure there's a much better way to do these things... some way more pythonic. I've seen some examples of people replacing switch statements with dicts and lambda functions, while other people simply recommended if..else nests.

dispatch = {
'check': do_check,
'search': do_search,
}
cmd, _, arg = input.partition(' ')
if cmd in dispatch:
dispatch[cmd](arg)
else:
do_default(cmd, arg)

I am fairly sure there's a much better way to do these things... some way more pythonic.
Not really. You code is simple, clear, obvious and English-like.
I've seen some examples of people replacing switch statements with dicts and lambda functions,
Yes, you've seen them and they're not clear, obvious or English-like. They exist because some people like to wring their hands over the switch statement.
while other people simply recommended if..else nests.
Correct. They work. They're simple, clear, ...
Your code is good. Leave it alone. Move on.

This lets you avoid giving each command name twice; function names are used almost directly as command names.
class CommandFunctions:
def c_check(self, arg):
print "checking", arg
def c_search(self, arg):
print "searching for", arg
def c_compare(self, arg1, arg2):
print "comparing", arg1, "with", arg2
def execute(self, line):
words = line.split(' ')
fn = getattr(self, 'c_' + words[0], None)
if fn is None:
import sys
sys.stderr.write('error: no such command "%s"\n' % words[0])
return
fn(*words[1:])
cf = CommandFunctions()
import sys
for line in sys.stdin:
cf.execute(line.strip())

If you're looking for a one liner 'pythonic' approach to this you can use this:
def do_check(x): print 'checking for:', x
def do_search(x): print 'searching for:', x
input = 'check yahoo.com'
{'check': do_check}.get(input.split()[0], do_search)(input.split()[1])
# checking for: yahoo.com
input = 'search google.com'
{'check': do_check}.get(input.split()[0], do_search)(input.split()[1])
# searching for: google.com
input = 'foo bar.com'
{'check': do_check}.get(input.split()[0], do_search)(input.split()[1])
# searching for: bar.com

Disregard, I just realized that my answer was similar to one of the other answers - and apparently there's no delete key :)

Variation on #MizardX's answer:
from collections import defaultdict
dispatch = defaultdict(do_default, check=do_check, search=do_search)
cmd, _, arg = input.partition(' ')
dispatch[cmd](arg)

Related

How to extract the [Documentation] text from Robot framework test case

I am trying to extract the content of the [Documentation] section as a string for comparision with other part in a Python script.
I was told to use Robot framework API https://robot-framework.readthedocs.io/en/stable/
to extract but I have no idea how.
However, I am required to work with version 3.1.2
Example:
*** Test Cases ***
ATC Verify that Sensor Battery can enable and disable manufacturing mode
[Documentation] E1: This is the description of the test 1
... E2: This is the description of the test 2
[Tags] E1 TRACE{Trace_of_E1}
... E2 TRACE{Trace_of_E2}
Extract the string as
E1: This is the description of the test 1
E2: This is the description of the test 2
Have a look at these examples. I did something similar to generate testplans descritio. I tried to adapt my code to your requirements and this could maybe work for you.
import os
import re
from robot.api.parsing import (
get_model, get_tokens, Documentation, EmptyLine, KeywordCall,
ModelVisitor, Token
)
class RobotParser(ModelVisitor):
def __init__(self):
# Create object with remarkup_text to store formated documentation
self.text = ''
def get_text(self):
return self.text
def visit_TestCase(self, node):
# The matched `TestCase` node is a block with `header` and
# `body` attributes. `header` is a statement with familiar
# `get_token` and `get_value` methods for getting certain
# tokens or their value.
for keyword in node.body:
# skip empty lines
if keyword.get_value(Token.DOCUMENTATION) == None:
continue
self.text += keyword.get_value(Token.ARGUMENT)
def visit_Documentation(self,node):
# The matched "Documentation" node with value
self.remarkup_text += node.value + self.new_line
def visit_File(self, node):
# Call `generic_visit` to visit also child nodes.
return self.generic_visit(node)
if __name__ == "__main__":
path = "../tests"
for filename in os.listdir(path):
if re.match(".*\.robot", filename):
model = get_model(os.path.join(path, filename))
robot_parser = RobotParser()
robot_parser.visit(model)
text=robot_parser._text()
The code marked as best answer didn't quite work for me and has a lot of redundancy but it inspired me enough to get into the parsing and write it in a much readable and efficient way that actually works as is. You just have to have your own way of generating & iterating through filesystem where you call the get_robot_metadata(filepath) function.
from robot.api.parsing import (get_model, ModelVisitor, Token)
class RobotParser(ModelVisitor):
def __init__(self):
self.testcases = {}
def visit_TestCase(self, node):
testcasename = (node.header.name)
self.testcases[testcasename] = {}
for section in node.body:
if section.get_value(Token.DOCUMENTATION) != None:
documentation = section.value
self.testcases[testcasename]['Documentation'] = documentation
elif section.get_value(Token.TAGS) != None:
tags = section.values
self.testcases[testcasename]['Tags'] = tags
def get_testcases(self):
return self.testcases
def get_robot_metadata(filepath):
if filepath.endswith('.robot'):
robot_parser = RobotParser()
model = get_model(filepath)
robot_parser.visit(model)
metadata = robot_parser.get_testcases()
return metadata
This function will be able to extract the [Documentation] section from the testcase:
def documentation_extractor(testcase):
documentation = []
for setting in testcase.settings:
if len(setting) > 2 and setting[1].lower() == "[documentation]":
for doc in setting[2:]:
if doc.startswith("#"):
# the start of a comment, so skip rest of the line
break
documentation.append(doc)
break
return "\n".join(documentation)

How to open online reference from IPython?

Is there a way to have IPython open a browser pointed at the appropriate online reference?
Especially for numpy,scipy, matplotlib?
For example, the doc for numpy.linalg.cholesky is pretty hard to read in a terminal.
I don't think there is a direct way to make IPython or any shell to open up documentation online, because the primary job of shells is to let you interact with the things they are shells to.
We could however write a script to open a new tab on a browser with the documentation. Like so:
import webbrowser
docsList = {
"numpy" : lambda x: "https://docs.scipy.org/doc/numpy/reference/generated/" + x + ".html",
"scipy" : lambda x: "https://docs.scipy.org/doc/scipy/reference/generated/" + x + ".html",
"matplotlib" : lambda x: "https://matplotlib.org/api/" + x.split('.')[1] + "_api.html",
"default" : lambda x: "https://www.google.com/search?q=documentation+" + x
}
def online(method_name):
"""
Opens up the documentation for method_name on the default browser.
If the package doesn't match any entry in the dictionary, falls back to
Google.
Usage
-------
>>> lookUp.online("numpy.linalg.cholesky")
>>> lookUp.online("matplotlib.contour")
"""
try:
url = make_url(method_name)
except AttributeError:
print("Enter the method name as a string and try again")
return
webbrowser.open(url, new = 2)
return
def make_url(method_name):
package_name = method_name.split('.')[0]
try:
return docsList[package_name](method_name)
except KeyError:
return docsList["default"](method_name)
You could save the above as "lookUp.py" at a location that Python can find it in, and then import it whenever you need to use it.
Caveats:
This method takes strings as input, so if you call it on a function it'll throw an error.
>>> lookUp.online("numpy.linalg.cholesky")
Will work.
>>> lookUp.online(numpy.linalg.cholesky)
Will ask you to give it as a string.
So use autocomplete to get to the function and then wrap it in quotes to get it to work.

insert_many in pymongo not persisting

I'm having some issues with persisting documents with pymongo when using insert_many.
I'm handing over a list of dicts to insert_many and it works fine from inside the same script that does the inserting. Less so once the script has finished.
def row_to_doc(row):
rowdict = row.to_dict()
for key in rowdict:
val = rowdict[key]
if type(val) == float or type(val) == np.float64:
if np.isnan(val):
# If we want a SQL style document collection
rowdict[key] = None
# If we want a NoSQL style document collection
# del rowdict[key]
return rowdict
def dataframe_to_collection(df):
n = len(df)
doc_list = []
for k in range(n):
doc_list.append(row_to_doc(df.iloc[k]))
return doc_list
def get_mongodb_client(host="localhost", port=27017):
return MongoClient(host, port)
def create_collection(client):
db = client["material"]
return db["master-data"]
def add_docs_to_mongo(collection, doc_list):
collection.insert_many(doc_list)
def main():
client = get_mongodb_client()
csv_fname = "some_csv_fname.csv"
df = get_clean_csv(csv_fname)
doc_list = dataframe_to_collection(df)
collection = create_collection(client)
add_docs_to_mongo(collection, doc_list)
test_doc = collection.find_one({"MATERIAL": "000000000000000001"})
When I open up another python REPL and start looking through the client.material.master_data collection with collection.find_one({"MATERIAL": "000000000000000001"}) or collection.count_documents({}) I get None for the find_one and 0 for the count_documents.
Is there a step where I need to call some method to persist the data to disk? db.collection.save() in the mongo client API sounds like what I need but it's just another way of inserting documents from what I have read. Any help would be greatly appreciated.
The problem was that I was getting my collection via client.db_name.collection_name and it wasn't getting the same collection I was creating with my code. client.db_name["collection-name"] solved my issue. Weird.

f.open() issue resulting in Unbound error for f.close()

I don't quite have an answer but I'm narrowing it down. Somehow I'm mixing/confusing types, I believe, between what is provided by commands like 'os.path' and type str().
As I've made the assignment of the logfile(s) globally, even though I can print it in the function, when the variable is used in fout = open(... it's actually a null that's being referenced, i.e. open() doesn't like/can't use the type it finds.
The error:
UnboundLocalError: local variable 'fout' referenced before assignment
I am simply writing a log of dot files (left on USB drives by OSX) for deletion, but the try/except is now falling over. First the original version.
working code:
logFile = "/Users/dee/Desktop/dotFile_names.txt"
try:
fout = open(logFile, 'w')
for line in dotFile_names:
fout.write(line)
except IOError as e:
print ("Error : %s not found." % fout)
finally:
fout.close()
Attempting better practice, I sought to put the log file specs and path as variables so they can be modified if need be - I hope to make it cross platform workable. these variables are at the head of the program, i.e. not in main(), but I pass them in and print() statements have shown me they are successfully being referenced. i.e. I get this printed:
/Users/dee/Desktop/dotFile_names.txt
Despite this the error I get is:
UnboundLocalError: local variable 'fout' referenced before assignment -
error points at the "fout.close()" line
Error producing code
logFilespec = "dotFile_names.txt"
fullLogFileSpec = []
userDesktop = os.path.join(os.path.expanduser('~'), 'Desktop')
fullLogFilespec = os.path.join(userDesktop, logFilespec)
try:
print "opening " + fullLogFilespec
fout = open(fullLogFileSpec, 'w')
for line in dotFile_names:
print "..", # are we executing this line..?
fout.write(line)
except IOError as e:
print ("Error : %s not found." % fout)
finally:
print "\nclosing " + fullLogFilespec
fout.close()
I've found that if I modify this line by converting to a string
fout = open(fullLogFileSpec, 'w')
fout = open(str(fullLogFileSpec), 'w')
the error goes away, BUT NO file is created on the Desktop!
At the very least I guess that I am passing something unrecognisable to fout = open() but it is not being caught by the except. Then when I pass something that does seem to allow fout =open() to work it seems to be a ghost?
So I figure I am lost between a String and whatever kind of reference/pointer os.path.expanduser() gives me.
I'm sure it's insanely simple. Before adding the str() code I also checked all indentation, removing them all and adding back using the editor indent hotkeys, just in case that was affecting things somehow.
OK, it looks like I was wearing my dumb glasses, I think declaring
fullLogFileSpec = []
as a list instead of a string was my error.
Similar as it is, having re-written it without that list declaration this code is working fine:
logfile_directory = os.path.join(os.path.expanduser('~'),'Desktop')
log_bf_file_spec = 'ItemsFoundByFolder_' + Deez_1.current_datetime() + '.txt'
log_by_folder = os.path.join(logfile_directory, log_bf_file_spec)
the function later calls, with no error:
fout_by_folder = open(log_by_folder, 'w')

saving and deleting fileI/O

Two part question.
First, how can i change this(i've tried using 'for' but i cant figure it out) so that it saves like;
'key value' instead of '{key: value}'.
with open("phonebook.txt", "w") as x:
json.dump(a, x)
Second, how do you delete from a file by using the users input.
I cannot see a way of changing this to delete from file instead of the dict 'a';
name = input("enter name of contact you want to delete: ")
if name in a:
del a[name]
EDIT. This is what ive done now but it doesnt do whats expected ( i also tried adding the .readlines where x is but it just gets errors.
def save(a):
with open("phonebook.txt", "w") as x:
for k in a:
json.dump(str(k)+" "+str(a[k]), x)
def load():
a = {}
with open("phonebook.txt", "r") as f:
for l in f:
a[l[0]] = l[1]
print (a)
def save works fine (as far as i can see anyway)
Also i have tried c = l.split() and a[c[0]] = c[1]. Just doesnt want to work !
First part
That's not JSON format. Do not use it if you need something else. Use plain text files, like
with open("phonebook.txt","w") as file :
for key, value in a.items() :
file.write(str(key)+" "+str(value))
Second part
It looks you loaded the file into dictionary a. In that case, you just need to write dictionary a back to the file after deleting. If you have not loaded the file into the dictionary yet, you can do it with:
a= {}
with open("phonebook.txt") as file :
for line in file.readlines() :
content= line.split()
a[content[0]]= content[1]