Skip to content

Commit b6a3455

Browse files
committed
WAL in ER
1 parent 0842def commit b6a3455

11 files changed

Lines changed: 552 additions & 136 deletions

File tree

finecode_extension_api/src/finecode_extension_api/code_action.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class DevEnv(enum.StrEnum):
5555
class RunActionMeta:
5656
trigger: RunActionTrigger
5757
dev_env: DevEnv
58+
wal_run_id: str = ""
5859

5960

6061
class RunReturnCode(enum.IntEnum):

finecode_extension_runner/src/finecode_extension_runner/_services/run_action.py

Lines changed: 197 additions & 32 deletions
Large diffs are not rendered by default.

finecode_extension_runner/src/finecode_extension_runner/cli.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from loguru import logger
88

99
import finecode_extension_runner.start as runner_start
10-
from finecode_extension_runner import global_state, logs
10+
from finecode_extension_runner import er_wal, global_state, logs
1111

1212

1313
@click.group()
@@ -19,6 +19,7 @@ def main():
1919
@main.command()
2020
@click.option("--trace", "trace", is_flag=True, default=False)
2121
@click.option("--debug", "debug", is_flag=True, default=False)
22+
@click.option("--wal", "wal", is_flag=True, default=False)
2223
@click.option(
2324
"--project-path",
2425
"project_path",
@@ -29,6 +30,7 @@ def main():
2930
def start(
3031
trace: bool,
3132
debug: bool,
33+
wal: bool,
3234
project_path: Path,
3335
env_name: str | None,
3436
):
@@ -55,6 +57,8 @@ def start(
5557
global_state.log_level = "INFO" if trace is False else "TRACE"
5658
global_state.project_dir_path = project_path
5759
global_state.env_name = env_name
60+
if wal:
61+
global_state.wal_writer = er_wal.ErWalWriter()
5862

5963
log_file_path = (project_path
6064
/ ".venvs"
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from __future__ import annotations
2+
3+
import dataclasses
4+
import enum
5+
import pathlib
6+
import sys
7+
import typing
8+
9+
from finecode_extension_runner import wal as shared_wal
10+
11+
12+
class ErWalEventType(enum.StrEnum):
13+
RUN_ACCEPTED = "run.accepted"
14+
RUN_DISPATCHED = "run.dispatched"
15+
RUN_COMPLETED = "run.completed"
16+
RUN_FAILED = "run.failed"
17+
HANDLER_STARTED = "handler.started"
18+
HANDLER_COMPLETED = "handler.completed"
19+
HANDLER_FAILED = "handler.failed"
20+
PARTIAL_RESULT_FIRST_SENT = "partial_result.first_sent"
21+
PARTIAL_RESULT_FINAL_SENT = "partial_result.final_sent"
22+
23+
24+
def default_wal_dir_path() -> pathlib.Path:
25+
venv_dir_path = pathlib.Path(sys.executable).parent.parent
26+
return venv_dir_path / "state" / "finecode" / "wal" / "er"
27+
28+
29+
def _serialize_payload(payload: typing.Any | None) -> dict[str, typing.Any]:
30+
if payload is None:
31+
return {}
32+
if dataclasses.is_dataclass(payload):
33+
return dataclasses.asdict(payload)
34+
if isinstance(payload, dict):
35+
return payload
36+
return {"value": str(payload)}
37+
38+
39+
@dataclasses.dataclass
40+
class ErWalConfig:
41+
dir_path: pathlib.Path | None = None
42+
max_segment_bytes: int = shared_wal.DEFAULT_MAX_SEGMENT_BYTES
43+
max_segments: int = shared_wal.DEFAULT_MAX_SEGMENTS
44+
45+
46+
class ErWalWriter:
47+
def __init__(self, config: ErWalConfig | None = None) -> None:
48+
effective_config = config or ErWalConfig()
49+
self._dir_path = effective_config.dir_path or default_wal_dir_path()
50+
self._writer = shared_wal.WalWriter(
51+
shared_wal.WalConfig(
52+
enabled=True,
53+
dir_path=self._dir_path,
54+
max_segment_bytes=effective_config.max_segment_bytes,
55+
max_segments=effective_config.max_segments,
56+
writer_id_prefix="er",
57+
)
58+
)
59+
60+
def append(
61+
self,
62+
*,
63+
event_type: ErWalEventType | str,
64+
wal_run_id: str,
65+
action_name: str,
66+
project_path: str,
67+
trigger: str,
68+
dev_env: str,
69+
payload: typing.Any | None = None,
70+
) -> None:
71+
serialized_event_type = (
72+
event_type.value if isinstance(event_type, ErWalEventType) else event_type
73+
)
74+
self._writer.append(
75+
event_type=serialized_event_type,
76+
wal_run_id=wal_run_id,
77+
action_name=action_name,
78+
project_path=project_path,
79+
trigger=trigger,
80+
dev_env=dev_env,
81+
payload=_serialize_payload(payload),
82+
)
83+
84+
def close(self) -> None:
85+
self._writer.close()
86+
87+
88+
def emit_run_event(
89+
wal_writer: ErWalWriter | None,
90+
*,
91+
event_type: ErWalEventType,
92+
wal_run_id: str,
93+
action_name: str,
94+
project_path: pathlib.Path | str,
95+
trigger: str,
96+
dev_env: str,
97+
payload: typing.Any | None = None,
98+
) -> None:
99+
if wal_writer is None:
100+
return
101+
wal_writer.append(
102+
event_type=event_type,
103+
wal_run_id=wal_run_id,
104+
action_name=action_name,
105+
project_path=str(project_path),
106+
trigger=trigger,
107+
dev_env=dev_env,
108+
payload=payload,
109+
)

finecode_extension_runner/src/finecode_extension_runner/global_state.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Literal
33

44
import finecode_extension_runner.context as context
5+
from finecode_extension_runner import er_wal
56

67
runner_context: context.RunnerContext | None = None
78
# it's the same as `runner_context.project.dir_path`, but it's available from the start of
@@ -10,3 +11,4 @@
1011
log_level: Literal["TRACE", "INFO"] = "INFO"
1112
env_name: str = ""
1213
log_file_path: Path | None = None
14+
wal_writer: er_wal.ErWalWriter | None = None

finecode_extension_runner/src/finecode_extension_runner/lsp_server.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from finecode_extension_api.interfaces import ifileeditor
2424
from pydantic.dataclasses import dataclass as pydantic_dataclass
2525

26-
from finecode_extension_runner import schemas, services
26+
from finecode_extension_runner import er_wal, global_state, schemas, services
2727
from finecode_extension_runner._services import run_action as run_action_service
2828
from finecode_extension_runner._services import merge_results as merge_results_service
2929
from finecode_extension_runner.di import resolver
@@ -318,6 +318,8 @@ def create_lsp_server() -> lsp_server.LanguageServer:
318318

319319
def on_process_exit():
320320
logger.info("Exit extension runner")
321+
if global_state.wal_writer is not None:
322+
global_state.wal_writer.close()
321323
services.shutdown_all_action_handlers()
322324
services.exit_all_action_handlers()
323325

@@ -353,6 +355,8 @@ def _on_initialized(ls: CustomLanguageServer, params: types.InitializedParams):
353355

354356
def _on_shutdown(ls: CustomLanguageServer, params):
355357
logger.info("Shutdown extension runner")
358+
if global_state.wal_writer is not None:
359+
global_state.wal_writer.close()
356360
services.shutdown_all_action_handlers()
357361

358362
logger.debug("Stop Finecode async tasks")
@@ -367,6 +371,8 @@ def _on_shutdown(ls: CustomLanguageServer, params):
367371

368372
def _on_exit(ls: lsp_server.LanguageServer, params):
369373
logger.info("Exit extension runner")
374+
if global_state.wal_writer is not None:
375+
global_state.wal_writer.close()
370376

371377

372378
def uri_to_path(uri: str) -> pathlib.Path:
@@ -537,6 +543,28 @@ async def run_action(
537543
options: dict[str, typing.Any] | None,
538544
):
539545
logger.trace(f"Run action: {action_name}")
546+
wal_run_id = (options or {}).get("wal_run_id")
547+
if not isinstance(wal_run_id, str) or wal_run_id.strip() == "":
548+
return {"error": "Missing required wal_run_id in run options"}
549+
550+
meta = (options or {}).get("meta") or {}
551+
trigger = meta.get("trigger", "unknown")
552+
dev_env = meta.get("dev_env", "unknown")
553+
project_path = global_state.project_dir_path or pathlib.Path(".")
554+
er_wal.emit_run_event(
555+
global_state.wal_writer,
556+
event_type=er_wal.ErWalEventType.RUN_ACCEPTED,
557+
wal_run_id=wal_run_id,
558+
action_name=action_name,
559+
project_path=project_path,
560+
trigger=trigger,
561+
dev_env=dev_env,
562+
payload={
563+
"partial_result_token": (options or {}).get("partial_result_token"),
564+
"progress_token": (options or {}).get("progress_token"),
565+
},
566+
)
567+
540568
request = schemas.RunActionRequest(action_name=action_name, params=params)
541569

542570
# use pydantic dataclass to convert dict to dataclass instance recursively
@@ -552,6 +580,16 @@ async def run_action(
552580
if isinstance(exception, services.StopWithResponse):
553581
status = "stopped"
554582
response = exception.response
583+
er_wal.emit_run_event(
584+
global_state.wal_writer,
585+
event_type=er_wal.ErWalEventType.RUN_FAILED,
586+
wal_run_id=wal_run_id,
587+
action_name=action_name,
588+
project_path=project_path,
589+
trigger=trigger,
590+
dev_env=dev_env,
591+
payload={"error": "stopped"},
592+
)
555593
else:
556594
error_msg = ""
557595
if isinstance(exception, services.ActionFailedException):
@@ -561,6 +599,16 @@ async def run_action(
561599
logger.error("Unhandled exception in action run:")
562600
logger.exception(exception)
563601
error_msg = f"{type(exception)}: {str(exception)}"
602+
er_wal.emit_run_event(
603+
global_state.wal_writer,
604+
event_type=er_wal.ErWalEventType.RUN_FAILED,
605+
wal_run_id=wal_run_id,
606+
action_name=action_name,
607+
project_path=project_path,
608+
trigger=trigger,
609+
dev_env=dev_env,
610+
payload={"error": error_msg},
611+
)
564612
return {"error": error_msg}
565613

566614
# dict key can be path, but pygls fails to handle slashes in dict keys, use strings
@@ -574,6 +622,16 @@ async def run_action(
574622
for fmt, result in result_by_format.items()
575623
}
576624
result_str = json.dumps(converted_result_by_format, cls=CustomJSONEncoder)
625+
er_wal.emit_run_event(
626+
global_state.wal_writer,
627+
event_type=er_wal.ErWalEventType.RUN_COMPLETED,
628+
wal_run_id=wal_run_id,
629+
action_name=action_name,
630+
project_path=project_path,
631+
trigger=trigger,
632+
dev_env=dev_env,
633+
payload={"status": status, "return_code": response.return_code},
634+
)
577635
return {
578636
"status": status,
579637
"resultByFormat": result_str,

finecode_extension_runner/src/finecode_extension_runner/schemas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class RunActionRequest(BaseSchema):
5858

5959
@dataclass
6060
class RunActionOptions(BaseSchema):
61+
wal_run_id: str
6162
meta: code_action.RunActionMeta
6263
partial_result_token: int | str | None = None
6364
progress_token: int | str | None = None

0 commit comments

Comments
 (0)