From 759c01380f9d6c8a35341de1b11705a3db3b93c6 Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Thu, 17 Apr 2025 10:57:57 +0200 Subject: [PATCH] update codebase --- .vscode/settings.json | 7 +++ pyproject.toml | 13 +++- src/komgapi/endpoints/baseapi.py | 51 +++++++++++---- src/komgapi/endpoints/library_controller.py | 5 -- src/komgapi/endpoints/series_controller.py | 69 +++++++++------------ src/komgapi/komgapi.py | 37 ++++++----- src/komgapi/schemas/AlternateTitle.py | 5 +- src/komgapi/schemas/Announcement.py | 8 ++- src/komgapi/schemas/Authentication.py | 39 ++++++------ src/komgapi/schemas/Author.py | 17 +++-- src/komgapi/schemas/Book.py | 5 +- src/komgapi/schemas/BooksMetadata.py | 14 +++-- src/komgapi/schemas/Collection.py | 5 +- src/komgapi/schemas/Duplicate.py | 8 ++- src/komgapi/schemas/Latest.py | 5 +- src/komgapi/schemas/Library.py | 18 +++--- src/komgapi/schemas/Link.py | 5 +- src/komgapi/schemas/Locations.py | 7 ++- src/komgapi/schemas/Manifest.py | 41 +++++++----- src/komgapi/schemas/Media.py | 5 +- src/komgapi/schemas/Metadata.py | 44 +++++++------ src/komgapi/schemas/Page.py | 5 +- src/komgapi/schemas/Pageable.py | 7 ++- src/komgapi/schemas/Position.py | 10 +-- src/komgapi/schemas/Progress.py | 13 ++-- src/komgapi/schemas/Readlist.py | 5 +- src/komgapi/schemas/Series.py | 56 ++++++++++------- src/komgapi/schemas/Sort.py | 5 +- src/komgapi/schemas/Text.py | 8 ++- src/komgapi/schemas/Thumbnail.py | 8 ++- src/komgapi/schemas/User.py | 39 ++++++------ src/komgapi/schemas/__init__.py | 4 +- src/komgapi/schemas/apikey.py | 7 ++- src/komgapi/schemas/settings.py | 14 +++-- src/komgapi/schemas/status.py | 15 +++++ src/komgapi/schemas/violation.py | 7 +-- tests/test_api.py | 19 ++++++ 37 files changed, 378 insertions(+), 252 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/komgapi/schemas/status.py create mode 100644 tests/test_api.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b38853 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6a35078..60e6dc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 } diff --git a/src/komgapi/endpoints/baseapi.py b/src/komgapi/endpoints/baseapi.py index 1d1692b..d66b968 100644 --- a/src/komgapi/endpoints/baseapi.py +++ b/src/komgapi/endpoints/baseapi.py @@ -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: - response = requests.post( - url, - auth=(self._username, self._password), - json=data, - timeout=self.timeout, - ) + 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: diff --git a/src/komgapi/endpoints/library_controller.py b/src/komgapi/endpoints/library_controller.py index db27cf0..96f07a0 100644 --- a/src/komgapi/endpoints/library_controller.py +++ b/src/komgapi/endpoints/library_controller.py @@ -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 diff --git a/src/komgapi/endpoints/series_controller.py b/src/komgapi/endpoints/series_controller.py index f45c816..351ac14 100644 --- a/src/komgapi/endpoints/series_controller.py +++ b/src/komgapi/endpoints/series_controller.py @@ -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]]: diff --git a/src/komgapi/komgapi.py b/src/komgapi/komgapi.py index b9232a2..cf6fd39 100644 --- a/src/komgapi/komgapi.py +++ b/src/komgapi/komgapi.py @@ -1,4 +1,3 @@ - """Generic API for KOMGA""" import requests @@ -28,30 +27,37 @@ 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 self.sync_point_controller = None self.oauth2_controller = None - + def notImplemented(self): raise NotImplementedError("Not implemented yet") - - + def from_env(self): """Create a KOMGA API object from environment variables. @@ -65,7 +71,7 @@ class KOMGAPI_REST: os.environ["KOMGA_PASSWORD"], os.environ["KOMGA_URL"], ) - + def test_connection(self): """Test the connection to the KOMGA server. @@ -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 - - - - \ No newline at end of file diff --git a/src/komgapi/schemas/AlternateTitle.py b/src/komgapi/schemas/AlternateTitle.py index f756e98..d73a22d 100644 --- a/src/komgapi/schemas/AlternateTitle.py +++ b/src/komgapi/schemas/AlternateTitle.py @@ -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 diff --git a/src/komgapi/schemas/Announcement.py b/src/komgapi/schemas/Announcement.py index 0016435..27784ba 100644 --- a/src/komgapi/schemas/Announcement.py +++ b/src/komgapi/schemas/Announcement.py @@ -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 diff --git a/src/komgapi/schemas/Authentication.py b/src/komgapi/schemas/Authentication.py index 154bc02..bc914c5 100644 --- a/src/komgapi/schemas/Authentication.py +++ b/src/komgapi/schemas/Authentication.py @@ -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 @@ -45,34 +49,27 @@ class Authentication(BaseModel): numberOfElements: int pageable: Pageable 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 @@ -83,4 +80,4 @@ class UserAuthActivity(BaseModel): last: bool numberOfElements: int pageable: Pageable - empty: bool \ No newline at end of file + empty: bool diff --git a/src/komgapi/schemas/Author.py b/src/komgapi/schemas/Author.py index b7ca2e6..2cbebce 100644 --- a/src/komgapi/schemas/Author.py +++ b/src/komgapi/schemas/Author.py @@ -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 @@ -35,4 +40,4 @@ class AuthorResponse(BaseModel): last: bool numberOfElements: int pageable: Pageable - empty: bool \ No newline at end of file + empty: bool diff --git a/src/komgapi/schemas/Book.py b/src/komgapi/schemas/Book.py index 56b52f3..639d0fc 100644 --- a/src/komgapi/schemas/Book.py +++ b/src/komgapi/schemas/Book.py @@ -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 diff --git a/src/komgapi/schemas/BooksMetadata.py b/src/komgapi/schemas/BooksMetadata.py index 742a99a..207c08b 100644 --- a/src/komgapi/schemas/BooksMetadata.py +++ b/src/komgapi/schemas/BooksMetadata.py @@ -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 diff --git a/src/komgapi/schemas/Collection.py b/src/komgapi/schemas/Collection.py index 28088b6..851aecf 100644 --- a/src/komgapi/schemas/Collection.py +++ b/src/komgapi/schemas/Collection.py @@ -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 diff --git a/src/komgapi/schemas/Duplicate.py b/src/komgapi/schemas/Duplicate.py index a7275db..269f78f 100644 --- a/src/komgapi/schemas/Duplicate.py +++ b/src/komgapi/schemas/Duplicate.py @@ -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 @@ -16,4 +18,4 @@ class Duplicate(BaseModel): last: Optional[bool] = None numberOfElements: Optional[int] = None pageable: Optional[Pageable] = None - empty: Optional[bool] = None \ No newline at end of file + empty: Optional[bool] = None diff --git a/src/komgapi/schemas/Latest.py b/src/komgapi/schemas/Latest.py index a7216f1..f4c9ba1 100644 --- a/src/komgapi/schemas/Latest.py +++ b/src/komgapi/schemas/Latest.py @@ -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 diff --git a/src/komgapi/schemas/Library.py b/src/komgapi/schemas/Library.py index bc0a18a..bed95c8 100644 --- a/src/komgapi/schemas/Library.py +++ b/src/komgapi/schemas/Library.py @@ -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 - -class CreateLibrary(BaseModel): + hashKoreader: Optional[bool] = None + + +class CreateLibrary: name: str root: str importComicInfoBook: bool @@ -64,4 +67,3 @@ class CreateLibrary(BaseModel): hashPages: bool analyzeDimensions: bool oneshotsDirectory: str - diff --git a/src/komgapi/schemas/Link.py b/src/komgapi/schemas/Link.py index 74b20fb..5812abb 100644 --- a/src/komgapi/schemas/Link.py +++ b/src/komgapi/schemas/Link.py @@ -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 diff --git a/src/komgapi/schemas/Locations.py b/src/komgapi/schemas/Locations.py index 7929e5f..d10454b 100644 --- a/src/komgapi/schemas/Locations.py +++ b/src/komgapi/schemas/Locations.py @@ -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 - \ No newline at end of file diff --git a/src/komgapi/schemas/Manifest.py b/src/komgapi/schemas/Manifest.py index ab5ea2c..94b08a3 100644 --- a/src/komgapi/schemas/Manifest.py +++ b/src/komgapi/schemas/Manifest.py @@ -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 diff --git a/src/komgapi/schemas/Media.py b/src/komgapi/schemas/Media.py index 6cd9ab7..148ddc3 100644 --- a/src/komgapi/schemas/Media.py +++ b/src/komgapi/schemas/Media.py @@ -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 diff --git a/src/komgapi/schemas/Metadata.py b/src/komgapi/schemas/Metadata.py index deb9788..89727eb 100644 --- a/src/komgapi/schemas/Metadata.py +++ b/src/komgapi/schemas/Metadata.py @@ -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 diff --git a/src/komgapi/schemas/Page.py b/src/komgapi/schemas/Page.py index 656fa65..7e1280d 100644 --- a/src/komgapi/schemas/Page.py +++ b/src/komgapi/schemas/Page.py @@ -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 diff --git a/src/komgapi/schemas/Pageable.py b/src/komgapi/schemas/Pageable.py index 2edf594..06047c1 100644 --- a/src/komgapi/schemas/Pageable.py +++ b/src/komgapi/schemas/Pageable.py @@ -2,15 +2,16 @@ 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 pageSize: int paged: bool - unpaged: bool \ No newline at end of file + unpaged: bool diff --git a/src/komgapi/schemas/Position.py b/src/komgapi/schemas/Position.py index 22f4c99..0cb0fe3 100644 --- a/src/komgapi/schemas/Position.py +++ b/src/komgapi/schemas/Position.py @@ -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 diff --git a/src/komgapi/schemas/Progress.py b/src/komgapi/schemas/Progress.py index bed151f..e29678b 100644 --- a/src/komgapi/schemas/Progress.py +++ b/src/komgapi/schemas/Progress.py @@ -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 diff --git a/src/komgapi/schemas/Readlist.py b/src/komgapi/schemas/Readlist.py index c432f8d..2f5f2b2 100644 --- a/src/komgapi/schemas/Readlist.py +++ b/src/komgapi/schemas/Readlist.py @@ -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 diff --git a/src/komgapi/schemas/Series.py b/src/komgapi/schemas/Series.py index 5c0765b..18eb9ab 100644 --- a/src/komgapi/schemas/Series.py +++ b/src/komgapi/schemas/Series.py @@ -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) diff --git a/src/komgapi/schemas/Sort.py b/src/komgapi/schemas/Sort.py index 0aa8bdb..0bbc0a8 100644 --- a/src/komgapi/schemas/Sort.py +++ b/src/komgapi/schemas/Sort.py @@ -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 diff --git a/src/komgapi/schemas/Text.py b/src/komgapi/schemas/Text.py index 14f6c17..8fa2db7 100644 --- a/src/komgapi/schemas/Text.py +++ b/src/komgapi/schemas/Text.py @@ -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 + before: Optional[str] = None highlight: Optional[str] = None diff --git a/src/komgapi/schemas/Thumbnail.py b/src/komgapi/schemas/Thumbnail.py index aadf845..24ff7f8 100644 --- a/src/komgapi/schemas/Thumbnail.py +++ b/src/komgapi/schemas/Thumbnail.py @@ -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 diff --git a/src/komgapi/schemas/User.py b/src/komgapi/schemas/User.py index 53bdb17..989d3c6 100644 --- a/src/komgapi/schemas/User.py +++ b/src/komgapi/schemas/User.py @@ -2,25 +2,24 @@ from __future__ import annotations from typing import List, Dict -from pydantic import BaseModel +from dataclasses import dataclass -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 - - -class CreateUser(BaseModel): - email:str - password:str - roles:List[str] \ No newline at end of file +@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] diff --git a/src/komgapi/schemas/__init__.py b/src/komgapi/schemas/__init__.py index 3dc44b3..6c8ad7a 100644 --- a/src/komgapi/schemas/__init__.py +++ b/src/komgapi/schemas/__init__.py @@ -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 \ No newline at end of file +from .violation import Violation diff --git a/src/komgapi/schemas/apikey.py b/src/komgapi/schemas/apikey.py index dacaa88..4eb899a 100644 --- a/src/komgapi/schemas/apikey.py +++ b/src/komgapi/schemas/apikey.py @@ -1,12 +1,13 @@ from __future__ import annotations -from pydantic import BaseModel +from dataclasses import dataclass -class APIKey(BaseModel): +@dataclass +class APIKey: id: str userId: str key: str comment: str createdDate: str - lastModifiedDate: str \ No newline at end of file + lastModifiedDate: str diff --git a/src/komgapi/schemas/settings.py b/src/komgapi/schemas/settings.py index 3a7806d..9933dc4 100644 --- a/src/komgapi/schemas/settings.py +++ b/src/komgapi/schemas/settings.py @@ -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 diff --git a/src/komgapi/schemas/status.py b/src/komgapi/schemas/status.py new file mode 100644 index 0000000..dd046fc --- /dev/null +++ b/src/komgapi/schemas/status.py @@ -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")) diff --git a/src/komgapi/schemas/violation.py b/src/komgapi/schemas/violation.py index be711cb..6b4ecd9 100644 --- a/src/komgapi/schemas/violation.py +++ b/src/komgapi/schemas/violation.py @@ -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 - - diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..cec0a65 --- /dev/null +++ b/tests/test_api.py @@ -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!")