Skip to content

Commit 54d8a5e

Browse files
committed
Finish error handling in prepare_runners and prepare_envs handlers. Extend iprojectinfoprovider with methods for current project and project raw config of any project, not only current
1 parent 9ca7c4f commit 54d8a5e

14 files changed

Lines changed: 263 additions & 164 deletions

File tree

finecode_extension_api/src/finecode_extension_api/actions/dump_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import dataclasses
22
import pathlib
3+
import pprint
34
import sys
45
import typing
5-
import pprint
66

77
if sys.version_info >= (3, 12):
88
from typing import override
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import pathlib
12
from typing import Any, Protocol
23

34

45
class IProjectInfoProvider(Protocol):
5-
async def get_project_raw_config(self) -> dict[str, Any]: ...
6+
def get_current_project_def_path(self) -> pathlib.Path: ...
7+
8+
async def get_project_raw_config(
9+
self, project_def_path: pathlib.Path
10+
) -> dict[str, Any]: ...
11+
12+
async def get_current_project_raw_config(self) -> dict[str, Any]: ...

finecode_extension_runner/src/finecode_extension_runner/_services/run_action.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,14 @@
2121
last_run_id: int = 0
2222
partial_result_sender: partial_result_sender_module.PartialResultSender
2323
handler_config_merger = deepmerge.Merger(
24-
[
25-
(list, ["override"]),
26-
(dict, ["merge"]),
27-
(set, ["override"])
28-
],
24+
[(list, ["override"]), (dict, ["merge"]), (set, ["override"])],
2925
# all other types:
3026
["override"],
3127
# strategies in the case where the types conflict:
32-
["override"]
28+
["override"],
3329
)
3430

31+
3532
class ActionFailedException(Exception):
3633
def __init__(self, message: str) -> None:
3734
self.message = message
@@ -579,7 +576,9 @@ async def run_subresult_coros_sequentially(
579576
try:
580577
coro_result = await coro
581578
except Exception as e:
582-
logger.error(f"Unhandled exception in subresult coroutine({action_name}, run {run_id}):")
579+
logger.error(
580+
f"Unhandled exception in subresult coroutine({action_name}, run {run_id}):"
581+
)
583582
logger.exception(e)
584583
raise ActionFailedException(
585584
f"Running action handlers of '{action_name}' failed(Run {run_id}): {e}"

finecode_extension_runner/src/finecode_extension_runner/action_handlers/dependency_config_utils.py

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,67 @@
55
def make_project_config_pip_compatible(
66
project_raw_config: dict[str, typing.Any], config_file_path: pathlib.Path
77
) -> None:
8-
# TODO: what to do with included groups in dependency groups? Inherit config from its env?
98
finecode_config = project_raw_config.get("tool", {}).get("finecode", {})
109
# apply changes to dependencies from env configuration to deps groups
11-
for env_name, env_config in finecode_config.get("env", {}).items():
12-
if "dependencies" not in env_config:
13-
continue
14-
15-
env_deps_group = project_raw_config.get("dependency-groups", {}).get(
16-
env_name, []
10+
for env_name in finecode_config.get("env", {}).keys():
11+
make_env_deps_pip_compatible(
12+
env_name=env_name,
13+
project_raw_config=project_raw_config,
14+
config_file_path=config_file_path,
1715
)
18-
dependencies = env_config["dependencies"]
19-
for dep_name, dep_params in dependencies.items():
20-
# handle 'path'. 'editable' cannot be handled here because dependency
21-
# specifier doesn't support it. It will read and processed by
22-
# `install_deps` action
23-
if "path" in dep_params:
24-
# replace dependency version / source in dependency group to this path
25-
#
26-
# check all dependencies because it can be duplicated: e.g. as explicit
27-
# dependency and as dependency of action handler.
28-
dep_indexes_in_group: list[int] = []
29-
for idx, dep in enumerate(env_deps_group):
30-
# check for string because dependency can be also dictionary like '{ "include-group": "runtime"}'
31-
if isinstance(dep, str) and get_dependency_name(dep) == dep_name:
16+
17+
18+
def make_env_deps_pip_compatible(
19+
env_name: str,
20+
project_raw_config: dict[str, typing.Any],
21+
config_file_path: pathlib.Path,
22+
) -> None:
23+
env_config = (
24+
project_raw_config.get("tool", {})
25+
.get("finecode", {})
26+
.get("env", {})
27+
.get(env_name, None)
28+
)
29+
if env_config is None or "dependencies" not in env_config:
30+
return
31+
32+
env_deps_group = project_raw_config.get("dependency-groups", {}).get(env_name, [])
33+
dependencies = env_config["dependencies"]
34+
for dep_name, dep_params in dependencies.items():
35+
# handle 'path'. 'editable' cannot be handled here because dependency
36+
# specifier doesn't support it. It will read and processed by
37+
# `install_deps` action
38+
if "path" in dep_params:
39+
# replace dependency version / source in dependency group to this path
40+
#
41+
# check all dependencies because it can be duplicated: e.g. as explicit
42+
# dependency and as dependency of action handler.
43+
dep_indexes_in_group: list[int] = []
44+
for idx, dep in enumerate(env_deps_group):
45+
if isinstance(dep, dict):
46+
if "include-group" in dep:
47+
included_group = dep["include-group"]
48+
make_env_deps_pip_compatible(
49+
env_name=included_group,
50+
project_raw_config=project_raw_config,
51+
config_file_path=config_file_path,
52+
)
53+
elif isinstance(dep, str):
54+
if get_dependency_name(dep) == dep_name:
3255
dep_indexes_in_group.append(idx)
3356

34-
if len(dep_indexes_in_group) == 0:
35-
continue
57+
if len(dep_indexes_in_group) == 0:
58+
continue
3659

37-
resolved_path_to_dep = pathlib.Path(dep_params["path"])
38-
if not resolved_path_to_dep.is_absolute():
39-
# resolve relative to project dir where project def file is
40-
resolved_path_to_dep = (
41-
config_file_path.parent / resolved_path_to_dep
42-
)
43-
new_dep_str_in_group = (
44-
f"{dep_name} @ file://{resolved_path_to_dep.as_posix()}"
45-
)
46-
for idx in dep_indexes_in_group:
47-
env_deps_group[idx] = new_dep_str_in_group
60+
resolved_path_to_dep = pathlib.Path(dep_params["path"])
61+
if not resolved_path_to_dep.is_absolute():
62+
# resolve relative to project dir where project def file is
63+
resolved_path_to_dep = config_file_path.parent / resolved_path_to_dep
64+
new_dep_str_in_group = (
65+
f"{dep_name} @ file://{resolved_path_to_dep.as_posix()}"
66+
)
67+
for idx in dep_indexes_in_group:
68+
env_deps_group[idx] = new_dep_str_in_group
4869

4970

5071
def get_dependency_name(dependency_str: str) -> str:
@@ -58,11 +79,6 @@ def get_dependency_name(dependency_str: str) -> str:
5879
return dependency_str
5980

6081

61-
class FailedToGetDependencies(Exception):
62-
def __init__(self, message: str) -> None:
63-
self.message = message
64-
65-
6682
def raw_dep_to_dep_dict(raw_dep: str, env_deps_config: dict) -> dict[str, str | bool]:
6783
name = get_dependency_name(raw_dep)
6884
version_or_source = raw_dep[len(name) :]
@@ -73,3 +89,10 @@ def raw_dep_to_dep_dict(raw_dep: str, env_deps_config: dict) -> dict[str, str |
7389
"editable": editable,
7490
}
7591
return dep_dict
92+
93+
94+
__all__ = [
95+
"make_project_config_pip_compatible",
96+
"get_dependency_name",
97+
"raw_dep_to_dep_dict",
98+
]

finecode_extension_runner/src/finecode_extension_runner/action_handlers/prepare_envs_read_configs.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import asyncio
12
import dataclasses
3+
import pathlib
24
import shutil
5+
import typing
36

47
from finecode_extension_api import code_action
58
from finecode_extension_api.actions import prepare_envs as prepare_envs_action
@@ -38,22 +41,30 @@ async def run(
3841
project_defs_pathes = set(
3942
[env_info.project_def_path for env_info in payload.envs]
4043
)
41-
if len(project_defs_pathes) != 1:
42-
raise code_action.ActionFailedException("PrepareEnvsReadConfigsHandler supports only reading config of envs from the current project")
44+
raw_config_by_project_def_path: dict[pathlib.Path, dict[str, typing.Any]] = {}
4345

44-
project_raw_config = await self.project_info_provider.get_project_raw_config()
46+
get_config_tasks: list[asyncio.Task] = []
47+
async with asyncio.TaskGroup() as tg:
48+
for project_def_path in project_defs_pathes:
49+
task = tg.create_task(
50+
self.project_info_provider.get_project_raw_config(project_def_path)
51+
)
52+
get_config_tasks.append(task)
4553

46-
project_def_path = project_defs_pathes.pop()
47-
project_dir_path = project_def_path.parent
48-
49-
dependency_config_utils.make_project_config_pip_compatible(
50-
project_raw_config, project_def_path
51-
)
54+
for idx, project_def_path in enumerate(project_defs_pathes):
55+
project_raw_config = get_config_tasks[idx].result()
56+
dependency_config_utils.make_project_config_pip_compatible(
57+
project_raw_config, project_def_path
58+
)
59+
raw_config_by_project_def_path[project_def_path] = project_raw_config
5260

5361
for env_info in payload.envs:
5462
run_context.project_def_path_by_venv_dir_path[env_info.venv_dir_path] = (
55-
project_def_path
63+
env_info.project_def_path
5664
)
65+
project_raw_config = raw_config_by_project_def_path[
66+
env_info.project_def_path
67+
]
5768
run_context.project_def_by_venv_dir_path[env_info.venv_dir_path] = (
5869
project_raw_config
5970
)

finecode_extension_runner/src/finecode_extension_runner/action_handlers/prepare_runners_read_configs.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import asyncio
12
import dataclasses
3+
import pathlib
24
import shutil
5+
import typing
36

47
from finecode_extension_api import code_action
58
from finecode_extension_api.actions import prepare_runners as prepare_runners_action
@@ -39,22 +42,30 @@ async def run(
3942
project_defs_pathes = set(
4043
[env_info.project_def_path for env_info in payload.envs]
4144
)
42-
if len(project_defs_pathes) != 1:
43-
raise code_action.ActionFailedException("PrepareRunnersReadConfigsHandler supports only reading config of envs from the current project")
45+
raw_config_by_project_def_path: dict[pathlib.Path, dict[str, typing.Any]] = {}
4446

45-
project_raw_config = await self.project_info_provider.get_project_raw_config()
47+
get_config_tasks: list[asyncio.Task] = []
48+
async with asyncio.TaskGroup() as tg:
49+
for project_def_path in project_defs_pathes:
50+
task = tg.create_task(
51+
self.project_info_provider.get_project_raw_config(project_def_path)
52+
)
53+
get_config_tasks.append(task)
4654

47-
project_def_path = project_defs_pathes.pop()
48-
project_dir_path = project_def_path.parent
49-
50-
dependency_config_utils.make_project_config_pip_compatible(
51-
project_raw_config, project_def_path
52-
)
55+
for idx, project_def_path in enumerate(project_defs_pathes):
56+
project_raw_config = get_config_tasks[idx].result()
57+
dependency_config_utils.make_project_config_pip_compatible(
58+
project_raw_config, project_def_path
59+
)
60+
raw_config_by_project_def_path[project_def_path] = project_raw_config
5361

5462
for env_info in payload.envs:
5563
run_context.project_def_path_by_venv_dir_path[env_info.venv_dir_path] = (
56-
project_def_path
64+
env_info.project_def_path
5765
)
66+
project_raw_config = raw_config_by_project_def_path[
67+
env_info.project_def_path
68+
]
5869
run_context.project_def_by_venv_dir_path[env_info.venv_dir_path] = (
5970
project_raw_config
6071
)

finecode_extension_runner/src/finecode_extension_runner/di/bootstrap.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from typing import Any, Callable, Type, TypeVar
1+
import functools
2+
import pathlib
3+
from typing import Any, Awaitable, Callable, Type, TypeVar
24

35
try:
46
import fine_python_ast
@@ -21,6 +23,7 @@
2123
)
2224
from finecode_extension_runner import global_state, schemas
2325
from finecode_extension_runner._services import run_action
26+
from finecode_extension_runner.di import _state
2427
from finecode_extension_runner.impls import (
2528
action_runner,
2629
command_runner,
@@ -29,10 +32,14 @@
2932
loguru_logger,
3033
project_info_provider,
3134
)
32-
from finecode_extension_runner.di import _state
3335

3436

35-
def bootstrap(get_document_func: Callable, save_document_func: Callable):
37+
def bootstrap(
38+
get_document_func: Callable,
39+
save_document_func: Callable,
40+
project_def_path_getter: Callable[[], pathlib.Path],
41+
project_raw_config_getter: Callable[[str], Awaitable[dict[str, Any]]],
42+
):
3643
# logger_instance = loguru_logger.LoguruLogger()
3744
logger_instance = loguru_logger.get_logger()
3845
command_runner_instance = command_runner.CommandRunner(logger=logger_instance)
@@ -62,7 +69,11 @@ def bootstrap(get_document_func: Callable, save_document_func: Callable):
6269
_state.factories[fine_python_mypy.IMypySingleAstProvider] = (
6370
mypy_single_ast_provider_factory
6471
)
65-
_state.factories[iprojectinfoprovider.IProjectInfoProvider] = project_info_provider_factory
72+
_state.factories[iprojectinfoprovider.IProjectInfoProvider] = functools.partial(
73+
project_info_provider_factory,
74+
project_def_path_getter=project_def_path_getter,
75+
project_raw_config_getter=project_raw_config_getter,
76+
)
6677

6778
# TODO: parameters from config
6879

@@ -97,5 +108,12 @@ def mypy_single_ast_provider_factory(container):
97108
)
98109

99110

100-
def project_info_provider_factory(container):
101-
return project_info_provider.ProjectInfoProvider()
111+
def project_info_provider_factory(
112+
container,
113+
project_def_path_getter: Callable[[], pathlib.Path],
114+
project_raw_config_getter: Callable[[str], Awaitable[dict[str, Any]]],
115+
):
116+
return project_info_provider.ProjectInfoProvider(
117+
project_def_path_getter=project_def_path_getter,
118+
project_raw_config_getter=project_raw_config_getter,
119+
)
Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1-
from typing import Any, Callable
1+
import pathlib
2+
from typing import Any, Awaitable, Callable
23

34
from finecode_extension_api.interfaces import iprojectinfoprovider
45

5-
project_raw_config_getter: Callable
6-
76

87
class ProjectInfoProvider(iprojectinfoprovider.IProjectInfoProvider):
9-
async def get_project_raw_config(self) -> dict[str, Any]:
10-
return await project_raw_config_getter()
8+
def __init__(
9+
self,
10+
project_def_path_getter: Callable[[], pathlib.Path],
11+
project_raw_config_getter: Callable[[str], Awaitable[dict[str, Any]]],
12+
) -> None:
13+
self.project_def_path_getter = project_def_path_getter
14+
self.project_raw_config_getter = project_raw_config_getter
15+
16+
def get_current_project_def_path(self) -> pathlib.Path:
17+
return self.project_def_path_getter()
18+
19+
async def get_project_raw_config(
20+
self, project_def_path: pathlib.Path
21+
) -> dict[str, Any]:
22+
return await self.project_raw_config_getter(str(project_def_path))
23+
24+
async def get_current_project_raw_config(self) -> dict[str, Any]:
25+
current_project_path = self.get_current_project_def_path()
26+
return await self.get_project_raw_config(project_def_path=current_project_path)

0 commit comments

Comments
 (0)