8 Commits

7 changed files with 252 additions and 133 deletions

View File

@@ -1,21 +0,0 @@
[tool.bumpversion]
current_version = "0.1.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 = false
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = false
commit = false
message = "Bump version: {current_version} → {new_version}"
moveable_tags = []
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []

View File

@@ -0,0 +1,46 @@
{
"categories": [
{
"title": "## 🚀 Features",
"labels": ["add","Add", "Kind/Feature"]
},
{
"title": "## 🐛 Fixes",
"labels": ["fix","Fix", "Kind/Bug"]
},
{
"title": "## 🧪 Upgrade",
"labels": ["upgrade","Upgrade","Clean"]
}
,
{
"title": "## 📝 Documentation",
"labels": ["docs","Docs", "Kind/Documentation"]
},
{
"title": "## 🧹 Chore",
"labels": ["chore","Chore", "Kind/Chore"]
},
{
"title": "## 🛠️ Maintenance",
"labels": ["maintenance","Maintenance", "Kind/Maintenance"]
},
{
"title": "## 🗑️ Deprecation",
"labels": ["deprecation","Deprecation", "Kind/Deprecation"]
}
],
"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,10 +1,6 @@
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
release_notes:
description: Release notes (use \n for newlines)
type: string
required: false
github_release: github_release:
description: 'Create Gitea Release' description: 'Create Gitea Release'
default: true default: true
@@ -48,13 +44,15 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }} branch: ${{ github.ref }}
- name: Create release notes - name: Build Changelog
run: | id: build_changelog
mkdir release_notes uses: https://github.com/mikepenz/release-changelog-builder-action@v5
echo -e "${{ inputs.release_notes }}" >> release_notes/release_notes.md with:
echo "Release notes:" platform: "gitea"
cat release_notes/release_notes.md baseURL: "http://gitea:3000"
echo "" configuration: ".gitea/changelog-config.json"
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Build package - name: Build package
run: uv build run: uv build
- name: Publish package - name: Publish package
@@ -70,7 +68,7 @@ jobs:
with: with:
tag_name: ${{ env.VERSION }} tag_name: ${{ env.VERSION }}
release_name: Release ${{ env.VERSION }} release_name: Release ${{ env.VERSION }}
body_path: release_notes/release_notes.md body: ${{steps.build_changelog.outputs.changelog}}
draft: false draft: false
prerelease: false prerelease: false
make_latest: true make_latest: true

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "komcache" name = "komcache"
version = "0.1.0" version = "0.1.1"
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
authors = [ authors = [
@@ -9,6 +9,8 @@ authors = [
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"komconfig", "komconfig",
"pymysql>=1.1.1",
"sqlalchemy[asyncio]>=2.0.41",
] ]
[build-system] [build-system]
@@ -17,3 +19,25 @@ build-backend = "hatchling.build"
[tool.uv.sources] [tool.uv.sources]
komconfig = { workspace = true } komconfig = { workspace = true }
[tool.bumpversion]
current_version = "0.1.1"
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 = []

View File

@@ -1,7 +1,10 @@
import sqlite3 from typing import Any
from komconfig import KomConfig, CONFIG_PATH from sqlalchemy import create_engine, Column, String, Integer, Date, text
from pathlib import Path from sqlalchemy.orm import sessionmaker, declarative_base
import os from sqlalchemy.exc import SQLAlchemyError
from komcache.schemas.sqlite import CREATE_SQLITE_TABLES
from komcache.schemas.mariadb import CREATE_MARIADB_TABLES
from komconfig import KomConfig
import loguru import loguru
log = loguru.logger log = loguru.logger
@@ -9,130 +12,156 @@ log.remove()
log.add("logs/cache.log", level="INFO", rotation="15MB", retention="1 week") log.add("logs/cache.log", level="INFO", rotation="15MB", retention="1 week")
log.add("logs/cli.log", rotation="15MB", retention="1 week") # type:ignore log.add("logs/cli.log", rotation="15MB", retention="1 week") # type:ignore
config = KomConfig()
Base = declarative_base()
class KomGrabber(Base):
__tablename__ = "komgrabber"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
series_id = Column(String, nullable=False)
status = Column(String, nullable=False)
created_at = Column(Date, nullable=False)
updated_at = Column(Date, nullable=False)
last_checked = Column(Date, nullable=False)
complete = Column(Integer, nullable=False)
class KomCache: class KomCache:
def __init__(self, db_path: str = CONFIG_PATH): def __init__(self, db_path: str = ""): # Default to empty string if not provided
self.db_path = Path(db_path, "komcache.db") self.db_path = db_path or config.cache.path
if "~" in str(self.db_path): log.debug(f"Cache path: {self.db_path}")
self.db_path = os.path.expanduser(str(self.db_path)) if config.cache.mode == "local":
self.conn = sqlite3.connect(self.db_path) self.db_path = db_path or config.cache.path
self.cursor = self.conn.cursor() log.debug(f"Cache path: {self.db_path}")
self.engine = create_engine(f"sqlite:///{self.db_path}")
elif config.cache.mode == "remote":
db_url = (
config.cache.url
) # e.g., "mysql+pymysql://user:pass@host:3306/dbname"
log.debug(f"Using remote DB URL: {db_url}")
self.engine = create_engine(db_url)
def __enter__(self): self.Session = sessionmaker(bind=self.engine)
return self # if tables do not exist, create them
if config.cache.mode == "local":
if not self.query(
"SELECT name FROM sqlite_master WHERE type='table' AND name='komgrabber'"
):
self.create_table()
elif config.cache.mode == "remote":
if not self.query("SHOW TABLES LIKE 'komgrabber'"):
self.create_table()
def __exit__(self, exc_type, exc_val, exc_tb): def create_table(self):
self.conn.close() """Ensure all tables are created in the database."""
pass if config.cache.mode == "local":
log.debug("Creating SQLite tables")
with self.engine.begin() as connection:
log.debug(f"DB engine URL: {self.engine.url}")
def create_table(self, table_query: str) -> None: for table_sql in CREATE_SQLITE_TABLES:
""" connection.execute(text(table_sql))
Create a table in the database. elif config.cache.mode == "remote":
log.debug("Creating MariaDB tables")
with self.engine.begin() as connection:
log.debug(f"DB engine URL: {self.engine.url}")
Parameters for table_sql in CREATE_MARIADB_TABLES:
---------- log.debug(f"Executing table creation SQL: {table_sql}")
table_query : str try:
the SQL query to create the table. connection.execute(text(table_sql))
""" except Exception as e:
self.cursor.execute(table_query) log.exception(
self.conn.commit() f"Failed to execute table creation SQL:\n{table_sql}"
)
log.exception(f"Error: {e}")
# create the KomGrabber table using SQLAlchemy ORM
Base.metadata.create_all(self.engine)
def delete_table(self, table_name: str) -> bool: def delete_table(self, table_name: str) -> bool:
try: try:
self.cursor.execute(f"DROP TABLE IF EXISTS {table_name}") with self.engine.connect() as connection:
self.conn.commit() connection.execute(text(f"DROP TABLE IF EXISTS {table_name}"))
return True return True
except sqlite3.Error as e: except SQLAlchemyError as e:
print(f"Error deleting table {table_name}: {e}") log.error(f"Error deleting table {table_name}: {e}")
return False return False
def query_table(self, table: str, query: str) -> list: def query(self, query: str, args: dict[str, Any] = None):
if args is None:
args = {}
try: try:
self.cursor.execute(f"SELECT {query} FROM {table}") session = self.Session()
return self.cursor.fetchall() result = session.execute(text(query), args).fetchall()
except sqlite3.Error as e: session.close()
print(f"Error querying table {table}: {e}") return result
except SQLAlchemyError as e:
log.error(f"Error executing query: {e}")
return [] return []
def query(self, query, args=None): def insert(self, query: str, args: dict[str, Any]) -> bool:
if args is None:
args = []
try: try:
self.cursor.execute(query, args) session = self.Session()
data = self.cursor.fetchall() session.execute(text(query), args)
if data: session.commit()
return data session.close()
else:
return None
except sqlite3.Error as e:
print(f"Error executing query: {e}")
return []
def insert(self, query: str, args=None):
if args is None:
args = []
try:
self.cursor.execute(query, args)
self.conn.commit()
return True return True
except sqlite3.Error as e: except SQLAlchemyError as e:
print(f"Error inserting data: {e}") log.error(f"Error inserting data: {e}")
return False return False
def update(self, query, args=None): def update(self, query: str, args: dict[str, Any]) -> bool:
if args is None:
args = []
try: try:
self.cursor.execute(query, args) session = self.Session()
log.debug("Query: {}, Args: {}".format(query, args)) session.execute(text(query), args)
self.conn.commit() session.commit()
session.close()
return True return True
except sqlite3.Error as e: except SQLAlchemyError as e:
print(f"Error updating data: {e}") log.error(f"Error updating data: {e}")
return False return False
def get_last_update_date(self, series_name: str) -> str: def get_last_update_date(self, series_name: str) -> str:
""" try:
Get the last update date for a series. session = self.Session()
result = (
Parameters session.query(KomGrabber.last_update_date)
---------- .filter_by(series_name=series_name)
series_name : str .first()
The name of the series. )
session.close()
Returns return result[0] if result else ""
------- except SQLAlchemyError as e:
str log.error(f"Error fetching last update date: {e}")
The last update date.
"""
query = "SELECT last_update_date FROM komgrabber WHERE series_name = ?"
result = self.query(query, (series_name,))
if result:
return result[0][0]
return "" return ""
def fetch_one(self, query: str, args=None): def fetch_one(self, query: str, args: dict[str, Any] = None):
if args is None: if args is None:
args = [] args = {}
try: try:
self.cursor.execute(query, args) session = self.Session()
return self.cursor.fetchone() result = session.execute(text(query), args).fetchone()
except sqlite3.Error as e: session.close()
print(f"Error fetching one: {e}") return result
except SQLAlchemyError as e:
log.error(f"Error executing query: {e}")
return None return None
def fetch_all(self, query: str, args=None): def fetch_all(self, query: str, args: dict[str, Any] | None = None):
if args is None: if args is None:
args = [] args = {}
try: try:
self.cursor.execute(query, args) session = self.Session()
return self.cursor.fetchall() result = session.execute(text(query), args).fetchall()
except sqlite3.Error as e: session.close()
print(f"Error fetching one: {e}") return result
return None except SQLAlchemyError as e:
log.error(f"Error executing query: {e}")
return []
if __name__ == "__main__": if __name__ == "__main__":
# Example usage cache = KomCache()
with KomCache() as cache: cache.create_table()
cache.delete_table("komgrabber")

View File

@@ -0,0 +1,23 @@
CREATE_MARIADB_TABLES = ["""
CREATE TABLE IF NOT EXISTS manga_requests (
id INT AUTO_INCREMENT PRIMARY KEY,
manga_id INT,
grabbed TINYINT(1) DEFAULT 0
);""","""CREATE TABLE IF NOT EXISTS komgrabber (
id INT AUTO_INCREMENT PRIMARY KEY,
name TEXT NOT NULL,
series_id TEXT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_checked TIMESTAMP DEFAULT NULL,
completed TINYINT(1) DEFAULT 0
);""","""CREATE TABLE IF NOT EXISTS komtagger (
id INT AUTO_INCREMENT PRIMARY KEY,
series_id TEXT NOT NULL,
title TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_checked TIMESTAMP DEFAULT NULL,
status TEXT NOT NULL
);
"""]

View File

@@ -0,0 +1,20 @@
CREATE_SQLITE_TABLES = ["""
CREATE TABLE IF NOT EXISTS manga_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
manga_id INTEGER,
grabbed BOOLEAN DEFAULT 0);""","""CREATE TABLE IF NOT EXISTS komgrabber (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
series_id TEXT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_checked TIMESTAMP DEFAULT 0,
completed BOOLEAN DEFAULT 0);""","""CREATE TABLE IF NOT EXISTS komtagger (
id INTEGER PRIMARY KEY AUTOINCREMENT,
series_id TEXT NOT NULL,
title TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_checked TIMESTAMP DEFAULT 0,
status TEXT NOT NULL);
"""]