• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

transentis / bptk_py / 18428480622

11 Oct 2025 10:55AM UTC coverage: 86.451% (-3.7%) from 90.129%
18428480622

push

github

olivergrasl
remove obsolete script

6183 of 7152 relevant lines covered (86.45%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.47
/BPTK_Py/externalstateadapter/externalStateAdapter.py
1
from abc import ABCMeta, abstractmethod
1✔
2
import datetime
1✔
3
from typing import Any
1✔
4

5
import jsonpickle
1✔
6
from ..util import statecompression
1✔
7
from dataclasses import dataclass
1✔
8
import os
1✔
9
from ..logger import log
1✔
10

11
@dataclass
1✔
12
class InstanceState:
1✔
13
    state: Any
1✔
14
    instance_id: str
1✔
15
    time: str
1✔
16
    timeout: Any
1✔
17
    step: Any
1✔
18

19
class ExternalStateAdapter(metaclass=ABCMeta):
1✔
20
    @abstractmethod
1✔
21
    def __init__(self, compress: bool):
1✔
22
        self.compress = compress
1✔
23
        log(f"[INFO] ExternalStateAdapter initialized with compression: {compress}")
1✔
24

25
  
26

27
    def save_instance(self, state: InstanceState):
1✔
28
        log(f"[INFO] Saving instance {state.instance_id if state else 'None'}")
×
29
        try:
×
30
            if(self.compress and state is not None and state.state is not None):
×
31
                log(f"[INFO] Compressing state for instance {state.instance_id}")
×
32
                state.state["settings_log"] = statecompression.compress_settings(state.state["settings_log"])
×
33
                state.state["results_log"] = statecompression.compress_results(state.state["results_log"])
×
34
                log(f"[INFO] State compression completed for instance {state.instance_id}")
×
35
            result = self._save_instance(state)
×
36
            log(f"[INFO] Instance {state.instance_id if state else 'None'} saved successfully")
×
37
            return result
×
38
        except Exception as e:
×
39
            log(f"[ERROR] Failed to save instance {state.instance_id if state else 'None'}: {str(e)}")
×
40
            raise
×
41

42

43
    def load_instance(self, instance_uuid: str) -> InstanceState:
1✔
44
        log(f"[INFO] Loading instance {instance_uuid}")
×
45
        try:
×
46
            state = self._load_instance(instance_uuid)
×
47

48
            if state is None:
×
49
                log(f"[WARN] No state found for instance {instance_uuid}")
×
50
                return state
×
51

52
            log(f"[INFO] State loaded for instance {instance_uuid}")
×
53

54
            if(self.compress and state.state is not None):
×
55
                log(f"[INFO] Decompressing state for instance {instance_uuid}")
×
56
                state.state["settings_log"] = statecompression.decompress_settings(state.state["settings_log"])
×
57
                state.state["results_log"] = statecompression.decompress_results(state.state["results_log"])
×
58
                log(f"[INFO] State decompression completed for instance {instance_uuid}")
×
59

60
            # Always restore numeric keys in scenario_cache (no compression, just JSON key conversion fix)
61
            if(state.state is not None):
×
62
                if "scenario_cache" in state.state:
×
63
                    log(f"[INFO] Restoring numeric keys in scenario_cache for instance {instance_uuid}")
×
64
                    state.state["scenario_cache"] = self._restore_numeric_keys(state.state["scenario_cache"])
×
65
                    log(f"[INFO] Numeric keys restored for instance {instance_uuid}")
×
66

67
            log(f"[INFO] Instance {instance_uuid} loaded successfully")
×
68
            return state
×
69
        except Exception as e:
×
70
            log(f"[ERROR] Failed to load instance {instance_uuid}: {str(e)}")
×
71
            raise
×
72

73
    def _restore_numeric_keys(self, data):
1✔
74
        """
75
        Recursively restore numeric keys that were converted to strings during JSON serialization.
76
        This handles the scenario_cache structure where floating point timesteps get converted to strings.
77
        """
78
        if not isinstance(data, dict):
1✔
79
            return data
×
80

81
        restored = {}
1✔
82
        for key, value in data.items():
1✔
83
            # Try to convert string keys back to numbers
84
            new_key = key
1✔
85
            if isinstance(key, str):
1✔
86
                # Try to convert to float first (for timesteps like "1.0", "2.5")
87
                try:
1✔
88
                    if '.' in key:
1✔
89
                        new_key = float(key)
1✔
90
                        log(f"[INFO] Converted string key '{key}' to float {new_key}")
1✔
91
                    else:
92
                        # Try integer conversion for whole numbers
93
                        new_key = int(key)
1✔
94
                        log(f"[INFO] Converted string key '{key}' to int {new_key}")
×
95
                except ValueError:
1✔
96
                    # If conversion fails, keep as string
97
                    new_key = key
1✔
98

99
            # Recursively process nested dictionaries
100
            if isinstance(value, dict):
1✔
101
                restored[new_key] = self._restore_numeric_keys(value)
1✔
102
            else:
103
                restored[new_key] = value
1✔
104

105
        return restored
1✔
106

107
 
108

109
    @abstractmethod
1✔
110
    def _save_instance(self, state: InstanceState):
1✔
111
        pass
1✔
112

113
    @abstractmethod
1✔
114
    def _load_instance(self, instance_uuid: str) -> InstanceState:
1✔
115
        pass
1✔
116

117
    @abstractmethod
1✔
118
    def delete_instance(self, instance_uuid: str):
1✔
119
        pass
1✔
120

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc