Skip to content

Commit e188cba

Browse files
committed
Update config_store to use generated exceptions
Also, use larger buffer size rather than doing unecessary resizes and address a few other comments from the PR.
1 parent 960d291 commit e188cba

3 files changed

Lines changed: 24 additions & 131 deletions

File tree

examples/config-store/config-store.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,6 @@ def test_get(store_name, key, default=None):
5252
return {"value": value}
5353

5454

55-
@app.route("/get_with_initial_buf_len/<store_name>/<key>/<initial_buf_len:int>")
56-
@handle_request
57-
def test_get_with_initial_buf_len(store_name, key, initial_buf_len):
58-
"""Proxy endpoint to test get with custom initial_buf_len using raw API."""
59-
config = ConfigStore.open(store_name)
60-
# Use _get_raw to test buffer sizing without automatic retry
61-
value = config._get_raw(key, initial_buf_len)
62-
return {"value": value}
63-
64-
6555
@app.route("/contains/<store_name>/<key>")
6656
@handle_request
6757
def test_contains(store_name, key):

fastly_compute/config_store.py

Lines changed: 19 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,10 @@
1212
"""
1313

1414
from wit_world.imports import config_store as wit_config_store
15-
from wit_world.imports.types import Error_BufferLen, Error_InvalidArgument, OpenError
1615

17-
from fastly_compute.exceptions import FastlyError, remap_wit_errors
18-
19-
20-
class ConfigStoreError(FastlyError):
21-
"""Base exception for all config store errors."""
22-
23-
24-
class ConfigStoreNotFoundError(ConfigStoreError):
25-
"""The requested config store does not exist."""
26-
27-
28-
class ConfigStoreInvalidNameError(ConfigStoreError):
29-
"""The config store name is invalid."""
30-
31-
32-
class ConfigStoreBufferTooSmallError(ConfigStoreError):
33-
"""The buffer provided was too small for the config value.
34-
35-
The required buffer size is available in the `required_size` attribute.
36-
"""
37-
38-
def __init__(self, error: Error_BufferLen):
39-
"""Initialize with the WIT error containing the required size.
40-
41-
:param error: The WIT Error_BufferLen dataclass with the required size
42-
"""
43-
self.required_size = error.value
44-
super().__init__(
45-
f"Buffer too small for config store value. Required size: {self.required_size} bytes"
46-
)
47-
48-
49-
class ConfigStoreInvalidKeyError(ConfigStoreError):
50-
"""The key provided is invalid."""
16+
# The maximum value for a u64, used to signal that we don't want to cap
17+
# the length of values returned by the host.
18+
_MAX_U64 = 0xFFFFFFFFFFFFFFFF
5119

5220

5321
class ConfigStore:
@@ -67,21 +35,14 @@ def __init__(self, store: wit_config_store.Store):
6735
self._store = store
6836

6937
@classmethod
70-
@remap_wit_errors(
71-
{
72-
OpenError.NOT_FOUND: ConfigStoreNotFoundError,
73-
OpenError.INVALID_SYNTAX: ConfigStoreInvalidNameError,
74-
OpenError.NAME_TOO_LONG: ConfigStoreInvalidNameError,
75-
OpenError.RESERVED: ConfigStoreInvalidNameError,
76-
}
77-
)
7838
def open(cls, name: str) -> "ConfigStore":
7939
"""Open a config store by name.
8040
8141
:param name: The name of the config store
8242
:return: ConfigStore instance
83-
:raises ConfigStoreNotFoundError: If the config store doesn't exist
84-
:raises ConfigStoreInvalidNameError: If the name is invalid or too long
43+
:raises ~fastly_compute.exceptions.types.open_error.NotFound: If the config store doesn't exist
44+
:raises ~fastly_compute.exceptions.types.open_error.InvalidSyntax: If the name is invalid
45+
:raises ~fastly_compute.exceptions.types.open_error.NameTooLong: If the name is too long
8546
8647
Example::
8748
@@ -90,91 +51,39 @@ def open(cls, name: str) -> "ConfigStore":
9051
store = wit_config_store.Store.open(name)
9152
return cls(store)
9253

93-
def get(
94-
self, key: str, default: str | None = None, initial_buf_len: int = 1024
95-
) -> str | None:
96-
"""Get a configuration value with automatic buffer resizing.
97-
98-
This method automatically handles buffer sizing for config store values.
99-
If the initial buffer is too small, it will automatically retry once with
100-
the exact size required by the host.
54+
def get(self, key: str, default: str | None = None) -> str | None:
55+
"""Get a configuration value.
10156
10257
:param key: The configuration key
10358
:param default: Default value if key not found
104-
:param initial_buf_len: Initial buffer size hint in bytes (default: 1024).
105-
This can be tuned for performance if you know your values are typically
106-
larger or smaller than 1KB.
10759
:return: Configuration value or default if not found
108-
:raises ConfigStoreInvalidKeyError: If the key is invalid
109-
:raises ConfigStoreBufferTooSmallError: If the value is too large even after
110-
automatic resizing (should not happen unless there's a host-level bug)
60+
:raises ~fastly_compute.exceptions.types.error.InvalidArgument: If the key is invalid
11161
11262
Example::
11363
11464
config = ConfigStore.open("app-config")
115-
# Basic usage with default buffer size
11665
api_url = config.get("api_url", "https://api.example.com")
117-
118-
# Optimize for large values by using a larger initial buffer
119-
large_value = config.get("large_config", initial_buf_len=16384)
12066
"""
121-
# Try with the initial buffer size
122-
try:
123-
result = self._get_raw(key, initial_buf_len)
124-
except ConfigStoreBufferTooSmallError as e:
125-
# Buffer was too small. The exception contains the exact required size.
126-
# Retry ONCE with the exact size needed. If this second attempt fails,
127-
# let the exception propagate (no infinite recursion).
128-
result = self._get_raw(key, e.required_size)
129-
67+
result = self._store.get(key, _MAX_U64)
13068
if result is None:
13169
result = default
13270

13371
return result
13472

135-
@remap_wit_errors(
136-
{
137-
Error_BufferLen: ConfigStoreBufferTooSmallError,
138-
Error_InvalidArgument: ConfigStoreInvalidKeyError,
139-
}
140-
)
141-
def _get_raw(self, key: str, max_len: int) -> str | None:
142-
"""Internal method to get a value with a specific buffer size.
143-
144-
:param key: The configuration key
145-
:param max_len: Maximum buffer length
146-
:return: Configuration value or None if not found
147-
:raises ConfigStoreBufferTooSmallError: If the buffer is too small
148-
:raises ConfigStoreInvalidKeyError: If the key is invalid
149-
"""
150-
return self._store.get(key, max_len)
151-
15273
def contains(self, key: str) -> bool:
15374
"""Check if a key exists in the config store.
15475
155-
Uses a zero-length buffer to efficiently check for key existence without
156-
retrieving the value.
157-
15876
:param key: The configuration key
15977
:return: True if the key exists, False otherwise
160-
:raises ConfigStoreInvalidKeyError: If the key is invalid
78+
:raises ~fastly_compute.exceptions.types.error.InvalidArgument: If the key is invalid
16179
16280
Example::
16381
16482
config = ConfigStore.open("app-config")
16583
if config.contains("feature_flag"):
16684
print("Feature flag exists")
16785
"""
168-
try:
169-
# Use a 0-length buffer to check existence without retrieving the value
170-
result = self._get_raw(key, 0)
171-
return result is not None
172-
except ConfigStoreBufferTooSmallError:
173-
# Buffer too small means the key exists with a non-empty value
174-
return True
175-
except ConfigStoreInvalidKeyError:
176-
# Re-raise invalid key errors
177-
raise
86+
return self.get(key) is not None
17887

17988
def close(self) -> None:
18089
"""Explicitly close the config store, releasing its resources.
@@ -196,5 +105,10 @@ def __enter__(self) -> "ConfigStore":
196105
return self
197106

198107
def __exit__(self, exc_type, exc_val, exc_tb):
199-
"""Context manager exit."""
200-
self._store.__exit__(exc_type, exc_val, exc_tb)
108+
"""Context manager exit.
109+
110+
Use of the context manager will free up the underlying host resource on
111+
exit. Referencing the resource after context manager exit will result in
112+
a trap.
113+
"""
114+
self.close()

tests/test_config_store.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class TestConfigStore(ViceroyTestBase):
1919
"whitespace": " ",
2020
"special_chars": "!@#$%^&*()",
2121
"unicode": "Hello 世界 🌍",
22-
"large_value": "x" * 100, # 100 byte value for buffer tests
22+
"large_value": "x" * 8000, # 8000 byte value for buffer tests
2323
},
2424
}
2525
}
@@ -52,7 +52,7 @@ def test_open_nonexistent_store(self):
5252
response = self.get("/get/nonexistent/key")
5353
assert response.status_code == 500
5454
data = response.json()
55-
assert data["error_type"] == "ConfigStoreNotFoundError"
55+
assert data["error_type"] == "NotFound"
5656

5757
def test_get_string_value(self):
5858
"""Test getting a string value."""
@@ -84,20 +84,9 @@ def test_unicode_support(self):
8484
"""Test handling of unicode characters."""
8585
self.assert_get_value("test-config", "unicode", "Hello 世界 🌍")
8686

87-
def test_automatic_buffer_resize_for_large_values(self):
88-
"""Test that automatic buffer resizing handles large values."""
89-
# The large_value is 100 bytes. With automatic resizing, this should work
90-
# even with a small initial buffer (default 1024 bytes).
91-
self.assert_get_value("test-config", "large_value", "x" * 100)
92-
93-
def test_buffer_too_small_with_raw_api(self):
94-
"""Test that _get_raw API can still raise buffer errors when called directly."""
95-
# The /get_with_initial_buf_len endpoint uses a small buffer without retry
96-
# to test that buffer errors can still occur at the lower level
97-
response = self.get("/get_with_initial_buf_len/test-config/large_value/10")
98-
assert response.status_code == 500
99-
data = response.json()
100-
assert data["error_type"] == "ConfigStoreBufferTooSmallError"
87+
def test_large_values(self):
88+
"""Test that large values are handled correctly."""
89+
self.assert_get_value("test-config", "large_value", "x" * 8000)
10190

10291
def test_contains_existing_key(self):
10392
"""Test that contains returns True for existing keys."""

0 commit comments

Comments
 (0)