omry / omegaconf

Flexible Python configuration system. The last one you will ever need.
BSD 3-Clause "New" or "Revised" License
1.97k stars 110 forks source link

Allow the use of typing.Literal as a pythonic alternative to Enums #422

Open tmke8 opened 4 years ago

tmke8 commented 4 years ago

Is your feature request related to a problem? Please describe.

Enums are a bit awkward to define in Python. On the other hand, using strings to represent a set of choices is very common in Python code.

Describe the solution you'd like

from typing import Literal  # before Python 3.8, import from `typing_extensions`

@dataclass
class SvmConfig:
  kernel: Literal["linear", "poly", "rbf"] = "linear"

config: SvmConfig = OmegaConf.structured(SvmConfig(kernel="rbf"))

config.kernel = "poly"  # ok!
config.kernel = "exponential"  # error!

Describe alternatives you've considered

Enums, see above.

Additional context

My main motivation is to use this with Hydra.

omry commented 4 years ago

This functionality is covered well by enums. I am open to adding support for it but it's low pri. One implementation idea is to convert it to an Enum internally and use EnumNode (you can define enums dynamically). Feel free to send a pull request if you would like this supported.

omry commented 3 years ago

Sorry, linked to wrong issue.

yuvsier commented 2 years ago

Upvoting the issue - the main use case is configuration serialization. When using Literal, pickling works without a hitch. When using Enum, we must have the appropriate class available in the scope where we unpickle the config. There are workarounds, but the easiest one is to just assert in runtime instead of work with an Enum... which defeats the purpose imo. Thanks!

ramshi236 commented 1 year ago

how do use Enum instead of Litreral?

Jasha10 commented 1 year ago

@ramshi236 See the docs:

busycalibrating commented 1 year ago

Edit: oops I thought I was posting on the hydra repo, I think my question is still valid for OmegaConf but if not I can move this over to a hydra issue

Does this also work if you're using the dataclass to define a schema (as described here)? It doesn't seem to actually do any type checking for me; i.e. given an app script:

# app.py
import hydra
from dataclasses import dataclass

from hydra.core.config_store import ConfigStore
from omegaconf import OmegaConf, MISSING
from enum import Enum

OPTIONS = ["model_a", "model_b", "model_c"]
OPTIONS_ENUM = Enum("OPTIONS", {k: k for k in OPTIONS})

@dataclass
class Config:
    lr: float = MISSING
    model: OPTIONS_ENUM = MISSING

cs = ConfigStore.instance()
cs.store(name="Config", node=Config)

@hydra.main(config_path="./", config_name="config", version_base='1.3')
def my_main(cfg: Config) -> None:
    print(OmegaConf.to_yaml(cfg))

if __name__ == "__main__":
    my_main()

and a config file:

# config.yaml
lr: 0.1
model: should_throw_exception

this will happily run:

$ python app.py
lr: 0.1
model: should_throw_exception
omry commented 1 year ago

There is nothing linking your config file with your config schema. Check the last tutorial page in the Structured Configs tutorial.

daturkel commented 1 year ago

Looks like there's a PR for this https://github.com/omry/omegaconf/pull/865 that's gone stale. I'd love to add a vote for this to get reviewed and merged as it would be useful for me as well.

TiansuYu commented 2 months ago

Literal is getting more and more pupular than the traditional Enum in a lot of cases. Please consider raise the prio for this.