0

I'm using an example function from the google-api-python-client library that takes command line arguments and parses them using argparser. Here's the code this is based on (converted to python3) https://developers.google.com/youtube/v3/guides/uploading_a_video

if __name__ == '__main__':
    argparser.add_argument("--file", required=True,
                           help="Video file to upload")
# ...bunch of possible arguments
    args = argparser.parse_args()
    youtube = get_authenticated_service(args)
    try:
        initialize_upload(youtube, args)

The top function then passes the arguments along to the run_flow function in the oauth library as the flags parameter. That function expects command line arguments:

It presumes it is run from a command-line application and supports the
    following flags:

Is there a way to cleanly parameterize this function so I can easily call it from another python function? I've messed around with creating a wrapper function that sets those arguments as defaults.

def uploadVideo(file, title, description, category):
    # this feels hacky (yah think?)
    argparser.add_argument("--file", required=True,
                           help="Video file to upload", default=file)
    argparser.add_argument("--title", help="Video title", default=title)

I've started writing a subprocess.run call too, but that doesn't seem great.

Any suggestions?

3
  • I have no clue if it is "clean", maybe I do not understand the problem, but what about not leaving the parse_args(args) argument to default None, and pass here (maybe with a wrapper function) a custom string tuple that holds the correct parameters? Another way would be ignore the argparser completle and give an object that ducktypes the parsed arguments object. Please let me know if I should make an answer out of this ideas. Commented Nov 6, 2024 at 21:32
  • Yeah duck typing is sort of what I was thinking about, but it looked like argparser added some elements. (Not a python developer, so it’s possible I’m missing some basic concepts) Commented Nov 7, 2024 at 23:34
  • If I understand the problem, you are missing that you can add attributes in a dynamic manner to any class. Give me some hours I write an answer with examples and concept explanations. Commented Nov 8, 2024 at 11:46

1 Answer 1

0

Disclaimer

First of all: Let me say, that I am working at a dark side, so I have no tools to install the uploader packages. So let me know if the solutions do not work.

How parse_args works

After reading the parse_args documentation, and a little bit of the source code. I think the method looks something like that:

from typing import Any

# helper class to have a namespace that can be dynamically manipulated
class Namespace:
    pass


def parse_args(args: list[str] | None = None, namespace: Any | None = None):
    if args is None:
        args = sys.argv[1:]
    if namespace is None:
        namespace = Namespace()

    # ommitting the args parsing logic

    # iterating over the parsed results and adding them as instance attributes 
    # to the above created namespace instance. These attributes can be accessed 
    # in the normal way (object.attribute) and are only visible in that 
    # specific instance.
    for parameter, value in parsed_args:
        setattr(namespace, parameter, value)

This means we have two ways to resolve the problem asked for.

Create a dataclass (or give a ducktyped alternative)

This approach is in my opinion the cleaner and easier to understand.

from dataclasses import dataclass

@dataclass(frozen=True)
class UploadOptions:
    file: str
    title: str

# This should not be necessary, but argparse.Namespace implements it, so it is better ducktyping.
def __contains__(self, key) -> bool:
    return hasattr(self, key)

initialize_upload(youtube, UploadOptions(title=custom_title, file=custom_file))

The dataclass decorator is a cool tool, to reduce boilerplate code. It implements for us the __init__, __eq__ and __repr__ methods, with frozen=True, we get also immutability and the __hash__ function.

If you get error messages like this one dataclasses.FrozenInstanceError: cannot assign to field 'bar' try to remove frozen (or set it explicitly to False), the initiliaze_upload function expects a mutable object then.


Maybe some syntactic sugar, that you can give the UploadOptions class is, implementing the __call__ method. (I have learned Python from a Java context and missed that feature a long time.)

def __call__(self, yt) -> None:
    initialize_upload(yt, self)

you can now call it like that:

UploadOptions(title=custom_title, file=custom_file)(youtube)

This is because calling an instance like a function calls implicitly the call method of that instance.

Creating args on our own

But this is only another approach to your hacky solution, for me it feels a little less hacky, but also hacky. I prefer the first solution and choose this one only if I the other one fails.

argparser = argparse.ArgumentParser()
argparser.add_argument("--file", required=True,
                       help="Video file to upload")
argparser.add_argument("--title", help="Video title")


def uploadVideo(file: str | None = None, title: str | None= None):
    args: list[str] = []
    if file is not None:
        args.extend(['--file', file])
    if title is not None:
        args.extend(['--title', title])
    parsed = argparser.parse_args(args=args)
    
    initialize_upload(youtube, parsed)

You can of course create the argparser also in the function, but this way you create only one ArgumentParser instance and use that multiple times, which has a lower cpu usage.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.