Skip to content

Commit 0632784

Browse files
committed
feat(runtime): Adding state to track runtime
1 parent 302341a commit 0632784

File tree

2 files changed

+147
-3
lines changed

2 files changed

+147
-3
lines changed

devservices/utils/state.py

+71-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@
33
import os
44
import sqlite3
55
from enum import Enum
6+
from typing import Literal
67

78
from devservices.constants import DEVSERVICES_LOCAL_DIR
89
from devservices.constants import STATE_DB_FILE
910

1011

12+
class ServiceRuntime(Enum):
13+
LOCAL = "local"
14+
CONTAINERIZED = "containerized"
15+
16+
1117
class StateTables(Enum):
1218
STARTED_SERVICES = "started_services"
1319
STARTING_SERVICES = "starting_services"
20+
SERVICE_RUNTIME = "service_runtime"
1421

1522

1623
class State:
@@ -30,7 +37,7 @@ def __new__(cls) -> State:
3037

3138
def initialize_database(self) -> None:
3239
cursor = self.conn.cursor()
33-
# Formatted strings here and throughout the fileshould be extremely low risk given these are constants
40+
# Formatted strings here and throughout the file should be extremely low risk given these are constants
3441
cursor.execute(
3542
f"""
3643
CREATE TABLE IF NOT EXISTS {StateTables.STARTED_SERVICES.value} (
@@ -50,10 +57,26 @@ def initialize_database(self) -> None:
5057
)
5158
"""
5259
)
60+
61+
cursor.execute(
62+
f"""
63+
CREATE TABLE IF NOT EXISTS {StateTables.SERVICE_RUNTIME.value} (
64+
service_name TEXT PRIMARY KEY,
65+
runtime TEXT
66+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
67+
)
68+
"""
69+
)
5370
self.conn.commit()
5471

5572
def update_service_entry(
56-
self, service_name: str, mode: str, table: StateTables
73+
self,
74+
service_name: str,
75+
mode: str,
76+
table: (
77+
Literal[StateTables.STARTED_SERVICES]
78+
| Literal[StateTables.STARTING_SERVICES]
79+
),
5780
) -> None:
5881
cursor = self.conn.cursor()
5982
service_entries = self.get_service_entries(table)
@@ -96,7 +119,12 @@ def get_service_entries(self, table: StateTables) -> list[str]:
96119
return [row[0] for row in cursor.fetchall()]
97120

98121
def get_active_modes_for_service(
99-
self, service_name: str, table: StateTables
122+
self,
123+
service_name: str,
124+
table: (
125+
Literal[StateTables.STARTED_SERVICES]
126+
| Literal[StateTables.STARTING_SERVICES]
127+
),
100128
) -> list[str]:
101129
cursor = self.conn.cursor()
102130
cursor.execute(
@@ -110,6 +138,41 @@ def get_active_modes_for_service(
110138
return []
111139
return str(result[0]).split(",")
112140

141+
def get_service_runtime(self, service_name: str) -> ServiceRuntime:
142+
cursor = self.conn.cursor()
143+
cursor.execute(
144+
f"""
145+
SELECT runtime FROM {StateTables.SERVICE_RUNTIME.value} WHERE service_name = ?
146+
""",
147+
(service_name,),
148+
)
149+
result = cursor.fetchone()
150+
if result is None:
151+
return ServiceRuntime.CONTAINERIZED
152+
return ServiceRuntime(result[0])
153+
154+
def update_service_runtime(
155+
self, service_name: str, runtime: ServiceRuntime
156+
) -> None:
157+
cursor = self.conn.cursor()
158+
cursor.execute(
159+
f"""
160+
INSERT OR REPLACE INTO {StateTables.SERVICE_RUNTIME.value} (service_name, runtime) VALUES (?, ?)
161+
""",
162+
(service_name, runtime.value),
163+
)
164+
self.conn.commit()
165+
166+
def get_services_by_runtime(self, runtime: ServiceRuntime) -> list[str]:
167+
cursor = self.conn.cursor()
168+
cursor.execute(
169+
f"""
170+
SELECT service_name FROM {StateTables.SERVICE_RUNTIME.value} WHERE runtime = ?
171+
""",
172+
(runtime.value,),
173+
)
174+
return [row[0] for row in cursor.fetchall()]
175+
113176
def clear_state(self) -> None:
114177
cursor = self.conn.cursor()
115178
cursor.execute(
@@ -122,4 +185,9 @@ def clear_state(self) -> None:
122185
DELETE FROM {StateTables.STARTING_SERVICES.value}
123186
"""
124187
)
188+
cursor.execute(
189+
f"""
190+
DELETE FROM {StateTables.SERVICE_RUNTIME.value}
191+
"""
192+
)
125193
self.conn.commit()

tests/utils/test_state.py

+76
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pathlib import Path
44
from unittest import mock
55

6+
from devservices.utils.state import ServiceRuntime
67
from devservices.utils.state import State
78
from devservices.utils.state import StateTables
89

@@ -82,3 +83,78 @@ def test_get_mode_for_nonexistent_service(tmp_path: Path) -> None:
8283
)
8384
== []
8485
)
86+
87+
88+
def test_get_and_update_service_runtime(tmp_path: Path) -> None:
89+
with mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")):
90+
state = State()
91+
state.update_service_runtime("example-service", ServiceRuntime.CONTAINERIZED)
92+
assert (
93+
state.get_service_runtime("example-service") == ServiceRuntime.CONTAINERIZED
94+
)
95+
state.update_service_runtime("example-service", ServiceRuntime.LOCAL)
96+
assert state.get_service_runtime("example-service") == ServiceRuntime.LOCAL
97+
state.update_service_runtime("example-service", ServiceRuntime.CONTAINERIZED)
98+
assert (
99+
state.get_service_runtime("example-service") == ServiceRuntime.CONTAINERIZED
100+
)
101+
102+
103+
def test_get_service_runtime_defaults_to_containerized(tmp_path: Path) -> None:
104+
with mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")):
105+
state = State()
106+
assert (
107+
state.get_service_runtime("unknown-service") == ServiceRuntime.CONTAINERIZED
108+
)
109+
110+
111+
def test_get_services_by_runtime(tmp_path: Path) -> None:
112+
with mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")):
113+
state = State()
114+
assert state.get_services_by_runtime(ServiceRuntime.CONTAINERIZED) == []
115+
assert state.get_services_by_runtime(ServiceRuntime.LOCAL) == []
116+
state.update_service_runtime("first-service", ServiceRuntime.CONTAINERIZED)
117+
state.update_service_runtime("second-service", ServiceRuntime.LOCAL)
118+
state.update_service_runtime("third-service", ServiceRuntime.CONTAINERIZED)
119+
assert state.get_services_by_runtime(ServiceRuntime.CONTAINERIZED) == [
120+
"first-service",
121+
"third-service",
122+
]
123+
assert state.get_services_by_runtime(ServiceRuntime.LOCAL) == ["second-service"]
124+
125+
state.update_service_runtime("first-service", ServiceRuntime.LOCAL)
126+
assert state.get_services_by_runtime(ServiceRuntime.CONTAINERIZED) == [
127+
"third-service"
128+
]
129+
assert state.get_services_by_runtime(ServiceRuntime.LOCAL) == [
130+
"second-service",
131+
"first-service",
132+
]
133+
134+
135+
def test_clear_state(tmp_path: Path) -> None:
136+
with mock.patch("devservices.utils.state.STATE_DB_FILE", str(tmp_path / "state")):
137+
state = State()
138+
state.update_service_entry(
139+
"first-service", "default", StateTables.STARTED_SERVICES
140+
)
141+
state.update_service_entry(
142+
"second-service", "default", StateTables.STARTING_SERVICES
143+
)
144+
state.update_service_runtime("first-service", ServiceRuntime.CONTAINERIZED)
145+
state.update_service_runtime("second-service", ServiceRuntime.LOCAL)
146+
assert state.get_service_entries(StateTables.STARTED_SERVICES) == [
147+
"first-service"
148+
]
149+
assert state.get_service_entries(StateTables.STARTING_SERVICES) == [
150+
"second-service"
151+
]
152+
assert state.get_services_by_runtime(ServiceRuntime.CONTAINERIZED) == [
153+
"first-service"
154+
]
155+
assert state.get_services_by_runtime(ServiceRuntime.LOCAL) == ["second-service"]
156+
state.clear_state()
157+
assert state.get_service_entries(StateTables.STARTED_SERVICES) == []
158+
assert state.get_service_entries(StateTables.STARTING_SERVICES) == []
159+
assert state.get_services_by_runtime(ServiceRuntime.CONTAINERIZED) == []
160+
assert state.get_services_by_runtime(ServiceRuntime.LOCAL) == []

0 commit comments

Comments
 (0)