Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/fine_python_ruff/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# fine_python_ruff
6 changes: 6 additions & 0 deletions extensions/fine_python_ruff/fine_python_ruff/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .format_handler import RuffFormatHandler, RuffFormatHandlerConfig

__all__ = [
"RuffFormatHandler",
"RuffFormatHandlerConfig"
]
121 changes: 121 additions & 0 deletions extensions/fine_python_ruff/fine_python_ruff/format_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from __future__ import annotations

import dataclasses
import sys
from pathlib import Path

if sys.version_info < (3, 12):
from typing_extensions import override
else:
from typing import override

from finecode_extension_api import code_action
from finecode_extension_api.actions import format as format_action
from finecode_extension_api.interfaces import icache, icommandrunner, ilogger


@dataclasses.dataclass
class RuffFormatHandlerConfig(code_action.ActionHandlerConfig):
line_length: int = 88
indent_width: int = 4
quote_style: str = "double" # "double" or "single"
target_version: str = "py38" # minimum Python version
preview: bool = False


class RuffFormatHandler(
code_action.ActionHandler[format_action.FormatAction, RuffFormatHandlerConfig]
):
CACHE_KEY = "RuffFormatter"

def __init__(
self,
config: RuffFormatHandlerConfig,
context: code_action.ActionContext,
logger: ilogger.ILogger,
cache: icache.ICache,
command_runner: icommandrunner.ICommandRunner,
) -> None:
self.config = config
self.context = context
self.logger = logger
self.cache = cache
self.command_runner = command_runner

self.ruff_bin_path = Path(sys.executable).parent / "ruff"

@override
async def run(
self,
payload: format_action.FormatRunPayload,
run_context: format_action.FormatRunContext,
) -> format_action.FormatRunResult:
result_by_file_path: dict[Path, format_action.FormatRunFileResult] = {}
for file_path in payload.file_paths:
file_content, file_version = run_context.file_info_by_path[file_path]
try:
new_file_content = await self.cache.get_file_cache(
file_path, self.CACHE_KEY
)
result_by_file_path[file_path] = format_action.FormatRunFileResult(
changed=False, code=new_file_content
)
continue
except icache.CacheMissException:
pass

new_file_content, file_changed = await self.format_one(
file_path, file_content
)

# save for next handlers
run_context.file_info_by_path[file_path] = format_action.FileInfo(
new_file_content, file_version
)

await self.cache.save_file_cache(
file_path, file_version, self.CACHE_KEY, new_file_content
)
result_by_file_path[file_path] = format_action.FormatRunFileResult(
changed=file_changed, code=new_file_content
)

return format_action.FormatRunResult(result_by_file_path=result_by_file_path)

async def format_one(self, file_path: Path, file_content: str) -> tuple[str, bool]:
"""Format a single file using ruff format"""
# Build ruff format command
cmd = [
str(self.ruff_bin_path),
"format",
"--cache-dir",
str(self.context.cache_dir / ".ruff_cache"),
"--line-length",
str(self.config.line_length),
f'--config="indent-width={str(self.config.indent_width)}"',
f"--config=\"format.quote-style='{self.config.quote_style}'\"",
"--target-version",
self.config.target_version,
"--stdin-filename",
str(file_path),
]

if self.config.preview:
cmd.append("--preview")

cmd_str = " ".join(cmd)
ruff_process = await self.command_runner.run(cmd_str)

ruff_process.write_to_stdin(file_content)
ruff_process.close_stdin() # Signal EOF

await ruff_process.wait_for_end()

if ruff_process.get_exit_code() == 0:
new_file_content = ruff_process.get_output()
file_changed = new_file_content != file_content
return new_file_content, file_changed
else:
raise code_action.ActionFailedException(
f"ruff failed with code {ruff_process.get_exit_code()}: {ruff_process.get_error_output()} || {ruff_process.get_output()}"
)
Empty file.
449 changes: 0 additions & 449 deletions extensions/fine_python_ruff/poetry.lock

This file was deleted.

8 changes: 8 additions & 0 deletions extensions/fine_python_ruff/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
name = "fine_python_ruff"
version = "0.2.0"
description = ""
authors = [{ name = "Vladyslav Hnatiuk", email = "[email protected]" }]
readme = "README.md"
requires-python = ">=3.11, < 3.14"
dependencies = ["finecode_extension_api==0.3.*", "ruff (>=0.8.0,<1.0.0)"]
67 changes: 67 additions & 0 deletions extensions/fine_python_ruff/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import atexit
import shutil
import sys
import tempfile

from setuptools import setup
from setuptools.command.build import build
from setuptools.command.build_ext import build_ext
from setuptools.command.build_py import build_py
from setuptools.command.egg_info import egg_info

# Create a single temp directory for all build operations
_TEMP_BUILD_DIR = None


def get_temp_build_dir(pkg_name):
global _TEMP_BUILD_DIR
if _TEMP_BUILD_DIR is None:
_TEMP_BUILD_DIR = tempfile.mkdtemp(prefix=f"{pkg_name}_build_")
atexit.register(lambda: shutil.rmtree(_TEMP_BUILD_DIR, ignore_errors=True))
return _TEMP_BUILD_DIR


class TempDirBuildMixin:
def initialize_options(self):
super().initialize_options()
temp_dir = get_temp_build_dir(self.distribution.get_name())
self.build_base = temp_dir


class TempDirEggInfoMixin:
def initialize_options(self):
super().initialize_options()
temp_dir = get_temp_build_dir(self.distribution.get_name())
self.egg_base = temp_dir


class CustomBuild(TempDirBuildMixin, build):
pass


class CustomBuildPy(TempDirBuildMixin, build_py):
pass


class CustomBuildExt(TempDirBuildMixin, build_ext):
pass


class CustomEggInfo(TempDirEggInfoMixin, egg_info):
def initialize_options(self):
# Don't use temp dir for editable installs
if "--editable" in sys.argv or "-e" in sys.argv:
egg_info.initialize_options(self)
else:
super().initialize_options()


setup(
name="fine_python_ruff",
cmdclass={
"build": CustomBuild,
"build_py": CustomBuildPy,
"build_ext": CustomBuildExt,
"egg_info": CustomEggInfo,
},
)
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ def get_output(self) -> str: ...

def get_error_output(self) -> str: ...

def write_to_stdin(self, value: str) -> None: ...

def close_stdin(self) -> None: ...


class ISyncProcess(IProcess):
def wait_for_end(self, timeout: float | None = None) -> None: ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
# the runner, not from updating the config
project_dir_path: Path | None = None
log_level: Literal["TRACE", "INFO"] = "INFO"
env_name: str = ""
env_name: str = ""
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ def get_error_output(self) -> str:
else:
return self._stderr

def write_to_stdin(self, value: str) -> None:
if self.async_subprocess.stdin is not None:
self.async_subprocess.stdin.write(value.encode())
else:
raise RuntimeError("Process was not created with stdin pipe")

def close_stdin(self) -> None:
if self.async_subprocess.stdin is not None:
self.async_subprocess.stdin.close()
else:
raise RuntimeError("Process was not created with stdin pipe")


class SyncProcess(icommandrunner.ISyncProcess):
def __init__(self, popen: subprocess.Popen):
Expand Down Expand Up @@ -67,6 +79,19 @@ def get_error_output(self) -> str:
else:
return self._stderr

def write_to_stdin(self, value: str) -> None:
if self.popen.stdin is not None:
self.popen.stdin.write(value.encode())
self.popen.stdin.flush()
else:
raise RuntimeError("Process was not created with stdin pipe")

def close_stdin(self) -> None:
if self.popen.stdin is not None:
self.popen.stdin.close()
else:
raise RuntimeError("Process was not created with stdin pipe")


class CommandRunner(icommandrunner.ICommandRunner):
def __init__(self, logger: ilogger.ILogger):
Expand All @@ -79,6 +104,7 @@ async def run(
# TODO: investigate why it works only with shell, not exec
async_subprocess = await asyncio.create_subprocess_shell(
cmd,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=cwd,
Expand All @@ -93,6 +119,7 @@ def run_sync(
self.logger.debug(f"Sync subprocess run: {cmd_parts}")
async_subprocess = subprocess.Popen(
cmd_parts,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,13 @@ async def get_project_raw_config(
return json.loads(raw_config.config)


async def update_config(ls: lsp_server.LanguageServer, working_dir: pathlib.Path, project_name: str, config: dict[str, typing.Any]):
logger.trace(f'Update config: {working_dir} {project_name} {config}')
async def update_config(
ls: lsp_server.LanguageServer,
working_dir: pathlib.Path,
project_name: str,
config: dict[str, typing.Any],
):
logger.trace(f"Update config: {working_dir} {project_name} {config}")
try:
actions = config["actions"]
action_handler_configs = config["action_handler_configs"]
Expand Down Expand Up @@ -222,7 +227,12 @@ def default(self, obj):
return super().default(obj)


async def run_action(ls: lsp_server.LanguageServer, action_name: str, params: dict[str, typing.Any], options: dict[str, typing.Any] | None):
async def run_action(
ls: lsp_server.LanguageServer,
action_name: str,
params: dict[str, typing.Any],
options: dict[str, typing.Any] | None,
):
logger.trace(f"Run action: {action_name}")
request = schemas.RunActionRequest(action_name=action_name, params=params)
options_schema = schemas.RunActionOptions(**options if options is not None else {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@
from pydantic.dataclasses import dataclass as pydantic_dataclass

from finecode_extension_api import code_action, textstyler
from finecode_extension_runner import (
context,
domain,
global_state,
run_utils,
schemas,
)
from finecode_extension_runner import context, domain, global_state, run_utils, schemas
from finecode_extension_runner._services import run_action as run_action_module
from finecode_extension_runner._services.run_action import (
ActionFailedException,
Expand Down
13 changes: 2 additions & 11 deletions presets/fine_python_format/fine_python_format/preset.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
[tool.finecode.action.format]
source = "finecode_extension_api.actions.format.FormatAction"
handlers = [
{ name = "isort", source = "fine_python_isort.IsortFormatHandler", env = "dev_no_runtime", dependencies = [
"fine_python_black==0.2.*",
] },
{ name = "black", source = "fine_python_black.BlackFormatHandler", env = "dev_no_runtime", dependencies = [
"fine_python_isort==0.2.*",
{ name = "ruff", source = "fine_python_ruff.RuffFormatHandler", env = "dev_no_runtime", dependencies = [
"fine_python_ruff==0.1.*",
] },
{ name = "save", source = "finecode_extension_api.actions.format.SaveFormatHandler", env = "dev_no_runtime", dependencies = [
"finecode_extension_api==0.3.*",
] },
]

[[tool.finecode.action_handler]]
source = "fine_python_isort.IsortFormatHandler"
# make isort config compatible with black
# see https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#isort
config.profile = "black"
8 changes: 6 additions & 2 deletions src/finecode/cli_app/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,15 @@ async def run_actions_in_running_project(
result_format=services.RunResultFormat.STRING,
)
except services.ActionRunFailed as exception:
raise RunFailed(f"Running of action {action_name} failed: {exception.message}")
raise RunFailed(
f"Running of action {action_name} failed: {exception.message}"
)
except Exception as error:
logger.error("Unexpected exception")
logger.exception(error)
raise RunFailed(f"Running of action {action_name} failed with unexpected exception")
raise RunFailed(
f"Running of action {action_name} failed with unexpected exception"
)

run_result_str = run_result_to_str(run_result.result, action_name)
result_by_action[action_name] = ActionRunResult(
Expand Down
6 changes: 2 additions & 4 deletions src/finecode/runner/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ async def stop_extension_runner(runner: runner_info.ExtensionRunnerInfo) -> None
logger.debug("Sent exit to server")
await runner.client.stop()
logger.trace(
f"Stop extension runner {runner.process_id}"
f" in {runner.readable_id}"
f"Stop extension runner {runner.process_id}" f" in {runner.readable_id}"
)
else:
logger.trace("Extension runner was not running")
Expand All @@ -194,8 +193,7 @@ def stop_extension_runner_sync(runner: runner_info.ExtensionRunnerInfo) -> None:
runner_client.exit_sync(runner)
logger.debug("Sent exit to server")
logger.trace(
f"Stop extension runner {runner.process_id}"
f" in {runner.readable_id}"
f"Stop extension runner {runner.process_id}" f" in {runner.readable_id}"
)
else:
logger.trace("Extension runner was not running")
Expand Down
Loading
Loading