diff --git a/example/config_types.yaml b/example/config_types.yaml new file mode 100644 index 0000000..ea9281e --- /dev/null +++ b/example/config_types.yaml @@ -0,0 +1,14 @@ +NONE: null +BOOL: True +INT: 42 +FLOAT: 3.14 +BINARY: !!binary YmluYXJ5 +DATE: 2020-03-05 +DATETIME: 2020-03-05T13:07:56Z +PAIRS: !!pairs [ one: 1, two: 2 ] +SET: !!set {1,2,3} +STRING: hello +# Check None overwrite +STR_NONE: hello +# Check overwrite by None +NONE_STR: null diff --git a/setup.py b/setup.py index f3d851e..0d588b4 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="yacs", - version="0.1.6", + version="0.2.0", author="Ross Girshick", author_email="ross.girshick@gmail.com", description="Yet Another Configuration System", diff --git a/yacs/config.py b/yacs/config.py index e39898d..829496d 100644 --- a/yacs/config.py +++ b/yacs/config.py @@ -21,6 +21,7 @@ """ import copy +import datetime import io import logging import os @@ -45,10 +46,13 @@ _FILE_TYPES = (io.IOBase,) # CfgNodes can only contain a limited set of valid types -_VALID_TYPES = {tuple, list, str, int, float, bool} -# py2 allow for str and unicode +_VALID_TYPES = {tuple, list, str, int, float, bool, datetime.date, datetime.datetime, set, type(None)} +# py2 allow for unicode and long if _PY2: - _VALID_TYPES = _VALID_TYPES.union({unicode}) # noqa: F821 + _VALID_TYPES = _VALID_TYPES.union({unicode, long}) # noqa: F821 +# py3 allow for bytes (py2 str) +else: + _VALID_TYPES = _VALID_TYPES.union({bytes}) # Utilities for importing modules from file paths if _PY2: @@ -482,6 +486,9 @@ def _check_and_coerce_cfg_value_type(replacement, original, key, full_key): the right type. The type is correct if it matches exactly or is one of a few cases in which the type can be easily coerced. """ + if original is None or replacement is None: + return replacement + original_type = type(original) replacement_type = type(replacement) diff --git a/yacs/tests.py b/yacs/tests.py index 6748214..c2a6ef4 100644 --- a/yacs/tests.py +++ b/yacs/tests.py @@ -1,6 +1,9 @@ import logging import tempfile import unittest +from datetime import date, datetime + +import yaml import yacs.config from yacs.config import CfgNode as CN @@ -196,8 +199,13 @@ def test_nonexistant_key_from_list(self): cfg.merge_from_list(opts) def test_load_cfg_invalid_type(self): - # FOO.BAR.QUUX will have type None, which is not allowed - cfg_string = "FOO:\n BAR:\n QUUX:" + class CustomClass(yaml.YAMLObject): + """A custom class that yaml.safe_load can load.""" + yaml_loader = yaml.SafeLoader + yaml_tag = u'!CustomClass' + + # FOO.BAR.QUUX will have type CustomClass, which is not allowed + cfg_string = "FOO:\n BAR:\n QUUX: !CustomClass {}" with self.assertRaises(AssertionError): yacs.config.load_cfg(cfg_string) @@ -298,6 +306,23 @@ def test_new_allowed_bad(self): with self.assertRaises(KeyError): cfg.merge_from_file("example/config_new_allowed_bad.yaml") + def test_all_types(self): + cfg = CN() + cfg.NONE = None + cfg.BOOL = False + cfg.INT = 0 + cfg.FLOAT = 2.72 + cfg.BINARY = b"binary" + cfg.DATE = date(2020, 1, 1) + cfg.DATETIME = datetime(2020, 1, 1, 0, 0, 1) + cfg.PAIRS = [("zero", 0)] + cfg.SET = {1, } + cfg.STRING = "string" + cfg.STR_NONE = None + cfg.NONE_STR = "string" + + cfg.merge_from_file("example/config_types.yaml") + class TestCfgNodeSubclass(unittest.TestCase): def test_merge_cfg_from_file(self):