Parsing python command line arguments - python

Complete newbie question.
I am developing a class for searching a movie database using an API.
Here is how you make a query using the movie title and year from within the Python interpreter:
import moviesearch
movie_db = moviesearch.MovieSearch()
result = movie_db.search('Clockwork Orange', year='1971')
print result
Doing this particular query yields one result from the service I am using.
To save typing for testing, I have created the following script m.py:
from sys import argv
import moviesearch
script, movie_name, params = argv
movie_db = moviesearch.MovieSearch()
result = movie_db.search(movie_name, params)
print result
When I execute this script like so:
python m.py 'Clockwork Orange', year='1971'
the service yields two results.
So this means that there is something wrong with the way I am formatting my parameters when I am testing the class with the script. Can someone please tell me what I am doing wrong?
I am not including the class for querying the movie database because I don't think that's necessary for figuring out what's wrong. What I need to know is how to properly format my parameters when using the script so that it executes exactly as it does in the example above where I am using the class directly from the Python interpreter.
Thanks in advance for any help.

When you do this in the shell (I'm assuming a *nix platform with an sh-compatible shell; Windows is probably slightly different):
python m.py 'Clockwork Orange', year='1971'
The sys.argv list will be:
['m.py', 'Clockwork Orange,', 'year=1971']
So, when you do this:
script, movie_name, params = argv
You end up with movie_name set to the string 'Clockwork Orange,' (with an extra comma), and params set to the string 'year=1971'. So, your call is ultimately:
result = movie_db.search('Clockwork Orange,', 'year=1971')
This is not the same as:
result = movie_db.search('Clockwork Orange', year='1971')
So, how do you get what you want?
Well, you can parse the params, with code something like this:
params = dict(pair.split('=') for pair in argv[2:])
result = movie_db.search(movie_name, **params)
The first line will give you a dict with {'year': '1971'}. Then you can use the ** syntax to forward that dict as the keyword arguments to the function.
Finally, you should take out the extra comma in the arguments you use to run the script. (You could write code to strip it, but it seems like it's just a typo, so the better fix is to not make that typo.)

Related

Python for maya: Why can't I use a variable in concatination with a wildcard?

I'm trying to use the "ls" python command in maya, to list certain objects with a matching string in the name in concatination with a wildcard.
Simple sample code like this:
from maya.cmds import *
list = ls('mesh*')
This code works and will return a list of objects with the matching string in the name, however, I would like to use a variable instead of hard coding in the string. More like this:
from maya.cmds import *
name = 'mesh'
list = ls('name*')
OR like this:
from maya.cmds import *
name = 'mesh'
list = ls('name' + '*')
However, in both examples, it returns an empty list unlike the first. I'm not sure why this is the case because in those examples, the string concatination should come out to 'mesh*' like the first example. I couldn't find an answer on this website, so I chose to ask a question.
Thank you.
JD
PS. If there is a better way to query for objects in maya, let me know what it's called and I'll do some research into what that is. At the moment, this is the only way I know of how to search for objects in maya.
As soon as you add quotes around your variable name like this 'name', you are actually just creating a new string instead of referring to the variable.
There are many different ways to concatenate a string in Python to achieve what you want:
Using %:
'name%s' % '*'
Using the string's format method:
'{}*'.format(name)
Simply using +:
name + '*'
All of these will yield the same output, 'mesh*', and will work with cmds.ls
Personally I stick with format, and this page demonstrates a lot of reasons why.

How to use lists or dicts as command line arguments in pyJWT

The following python code produces a valid JWT token, using pyjwt:
>>> import jwt
>>> payload = {'nested': [{'name': 'me', 'id': '1'}]}
>>> token = jwt.encode(payload, 'secret')
>>> token.decode()
ey[...]ko0Zq_k
pyjwt also supports calls from the command line interface. But the docs only show examples with = separated key value pairs and not with nested payloads.
My best guess was this:
$ pyjwt --key=secret encode nested=[{name=me, id=1}]
ey[...]0FRW9gyU # not the same token as above :(
Which didn't work. Is it simply not supported?
As mentioned, your command line token when decoded returns this json object:
{'nested': '[{name=me,', 'id': '1}]'}
A quick dive into the __main__.py of jwt package gives this little snippet:
... snipped
def encode_payload(args):
# Try to encode
if args.key is None:
raise ValueError('Key is required when encoding. See --help for usage.')
# Build payload object to encode
payload = {}
for arg in args.payload:
k, v = arg.split('=', 1)
... some additional handling on v for time, int, float and True/False/None
... snipped
As you can see the key and value of the payload is determined directly based on the split('=', 1), so it anything passed the first = in your command line following a key will always be determined as a single value (with some conversion afterwards).
So in short, nested dicts in CLI is not supported.
However, the semi-good news is, there are certain ways you can work around these:
Run an impromptu statement off Python's CLI directly like so:
> python -c "import jwt; print(jwt.encode({'nested':[{'name':'me', 'id':'1'}]}, 'secret').decode('utf-8'))"
# eyJ...Zq_k
Not exactly ideal, but it gives you what you need.
Save the same script into a .py capable of taking args and execute it on Python's CLI:
import sys, jwt
my_json = sys.argv[0]
token = jwt.encode(eval(my_json), 'secret')
print(token.decode('utf-8'))
# run in CLI
> python my_encode.py "{'nested':[{'name':'me', 'id':'1'}]}"
# eyJ...Zq_k
Note the use of eval() here is not ideal because of security concerns. This is just my lazy way of implementing it because I don't want to write a parser for the args. If you absolutely must use CLI for your implementation and it's exposed, I would highly recommend you invest the effort into cleansing and parsing the argvs more carefully.
The most contrived way: you can try to modify the Lib\site-packages\jwt\__main__.py function (at your own peril) to suit your need until official support is added. I'd caution you should be rather comfortable with writing your own parse though before considering messing with the main code. I took a few stab at it before I realize the limitations you will be running into:
a. The main encode() method doesn't consider a list as a valid JSON object (but it should). So right off the bat you must have a dict like string to manipulate.
b. The code always forces numbers to be cast as int or float if possible. You'll need to escape it somehow or entirely change the way it handle numbers.
My attempt went something like this:
def func(result, payload):
for arg in payload:
k, v = arg.split('=', 1)
if v.startswith('{') and v.endswith('}'):
result[k] = func({}, v[1:-1])
else:
... the rest of the existing code
However I quickly ran into the limitation of the original arguments are already space delimited and assume it's a k, v pair, I would need to further handle another delimiter like , as well as capability to handle lists, and it could get messier. It's definitely doable, and the effect is immediate i.e. the CLI runs directly off of this __main__.py, but it's more work than I'd like to invest at the moment so I leave it with your capable hands.
The effort to overcome these issues to achieve what you need might be more than necessary, depend on your skill and comfort level. So pick your battle... if CLI is not absolutely necessary, I'd suggest just use the .py methods instead.

Using argparse within a class for repeated iterations?

Maybe this isn't the best way to frame my problem. Right now, I have a program that already uses argparse to enter my class in 'manual' mode. So for example, if I type python parser.py --m, I go to Parse(args), which is my class. This all works fine.
Once this is done, the class parses the file for its table of contents list and prints it to the screen. The table of contents is an OrderedDict with the page number as the key and the page title as the value. The idea is that you could press a number and it would print out the text on that respective page, and that you could do this until you type any command that doesn't correspond to a number in the dict.
I was wondering if this would be possible to do with argparse or sys?
args = parser.parse_args() parses sys.argv[1:], the list like structure that the command line produced and gave to the Python interpreter. You can also call parse_args with any similar list of strings.
How to split a string like the shell in python?
ipython uses a modified argparse to handle its main input. It uses the config files to populate the parser, giving the user a last minute way of fiddling with the configuration. But its magic commands also parse their arguments with a form of argparse. For that it has its own REPL, rather than using input/raw_input.

Python 3.3 library abpy, file undefined

I'm using a library ABPY (library here) for python but it is in older version i think. I'm using Python 3.3.
I did fix some PRINT errors, but that's how much i know, I'm really new on programing.
I want to fetch some webpage and filter it from advertising and then print it again.
EDITED after Sg'te'gmuj told me how to convert from python 2.x to 3.x this is my new code:
#!/usr/local/bin/python3.1
import cgitb;cgitb.enable()
import urllib.request
response = urllib.request.build_opener()
response.addheaders = [('User-agent', 'Mozilla/5.0')]
response = urllib.request.urlopen("http://www.youtube.com")
html = response.read()
from abpy import Filter
with open("easylist.txt") as f:
ABPFilter = Filter(file('easylist.txt'))
ABPFilter.match(html)
print("Content-type: text/html")
print()
print (html)
Now it is displaying a blank page
Just took a peek at the library, it seems that the file "easylist.txt" does not exist; you need to create the file, and populate it with the appropriate filters (in whatever format ABP specifies).
Additionally, it appears it takes a file object; try something like this instead:
with open("easylist.txt") as f:
ABPFilter = Filter(f)
I can't say this is wholly accurate though since I have no experience with the library, but looking at it's code I'd suspect either of the two are the problem, if not both.
Addendum #1
Looking at the code more in-depth, I have to agree that even if that fix I supplied does work, you're going to have more problems (it's in 2.x as you suggested, when you're using 3.x). I'd suggest utilizing Python's 2to3 function, to convert from typical Python 2 to Python 3 code (it's not foolproof though). The command line would be as so:
2to3 -w abpy.py
That will convert it from Python 2.x to 3.x code, and re-write the source file.
Addendum #2
The code to pass the file object should be the "f" variable, as shown above (modified to represent that; I wasn't paying attention and just left the old file function call in the argument).
You need to pass a URI to the function as well:
ABPFilter.match(URI)
You'll need to modify the code to pass those items into an array (I'm assuming at least); I'm playing with it now to see. At present I'm getting a rule error (not a Python error; but merely error handling used by abpy.py, which is good because it suggests that it's the right train of thought).
The code for the Filter.match function is as following (after using the 2to3 Python script):
def match(self, url, elementtype=None):
tokens = RE_TOK.split(url)
print(tokens)
for tok in tokens:
if len(tok) > 2:
if tok in self.index:
for rule in self.index[tok]:
if rule.match(url, elementtype=elementtype):
print(str(rule))
What this means is you're, at present, at a point where you need to program the functionality; it appears this module only indicates the rule. However, that is still useful.
What this means is that you're going to have to modify this function to take the HTML, in place of the the "url" parameter. You're going to regex the HTML (this may be rather intensive) for a list of URIs and then run each item through the match loop Where you go from there to actually filter the nodes, I'm not sure; but there is a list of filter types, so I'm assuming there is a typical procedural ABP does to remove the nodes (possibly, in some cases merely by removing the given URI from the HTML?)
References
http://docs.python.org/3.3/library/2to3.html

Using Python Web GET data

I'm trying to pass information to a python page via the url. I have the following link text:
"<a href='complete?id=%s'>" % (str(r[0]))
on the complete page, I have this:
import cgi
def complete():
form = cgi.FieldStorage()
db = MySQLdb.connect(user="", passwd="", db="todo")
c = db.cursor()
c.execute("delete from tasks where id =" + str(form["id"]))
return "<html><center>Task completed! Click <a href='/chris'>here</a> to go back!</center></html>"
The problem is that when i go to the complete page, i get a key error on "id". Does anyone know how to fix this?
EDIT
when i run cgi.test() it gives me nothing
I think something is wrong with the way i'm using the url because its not getting passed through.
its basically localhost/chris/complete?id=1
/chris/ is a folder and complete is a function within index.py
Am i formatting the url the wrong way?
The error means that form["id"] failed to find the key "id" in cgi.FieldStorage().
To test what keys are in the called URL, use cgi.test():
cgi.test()
Robust test CGI script, usable as main program. Writes minimal HTTP headers and formats all information provided to the script in HTML form.
EDIT: a basic test script (using the python cgi module with Linux path) is only 3 lines. Make sure you know how to run it on your system, then call it from a browser to check arguments are seen on the CGI side. You may also want to add traceback formatting with import cgitb; cgitb.enable().
#!/usr/bin/python
import cgi
cgi.test()
Have you tried printing out the value of form to make sure you're getting what you think you're getting? You do have a little problem with your code though... you should be doing form["id"].value to get the value of the item from FieldStorage. Another alternative is to just do it yourself, like so:
import os
import cgi
query_string = os.environ.get("QUERY_STRING", "")
form = cgi.parse_qs(query_string)
This should result in something like this:
{'id': ['123']}
First off, you should make dictionary lookups via
possibly_none = my_dict.get( "key_name" )
Because this assigns None to the variable, if the key is not in the dict. You can then use the
if key is not None:
do_stuff
idiom (yes, I'm a fan of null checks and defensive programming in general...). The python documentation suggests something along these lines as well.
Without digging into the code too much, I think you should reference
form.get( 'id' ).value
in order to extract the data you seem to be asking for.

Categories