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
12 changes: 8 additions & 4 deletions extensions/fine_python_mypy/fine_python_mypy/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
icommandrunner,
ifilemanager,
ilogger,
iextensionrunnerinfoprovider,
iprojectinfoprovider
)


Expand Down Expand Up @@ -44,14 +46,16 @@ class MypyLintHandler(

def __init__(
self,
context: code_action.ActionContext,
extension_runner_info_provider: iextensionrunnerinfoprovider.IExtensionRunnerInfoProvider,
project_info_provider: iprojectinfoprovider.IProjectInfoProvider,
cache: icache.ICache,
logger: ilogger.ILogger,
file_manager: ifilemanager.IFileManager,
lifecycle: code_action.ActionHandlerLifecycle,
command_runner: icommandrunner.ICommandRunner,
) -> None:
self.context = context
self.extension_runner_info_provider = extension_runner_info_provider
self.project_info_provider = project_info_provider
self.cache = cache
self.logger = logger
self.file_manager = file_manager
Expand Down Expand Up @@ -189,7 +193,7 @@ async def run(
file_paths = [file_path async for file_path in payload]

files_by_projects: dict[Path, list[Path]] = self.group_files_by_projects(
file_paths, self.context.project_dir
file_paths, self.project_info_provider.get_current_project_dir_path()
)

for project_path, project_files in files_by_projects.items():
Expand Down Expand Up @@ -225,7 +229,7 @@ def exit(self) -> None:
self.logger.error(str(error))

def _get_status_file_path(self, dmypy_cwd: Path) -> Path:
file_dir_path = self.context.cache_dir
file_dir_path = self.extension_runner_info_provider.get_cache_dir_path()
# use hash to avoid name conflict if python packages have the same name
file_dir_path_hash = hashlib.md5(str(dmypy_cwd).encode("utf-8")).hexdigest()
file_path = (
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .list_project_files_by_lang_python import ListProjectFilesByLangPythonHandler
from .py_package_layout_info_provider import PyPackageLayoutInfoProvider

__all__ = [
"ListProjectFilesByLangPythonHandler",
"PyPackageLayoutInfoProvider"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from finecode_extension_api.interfaces import iprojectinfoprovider, ipypackagelayoutinfoprovider, ilogger
import dataclasses
import pathlib

from finecode_extension_api import code_action
from finecode_extension_api.actions import list_project_files_by_lang as list_project_files_by_lang_action


@dataclasses.dataclass
class ListProjectFilesByLangPythonHandlerConfig(code_action.ActionHandlerConfig):
# list of relative pathes relative to project directory with additional python
# sources if they are not in one of default pathes
additional_dirs: list[pathlib.Path] | None = None


class ListProjectFilesByLangPythonHandler(
code_action.ActionHandler[
list_project_files_by_lang_action.ListProjectFilesByLangAction, ListProjectFilesByLangPythonHandlerConfig
]
):
def __init__(
self,
config: ListProjectFilesByLangPythonHandlerConfig,
project_info_provider: iprojectinfoprovider.IProjectInfoProvider,
py_package_layout_info_provider: ipypackagelayoutinfoprovider.IPyPackageLayoutInfoProvider,
logger: ilogger.ILogger
) -> None:
self.config = config
self.project_info_provider = project_info_provider
self.py_package_layout_info_provider = py_package_layout_info_provider
self.logger = logger

self.current_project_dir_path = self.project_info_provider.get_current_project_dir_path()
self.tests_dir_path = self.current_project_dir_path / 'tests'
self.scripts_dir_path = self.current_project_dir_path / 'scripts'
self.setup_py_path = self.current_project_dir_path / 'setup.py'

async def run(
self,
payload: list_project_files_by_lang_action.ListProjectFilesByLangRunPayload,
run_context: list_project_files_by_lang_action.ListProjectFilesByLangRunContext,
) -> list_project_files_by_lang_action.ListProjectFilesByLangRunResult:
py_files: list[pathlib.Path] = []
project_package_src_root_dir_path = await self.py_package_layout_info_provider.get_package_src_root_dir_path(package_dir_path=self.current_project_dir_path)
py_files += list(project_package_src_root_dir_path.rglob('*.py'))

if self.scripts_dir_path.exists():
py_files += list(self.scripts_dir_path.rglob('*.py'))

if self.tests_dir_path.exists():
py_files += list(self.tests_dir_path.rglob('*.py'))

if self.setup_py_path.exists():
py_files.append(self.setup_py_path)

if self.config.additional_dirs is not None:
for dir_path in self.config.additional_dirs:
dir_absolute_path = self.current_project_dir_path / dir_path
if not dir_absolute_path.exists():
self.logger.warning(f"Skip {dir_path} because {dir_absolute_path} doesn't exist")
continue

py_files += list(dir_absolute_path.rglob('*.py'))

return list_project_files_by_lang_action.ListProjectFilesByLangRunResult(
files_by_lang={"python": py_files}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import enum
import pathlib

import tomlkit

from finecode_extension_api.interfaces import ifilemanager, ipypackagelayoutinfoprovider, icache
from finecode_extension_api import service


class ConfigParseError(Exception):
def __init__(self, message: str) -> None:
self.message = message


class PyPackageLayoutInfoProvider(ipypackagelayoutinfoprovider.IPyPackageLayoutInfoProvider, service.Service):
PACKAGE_NAME_CACHE_KEY = 'PyPackageLayoutInfoProviderPackageName'

def __init__(self, file_manager: ifilemanager.IFileManager, cache: icache.ICache) -> None:
self.file_manager = file_manager
self.cache = cache

async def _get_package_name(self, package_dir_path: pathlib.Path) -> str:
# raises ConfigParseError
package_def_file = package_dir_path / 'pyproject.toml'
if not package_def_file.exists():
raise NotImplementedError("Only python packages with pyproject.toml config file are supported")

try:
cached_package_name = await self.cache.get_file_cache(file_path=package_def_file, key=self.PACKAGE_NAME_CACHE_KEY)
return cached_package_name
except icache.CacheMissException:
...

package_def_file_content = await self.file_manager.get_content(file_path=package_def_file)
package_def_file_version = await self.file_manager.get_file_version(file_path=package_def_file)
try:
package_def_dict = tomlkit.loads(package_def_file_content)
except tomlkit.exceptions.ParseError as exception:
raise ConfigParseError(f"Failed to parse package config {package_def_file}: {exception.message} at {exception.line}:{exception.col}")

package_raw_name = package_def_dict.get('project', {}).get('name', None)
if package_raw_name is None:
raise ValueError(f"package.name not found in {package_def_file}")

package_name = package_raw_name.replace('-', '_')
await self.cache.save_file_cache(file_path=package_def_file, file_version=package_def_file_version, key=self.PACKAGE_NAME_CACHE_KEY, value=package_name)
return package_name

async def get_package_layout(self, package_dir_path: pathlib.Path) -> ipypackagelayoutinfoprovider.PyPackageLayout:
if (package_dir_path / 'src').exists():
return ipypackagelayoutinfoprovider.PyPackageLayout.SRC
else:
try:
package_name = await self._get_package_name(package_dir_path=package_dir_path)
except ConfigParseError as exception:
raise ipypackagelayoutinfoprovider.FailedToGetPackageLayout(exception.message)

if (package_dir_path / package_name).exists():
return ipypackagelayoutinfoprovider.PyPackageLayout.FLAT
else:
return ipypackagelayoutinfoprovider.PyPackageLayout.CUSTOM

async def get_package_src_root_dir_path(self, package_dir_path: str) -> pathlib.Path:
try:
package_layout = await self.get_package_layout(package_dir_path=package_dir_path)
except ipypackagelayoutinfoprovider.FailedToGetPackageLayout as exception:
raise ipypackagelayoutinfoprovider.FailedToGetPackageSrcRootDirPath(exception.message)

try:
package_name = await self._get_package_name(package_dir_path=package_dir_path)
except ConfigParseError as exception:
raise ipypackagelayoutinfoprovider.FailedToGetPackageSrcRootDirPath(exception.message)

if package_layout == ipypackagelayoutinfoprovider.PyPackageLayout.SRC:
return package_dir_path / 'src' / package_name
elif package_layout == ipypackagelayoutinfoprovider.PyPackageLayout.FLAT:
return package_dir_path / package_name
else:
raise NotImplementedError(f"Custom python package layout in {package_dir_path} is not supported")
8 changes: 8 additions & 0 deletions extensions/fine_python_package_info/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
name = "fine_python_package_info"
version = "0.1.0"
description = ""
authors = [{ name = "Vladyslav Hnatiuk", email = "aders1234@gmail.com" }]
readme = "README.md"
requires-python = ">=3.11, < 3.14"
dependencies = ["finecode_extension_api==0.3.*", "tomlkit==0.11.*"]
67 changes: 67 additions & 0 deletions extensions/fine_python_package_info/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_package_info",
cmdclass={
"build": CustomBuild,
"build_py": CustomBuildPy,
"build_ext": CustomBuildExt,
"egg_info": CustomEggInfo,
},
)
1 change: 1 addition & 0 deletions extensions/fine_python_package_info/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Tests for fine_python_package_info extension
3 changes: 2 additions & 1 deletion extensions/fine_python_pip/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fine_python_pip"
version = "0.1.0"
version = "0.1.1"
description = ""
authors = [{ name = "Vladyslav Hnatiuk", email = "aders1234@gmail.com" }]
readme = "README.md"
Expand All @@ -17,3 +17,4 @@ presets = [{ source = "finecode_dev_common_preset" }]
finecode_dev_common_preset = { path = "../../finecode_dev_common_preset", editable = true }
finecode = { path = "../../", editable = true }
finecode_extension_runner = { path = "../../finecode_extension_runner", editable = true }
finecode_extension_api = { path = "../../finecode_extension_api", editable = true }
1 change: 1 addition & 0 deletions extensions/fine_python_pip/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@dataclasses.dataclass
class PipInstallDepsInEnvHandlerConfig(code_action.ActionHandlerConfig):
find_links: list[str] | None = None
editable_mode: str | None = None


class PipInstallDepsInEnvHandler(
Expand Down Expand Up @@ -62,7 +63,10 @@ def _construct_pip_install_cmd(

if self.config.find_links is not None:
for link in self.config.find_links:
install_params += f' --find-links="{link}" '
install_params += f'--find-links="{link}" '

if self.config.editable_mode is not None:
install_params += f"--config-settings editable_mode='{self.config.editable_mode}' "

for dependency in dependencies:
if dependency.editable:
Expand Down
Loading
Loading