-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
decouple library (modules/classes/etc.) from command-line interface (argparse) #16
Comments
The idea behind the
Supplying each function with separate params would mean:
This means, the It just so happens that the program options for command line tool are set - well, from the command line. It doesn't mean that having program options in one class is somehow linked to command line tools design. Actually, it is quite common to have Which options to set - it might be a good idea to just set all of them. Some probably are not needed for your use case, but setting them doesn't really cost much. Also, you may want to look at dataclass for clearer implementation. The tests are mocking command line args - yes, these are supposed to be "black box" tests - verifying the scripts as the user would execute them. I just don't use |
Thanks for the answers. That makes sense. Here is what I think you're describing from dataclasses import dataclass, field
from enum import Enum
from typing import IO, Any, Dict
from pylabview.LVrsrcontainer import VI
vi_path = "/path/to/my.vi"
@dataclass
class PrintMap(str, Enum):
"""Enum with string values for the `print_map` program option."""
RSRC = "rsrc"
NONE = ""
@dataclass
class ProgramOptions:
"""global configuration settings for the pylabview tool."""
print_map: PrintMap = field(default=PrintMap.NONE, metadata={"help": "Print the RSRC map to stdout."})
typedesc_list_limit: int = field(default=4095, metadata={"help": "Limit for the length of type descriptors."})
array_data_limit: int = field(default=(2 ** 28) - 1, metadata={"help": "Limit for the length of array data."})
store_as_data_above: int = field(default=4095, metadata={"help": "Store as data above this length."})
verbose: int = field(default=0, metadata={"help": "Increase verbosity of output. 0 = quiet, 1 = normal, 2 = verbose."})
rsrc: IO[Any] | None = field(default=None, metadata={"help": "file (IO) reference to the RSRC file."})
def get_vi_labview_version(vi_path: str) -> Dict:
"""return the labview version a VI is saved in"""
with open(vi_path, "rb") as f:
po = ProgramOptions(rsrc = f)
vi = VI(po, rsrc_fh=po.rsrc)
return vi.getFileVersion()
lv_version = get_vi_labview_version(vi_path)
print(lv_version) Some ideas:
@dataclass
class ProgramOptions:
...
@classmethod
def from_argparse(cls, parser: ArgumentParser):
po = parser.parse_args()
return cls(**po)
|
Side question: How can I suppress (or fix) these warnings, below, that are printing to the console? I get them when I run the above code.
|
Text printed to The other ideas - it's more code to do the same thing, but it is a better library interface. If properly implemented, it should be acceptable. The I don't think a class is required for |
OK, it's starting to simplify... from dataclasses import dataclass, field
from typing import Dict
from pylabview.LVrsrcontainer import VI
@dataclass
class ProgramOptions:
"""global configuration settings for the pylabview tool."""
print_map: str = field(default="RSRC", metadata={"help": "Print the RSRC map to stdout."})
typedesc_list_limit: int = field(default=4095, metadata={"help": "Limit for the length of type descriptors."})
array_data_limit: int = field(default=(2 ** 28) - 1, metadata={"help": "Limit for the length of array data."})
store_as_data_above: int = field(default=4095, metadata={"help": "Store as data above this length."})
verbose: int = field(default=0,
metadata={"help": "Increase verbosity of output. 0 = quiet, 1 = normal, 2 = verbose."})
rsrc: str | None = field(default=None, metadata={"help": "file path to RSRC file."})
def get_file_labview_version(vi_path: str) -> Dict:
"""return the labview version a VI is saved in"""
with open(vi_path, "rb") as f:
vi = VI(po=ProgramOptions(), rsrc_fh=f)
return vi.getFileVersion() my thoughts about helpful tweaks then become:
I'm happy to help with the above suggestions. Which of the above would you be friendly to, and I'll create some PRs and work to make sure they meet your expectations/requirements. Thanks! |
That sounds ok.
I don't think it's a good idea. The options should always be provided.
If you see any invalid propagation of errors, it can be fixed. But stdout/stderr are not something to be afraid of when programming. The standard streams are there to be used, and all well designed operating systems handle that correctly, GUI or not. That said, what could be placed instead of just printing, is the
Sure, tests are ok. I only created black box tests, and only for extraction - but I'd be happy to have unit tests as well. |
I agree stdio is a best practice for console applications as a way to communicate with the calling process, terminal UI, and OS -- it's the console application's external interface to the world. However, I would argue that use of stdio in libraries designed for data parsing/generating limits their use in other types of applications and adds complexity for code that calls them and may want to handle stdio differently or not at all (in the case of GUI applications). |
Most open-source apps print things on the console. You just don't see it, unless you have an actual console attached to that process. It is not normal for one kind of apps, but universal. But in case of Python and pylabview, you are right with your arguments about complexity, and switching the standard streams having potentially wider consequences. |
Great tool!
I noticed that the tests are mocking up CLI calls and then invoking the main function/entrypoint (
readRSRC_main()
) of thepylabview
library.I had a thought... would it make sense to decouple the the various classes like
VI
from the argparse objectpo
(and the command-line usage, in general)?Currently, it's a bit tricky to the the library. I want to be able to read a VI's saved-in labview version. The call would look like:
However, we're missing the bit of magic in the
po
argument. What is this?We can see by looking at the test example code:
Then, in
readRSRC_main()
it works by parsing those arguments using something like this:then, it looks for these arguments as attributes of the of the
po
object.inside of the
VI
class, thepo
object is passed along into the constructors of other classes (shown in the various uses ofself.po
below):It seems that it there is a lot of coupling the the various classes like
VI
with the argparse objectpo
.For my use case of checking a VI's saved-in labview version, I can create my own
po
object and set it's attribute like this:I'm not 100% certain which attributes should be set on
po
for this call to simply get a VI's version such as -- I have to read through the code and do some trial and error.It seems that passing the various
po
parameters as individual arguments to the constructors/methods would make the library very useful (and easy to use), independent from the command-line interface mode.The text was updated successfully, but these errors were encountered: