Skip to content

Commit

Permalink
Fix package resource file path bug.
Browse files Browse the repository at this point in the history
  • Loading branch information
eli64s committed Feb 26, 2024
1 parent 12c983d commit 4e2e83b
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 80 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "readmeai"
version = "0.5.061"
version = "0.5.062"
description = "👾 Automated README file generator, powered by large language model APIs."
authors = ["Eli <egsalamie@gmail.com>"]
license = "MIT"
Expand Down
39 changes: 19 additions & 20 deletions readmeai/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
from pydantic import BaseModel, DirectoryPath, HttpUrl, validator

from readmeai.config.enums import ModelOptions
from readmeai.config.utils import get_resource_path
from readmeai.config.validators import GitValidator
from readmeai.exceptions import FileReadError
from readmeai.utils.file_handler import FileHandler
from readmeai.utils.logger import Logger

_base_dir = Path(__file__).resolve().parent
_github_discussions = "https://github.com/eli64s/readme-ai/discussions"
_output_file = "readme-ai.md"
_logger = Logger(__name__)
Expand Down Expand Up @@ -121,34 +122,32 @@ class ConfigLoader:
def __init__(
self,
config_file: Union[str, Path] = "config.toml",
package: str = "readmeai/config",
submodule: str = "settings",
) -> None:
"""Initialize the ConfigLoader."""
self.package = package
self.submodule = submodule
self.config_path = f"{package}/{submodule}/{config_file}"
self.file_handler = FileHandler()
self.config_file = config_file
self.config = self._load_base_config()
self._load_all_configs()

def _load_base_config(self) -> Settings:
"""Load the base configuration file."""
config_dict = get_resource_path(self.file_handler, self.config_file)
return Settings.parse_obj(config_dict)

def _load_all_configs(self) -> None:
"""Load additional configuration files specified in the Settings."""
for key, file_name in self.config.files.__dict__.items():
for (
key,
file_name,
) in self.config.files.dict().items():
if not file_name.endswith(".toml"):
continue

file_path = _base_dir / self.submodule / file_name
log_path = f"{self.package}/{self.submodule}/{file_name}"

if file_path.exists():
config_data = FileHandler().read(file_path)
try:
config_data = get_resource_path(self.file_handler, file_name)
setattr(self, key, config_data)
_logger.debug(f"Loaded config file @ {log_path}")
else:
setattr(self, key, None)
_logger.warning(f"Config file not found: {log_path}")
_logger.debug(f"Loaded config file: {file_name}")

def _load_base_config(self) -> Settings:
"""Load the main configuration file for the readme-ai package."""
config_dict = FileHandler().read(self.config_path)
return Settings(**config_dict)
except FileReadError as exc:
setattr(self, key, None)
_logger.warning(f"Config file not found: {file_name} - {exc}")
56 changes: 56 additions & 0 deletions readmeai/config/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Utility methods to read package resources."""

from importlib import resources
from pathlib import Path

from readmeai.exceptions import FileReadError
from readmeai.utils.file_handler import FileHandler
from readmeai.utils.logger import Logger

_logger = Logger(__name__)


def get_resource_path(
handler: FileHandler,
file_path: str,
package: str = "readmeai.config",
submodule: str = "settings",
) -> dict:
"""Get configuration dictionary from TOML file."""
try:
resource_path = resources.files(package).joinpath(submodule, file_path)
_logger.debug(f"Loading file via importlib.resources: {resource_path}")

except TypeError as exc:
_logger.debug(f"Error using importlib.resources: {exc}")

try:
import pkg_resources

submodule = submodule.replace(".", "/")
resource_path = Path(
pkg_resources.resource_filename(
"readmeai", f"{submodule}/{file_path}"
)
).resolve()

except Exception as exc:
_logger.error(f"Error loading file via pkg_resources: {exc}")

raise FileReadError(
"Error loading file via pkg_resources", str(file_path)
) from exc

if not resource_path.exists():
_logger.error(f"File not found: {str(resource_path)}")
raise FileReadError("File not found", str(resource_path))

if str(file_path).endswith(".toml"):
return handler.read_toml(str(resource_path))

elif str(file_path).endswith(".json"):
return handler.read_json(str(resource_path))

_logger.error(f"Unsupported file format: {file_path}")

raise FileReadError("Unsupported file format", str(file_path))
25 changes: 0 additions & 25 deletions readmeai/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
from __future__ import annotations

import os
from importlib import resources
from pathlib import Path

from readmeai.config.enums import ModelOptions, SecretKeys
from readmeai.config.settings import ConfigLoader, Settings
from readmeai.exceptions import FileReadError
from readmeai.utils.logger import Logger

_logger = Logger(__name__)
Expand All @@ -33,29 +31,6 @@ def filter_file(config_loader: ConfigLoader, file_path: Path) -> bool:
return False


def get_resource_path(
file_name: str, module: str = "settings", package: str = "readmeai.config"
) -> Path:
"""
Get the resource path using importlib.resources for Python >= 3.9
or fallback to pkg_resources for older Python versions.
"""
try:
full_package_path = f"{package}.{module}".replace("/", ".")
resource_path = resources.files(full_package_path) / file_name
except Exception as exc:
_logger.debug(f"Error using importlib.resources: {exc}")
raise FileReadError(
"Error accessing resource using importlib.resources",
str(file_name),
) from exc

if not resource_path.exists():
raise FileReadError("File not found", str(resource_path))

return resource_path


def set_model_engine(config: Settings) -> None:
"""Set LLM environment variables based on the specified LLM service."""
llm_engine = config.llm.api
Expand Down
24 changes: 11 additions & 13 deletions readmeai/generators/badges.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

from readmeai.config.enums import BadgeOptions
from readmeai.config.settings import ConfigLoader
from readmeai.core.utils import get_resource_path
from readmeai.config.utils import get_resource_path
from readmeai.services.git import GitService
from readmeai.utils.file_handler import FileHandler

BASE_DIR = "readmeai"
BASE_SUBDIR = "generators.assets"
_package = "readmeai.generators"
_submodule = "assets"


def _format_badges(badges: list[str]) -> str:
Expand Down Expand Up @@ -67,13 +67,14 @@ def shields_icons(
conf: ConfigLoader, dependencies: list, full_name: str, git_host: str
) -> Tuple[str, str]:
"""
Generates badges for the README using shields.io icons, referencing
the repository - https://github.com/Aveek-Saha/GitHub-Profile-Badges.
Generates badges for the README using shields.io icons.
"""
file_path = get_resource_path(
conf.files.shields_icons, BASE_SUBDIR, BASE_DIR
icons_dict = get_resource_path(
FileHandler(),
conf.files.shields_icons,
_package,
_submodule,
)
icons_dict = FileHandler().read(file_path)

default_badges = build_default_badges(conf, full_name, git_host)

Expand Down Expand Up @@ -109,17 +110,14 @@ def skill_icons(conf: ConfigLoader, dependencies: list) -> str:
"""
dependencies.extend(["md"])

file_path = get_resource_path(
conf.files.skill_icons, BASE_SUBDIR, BASE_DIR
icons_dict = get_resource_path(
FileHandler(), conf.files.skill_icons, _package, _submodule
)
icons_dict = FileHandler().read(file_path)

skill_icons = [
icon for icon in icons_dict["icons"]["names"] if icon in dependencies
]
skill_icons = ",".join(skill_icons)
# per_line = (len(skill_icons) + 2) // 2
# icon_names = f"{icon_names}" # &perline={per_line}"
skill_icons = icons_dict["url"]["base_url"] + skill_icons

if conf.md.badge_style == "skills-light":
Expand Down
25 changes: 25 additions & 0 deletions tests/config/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tests for package resources utility methods."""

import pytest

from readmeai.config.utils import get_resource_path
from readmeai.exceptions import FileReadError
from readmeai.utils.file_handler import FileHandler


def test_get_resource_path():
"""Test that the resource path is returned correctly."""
try:
file_path = get_resource_path(
FileHandler(), "config.toml", "readmeai.config", "settings"
)
assert isinstance(file_path, dict)
except FileReadError as exc:
assert isinstance(exc, FileReadError)


def test_file_read_error():
"""Test that the FileReadError is raised correctly."""
with pytest.raises(Exception) as exc:
get_resource_path(FileHandler(), "config.yaml", "readmeai", "config")
assert isinstance(exc.value, FileReadError)
22 changes: 1 addition & 21 deletions tests/core/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@

import os
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from unittest.mock import patch

from readmeai.config.enums import ModelOptions, SecretKeys
from readmeai.config.settings import ConfigLoader
from readmeai.core.utils import (
FileReadError,
_scan_environ,
_set_offline,
filter_file,
get_resource_path,
set_model_engine,
)

Expand Down Expand Up @@ -181,19 +177,3 @@ def test_scan_environ_missing():
assert _scan_environ(keys) is False
keys = ("VERTEX_LOCATION", "VERTEX_PROJECT")
assert _scan_environ(keys) is False


@pytest.mark.skip
def test_get_resource_path_with_mock():
"""Test that the resource path is returned correctly using mock."""
mock_path = "config.toml"
with patch("pathlib.Path.exists", MagicMock(return_value=True)):
resource_path = get_resource_path(mock_path)
assert isinstance(resource_path, Path)


def test_file_read_error():
"""Test that the FileReadError is raised correctly."""
with pytest.raises(Exception) as exc:
get_resource_path("non_existent_file.toml")
assert isinstance(exc.value, FileReadError)

0 comments on commit 4e2e83b

Please sign in to comment.