Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
7131930d8e |
|||
| 4c2f85c105 | |||
| 13e21d53c1 | |||
|
983e4db02e |
|||
| dca90c8b22 | |||
|
4b7ffbc914 |
14 changed files with 980 additions and 688 deletions
|
|
@ -1 +1 @@
|
||||||
3.11
|
3.13
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: 0.3.3
|
appVersion: 0.3.5
|
||||||
description: A Helm chart for Kubernetes
|
description: A Helm chart for Kubernetes
|
||||||
name: huesoporro
|
name: huesoporro
|
||||||
type: application
|
type: application
|
||||||
version: 0.3.3
|
version: 0.3.5
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ fullnameOverride: ''
|
||||||
image:
|
image:
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
repository: git.roboces.dev/catalin/huesoporro
|
repository: git.roboces.dev/catalin/huesoporro
|
||||||
tag: 0.3.3
|
tag: 0.3.5
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
ingress:
|
ingress:
|
||||||
annotations: {}
|
annotations: {}
|
||||||
|
|
|
||||||
55
devenv.lock
55
devenv.lock
|
|
@ -19,10 +19,10 @@
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1733328505,
|
"lastModified": 1747046372,
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -46,10 +46,31 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"git-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1747372754,
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gitignore": {
|
"gitignore": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"pre-commit-hooks",
|
"git-hooks",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -83,7 +104,7 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-python": {
|
"nixpkgs-python": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat_2",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
|
|
@ -101,33 +122,15 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pre-commit-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat_2",
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1737465171,
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"devenv": "devenv",
|
"devenv": "devenv",
|
||||||
|
"git-hooks": "git-hooks",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs-python": "nixpkgs-python",
|
"nixpkgs-python": "nixpkgs-python",
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
"pre-commit-hooks": [
|
||||||
|
"git-hooks"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "huesoporro"
|
name = "huesoporro"
|
||||||
version = "0.3.3"
|
version = "0.3.5"
|
||||||
description = "Misc Twitch bot"
|
description = "Misc Twitch bot"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|
@ -24,6 +24,7 @@ dependencies = [
|
||||||
"pytz>=2024.2",
|
"pytz>=2024.2",
|
||||||
"discord-py>=2.4.0",
|
"discord-py>=2.4.0",
|
||||||
"tenacity>=9.0.0",
|
"tenacity>=9.0.0",
|
||||||
|
"uvicorn>=0.34.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|
|
||||||
6
renovate.json5
Normal file
6
renovate.json5
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
lockFileMaintenance: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ app = Typer()
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def import_vod_cc(channel_name: str, youtube_url: str, db_path: Path | None = None):
|
def import_vod(channel_name: str, youtube_url: str, db_path: Path | None = None):
|
||||||
logger.info(f"Importing VOD closed captions for {channel_name} from {youtube_url}")
|
logger.info(f"Importing VOD closed captions for {channel_name} from {youtube_url}")
|
||||||
s = Settings.get(db_filepath=db_path)
|
s = Settings.get(db_filepath=db_path)
|
||||||
import_from_vod_action = ImportFromVODAction(
|
import_from_vod_action = ImportFromVODAction(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from huesoporro.actions.chatbot.create_or_update_chatbot import (
|
||||||
from huesoporro.actions.chatbot.get_chatbot_by_user_id import GetChatbotByUserIdAction
|
from huesoporro.actions.chatbot.get_chatbot_by_user_id import GetChatbotByUserIdAction
|
||||||
from huesoporro.actions.users.authenticate_user import AuthenticateUserAction
|
from huesoporro.actions.users.authenticate_user import AuthenticateUserAction
|
||||||
from huesoporro.actions.users.get_user_by_jwt import GetUserByJWTAction
|
from huesoporro.actions.users.get_user_by_jwt import GetUserByJWTAction
|
||||||
|
from huesoporro.bot import BotsManager
|
||||||
from huesoporro.infra.authenticator import TwitchAuthenticator
|
from huesoporro.infra.authenticator import TwitchAuthenticator
|
||||||
from huesoporro.infra.repos import ChatbotRepo, UserRepo
|
from huesoporro.infra.repos import ChatbotRepo, UserRepo
|
||||||
from huesoporro.libs.db import MarkovDatabase
|
from huesoporro.libs.db import MarkovDatabase
|
||||||
|
|
@ -28,29 +29,29 @@ from huesoporro.svc.users_svcs import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_settings() -> Settings:
|
async def get_settings() -> Settings:
|
||||||
return Settings.get()
|
return Settings.get()
|
||||||
|
|
||||||
|
|
||||||
def get_authenticator(s: Settings) -> TwitchAuthenticator:
|
async def get_authenticator(s: Settings) -> TwitchAuthenticator:
|
||||||
return TwitchAuthenticator(s=s)
|
return TwitchAuthenticator(s=s)
|
||||||
|
|
||||||
|
|
||||||
def get_chatbot_repo(s: Settings):
|
async def get_chatbot_repo(s: Settings):
|
||||||
return ChatbotRepo(s=s)
|
return ChatbotRepo(s=s)
|
||||||
|
|
||||||
|
|
||||||
def get_get_chatbot_by_user_id_svc(chatbot_repo: ChatbotRepo):
|
async def get_get_chatbot_by_user_id_svc(chatbot_repo: ChatbotRepo):
|
||||||
return GetChatbotByUserIdSvc(repo=chatbot_repo)
|
return GetChatbotByUserIdSvc(repo=chatbot_repo)
|
||||||
|
|
||||||
|
|
||||||
def get_get_tokens_by_auth_code_svc(
|
async def get_get_tokens_by_auth_code_svc(
|
||||||
twitch_authenticator: TwitchAuthenticator, s: Settings
|
twitch_authenticator: TwitchAuthenticator, s: Settings
|
||||||
):
|
):
|
||||||
return GetTwitchAuthByAuthCodeSvc(s=s, authenticator=twitch_authenticator)
|
return GetTwitchAuthByAuthCodeSvc(s=s, authenticator=twitch_authenticator)
|
||||||
|
|
||||||
|
|
||||||
def get_create_chatbot_svc(chatbot_repo: ChatbotRepo):
|
async def get_create_chatbot_svc(chatbot_repo: ChatbotRepo):
|
||||||
return CreateChatbotSvc(repo=chatbot_repo)
|
return CreateChatbotSvc(repo=chatbot_repo)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,19 +59,19 @@ async def get_user_repo(s: Settings):
|
||||||
return UserRepo(s=s)
|
return UserRepo(s=s)
|
||||||
|
|
||||||
|
|
||||||
def get_create_user_svc(user_repo: UserRepo):
|
async def get_create_user_svc(user_repo: UserRepo):
|
||||||
return CreateUserSvc(user_repo=user_repo)
|
return CreateUserSvc(user_repo=user_repo)
|
||||||
|
|
||||||
|
|
||||||
def get_update_user_svc(user_repo: UserRepo):
|
async def get_update_user_svc(user_repo: UserRepo):
|
||||||
return UpdateUserSvc(user_repo=user_repo)
|
return UpdateUserSvc(user_repo=user_repo)
|
||||||
|
|
||||||
|
|
||||||
def get_refresh_token_svc(twitch_authenticator: TwitchAuthenticator):
|
async def get_refresh_token_svc(twitch_authenticator: TwitchAuthenticator):
|
||||||
return RefreshTokenSvc(twitch_authenticator=twitch_authenticator)
|
return RefreshTokenSvc(twitch_authenticator=twitch_authenticator)
|
||||||
|
|
||||||
|
|
||||||
def get_is_valid_token_svc(twitch_authenticator: TwitchAuthenticator):
|
async def get_is_valid_token_svc(twitch_authenticator: TwitchAuthenticator):
|
||||||
return IsValidTokenSvc(authenticator=twitch_authenticator)
|
return IsValidTokenSvc(authenticator=twitch_authenticator)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,11 +119,11 @@ async def get_sentences_storer_svc(db: MarkovDatabase):
|
||||||
return SentenceStorerSvc(db=db)
|
return SentenceStorerSvc(db=db)
|
||||||
|
|
||||||
|
|
||||||
def get_update_chatbot_svc(chatbot_repo: ChatbotRepo):
|
async def get_update_chatbot_svc(chatbot_repo: ChatbotRepo):
|
||||||
return UpdateChatbotSvc(repo=chatbot_repo)
|
return UpdateChatbotSvc(repo=chatbot_repo)
|
||||||
|
|
||||||
|
|
||||||
def get_create_or_update_chatbot_action(
|
async def get_create_or_update_chatbot_action(
|
||||||
create_chatbot_svc: CreateChatbotSvc,
|
create_chatbot_svc: CreateChatbotSvc,
|
||||||
update_chatbot_svc: UpdateChatbotSvc,
|
update_chatbot_svc: UpdateChatbotSvc,
|
||||||
get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc,
|
get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc,
|
||||||
|
|
@ -134,7 +135,7 @@ def get_create_or_update_chatbot_action(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_get_chatbot_by_user_id_action(
|
async def get_get_chatbot_by_user_id_action(
|
||||||
get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc,
|
get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc,
|
||||||
):
|
):
|
||||||
return GetChatbotByUserIdAction(
|
return GetChatbotByUserIdAction(
|
||||||
|
|
@ -158,6 +159,10 @@ async def get_authenticate_action(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_bot_manager(s: Settings):
|
||||||
|
return BotsManager(s=s)
|
||||||
|
|
||||||
|
|
||||||
async def chatbot(
|
async def chatbot(
|
||||||
get_chatbot_by_user_id_action: GetChatbotByUserIdAction,
|
get_chatbot_by_user_id_action: GetChatbotByUserIdAction,
|
||||||
create_or_update_chatbot_action: CreateOrUpdateChatbotAction,
|
create_or_update_chatbot_action: CreateOrUpdateChatbotAction,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from apps.httpapi.litestar.dependencies import (
|
||||||
authenticate,
|
authenticate,
|
||||||
get_authenticate_action,
|
get_authenticate_action,
|
||||||
get_authenticator,
|
get_authenticator,
|
||||||
|
get_bot_manager,
|
||||||
get_chatbot_repo,
|
get_chatbot_repo,
|
||||||
get_create_chatbot_svc,
|
get_create_chatbot_svc,
|
||||||
get_create_or_update_chatbot_action,
|
get_create_or_update_chatbot_action,
|
||||||
|
|
@ -43,12 +44,11 @@ from apps.httpapi.litestar.routes.api import (
|
||||||
save_bot_settings,
|
save_bot_settings,
|
||||||
)
|
)
|
||||||
from apps.httpapi.litestar.routes.auth import get_code, login
|
from apps.httpapi.litestar.routes.auth import get_code, login
|
||||||
from huesoporro.bot import BotsManager
|
|
||||||
from huesoporro.settings import Settings
|
from huesoporro.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
@get("/healthz")
|
@get("/healthz")
|
||||||
def get_health() -> dict:
|
async def get_health() -> dict:
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ def create_app():
|
||||||
"s": Provide(get_settings, use_cache=True),
|
"s": Provide(get_settings, use_cache=True),
|
||||||
"a": Provide(get_authenticator, use_cache=True),
|
"a": Provide(get_authenticator, use_cache=True),
|
||||||
"user": Provide(authenticate),
|
"user": Provide(authenticate),
|
||||||
"bm": Provide(BotsManager, use_cache=True),
|
"bm": Provide(get_bot_manager, use_cache=True),
|
||||||
"sss": Provide(get_sentences_storer_svc),
|
"sss": Provide(get_sentences_storer_svc),
|
||||||
"twitch_authenticator": Provide(get_authenticator),
|
"twitch_authenticator": Provide(get_authenticator),
|
||||||
"authenticate_action": Provide(get_authenticate_action),
|
"authenticate_action": Provide(get_authenticate_action),
|
||||||
|
|
@ -115,9 +115,8 @@ def create_app():
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
s = Settings.get()
|
s = Settings.get()
|
||||||
config = uvicorn.Config("main:app", host=s.port, port=s.port, log_level="info")
|
config = uvicorn.Config("main:app", host=s.host, port=s.port, log_level="info")
|
||||||
server = uvicorn.Server(config)
|
server = uvicorn.Server(config)
|
||||||
server.run()
|
server.run()
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ class MessageType(StrEnum):
|
||||||
YES = "YES"
|
YES = "YES"
|
||||||
WHAT = "WHAT"
|
WHAT = "WHAT"
|
||||||
LAUGH = "LAUGH"
|
LAUGH = "LAUGH"
|
||||||
|
ANO_SUFFIX = "ANO_SUFFIX"
|
||||||
OTHER = "OTHER"
|
OTHER = "OTHER"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -168,6 +169,8 @@ class MessageHandler:
|
||||||
"keking",
|
"keking",
|
||||||
"KEKW",
|
"KEKW",
|
||||||
"OMEGADANCEBUTFAST",
|
"OMEGADANCEBUTFAST",
|
||||||
|
"xdd",
|
||||||
|
"xdding",
|
||||||
]
|
]
|
||||||
self.send = channel_send_func
|
self.send = channel_send_func
|
||||||
|
|
||||||
|
|
@ -175,16 +178,17 @@ class MessageHandler:
|
||||||
"""Determines the type of message based on its content"""
|
"""Determines the type of message based on its content"""
|
||||||
if content.startswith("!"):
|
if content.startswith("!"):
|
||||||
return MessageType.COMMAND
|
return MessageType.COMMAND
|
||||||
if content == "Yes":
|
if content in ["Yes", "yes"]:
|
||||||
return MessageType.YES
|
return MessageType.YES
|
||||||
if content.startswith("WHAT"):
|
if content.startswith("WHAT"):
|
||||||
return MessageType.WHAT
|
return MessageType.WHAT
|
||||||
|
if content.endswith("ano") and len(content) > 3: # noqa: PLR2004
|
||||||
|
return MessageType.ANO_SUFFIX
|
||||||
if content in self.laugh_patterns:
|
if content in self.laugh_patterns:
|
||||||
return MessageType.LAUGH
|
return MessageType.LAUGH
|
||||||
return MessageType.OTHER
|
return MessageType.OTHER
|
||||||
|
|
||||||
async def handle_laugh(self) -> str:
|
async def handle_laugh(self) -> str:
|
||||||
"""Handles laugh messages"""
|
|
||||||
return random.choice(self.laugh_patterns) # noqa: S311
|
return random.choice(self.laugh_patterns) # noqa: S311
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -200,6 +204,7 @@ class SaveMessagesCog(commands.Cog):
|
||||||
MessageType.YES: self._create_typed_send("yes"),
|
MessageType.YES: self._create_typed_send("yes"),
|
||||||
MessageType.WHAT: self._create_typed_send("what"),
|
MessageType.WHAT: self._create_typed_send("what"),
|
||||||
MessageType.LAUGH: self._create_typed_send("laugh"),
|
MessageType.LAUGH: self._create_typed_send("laugh"),
|
||||||
|
MessageType.ANO_SUFFIX: self._create_typed_send("ano_suffix"),
|
||||||
}
|
}
|
||||||
|
|
||||||
for func in self.send_functions.values():
|
for func in self.send_functions.values():
|
||||||
|
|
@ -227,13 +232,10 @@ class SaveMessagesCog(commands.Cog):
|
||||||
if not message.author:
|
if not message.author:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Store reference to current message for send functions
|
|
||||||
self.current_message = message
|
self.current_message = message
|
||||||
|
|
||||||
# Store the message content
|
|
||||||
await self.store_svc.run(message.content)
|
await self.store_svc.run(message.content)
|
||||||
|
|
||||||
# Determine message type and handle accordingly
|
|
||||||
msg_type = self.message_handler.get_message_type(message.content)
|
msg_type = self.message_handler.get_message_type(message.content)
|
||||||
|
|
||||||
response = None
|
response = None
|
||||||
|
|
@ -247,11 +249,14 @@ class SaveMessagesCog(commands.Cog):
|
||||||
response = "WHAT Ramon"
|
response = "WHAT Ramon"
|
||||||
case MessageType.LAUGH:
|
case MessageType.LAUGH:
|
||||||
response = await self.message_handler.handle_laugh()
|
response = await self.message_handler.handle_laugh()
|
||||||
|
case MessageType.ANO_SUFFIX:
|
||||||
|
response = (
|
||||||
|
f"@{message.author.name} me la agarras con la mano. venga, tira"
|
||||||
|
)
|
||||||
case MessageType.OTHER:
|
case MessageType.OTHER:
|
||||||
return
|
return
|
||||||
|
|
||||||
if response and msg_type in self.send_functions:
|
if response and msg_type in self.send_functions:
|
||||||
# Use the type-specific send function
|
|
||||||
await self.backoff_svc.call_async(self.send_functions[msg_type], response)
|
await self.backoff_svc.call_async(self.send_functions[msg_type], response)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,7 @@ class IRepo(BaseModel, ABC, Generic[T]):
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def list(
|
async def list(self, offset: int = 0, limit: int = 10, auto_commit=True) -> list[T]:
|
||||||
self, obj: T, offset: int = 0, limit: int = 10, auto_commit=True
|
|
||||||
) -> list[T]:
|
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -153,7 +151,7 @@ class UserRepo(IRepo[User]):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def list( # type: ignore[empty-body]
|
async def list( # type: ignore[empty-body]
|
||||||
self, obj: User, offset: int = 0, limit: int = 10, auto_commit=True
|
self, offset: int = 0, limit: int = 10, auto_commit=True
|
||||||
) -> list[User]:
|
) -> list[User]:
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
|
|
||||||
|
|
@ -174,6 +172,8 @@ class QuoteRepo(IRepo[Quote]):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create(self, obj: Quote, auto_commit=True) -> Quote:
|
async def create(self, obj: Quote, auto_commit=True) -> Quote:
|
||||||
|
if await self.get_by_quote(obj.quote):
|
||||||
|
raise ValueError(f"Quote {obj.quote} already exists")
|
||||||
async with (
|
async with (
|
||||||
self.get_client(auto_commit=auto_commit) as db,
|
self.get_client(auto_commit=auto_commit) as db,
|
||||||
await db.execute(
|
await db.execute(
|
||||||
|
|
@ -196,18 +196,81 @@ class QuoteRepo(IRepo[Quote]):
|
||||||
return self._deserialize(data)
|
return self._deserialize(data)
|
||||||
|
|
||||||
async def update(self, obj: Quote, auto_commit=True) -> Quote: # type: ignore[empty-body]
|
async def update(self, obj: Quote, auto_commit=True) -> Quote: # type: ignore[empty-body]
|
||||||
pass # pragma: no cover
|
if not await self.get_by_id(obj.id):
|
||||||
|
raise ValueError(f"Quote {obj.id} does not exist")
|
||||||
|
async with (
|
||||||
|
self.get_client(auto_commit=auto_commit) as db,
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE quotes
|
||||||
|
SET quote = ?,
|
||||||
|
author = ?,
|
||||||
|
channel = ?,
|
||||||
|
last_updated_at = ?
|
||||||
|
WHERE id = ?
|
||||||
|
RETURNING *
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
obj.quote,
|
||||||
|
obj.author,
|
||||||
|
obj.channel_name,
|
||||||
|
utils.get_utc_now(),
|
||||||
|
obj.id.hex,
|
||||||
|
),
|
||||||
|
) as cursor,
|
||||||
|
):
|
||||||
|
data = await cursor.fetchone()
|
||||||
|
return self._deserialize(data)
|
||||||
|
|
||||||
async def delete(self, obj: Quote, auto_commit=True):
|
async def delete(self, obj: Quote, auto_commit=True):
|
||||||
pass # pragma: no cover
|
async with self.get_client(auto_commit=auto_commit) as db:
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM quotes WHERE id = ?
|
||||||
|
""",
|
||||||
|
(obj.id.hex,),
|
||||||
|
)
|
||||||
|
|
||||||
async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Quote | None: # type: ignore[empty-body]
|
async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Quote | None: # type: ignore[empty-body]
|
||||||
pass # pragma: no cover
|
async with (
|
||||||
|
self.get_client(auto_commit=auto_commit) as db,
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM quotes WHERE id = ?
|
||||||
|
""",
|
||||||
|
(obj_id.hex,),
|
||||||
|
) as cursor,
|
||||||
|
):
|
||||||
|
data = await cursor.fetchone()
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
return self._deserialize(data)
|
||||||
|
|
||||||
|
async def get_by_quote(self, quote: str, auto_commit=True) -> Quote | None:
|
||||||
|
async with (
|
||||||
|
self.get_client(auto_commit=auto_commit) as db,
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM quotes WHERE quote = ?
|
||||||
|
""",
|
||||||
|
(quote,),
|
||||||
|
) as cursor,
|
||||||
|
):
|
||||||
|
data = await cursor.fetchone()
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
return self._deserialize(data)
|
||||||
|
|
||||||
async def list( # type: ignore[empty-body]
|
async def list( # type: ignore[empty-body]
|
||||||
self, obj: T, offset: int = 0, limit: int = 10, auto_commit=True
|
self, offset: int = 0, limit: int = 10, auto_commit=True
|
||||||
) -> list[T]:
|
) -> list[Quote]:
|
||||||
pass # pragma: no cover
|
async with self.get_client() as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute(
|
||||||
|
"SELECT * FROM quotes LIMIT ? OFFSET ?", (limit, offset)
|
||||||
|
) as cursor:
|
||||||
|
results = await cursor.fetchall()
|
||||||
|
return [self._deserialize(result) for result in results]
|
||||||
|
|
||||||
async def get_random(self, channel_name: str, auto_commit=True) -> Quote | None:
|
async def get_random(self, channel_name: str, auto_commit=True) -> Quote | None:
|
||||||
async with (
|
async with (
|
||||||
|
|
@ -298,11 +361,22 @@ class ChatbotRepo(IRepo[Chatbot]):
|
||||||
data = await cursor.fetchone()
|
data = await cursor.fetchone()
|
||||||
return self._deserialize(data)
|
return self._deserialize(data)
|
||||||
|
|
||||||
async def delete(self, obj: T, auto_commit=True):
|
async def delete(self, obj: Chatbot, auto_commit=True):
|
||||||
pass # pragma: no cover
|
if not await self.get_by_id(obj.id):
|
||||||
|
raise ValueError(f"Chatbot {obj.id} does not exist")
|
||||||
|
async with self.get_client() as db:
|
||||||
|
await db.execute("DELETE FROM chatbot WHERE id = ?", (obj.id.hex,))
|
||||||
|
|
||||||
async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Chatbot | None: # type: ignore[empty-body]
|
async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Chatbot | None: # type: ignore[empty-body]
|
||||||
pass # pragma: no cover
|
async with self.get_client() as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute(
|
||||||
|
"SELECT * FROM chatbot WHERE id = ?", (obj_id.hex,)
|
||||||
|
) as cursor:
|
||||||
|
result = await cursor.fetchone()
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
return self._deserialize(result)
|
||||||
|
|
||||||
async def get_by_user_id(self, user_id: UUID) -> Chatbot | None:
|
async def get_by_user_id(self, user_id: UUID) -> Chatbot | None:
|
||||||
async with self.get_client() as db:
|
async with self.get_client() as db:
|
||||||
|
|
@ -316,6 +390,12 @@ class ChatbotRepo(IRepo[Chatbot]):
|
||||||
return self._deserialize(result)
|
return self._deserialize(result)
|
||||||
|
|
||||||
async def list( # type: ignore[empty-body]
|
async def list( # type: ignore[empty-body]
|
||||||
self, obj: T, offset: int = 0, limit: int = 10, auto_commit=True
|
self, offset: int = 0, limit: int = 10, auto_commit=True
|
||||||
) -> list[T]:
|
) -> list[Chatbot]:
|
||||||
pass # pragma: no cover
|
async with self.get_client() as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute(
|
||||||
|
"SELECT * FROM chatbot LIMIT ? OFFSET ?", (limit, offset)
|
||||||
|
) as cursor:
|
||||||
|
results = await cursor.fetchall()
|
||||||
|
return [self._deserialize(result) for result in results]
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ def db(s, cdb):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def list( # type: ignore[empty-body]
|
async def list( # type: ignore[empty-body]
|
||||||
self, obj: BaseModel, offset: int = 0, limit: int = 10, auto_commit=True
|
self, offset: int = 0, limit: int = 10, auto_commit=True
|
||||||
) -> list[BaseModel]:
|
) -> list[BaseModel]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -169,6 +169,16 @@ async def chatbot(chatbot_factory, user):
|
||||||
return chatbot_factory.build(user_id=user.id)
|
return chatbot_factory.build(user_id=user.id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def five_chatbots(chatbot_factory, user):
|
||||||
|
return [chatbot_factory.build() for _ in range(5)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def persisted_five_chatbots(five_chatbots, chatbot_repo):
|
||||||
|
return [await chatbot_repo.create(chatbot) for chat in five_chatbots]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def persisted_chatbot(chatbot_repo, chatbot, persisted_user):
|
async def persisted_chatbot(chatbot_repo, chatbot, persisted_user):
|
||||||
return await chatbot_repo.create(chatbot)
|
return await chatbot_repo.create(chatbot)
|
||||||
|
|
@ -204,11 +214,21 @@ async def quote(quote_factory):
|
||||||
return quote_factory.build()
|
return quote_factory.build()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def five_quotes(quote_factory):
|
||||||
|
return [quote_factory.build() for _ in range(5)]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def persisted_quote(quote_repo, quote):
|
async def persisted_quote(quote_repo, quote):
|
||||||
return await quote_repo.create(quote)
|
return await quote_repo.create(quote)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def persisted_five_quotes(five_quotes, quote_repo):
|
||||||
|
return [await quote_repo.create(quote) for quote in five_quotes]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def create_quote_svc(quote_repo):
|
async def create_quote_svc(quote_repo):
|
||||||
return CreateQuoteSvc(repo=quote_repo)
|
return CreateQuoteSvc(repo=quote_repo)
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,79 @@ async def test_update_chatbot_raises_value_error_on_non_existing_chatbot(
|
||||||
await chatbot_repo.update(chatbot)
|
await chatbot_repo.update(chatbot)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_delete_chatbot_raises_value_error_on_non_existing_chatbot(
|
||||||
|
chatbot_repo, chatbot
|
||||||
|
):
|
||||||
|
with pytest.raises(ValueError, match=f"Chatbot {chatbot.id} does not exist"):
|
||||||
|
await chatbot_repo.delete(chatbot)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_delete_chatbot(chatbot_repo, persisted_chatbot):
|
||||||
|
assert await chatbot_repo.delete(persisted_chatbot) is None
|
||||||
|
assert await chatbot_repo.get_by_id(persisted_chatbot.id) is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_by_id(chatbot_repo, persisted_chatbot):
|
||||||
|
chatbot = await chatbot_repo.get_by_id(persisted_chatbot.id)
|
||||||
|
assert chatbot == persisted_chatbot
|
||||||
|
|
||||||
|
|
||||||
|
async def test_list(chatbot_repo, persisted_five_chatbots):
|
||||||
|
chatbots = await chatbot_repo.list()
|
||||||
|
assert len(chatbots) == 5 # noqa: PLR2004
|
||||||
|
|
||||||
|
|
||||||
|
async def test_list_offset_limit(chatbot_repo, persisted_five_chatbots):
|
||||||
|
chatbots = await chatbot_repo.list(offset=1, limit=2)
|
||||||
|
assert len(chatbots) == 2 # noqa: PLR2004
|
||||||
|
|
||||||
|
|
||||||
async def test_get_random_quote(quote_repo: QuoteRepo, persisted_quote):
|
async def test_get_random_quote(quote_repo: QuoteRepo, persisted_quote):
|
||||||
quote = await quote_repo.get_random(persisted_quote.channel_name)
|
quote = await quote_repo.get_random(persisted_quote.channel_name)
|
||||||
assert quote
|
assert quote
|
||||||
assert quote.author == persisted_quote.author
|
assert quote.author == persisted_quote.author
|
||||||
assert quote.channel_name == persisted_quote.channel_name
|
assert quote.channel_name == persisted_quote.channel_name
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_quote_raises_value_error_for_existing_quote(
|
||||||
|
quote_repo: QuoteRepo, persisted_quote
|
||||||
|
):
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError, match=f"Quote {persisted_quote.quote} already exists"
|
||||||
|
):
|
||||||
|
await quote_repo.create(persisted_quote)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_quote(quote_repo: QuoteRepo, quote_factory):
|
||||||
|
quote = quote_factory.build()
|
||||||
|
created_quote = await quote_repo.create(quote)
|
||||||
|
assert created_quote == quote
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_quote_raises_value_error_on_non_existing_quote(
|
||||||
|
quote_repo: QuoteRepo, quote
|
||||||
|
):
|
||||||
|
with pytest.raises(ValueError, match=f"Quote {quote.id} does not exist"):
|
||||||
|
await quote_repo.update(quote)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_quote(quote_repo: QuoteRepo, persisted_quote):
|
||||||
|
persisted_quote.quote = "new quote"
|
||||||
|
updated_quote = await quote_repo.update(persisted_quote)
|
||||||
|
persisted_quote.last_updated_at = updated_quote.last_updated_at
|
||||||
|
assert updated_quote == persisted_quote
|
||||||
|
|
||||||
|
|
||||||
|
async def test_delete_quote(quote_repo: QuoteRepo, persisted_quote):
|
||||||
|
assert await quote_repo.delete(persisted_quote) is None
|
||||||
|
assert await quote_repo.get_by_id(persisted_quote.id) is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_list_quotes(quote_repo, persisted_five_quotes):
|
||||||
|
quotes = await quote_repo.list()
|
||||||
|
assert len(quotes) == 5 # noqa: PLR2004
|
||||||
|
|
||||||
|
|
||||||
|
async def test_list_quotes_offset_limit(quote_repo, persisted_five_quotes):
|
||||||
|
quotes = await quote_repo.list(offset=1, limit=2)
|
||||||
|
assert len(quotes) == 2 # noqa: PLR2004
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue