Code Monkey home page Code Monkey logo

Comments (13)

rubencart avatar rubencart commented on June 10, 2024 2

I'm not sure if I can reopen this issue, maybe you could reopen it @martinjm97?

from typed-argument-parser.

rubencart avatar rubencart commented on June 10, 2024 2

I understand. I think my use case actually does not really correspond to subparsers, because of that reason. What I want is to be able to specify arguments in json or yaml files with >1 level of nesting, or like --model.lr which then gets parsed into a nested Tap Config.
Like

{
  "dataset": "mnist",
  "model": {
    "layers": 6,
  }
}

Which would get parsed into a Tap config with fields that you could access like config.dataset or config.model.layers. Of course, this would mainly be useful if you can parse json/yaml files, but even with cli arguments like --model.layers I think it could already be useful. I don't like 100s of unstructured argument fields thrown together in a single Config class without structure.

from typed-argument-parser.

dalek-who avatar dalek-who commented on June 10, 2024 2

@martinjm97 I agree with @rubencart. It's a frequent use-case to define nested config in deep learning projects. Wish you can support this extension in the future :)

Since Tap supports load/save config, type hint, and auto complete, sometimes I use Tap not only as arg-parser, but also a unified config class, and even dynamically generate some config value during runtime:

# use as argparser
class MyArgParser(Tap):
    do_train: bool = False
    do_test_only: bool = False
    disable_gpu: bool = False
    debug: bool = Fasle
    comment: str = ""

# use as config class
@dataclass
class Config_Training(Tap):
    learning_rate: float = 2e-5
    epoch: int = 3
    optimizer: str = "adam"
    ...

    def parse_args(self):
        # when used as a pure config class, I will disable this function.
        raise NotImplementError()

# use as runtime calculated config class
@dataclass
class Config_FilePath(Tap):
    debug: bool
    start_time: str
    comment: str

    def __post_init__(self):
        self.OUTPUT_DIR = Path("./output") / f"{self.start_time}_debug-{self.debug}_{self.comment}"
        self.CONFIG_DIR = self.OUTPUT_DIR / "config"
        self.CHECKPOINT_DIR = self.OUTPUT_DIR / "checkpoint"
        self.log_path = self.OUTPUT_DIR / "log.txt"
        ...

    def parse_args(self):
        # when used as a pure config class, I will disable this function.
        raise NotImplementError()

if __name__=="__main__":
    start_time = datetime.now().strftime('%b-%d_%H-%M-%S')
    args = MyArgParser().parse_args()
    config_train = Config_train()
    config_filepath = Config_FilePath(debug=args.debug, start_time=start_time, comment=args.comment)

    # save config
    args.save(config_filepath.CONFIG_DIR / "args.json")
    config_train.save(config_filepath.CONFIG_DIR / "config_train.json")
    config_filepath.save(config_filepath.CONFIG_DIR / "config_filepath.json")
    

I know these are far away from standard Tap use-case, but they are very convenient!

from typed-argument-parser.

rubencart avatar rubencart commented on June 10, 2024

My solution so far:

class SubArgs(Tap):
    subarg1: int
    subarg2: str

class Args(Tap):
    arg1: int
    arg2: str
    subargs: str

    def add_arguments(self) -> None:
        self.add_argument("--subargs", type=lambda x: SubArgs, required=True)

    def process_args(self) -> None:
        tmp = SubArgs()
        tmp.from_dict(self.subargs)
        self.subargs = tmp

args = Args().parse_args()
print(args.arg1, args.subargs.subarg1)

This seemed to work but the resulting Args object could not be pickled. Adding the following method to Args solved it.

    def __getstate__(self):
        dct = {
            k: v for (k, v) in self.as_dict().items()
        }
        dct.update({'model': self.model.as_dict()})
        return dct

from typed-argument-parser.

martinjm97 avatar martinjm97 commented on June 10, 2024

Hi @rubencart,

If I understand you correctly, you're looking for the functionality of a subparser. We implemented subparsers here. Let me know if I'm misunderstanding you. Your solution is quite inventive though :)

--Jesse

from typed-argument-parser.

martinjm97 avatar martinjm97 commented on June 10, 2024

Feel free to reopen if subparsers don't actually resolve the issue.

from typed-argument-parser.

rubencart avatar rubencart commented on June 10, 2024

If I'm understanding it correctly, they're not what I'm looking for. It seems like you can add multiple subparsers, but that you can actually only 'choose' one when you're executing the command, based on which subcommand you choose. Is that correct?
I want multiple subparsers that are all activated when I run my main command, to be able to direct different arguments to the parts of my application where they are relevant.

from typed-argument-parser.

rubencart avatar rubencart commented on June 10, 2024

This relates in a way to #39 and #25, especially when loading json/yaml config files, it would be nice if you could structure those config files, and then access the parsed configs in python afterwards according to that structure IMO 🙂 .

from typed-argument-parser.

aarbelle avatar aarbelle commented on June 10, 2024

I agree this could be a very useful functionality!

from typed-argument-parser.

martinjm97 avatar martinjm97 commented on June 10, 2024

you can actually only 'choose' one when you're executing the command, based on which subcommand you choose. Is that correct?

Yes!

I want multiple subparsers that are all activated when I run my main command, to be able to direct different arguments to the parts of my application where they are relevant.

At a high-level, we don't believe there's a clean way to implement this and it extends beyond the functionality of argparse. The spec is ambiguous because it's not clear whether a subparser or subparser of a subparser is being selected. For example, consider:

from tap import Tap

class OtherSubparser(Tap):
    bar: str

class SubparserA(Tap):
    baz: int

    def configure(self):
        self.add_subparser('b', OtherSubparser)

class SubparserB(Tap):
    bar: int

class Args(Tap):
    foo: bool = False

    def configure(self):
        self.add_subparser('a', SubparserA)
        self.add_subparser('b', SubparserB)

args = Args().parse_args('a --baz 3 b --bar 14'.split())

Is b activating SubparserB or OtherSubparser? While this is sort of an odd case, it is still a case that would have to be handled by an implementation. What do you think?

Best,
Kyle and Jesse

from typed-argument-parser.

martinjm97 avatar martinjm97 commented on June 10, 2024

Hi @rubencart,

This makes perfect sense to me. This is definitely an extension to argparse (correct me if I'm mistaken) and our primary goal is to be a typed wrapper to argparse. I think it's a great additional feature, but we may not get to it in the near future since it is an extension (e.g., we'll want to support Python 3.9 types first). Thank you for the awesome idea!

Best,
Jesse

from typed-argument-parser.

rubencart avatar rubencart commented on June 10, 2024

I understand! :)

from typed-argument-parser.

martinjm97 avatar martinjm97 commented on June 10, 2024

Thank you again for bringing up this interesting use case. We've decided that we won't be adding this feature to Tap. We're happy to reconsider/reopen the issue. Our reasoning and recommendations are below.

Our primary goal is to be a typed wrapper to argparse. In order to both be consistent with argparse and maintain typing for nested arguments, we would need to introduce significant complexity. Specifying nested argument from the command line is complicated and it is beyond the default behavior of argparse (although you can always write a custom type parsing function).

If you need typing, you could write a custom class and unpacking function.

If you're willing to forego typing, our recommendation is to use easydict, which works as follows:
in config.json,

{
  "dataset": "mnist",
  "model": {
    "layers": 6
  }
}

in Python,

import json
from easydict import EasyDict as edict

with open('config.json') as f:
    args = edict(json.load(f))

print(args.model.layers)

from typed-argument-parser.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.