update codebase

This commit is contained in:
2025-04-17 10:57:57 +02:00
parent e685c7b930
commit 759c01380f
37 changed files with 378 additions and 252 deletions

7
.vscode/settings.json vendored Normal file
View File

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

View File

@@ -7,8 +7,19 @@ authors = [
{ name = "WorldTeacher", email = "coding_contact@pm.me" }
]
requires-python = ">=3.13"
dependencies = []
dependencies = [
"komconfig",
"typing-extensions>=4.12.2",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[dependency-groups]
test = [
"pytest>=8.3.4",
]
[tool.uv.sources]
komconfig = { workspace = true }

View File

@@ -3,6 +3,14 @@ from komgapi.errors import KomgaError, LoginError, ResultErrror
from typing import Any, Union
from limit import limit
import loguru
import sys
log = loguru.logger
log.remove()
log.add("logs/komga_api.log", rotation="1 week", retention="1 month")
log.add(sys.stdout, level="INFO")
class BaseAPI:
def __init__(self, username, password, url, timeout=20, api_version=1) -> None:
@@ -39,15 +47,16 @@ class BaseAPI:
if params is None:
params = {}
try:
# ic(url, params)
response = requests.get(
url,
auth=(self._username, self._password),
params=params,
timeout=self.timeout,
)
response.raise_for_status()
if response.status_code != 200:
self.getRequest(url, params)
# print(response.content)
log.debug(f"Response: {response.content}")
return response.json()
except ConnectionError as e:
message = f"Connection Error: {e}"
@@ -55,22 +64,37 @@ class BaseAPI:
except requests.exceptions.Timeout as e:
raise KomgaError(f"Timeout Error: {e}") from e
def postRequest(self, url, data: Union[dict, None] = None):
def postRequest(self, url, data: Union[dict, None] = None, body: dict = None):
if data is None:
data = {}
try:
if body is not None:
response = requests.post(
url,
auth=(self._username, self._password),
json=body,
params=data,
timeout=self.timeout,
)
else:
response = requests.post(
url,
auth=(self._username, self._password),
json=data,
timeout=self.timeout,
params=data,
)
response.raise_for_status()
status_code = response.status_code
if status_code != 202:
raise ResultErrror(f"Result Error: {response.json()}")
if status_code == 202:
log.debug(f"Response: {response}")
# raise ResultErrror(f"Result Error: {response}")
elif status_code == 200:
log.debug(f"Response: {response}")
return response.json()
else:
log.debug(f"Response: {response}")
raise ResultErrror(f"Result Error: {response.content}")
except ConnectionError as e:
message = f"Connection Error: {e}"
raise KomgaError(message) from e
@@ -81,6 +105,7 @@ class BaseAPI:
if data is None:
data = {}
try:
print("patching data", data, url)
response = requests.patch(
url,
auth=(self._username, self._password),
@@ -88,6 +113,10 @@ class BaseAPI:
timeout=self.timeout,
)
response.raise_for_status()
log.debug(
f"Response: {response}, {response.status_code}, {response.content}"
)
print(response.status_code, response.content)
if response.status_code != 204:
raise ResultErrror(f"Result Error: {response.json()}")
except ConnectionError as e:

View File

@@ -1,10 +1,5 @@
from .baseapi import BaseAPI
import pathlib
import subprocess
import requests
import typing_extensions
from typing import List, Optional, Dict, Any, Union
from komgapi.errors import KomgaError, LoginError, ResultErrror
from komgapi.schemas import * # Progress, Series

View File

@@ -7,6 +7,14 @@ from typing import List, Optional, Dict, Any, Union
from komgapi.schemas import * # Progress, Series
import loguru
import sys
log = loguru.logger
log.remove()
log.add("logs/komgapi.log", rotation="1 week", retention="1 month")
log.add(sys.stdout)
class SeriesController(BaseAPI):
def __init__(self, username, password, url, timeout=20) -> None:
@@ -14,56 +22,39 @@ class SeriesController(BaseAPI):
def getAllSeries(
self,
library_id: List[str] = None,
collection_id: List[str] = None,
status: List[str] = None,
publisher: List[str] = None,
lang: List[str] = None,
genre: List[str] = None,
tag: List[str] = None,
age_rating: List[str] = None,
release_year: List[str] = None,
deleted: bool = None,
complete: bool = None,
unpaged: bool = True,
sort: List[str] = None,
author: List[str] = None,
oneshot: bool = None,
size: int = None,
page: int = None,
body: dict[Any, Any] = {},
) -> list[Series]:
"""Get all series from the server.
By default, this will return all series in the server. You can filter the results by using the parameters.
"""
Get all series from the server that match the query.
Args:
----
- library_id (List[str], optional): The library to be queried. If None, all available libraries will be used. Defaults to None.
- collection_id (List[str], optional): The collection to be queried. Defaults to None.
- status (List[str], optional): The status of the series. Can be: ENDED,ONGOING,ABANDONED,HIATUS. Defaults to None.
- publisher (List[str], optional): Publisher(s) to be searched for. Defaults to None.
- lang (List[str], optional): Language to query for. Uses two-letter codec. Defaults to None.
- genre (List[str], optional): Genre(s) to query for. Defaults to None.
- tag (List[str], optional): Tag(s) to query. Defaults to None.
- age_rating (List[str], optional): A custom age-rating to search for. Needs to be configured manually. Defaults to None.
- release_year (List[str], optional): When the series were released. Defaults to None.
- deleted (bool, optional): If the series is deleted. Defaults to None.
- complete (bool, optional): Turn to true to only search series that are complete. Complete requires TotalBookCount to be set and equal to BookCount. Defaults to None.
- unpaged (bool, optional): Set to False if a single Page of results should be returned. By default, a page contains 20 entries. Defaults to True.
- sort (List[str], optional): Sorting of the returned data. Sort using asc|desc. Multiple sort criteria are supported. Defaults to None.
- author (List[str], optional): Author(s) to include in the query. Defaults to None.
- oneshot (bool, optional): If the series is categorized as oneshot. Defaults to None.
- size (int, optional): The size of the page. Defaults to None.
- page (int, optional): The page to be returned. Defaults to None.
Parameters
----------
unpaged : bool, optional
limits the amount of results. If set to false, 20 entries will be returned, by default True
sort : List[str], optional
sorting parameter, by default None
size : int, optional
How many entries should be returned. Set to None to get all entries, by default None
page : int, optional
If unpaged is False and size is set, this allows the selection of a subset of result, by default None
body : dict, optional
The query that requests the data from the API, by default {}
Returns:
Returns
-------
- list[Series]: a list of all series that match the query. Each series is represented as a Series object.
list[Series]
The result of the query. Each series is represented as a Series object.
"""
params = locals()
body = params.pop("body")
params = self.setParams(params)
url = self.url + "series"
data = self.getRequest(url, params)
url = self.url + "series/list"
data = self.postRequest(url, data=params, body=body)
ret = []
for series in data["content"]:
ret.append(Series(**series))
@@ -250,6 +241,8 @@ class SeriesController(BaseAPI):
"""
url = self.url + f"series/{series_id}/metadata"
data = self.patchRequest(url, changed_metadata)
log.debug("Changed metadata: {}", data)
return data
def refreshMetadata(self, series_id: str) -> Optional[Dict[str, Any]]:

View File

@@ -1,4 +1,3 @@
"""Generic API for KOMGA"""
import requests
@@ -28,20 +27,28 @@ class KOMGAPI_REST:
self.timeout = timeout
if not url.endswith("/"):
url += "/"
self.common_book_controller = CommonBookController(username, password, url, timeout)
self.common_book_controller = CommonBookController(
username, password, url, timeout
)
self.series_controller = SeriesController(username, password, url, timeout)
self.readlist_controller = ReadListController(username, password, url, timeout)
self.page_hash_controller = None
self.library_controller = LibraryController(username, password, url, timeout)
self.series_collection_controller = SeriesCollectionController(username, password, url, timeout)
self.series_collection_controller = SeriesCollectionController(
username, password, url, timeout
)
self.book_controler = BookController(username, password, url, timeout)
self.announcement_controller = AnnouncementController(username, password, url, timeout)
self.announcement_controller = AnnouncementController(
username, password, url, timeout
)
self.user_controller = UserController(username, password, url, timeout)
self.transient_books_controller = None
self.file_system_controller = None
self.claim_controller = ClaimController(username, password, url, timeout)
self.settings_controller = SettingsController(username, password, url, timeout)
self.referential_controller = ReferentialController(username, password, url, timeout)
self.referential_controller = ReferentialController(
username, password, url, timeout
)
self.login_controller = None
self.historical_events_controller = None
self.task_controller = None
@@ -51,7 +58,6 @@ class KOMGAPI_REST:
def notImplemented(self):
raise NotImplementedError("Not implemented yet")
def from_env(self):
"""Create a KOMGA API object from environment variables.
@@ -73,18 +79,11 @@ class KOMGAPI_REST:
bool: True if the connection is successful, False otherwise.
"""
try:
requests.get(self.url, auth=(self._username, self._password), timeout=self.timeout)
requests.get(
self.url, auth=(self._username, self._password), timeout=self.timeout
)
return True
except Exception as e:
return False
# Book controller

View File

@@ -2,9 +2,10 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class AlternateTitle(BaseModel):
@dataclass
class AlternateTitle:
label: str
title: str

View File

@@ -2,11 +2,12 @@ from __future__ import annotations
from typing import List, Dict
from pydantic import BaseModel
from dataclasses import dataclass
from komgapi.schemas.Author import Author
class AnnouncementItem(BaseModel):
@dataclass
class AnnouncementItem:
id: str
url: str
title: str
@@ -16,7 +17,8 @@ class AnnouncementItem(BaseModel):
author: Author
class Announcement(BaseModel):
@dataclass
class Announcement:
version: str
title: str
home_page_url: str

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class UserAccess(BaseModel):
@dataclass
class UserAccess:
userId: str
email: str
apiKeyId: str
@@ -18,13 +19,15 @@ class UserAccess(BaseModel):
source: str
class Sort(BaseModel):
@dataclass
class Sort:
empty: bool
sorted: bool
unsorted: bool
class Pageable(BaseModel):
@dataclass
class Pageable:
offset: int
sort: Sort
pageNumber: int
@@ -33,7 +36,8 @@ class Pageable(BaseModel):
unpaged: bool
class Authentication(BaseModel):
@dataclass
class Authentication:
totalElements: int
totalPages: int
size: int
@@ -47,32 +51,25 @@ class Authentication(BaseModel):
empty: bool
class Sort(BaseModel):
@dataclass
class Sort:
empty: bool
sorted: bool
unsorted: bool
class Sort1(BaseModel):
empty: bool
sorted: bool
unsorted: bool
class Pageable(BaseModel):
@dataclass
class Pageable:
offset: int
sort: Sort1
sort: Sort
pageNumber: int
pageSize: int
paged: bool
unpaged: bool
class UserAuthActivity(BaseModel):
@dataclass
class UserAuthActivity:
totalElements: int
totalPages: int
size: int

View File

@@ -2,20 +2,24 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class Author(BaseModel):
@dataclass
class Author:
name: str
role: str
class Sort(BaseModel):
@dataclass
class Sort:
empty: bool
sorted: bool
unsorted: bool
class Pageable(BaseModel):
@dataclass
class Pageable:
offset: int
sort: Sort
pageNumber: int
@@ -24,7 +28,8 @@ class Pageable(BaseModel):
unpaged: bool
class AuthorResponse(BaseModel):
@dataclass
class AuthorResponse:
totalElements: int
totalPages: int
size: int

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import Any, List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
from .Author import Author
from .BooksMetadata import BookMetadata
@@ -10,7 +10,8 @@ from .Link import Link
from .Media import Media
class Book(BaseModel):
@dataclass
class Book:
id: Optional[str] = None
seriesId: Optional[str] = None
seriesTitle: Optional[str] = None

View File

@@ -2,13 +2,14 @@ from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
from .Author import Author
from .Link import Link
class BooksMetadata(BaseModel):
@dataclass
class BooksMetadata:
authors: Optional[List[Author]] = None
tags: Optional[List[str]] = None
releaseDate: Optional[str] = None
@@ -18,7 +19,8 @@ class BooksMetadata(BaseModel):
lastModified: Optional[str] = None
class BookMetadata(BaseModel):
@dataclass
class BookMetadata:
title: Optional[str] = None
titleLock: Optional[bool] = None
summary: Optional[str] = None
@@ -29,13 +31,13 @@ class BookMetadata(BaseModel):
numberSortLock: Optional[bool] = None
releaseDate: Optional[str] = None
releaseDateLock: Optional[bool] = None
authors: List[Author]
authors: List[Author] = None
authorsLock: Optional[bool] = None
tags: List
tags: List = None
tagsLock: Optional[bool] = None
isbn: Optional[str] = None
isbnLock: Optional[bool] = None
links: List[Link]
links: List[Link] = None
linksLock: Optional[bool] = None
created: Optional[str] = None
lastModified: Optional[str] = None

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class Collection(BaseModel):
@dataclass
class Collection:
id: str
name: str
ordered: bool

View File

@@ -1,11 +1,13 @@
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
from .Sort import Sort
from .Pageable import Pageable
from .Book import Book
class Duplicate(BaseModel):
@dataclass
class Duplicate:
totalElements: Optional[int] = None
totalPages: Optional[int] = None
size: Optional[int] = None

View File

@@ -2,14 +2,15 @@ from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
from .Pageable import Pageable
from .Series import Series
from .Sort import Sort
class LatestSeriesData(BaseModel):
@dataclass
class LatestSeriesData:
totalElements: Optional[int] = None
totalPages: Optional[int] = None
size: Optional[int] = None

View File

@@ -2,13 +2,14 @@ from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
class Library(BaseModel):
id: Optional[str] = None
name: Optional[str] = None
root: Optional[str] = None
@dataclass
class Library:
id: str
name: str
root: str
importComicInfoBook: Optional[bool] = None
importComicInfoSeries: Optional[bool] = None
importComicInfoCollection: Optional[bool] = None
@@ -35,8 +36,10 @@ class Library(BaseModel):
analyzeDimensions: Optional[bool] = None
oneshotsDirectory: Optional[str] = None
unavailable: Optional[bool] = None
hashKoreader: Optional[bool] = None
class CreateLibrary(BaseModel):
class CreateLibrary:
name: str
root: str
importComicInfoBook: bool
@@ -64,4 +67,3 @@ class CreateLibrary(BaseModel):
hashPages: bool
analyzeDimensions: bool
oneshotsDirectory: str

View File

@@ -2,9 +2,10 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class Link(BaseModel):
@dataclass
class Link:
label: str
url: str

View File

@@ -1,10 +1,11 @@
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
class Locations(BaseModel):
@dataclass
class Locations:
fragments: Optional[List[str]] = None
progression: Optional[int] = None
position: Optional[int] = None
totalProgression: Optional[int] = None

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
class Link(BaseModel):
@dataclass
class Link:
title: Optional[str] = None
rel: Optional[str] = None
href: Optional[str] = None
@@ -15,30 +16,35 @@ class Link(BaseModel):
height: Optional[int] = None
class Series(BaseModel):
@dataclass
class Series:
name: Optional[str] = None
position: Optional[int] = None
links: Optional[List[Link]] = None
class CollectionItem(BaseModel):
@dataclass
class CollectionItem:
name: Optional[str] = None
position: Optional[int] = None
links: Optional[List[Link]] = None
class BelongsTo(BaseModel):
@dataclass
class BelongsTo:
series: Optional[List[Series]] = None
collection: Optional[List[CollectionItem]] = None
class Rendition(BaseModel):
@dataclass
class Rendition:
additionalProp1: Dict[str, Any]
additionalProp2: Dict[str, Any]
additionalProp3: Dict[str, Any]
class Metadata(BaseModel):
@dataclass
class Metadata:
title: Optional[str] = None
identifier: Optional[str] = None
type: Optional[str] = None
@@ -67,7 +73,8 @@ class Metadata(BaseModel):
rendition: Optional[Rendition] = None
class Image(BaseModel):
@dataclass
class Image:
title: Optional[str] = None
rel: Optional[str] = None
href: Optional[str] = None
@@ -77,7 +84,8 @@ class Image(BaseModel):
height: Optional[int] = None
class ReadingOrderItem(BaseModel):
@dataclass
class ReadingOrderItem:
title: Optional[str] = None
rel: Optional[str] = None
href: Optional[str] = None
@@ -87,7 +95,8 @@ class ReadingOrderItem(BaseModel):
height: Optional[int] = None
class Resource(BaseModel):
@dataclass
class Resource:
title: Optional[str] = None
rel: Optional[str] = None
href: Optional[str] = None
@@ -97,7 +106,8 @@ class Resource(BaseModel):
height: Optional[int] = None
class TocItem(BaseModel):
@dataclass
class TocItem:
title: Optional[str] = None
rel: Optional[str] = None
href: Optional[str] = None
@@ -107,7 +117,8 @@ class TocItem(BaseModel):
height: Optional[int] = None
class Landmark(BaseModel):
@dataclass
class Landmark:
title: Optional[str] = None
rel: Optional[str] = None
href: Optional[str] = None
@@ -117,7 +128,8 @@ class Landmark(BaseModel):
height: Optional[int] = None
class PageListItem(BaseModel):
@dataclass
class PageListItem:
title: Optional[str] = None
rel: Optional[str] = None
href: Optional[str] = None
@@ -127,7 +139,8 @@ class PageListItem(BaseModel):
height: Optional[int] = None
class Manifest(BaseModel):
@dataclass
class Manifest:
context: Optional[str] = None
metadata: Optional[Metadata] = None
links: Optional[List[Link]] = None

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class Media(BaseModel):
@dataclass
class Media:
status: str
mediaType: str
pagesCount: int

View File

@@ -1,41 +1,45 @@
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from typing import List, Optional, Union
from dataclasses import dataclass
from .AlternateTitle import AlternateTitle
from .Link import Link
from .status import Status
class Metadata(BaseModel):
status: Optional[str] | None = None
statusLock: bool
@dataclass
class Metadata:
# set status to be either ENDED, ONGOING, HIATUS, ABANDONED
# status has to be one of the status listed in enum status
status: Optional[Status] | None = None
statusLock: bool = None
title: Optional[str] | None = None
titleLock: bool
titleLock: bool = None
titleSort: Optional[str] | None = None
titleSortLock: bool
titleSortLock: bool = None
summary: Optional[str] | None = None
summaryLock: bool
summaryLock: bool = None
readingDirection: Optional[str] | None = None
readingDirectionLock: bool
readingDirectionLock: bool = None
publisher: Optional[str] | None = None
publisherLock: bool
publisherLock: bool = None
ageRating: Optional[int] | None = None
ageRatingLock: bool
ageRatingLock: bool = None
language: Optional[str] | None = None
languageLock: bool
genres: List[str] | None
genresLock: bool
languageLock: bool = None
genres: List[str] | None = None
genresLock: bool = None
tags: List[str] | None = None
tagsLock: bool
tagsLock: bool = None
totalBookCount: Optional[int] | None = None
totalBookCountLock: bool
totalBookCountLock: bool = None
sharingLabels: List[str | None] = None
sharingLabelsLock: bool
sharingLabelsLock: bool = None
links: List[Link] | None = None
linksLock: bool
linksLock: bool = None
alternateTitles: List[AlternateTitle] | None = None
alternateTitlesLock: bool
alternateTitlesLock: bool = None
created: Optional[str] | None = None
lastModified: Optional[str] | None = None

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
class Page(BaseModel):
@dataclass
class Page:
number: Optional[int] = None
fileName: Optional[str] = None
mediaType: Optional[str] = None

View File

@@ -2,12 +2,13 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
from .Sort import Sort
class Pageable(BaseModel):
@dataclass
class Pageable:
offset: int
sort: Sort
pageNumber: int

View File

@@ -2,14 +2,13 @@ from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
from .Text import Text
from .Locations import Locations
class Position(BaseModel):
@dataclass
class Position:
href: Optional[str] = None
type: Optional[str] = None
title: Optional[str] = None
@@ -17,6 +16,7 @@ class Position(BaseModel):
text: Optional[Text] = None
class Position(BaseModel):
@dataclass
class Position:
total: Optional[int] = None
positions: Optional[List[Position]] = None

View File

@@ -2,17 +2,19 @@ from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
from .Text import Text
from .Locations import Locations
class Device(BaseModel):
@dataclass
class Device:
id: Optional[str] = None
name: Optional[str] = None
class Locator(BaseModel):
@dataclass
class Locator:
href: Optional[str] = None
type: Optional[str] = None
title: Optional[str] = None
@@ -20,7 +22,8 @@ class Locator(BaseModel):
text: Optional[Text] = None
class Progress(BaseModel):
@dataclass
class Progress:
modified: Optional[str] = None
device: Optional[Device] = None
locator: Optional[Locator] = None

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class Readlist(BaseModel):
@dataclass
class Readlist:
name: str
summary: str
ordered: bool

View File

@@ -1,28 +1,38 @@
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from .AlternateTitle import AlternateTitle
from typing import Optional
from dataclasses import dataclass
from .BooksMetadata import BooksMetadata
from .Link import Link
from .Metadata import Metadata
class Series(BaseModel):
id: Optional[str] = None
libraryId: Optional[str] = None
name: Optional[str] = None
url: Optional[str] = None
created: Optional[str] = None
lastModified: Optional[str] = None
fileLastModified: Optional[str] = None
booksCount: Optional[int] = None
booksReadCount: Optional[int] = None
booksUnreadCount: Optional[int] = None
booksInProgressCount: Optional[int] = None
metadata: Optional[Metadata] = None
booksMetadata: Optional[BooksMetadata] = None
deleted: Optional[bool] = None
oneshot: Optional[bool] = None
@dataclass
class Series:
id: str = None
libraryId: str = None
name: str = None
url: str = None
created: str = None
lastModified: str = None
fileLastModified: str = None
booksCount: int = 0
booksReadCount: int = None
booksUnreadCount: int = None
booksInProgressCount: int = None
metadata: Metadata = None
booksMetadata: BooksMetadata = None
deleted: bool = None
oneshot: bool = None
def __post_init__(self):
if self.metadata is None:
self.metadata = Metadata()
if self.booksMetadata is None:
self.booksMetadata = BooksMetadata()
if self.deleted is None:
self.deleted = False
if self.oneshot is None:
self.oneshot = False
if self.metadata:
self.metadata = Metadata(**self.metadata)
if self.booksMetadata:
self.booksMetadata = BooksMetadata(**self.booksMetadata)

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class Sort(BaseModel):
@dataclass
class Sort:
empty: bool
sorted: bool
unsorted: bool

View File

@@ -1,8 +1,10 @@
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel
from dataclasses import dataclass
class Text(BaseModel):
@dataclass
class Text:
after: Optional[str] = None
before: Optional[str] = None
highlight: Optional[str] = None

View File

@@ -2,10 +2,11 @@ from __future__ import annotations
from typing import List
from pydantic import BaseModel
from dataclasses import dataclass
class Thumbnail(BaseModel):
@dataclass
class Thumbnail:
id: str
seriesId: str
type: str
@@ -16,7 +17,8 @@ class Thumbnail(BaseModel):
height: int
class ReadlistThumbnail(BaseModel):
@dataclass
class ReadlistThumbnail:
id: str
readlistId: str
type: str

View File

@@ -2,25 +2,24 @@ from __future__ import annotations
from typing import List, Dict
from pydantic import BaseModel
class User(BaseModel):
"""Object representing a User
Args:
BaseModel (Model): Pydantic BaseModel
"""
id:str
email:str
roles:List[str]
sharedAllLibraries:bool
sharedLibrariesIds:List[str]
labelsAllow:List[str]
labelsExclude:List[str]
ageRestriction:Dict[str, int|str] | None
from dataclasses import dataclass
class CreateUser(BaseModel):
email:str
password:str
roles:List[str]
@dataclass
class User:
"""Object representing a User"""
id: str
email: str
roles: List[str]
sharedAllLibraries: bool
sharedLibrariesIds: List[str]
labelsAllow: List[str]
labelsExclude: List[str]
ageRestriction: Dict[str, int | str] | None
class CreateUser:
email: str
password: str
roles: List[str]

View File

@@ -25,6 +25,6 @@ from .User import User, CreateUser
from .Authentication import Authentication, UserAuthActivity
from .apikey import APIKey
from .settings import Settings
from .status import Status
from .violation import Violation

View File

@@ -1,9 +1,10 @@
from __future__ import annotations
from pydantic import BaseModel
from dataclasses import dataclass
class APIKey(BaseModel):
@dataclass
class APIKey:
id: str
userId: str
key: str

View File

@@ -1,27 +1,31 @@
from __future__ import annotations
from pydantic import BaseModel
from dataclasses import dataclass
class ServerPort(BaseModel):
@dataclass
class ServerPort:
configurationSource: int
databaseSource: int
effectiveValue: int
class ServerContextPath(BaseModel):
@dataclass
class ServerContextPath:
configurationSource: str
databaseSource: str
effectiveValue: str
class KepubifyPath(BaseModel):
@dataclass
class KepubifyPath:
configurationSource: str
databaseSource: str
effectiveValue: str
class Settings(BaseModel):
@dataclass
class Settings:
deleteEmptyCollections: bool
deleteEmptyReadLists: bool
rememberMeDurationDays: int

View File

@@ -0,0 +1,15 @@
# enum
from enum import Enum
class Status(str, Enum):
ENDED = "ENDED"
ONGOING = "ONGOING"
HIATUS = "HIATUS"
ABANDONED = "ABANDONED"
if __name__ == "__main__":
options = [e.name for e in Status]
print(options)
print(Status("ENDED"))

View File

@@ -1,11 +1,10 @@
from __future__ import annotations
from pydantic import BaseModel
from dataclasses import dataclass
class Violation(BaseModel):
@dataclass
class Violation:
fieldName: str
message: str

19
tests/test_api.py Normal file
View File

@@ -0,0 +1,19 @@
from komgapi import komgapi
from komconfig import KomConfig
config = KomConfig()
client = komgapi(
url=config.komga.url,
username=config.komga.user,
password=config.komga.password,
)
def test_series_controller(client: komgapi):
assert client.series_controller.getAllSeries() is not None
if __name__ == "__main__":
test_series_controller(client)
print("All tests passed!")