Skip to content

Commit d317556

Browse files
enhance: improve Struct class with comprehensive dict-like interface
- Add __contains__ and __len__ methods for better dict compatibility - Add comprehensive docstrings to all dict-like methods - Include comprehensive test suite with arrange-act-assert pattern - Update documentation with examples of new usage patterns - Support mixed dot/bracket notation access for flexibility
1 parent a61c9b5 commit d317556

File tree

3 files changed

+176
-7
lines changed

3 files changed

+176
-7
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,18 @@ def train(
101101
:param config_file: path to YAML configuration file
102102
"""
103103
config = load_config_from_yaml(config_file)
104+
104105
# Access config values using dot notation
105106
print(config.data.num_classes) # prints: 80
107+
108+
# Or use dictionary-style access
109+
print(config["data"]["num_classes"]) # prints: 80
110+
111+
# Mix both styles as needed
112+
print(config.data["max_instances"]) # prints: 65
113+
114+
# Use .get() for safe access with defaults
115+
batch_size = config.get("training", {}).get("batch_size", 32)
106116
```
107117

108118
We can now call this like so:

func_to_script/config_loader.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,38 @@ def __init__(self, **kwargs):
1010

1111
def __repr__(self):
1212
return str(self.__dict__)
13-
14-
# Make it behave like a dictionary
13+
14+
# Dictionary-like interface methods
1515
def __iter__(self):
16+
"""Allow iteration over keys like a dictionary."""
1617
return iter(self.__dict__)
17-
18+
1819
def __getitem__(self, key):
20+
"""Allow bracket notation access like a dictionary."""
1921
return self.__dict__[key]
20-
22+
23+
def __contains__(self, key):
24+
"""Support 'in' operator to check if key exists."""
25+
return key in self.__dict__
26+
27+
def __len__(self):
28+
"""Return the number of attributes."""
29+
return len(self.__dict__)
30+
2131
def items(self):
32+
"""Return key-value pairs like dict.items()."""
2233
return self.__dict__.items()
23-
34+
2435
def keys(self):
36+
"""Return keys like dict.keys()."""
2537
return self.__dict__.keys()
26-
38+
2739
def values(self):
40+
"""Return values like dict.values()."""
2841
return self.__dict__.values()
29-
42+
3043
def get(self, key, default=None):
44+
"""Get value with optional default like dict.get()."""
3145
return self.__dict__.get(key, default)
3246

3347

test/test_config_loader.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import tempfile
2+
import os
3+
from func_to_script.config_loader import Struct, load_config_from_yaml
4+
5+
6+
def test_struct_dict_like_access():
7+
"""Test that Struct supports dictionary-style access"""
8+
# Arrange
9+
struct = Struct(name="test", value=42, nested={"inner": "data"})
10+
11+
# Act & Assert - __getitem__
12+
assert struct["name"] == "test"
13+
assert struct["value"] == 42
14+
assert struct["nested"]["inner"] == "data"
15+
16+
# Act & Assert - dot notation still works
17+
assert struct.name == "test"
18+
assert struct.value == 42
19+
assert struct.nested.inner == "data"
20+
21+
22+
def test_struct_iteration():
23+
"""Test that Struct can be iterated over like a dict"""
24+
# Arrange
25+
struct = Struct(a=1, b=2, c=3)
26+
27+
# Act
28+
keys = list(struct)
29+
struct_keys = set(struct.keys())
30+
struct_values = set(struct.values())
31+
struct_items = set(struct.items())
32+
33+
# Assert
34+
assert set(keys) == {"a", "b", "c"}
35+
assert struct_keys == {"a", "b", "c"}
36+
assert struct_values == {1, 2, 3}
37+
assert struct_items == {("a", 1), ("b", 2), ("c", 3)}
38+
39+
40+
def test_struct_get_method():
41+
"""Test the get method with default values"""
42+
# Arrange
43+
struct = Struct(existing="value")
44+
45+
# Act
46+
existing_value = struct.get("existing")
47+
missing_value_default = struct.get("missing")
48+
missing_value_custom = struct.get("missing", "default")
49+
50+
# Assert
51+
assert existing_value == "value"
52+
assert missing_value_default is None
53+
assert missing_value_custom == "default"
54+
55+
56+
def test_struct_nested_dict_behavior():
57+
"""Test that nested structs also have dict-like behavior"""
58+
# Arrange
59+
struct = Struct(
60+
level1={
61+
"level2": {
62+
"level3": "deep_value"
63+
},
64+
"simple": "value"
65+
}
66+
)
67+
68+
# Act & Assert - mixed access patterns
69+
assert struct["level1"]["level2"]["level3"] == "deep_value"
70+
assert struct.level1["simple"] == "value"
71+
assert struct["level1"].level2.level3 == "deep_value"
72+
73+
74+
def test_yaml_config_dict_access():
75+
"""Test that YAML-loaded configs have dict-like access"""
76+
# Arrange
77+
yaml_content = """
78+
data:
79+
num_classes: 80
80+
max_instances: 65
81+
image_size: 640
82+
training:
83+
batch_size: 32
84+
learning_rate: 0.001
85+
"""
86+
87+
# Act
88+
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
89+
f.write(yaml_content)
90+
f.flush()
91+
92+
try:
93+
config = load_config_from_yaml(f.name)
94+
95+
# Assert - dot notation access
96+
assert config.data.num_classes == 80
97+
assert config.training.batch_size == 32
98+
99+
# Assert - dict-style access
100+
assert config["data"]["num_classes"] == 80
101+
assert config["training"]["batch_size"] == 32
102+
103+
# Assert - mixed access
104+
assert config.data["max_instances"] == 65
105+
assert config["training"].learning_rate == 0.001
106+
107+
# Assert - get method
108+
assert config.get("data").num_classes == 80
109+
assert config.get("missing", "default") == "default"
110+
111+
# Assert - iteration
112+
top_level_keys = set(config.keys())
113+
assert top_level_keys == {"data", "training"}
114+
115+
finally:
116+
os.unlink(f.name)
117+
118+
119+
def test_struct_contains_and_len():
120+
"""Test __contains__ and __len__ methods"""
121+
# Arrange
122+
struct = Struct(a=1, b=2, c=3)
123+
124+
# Act & Assert - __contains__
125+
assert "a" in struct
126+
assert "b" in struct
127+
assert "missing" not in struct
128+
129+
# Act & Assert - __len__
130+
assert len(struct) == 3
131+
132+
133+
def test_struct_repr():
134+
"""Test that __repr__ works correctly"""
135+
# Arrange
136+
struct = Struct(a=1, b="test")
137+
138+
# Act
139+
repr_str = repr(struct)
140+
141+
# Assert
142+
assert "a" in repr_str
143+
assert "1" in repr_str
144+
assert "b" in repr_str
145+
assert "test" in repr_str

0 commit comments

Comments
 (0)