Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small refactor #5

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ LINT_PATHS = \
$(SRC_PATH) \
$(TESTS_PATH)

sync-deps:
poetry install

update-deps:
poetry update

lint:
poetry run black $(LINT_PATHS)
poetry run ruff check $(LINT_PATHS) --fix
Expand All @@ -16,4 +22,4 @@ lint-ci:
poetry run mypy $(LINT_PATHS)

test:
poetry run pytest
poetry run pytest -s
434 changes: 212 additions & 222 deletions poetry.lock

Large diffs are not rendered by default.

33 changes: 18 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 <[email protected]>"]
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,14 @@ 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"]


[tool.pytest.ini_options]
addopts = ["--verbose"]
pythonpath = ["src", "tests"]
testpaths = ["tests"]
33 changes: 19 additions & 14 deletions src/adrpy/entrypoints/cli.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
from pathlib import Path
from typing import Optional
from typing import Annotated

import typer
from adrpy.injection import lidi
from adrpy.shared_kernel.dtos import CreateADRDTO, InitializeADRDTO
from adrpy.shared_kernel.dtos import CreateAdrDto, InitializeAdrDto
from adrpy.shared_kernel.settings import Settings
from adrpy.use_cases.creating import CreatingADR
from adrpy.use_cases.initializing import InitializingADR
from adrpy.use_cases.create import CreateAdr
from adrpy.use_cases.initialize import InitializeAdr

app = typer.Typer()


@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
"""
if path:
new_settings = Settings(initial_adr_dir=path)
lidi.bind(Settings, new_settings, singleton=True)
dto = InitializeADRDTO(path=path)
InitializingADR().execute(dto=dto)
dto = InitializeAdrDto(path=path)
InitializeAdr.execute(dto=dto)


@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
"""
dto = CreateADRDTO(name=name)
CreatingADR().execute(dto=dto)
dto = CreateAdrDto(name=name)
CreateAdr.execute(dto=dto)


def cli_entrypoint() -> None:
Expand Down
14 changes: 7 additions & 7 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)))
lidi.bind(BaseTemplateService, MakoTemplateService())
from adrpy.repositories.adr.base import IADRRepository
from adrpy.repositories.adr.repository import ADRFileRepository
from adrpy.services.template.base import ITemplateService
from adrpy.services.template.service import MakoTemplateService

lidi.bind(IADRRepository, ADRFileRepository())
lidi.bind(ITemplateService, 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)
2 changes: 1 addition & 1 deletion src/adrpy/repositories/adr/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# TODO: Add example how to use adrpy with custom DatabaseRepository


class BaseADRRepository(ABC):
class IADRRepository(ABC):
@abstractmethod
def get_template(self, name: str) -> Template: # TODO: Rename to get_app_template
...
Expand Down
23 changes: 12 additions & 11 deletions src/adrpy/repositories/adr/repository.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import re
from typing import Final

from adrpy.repositories.adr.base import BaseADRRepository
from adrpy.injection import lidi
from adrpy.repositories.adr.base import IADRRepository
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
class ADRFileRepository(IADRRepository):
SETTINGS: Final[Settings] = lidi.resolve_attr(Settings)

def get_template(self, name: str) -> Template:
template_path = self.settings.APP_TEMPLATES_DIR / name
# TODO: Move `get_template` to `ITemplateService` or create persistence repository/facade
template_path = self.SETTINGS.APP_TEMPLATES_DIR / name
with open(template_path, "r") as file:
content = file.read()
return Template(name=name, content=content)

def create(self, adr_name: str, template: RenderedTemplate) -> None:
self.settings.adr_dir.mkdir(parents=True, exist_ok=True)
with open(
self.settings.adr_dir / self.__get_filename_with_extension(name=adr_name), "w"
) as file:
self.SETTINGS.adr_dir.mkdir(parents=True, exist_ok=True)
new_adr_path = self.SETTINGS.adr_dir / self.__get_filename_with_extension(name=adr_name)
with open(new_adr_path, "w") as file:
file.write(template.content)

def get_next_ordinal_number(self) -> int:
ordinal_number = 0
for path in self.settings.adr_dir.glob("*.md"):
for path in self.SETTINGS.adr_dir.glob("*.md"):
filename = path.stem
maybe_number_prefix = re.findall("\d+", filename)
maybe_number_prefix = re.findall(r"\d+", filename)
if not maybe_number_prefix:
continue
if (prefix_as_int := int(maybe_number_prefix[0])) > ordinal_number:
Expand Down
2 changes: 1 addition & 1 deletion src/adrpy/services/template/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from adrpy.shared_kernel.value_objects.template import RenderedTemplate, Template


class BaseTemplateService(ABC):
class ITemplateService(ABC):
@abstractmethod
def render(self, template_file: Template, data: dict) -> RenderedTemplate:
...
4 changes: 2 additions & 2 deletions src/adrpy/services/template/service.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from adrpy.services.template.base import BaseTemplateService
from adrpy.services.template.base import ITemplateService
from adrpy.shared_kernel.value_objects.template import RenderedTemplate, Template
from mako.template import Template as MakoTemplate


class MakoTemplateService(BaseTemplateService):
class MakoTemplateService(ITemplateService):
def render(self, template_file: Template, data: dict) -> RenderedTemplate:
mako_template = MakoTemplate(template_file.content)
mako_render = mako_template.render(**data)
Expand Down
4 changes: 2 additions & 2 deletions src/adrpy/shared_kernel/dtos.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@


@dataclass(frozen=True)
class InitializeADRDTO:
class InitializeAdrDto:
path: Path | None
adr_template_name: str = field(default=AppTemplates.INITIAL_ADR, init=False)


@dataclass(frozen=True)
class CreateADRDTO:
class CreateAdrDto:
name: str
adr_template_name: str = field(default=AppTemplates.NEW_ADR, init=False)

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
6 changes: 6 additions & 0 deletions src/adrpy/templates/initial-adr.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
title: ...
tags: ...

---

# 1. Record architecture decisions

Date: ${ date_created.strftime("%d/%m/%Y %H:%M") }
Expand Down
28 changes: 28 additions & 0 deletions src/adrpy/use_cases/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from datetime import datetime
from typing import Final

from adrpy.injection import lidi
from adrpy.repositories.adr.base import IADRRepository
from adrpy.services.template.base import ITemplateService
from adrpy.shared_kernel.dtos import CreateAdrDto


class CreateAdr:
TEMPLATE_SERVICE: Final[ITemplateService] = lidi.resolve_attr(ITemplateService)
REPOSITORY: Final[IADRRepository] = lidi.resolve_attr(IADRRepository)

@classmethod
def execute(cls, dto: CreateAdrDto) -> None:
template = cls.REPOSITORY.get_template(name=dto.adr_template_name)
ordinal_number = cls.REPOSITORY.get_next_ordinal_number()
rendered_template = cls.TEMPLATE_SERVICE.render(
template_file=template,
data={
"date_created": datetime.now(),
"status": "ACCEPTED",
"name": dto.name,
"ordinal_num": ordinal_number,
},
)
adr_name = dto.adr_name_with_ordinal(ordinal_number=ordinal_number)
cls.REPOSITORY.create(adr_name=adr_name, template=rendered_template)
28 changes: 0 additions & 28 deletions src/adrpy/use_cases/creating.py

This file was deleted.

Loading
Loading