Skip to content

Commit f399f74

Browse files
committed
feat: Support multiple value types (string, number, boolean, null)
- Update Value class to support multiple data types - Modify ValueBackend and SimpleValueBackend to handle new types - Add test cases for type validation and handling - Improve docstrings with type information
1 parent a2e061b commit f399f74

File tree

6 files changed

+83
-43
lines changed

6 files changed

+83
-43
lines changed

helm_values_manager/backends/base.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from abc import ABC, abstractmethod
8-
from typing import Dict
8+
from typing import Dict, Union
99

1010

1111
class ValueBackend(ABC):
@@ -60,34 +60,37 @@ def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:
6060
raise ValueError(f"Invalid auth type: {auth_config['type']}. " f"Must be one of: {', '.join(valid_types)}")
6161

6262
@abstractmethod
63-
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
63+
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:
6464
"""
65-
Get a value from storage.
65+
Get a value from the backend.
6666
6767
Args:
68-
path: The configuration path
69-
environment: The environment name
70-
resolve: If True, resolve any secret references to their actual values.
71-
If False, return the raw value which may be a secret reference.
68+
path: The configuration path (e.g., "app.replicas")
69+
environment: The environment name (e.g., "dev", "prod")
70+
resolve: Whether to resolve any secret references
7271
7372
Returns:
74-
str: The value (resolved or raw depending on resolve parameter)
73+
The value from the backend, can be a string, number, boolean, or None
74+
75+
Raises:
76+
ValueError: If the value doesn't exist
77+
RuntimeError: If backend operation fails
7578
"""
7679
pass
7780

7881
@abstractmethod
79-
def set_value(self, path: str, environment: str, value: str) -> None:
80-
"""Set a value in the storage backend.
82+
def set_value(self, path: str, environment: str, value: Union[str, int, float, bool, None]) -> None:
83+
"""
84+
Set a value in the backend.
8185
8286
Args:
8387
path: The configuration path (e.g., "app.replicas")
8488
environment: The environment name (e.g., "dev", "prod")
85-
value: The value to store. Must be a string.
89+
value: The value to store, can be a string, number, boolean, or None
8690
8791
Raises:
88-
ValueError: If the value is invalid
89-
ConnectionError: If there's an error connecting to the backend
90-
PermissionError: If there's an authentication or authorization error
92+
ValueError: If the value is not a string, number, boolean, or None
93+
RuntimeError: If backend operation fails
9194
"""
9295
pass
9396

helm_values_manager/backends/simple.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
This module provides a simple in-memory backend for storing non-sensitive values.
55
"""
66

7-
from typing import Dict
7+
from typing import Dict, Union
88

99
from helm_values_manager.backends.base import ValueBackend
1010

@@ -19,24 +19,23 @@ class SimpleValueBackend(ValueBackend):
1919
def __init__(self) -> None:
2020
"""Initialize an empty in-memory storage."""
2121
super().__init__({"type": "direct"}) # Simple backend doesn't need auth
22-
self._storage: Dict[str, str] = {}
22+
self._storage: Dict[str, Union[str, int, float, bool, None]] = {}
2323

2424
def _get_storage_key(self, path: str, environment: str) -> str:
2525
"""Generate a unique storage key."""
2626
return f"{path}:{environment}"
2727

28-
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
28+
def get_value(self, path: str, environment: str, resolve: bool = False) -> Union[str, int, float, bool, None]:
2929
"""
3030
Get a value from the in-memory storage.
3131
3232
Args:
3333
path: The configuration path (e.g., "app.replicas")
3434
environment: The environment name (e.g., "dev", "prod")
35-
resolve: If True, resolve any secret references to their actual values.
36-
If False, return the raw value which may be a secret reference.
35+
resolve: Whether to resolve any secret references
3736
3837
Returns:
39-
str: The value (resolved or raw depending on resolve parameter)
38+
The value from the backend, can be a string, number, boolean, or None
4039
4140
Raises:
4241
ValueError: If the value doesn't exist
@@ -46,20 +45,21 @@ def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
4645
raise ValueError(f"No value found for {path} in {environment}")
4746
return self._storage[key]
4847

49-
def set_value(self, path: str, environment: str, value: str) -> None:
48+
def set_value(self, path: str, environment: str, value: Union[str, int, float, bool, None]) -> None:
5049
"""
5150
Set a value in the in-memory storage.
5251
5352
Args:
5453
path: The configuration path (e.g., "app.replicas")
5554
environment: The environment name (e.g., "dev", "prod")
56-
value: The value to store
55+
value: The value to store, can be a string, number, boolean, or None
5756
5857
Raises:
59-
ValueError: If the value is not a string
58+
ValueError: If the value is not a string, number, boolean, or None
6059
"""
61-
if not isinstance(value, str):
62-
raise ValueError("Value must be a string")
60+
if not isinstance(value, (str, int, float, bool, type(None))):
61+
raise ValueError("Value must be a string, number, boolean, or None")
62+
6363
key = self._get_storage_key(path, environment)
6464
self._storage[key] = value
6565

helm_values_manager/models/value.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"""
77

88
from dataclasses import dataclass
9-
from typing import Any, Dict
9+
from typing import Any, Dict, Union
1010

1111
from helm_values_manager.backends.base import ValueBackend
1212
from helm_values_manager.utils.logger import HelmLogger
@@ -31,19 +31,17 @@ def __post_init__(self):
3131
"""Post-initialization validation and logging."""
3232
HelmLogger.debug("Created Value instance for path %s in environment %s", self.path, self.environment)
3333

34-
def get(self, resolve: bool = False) -> str:
34+
def get(self, resolve: bool = False) -> Union[str, int, float, bool, None]:
3535
"""
36-
Get the value.
36+
Get the value using the backend.
3737
3838
Args:
39-
resolve (bool): If True, resolve any secret references to their actual values.
40-
If False, return the raw value which may be a secret reference.
39+
resolve: Whether to resolve any secret references
4140
4241
Returns:
43-
str: The value (resolved or raw depending on resolve parameter)
42+
The value from the backend, can be a string, number, boolean, or None
4443
4544
Raises:
46-
ValueError: If value doesn't exist
4745
RuntimeError: If backend operation fails
4846
"""
4947
try:
@@ -54,19 +52,19 @@ def get(self, resolve: bool = False) -> str:
5452
HelmLogger.error("Failed to get value for path %s in environment %s: %s", self.path, self.environment, e)
5553
raise
5654

57-
def set(self, value: str) -> None:
55+
def set(self, value: Union[str, int, float, bool, None]) -> None:
5856
"""
5957
Set the value using the backend.
6058
6159
Args:
62-
value: The value to store, can be a raw value or a secret reference
60+
value: The value to store, can be a raw value, a secret reference, or None
6361
6462
Raises:
65-
ValueError: If value is not a string
63+
ValueError: If value is not a string, number, boolean, or None
6664
RuntimeError: If backend operation fails
6765
"""
68-
if not isinstance(value, str):
69-
raise ValueError("Value must be a string")
66+
if not isinstance(value, (str, int, float, bool, type(None))):
67+
raise ValueError("Value must be a string, number, boolean, or None")
7068

7169
try:
7270
self._backend.set_value(self.path, self.environment, value)

tests/unit/backends/test_simple.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def test_set_and_get_value(backend):
2424

2525

2626
def test_set_invalid_value_type(backend):
27-
"""Test setting a non-string value."""
28-
with pytest.raises(ValueError, match="Value must be a string"):
29-
backend.set_value("app.replicas", "dev", 3)
27+
"""Test setting an invalid value type."""
28+
with pytest.raises(ValueError, match="Value must be a string, number, boolean, or None"):
29+
backend.set_value("app.replicas", "dev", {"key": "value"}) # Dictionary is not a valid type
3030

3131

3232
def test_remove_value(backend):

tests/unit/models/test_helm_values_config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ def test_get_value_nonexistent_value():
7777
assert config.get_value(path, "dev") is None
7878

7979

80+
def test_get_value_returns_none():
81+
"""Test getting a value when value_obj.get() returns None."""
82+
config = HelmValuesConfig()
83+
path = "app.config.key1"
84+
environment = "dev"
85+
86+
# Add path and set None value
87+
config.add_config_path(path, description="Test config")
88+
config.set_value(path, environment, None)
89+
90+
# Verify that get_value returns None
91+
assert config.get_value(path, environment) is None
92+
93+
8094
def test_set_value_without_path():
8195
"""Test setting a value without first adding its path."""
8296
config = HelmValuesConfig()

tests/unit/models/test_value.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,36 @@ def test_set_value(mock_backend):
4949
mock_backend.set_value.assert_called_once_with("app.replicas", "dev", "3")
5050

5151

52+
def test_set_valid_types(mock_backend):
53+
"""Test setting various valid value types."""
54+
value = Value(path="app.replicas", environment="dev", _backend=mock_backend)
55+
56+
# Test string
57+
value.set("test-value")
58+
mock_backend.set_value.assert_called_with("app.replicas", "dev", "test-value")
59+
60+
# Test integer
61+
value.set(42)
62+
mock_backend.set_value.assert_called_with("app.replicas", "dev", 42)
63+
64+
# Test float
65+
value.set(3.14)
66+
mock_backend.set_value.assert_called_with("app.replicas", "dev", 3.14)
67+
68+
# Test boolean
69+
value.set(True)
70+
mock_backend.set_value.assert_called_with("app.replicas", "dev", True)
71+
72+
# Test None
73+
value.set(None)
74+
mock_backend.set_value.assert_called_with("app.replicas", "dev", None)
75+
76+
5277
def test_set_invalid_type(mock_backend):
53-
"""Test setting a non-string value."""
78+
"""Test setting an invalid value type."""
5479
value = Value(path="app.replicas", environment="dev", _backend=mock_backend)
55-
with pytest.raises(ValueError, match="Value must be a string"):
56-
value.set(3)
80+
with pytest.raises(ValueError, match="Value must be a string, number, boolean, or None"):
81+
value.set({"key": "value"}) # Dictionary is not a valid type
5782

5883

5984
def test_to_dict(mock_backend):

0 commit comments

Comments
 (0)