diff --git a/src/pytoolconfig/utils.py b/src/pytoolconfig/utils.py index 81a9f7a..358614e 100644 --- a/src/pytoolconfig/utils.py +++ b/src/pytoolconfig/utils.py @@ -2,7 +2,9 @@ from __future__ import annotations import sys +import warnings from dataclasses import Field, fields, is_dataclass, replace +from enum import EnumMeta from pathlib import Path from typing import TYPE_CHECKING, Any, Generator, Mapping, TypeVar @@ -90,6 +92,12 @@ def _fields(dataclass: DataclassInstance | type[DataclassInstance]) -> dict[str, return {field.name: field for field in fields(dataclass) if field.init} +def _format_enum(option: Any) -> str: + if isinstance(option, str): + return f'"{option}"' + return str(option) + + def _dict_to_dataclass( dataclass: type[T], dictionary: Mapping[str, Key], @@ -103,7 +111,19 @@ def _dict_to_dataclass( assert isinstance(value, Mapping) filtered_arg_dict[key_name] = _dict_to_dataclass(sub_table, value) elif key_name in dataclass_fields: - filtered_arg_dict[key_name] = value + keytype = dataclass_fields[key_name].type + if isinstance(keytype, EnumMeta): + try: + filtered_arg_dict[key_name] = keytype(value) + except ValueError: + valid = set(keytype._value2member_map_.keys()) + warnings.warn( + f"{value} is not a valid option for {key_name}, skipping." + f"Valid options are: {','.join(map(_format_enum, valid))}.", + stacklevel=1, + ) + else: + filtered_arg_dict[key_name] = value return dataclass(**filtered_arg_dict) diff --git a/tests/configfiles/pyproject.toml b/tests/configfiles/pyproject.toml index 67a7a1b..9e04963 100644 --- a/tests/configfiles/pyproject.toml +++ b/tests/configfiles/pyproject.toml @@ -4,28 +4,28 @@ build-backend = "flit_core.buildapi" [project] name = "tomli" -version = "2.0.1" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT +version = "2.0.1" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT description = "A lil' TOML parser" authors = [ - { name = "Taneli Hukkinen", email = "hukkin@users.noreply.github.com" }, + { name = "Taneli Hukkinen", email = "hukkin@users.noreply.github.com" }, ] license = { file = "LICENSE" } requires-python = ">=3.7" readme = "README.md" classifiers = [ - "License :: OSI Approved :: MIT License", - "Operating System :: MacOS", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Software Development :: Libraries :: Python Modules", - "Typing :: Typed", + "License :: OSI Approved :: MIT License", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", ] keywords = ["toml"] @@ -34,6 +34,10 @@ keywords = ["toml"] "Changelog" = "https://github.com/hukkin/tomli/blob/master/CHANGELOG.md" [tool.pytoolconfig] formatter = "black" +[tool.pytoolconfig2] +option2 = true +option1 = false +option3 = "alternate" [tool.fall_through] foo_other = "ba" [tool.isort] @@ -50,16 +54,9 @@ profile = "black" [project.optional-dependencies] -validation = [ - "pydantic>=1.7.4", -] -global = [ - "appdirs>=1.4.4", -] -doc = [ - "tabulate>=0.8.9", - "sphinx>=4.5.0", -] +validation = ["pydantic>=1.7.4"] +global = ["appdirs>=1.4.4"] +doc = ["tabulate>=0.8.9", "sphinx>=4.5.0"] [tool.tox] legacy_tox_ini = ''' [tox] @@ -135,12 +132,12 @@ source = ['tomli'] [tool.coverage.report] # Regexes for lines to exclude from consideration exclude_lines = [ - # Re-enable the standard pragma (with extra strictness) - '# pragma: no cover\b', - # Code for static type checkers - 'if TYPE_CHECKING:', - # Scripts - 'if __name__ == .__main__.:', + # Re-enable the standard pragma (with extra strictness) + '# pragma: no cover\b', + # Code for static type checkers + 'if TYPE_CHECKING:', + # Scripts + 'if __name__ == .__main__.:', ] diff --git a/tests/test_enum.py b/tests/test_enum.py new file mode 100644 index 0000000..378df37 --- /dev/null +++ b/tests/test_enum.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass +from enum import Enum + +from pytoolconfig.pytoolconfig import PyToolConfig + + +class Demo(Enum): + DISABLED = False + ENABLED = True + ALT = "alternate" + + +@dataclass +class EnumModel: + option1: Demo = Demo.DISABLED + option2: Demo = Demo.DISABLED + option3: Demo = Demo.DISABLED + + +def test_simple(cwd): + config = PyToolConfig("pytoolconfig2", cwd, EnumModel) + result = config.parse() + assert result.option1 == Demo.DISABLED + assert result.option2 == Demo.ENABLED + assert result.option3 == Demo.ALT