init mangacollec
This commit is contained in:
commit
2581d4f1fc
106 files changed
+2670
No files matched your search
@@ -0,0 +1,14 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "mangacollec"
|
||||
version = "0.1.0"
|
||||
description = "Client library for MangaCollec."
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
include = ["mangacollec*"]
|
||||
@@ -0,0 +1 @@
|
||||
"""Client library for MangaCollec."""
|
||||
@@ -0,0 +1,100 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto.author_response import AuthorResponse
|
||||
from libs.mangacollec.dto.authors_response import AuthorsResponse
|
||||
from libs.mangacollec.dto.borrower_create_request import BorrowerCreateRequest
|
||||
from libs.mangacollec.dto.borrower_create_response import BorrowerCreateResponse
|
||||
from libs.mangacollec.dto.box_response import BoxResponse
|
||||
from libs.mangacollec.dto.create_box_possessions_multiple_request import (
|
||||
CreateBoxesPossessionsMultipleRequest,
|
||||
CreateBoxPossessionMultipleRequest,
|
||||
)
|
||||
from libs.mangacollec.dto.create_box_possessions_multiple_response import (
|
||||
CreateBoxPossessionsMultipleResponse,
|
||||
)
|
||||
from libs.mangacollec.dto.create_volume_possessions_multiple_request import (
|
||||
CreateVolumePossessionMultipleRequest,
|
||||
CreateVolumesPossessionsMultipleRequest,
|
||||
)
|
||||
from libs.mangacollec.dto.create_volume_possessions_multiple_response import (
|
||||
CreateVolumePossessionsMultipleResponse,
|
||||
)
|
||||
from libs.mangacollec.dto.delete_box_possessions_multiple_request import (
|
||||
DeleteBoxPossessionMultipleRequest,
|
||||
DeleteBoxPossessionsMultipleRequest,
|
||||
DeletePossessionMultipleRequest,
|
||||
)
|
||||
from libs.mangacollec.dto.delete_box_possessions_multiple_response import (
|
||||
DeleteBoxPossessionsMultipleResponse,
|
||||
)
|
||||
from libs.mangacollec.dto.delete_box_possessions_multiple_response import (
|
||||
DeletedResource as DeletedBoxResource,
|
||||
)
|
||||
from libs.mangacollec.dto.delete_volume_possessions_multiple_response import (
|
||||
DeletedResource,
|
||||
DeleteVolumePossessionsMultipleResponse,
|
||||
)
|
||||
from libs.mangacollec.dto.edition_response import EditionResponse
|
||||
from libs.mangacollec.dto.follow_edition_create_request import FollowEditionCreateRequest
|
||||
from libs.mangacollec.dto.kinds_response import KindsResponse
|
||||
from libs.mangacollec.dto.loans_create_request import LoanCreateRequest, LoansCreateRequest
|
||||
from libs.mangacollec.dto.loans_create_response import LoanCreateError, LoansCreateResponse
|
||||
from libs.mangacollec.dto.loans_delete_request import LoanDeleteRequest, LoansDeleteRequest
|
||||
from libs.mangacollec.dto.loans_delete_response import LoansDeleteResponse
|
||||
from libs.mangacollec.dto.news_response import NewsResponse
|
||||
from libs.mangacollec.dto.publisher_response import PublisherResponse
|
||||
from libs.mangacollec.dto.publishers_response import PublishersResponse
|
||||
from libs.mangacollec.dto.reads_create_request import ReadCreateRequest, ReadsCreateRequest
|
||||
from libs.mangacollec.dto.reads_create_response import ReadsCreateResponse
|
||||
from libs.mangacollec.dto.reads_delete_request import ReadDeleteRequest, ReadsDeleteRequest
|
||||
from libs.mangacollec.dto.reads_delete_response import ReadDeleteError, ReadsDeleteResponse
|
||||
from libs.mangacollec.dto.serie_response import SerieResponse
|
||||
from libs.mangacollec.dto.series_response import SeriesResponse
|
||||
from libs.mangacollec.dto.volume_response import VolumeResponse
|
||||
|
||||
__all__ = [
|
||||
"AuthorResponse",
|
||||
"AuthorsResponse",
|
||||
"BorrowerCreateRequest",
|
||||
"BorrowerCreateResponse",
|
||||
"BoxResponse",
|
||||
"CreateBoxPossessionMultipleRequest",
|
||||
"CreateBoxPossessionsMultipleResponse",
|
||||
"CreateBoxesPossessionsMultipleRequest",
|
||||
"CreateVolumePossessionMultipleRequest",
|
||||
"CreateVolumePossessionsMultipleResponse",
|
||||
"CreateVolumesPossessionsMultipleRequest",
|
||||
"DeleteBoxPossessionMultipleRequest",
|
||||
"DeleteBoxPossessionsMultipleRequest",
|
||||
"DeleteBoxPossessionsMultipleResponse",
|
||||
"DeletePossessionMultipleRequest",
|
||||
"DeleteVolumePossessionsMultipleResponse",
|
||||
"DeletedBoxResource",
|
||||
"DeletedResource",
|
||||
"EditionResponse",
|
||||
"FollowEditionCreateRequest",
|
||||
"KindsResponse",
|
||||
"LoanCreateError",
|
||||
"LoanCreateRequest",
|
||||
"LoanDeleteRequest",
|
||||
"LoansCreateRequest",
|
||||
"LoansCreateResponse",
|
||||
"LoansDeleteRequest",
|
||||
"LoansDeleteResponse",
|
||||
"NewsResponse",
|
||||
"PublisherResponse",
|
||||
"PublishersResponse",
|
||||
"ReadCreateRequest",
|
||||
"ReadDeleteError",
|
||||
"ReadDeleteRequest",
|
||||
"ReadsCreateRequest",
|
||||
"ReadsCreateResponse",
|
||||
"ReadsDeleteRequest",
|
||||
"ReadsDeleteResponse",
|
||||
"SerieResponse",
|
||||
"SeriesResponse",
|
||||
"VolumeResponse",
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue.author import Author
|
||||
from libs.mangacollec.model.catalogue.edition import Edition
|
||||
from libs.mangacollec.model.catalogue.job import Job
|
||||
from libs.mangacollec.model.catalogue.serie import Serie
|
||||
from libs.mangacollec.model.catalogue.task import Task
|
||||
from libs.mangacollec.model.catalogue.volume import Volume
|
||||
|
||||
|
||||
class AuthorResponse(BaseModel):
|
||||
"""Réponse détaillée d'un auteur et de ses ressources associées."""
|
||||
|
||||
authors: list[Author]
|
||||
tasks: list[Task]
|
||||
jobs: list[Job]
|
||||
series: list[Serie]
|
||||
editions: list[Edition]
|
||||
volumes: list[Volume]
|
||||
@@ -0,0 +1,15 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue.author import Author
|
||||
|
||||
|
||||
class AuthorsResponse(BaseModel):
|
||||
"""Réponse de l'endpoint listant les auteurs du catalogue."""
|
||||
|
||||
authors: list[Author]
|
||||
@@ -0,0 +1,21 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de création d'un emprunteur ou d'un lieu de stockage."""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BorrowerCreateRequest(BaseModel):
|
||||
"""Corps JSON attendu par POST /v2/borrowers."""
|
||||
|
||||
category: Literal["person", "storage"]
|
||||
title: str
|
||||
|
||||
def to_api_payload(self) -> dict[str, object]:
|
||||
"""Sérialiser les champs de création dans la forme attendue par l'API."""
|
||||
return self.model_dump(mode="json")
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de l'emprunteur retourné par POST /v2/borrowers."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model.loans.borrower import Borrower
|
||||
|
||||
|
||||
class BorrowerCreateResponse(Borrower):
|
||||
"""Emprunteur créé avec la date de mise à jour propre à la réponse POST."""
|
||||
|
||||
updated_at: datetime
|
||||
@@ -0,0 +1,31 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue import (
|
||||
Boxe,
|
||||
BoxEdition,
|
||||
BoxVolume,
|
||||
Edition,
|
||||
Publisher,
|
||||
Serie,
|
||||
SerieType,
|
||||
Volume,
|
||||
)
|
||||
|
||||
|
||||
class BoxResponse(BaseModel):
|
||||
"""Réponse détaillée d'un coffret et de ses ressources associées."""
|
||||
|
||||
boxes: list[Boxe]
|
||||
box_editions: list[BoxEdition]
|
||||
publishers: list[Publisher]
|
||||
box_volumes: list[BoxVolume]
|
||||
volumes: list[Volume]
|
||||
editions: list[Edition]
|
||||
series: list[Serie]
|
||||
types: list[SerieType]
|
||||
@@ -0,0 +1,26 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de création groupée de possessions."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CreateBoxPossessionMultipleRequest(BaseModel):
|
||||
"""box à ajouter à la collection."""
|
||||
|
||||
box_id: ID
|
||||
|
||||
|
||||
class CreateBoxesPossessionsMultipleRequest(BaseModel):
|
||||
"""Liste des boxes à ajouter."""
|
||||
|
||||
possessions: list[CreateBoxPossessionMultipleRequest]
|
||||
|
||||
def to_api_payload(self) -> list[dict[str, str]]:
|
||||
"""Convertir le DTO vers le tableau JSON attendu par l'API."""
|
||||
return [item.model_dump(mode="json") for item in self.possessions]
|
||||
@@ -0,0 +1,22 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de réponse de création groupée de possessions."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.possessions.box_follow_edition import BoxFollowEdition
|
||||
from libs.mangacollec.model.possessions.box_possession import BoxPossession
|
||||
from libs.mangacollec.model.possessions.volume_follow_edition import VolumeFollowEdition
|
||||
from libs.mangacollec.model.possessions.volume_possession import VolumePossession
|
||||
|
||||
|
||||
class CreateBoxPossessionsMultipleResponse(BaseModel):
|
||||
"""Réponse de l'endpoint de boxe possessions multiples."""
|
||||
|
||||
box_possessions: list[BoxPossession]
|
||||
box_follow_editions: list[BoxFollowEdition]
|
||||
possessions: list[VolumePossession]
|
||||
follow_editions: list[VolumeFollowEdition]
|
||||
@@ -0,0 +1,26 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de création groupée de possessions."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CreateVolumePossessionMultipleRequest(BaseModel):
|
||||
"""Volume à ajouter à la collection."""
|
||||
|
||||
volume_id: ID
|
||||
|
||||
|
||||
class CreateVolumesPossessionsMultipleRequest(BaseModel):
|
||||
"""Liste des volumes à ajouter."""
|
||||
|
||||
possessions: list[CreateVolumePossessionMultipleRequest]
|
||||
|
||||
def to_api_payload(self) -> list[dict[str, str]]:
|
||||
"""Convertir le DTO vers le tableau JSON attendu par l'API."""
|
||||
return [item.model_dump(mode="json") for item in self.possessions]
|
||||
@@ -0,0 +1,18 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de réponse de création groupée de possessions."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.possessions import VolumePossession
|
||||
from libs.mangacollec.model.possessions.volume_follow_edition import VolumeFollowEdition
|
||||
|
||||
|
||||
class CreateVolumePossessionsMultipleResponse(BaseModel):
|
||||
"""Réponse de l'endpoint de volume possessions multiples."""
|
||||
|
||||
possessions: list[VolumePossession]
|
||||
follow_editions: list[VolumeFollowEdition]
|
||||
@@ -0,0 +1,38 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de suppression groupée de possessions de boxes."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class DeleteBoxPossessionMultipleRequest(BaseModel):
|
||||
"""Box possession à supprimer."""
|
||||
|
||||
id: ID
|
||||
|
||||
|
||||
class DeletePossessionMultipleRequest(BaseModel):
|
||||
"""Possession de volume à supprimer."""
|
||||
|
||||
id: ID
|
||||
|
||||
|
||||
class DeleteBoxPossessionsMultipleRequest(BaseModel):
|
||||
"""Objet de requête de suppression groupée de box possessions."""
|
||||
|
||||
box_possessions: list[DeleteBoxPossessionMultipleRequest]
|
||||
possessions: list[DeletePossessionMultipleRequest] | None = None
|
||||
|
||||
def to_api_payload(self) -> dict[str, object]:
|
||||
"""Convertir le DTO vers l'objet JSON attendu par l'API."""
|
||||
payload: dict[str, object] = {
|
||||
"box_possessions": [item.model_dump(mode="json") for item in self.box_possessions]
|
||||
}
|
||||
if self.possessions is not None:
|
||||
payload["possessions"] = [item.model_dump(mode="json") for item in self.possessions]
|
||||
return payload
|
||||
@@ -0,0 +1,26 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de réponse de suppression groupée de possessions de boxes."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class DeletedResource(BaseModel):
|
||||
"""Ressource supprimée par l'API."""
|
||||
|
||||
id: ID
|
||||
deleted: bool
|
||||
|
||||
|
||||
class DeleteBoxPossessionsMultipleResponse(BaseModel):
|
||||
"""Réponse de l'endpoint de suppression groupée de box possessions."""
|
||||
|
||||
box_possessions: list[DeletedResource]
|
||||
box_follow_editions: list[DeletedResource]
|
||||
possessions: list[DeletedResource]
|
||||
follow_editions: list[DeletedResource]
|
||||
@@ -0,0 +1,25 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de réponse de suppression groupée de possessions."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class DeletedResource(BaseModel):
|
||||
"""Ressource supprimée par l'API."""
|
||||
|
||||
id: ID
|
||||
deleted: bool
|
||||
|
||||
|
||||
class DeleteVolumePossessionsMultipleResponse(BaseModel):
|
||||
"""Réponse de l'endpoint de suppression groupée."""
|
||||
|
||||
possessions: list[DeletedResource]
|
||||
follow_editions: list[DeletedResource]
|
||||
loans: list[DeletedResource] | None = None
|
||||
@@ -0,0 +1,22 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue.edition import Edition
|
||||
from libs.mangacollec.model.catalogue.publisher import Publisher
|
||||
from libs.mangacollec.model.catalogue.serie import Serie
|
||||
from libs.mangacollec.model.catalogue.serie_type import SerieType
|
||||
from libs.mangacollec.model.catalogue.volume import Volume
|
||||
|
||||
|
||||
class EditionResponse(BaseModel):
|
||||
"""Réponse détaillée de l'endpoint d'une édition."""
|
||||
|
||||
editions: list[Edition]
|
||||
publishers: list[Publisher]
|
||||
series: list[Serie]
|
||||
types: list[SerieType]
|
||||
volumes: list[Volume]
|
||||
@@ -0,0 +1,21 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de création ou d'activation d'un suivi d'édition."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class FollowEditionCreateRequest(BaseModel):
|
||||
"""Corps JSON attendu par POST /v1/follow_editions."""
|
||||
|
||||
edition_id: ID
|
||||
following: bool
|
||||
|
||||
def to_api_payload(self) -> dict[str, object]:
|
||||
"""Sérialiser la requête dans la forme JSON attendue par l'API."""
|
||||
return self.model_dump(mode="json")
|
||||
@@ -0,0 +1,14 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue.kind import Kind
|
||||
|
||||
|
||||
class KindsResponse(BaseModel):
|
||||
"""Réponse contenant les genres."""
|
||||
|
||||
kinds: list[Kind]
|
||||
@@ -0,0 +1,27 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de création groupée de prêts."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class LoanCreateRequest(BaseModel):
|
||||
"""Association entre un emprunteur et une possession à prêter."""
|
||||
|
||||
borrower_id: ID
|
||||
possession_id: ID
|
||||
|
||||
|
||||
class LoansCreateRequest(BaseModel):
|
||||
"""Liste des associations emprunteur-possession à créer."""
|
||||
|
||||
loans: list[LoanCreateRequest]
|
||||
|
||||
def to_api_payload(self) -> dict[str, object]:
|
||||
"""Convertir les associations vers l'objet attendu par l'API."""
|
||||
return {"loans": [loan.model_dump(mode="json") for loan in self.loans]}
|
||||
@@ -0,0 +1,22 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO des résultats de création groupée de prêts."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.loans.loan import Loan
|
||||
|
||||
|
||||
class LoanCreateError(BaseModel):
|
||||
"""Erreurs métier d'un prêt refusé, renvoyées sous un statut HTTP 200."""
|
||||
|
||||
errors: dict[str, list[str]]
|
||||
|
||||
|
||||
class LoansCreateResponse(BaseModel):
|
||||
"""Prêts créés et erreurs métier propres aux éléments du lot."""
|
||||
|
||||
loans: list[Loan | LoanCreateError]
|
||||
@@ -0,0 +1,26 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de requête de suppression groupée de prêts."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class LoanDeleteRequest(BaseModel):
|
||||
"""Prêt à supprimer, identifié par l'ID reçu à sa création."""
|
||||
|
||||
id: ID
|
||||
|
||||
|
||||
class LoansDeleteRequest(BaseModel):
|
||||
"""Prêts à sérialiser dans le tableau loans du corps de requête."""
|
||||
|
||||
loans: list[LoanDeleteRequest]
|
||||
|
||||
def to_api_payload(self) -> dict[str, object]:
|
||||
"""Convertir les identifiants validés vers l'objet attendu par l'API."""
|
||||
return {"loans": [{"id": str(loan.id)} for loan in self.loans]}
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de réponse de suppression groupée de prêts."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.loans.loan_delete_result import LoanDeleteResult
|
||||
|
||||
|
||||
class LoansDeleteResponse(BaseModel):
|
||||
"""Résultats individuels de suppression, incluant les erreurs métier."""
|
||||
|
||||
loans: list[LoanDeleteResult]
|
||||
@@ -0,0 +1,30 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue import (
|
||||
Boxe,
|
||||
BoxEdition,
|
||||
BoxVolume,
|
||||
Edition,
|
||||
NativeAdVolumeHomeFirst,
|
||||
Serie,
|
||||
SerieType,
|
||||
Volume,
|
||||
)
|
||||
|
||||
|
||||
class NewsResponse(BaseModel):
|
||||
"""Réponse des dernières sorties de volumes et de coffrets."""
|
||||
|
||||
volumes: list[Volume]
|
||||
editions: list[Edition]
|
||||
series: list[Serie]
|
||||
types: list[SerieType]
|
||||
boxes: list[Boxe]
|
||||
box_editions: list[BoxEdition]
|
||||
box_volumes: list[BoxVolume]
|
||||
native_ad_volume_home_first: NativeAdVolumeHomeFirst | None = None
|
||||
@@ -0,0 +1,28 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue import (
|
||||
Boxe,
|
||||
BoxEdition,
|
||||
Edition,
|
||||
Publisher,
|
||||
Serie,
|
||||
SerieType,
|
||||
Volume,
|
||||
)
|
||||
|
||||
|
||||
class PublisherResponse(BaseModel):
|
||||
"""PlublisherResponse."""
|
||||
|
||||
box_editions: list[BoxEdition]
|
||||
boxes: list[Boxe]
|
||||
editions: list[Edition]
|
||||
publishers: list[Publisher]
|
||||
series: list[Serie]
|
||||
types: list[SerieType]
|
||||
volumes: list[Volume]
|
||||
@@ -0,0 +1,14 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue import Publisher
|
||||
|
||||
|
||||
class PublishersResponse(BaseModel):
|
||||
"""PlublishersResponse."""
|
||||
|
||||
publishers: list[Publisher]
|
||||
@@ -0,0 +1,38 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de création groupée de lectures."""
|
||||
|
||||
from pydantic import BaseModel, field_validator
|
||||
|
||||
from libs.mangacollec.exceptions import DomainValidationError
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class ReadCreateRequest(BaseModel):
|
||||
"""Volume à marquer comme lu."""
|
||||
|
||||
volume_id: ID
|
||||
|
||||
|
||||
class ReadsCreateRequest(BaseModel):
|
||||
"""Volumes à marquer lus, avec garde-fous locaux de non-vacuité et d'unicité."""
|
||||
|
||||
reads: list[ReadCreateRequest]
|
||||
|
||||
@field_validator("reads")
|
||||
@classmethod
|
||||
def validate_reads(cls, reads: list[ReadCreateRequest]) -> list[ReadCreateRequest]:
|
||||
"""Refuser localement les listes vides et les volumes répétés."""
|
||||
if not reads:
|
||||
raise DomainValidationError("La liste des volumes ne peut pas être vide.")
|
||||
volume_ids = [read.volume_id for read in reads]
|
||||
if len(set(volume_ids)) != len(volume_ids):
|
||||
raise DomainValidationError("Les identifiants de volumes doivent être distincts.")
|
||||
return reads
|
||||
|
||||
def to_api_payload(self) -> list[dict[str, str]]:
|
||||
"""Sérialiser le tableau racine attendu par POST /v1/reads_multiple."""
|
||||
return [{"volume_id": str(read.volume_id)} for read in self.reads]
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de réponse de création groupée de lectures."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.reads import Read, ReadEdition
|
||||
|
||||
|
||||
class ReadsCreateResponse(BaseModel):
|
||||
"""Lectures et états des éditions renvoyés par POST /v1/reads_multiple."""
|
||||
|
||||
reads: list[Read]
|
||||
read_editions: list[ReadEdition]
|
||||
@@ -0,0 +1,26 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de requête de suppression groupée de lectures."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class ReadDeleteRequest(BaseModel):
|
||||
"""Lecture à supprimer, identifiée par l'ID reçu à sa création."""
|
||||
|
||||
id: ID
|
||||
|
||||
|
||||
class ReadsDeleteRequest(BaseModel):
|
||||
"""Lectures à sérialiser sous forme de tableau à la racine du corps JSON."""
|
||||
|
||||
reads: list[ReadDeleteRequest]
|
||||
|
||||
def to_api_payload(self) -> list[dict[str, str]]:
|
||||
"""Convertir les identifiants validés vers le corps attendu par l'API."""
|
||||
return [{"id": str(read.id)} for read in self.reads]
|
||||
@@ -0,0 +1,23 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""DTO de réponse de suppression groupée de lectures."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.deleted_item import DeletedItem
|
||||
|
||||
|
||||
class ReadDeleteError(BaseModel):
|
||||
"""Erreur métier de suppression, renvoyée dans reads même sous HTTP 200."""
|
||||
|
||||
errors: list[str]
|
||||
|
||||
|
||||
class ReadsDeleteResponse(BaseModel):
|
||||
"""Suppressions et erreurs métier des lectures, avec leurs suivis supprimés."""
|
||||
|
||||
reads: list[DeletedItem | ReadDeleteError]
|
||||
read_editions: list[DeletedItem]
|
||||
@@ -0,0 +1,36 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue.author import Author
|
||||
from libs.mangacollec.model.catalogue.box_edition import BoxEdition
|
||||
from libs.mangacollec.model.catalogue.box_volume import BoxVolume
|
||||
from libs.mangacollec.model.catalogue.boxe import Boxe
|
||||
from libs.mangacollec.model.catalogue.edition import Edition
|
||||
from libs.mangacollec.model.catalogue.job import Job
|
||||
from libs.mangacollec.model.catalogue.kind import Kind
|
||||
from libs.mangacollec.model.catalogue.publisher import Publisher
|
||||
from libs.mangacollec.model.catalogue.serie import Serie
|
||||
from libs.mangacollec.model.catalogue.serie_type import SerieType
|
||||
from libs.mangacollec.model.catalogue.task import Task
|
||||
from libs.mangacollec.model.catalogue.volume import Volume
|
||||
|
||||
|
||||
class SerieResponse(BaseModel):
|
||||
"""SerieResponse."""
|
||||
|
||||
series: list[Serie]
|
||||
types: list[SerieType]
|
||||
kinds: list[Kind]
|
||||
tasks: list[Task]
|
||||
jobs: list[Job]
|
||||
authors: list[Author]
|
||||
editions: list[Edition]
|
||||
publishers: list[Publisher]
|
||||
volumes: list[Volume]
|
||||
box_editions: list[BoxEdition]
|
||||
boxes: list[Boxe]
|
||||
box_volumes: list[BoxVolume]
|
||||
@@ -0,0 +1,15 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue import Serie, SerieType
|
||||
|
||||
|
||||
class SeriesResponse(BaseModel):
|
||||
"""Réponse contenant les séries et leurs types."""
|
||||
|
||||
series: list[Serie]
|
||||
types: list[SerieType]
|
||||
@@ -0,0 +1,30 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue import (
|
||||
BoxEdition,
|
||||
Edition,
|
||||
Publisher,
|
||||
Serie,
|
||||
Volume,
|
||||
)
|
||||
from libs.mangacollec.model.catalogue.box_volume import BoxVolume
|
||||
from libs.mangacollec.model.catalogue.boxe import Boxe
|
||||
from libs.mangacollec.model.catalogue.serie_type import SerieType
|
||||
|
||||
|
||||
class VolumeResponse(BaseModel):
|
||||
"""Réponse détaillée d'un volume et de ses relations de catalogue."""
|
||||
|
||||
volumes: list[Volume]
|
||||
editions: list[Edition]
|
||||
publishers: list[Publisher]
|
||||
series: list[Serie]
|
||||
types: list[SerieType]
|
||||
box_volumes: list[BoxVolume] | None
|
||||
boxes: list[Boxe] | None
|
||||
box_editions: list[BoxEdition] | None
|
||||
@@ -0,0 +1,5 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
@@ -0,0 +1,31 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from .call_author import CallAuthorEndpoint
|
||||
from .call_authors import CallAuthorsEndpoint
|
||||
from .call_boxes import CallBoxesEndpoint
|
||||
from .call_edition import CallEditionEndpoint
|
||||
from .call_kinds import CallKindsEndpoint
|
||||
from .call_news import CallNewsEndpoint
|
||||
from .call_publisher import CallPublisherEndpoint
|
||||
from .call_publishers import CallPublishesEndpoint
|
||||
from .call_serie import CallSerieEndpoint
|
||||
from .call_series import CallSeriesEndpoint
|
||||
from .call_volume import CallVolumeEndpoint
|
||||
|
||||
__all__ = [
|
||||
"CallAuthorEndpoint",
|
||||
"CallAuthorsEndpoint",
|
||||
"CallBoxesEndpoint",
|
||||
"CallEditionEndpoint",
|
||||
"CallKindsEndpoint",
|
||||
"CallNewsEndpoint",
|
||||
"CallPublisherEndpoint",
|
||||
"CallPublishesEndpoint",
|
||||
"CallSerieEndpoint",
|
||||
"CallSeriesEndpoint",
|
||||
"CallVolumeEndpoint",
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto import AuthorResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallAuthorEndpoint(IEndpoint):
|
||||
"""Appeler l'endpoint de détail d'un auteur."""
|
||||
|
||||
async def execute(self, id_author: ID) -> AuthorResponse:
|
||||
"""Récupérer et valider le détail de l'auteur identifié."""
|
||||
response = await self.client.get(f"v2/authors/{id_author}")
|
||||
return AuthorResponse.model_validate(response)
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.dto.authors_response import AuthorsResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
|
||||
|
||||
class CallAuthorsEndpoint(IEndpoint):
|
||||
"""Appeler l'endpoint de liste des auteurs."""
|
||||
|
||||
async def execute(self) -> AuthorsResponse:
|
||||
"""Récupérer et valider les auteurs du catalogue."""
|
||||
response = await self.client.get("v2/authors")
|
||||
return AuthorsResponse.model_validate(response)
|
||||
@@ -0,0 +1,21 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.dto import BoxResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallBoxesEndpoint(IEndpoint):
|
||||
"""Appeler GET /v2/boxes/{id}."""
|
||||
|
||||
async def execute(self, id_box: ID) -> BoxResponse:
|
||||
"""Récupérer et valider le détail du coffret indiqué."""
|
||||
response = await self.client.get(f"v2/boxes/{id_box}")
|
||||
return BoxResponse.model_validate(response)
|
||||
|
||||
|
||||
CallBoxEndpoint = CallBoxesEndpoint
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto import EditionResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallEditionEndpoint(IEndpoint):
|
||||
"""Appeler GET /v2/editions/{id}."""
|
||||
|
||||
async def execute(self, id_edition: ID) -> EditionResponse:
|
||||
"""Récupérer et valider le détail d'une édition."""
|
||||
response = await self.client.get(f"v2/editions/{id_edition}")
|
||||
return EditionResponse.model_validate(response)
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto.kinds_response import KindsResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
|
||||
|
||||
class CallKindsEndpoint(IEndpoint):
|
||||
"""Appeler l'endpoint de récupération des genres."""
|
||||
|
||||
async def execute(self) -> KindsResponse:
|
||||
"""Récupérer les genres depuis MangaCollec."""
|
||||
response = await self.client.get("v2/kinds")
|
||||
return KindsResponse.model_validate(response)
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto.news_response import NewsResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
|
||||
|
||||
class CallNewsEndpoint(IEndpoint):
|
||||
"""Appeler l'endpoint des dernières sorties du catalogue."""
|
||||
|
||||
async def execute(self) -> NewsResponse:
|
||||
"""Récupérer et valider les dernières sorties."""
|
||||
response = await self.client.get("v2/volumes/news")
|
||||
return NewsResponse.model_validate(response)
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto import PublisherResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallPublisherEndpoint(IEndpoint):
|
||||
"""Appeler l'endpoint de récupération d'un éditeur."""
|
||||
|
||||
async def execute(self, id_publisher: ID) -> PublisherResponse:
|
||||
"""Récupérer un éditeur depuis MangaCollec."""
|
||||
response = await self.client.get(f"v2/publishers/{id_publisher}")
|
||||
return PublisherResponse.model_validate(response)
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto.publishers_response import PublishersResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
|
||||
|
||||
class CallPublishesEndpoint(IEndpoint):
|
||||
"""Call Publishers Class."""
|
||||
|
||||
async def execute(self) -> PublishersResponse:
|
||||
"""."""
|
||||
response = await self.client.get("v2/publishers")
|
||||
return PublishersResponse.model_validate(response)
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto import SerieResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallSerieEndpoint(IEndpoint):
|
||||
"""callSerie Class."""
|
||||
|
||||
async def execute(self, id_serie: ID) -> SerieResponse:
|
||||
"""."""
|
||||
response = await self.client.get(f"v2/series/{id_serie}")
|
||||
return SerieResponse.model_validate(response)
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto.series_response import SeriesResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
|
||||
|
||||
class CallSeriesEndpoint(IEndpoint):
|
||||
"""Appeler l'endpoint de récupération des séries."""
|
||||
|
||||
async def execute(self) -> SeriesResponse:
|
||||
"""Récupérer les séries depuis MangaCollec."""
|
||||
response = await self.client.get("v2/series")
|
||||
return SeriesResponse.model_validate(response)
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.dto.volume_response import VolumeResponse
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallVolumeEndpoint(IEndpoint):
|
||||
"""Appeler l'endpoint de détail d'un volume."""
|
||||
|
||||
async def execute(self, id_volume: ID) -> VolumeResponse:
|
||||
"""Récupérer et valider le détail du volume indiqué."""
|
||||
response = await self.client.get(f"v2/volumes/{id_volume}")
|
||||
return VolumeResponse.model_validate(response)
|
||||
@@ -0,0 +1,18 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoints de suivi MangaCollec."""
|
||||
|
||||
from libs.mangacollec.endpoints.follows.call_create_follow_edition import (
|
||||
CallCreateFollowEditionEndpoint,
|
||||
)
|
||||
from libs.mangacollec.endpoints.follows.call_delete_follow_edition import (
|
||||
CallDeleteFollowEditionEndpoint,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CallCreateFollowEditionEndpoint",
|
||||
"CallDeleteFollowEditionEndpoint",
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Wrapper de l'endpoint de création d'un suivi d'édition."""
|
||||
|
||||
from libs.mangacollec.dto import FollowEditionCreateRequest
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.model.possessions.volume_follow_edition import VolumeFollowEdition
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallCreateFollowEditionEndpoint(IEndpoint):
|
||||
"""Appel de POST /v1/follow_editions."""
|
||||
|
||||
async def execute(self, edition_id: ID, *, following: bool) -> VolumeFollowEdition:
|
||||
"""Créer ou activer le suivi de l'édition indiquée."""
|
||||
request = FollowEditionCreateRequest(edition_id=edition_id, following=following)
|
||||
response = await self.client.post(
|
||||
"v1/follow_editions",
|
||||
data=request.to_api_payload(),
|
||||
)
|
||||
return VolumeFollowEdition.model_validate(response)
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Wrapper de suppression d'un suivi d'édition."""
|
||||
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallDeleteFollowEditionEndpoint(IEndpoint):
|
||||
"""Appeler DELETE /v1/follow_editions/{follow_edition_id}."""
|
||||
|
||||
async def execute(self, follow_edition_id: ID) -> None:
|
||||
"""Supprimer le suivi d'édition identifié pour l'utilisateur courant."""
|
||||
await self.client.delete(f"v1/follow_editions/{follow_edition_id}")
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.endpoints.loans.call_create_borrower import CallCreateBorrowerEndpoint
|
||||
from libs.mangacollec.endpoints.loans.call_create_loans_multiple import CallCreateLoansMultipleEndpoint
|
||||
from libs.mangacollec.endpoints.loans.call_delete_borrower import CallDeleteBorrowerEndpoint
|
||||
from libs.mangacollec.endpoints.loans.call_delete_loans_multiple import CallDeleteLoansMultipleEndpoint
|
||||
|
||||
__all__ = [
|
||||
"CallCreateBorrowerEndpoint",
|
||||
"CallCreateLoansMultipleEndpoint",
|
||||
"CallDeleteBorrowerEndpoint",
|
||||
"CallDeleteLoansMultipleEndpoint",
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoint de création d'un emprunteur ou d'un lieu de stockage."""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from libs.mangacollec.dto import BorrowerCreateRequest, BorrowerCreateResponse
|
||||
from libs.mangacollec.interface.mutation_client_interface import IPostClient
|
||||
|
||||
|
||||
class CallCreateBorrowerEndpoint:
|
||||
"""Appel de POST /v2/borrowers via un transport injecté."""
|
||||
|
||||
def __init__(self, client: IPostClient) -> None:
|
||||
self.client = client
|
||||
|
||||
async def execute(self, *, category: Literal["person", "storage"], title: str) -> BorrowerCreateResponse:
|
||||
"""Créer un emprunteur après validation des paramètres de la requête."""
|
||||
request = BorrowerCreateRequest(category=category, title=title)
|
||||
response = await self.client.post("v2/borrowers", data=request.to_api_payload())
|
||||
return BorrowerCreateResponse.model_validate(response)
|
||||
@@ -0,0 +1,22 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoint de création groupée de prêts."""
|
||||
|
||||
from libs.mangacollec.dto import LoanCreateRequest, LoansCreateRequest, LoansCreateResponse
|
||||
from libs.mangacollec.interface.mutation_client_interface import IPostClient
|
||||
|
||||
|
||||
class CallCreateLoansMultipleEndpoint:
|
||||
"""Appel de création de prêts pour les emprunteurs et possessions indiqués."""
|
||||
|
||||
def __init__(self, client: IPostClient) -> None:
|
||||
self.client = client
|
||||
|
||||
async def execute(self, loans: list[LoanCreateRequest]) -> LoansCreateResponse:
|
||||
"""Créer le lot et conserver les erreurs métier éventuelles dans la réponse."""
|
||||
request = LoansCreateRequest(loans=loans)
|
||||
response = await self.client.post("v1/loans_multiple", data=request.to_api_payload())
|
||||
return LoansCreateResponse.model_validate(response)
|
||||
@@ -0,0 +1,21 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Wrapper de suppression d'un emprunteur."""
|
||||
|
||||
from libs.mangacollec.interface.mutation_client_interface import IDeleteClient
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallDeleteBorrowerEndpoint:
|
||||
"""Appeler DELETE /v2/borrowers/{borrower_id} via un client injectable."""
|
||||
|
||||
def __init__(self, client: IDeleteClient) -> None:
|
||||
self.client = client
|
||||
|
||||
async def execute(self, borrower_id: ID) -> None:
|
||||
"""Supprimer l'emprunteur identifié après validation de son UUID."""
|
||||
validated_id = ID(borrower_id)
|
||||
await self.client.delete(f"v2/borrowers/{validated_id}")
|
||||
@@ -0,0 +1,33 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Wrapper de suppression groupée de prêts."""
|
||||
|
||||
from libs.mangacollec.dto import LoanDeleteRequest, LoansDeleteRequest, LoansDeleteResponse
|
||||
from libs.mangacollec.exceptions import DomainValidationError
|
||||
from libs.mangacollec.interface.mutation_client_interface import IDeleteClient
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallDeleteLoansMultipleEndpoint:
|
||||
"""Appeler DELETE /v1/loans_multiple via un client injectable."""
|
||||
|
||||
def __init__(self, client: IDeleteClient) -> None:
|
||||
self.client = client
|
||||
|
||||
async def execute(self, loan_ids: list[ID]) -> LoansDeleteResponse:
|
||||
"""Supprimer les prêts indiqués et conserver leurs erreurs métier.
|
||||
|
||||
Le client exige une liste non vide et des identifiants distincts ; ces
|
||||
garde-fous locaux ne constituent pas des contraintes documentées de l'API.
|
||||
"""
|
||||
if not loan_ids:
|
||||
raise DomainValidationError("La liste des prêts ne peut pas être vide.")
|
||||
if len(set(loan_ids)) != len(loan_ids):
|
||||
raise DomainValidationError("Les identifiants de prêts doivent être distincts.")
|
||||
|
||||
request = LoansDeleteRequest(loans=[LoanDeleteRequest(id=loan_id) for loan_id in loan_ids])
|
||||
response = await self.client.delete("v1/loans_multiple", data=request.to_api_payload())
|
||||
return LoansDeleteResponse.model_validate(response)
|
||||
@@ -0,0 +1,33 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
# __project__: app_booksync
|
||||
# __author__: ShooterDev
|
||||
# __filename__: __init__.py.py
|
||||
# __directory__: core/mangacollec/possessions
|
||||
|
||||
from libs.mangacollec.endpoints.possessions.call_create_box_multiple_possessions import (
|
||||
CallCreateBoxMultiplePossessionsEndpoint,
|
||||
)
|
||||
from libs.mangacollec.endpoints.possessions.call_create_volume_multiple_possessions import (
|
||||
CallCreateVolumeMultiplePossessionsEndpoint,
|
||||
)
|
||||
from libs.mangacollec.endpoints.possessions.call_delete_box_multiple_possessions import (
|
||||
CallDeleteBoxMultiplePossessionsEndpoint,
|
||||
)
|
||||
from libs.mangacollec.endpoints.possessions.call_delete_volume_multiple_possessions import (
|
||||
CallDeleteMultiplePossessionsEndpoint,
|
||||
DeletePossessionsMultipleRequest,
|
||||
DeleteVolumePossessionMultipleRequest,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CallCreateBoxMultiplePossessionsEndpoint",
|
||||
"CallCreateVolumeMultiplePossessionsEndpoint",
|
||||
"CallDeleteBoxMultiplePossessionsEndpoint",
|
||||
"CallDeleteMultiplePossessionsEndpoint",
|
||||
"DeletePossessionsMultipleRequest",
|
||||
"DeleteVolumePossessionMultipleRequest",
|
||||
]
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoint de création groupée de possessions."""
|
||||
|
||||
from libs.mangacollec.dto import (
|
||||
CreateBoxesPossessionsMultipleRequest,
|
||||
CreateBoxPossessionMultipleRequest,
|
||||
CreateBoxPossessionsMultipleResponse,
|
||||
)
|
||||
from libs.mangacollec.exceptions import DomainValidationError
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallCreateBoxMultiplePossessionsEndpoint(IEndpoint):
|
||||
"""Appel de l'endpoint de création groupée de possessions."""
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
box_ids: list[ID],
|
||||
) -> CreateBoxPossessionsMultipleResponse:
|
||||
"""Créer les possessions correspondant aux boxes indiqués."""
|
||||
if not box_ids:
|
||||
raise DomainValidationError("La liste des boxes ne peut pas être vide.")
|
||||
if len(set(box_ids)) != len(box_ids):
|
||||
raise DomainValidationError("Les identifiants de boxes doivent être distincts.")
|
||||
|
||||
request = CreateBoxesPossessionsMultipleRequest(
|
||||
possessions=[CreateBoxPossessionMultipleRequest(box_id=box_id) for box_id in box_ids]
|
||||
)
|
||||
response = await self.client.post(
|
||||
"v1/box_possessions_multiple",
|
||||
data=request.to_api_payload(),
|
||||
)
|
||||
return CreateBoxPossessionsMultipleResponse.model_validate(response)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoint de création groupée de possessions."""
|
||||
|
||||
from libs.mangacollec.dto import (
|
||||
CreateVolumePossessionMultipleRequest,
|
||||
CreateVolumePossessionsMultipleResponse,
|
||||
CreateVolumesPossessionsMultipleRequest,
|
||||
)
|
||||
from libs.mangacollec.exceptions import DomainValidationError
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallCreateVolumeMultiplePossessionsEndpoint(IEndpoint):
|
||||
"""Appel de l'endpoint de création groupée de possessions."""
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
volume_ids: list[ID],
|
||||
) -> CreateVolumePossessionsMultipleResponse:
|
||||
"""Créer les possessions correspondant aux volumes indiqués."""
|
||||
if not volume_ids:
|
||||
raise DomainValidationError("La liste des volumes ne peut pas être vide.")
|
||||
if len(set(volume_ids)) != len(volume_ids):
|
||||
raise DomainValidationError("Les identifiants de volumes doivent être distincts.")
|
||||
|
||||
request = CreateVolumesPossessionsMultipleRequest(
|
||||
possessions=[CreateVolumePossessionMultipleRequest(volume_id=volume_id) for volume_id in volume_ids]
|
||||
)
|
||||
response = await self.client.post(
|
||||
"v1/possessions_multiple",
|
||||
data=request.to_api_payload(),
|
||||
)
|
||||
return CreateVolumePossessionsMultipleResponse.model_validate(response)
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoint de suppression groupée de possessions de boxes."""
|
||||
|
||||
from libs.mangacollec.dto import (
|
||||
DeleteBoxPossessionMultipleRequest,
|
||||
DeleteBoxPossessionsMultipleRequest,
|
||||
DeleteBoxPossessionsMultipleResponse,
|
||||
DeletePossessionMultipleRequest,
|
||||
)
|
||||
from libs.mangacollec.exceptions import DomainValidationError
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallDeleteBoxMultiplePossessionsEndpoint(IEndpoint):
|
||||
"""Appel de l'endpoint de suppression groupée de box possessions."""
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
box_possession_ids: list[ID],
|
||||
possession_ids: list[ID] | None = None,
|
||||
) -> DeleteBoxPossessionsMultipleResponse:
|
||||
"""Supprimer les box possessions et possessions indiquées."""
|
||||
if not box_possession_ids:
|
||||
raise DomainValidationError("La liste des box possessions ne peut pas être vide.")
|
||||
if len(set(box_possession_ids)) != len(box_possession_ids):
|
||||
raise DomainValidationError("Les identifiants de box possessions doivent être distincts.")
|
||||
if possession_ids is not None:
|
||||
if not possession_ids:
|
||||
raise DomainValidationError("La liste des possessions ne peut pas être vide.")
|
||||
if len(set(possession_ids)) != len(possession_ids):
|
||||
raise DomainValidationError("Les identifiants de possessions doivent être distincts.")
|
||||
|
||||
request = DeleteBoxPossessionsMultipleRequest(
|
||||
box_possessions=[
|
||||
DeleteBoxPossessionMultipleRequest(id=box_possession_id) for box_possession_id in box_possession_ids
|
||||
],
|
||||
possessions=(
|
||||
[DeletePossessionMultipleRequest(id=possession_id) for possession_id in possession_ids]
|
||||
if possession_ids is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
response = await self.client.delete(
|
||||
"v1/box_possessions_multiple",
|
||||
data=request.to_api_payload(),
|
||||
)
|
||||
return DeleteBoxPossessionsMultipleResponse.model_validate(response)
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoint de suppression groupée de possessions."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.dto import (
|
||||
DeleteVolumePossessionsMultipleResponse,
|
||||
)
|
||||
from libs.mangacollec.exceptions import DomainValidationError
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class DeleteVolumePossessionMultipleRequest(BaseModel):
|
||||
"""VolumePossession à supprimer."""
|
||||
|
||||
id: ID
|
||||
|
||||
|
||||
class DeletePossessionsMultipleRequest(BaseModel):
|
||||
"""Liste des possessions à supprimer."""
|
||||
|
||||
possessions: list[DeleteVolumePossessionMultipleRequest]
|
||||
|
||||
def to_api_payload(self) -> list[dict[str, str]]:
|
||||
"""Convertir la requête vers le tableau JSON de l'API."""
|
||||
return [item.model_dump(mode="json") for item in self.possessions]
|
||||
|
||||
|
||||
class CallDeleteMultiplePossessionsEndpoint(IEndpoint):
|
||||
"""Appel de l'endpoint de suppression groupée de possessions."""
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
possession_ids: list[ID],
|
||||
) -> DeleteVolumePossessionsMultipleResponse:
|
||||
"""Supprimer les possessions indiquées."""
|
||||
if not possession_ids:
|
||||
raise DomainValidationError("La liste des possessions ne peut pas être vide.")
|
||||
if len(set(possession_ids)) != len(possession_ids):
|
||||
raise DomainValidationError("Les identifiants de possessions doivent être distincts.")
|
||||
|
||||
request = DeletePossessionsMultipleRequest(
|
||||
possessions=[DeleteVolumePossessionMultipleRequest(id=possession_id) for possession_id in possession_ids]
|
||||
)
|
||||
response = await self.client.delete(
|
||||
"v1/possessions_multiple",
|
||||
data=request.to_api_payload(),
|
||||
)
|
||||
return DeleteVolumePossessionsMultipleResponse.model_validate(response)
|
||||
@@ -0,0 +1,18 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Endpoints de lectures MangaCollec."""
|
||||
|
||||
from libs.mangacollec.endpoints.reads.call_create_reads_multiple import (
|
||||
CallCreateReadsMultipleEndpoint,
|
||||
)
|
||||
from libs.mangacollec.endpoints.reads.call_delete_reads_multiple import (
|
||||
CallDeleteReadsMultipleEndpoint,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CallCreateReadsMultipleEndpoint",
|
||||
"CallDeleteReadsMultipleEndpoint",
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Wrapper de création groupée de lectures."""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from libs.mangacollec.dto import ReadCreateRequest, ReadsCreateRequest, ReadsCreateResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from libs.mangacollec.interface.mutation_client_interface import IPostClient
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallCreateReadsMultipleEndpoint:
|
||||
"""Appel de POST /v1/reads_multiple avec un client HTTP injectable."""
|
||||
|
||||
def __init__(self, client: "IPostClient") -> None:
|
||||
self.client = client
|
||||
|
||||
async def execute(self, volume_ids: "list[ID]") -> ReadsCreateResponse:
|
||||
"""Marquer les volumes lus et valider les lectures créées."""
|
||||
request = ReadsCreateRequest(reads=[ReadCreateRequest(volume_id=volume_id) for volume_id in volume_ids])
|
||||
response = await self.client.post("v1/reads_multiple", data=request.to_api_payload())
|
||||
return ReadsCreateResponse.model_validate(response)
|
||||
@@ -0,0 +1,29 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Wrapper de suppression groupée de lectures."""
|
||||
|
||||
from libs.mangacollec.dto import ReadDeleteRequest, ReadsDeleteRequest, ReadsDeleteResponse
|
||||
from libs.mangacollec.exceptions import DomainValidationError
|
||||
from libs.mangacollec.interface.mutation_client_interface import IDeleteClient
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class CallDeleteReadsMultipleEndpoint:
|
||||
"""Appeler DELETE /v1/reads_multiple via un client injectable."""
|
||||
|
||||
def __init__(self, client: IDeleteClient) -> None:
|
||||
self.client = client
|
||||
|
||||
async def execute(self, read_ids: list[ID]) -> ReadsDeleteResponse:
|
||||
"""Supprimer les lectures indiquées et conserver les erreurs métier."""
|
||||
if not read_ids:
|
||||
raise DomainValidationError("La liste des lectures ne peut pas être vide.")
|
||||
if len(set(read_ids)) != len(read_ids):
|
||||
raise DomainValidationError("Les identifiants de lectures doivent être distincts.")
|
||||
|
||||
request = ReadsDeleteRequest(reads=[ReadDeleteRequest(id=read_id) for read_id in read_ids])
|
||||
response = await self.client.delete("v1/reads_multiple", data=request.to_api_payload())
|
||||
return ReadsDeleteResponse.model_validate(response)
|
||||
Whitespace-only changes.
@@ -0,0 +1,26 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Exceptions explicites utilisées par le domaine et ses adaptateurs."""
|
||||
|
||||
|
||||
class BooksyncError(Exception):
|
||||
"""Base commune des erreurs attendues par Booksync."""
|
||||
|
||||
|
||||
class DomainValidationError(ValueError, BooksyncError):
|
||||
"""Indiquer qu'une donnée métier ou une commande est invalide."""
|
||||
|
||||
|
||||
class ConfigurationError(ValueError, BooksyncError):
|
||||
"""Indiquer qu'une configuration requise est absente ou invalide."""
|
||||
|
||||
|
||||
class AuthenticationError(RuntimeError, BooksyncError):
|
||||
"""Indiquer qu'une authentification ou un renouvellement a échoué."""
|
||||
|
||||
|
||||
class RemoteRequestError(RuntimeError, BooksyncError):
|
||||
"""Indiquer qu'une requête distante ne peut pas aboutir."""
|
||||
@@ -0,0 +1,14 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
# __project__: app_booksync
|
||||
# __author__: ShooterDev
|
||||
# __filename__: __init__.py.py
|
||||
# __directory__: core/mangacollec/interface
|
||||
|
||||
from libs.mangacollec.interface.endpoint_interface import IEndpoint
|
||||
from libs.mangacollec.interface.mutation_client_interface import IDeleteClient, IPostClient
|
||||
|
||||
__all__ = ["IDeleteClient", "IEndpoint", "IPostClient"]
|
||||
@@ -0,0 +1,18 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from typing import Protocol
|
||||
|
||||
from libs.mangacollec.core.mangacollec import Mangacollec
|
||||
|
||||
|
||||
class IEndpoint(Protocol):
|
||||
"""Définir le contrat commun des endpoints."""
|
||||
|
||||
client: Mangacollec
|
||||
|
||||
def __init__(self, client: Mangacollec) -> None:
|
||||
"""Initialize the endpoint with a MangaCollec client."""
|
||||
self.client = client
|
||||
@@ -0,0 +1,24 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Ports HTTP des endpoints de mutation, substituables par des doubles de test."""
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class IPostClient(Protocol):
|
||||
"""Transport requis par un endpoint de création."""
|
||||
|
||||
async def post(self, url: str, data: dict[str, object] | list[dict[str, str]]) -> dict[str, object]:
|
||||
"""Envoyer le corps JSON et retourner la réponse décodée."""
|
||||
...
|
||||
|
||||
|
||||
class IDeleteClient(Protocol):
|
||||
"""Transport requis par un endpoint de suppression."""
|
||||
|
||||
async def delete(self, url: str, data: dict[str, object] | list[dict[str, str]] | None = None) -> dict[str, object]:
|
||||
"""Supprimer la ressource ; une réponse sans contenu devient un objet vide."""
|
||||
...
|
||||
@@ -0,0 +1,341 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
|
||||
import httpx
|
||||
|
||||
from libs.mangacollec.core.retry_policy import RetryPolicy
|
||||
from libs.mangacollec.exceptions import AuthenticationError, ConfigurationError, RemoteRequestError
|
||||
from libs.mangacollec.model.credential import Credential
|
||||
from libs.mangacollec.model.token import Token
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Mangacollec:
|
||||
"""Client asynchrone de l'API MangaCollec."""
|
||||
|
||||
_AUTHENTICATION_PATH: str = "oauth/token"
|
||||
_DEFAULT_REQUEST_TIMEOUT: float = 30.0
|
||||
_TOKEN_SAFETY_MARGIN: int = 30 * 60
|
||||
_SERVER_ERROR_MIN: int = 500
|
||||
_SERVER_ERROR_MAX: int = 600
|
||||
_NO_CONTENT_STATUS: int = 204
|
||||
_headers: dict[str, str]
|
||||
_client: httpx.AsyncClient
|
||||
_oauth_client: httpx.AsyncClient
|
||||
_is_auth: bool
|
||||
|
||||
_MANGACOLLEC_CLIENT_ID: str
|
||||
_MANGACOLLEC_CLIENT_SECRET: str
|
||||
|
||||
def __init__(self, proxy: str | None = None) -> None:
|
||||
"""Configure HTTP clients with environment-provided credentials.
|
||||
|
||||
Les requêtes vers les ressources peuvent passer par ``proxy``. Les échanges
|
||||
OAuth restent toujours directs afin de ne pas transmettre les identifiants
|
||||
de l'application à un proxy tiers.
|
||||
"""
|
||||
self.validate_mangacollec_credentials()
|
||||
|
||||
self._headers = {
|
||||
"Accept": "application/json",
|
||||
}
|
||||
|
||||
self._token: Token | None = None
|
||||
|
||||
self._credential: Credential | None = None
|
||||
|
||||
self._is_auth = False
|
||||
|
||||
self._token_received_at: float | None = None
|
||||
|
||||
self._token_refresh_lock = asyncio.Lock()
|
||||
|
||||
validated_proxy = self._validate_proxy_url(proxy)
|
||||
request_timeout = self._request_timeout_from_environment()
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url="https://api.mangacollec.com/",
|
||||
timeout=request_timeout,
|
||||
headers=self._headers,
|
||||
proxy=validated_proxy,
|
||||
trust_env=False,
|
||||
verify=True,
|
||||
)
|
||||
self._oauth_client = httpx.AsyncClient(
|
||||
base_url="https://api.mangacollec.com/",
|
||||
timeout=request_timeout,
|
||||
headers=self._headers,
|
||||
proxy=None,
|
||||
trust_env=False,
|
||||
verify=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _validate_proxy_url(proxy: str | None) -> str | None:
|
||||
"""Validate an HTTP(S) proxy URL that HTTPX can use."""
|
||||
if proxy is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
proxy_url = httpx.URL(proxy)
|
||||
except (httpx.InvalidURL, TypeError) as error:
|
||||
raise ConfigurationError("L'URL du proxy MangaCollec doit être une URL HTTP(S) valide.") from error
|
||||
|
||||
if (
|
||||
proxy_url.scheme not in {"http", "https"}
|
||||
or not proxy_url.host
|
||||
or proxy_url.path not in {"", "/"}
|
||||
or proxy_url.query
|
||||
or proxy_url.fragment
|
||||
):
|
||||
raise ConfigurationError("L'URL du proxy MangaCollec doit être une URL HTTP(S) valide.")
|
||||
|
||||
return str(proxy_url)
|
||||
|
||||
def validate_mangacollec_credentials(self) -> None:
|
||||
"""Charger et valider les identifiants MangaCollec depuis l'environnement."""
|
||||
client_id = os.environ.get("MANGACOLLEC_CLIENT_ID", None)
|
||||
|
||||
client_secret = os.environ.get("MANGACOLLEC_CLIENT_SECRET", None)
|
||||
|
||||
if client_id is None or client_secret is None:
|
||||
raise ConfigurationError("Les identifiants MangaCollec sont requis.")
|
||||
|
||||
self._MANGACOLLEC_CLIENT_ID = client_id
|
||||
|
||||
self._MANGACOLLEC_CLIENT_SECRET = client_secret
|
||||
|
||||
@classmethod
|
||||
def _request_timeout_from_environment(cls) -> float:
|
||||
"""Read a positive finite HTTP timeout from the local environment."""
|
||||
raw_timeout = os.environ.get("BOOKSYNC_REQUEST_TIMEOUT", str(cls._DEFAULT_REQUEST_TIMEOUT))
|
||||
try:
|
||||
timeout = float(raw_timeout)
|
||||
except ValueError as error:
|
||||
raise ConfigurationError("BOOKSYNC_REQUEST_TIMEOUT doit être un nombre positif.") from error
|
||||
if not math.isfinite(timeout) or timeout <= 0:
|
||||
raise ConfigurationError("BOOKSYNC_REQUEST_TIMEOUT doit être un nombre positif.")
|
||||
return timeout
|
||||
|
||||
@property
|
||||
def is_auth(self) -> bool:
|
||||
"""Indiquer si le token courant provient d'une authentification utilisateur."""
|
||||
return self._is_auth
|
||||
|
||||
@property
|
||||
def authorization_header(self) -> str:
|
||||
"""Retourner l'en-tête d'autorisation du token courant."""
|
||||
authorization_header = self._client.headers.get("Authorization")
|
||||
if not isinstance(authorization_header, str):
|
||||
raise AuthenticationError("Le client MangaCollec n'est pas authentifié.")
|
||||
return authorization_header
|
||||
|
||||
async def get_authorization_header(self, *, force_refresh: bool = False) -> str:
|
||||
"""Return a valid authorization header, refreshing it when requested."""
|
||||
expected_access_token = None if self._token is None else self._token.access_token
|
||||
await self._ensure_token_valid(
|
||||
force_refresh=force_refresh,
|
||||
expected_access_token=expected_access_token,
|
||||
)
|
||||
return self.authorization_header
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
url: str,
|
||||
param: dict[str, str] | None = None,
|
||||
data: dict[str, object] | list[dict[str, str]] | None = None,
|
||||
*,
|
||||
is_token_required: bool = True,
|
||||
) -> dict[str, object]:
|
||||
"""Execute a request with up to five delayed retries."""
|
||||
if is_token_required:
|
||||
await self._ensure_token_valid()
|
||||
|
||||
retryable = method.upper() == "GET" or url == self._AUTHENTICATION_PATH
|
||||
request_client = self._client if is_token_required else self._oauth_client
|
||||
for retry_count in range(RetryPolicy.max_retries + 1):
|
||||
try:
|
||||
response = await request_client.request(method=method, url=url, params=param, json=data)
|
||||
logger.info("%s | %s | %s", method.upper(), response.url, response.status_code)
|
||||
|
||||
if self._SERVER_ERROR_MIN <= response.status_code < self._SERVER_ERROR_MAX:
|
||||
if not retryable or retry_count == RetryPolicy.max_retries:
|
||||
response.raise_for_status()
|
||||
else:
|
||||
response.raise_for_status()
|
||||
if response.status_code == self._NO_CONTENT_STATUS:
|
||||
return {}
|
||||
payload = response.json()
|
||||
if not isinstance(payload, dict):
|
||||
raise RemoteRequestError("La réponse HTTP doit être un objet JSON.")
|
||||
return payload
|
||||
except (httpx.TimeoutException, httpx.NetworkError, httpx.RemoteProtocolError) as error:
|
||||
logger.warning(
|
||||
"%s %s : échec réseau à la tentative %d/%d (%s).",
|
||||
method.upper(),
|
||||
url,
|
||||
retry_count + 1,
|
||||
RetryPolicy.max_retries + 1,
|
||||
type(error).__name__,
|
||||
)
|
||||
if not retryable or retry_count == RetryPolicy.max_retries:
|
||||
raise
|
||||
|
||||
await asyncio.sleep(RetryPolicy.delay_for_retry(retry_count + 1))
|
||||
|
||||
raise RemoteRequestError("La requête a échoué après cinq reprises.")
|
||||
|
||||
async def get(self, url: str) -> dict[str, object]:
|
||||
"""Effectuer une requête GET."""
|
||||
return await self._request(method="GET", url=url)
|
||||
|
||||
async def post(
|
||||
self,
|
||||
url: str,
|
||||
data: dict[str, object] | list[dict[str, str]],
|
||||
) -> dict[str, object]:
|
||||
"""Effectuer une requête POST."""
|
||||
return await self._request(method="POST", url=url, data=data)
|
||||
|
||||
async def put(
|
||||
self,
|
||||
url: str,
|
||||
data: dict[str, object] | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""Effectuer une requête PUT."""
|
||||
return await self._request(method="PUT", url=url, data=data)
|
||||
|
||||
async def delete(
|
||||
self,
|
||||
url: str,
|
||||
data: dict[str, object] | list[dict[str, str]] | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""Effectuer une requête DELETE."""
|
||||
return await self._request(method="DELETE", url=url, data=data)
|
||||
|
||||
async def login(self) -> None:
|
||||
"""Obtenir et installer un token OAuth avec les identifiants du client."""
|
||||
credential = Credential(
|
||||
client_id=self._MANGACOLLEC_CLIENT_ID,
|
||||
client_secret=self._MANGACOLLEC_CLIENT_SECRET,
|
||||
grant_type="client_credentials",
|
||||
)
|
||||
await self._authenticate(credential)
|
||||
|
||||
async def login_password(self, username: str, password: str) -> None:
|
||||
"""Obtenir et installer un token OAuth avec un compte utilisateur."""
|
||||
credential = Credential(
|
||||
client_id=self._MANGACOLLEC_CLIENT_ID,
|
||||
client_secret=self._MANGACOLLEC_CLIENT_SECRET,
|
||||
grant_type="password",
|
||||
username=username,
|
||||
password=password,
|
||||
)
|
||||
await self._authenticate(credential)
|
||||
|
||||
async def _authenticate(self, credential: Credential) -> None:
|
||||
"""Obtenir un token avec les identifiants fournis et le mémoriser."""
|
||||
logger.info("Récupération du token MangaCollec en cours (grant_type=%s).", credential.grant_type)
|
||||
response = await self._request(
|
||||
method="POST",
|
||||
url=self._AUTHENTICATION_PATH,
|
||||
data=credential.model_dump(exclude_none=True),
|
||||
is_token_required=False,
|
||||
)
|
||||
self._credential = credential
|
||||
self._is_auth = credential.grant_type == "password"
|
||||
self._set_token(response)
|
||||
logger.info("Récupération du token MangaCollec réussie.")
|
||||
|
||||
async def _ensure_token_valid(
|
||||
self,
|
||||
*,
|
||||
force_refresh: bool = False,
|
||||
expected_access_token: str | None = None,
|
||||
) -> None:
|
||||
"""Renouveler le token avant son expiration effective ou sur demande."""
|
||||
if self._token is None:
|
||||
async with self._token_refresh_lock:
|
||||
if self._token is None:
|
||||
await self.login()
|
||||
return
|
||||
if not force_refresh and not self._token_is_expiring():
|
||||
return
|
||||
|
||||
async with self._token_refresh_lock:
|
||||
if self._token is None:
|
||||
return
|
||||
if (
|
||||
force_refresh
|
||||
and expected_access_token is not None
|
||||
and self._token.access_token != expected_access_token
|
||||
):
|
||||
return
|
||||
if not force_refresh and not self._token_is_expiring():
|
||||
return
|
||||
|
||||
logger.debug("Le token MangaCollec arrive dans sa fenêtre de renouvellement.")
|
||||
if self._token.refresh_token is not None:
|
||||
await self._refresh_token()
|
||||
elif self._credential is not None:
|
||||
await self._authenticate(self._credential)
|
||||
else:
|
||||
raise AuthenticationError("Impossible de renouveler le token sans identifiants.")
|
||||
|
||||
def _token_is_expiring(self) -> bool:
|
||||
"""Indiquer si le token est dans sa fenêtre de renouvellement."""
|
||||
if self._token is None or self._token_received_at is None:
|
||||
return False
|
||||
renewal_delay = max(0, self._token.expires_in - self._TOKEN_SAFETY_MARGIN)
|
||||
return time.monotonic() >= self._token_received_at + renewal_delay
|
||||
|
||||
async def _refresh_token(self) -> None:
|
||||
"""Renouveler le token avec le refresh token OAuth courant."""
|
||||
if self._token is None or self._token.refresh_token is None:
|
||||
raise AuthenticationError("Aucun refresh token disponible.")
|
||||
|
||||
logger.info("Renouvellement du token MangaCollec en cours.")
|
||||
previous_refresh_token = self._token.refresh_token
|
||||
credential = Credential(
|
||||
client_id=self._MANGACOLLEC_CLIENT_ID,
|
||||
client_secret=self._MANGACOLLEC_CLIENT_SECRET,
|
||||
grant_type="refresh_token",
|
||||
refresh_token=previous_refresh_token,
|
||||
)
|
||||
response = await self._request(
|
||||
"POST",
|
||||
url=self._AUTHENTICATION_PATH,
|
||||
data=credential.model_dump(exclude_none=True),
|
||||
is_token_required=False,
|
||||
)
|
||||
if response.get("refresh_token") is None:
|
||||
response["refresh_token"] = previous_refresh_token
|
||||
self._set_token(response)
|
||||
logger.info("Renouvellement du token MangaCollec réussi.")
|
||||
|
||||
def _set_token(self, response: dict[str, object]) -> None:
|
||||
"""Installe un token pour les requêtes suivantes."""
|
||||
self._token = Token.model_validate(response)
|
||||
self._token_received_at = time.monotonic()
|
||||
self._client.headers["Authorization"] = f"Bearer {self._token.access_token}"
|
||||
logger.debug(
|
||||
"Token MangaCollec installé (expires_in=%s, is_auth=%s).",
|
||||
self._token.expires_in,
|
||||
self.is_auth,
|
||||
)
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Fermer les clients HTTP des ressources et d'authentification."""
|
||||
try:
|
||||
await self._client.aclose()
|
||||
finally:
|
||||
await self._oauth_client.aclose()
|
||||
@@ -0,0 +1,9 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
__all__ = ["Base"]
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
@@ -0,0 +1,14 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
"""Modèle de base portant un identifiant."""
|
||||
|
||||
id: ID
|
||||
@@ -0,0 +1,40 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Modèles du catalogue."""
|
||||
|
||||
from .author import Author
|
||||
from .box_edition import BoxEdition
|
||||
from .box_volume import BoxVolume
|
||||
from .boxe import Boxe
|
||||
from .edition import Edition
|
||||
from .job import Job
|
||||
from .kind import Kind
|
||||
from .native_ad_volume_home_first import NativeAdVolumeHomeFirst
|
||||
from .news import News, NewsBox, NewsVolume
|
||||
from .publisher import Publisher
|
||||
from .serie import Serie
|
||||
from .serie_type import SerieType
|
||||
from .task import Task
|
||||
from .volume import Volume
|
||||
|
||||
__all__ = [
|
||||
"Author",
|
||||
"BoxEdition",
|
||||
"BoxVolume",
|
||||
"Boxe",
|
||||
"Edition",
|
||||
"Job",
|
||||
"Kind",
|
||||
"NativeAdVolumeHomeFirst",
|
||||
"News",
|
||||
"NewsBox",
|
||||
"NewsVolume",
|
||||
"Publisher",
|
||||
"Serie",
|
||||
"SerieType",
|
||||
"Task",
|
||||
"Volume",
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
|
||||
|
||||
class Author(Base):
|
||||
"""Auteur associé à une série."""
|
||||
|
||||
name: str
|
||||
first_name: str | None
|
||||
tasks_count: int
|
||||
@@ -0,0 +1,18 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object import ID
|
||||
|
||||
|
||||
class BoxEdition(Base):
|
||||
"""Édition commerciale d'un coffret."""
|
||||
|
||||
title: str
|
||||
publisher_id: ID
|
||||
boxes_count: int
|
||||
adult_content: bool
|
||||
box_follow_editions_count: int
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object import ID
|
||||
|
||||
|
||||
class BoxVolume(Base):
|
||||
"""Association entre un coffret et un volume."""
|
||||
|
||||
box_id: ID
|
||||
volume_id: ID
|
||||
included: bool
|
||||
@@ -0,0 +1,42 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from datetime import date
|
||||
|
||||
from pydantic import field_validator
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object.asin import ASIN
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
from libs.mangacollec.value_object.isbn import ISBN
|
||||
|
||||
|
||||
class Boxe(Base):
|
||||
"""Coffret regroupant des volumes."""
|
||||
|
||||
id: ID
|
||||
title: str
|
||||
number: int
|
||||
release_date: date | None
|
||||
isbn: ISBN | None
|
||||
asin: ASIN | None
|
||||
commercial_stop: bool
|
||||
box_edition_id: ID
|
||||
box_possessions_count: int
|
||||
image_url: str | None
|
||||
content: str | None = None
|
||||
|
||||
@field_validator("isbn", mode="before")
|
||||
@classmethod
|
||||
def invalid_isbn_is_missing(cls, value: object) -> object:
|
||||
"""Convertir un ISBN externe malformé en valeur absente."""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
ISBN(str(value))
|
||||
except ValueError:
|
||||
return None
|
||||
return value
|
||||
@@ -0,0 +1,21 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class Edition(Base):
|
||||
"""Représenter une édition de série."""
|
||||
|
||||
title: str | None
|
||||
series_id: ID
|
||||
publisher_id: ID
|
||||
parent_edition_id: ID | None
|
||||
volumes_count: int
|
||||
last_volume_number: int | None
|
||||
commercial_stop: bool
|
||||
not_finished: bool
|
||||
follow_editions_count: int
|
||||
@@ -0,0 +1,13 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
|
||||
|
||||
class Job(Base):
|
||||
"""Métier exercé par un auteur."""
|
||||
|
||||
title: str
|
||||
@@ -0,0 +1,15 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object import ID
|
||||
|
||||
|
||||
class Kind(Base):
|
||||
"""Genre associé à une série."""
|
||||
|
||||
title: str
|
||||
series_ids: list[ID] | None = None
|
||||
@@ -0,0 +1,18 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from datetime import date
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class NativeAdVolumeHomeFirst(Base):
|
||||
"""Publicité native associée au volume mis en avant sur l'accueil."""
|
||||
|
||||
volume_id: ID
|
||||
title: str
|
||||
start_date: date
|
||||
end_date: date
|
||||
@@ -0,0 +1,34 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libs.mangacollec.model.catalogue.boxe import Boxe
|
||||
from libs.mangacollec.model.catalogue.volume import Volume
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class NewsVolume(BaseModel):
|
||||
"""Volume de nouveauté avec les relations utiles à sa présentation."""
|
||||
|
||||
volume: Volume
|
||||
publisher_id: ID | None
|
||||
publisher_title: str | None
|
||||
series_title: str | None
|
||||
|
||||
|
||||
class NewsBox(BaseModel):
|
||||
"""Coffret de nouveauté avec les relations utiles à sa présentation."""
|
||||
|
||||
box: Boxe
|
||||
publisher_id: ID | None
|
||||
publisher_title: str | None
|
||||
|
||||
|
||||
class News(BaseModel):
|
||||
"""Nouveautés résolues depuis l'index du store local."""
|
||||
|
||||
volumes: list[NewsVolume]
|
||||
boxes: list[NewsBox]
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
|
||||
|
||||
class Publisher(Base):
|
||||
"""Éditeur et ses métadonnées."""
|
||||
|
||||
title: str
|
||||
closed: bool
|
||||
editions_count: int
|
||||
no_amazon: bool
|
||||
@@ -0,0 +1,19 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object import ID
|
||||
|
||||
|
||||
class Serie(Base):
|
||||
"""Représenter une série du catalogue."""
|
||||
|
||||
title: str
|
||||
type_id: ID | None = None
|
||||
kinds_ids: list[ID] | None = None
|
||||
adult_content: bool | None = None
|
||||
editions_count: int | None = None
|
||||
tasks_count: int | None = None
|
||||
@@ -0,0 +1,14 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
|
||||
|
||||
class SerieType(Base):
|
||||
"""Représenter un type de série."""
|
||||
|
||||
title: str
|
||||
to_display: bool | None = None
|
||||
@@ -0,0 +1,14 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from libs.mangacollec.model import Base
|
||||
|
||||
|
||||
class Task(Base):
|
||||
"""Représenter une tâche de série."""
|
||||
|
||||
series_id: str
|
||||
author_id: str | None = None
|
||||
job_id: str | None = None
|
||||
@@ -0,0 +1,41 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from datetime import date
|
||||
|
||||
from pydantic import field_validator
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object.asin import ASIN
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
from libs.mangacollec.value_object.isbn import ISBN
|
||||
|
||||
|
||||
class Volume(Base):
|
||||
"""Représenter un volume d'édition."""
|
||||
|
||||
title: str | None
|
||||
number: int
|
||||
release_date: date | None
|
||||
isbn: ISBN | None
|
||||
asin: ASIN | None
|
||||
edition_id: ID
|
||||
possessions_count: int
|
||||
not_sold: bool
|
||||
image_url: str | None
|
||||
nb_pages: int | None = None
|
||||
content: str | None = None
|
||||
|
||||
@field_validator("isbn", mode="before")
|
||||
@classmethod
|
||||
def invalid_isbn_is_missing(cls, value: object) -> object:
|
||||
"""Convertir un ISBN externe malformé en valeur absente."""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
ISBN(str(value))
|
||||
except ValueError:
|
||||
return None
|
||||
return value
|
||||
@@ -0,0 +1,19 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Credential(BaseModel):
|
||||
"""Identifiants transmis à l'endpoint OAuth."""
|
||||
|
||||
client_id: str
|
||||
client_secret: str
|
||||
grant_type: Literal["password", "client_credentials", "refresh_token"] = "client_credentials"
|
||||
username: str | None = None
|
||||
password: str | None = None
|
||||
refresh_token: str | None = None
|
||||
@@ -0,0 +1,14 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Modèle technique commun d'une ressource supprimée par l'API."""
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
|
||||
|
||||
class DeletedItem(Base):
|
||||
"""Ressource identifiée dont la suppression a réussi."""
|
||||
|
||||
deleted: bool
|
||||
@@ -0,0 +1,10 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from .borrower import Borrower
|
||||
from .loan import Loan
|
||||
from .loan_delete_result import LoanDeleteResult
|
||||
|
||||
__all__ = ["Borrower", "Loan", "LoanDeleteResult"]
|
||||
@@ -0,0 +1,20 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Projection d'un emprunteur ou d'un lieu de stockage dans la collection."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class Borrower(Base):
|
||||
"""Emprunteur appartenant à un utilisateur, sans date de mise à jour."""
|
||||
|
||||
user_id: ID
|
||||
title: str
|
||||
category: str
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,19 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Modèle d'un prêt de possession à un emprunteur."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class Loan(Base):
|
||||
"""Prêt créé avec ses relations et sa date de création."""
|
||||
|
||||
borrower_id: ID
|
||||
possession_id: ID
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Résultat individuel de suppression d'un prêt."""
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
|
||||
|
||||
class LoanDeleteResult(Base):
|
||||
"""Identifiant traité et erreurs métier éventuelles, même sous HTTP 200."""
|
||||
|
||||
errors: list[str] = Field(default_factory=list)
|
||||
@@ -0,0 +1,22 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
# __project__: app_booksync
|
||||
# __author__: ShooterDev
|
||||
# __filename__: __init__.py
|
||||
# __directory__: core/model/possessions
|
||||
"""Modèles liés aux possessions de l'utilisateur."""
|
||||
|
||||
from .box_follow_edition import BoxFollowEdition
|
||||
from .box_possession import BoxPossession
|
||||
from .volume_follow_edition import VolumeFollowEdition
|
||||
from .volume_possession import VolumePossession
|
||||
|
||||
__all__ = [
|
||||
"BoxFollowEdition",
|
||||
"BoxPossession",
|
||||
"VolumeFollowEdition",
|
||||
"VolumePossession",
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class BoxFollowEdition(Base):
|
||||
"""Suivi d'édition créé implicitement par l'API."""
|
||||
|
||||
user_id: ID
|
||||
box_edition_id: ID
|
||||
following: bool
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object import ID
|
||||
|
||||
|
||||
class BoxPossession(Base):
|
||||
"""VolumePossession de box créée."""
|
||||
|
||||
user_id: ID
|
||||
box_id: ID
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,19 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class VolumeFollowEdition(Base):
|
||||
"""Suivi d'édition créé implicitement par l'API."""
|
||||
|
||||
user_id: ID
|
||||
edition_id: ID
|
||||
following: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime | None = None
|
||||
@@ -0,0 +1,17 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class VolumePossession(Base):
|
||||
"""VolumePossession de volume créée."""
|
||||
|
||||
user_id: ID
|
||||
volume_id: ID
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,10 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
from .read import Read
|
||||
from .read_edition import ReadEdition
|
||||
|
||||
__all__ = ["Read", "ReadEdition"]
|
||||
@@ -0,0 +1,19 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Lecture d'un volume par un utilisateur."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class Read(Base):
|
||||
"""Lecture observée dans les collections et la création groupée."""
|
||||
|
||||
user_id: ID
|
||||
volume_id: ID
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,24 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
"""Projection de création de l'état de lecture d'une édition."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from libs.mangacollec.model.base import Base
|
||||
from libs.mangacollec.value_object.id import ID
|
||||
|
||||
|
||||
class ReadEdition(Base):
|
||||
"""État renvoyé par POST /v1/reads_multiple, sans champ updated_at.
|
||||
|
||||
La variante de collection expose updated_at ; sa nullabilité n'est pas
|
||||
documentée. Cette projection de création ne l'ajoute donc pas.
|
||||
"""
|
||||
|
||||
user_id: ID
|
||||
edition_id: ID
|
||||
reading: bool
|
||||
created_at: datetime
|
||||
@@ -0,0 +1,16 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
"""Représenter un token OAuth2 MangaCollec."""
|
||||
|
||||
access_token: str
|
||||
token_type: str
|
||||
expires_in: int
|
||||
refresh_token: str | None = None
|
||||
created_at: int
|
||||
@@ -0,0 +1,27 @@
|
||||
# __ _____ _ __ ____________________ __ _____________________ _____ _______________
|
||||
# / ___/ / /_ ____ ____ / /_ ___ ____ / __ \ ___ _ __/
|
||||
# \__ \ / __ \ / __ \ / __ \ / __/ / _ \ / __/ ____ / / / / / _ \ | | / /
|
||||
# ___/ / / / / / / /_/ / / /_/ / / /_ / __/ / / /___/ / /_/ / / __/ | |/ /
|
||||
# /____/ /_/ /_/ \____/ \____/ \__/ \___/ /_/ _______ /______/ \___/ |___/
|
||||
|
||||
"""Fixed retry policy shared by MangaCollec transports."""
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
class RetryPolicy:
|
||||
"""Define the bounded exponential delay used before retrying MangaCollec requests."""
|
||||
|
||||
max_retries: ClassVar[int] = 5
|
||||
initial_delay_seconds: ClassVar[float] = 0.5
|
||||
maximum_delay_seconds: ClassVar[float] = 8.0
|
||||
|
||||
@classmethod
|
||||
def delay_for_retry(cls, retry_count: int) -> float:
|
||||
"""Return the delay for a scheduled retry, where the first retry is one."""
|
||||
if retry_count < 1:
|
||||
raise ValueError("Le numéro de reprise doit être supérieur ou égal à un.")
|
||||
delay_seconds: float = cls.initial_delay_seconds * 2 ** (retry_count - 1)
|
||||
if delay_seconds >= cls.maximum_delay_seconds:
|
||||
return cls.maximum_delay_seconds
|
||||
return delay_seconds
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user