21

I think I'm not understanding something basic about python's argparse.

I am trying to use the Google YouTube API for python script, but I am not understanding how to pass values to the script without using the command line.

For example, here is the example for the API. The examples on github and elsewhere show this example as being called from the command line, from where the argparse values are passed when the script is called.

I don't want to use the command line. I am building an app that uses a decorator to obtain login credentials for the user, and when that user wants to upload to their YouTube account, they submit a form which will then call this script and have the argparse values passed to it.

How do I pass values to argparser (see below for portion of code in YouTube upload API script) from another python script?

if __name__ == '__main__':
    argparser.add_argument("--file", required=True, help="Video file to upload")
    argparser.add_argument("--title", help="Video title", default="Test Title")
    argparser.add_argument("--description", help="Video description",
        default="Test Description")
    argparser.add_argument("--category", default="22",
        help="Numeric video category. " +
            "See https://developers.google.com/youtube/v3/docs/videoCategories/list")
    argparser.add_argument("--keywords", help="Video keywords, comma separated",
        default="")
    argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES,
        default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.")
    args = argparser.parse_args()

    if not os.path.exists(args.file):
        exit("Please specify a valid file using the --file= parameter.")

    youtube = get_authenticated_service(args)
    try:
        initialize_upload(youtube, args)
    except HttpError, e:
        print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content)

EDIT: Per request, here is the traceback for the 400 Error I am getting using either the standard method to initialize a dictionary or using argparse to create a dictionary. I thought I was getting this due to badly formed parameters, but perhaps not:

Traceback (most recent call last):
  File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2-2.5.2\webapp2.py", line 1535, in __call__
    rv = self.handle_exception(request, response, e)
  File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2-2.5.2\webapp2.py", line 1529, in __call__
    rv = self.router.dispatch(request, response)
  File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2-2.5.2\webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2-2.5.2\webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2-2.5.2\webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "C:\Program Files (x86)\Google\google_appengine\lib\webapp2-2.5.2\webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "C:\Users\...\testapp\oauth2client\appengine.py", line 796, in setup_oauth
    resp = method(request_handler, *args, **kwargs)
  File "C:\Users\...\testapp\testapp.py", line 116, in get
    resumable_upload(insert_request)
  File "C:\Users\...\testapp\testapp.py", line 183, in resumable_upload
    status, response = insert_request.next_chunk()
  File "C:\Users\...\testapp\oauth2client\util.py", line 129, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "C:\Users\...\testapp\apiclient\http.py", line 874, in next_chunk
    return self._process_response(resp, content)
  File "C:\Users\...\testapp\apiclient\http.py", line 901, in _process_response
    raise HttpError(resp, content, uri=self.uri)
HttpError: <HttpError 400 when requesting https://www.googleapis.com/upload/youtube/v3/videos?alt=json&part=status%2Csnippet&uploadType=resumable returned "Bad Request">
sean
  • 3,484
  • 5
  • 27
  • 45
  • 1
    The easiest way is to **refactor** the code, such that `sys.argv` is explicitly passed to a function that uses `argparse` to process the resulting *list*; this also makes testing much simpler. See e.g. https://github.com/textbook/py_wlc/blob/develop/py_wlc/data/webtag_parser.py#L220 – jonrsharpe Jun 27 '15 at 15:51
  • here is only one BIG question, if you will not use command line, why you need parse command line arguments? You just copy-paste example from google site without understanding the code itself. – Reishin Jun 27 '15 at 23:58
  • As JL Peyret demonstrated, argparse can be useful without using the command line. Although I am new to python, I am attempting to understand the code, which is why I tried a different dictionary creation method. I suppose either works. I appreciate people's attempts to help me better understand the argparse method. Because I am getting the 400 Error with either method, I obviously have more to learn about that API, so you are correct that I don't fully understand the code. I hope I eventually will with more work. Thanks for taking the time to stop by, Reishin. – sean Jun 28 '15 at 00:39
  • The main issue is that `args` is an object with attributes, not a dictionary. I found the API code that you need to call. – hpaulj Jun 28 '15 at 02:15
  • Yes, the vars(args) in my examples was just a quickie way to show what was in args. Wasn't meant to be used elsewhere. I – JL Peyret Jun 28 '15 at 02:28

2 Answers2

25

Whether it is the best approach or not is really for you to figure out. But using argparse without command line is easy. I do it all the time because I have batches that can be run from the command line. Or can also be called by other code - which is great for unit testing, as mentioned. argparse is especially good at defaulting parameters for example.

Starting with your sample.

import argparse

argparser = argparse.ArgumentParser()
argparser.add_argument("--file", required=True, help="Video file to upload")
argparser.add_argument("--title", help="Video title", default="Test Title")
argparser.add_argument("--description", help="Video description",
    default="Test Description")
argparser.add_argument("--category", default="22",
    help="Numeric video category. " +
        "See https://developers.google.com/youtube/v3/docs/videoCategories/list")
argparser.add_argument("--keywords", help="Video keywords, comma separated",
    default="")
VALID_PRIVACY_STATUSES = ("private","public")
argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES,
    default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.")

#pass in any positional or required variables.. as strings in a list
#which corresponds to sys.argv[1:].  Not a string => arcane errors.
args = argparser.parse_args(["--file", "myfile.avi"])

#you can populate other optional parameters, not just positionals/required
#args = argparser.parse_args(["--file", "myfile.avi", "--title", "my title"])


print vars(args)

#modify them as you see fit, but no more validation is taking place
#so best to use parse_args.
args.privacyStatus = "some status not in choices - already parsed"
args.category = 42

print vars(args)

#proceed as before, the system doesn't care if it came from the command line or not
# youtube = get_authenticated_service(args)    

output:

{'category': '22', 'description': 'Test Description', 'title': 'Test Title', 'privacyStatus': 'private', 'file': 'myfile.avi', 'keywords': ''}
{'category': 42, 'description': 'Test Description', 'title': 'Test Title', 'privacyStatus': 'some status not in choices - already parsed', 'file': 'myfile.avi', 'keywords': ''}
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • This is exactly what I needed. Thanks! I am still getting a 400 error when I try to upload the video, but I think this is a different issue - your method of passing values to argparse is working. – sean Jun 27 '15 at 18:21
  • The answer is an example, what ppl shouldn't to do, great! – Reishin Jun 27 '15 at 23:50
  • p.s. FWIW, I have recently started using [click](https://pypi.org/project/click/) and I find it a bit easier to use programmatically, though you still have to compose the command line yourself - it's much easier to introspect and calling the target function with `standalone_mode=False` causes it to throw a normal exception rather than a `SystemExit`. – JL Peyret Sep 27 '19 at 00:24
14

Calling parse_args with your own list of strings is a common argparse testing method. If you don't give parse_args this list, it uses sys.argv[1:] - i.e. the strings that the shell gives. sys.argv[0] is the strip name.

args = argparser.parse_args(['--foo','foovalue','barvalue'])

It is also easy to construct an args object.

args = argparse.Namespace(foo='foovalue', bar='barvalue')

In fact, if you print args from a parse_args call it should look something like that. As described in the documentation, a Namespace is a simple object, and the values are artributes. So it is easy to construct your own namespace class. All args needs to be is something that returns the appropriate value when used as:

x = args.foo
b = args.bar

Also as noted in the docs, vars(args) turns this namespace into a dictionary. Some code likes to use dictionary, but evidently these youtub functions want a Namespace (or equivalent).

get_authenticated_service(args)
initialize_upload(youtube, args)

https://docs.python.org/3/library/argparse.html#beyond-sys-argv

https://docs.python.org/3/library/argparse.html#the-namespace-object


https://developers.google.com/youtube/v3/guides/uploading_a_video?hl=id-ID

has get_authenticated_service and initialize_upload code

def initialize_upload(youtube, options):
  tags = None
  if options.keywords:
    tags = options.keywords.split(",")

  body=dict(
    snippet=dict(
      title=options.title,
      description=options.description,
      tags=tags,
      categoryId=options.category
    ),
    status=dict(
      privacyStatus=options.privacyStatus
    )
  )
 ....

The args from the parser is options, which it uses as options.category, options.title, etc. You could substitute any other object which has the same behavior and the necessary attributes.

hpaulj
  • 221,503
  • 14
  • 230
  • 353