Skip to content
This repository has been archived by the owner on Dec 5, 2023. It is now read-only.

Commit

Permalink
Fixing loading var types utilizing typehints (#39)
Browse files Browse the repository at this point in the history
Fixing loading var types utilizing typehints
  • Loading branch information
stephen-bunn committed Oct 4, 2019
2 parents 065fbeb + a4e353b commit abf1f3c
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 19 deletions.
1 change: 1 addition & 0 deletions news/38.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allowing Python 3 typehints to be considered as the config var's type just like the var's ``type`` kwarg
1 change: 1 addition & 0 deletions news/collections-deprecation-warning.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixing access to collections.abc for Iterable deprecation warning
38 changes: 20 additions & 18 deletions src/file_config/_file_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,51 +298,52 @@ def _build(config_cls, dictionary, validate=False): # noqa
entry = var.metadata[CONFIG_KEY]
arg_key = entry.name if entry.name else var.name
arg_default = var.default if var.default is not None else None
arg_type = entry.type if entry.type else var.type

if callable(entry.decoder):
kwargs[var.name] = entry.decoder(dictionary.get(arg_key, arg_default))
continue

if is_array_type(entry.type):
if is_typing_type(entry.type) and len(entry.type.__args__) > 0:
nested_type = entry.type.__args__[0]
if is_array_type(arg_type):
if is_typing_type(arg_type) and len(arg_type.__args__) > 0:
nested_type = arg_type.__args__[0]
if is_config_type(nested_type):
kwargs[var.name] = [
_build(nested_type, item)
for item in dictionary.get(arg_key, [])
]
else:
kwargs[var.name] = typecast(entry.type, dictionary.get(arg_key, []))
elif is_object_type(entry.type):
kwargs[var.name] = typecast(arg_type, dictionary.get(arg_key, []))
elif is_object_type(arg_type):
item = dictionary.get(arg_key, {})
if is_typing_type(entry.type) and len(entry.type.__args__) == 2:
(_, value_type) = entry.type.__args__
if is_typing_type(arg_type) and len(arg_type.__args__) == 2:
(_, value_type) = arg_type.__args__
kwargs[var.name] = {
key: _build(value_type, value)
if is_config_type(value_type)
else typecast(value_type, value)
for (key, value) in item.items()
}
else:
kwargs[var.name] = typecast(entry.type, item)
elif is_config_type(entry.type):
kwargs[var.name] = typecast(arg_type, item)
elif is_config_type(arg_type):
if arg_key not in dictionary:
# if the default value for a nested config is the nested config class
# then build the empty state of the nested config
if is_config_type(arg_default) and entry.type == arg_default:
kwargs[var.name] = _build(entry.type, {})
if is_config_type(arg_default) and arg_type == arg_default:
kwargs[var.name] = _build(arg_type, {})
else:
kwargs[var.name] = arg_default
else:
kwargs[var.name] = _build(
entry.type, dictionary.get(arg_key, arg_default)
arg_type, dictionary.get(arg_key, arg_default)
)
else:
if arg_key not in dictionary:
kwargs[var.name] = arg_default
else:
kwargs[var.name] = typecast(
entry.type, dictionary.get(arg_key, arg_default)
arg_type, dictionary.get(arg_key, arg_default)
)

return config_cls(**kwargs)
Expand Down Expand Up @@ -371,31 +372,32 @@ def _dump(config_instance, dict_type=OrderedDict):
entry = var.metadata[CONFIG_KEY]
dump_key = entry.name if entry.name else var.name
dump_default = var.default if var.default else None
dump_type = entry.type if entry.type else var.type

if callable(entry.encoder):
result[dump_key] = entry.encoder(
getattr(config_instance, var.name, dump_default)
)
continue

if is_array_type(entry.type):
if is_array_type(dump_type):
items = getattr(config_instance, var.name, [])
if items is not None:
result[dump_key] = [
(_dump(item, dict_type=dict_type) if is_config(item) else item)
for item in items
]
elif is_enum_type(entry.type):
elif is_enum_type(dump_type):
dump_value = getattr(config_instance, var.name, dump_default)
result[dump_key] = (
dump_value.value if dump_value in entry.type else dump_value
dump_value.value if dump_value in dump_type else dump_value
)
elif is_bytes_type(entry.type):
elif is_bytes_type(dump_type):
result[dump_key] = encode_bytes(
getattr(config_instance, var.name, dump_default)
)
else:
if is_config_type(entry.type):
if is_config_type(dump_type):
result[dump_key] = _dump(
getattr(config_instance, var.name, {}), dict_type=dict_type
)
Expand Down
5 changes: 4 additions & 1 deletion src/file_config/schema_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,10 @@ def _build_var(var, property_path=None):
schema["title"] = entry.title
if isinstance(entry.description, str):
schema["description"] = entry.description
if isinstance(entry.examples, collections.Iterable) and len(entry.examples) > 0:
if (
isinstance(entry.examples, collections.abc.Iterable)
and len(entry.examples) > 0
):
schema["examples"] = entry.examples

# handle typing.Union types by simply using the "anyOf" key
Expand Down
36 changes: 36 additions & 0 deletions tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,39 @@ class InnerConfig:
)
assert yaml_cfg.inner.foo == "Default"


def test_regression_issue38():
"""Test that utilizing typehints vs. var type kwarg handles serailization the same.
.. note:: Refer to https://github.com/stephen-bunn/file-config/issues/38
"""

@file_config.config
class TestCase1(object):
@file_config.config
class Nested(object):
name = file_config.var(str)

nests = file_config.var(List[Nested])

@file_config.config
class TestCase2(object):
@file_config.config
class Nested(object):
name: str = file_config.var()

nests: List[Nested] = file_config.var()

loadable_json = """{"nests": [{"name": "hello"}, {"name": "world"}]}"""

test_1 = TestCase1.loads_json(loadable_json)
test_2 = TestCase2.loads_json(loadable_json)
assert all(
isinstance(nest, TestCase1.Nested) and isinstance(nest.name, str)
for nest in test_1.nests
)
assert all(
isinstance(nest, TestCase2.Nested) and isinstance(nest.name, str)
for nest in test_2.nests
)
assert test_1.dumps_json() == test_2.dumps_json()

0 comments on commit abf1f3c

Please sign in to comment.