|
| 1 | +# Config Metadata Implementation |
| 2 | + |
| 3 | +This document describes the implementation of config metadata functionality for the DevCycle Python SDK, based on the [Java SDK implementation in PR #178](https://github.com/DevCycleHQ/java-server-sdk/pull/178). |
| 4 | + |
| 5 | +## 🎯 Overview |
| 6 | + |
| 7 | +The config metadata feature adds project information, environment details, and config versioning to the local SDK evaluation context, making it accessible to evaluation hooks for enhanced debugging and monitoring capabilities. |
| 8 | + |
| 9 | +## 📋 Implementation Summary |
| 10 | + |
| 11 | +### ✅ Core Requirements Met |
| 12 | + |
| 13 | +1. **New Data Models Created** |
| 14 | + - `ConfigMetadata` - Contains project, environment, and versioning information |
| 15 | + - `ProjectMetadata` - Project information (id, key) |
| 16 | + - `EnvironmentMetadata` - Environment information (id, key) |
| 17 | + |
| 18 | +2. **HookContext Updated** |
| 19 | + - Added `metadata: Optional[ConfigMetadata]` parameter |
| 20 | + - Added `get_metadata()` method for accessing metadata |
| 21 | + - Maintains backward compatibility with optional parameter |
| 22 | + |
| 23 | +3. **Configuration Manager Enhanced** |
| 24 | + - `EnvironmentConfigManager` now stores config metadata |
| 25 | + - Added `get_config_metadata()` method |
| 26 | + - Metadata created from API response headers and data |
| 27 | + |
| 28 | +4. **Client Interface Updated** |
| 29 | + - `DevCycleLocalClient` exposes metadata via `get_metadata()` method |
| 30 | + - Local client passes metadata to HookContext in variable evaluation |
| 31 | + - `DevCycleCloudClient` passes null metadata (maintains distinction) |
| 32 | + |
| 33 | +5. **JSON Serialization Centralized** |
| 34 | + - Created `JSONUtils` class for consistent serialization |
| 35 | + - Handles unknown properties gracefully for API compatibility |
| 36 | + - Separate configurations for config vs events |
| 37 | + |
| 38 | +## 🏗️ Architecture |
| 39 | + |
| 40 | +### Data Flow |
| 41 | + |
| 42 | +``` |
| 43 | +Config API Response → Extract Headers & Data → Create Metadata → Store in Manager → Pass to Hooks |
| 44 | +``` |
| 45 | + |
| 46 | +### Key Components |
| 47 | + |
| 48 | +1. **ConfigMetadata** (`devcycle_python_sdk/models/config_metadata.py`) |
| 49 | + - Contains ETag, Last-Modified, project, and environment information |
| 50 | + - Factory method `from_config_response()` for easy creation from API data |
| 51 | + |
| 52 | +2. **HookContext** (`devcycle_python_sdk/models/eval_hook_context.py`) |
| 53 | + - Updated to include optional metadata parameter |
| 54 | + - Provides `get_metadata()` method for hook access |
| 55 | + |
| 56 | +3. **EnvironmentConfigManager** (`devcycle_python_sdk/managers/config_manager.py`) |
| 57 | + - Stores config metadata as instance variable |
| 58 | + - Creates metadata from API response |
| 59 | + - Exposes metadata via `get_config_metadata()` |
| 60 | + |
| 61 | +4. **DevCycleLocalClient** (`devcycle_python_sdk/local_client.py`) |
| 62 | + - Exposes metadata via `get_metadata()` method |
| 63 | + - Passes metadata to HookContext in variable evaluation |
| 64 | + |
| 65 | +5. **DevCycleCloudClient** (`devcycle_python_sdk/cloud_client.py`) |
| 66 | + - Passes null metadata to HookContext (maintains separation) |
| 67 | + |
| 68 | +6. **JSONUtils** (`devcycle_python_sdk/util/json_utils.py`) |
| 69 | + - Centralized JSON serialization/deserialization |
| 70 | + - Handles unknown properties gracefully |
| 71 | + - Consistent behavior across SDK |
| 72 | + |
| 73 | +## 🔧 Usage Examples |
| 74 | + |
| 75 | +### Accessing Metadata in Local Client |
| 76 | + |
| 77 | +```python |
| 78 | +from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions |
| 79 | + |
| 80 | +client = DevCycleLocalClient("server-sdk-key", DevCycleLocalOptions()) |
| 81 | + |
| 82 | +# Get current config metadata |
| 83 | +metadata = client.get_metadata() |
| 84 | +if metadata: |
| 85 | + print(f"Project: {metadata.project.key}") |
| 86 | + print(f"Environment: {metadata.environment.key}") |
| 87 | + print(f"Config ETag: {metadata.config_etag}") |
| 88 | + print(f"Last Modified: {metadata.config_last_modified}") |
| 89 | +``` |
| 90 | + |
| 91 | +### Using Metadata in Evaluation Hooks |
| 92 | + |
| 93 | +```python |
| 94 | +from devcycle_python_sdk.models.eval_hook import EvalHook |
| 95 | +from devcycle_python_sdk.models.eval_hook_context import HookContext |
| 96 | + |
| 97 | +class MyEvalHook(EvalHook): |
| 98 | + def before(self, context: HookContext): |
| 99 | + metadata = context.get_metadata() |
| 100 | + if metadata: |
| 101 | + print(f"Evaluating variable {context.key} for project {metadata.project.key}") |
| 102 | + return context |
| 103 | + |
| 104 | + def after(self, context: HookContext, variable): |
| 105 | + metadata = context.get_metadata() |
| 106 | + if metadata: |
| 107 | + print(f"Variable {context.key} evaluated in environment {metadata.environment.key}") |
| 108 | + |
| 109 | + def error(self, context: HookContext, error): |
| 110 | + metadata = context.get_metadata() |
| 111 | + if metadata: |
| 112 | + print(f"Error evaluating {context.key} in project {metadata.project.key}") |
| 113 | + |
| 114 | + def on_finally(self, context: HookContext, variable): |
| 115 | + metadata = context.get_metadata() |
| 116 | + if metadata: |
| 117 | + print(f"Completed evaluation for {context.key}") |
| 118 | + |
| 119 | +# Add hook to client |
| 120 | +client.add_hook(MyEvalHook()) |
| 121 | +``` |
| 122 | + |
| 123 | +### Cloud vs Local Client Distinction |
| 124 | + |
| 125 | +```python |
| 126 | +# Local client - has metadata |
| 127 | +local_client = DevCycleLocalClient("server-sdk-key", DevCycleLocalOptions()) |
| 128 | +metadata = local_client.get_metadata() # Returns ConfigMetadata or None |
| 129 | + |
| 130 | +# Cloud client - no metadata (uses external API) |
| 131 | +cloud_client = DevCycleCloudClient("server-sdk-key", DevCycleCloudOptions()) |
| 132 | +# cloud_client.get_metadata() would return None (not implemented for cloud) |
| 133 | +``` |
| 134 | + |
| 135 | +## 🧪 Testing |
| 136 | + |
| 137 | +### Test Coverage |
| 138 | + |
| 139 | +The implementation includes comprehensive test coverage: |
| 140 | + |
| 141 | +1. **Unit Tests** (`test/test_config_metadata.py`) |
| 142 | + - Metadata creation and serialization |
| 143 | + - HookContext integration |
| 144 | + - Null safety and edge cases |
| 145 | + |
| 146 | +2. **Integration Tests** (`test/test_client_metadata.py`) |
| 147 | + - Local client metadata functionality |
| 148 | + - Cloud client null metadata |
| 149 | + - Config manager metadata creation |
| 150 | + |
| 151 | +3. **Standalone Tests** (`test_metadata_standalone.py`) |
| 152 | + - Complete functionality verification |
| 153 | + - No external dependencies required |
| 154 | + |
| 155 | +### Running Tests |
| 156 | + |
| 157 | +```bash |
| 158 | +# Run standalone tests (no dependencies required) |
| 159 | +python3 test_metadata_standalone.py |
| 160 | + |
| 161 | +# Run unit tests (requires test dependencies) |
| 162 | +python3 -m pytest test/test_config_metadata.py -v |
| 163 | + |
| 164 | +# Run integration tests |
| 165 | +python3 -m pytest test/test_client_metadata.py -v |
| 166 | +``` |
| 167 | + |
| 168 | +## 🔍 Key Features |
| 169 | + |
| 170 | +### 1. Null Safety |
| 171 | +- All metadata fields are optional |
| 172 | +- Graceful handling of missing API data |
| 173 | +- Cloud client passes null metadata |
| 174 | + |
| 175 | +### 2. Backward Compatibility |
| 176 | +- HookContext metadata parameter is optional |
| 177 | +- Existing code continues to work unchanged |
| 178 | +- No breaking changes to public API |
| 179 | + |
| 180 | +### 3. API Evolution Support |
| 181 | +- JSONUtils handles unknown properties gracefully |
| 182 | +- Configurable serialization for different use cases |
| 183 | +- Robust error handling for malformed responses |
| 184 | + |
| 185 | +### 4. Clear Client Distinction |
| 186 | +- Local client: populated metadata from config API |
| 187 | +- Cloud client: null metadata (uses external API) |
| 188 | +- Maintains separation of concerns |
| 189 | + |
| 190 | +## 📊 Success Criteria Met |
| 191 | + |
| 192 | +- ✅ Config metadata accessible via `client.get_metadata()` |
| 193 | +- ✅ Metadata available in all evaluation hooks |
| 194 | +- ✅ Local client populates metadata, cloud client uses null |
| 195 | +- ✅ Robust error handling and null safety |
| 196 | +- ✅ Comprehensive test coverage |
| 197 | +- ✅ Consistent JSON serialization behavior |
| 198 | + |
| 199 | +## 🔗 Files Modified |
| 200 | + |
| 201 | +### New Files |
| 202 | +- `devcycle_python_sdk/models/config_metadata.py` - Metadata data models |
| 203 | +- `devcycle_python_sdk/util/json_utils.py` - Centralized JSON utilities |
| 204 | +- `test/test_config_metadata.py` - Unit tests |
| 205 | +- `test/test_client_metadata.py` - Integration tests |
| 206 | +- `test_metadata_standalone.py` - Standalone verification tests |
| 207 | + |
| 208 | +### Modified Files |
| 209 | +- `devcycle_python_sdk/models/eval_hook_context.py` - Added metadata support |
| 210 | +- `devcycle_python_sdk/managers/config_manager.py` - Added metadata storage |
| 211 | +- `devcycle_python_sdk/local_client.py` - Added metadata exposure |
| 212 | +- `devcycle_python_sdk/cloud_client.py` - Added null metadata |
| 213 | +- `devcycle_python_sdk/api/config_client.py` - Added JSON utility usage |
| 214 | +- `devcycle_python_sdk/models/__init__.py` - Exported new classes |
| 215 | + |
| 216 | +## 🎯 Benefits |
| 217 | + |
| 218 | +1. **Enhanced Debugging**: Hooks can access project/environment context |
| 219 | +2. **Better Monitoring**: Config versioning information available |
| 220 | +3. **API Compatibility**: Graceful handling of API evolution |
| 221 | +4. **Clear Separation**: Local vs cloud client distinction maintained |
| 222 | +5. **Backward Compatibility**: No breaking changes to existing code |
| 223 | + |
| 224 | +## 🔮 Future Enhancements |
| 225 | + |
| 226 | +1. **Cloud Client Metadata**: Could add metadata support for cloud client if needed |
| 227 | +2. **Extended Metadata**: Could include additional config information |
| 228 | +3. **Caching**: Could add metadata caching for performance |
| 229 | +4. **Validation**: Could add metadata validation for data integrity |
| 230 | + |
| 231 | +--- |
| 232 | + |
| 233 | +**Goal Achieved**: Enhanced debugging capabilities by providing evaluation context about which project/environment configuration is being used, along with versioning information for troubleshooting configuration-related issues. |
0 commit comments