Skip to content

Commit

Permalink
Merge pull request #8 from AlTosterino/bug/7-init-process-does-not-work
Browse files Browse the repository at this point in the history
Fix initializing ADR
  • Loading branch information
AlTosterino authored Jul 25, 2024
2 parents 766b4a9 + 1468e55 commit 4a814e7
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 274 deletions.
434 changes: 212 additions & 222 deletions poetry.lock

Large diffs are not rendered by default.

27 changes: 12 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "adr"
version = "0.2.0"
version = "0.3.0"
description = "This Python script is designed to help software development teams document their architecture decisions using Architecture Decision Records (ADRs)."
authors = ["Daniel Różycki <altosterino@gmail.com>"]
readme = "README.md"
Expand All @@ -10,28 +10,23 @@ packages = [{include = "adrpy", from = "src"}]
adr = "adrpy.entrypoints.cli:cli_entrypoint"

[tool.poetry.dependencies]
python = "^3.11"
typer = "^0.7.0"
python = ">=3.11,<3.13"
typer = "^0.12.0"
loguru = "^0.7.0"
rich = "^13.3.3"
mako = "^1.2.4"
lidipy = "^0.1.1"
mako = "^1.3.0"
lidipy = "^0.3.0"

[tool.poetry.group.dev.dependencies]
black = "^23.3.0"
mypy = "^1.2.0"
pytest = "^7.3.0"
ruff = "^0.0.261"
typer-cli = "^0.0.13"
mypy = "^1.10.0"
pytest = "^8.2.0"
ruff = "^0.5.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

# ADR-py
[tool.adrpy]
dir = "docs/adr"

# THIRD PARTY
[tool.black]
line_length = 100
Expand All @@ -50,6 +45,8 @@ check_untyped_defs = true
ignore_missing_imports = true

[tool.ruff]
select = ["E", "F", "I", "PL"]
line-length = 100
target-version = "py311"
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I", "PL", "T20"]
19 changes: 12 additions & 7 deletions src/adrpy/entrypoints/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Optional
from typing import Annotated

import typer
from adrpy.injection import lidi
Expand All @@ -13,13 +13,15 @@

@app.command()
def init(
path: Optional[Path] = typer.Argument(
path: Path = typer.Argument(
None,
help=(
"Path in where ADRs should reside. "
"If not provided Path will be extracted from pyproject.toml"
"If not provided, Path will be extracted from pyproject.toml. "
"If no pyproject.toml is found, ADRs will be initialized in the current "
"working directory."
),
)
),
) -> None:
"""
Initialize ADR directory with first ADR in given PATH
Expand All @@ -33,9 +35,12 @@ def init(

@app.command()
def new(
name: str = typer.Argument(
..., help="Name of new ADR. Longer names (with spaces) should be put in quotation marks."
),
name: Annotated[
str,
typer.Argument(
help="Name of new ADR. Longer names (with spaces) should be put in quotation marks."
),
]
) -> None:
"""
Create new ADR with given NAME
Expand Down
12 changes: 6 additions & 6 deletions src/adrpy/injection/modules.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from adrpy.repositories.adr.base import BaseADRRepository
from adrpy.repositories.adr.repository import ADRFileRepository
from adrpy.services.template.base import BaseTemplateService
from adrpy.services.template.service import MakoTemplateService
from adrpy.shared_kernel.settings import Settings
from lidipy import Lidi


def bind_modules(lidi: Lidi) -> None:
lidi.bind(BaseADRRepository, ADRFileRepository(settings=lidi.resolve(Settings)))
from adrpy.repositories.adr.base import BaseADRRepository
from adrpy.repositories.adr.repository import ADRFileRepository
from adrpy.services.template.base import BaseTemplateService
from adrpy.services.template.service import MakoTemplateService

lidi.bind(BaseADRRepository, ADRFileRepository())
lidi.bind(BaseTemplateService, MakoTemplateService())
3 changes: 2 additions & 1 deletion src/adrpy/injection/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from adrpy.shared_kernel.settings import Settings
from lidipy import Lidi


def bind_settings(lidi: Lidi) -> None:
from adrpy.shared_kernel.settings import Settings

lidi.bind(Settings, Settings(), singleton=True)
4 changes: 2 additions & 2 deletions src/adrpy/repositories/adr/repository.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import re

from adrpy.injection import lidi
from adrpy.repositories.adr.base import BaseADRRepository
from adrpy.shared_kernel.settings import Settings
from adrpy.shared_kernel.value_objects.template import RenderedTemplate, Template


class ADRFileRepository(BaseADRRepository):
def __init__(self, settings: Settings) -> None:
self.settings = settings
settings = lidi.resolve_attr(Settings)

def get_template(self, name: str) -> Template:
template_path = self.settings.APP_TEMPLATES_DIR / name
Expand Down
26 changes: 18 additions & 8 deletions src/adrpy/shared_kernel/settings.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
from dataclasses import dataclass, field
from functools import cached_property
from pathlib import Path


@dataclass(frozen=True)
class Settings:
working_directory: Path = field(default_factory=Path.cwd)
initial_adr_dir: Path | None = None
# TODO: Add DEBUG logger handler
initial_adr_dir: Path | None = None # TODO: Rename to requested_adr_dir or something
APP_TEMPLATES_DIR: Path = field(init=False, default=Path(__file__).parents[1] / "templates")

@property
@cached_property
def adr_dir(self) -> Path | None:
# TODO: Handle missing dir
if self.initial_adr_dir:
return self.initial_adr_dir
return self.__get_adr_dir_from_pyproject()
if adr_dir_from_config := self.__get_adr_dir_from_config():
return adr_dir_from_config
return self.working_directory

def __get_adr_dir_from_pyproject(self) -> Path | None:
@cached_property
def working_directory(self) -> Path:
return Path.cwd()

def __get_adr_dir_from_config(self) -> Path | None:
import tomllib

with open("pyproject.toml", "rb") as f:
data = tomllib.load(f)
try:
with open(self.working_directory / "pyproject.toml", "rb") as f:
data = tomllib.load(f)
except FileNotFoundError:
# TODO): Add debug log here
return None
tools = data.get("tool", {})
adrpy_tool = tools.get("adrpy", {})
adrpy_dir = adrpy_tool.get("dir", None)
Expand Down
4 changes: 2 additions & 2 deletions src/adrpy/use_cases/creating.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

@dataclass
class CreatingADR:
template_service = lidi.resolve(BaseTemplateService)
adr_repository = lidi.resolve(BaseADRRepository)
template_service = lidi.resolve_attr(BaseTemplateService)
adr_repository = lidi.resolve_attr(BaseADRRepository)

def execute(self, dto: CreateADRDTO) -> None:
template = self.adr_repository.get_template(name=dto.adr_template_name)
Expand Down
6 changes: 2 additions & 4 deletions src/adrpy/use_cases/initializing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
from adrpy.repositories.adr.base import BaseADRRepository
from adrpy.services.template.base import BaseTemplateService
from adrpy.shared_kernel.dtos import InitializeADRDTO
from adrpy.shared_kernel.settings import Settings


@dataclass
class InitializingADR:
template_service = lidi.resolve(BaseTemplateService)
file_service = lidi.resolve(BaseADRRepository)
settings = lidi.resolve(Settings)
template_service = lidi.resolve_attr(BaseTemplateService)
file_service = lidi.resolve_attr(BaseADRRepository)
INITIAL_ADR_NAME: str = field(init=False, default="0001-record-architecture-decisions")

def execute(self, dto: InitializeADRDTO) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Iterator

import pytest
from adrpy.repositories.adr.repository import ADRFileRepository, BaseADRRepository
from adrpy.repositories.adr.repository import BaseADRRepository
from adrpy.shared_kernel.constants import AppTemplates
from adrpy.shared_kernel.settings import Settings
from adrpy.shared_kernel.value_objects.template import RenderedTemplate
Expand All @@ -15,12 +15,9 @@

@pytest.fixture()
def repo_service(lidi: Lidi) -> Iterator[BaseADRRepository]:
original_repo = lidi.resolve(BaseADRRepository)
original_settings = lidi.resolve(Settings)
new_settings = Settings(initial_adr_dir=TEST_DIRECTORY)
lidi.bind(BaseADRRepository, ADRFileRepository(settings=new_settings))
lidi.bind(Settings, Settings(initial_adr_dir=TEST_DIRECTORY))
yield lidi.resolve(BaseADRRepository)
lidi.bind(BaseADRRepository, original_repo)
lidi.bind(Settings, original_settings)


Expand Down Expand Up @@ -58,8 +55,7 @@ def test_should_get_template_file(repo_service: BaseADRRepository) -> None:
def test_should_create_file_in_nested_directories(lidi: Lidi) -> None:
# Given
nested_dir = TEST_DIRECTORY / "nested1" / "nested2"
new_settings = Settings(initial_adr_dir=nested_dir)
lidi.bind(BaseADRRepository, ADRFileRepository(settings=new_settings))
lidi.bind(Settings, Settings(initial_adr_dir=nested_dir))
rendered_template = RenderedTemplate(name=TEST_FILENAME, content="TEST_CONTENT")

# When
Expand Down
Empty file added tests/unit/__init__.py
Empty file.
74 changes: 74 additions & 0 deletions tests/unit/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from pathlib import Path
from typing import Generator
from unittest.mock import patch

import pytest
from adrpy.shared_kernel.settings import Settings

DIR_PATH = Path(__file__).parent
PYPROJECT_PATH = DIR_PATH / Path("pyproject.toml")
PYPROJECT_DATA = """
[tool.adrpy]
dir = "docs/adr"
"""
PYPROJECT_WRONG_DATA = """
[tool.adrpython]
dir = "docs/adr"
"""


@pytest.fixture(scope="module")
def correct_pyproject_toml() -> Generator[None, None, None]:
with open(PYPROJECT_PATH, "w") as pyproject:
pyproject.write(PYPROJECT_DATA)
yield
Path(PYPROJECT_PATH).unlink(missing_ok=True)


@pytest.fixture(scope="module")
def wrong_pyproject_toml() -> Generator[None, None, None]:
with open(PYPROJECT_PATH, "w") as pyproject:
pyproject.write(PYPROJECT_WRONG_DATA)
yield
Path(PYPROJECT_PATH).unlink(missing_ok=True)


def test_should_get_adr_dir_from_settings_when_no_initial_dir_set() -> None:
# Given
settings = Settings()

# When & Then
assert Path.cwd() == settings.adr_dir


def test_should_get_adr_dir_from_settings_when_initial_dir_set() -> None:
# Given
settings = Settings(initial_adr_dir=Path(__file__).parent)

# When & Then
assert settings.adr_dir == Path(__file__).parent


def test_should_get_adr_dir_from_pyproject_toml(correct_pyproject_toml: None) -> None:
# Given
with patch.object(Path, "cwd") as path_cwd_mock:
path_cwd_mock.return_value = DIR_PATH
settings = Settings()
adr_dir = settings.adr_dir

# When & Then
assert adr_dir == DIR_PATH / Path("docs/adr")


def test_should_fallback_wrong_adr_dir_from_pyproject_toml_to_working_directory(
wrong_pyproject_toml: None,
) -> None:
# TODO: Maybe this shouldn't fallback, but raise instead?
# Given
with patch.object(Path, "cwd") as path_cwd_mock:
path_cwd_mock.return_value = DIR_PATH
settings = Settings()
adr_dir = settings.adr_dir

# When & Then
assert adr_dir == DIR_PATH

0 comments on commit 4a814e7

Please sign in to comment.