Skip to content

Commit

Permalink
Add: Easier setup by writing the settings during activation #345
Browse files Browse the repository at this point in the history
Improve installation, setup and usage of autohooks activate by writing the settings to the pyproject.toml file.
With this change the user doesn't need to change the pyproject.toml file manually during the setup.
  • Loading branch information
bjoernricks committed Aug 11, 2022
2 parents 4220c1c + 4cbe779 commit 5f352a8
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 107 deletions.
27 changes: 17 additions & 10 deletions autohooks/cli/activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
load_config_from_pyproject_toml,
)
from autohooks.hooks import PreCommitHook
from autohooks.settings import Mode
from autohooks.settings import AutohooksSettings, Mode
from autohooks.terminal import Terminal


Expand All @@ -47,20 +47,27 @@ def install_hooks(term: Terminal, args: Namespace) -> None:
"the installed pre-commit hook."
)
else:
if not config.is_autohooks_enabled():
term.warning(
f"autohooks is not enabled in your {str(pyproject_toml)} "
"file. Run 'autohooks check' for more details."
)

if args.mode:
mode = Mode.from_string(args.mode)
else:
mode = config.get_mode()
mode = config.get_mode().get_effective_mode()

if not config.has_autohooks_config():
settings = AutohooksSettings(mode=mode)
config.settings = settings
settings.write(pyproject_toml)

term.ok(f"autohooks settings written to {pyproject_toml}.")
elif args.force:
settings = config.settings
settings.mode = mode
settings.write(pyproject_toml)

term.ok(f"autohooks settings written to {pyproject_toml}.")

pre_commit_hook.write(mode=mode)

term.ok(
f"autohooks pre-commit hook installed at {str(pre_commit_hook)}"
f" using {str(mode.get_effective_mode())} mode."
f"autohooks pre-commit hook installed at {pre_commit_hook}"
f" using {mode} mode."
)
2 changes: 1 addition & 1 deletion autohooks/cli/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def check_config(
)
else:
config = load_config_from_pyproject_toml(pyproject_toml)
if not config.is_autohooks_enabled():
if not config.has_autohooks_config():
term.error(
f"autohooks is not enabled in your {str(pyproject_toml)} file."
f' Please add a "{AUTOHOOKS_SECTION}" section.'
Expand Down
130 changes: 84 additions & 46 deletions autohooks/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from pathlib import Path
from typing import Any, Dict, List, Union
from typing import Any, Dict, List, Optional, Union

import tomlkit

from autohooks.settings import Mode
from autohooks.settings import AutohooksSettings, Mode
from autohooks.utils import get_pyproject_toml_path, is_split_env

AUTOHOOKS_SECTION = "tool.autohooks"
Expand All @@ -45,64 +45,102 @@ def is_empty(self) -> bool:
return not bool(self._config_dict)


class BaseToolConfig:
def __init__(self, config_dict: Dict = None) -> None:
self._config = Config(config_dict)

def has_config(self) -> bool:
return not self._config.is_empty()
def _gather_mode(mode: Optional[str]) -> Mode:
"""
Gather the mode from a mode string
"""
mode = Mode.from_string(mode)
is_virtual_env = mode == Mode.PIPENV or mode == Mode.POETRY
if is_virtual_env and not is_split_env():
if mode == Mode.POETRY:
mode = Mode.POETRY_MULTILINE
else:
mode = Mode.PIPENV_MULTILINE
return mode


class AutohooksConfig:
def __init__(
self,
*,
settings: Optional[AutohooksSettings] = None,
config: Optional[Config] = None,
) -> None:
self.config = Config() if config is None else config
self.settings = settings

def get_config(self) -> Config:
return self._config


class AutohooksConfig(BaseToolConfig):
def __init__(self, config_dict: Dict = None) -> None:
super().__init__(config_dict)
self._autohooks_config = self._config.get("tool").get("autohooks")
return self.config

def has_autohooks_config(self) -> bool:
return not self._autohooks_config.is_empty()

def is_autohooks_enabled(self) -> bool:
return self.has_autohooks_config()
return self.settings is not None

def get_pre_commit_script_names(self) -> List[str]:
if self.has_autohooks_config():
return self._autohooks_config.get_value("pre-commit", [])

return []
return self.settings.pre_commit if self.has_autohooks_config() else []

def get_mode(self) -> Mode:
if self.has_autohooks_config():
mode = self._autohooks_config.get_value("mode")
if not mode:
return Mode.UNDEFINED

mode = Mode.from_string(mode.upper())
is_virtual_env = mode == Mode.PIPENV or mode == Mode.POETRY
if is_virtual_env and not is_split_env():
if mode == Mode.POETRY:
mode = Mode.POETRY_MULTILINE
else:
mode = Mode.PIPENV_MULTILINE
return mode

return Mode.UNDEFINED
return (
self.settings.mode
if self.has_autohooks_config()
else Mode.UNDEFINED
)

@staticmethod
def from_pyproject_toml(pyproject_toml: Path = None) -> "AutohooksConfig":
if pyproject_toml is None:
pyproject_toml = get_pyproject_toml_path()
def from_dict(config_dict: Dict[str, Any]) -> "AutohooksConfig":
"""
Create a new AutohooksConfig from a dictionary
Args:
config_data: A dictionary containing the config data
Returns:
A new AutohooksConfig
"""
config = Config(config_dict)
autohooks_dict = config.get("tool", "autohooks")
if autohooks_dict.is_empty():
settings = None
else:
settings = AutohooksSettings(
mode=_gather_mode(autohooks_dict.get_value("mode")),
pre_commit=autohooks_dict.get_value("pre-commit", []),
)
return AutohooksConfig(settings=settings, config=config)

if not pyproject_toml.exists():
return AutohooksConfig()
@staticmethod
def from_toml(toml_file: Path) -> "AutohooksConfig":
"""
Load an AutohooksConfig from a TOML file
Args:
toml_file: Path for the toml file to load
config_dict = tomlkit.loads(pyproject_toml.read_text())
return AutohooksConfig(config_dict)
Returns:
A new AutohooksConfig
"""
config_dict = tomlkit.loads(toml_file.read_text())
return AutohooksConfig.from_dict(config_dict)


def load_config_from_pyproject_toml(
pyproject_toml: Path = None,
) -> AutohooksConfig:
return AutohooksConfig.from_pyproject_toml(pyproject_toml)
"""
Load an AutohooksConfig from a pyproject.toml file
If no path to the pyproject.toml file is passed the path will be determined
from the current working directory and the project.
Args:
pyproject_toml: Path to the pyproject.toml file.
Returns:
A new AutohooksConfig
"""
if pyproject_toml is None:
pyproject_toml = get_pyproject_toml_path()

if not pyproject_toml.exists():
return AutohooksConfig()

return AutohooksConfig.from_toml(pyproject_toml)
39 changes: 38 additions & 1 deletion autohooks/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Iterable, Optional

import tomlkit


class Mode(Enum):
Expand All @@ -40,7 +45,7 @@ def get_effective_mode(self):
return Mode.PYTHONPATH

@staticmethod
def from_string(modestring: str) -> "Mode":
def from_string(modestring: Optional[str]) -> "Mode":
if not modestring:
return Mode.UNDEFINED

Expand All @@ -51,3 +56,35 @@ def from_string(modestring: str) -> "Mode":

def __str__(self) -> str:
return self.name.lower() # pylint: disable=no-member


@dataclass
class AutohooksSettings:
mode: Mode = Mode.UNDEFINED
pre_commit: Iterable[str] = field(default_factory=list)

def write(self, filename: Path) -> None:
"""
Write the current AutohooksSettings to a TOML file
If the TOML file already exists only the [tool.autohooks] section is
overridden.
"""
if filename.exists():
toml_doc = tomlkit.loads(filename.read_text())
else:
toml_doc = tomlkit.document()

if "tool" not in toml_doc:
toml_doc["tool"] = tomlkit.table(is_super_table=True)
if "autohooks" not in toml_doc["tool"]:
toml_doc["tool"]["autohooks"] = tomlkit.table()

config_dict = {
"mode": str(self.mode.get_effective_mode()),
"pre-commit": self.pre_commit,
}

toml_doc["tool"]["autohooks"].update(config_dict)

filename.write_text(tomlkit.dumps(toml_doc), encoding="utf8")
10 changes: 9 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import tempfile
from contextlib import contextmanager
from pathlib import Path
from typing import Generator
from typing import Generator, Optional

from autohooks.utils import exec_git

Expand Down Expand Up @@ -51,3 +51,11 @@ def tempgitdir() -> Generator[Path, None, None]:
yield temp_path

temp_dir.cleanup()


@contextmanager
def testfile(content: str, *, name: Optional[str] = "test.toml") -> Path:
with tempdir() as tmp_dir:
test_file = tmp_dir / name
test_file.write_text(content, encoding="utf8")
yield test_file
32 changes: 22 additions & 10 deletions tests/cli/test_activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,39 @@ def test_install_hooks_force(self):

term = MagicMock()
args = Namespace(force=True, mode=None)

install_hooks(term, args)

term.warning.assert_not_called()
term.ok.assert_called_once_with(
f"autohooks pre-commit hook installed at {tmpdir}/"
".git/hooks/pre-commit using poetry mode."
term.ok.assert_has_calls(
(
call(f"autohooks settings written to {pyproject_toml}."),
call(
f"autohooks pre-commit hook installed at {pre_commit}"
" using poetry mode."
),
)
)

def test_install_no_config(self):
with tempgitdir() as tmpdir:
term = MagicMock()
args = Namespace(force=False, mode=None)

install_hooks(term, args)

term.warning.assert_called_once_with(
f"autohooks is not enabled in your {tmpdir}/pyproject.toml "
"file. Run 'autohooks check' for more details."
)
term.ok.assert_called_once_with(
f"autohooks pre-commit hook installed at {tmpdir}/"
".git/hooks/pre-commit using pythonpath mode."
term.warning.assert_not_called()
term.ok.assert_has_calls(
(
call(
"autohooks settings written to "
f"{tmpdir}/pyproject.toml."
),
call(
f"autohooks pre-commit hook installed at {tmpdir}/"
".git/hooks/pre-commit using pythonpath mode."
),
)
)

def test_install_hooks_with_mode(self):
Expand Down
Loading

0 comments on commit 5f352a8

Please sign in to comment.