9 Commits
dev ... v0.1.8

Author SHA1 Message Date
Gitea CI
ea68370451 Bump version: 0.1.7 → 0.1.8 2025-05-23 14:43:37 +00:00
2343d66fe2 Merge pull request 'fix: update changelog generator token to use GITHUB_TOKEN' (#8) from dev into main
Reviewed-on: #8
2025-05-23 15:43:10 +01:00
Gitea CI
386fa02ff9 Bump version: 0.1.6 → 0.1.7 2025-05-23 14:22:31 +00:00
4add66496b Merge pull request 'add git ghost identity' (#7) from dev into main
Reviewed-on: #7
2025-05-23 15:07:00 +01:00
b1432e8a85 Merge pull request 'change wf order to bump before attempting to push builds' (#6) from dev into main
Reviewed-on: #6
2025-05-23 15:01:59 +01:00
2a9958fbed Merge pull request 'update komconfig workflow' (#5) from dev into main
Reviewed-on: #5
2025-05-23 14:58:59 +01:00
7d438bcd3e Merge pull request 'dev' (#4) from dev into main
All checks were successful
/ build (push) Successful in 24s
Reviewed-on: #4
2025-05-18 18:06:56 +01:00
WorldTeacher
7090920424 Merge pull request 'bump to 0.1.5' (#2) from dev into main
Some checks failed
/ build (push) Failing after 1m5s
Reviewed-on: #2
2025-03-16 10:11:01 +00:00
WorldTeacher
f308c60525 Merge pull request 'use token instead of username/password' (#1) from dev into main
All checks were successful
/ build (push) Successful in 11s
Reviewed-on: #1
2025-03-16 10:08:58 +00:00
12 changed files with 80 additions and 428 deletions

24
.bumpversion.toml Normal file
View File

@@ -0,0 +1,24 @@
[tool.bumpversion]
current_version = "0.1.8"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
replace = "{new_version}"
regex = false
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = false
commit = true
message = "Bump version: {current_version} → {new_version}"
moveable_tags = []
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []
[[tool.bumpversion.files]]
filename = "pyproject.toml"

View File

@@ -1,103 +0,0 @@
{
"categories": [
{
"title": "## 🚀 Features",
"labels": [
"add",
"Add",
"Kind/Feature",
"feat",
"Feature",
"Feat"
]
},
{
"title": "## 🧰 Enhancements",
"labels": [
"enhancement",
"Enhancement",
"Kind/Enhancement",
"improvement",
"Improvement",
"Kind/Improvement"
]
},
{
"title": "## 🐛 Fixes",
"labels": [
"fix",
"Fix",
"Kind/Bug",
"Kind/Security"
]
},
{
"title": "## 🧪 Upgrade",
"labels": ["upgrade","Upgrade","Clean"]
}
,
{
"title": "## 📝 Documentation",
"labels": ["docs","Docs", "Kind/Documentation"]
},
{
"title": "## 🛠️ Maintenance",
"labels": [
"maintenance",
"Maintenance",
"Kind/Maintenance",
"chore",
"Chore",
"Kind/Chore"
]
},
{
"title": "## ⏪ Reverts",
"labels": [
"revert",
"Revert",
"Kind/Revert",
"Kind/Reverts",
"reverts",
"Reverts"
]
},
{
"title": "## 🗑️ Deprecation",
"labels": ["deprecation","Deprecation", "Kind/Deprecation"]
},
{
"title": "## ⚡️ Performance Improvements",
"labels": [
"perf",
"Perf",
"Kind/Performance"
]
},
{
"title": "## 🎨 Styling",
"labels": [
"style",
"Style",
"Kind/Style"
]
},
{
"title": "## 🎯 Other Changes",
"labels": []
}
],
"label_extractor": [
{
"pattern": "(\\w+) (.+)",
"target": "$1",
"on_property": "title"
}
],
"sort": "ASC",
"template": "${{CHANGELOG}}",
"pr_template": "- ${{TITLE}}\n - PR: #${{NUMBER}}",
"empty_template": "- no changes",
"max_pull_requests": 1000,
"max_back_track_time_days": 1000
}

View File

@@ -1,6 +1,10 @@
on:
workflow_dispatch:
inputs:
release_notes:
description: Release notes (use \n for newlines)
type: string
required: false
github_release:
description: 'Create Gitea Release'
default: true
@@ -25,8 +29,6 @@ jobs:
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv python install
with:
python-version-file: "pyproject.toml"
- name: Set Git identity
run: |
git config user.name "Gitea CI"
@@ -46,31 +48,37 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
- name: Build Changelog
id: build_changelog
uses: https://github.com/mikepenz/release-changelog-builder-action@v5
with:
platform: "gitea"
baseURL: "http://192.168.178.110:3000"
configuration: ".gitea/changelog-config.json"
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Add release notes to environment
id: add_release_notes
run: |
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
echo "${{ github.event.inputs.release_notes }}" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Build package
run: uv build
- name: Publish package
env:
USERNAME: ${{ github.repository_owner }}
run: uv publish --publish-url https://git.theprivateserver.de/api/packages/$USERNAME/pypi/ -t ${{ secrets.TOKEN }}
- name: Generate changelog
id: changelog
uses: metcalfc/changelog-generator@v4.6.2
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Get the changelog
run: |
cat << "EOF"
${{ steps.changelog.outputs.changelog }}
EOF
- name: Create release
id: create_release
if: ${{ github.event.inputs.github_release == 'true' }}
uses: softprops/action-gh-release@master
with:
tag_name: v${{ env.VERSION }}
tag_name: ${{ env.VERSION }}
release_name: Release ${{ env.VERSION }}
body: ${{steps.build_changelog.outputs.changelog}}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
make_latest: true

View File

@@ -1,7 +0,0 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

View File

@@ -1,9 +0,0 @@
from src.komconfig import KomConfig
def test_komconfig():
print(KomConfig().general.log_file)
if __name__ == "__main__":
test_komconfig()

View File

@@ -1,46 +1,15 @@
[project]
name = "komconfig"
version = "0.2.0"
version = "0.1.8"
description = "A small library providing a config class that provides settings data for the KomSuite"
readme = "README.md"
authors = [{ name = "WorldTeacher", email = "coding_contact@pm.me" }]
requires-python = ">=3.13"
dependencies = [
"appdirs>=1.4.4",
"omegaconf>=2.3.0",
]
dependencies = ["omegaconf>=2.3.0"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[dependency-groups]
dev = [
"pip>=25.1.1",
]
test = [
"pytest>=8.3.4",
"pytest-cov>=6.1.1",
]
[tool.bumpversion]
current_version = "0.2.0"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
replace = "{new_version}"
regex = false
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = true
commit = true
message = "Bump version: {current_version} → {new_version}"
moveable_tags = []
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []
test = ["pytest>=8.3.4"]

View File

@@ -1,38 +1,2 @@
__all__ = ["KomConfig", "app"]
from appdirs import AppDirs
app = AppDirs("KomSuite", "KomConfig")
import argparse
CONFIG_PATH = "~/.config/KomSuite/"
from .config import Settings as KomConfig
def main():
parser = argparse.ArgumentParser(description="Retrieve configuration values.")
parser.add_argument("key", type=str, help="The configuration key to retrieve.")
args = parser.parse_args()
config = KomConfig()
try:
config_dict = config.dict()
if isinstance(config_dict, dict):
value = config_dict.get(args.key, None)
if value is None:
print(f"Key '{args.key}' not found in the configuration.")
else:
try:
print(str(value))
except Exception:
print(repr(value))
else:
print("Configuration data is not in dictionary format.")
except Exception as e:
print(f"Error retrieving key '{args.key}': {e}")
if __name__ == "__main__":
main()

View File

@@ -1,30 +0,0 @@
import argparse
from src.komconfig import KomConfig
def main():
parser = argparse.ArgumentParser(description="Retrieve configuration values.")
parser.add_argument("key", type=str, help="The configuration key to retrieve.")
args = parser.parse_args()
config = KomConfig()
try:
config_dict = config.dict()
if isinstance(config_dict, dict):
value = config_dict.get(args.key, None)
if value is None:
print(f"Key '{args.key}' not found in the configuration.")
else:
try:
print(str(value))
except Exception:
print(repr(value))
else:
print("Configuration data is not in dictionary format.")
except Exception as e:
print(f"Error retrieving key '{args.key}': {e}")
if __name__ == "__main__":
main()

View File

@@ -1,69 +1,11 @@
import os
from dataclasses import dataclass
from typing import List, Optional
import os
from omegaconf import OmegaConf, DictConfig
from pathlib import Path
from typing import List, Optional, Union
from urllib.parse import quote_plus
from komconfig import CONFIG_PATH
from omegaconf import DictConfig, OmegaConf
from komconfig import app
SETTINGS_PATH = os.path.join(app.user_config_dir, "config.yaml")
@dataclass
class RemoteSettings:
url: str
port: int
user: str
password: str
database_name: str = "komcache"
@dataclass
class Cache:
mode: str
remote: dict[str, Union[str, int]] | RemoteSettings
local_path: str | Path
def __post__init__(self):
if self.mode == "remote":
self.remote = RemoteSettings(**self.remote)
else:
self.remote = None
self.local_path = Path(self.local_path).expanduser()
@property
def path(self):
if self.mode == "local":
return Path(self.local_path).expanduser()
else:
return f"{self.remote.url}:{self.remote.port}"
@property
def url(self):
password = quote_plus(self.remote.password) if self.remote else ""
return f"mysql+pymysql://{self.remote.user}:{password}@{self.remote.url}:{self.remote.port}/{self.remote.database_name}"
@dataclass
class Library:
"""Komga library settings."""
id: Optional[str] = None
name: Optional[str] = None
type: Optional[str] = None
media_path: Optional[str] = None
valid_extensions: Optional[List[str]] = None
def from_ddict(self, d: dict):
keys = d.keys()
for key in keys:
self.name = key
data = d[self.name]
for key, value in data.items():
setattr(self, key, value)
return self
SETTINGS_PATH = os.path.join(CONFIG_PATH, "config.yaml")
@dataclass
@@ -73,9 +15,9 @@ class Komga:
url: str
user: str
password: str
media_path: Path
media_path: str
api_key: str = None
libraries: List[Library] = None
libraries: dict[str, str] = None
def getattr(self, name):
return getattr(self, name)
@@ -85,24 +27,7 @@ class Komga:
def __post_init__(self):
if "~" in self.media_path:
self.media_path = Path(os.path.expanduser(self.media_path))
if self.libraries:
self.libraries = [Library().from_ddict(lib) for lib in self.libraries]
def getLibraryByName(self, name: str) -> Optional[Library]:
"""Get a library by its name.
Args:
name (str): The name of the library.
Returns:
Optional[Library]: The library object if found, None otherwise.
"""
if self.libraries:
for lib in self.libraries:
if lib.name == name:
return lib
return None
self.media_path = os.path.expanduser(self.media_path)
@dataclass
@@ -136,23 +61,6 @@ class Aria2:
setattr(self, name, value)
@dataclass
class QbitTorrent:
"""QbitTorrent settings."""
host: str
port: int
username: str
password: str
category: str
def getattr(self, name):
return getattr(self, name)
def _setattr(self, name, value):
setattr(self, name, value)
@dataclass
class EbookSettings:
min_filesize: int
@@ -225,35 +133,22 @@ class KomGrabber:
"""
download_location: Path
tag_location: Path
copy_location: Optional[Path]
copy_files: Optional[List[str]]
copy: bool
get_chapters: bool
skip_parameters: List[str]
aria2: Aria2
tag_interactive: bool
ebook: EbookSettings
manga: ComicSettings
check_interval: int
use_cache: bool
cache_check_interval: int
downloader: str
downloader_settings: Union[Aria2, QbitTorrent]
def __post_init__(self):
self.skip_parameters = [param.lower() for param in self.skip_parameters]
# if "~" in self.download_location:
# self.download_location = os.path.expanduser(self.download_location)
if "~" in self.download_location:
self.download_location = os.path.expanduser(self.download_location)
if isinstance(self.download_location, str):
self.download_location = Path(self.download_location).expanduser()
if isinstance(self.tag_location, str):
self.tag_location = Path(self.tag_location).expanduser()
if isinstance(self.downloader_settings, dict):
if self.downloader == "aria2":
self.downloader_settings = Aria2(**self.downloader_settings)
elif self.downloader == "qbit":
self.downloader_settings = QbitTorrent(**self.downloader_settings)
self.download_location = Path(self.download_location)
def getattr(self, name):
return getattr(self, name)
@@ -266,9 +161,9 @@ class KomGrabber:
class KomTagger:
"""KomTagger settings."""
failed_location: str
success_location: str
sanitize_description: bool
check_cache: bool
check_interval: int
def getattr(self, name):
return getattr(self, name)
@@ -277,15 +172,18 @@ class KomTagger:
setattr(self, name, value)
def __post_init__(self):
pass
if "~" in self.failed_location:
self.failed_location = os.path.expanduser(self.failed_location)
if "~" in self.success_location:
self.success_location = os.path.expanduser(self.success_location)
@dataclass
class ComicVine:
"""ComicVine settings."""
url: str
api_key: str
url: str
def getattr(self, name):
return getattr(self, name)
@@ -334,11 +232,17 @@ class API:
mangadex: MangaDex
comicsorg: ComicsOrg
def __post_init__(self):
# Convert dictionaries to their respective dataclass objects
self.comicvine = ComicVine(**self.comicvine)
self.mangadex = MangaDex(**self.mangadex)
self.comicsorg = ComicsOrg(**self.comicsorg)
@property
def comicvine(self):
return self.comicvine
@property
def mangadex(self):
return self.mangadex
@property
def comicsorg(self):
return self.comicsorg
def getattr(self, name):
return getattr(self, name)
@@ -425,10 +329,6 @@ class Settings:
def komtagger(self):
return KomTagger(**self._config.komtagger)
@property
def cache(self):
return Cache(**self._config.cache)
def komtagger_attr(self, name):
return getattr(self.komtagger, name)

View File

@@ -1,6 +0,0 @@
from src.komconfig import KomConfig
cfg = KomConfig()
print(cfg.komga.libraries)

View File

@@ -1,40 +0,0 @@
import pytest
from unittest.mock import patch, MagicMock
from src.komconfig.__init__ import main
import sys
def test_valid_key():
mock_config = MagicMock()
mock_config.dict.return_value = {"valid_key": "value"}
with patch("src.komconfig.__init__.KomConfig", return_value=mock_config):
with patch("sys.argv", ["__init__.py", "valid_key"]):
main()
def test_key_not_found():
mock_config = MagicMock()
mock_config.dict.return_value = {"another_key": "value"}
with patch("src.komconfig.__init__.KomConfig", return_value=mock_config):
with patch("sys.argv", ["__init__.py", "missing_key"]):
main()
def test_non_dict_config():
mock_config = MagicMock()
mock_config.dict.return_value = None
with patch("src.komconfig.__init__.KomConfig", return_value=mock_config):
with patch("sys.argv", ["__init__.py", "any_key"]):
main()
def test_exception_handling():
mock_config = MagicMock()
mock_config.dict.side_effect = Exception("Test Exception")
with patch("src.komconfig.__init__.KomConfig", return_value=mock_config):
with patch("sys.argv", ["__init__.py", "any_key"]):
main()

View File

@@ -1,18 +0,0 @@
from komconfig import KomConfig
import pytest
@pytest.fixture
def komconfig():
return KomConfig()
def test_komconfig(komconfig):
assert komconfig is not None
def test_general(komconfig):
assert KomConfig().general.log_file == "/var/log/komgrabber.log"
assert KomConfig().general.log_level == "INFO"