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.