Skip to content

Commit e522c02

Browse files
bojeansonclaude
andauthored
refactor: replace Supervisor singleton with FastAPI lifespan (#129)
* refactor: replace Supervisor singleton with FastAPI lifespan - Remove SingletonMeta from Supervisor and DataGathering - Add lifespan to create_app() — all service instances created once at startup and stored in app.state - Make BinaryStorageFactory and MetadataStorageFactory config-independent: StoringPathManager is now built inside create_*_storage(), removing the dependency on StationConfig at factory construction time - Simplify reset_managers() on Supervisor and DataGathering: no factory args needed since factories are stateless - Simplify reset() on storage managers: just clears the cache - Remove config_updated flag from ConfigManager: reset is now triggered directly from set_config route via app.state.supervisor.reset_managers() - Expose binary_storage_manager and metadata_storage_manager in app.state directly, avoiding private attribute access in dependency_injection.py - Update unit tests for storage factories and managers * fix: enter TestClient context manager to trigger FastAPI lifespan in functional tests TestClient only fires lifespan events (startup/shutdown) when used as a context manager. Without it, app.state is never populated and routes that access request.app.state.supervisor raise KeyError. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: delete dead V2 router V2 was never registered in app.py, contained bugs (sync binary.read() without await, missing station_config in supervisor.inspect), and was superseded by V1. No code referenced it. * fix: ci test reporter --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a191a73 commit e522c02

20 files changed

Lines changed: 125 additions & 354 deletions

File tree

.github/workflows/ci_edge_orchestrator.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ jobs:
1414
lint_and_test_on_edge_orchestrator:
1515
name: Run Python linter and tests (unit > integration > functional)
1616
runs-on: ubuntu-latest
17+
permissions:
18+
checks: write
19+
contents: read
20+
packages: read
1721
strategy:
1822
matrix:
1923
python-version: ["3.9", "3.10", "3.11"]

edge_orchestrator/src/edge_orchestrator/application/config/config_manager.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ def __init__(self):
1717
self._active_station_name: Optional[str] = os.getenv("ACTIVE_CONFIG_NAME", "marker_classif_with_2_fake_cam")
1818
self._logger = logging.getLogger(__name__)
1919
self._config_dir = Path(os.getenv("CONFIG_DIR", "config")).resolve()
20-
self._config_updated = False
2120
self._load_all_configs()
2221

2322
def _load_all_configs(self) -> StationConfig:
@@ -58,7 +57,6 @@ def set_config(self, new_station_config: StationConfig):
5857
self._active_station_name = new_active_station_name
5958
self._station_configs[new_active_station_name] = new_station_config
6059
self._save_config_as_json(new_station_config)
61-
self._config_updated = True
6260

6361
def get_config(self) -> Optional[StationConfig]:
6462
return self._station_configs.get(self._active_station_name)
@@ -70,14 +68,6 @@ def reload_configs(self):
7068
def all_configs(self) -> Dict[str, StationConfig]:
7169
return self._station_configs
7270

73-
@property
74-
def config_updated(self) -> bool:
75-
return self._config_updated
76-
77-
@config_updated.setter
78-
def config_updated(self, value: bool):
79-
self._config_updated = value
80-
8171
def _is_an_existing_config_name(self, station_name: str) -> bool:
8272
if station_name not in self.all_configs:
8373
return False
@@ -90,4 +80,6 @@ def set_config_by_name(self, station_name: str):
9080
detail=f"{station_name} is not one of existing station names: {self.get_all_config_names()}",
9181
)
9282
self._active_station_name = station_name
93-
self._config_updated = True
83+
84+
def get_all_config_names(self):
85+
return list(self._station_configs.keys())

edge_orchestrator/src/edge_orchestrator/application/use_cases/data_gathering.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,16 @@
22

33
from edge_orchestrator.domain.models.item import Item
44
from edge_orchestrator.domain.models.station_config import StationConfig
5-
from edge_orchestrator.domain.ports.binary_storage.i_binary_storage_factory import (
6-
IBinaryStorageFactory,
7-
)
85
from edge_orchestrator.domain.ports.binary_storage.i_binary_storage_manager import (
96
IBinaryStorageManager,
107
)
118
from edge_orchestrator.domain.ports.camera.i_camera_manager import ICameraManager
12-
from edge_orchestrator.domain.ports.metadata_storage.i_metadata_storage_factory import (
13-
IMetadataStorageFactory,
14-
)
159
from edge_orchestrator.domain.ports.metadata_storage.i_metadata_storage_manager import (
1610
IMetadataStorageManager,
1711
)
18-
from edge_orchestrator.utils.singleton import SingletonMeta
1912

2013

21-
class DataGathering(metaclass=SingletonMeta):
14+
class DataGathering:
2215
def __init__(
2316
self,
2417
metadata_storage_manager: IMetadataStorageManager,
@@ -43,11 +36,9 @@ async def upload(self, item: Item, station_config: StationConfig):
4336

4437
self._metadata_storage_manager.get_metadata_storage(station_config).save_item_metadata(item)
4538

46-
def reset_managers(
47-
self, binary_storage_factory: IBinaryStorageFactory, metadata_storage_factory: IMetadataStorageFactory
48-
):
39+
def reset_managers(self):
4940
self._logger.info("Resetting all managers after configuration update...")
50-
self._metadata_storage_manager.reset(metadata_storage_factory)
51-
self._binary_storage_manager.reset(binary_storage_factory)
41+
self._metadata_storage_manager.reset()
42+
self._binary_storage_manager.reset()
5243
self._camera_manager.reset()
5344
self._logger.info("Managers reset done!")

edge_orchestrator/src/edge_orchestrator/application/use_cases/supervisor.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
from edge_orchestrator.domain.models.item import Item
44
from edge_orchestrator.domain.models.station_config import StationConfig
5-
from edge_orchestrator.domain.ports.binary_storage.i_binary_storage_factory import (
6-
IBinaryStorageFactory,
7-
)
85
from edge_orchestrator.domain.ports.binary_storage.i_binary_storage_manager import (
96
IBinaryStorageManager,
107
)
@@ -15,19 +12,15 @@
1512
from edge_orchestrator.domain.ports.item_rule.i_item_rule_manager import (
1613
IItemRuleManager,
1714
)
18-
from edge_orchestrator.domain.ports.metadata_storage.i_metadata_storage_factory import (
19-
IMetadataStorageFactory,
20-
)
2115
from edge_orchestrator.domain.ports.metadata_storage.i_metadata_storage_manager import (
2216
IMetadataStorageManager,
2317
)
2418
from edge_orchestrator.domain.ports.model_forwarder.i_model_forwarder_manager import (
2519
IModelForwarderManager,
2620
)
27-
from edge_orchestrator.utils.singleton import SingletonMeta
2821

2922

30-
class Supervisor(metaclass=SingletonMeta):
23+
class Supervisor:
3124
def __init__(
3225
self,
3326
metadata_storage_manager: IMetadataStorageManager,
@@ -59,12 +52,10 @@ async def inspect(self, item: Item, station_config: StationConfig):
5952

6053
self._metadata_storage_manager.get_metadata_storage(station_config).save_item_metadata(item)
6154

62-
def reset_managers(
63-
self, binary_storage_factory: IBinaryStorageFactory, metadata_storage_factory: IMetadataStorageFactory
64-
):
55+
def reset_managers(self):
6556
self._logger.info("Resetting all managers after configuration update...")
66-
self._metadata_storage_manager.reset(metadata_storage_factory)
67-
self._binary_storage_manager.reset(binary_storage_factory)
57+
self._metadata_storage_manager.reset()
58+
self._binary_storage_manager.reset()
6859
self._model_forwarder_manager.reset()
6960
self._camera_rule_manager.reset()
7061
self._item_rule_manager.reset()

edge_orchestrator/src/edge_orchestrator/domain/ports/binary_storage/i_binary_storage_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ def get_binary_storage(self, station_config: StationConfig) -> IBinaryStorage:
2121
pass
2222

2323
@abstractmethod
24-
def reset(self, binary_storage_factory: IBinaryStorageFactory):
24+
def reset(self):
2525
pass

edge_orchestrator/src/edge_orchestrator/domain/ports/metadata_storage/i_metadata_storage_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ def get_metadata_storage(self, station_config: StationConfig) -> IMetadataStorag
2121
pass
2222

2323
@abstractmethod
24-
def reset(self, metadata_storage_factory: IMetadataStorageFactory):
24+
def reset(self):
2525
pass

edge_orchestrator/src/edge_orchestrator/infrastructure/adapters/binary_storage/binary_storage_factory.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,34 @@
1212

1313

1414
class BinaryStorageFactory(IBinaryStorageFactory):
15-
def __init__(self, storing_path_manager: StoringPathManager):
15+
def __init__(self):
1616
self._logger = logging.getLogger(__name__)
17-
self._storing_path_manager = storing_path_manager
1817

1918
def create_binary_storage(self, station_config: StationConfig) -> IBinaryStorage:
19+
storing_path_manager = StoringPathManager(station_config.binary_storage_config, station_config.station_name)
2020
if station_config.binary_storage_config.storage_type == StorageType.FILESYSTEM:
2121
from edge_orchestrator.infrastructure.adapters.binary_storage.filesystem_binary_storage import (
2222
FileSystemBinaryStorage,
2323
)
2424

25-
return FileSystemBinaryStorage(station_config, self._storing_path_manager)
25+
return FileSystemBinaryStorage(station_config, storing_path_manager)
2626
elif station_config.binary_storage_config.storage_type == StorageType.AWS:
2727
from edge_orchestrator.infrastructure.adapters.binary_storage.aws_binary_storage import (
2828
AWSBinaryStorage,
2929
)
3030

31-
return AWSBinaryStorage(station_config, self._storing_path_manager)
31+
return AWSBinaryStorage(station_config, storing_path_manager)
3232
elif station_config.binary_storage_config.storage_type == StorageType.GCP:
3333
from edge_orchestrator.infrastructure.adapters.binary_storage.gcp_binary_storage import (
3434
GCPBinaryStorage,
3535
)
3636

37-
return GCPBinaryStorage(station_config, self._storing_path_manager)
37+
return GCPBinaryStorage(station_config, storing_path_manager)
3838
elif station_config.binary_storage_config.storage_type == StorageType.AZURE:
3939
from edge_orchestrator.infrastructure.adapters.binary_storage.azure_binary_storage import (
4040
AzureBinaryStorage,
4141
)
4242

43-
return AzureBinaryStorage(station_config, self._storing_path_manager)
43+
return AzureBinaryStorage(station_config, storing_path_manager)
4444
else:
4545
raise Exception(f"Binary storage type {station_config.binary_storage_config.storage_type} not supported")

edge_orchestrator/src/edge_orchestrator/infrastructure/adapters/binary_storage/binary_storage_manager.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ def get_binary_storage(self, station_config: StationConfig) -> IBinaryStorage:
2525
self._binary_storages[binary_storage_type] = binary_storage
2626
return self._binary_storages[binary_storage_type]
2727

28-
def reset(self, binary_storage_factory: IBinaryStorageFactory):
29-
self._binary_storage_factory = binary_storage_factory
28+
def reset(self):
3029
self._binary_storages = {}

edge_orchestrator/src/edge_orchestrator/infrastructure/adapters/metadata_storage/metadata_storage_factory.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,35 @@
1212

1313

1414
class MetadataStorageFactory(IMetadataStorageFactory):
15-
def __init__(self, storing_path_manager: StoringPathManager):
15+
def __init__(self):
1616
self._logger = logging.getLogger(__name__)
17-
self._storing_path_manager = storing_path_manager
1817

1918
def create_metadata_storage(self, station_config: StationConfig) -> IMetadataStorage:
19+
storing_path_manager = StoringPathManager(station_config.metadata_storage_config, station_config.station_name)
2020
if station_config.metadata_storage_config.storage_type == StorageType.FILESYSTEM:
2121
from edge_orchestrator.infrastructure.adapters.metadata_storage.filesystem_metadata_storage import (
2222
FileSystemMetadataStorage,
2323
)
2424

25-
return FileSystemMetadataStorage(station_config, self._storing_path_manager)
25+
return FileSystemMetadataStorage(station_config, storing_path_manager)
2626
elif station_config.metadata_storage_config.storage_type == StorageType.AWS:
2727
from edge_orchestrator.infrastructure.adapters.metadata_storage.aws_metadata_storage import (
2828
AWSMetadataStorage,
2929
)
3030

31-
return AWSMetadataStorage(station_config, self._storing_path_manager)
31+
return AWSMetadataStorage(station_config, storing_path_manager)
3232
elif station_config.metadata_storage_config.storage_type == StorageType.GCP:
3333
from edge_orchestrator.infrastructure.adapters.metadata_storage.gcp_metadata_storage import (
3434
GCPMetadataStorage,
3535
)
3636

37-
return GCPMetadataStorage(station_config, self._storing_path_manager)
37+
return GCPMetadataStorage(station_config, storing_path_manager)
3838
elif station_config.metadata_storage_config.storage_type == StorageType.AZURE:
3939
from edge_orchestrator.infrastructure.adapters.metadata_storage.azure_metadata_storage import (
4040
AzureMetadataStorage,
4141
)
4242

43-
return AzureMetadataStorage(station_config, self._storing_path_manager)
43+
return AzureMetadataStorage(station_config, storing_path_manager)
4444
else:
4545
raise ValueError(
4646
f"Metadata storage type {station_config.metadata_storage_config.storage_type} is not supported"

edge_orchestrator/src/edge_orchestrator/infrastructure/adapters/metadata_storage/metadata_storage_manager.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ def get_metadata_storage(self, station_config: StationConfig) -> IMetadataStorag
2525
self._metadata_storages[metadata_storage_type] = metadata_storage
2626
return self._metadata_storages[metadata_storage_type]
2727

28-
def reset(self, metadata_storage_factory: IMetadataStorageFactory):
29-
self._metadata_storage_factory = metadata_storage_factory
28+
def reset(self):
3029
self._metadata_storages = {}

0 commit comments

Comments
 (0)