Migrated links, lyrics and properties

This commit is contained in:
marc
2025-03-23 21:46:04 +01:00
parent c097811e40
commit d596861a2e
47 changed files with 1403 additions and 844 deletions

View File

@@ -48,7 +48,7 @@ async def store_file(tema_id: int, upload_file: UploadFile) -> str:
filepath = filedir / filename
with open(filepath, "wb") as f:
f.write(await upload_file.read())
_ = f.write(await upload_file.read())
return get_db_file_path(filepath)

View File

@@ -1,6 +1,7 @@
import re
from typing import Optional
from collections.abc import Iterable, Iterator
from folkugat_web.dal.sql.temes import links as links_dal
from folkugat_web.model import temes as model
IMAGE_FORMATS_RE = "|".join([
@@ -30,3 +31,29 @@ def guess_link_type(url: str) -> model.LinkType | None:
if regex.match(url):
return link_type
return None
def add_links_to_tema(tema: model.Tema) -> model.Tema:
if tema.id is not None:
tema.links = list(links_dal.get_links(tema_id=tema.id))
return tema
def add_links_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
return map(add_links_to_tema, temes)
def get_link_by_id(link_id: int, tema_id: int | None = None) -> model.Link | None:
return next(iter(links_dal.get_links(link_id=link_id, tema_id=tema_id)), None)
def update_link(link: model.Link):
links_dal.update_link(link=link)
def delete_link(link_id: int, tema_id: int | None = None):
links_dal.delete_link(link_id=link_id, tema_id=tema_id)
def create_link(tema_id: int) -> model.Link:
return links_dal.create_link(tema_id=tema_id)

View File

@@ -0,0 +1,30 @@
from collections.abc import Iterable, Iterator
from folkugat_web.dal.sql.temes import lyrics as lyrics_dal
from folkugat_web.model import temes as model
def add_lyrics_to_tema(tema: model.Tema) -> model.Tema:
if tema.id is not None:
tema.lyrics = list(lyrics_dal.get_lyrics(tema_id=tema.id))
return tema
def add_lyrics_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
return map(add_lyrics_to_tema, temes)
def get_lyric_by_id(lyric_id: int, tema_id: int | None = None) -> model.Lyrics | None:
return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None)
def update_lyric(lyric: model.Lyrics):
lyrics_dal.update_lyric(lyric=lyric)
def delete_lyric(lyric_id: int, tema_id: int | None = None):
lyrics_dal.delete_lyric(lyric_id=lyric_id, tema_id=tema_id)
def create_lyric(tema_id: int) -> model.Lyrics:
return lyrics_dal.create_lyric(tema_id=tema_id)

View File

@@ -0,0 +1,30 @@
from collections.abc import Iterable, Iterator
from folkugat_web.dal.sql.temes import properties as properties_dal
from folkugat_web.model import temes as model
def add_properties_to_tema(tema: model.Tema) -> model.Tema:
if tema.id is not None:
tema.properties = list(properties_dal.get_properties(tema_id=tema.id))
return tema
def add_properties_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
return map(add_properties_to_tema, temes)
def get_property_by_id(property_id: int, tema_id: int | None = None) -> model.Property | None:
return next(iter(properties_dal.get_properties(property_id=property_id, tema_id=tema_id)), None)
def update_property(property: model.Property):
properties_dal.update_property(property=property)
def delete_property(property_id: int, tema_id: int | None = None):
properties_dal.delete_property(property_id=property_id, tema_id=tema_id)
def create_property(tema_id: int) -> model.Property:
return properties_dal.create_property(tema_id=tema_id)

View File

@@ -1,7 +1,7 @@
import functools
import time
import typing
from collections.abc import Callable, Iterable
from collections.abc import Iterable, Iterator
from sqlite3 import Connection
from typing import Callable
import Levenshtein
from folkugat_web.config import search as config
@@ -10,6 +10,7 @@ from folkugat_web.dal.sql.temes import query as temes_q
from folkugat_web.log import logger
from folkugat_web.model import search as search_model
from folkugat_web.model import temes as model
from folkugat_web.utils import FnChain
def get_query_word_similarity(query_word: str, text_ngrams: search_model.NGrams) -> search_model.SearchMatch:
@@ -33,40 +34,66 @@ def get_query_similarity(query: str, ngrams: search_model.NGrams) -> search_mode
return search_model.SearchMatch.combine_matches(word_matches)
def build_result(query: str, entry: tuple[int, search_model.NGrams]) -> search_model.QueryResult:
if len(query) == 0:
def _build_results_fn(query: str) -> Callable[[Iterable[tuple[int, search_model.NGrams]]],
Iterator[search_model.QueryResult]]:
def build_result(entry: tuple[int, search_model.NGrams]) -> search_model.QueryResult:
if len(query) == 0:
return search_model.QueryResult(
id=entry[0],
distance=0,
ngram="",
)
match = get_query_similarity(query, entry[1])
return search_model.QueryResult(
id=entry[0],
distance=0,
ngram="",
distance=match.distance,
ngram=match.ngram,
)
match = get_query_similarity(query, entry[1])
return search_model.QueryResult(
id=entry[0],
distance=match.distance,
ngram=match.ngram,
)
def build_results(entries: Iterable[tuple[int, search_model.NGrams]]) -> Iterator[search_model.QueryResult]:
return map(build_result, entries)
return build_results
T = typing.TypeVar("T")
def _filter_distance(qrs: Iterable[search_model.QueryResult]) -> Iterator[search_model.QueryResult]:
return filter(lambda qr: qr.distance <= config.SEARCH_DISTANCE_THRESHOLD, qrs)
@typing.no_type_check
def _thread(it: Iterable[T], *funcs: Callable[[Iterable], Iterable]) -> Iterable:
return functools.reduce(lambda i, fn: fn(i), funcs, it)
def _sort_by_distance(qrs: Iterable[search_model.QueryResult]) -> list[search_model.QueryResult]:
return sorted(qrs, key=lambda qr: qr.distance)
def _query_results_to_temes(con: Connection) -> Callable[[Iterable[search_model.QueryResult]], Iterator[model.Tema]]:
def fetch_temes(qrs: Iterable[search_model.QueryResult]) -> Iterator[model.Tema]:
return filter(None, map(lambda qr: temes_q.get_tema_by_id(tema_id=qr.id, con=con), qrs))
return fetch_temes
def _filter_hidden(hidden: bool) -> Callable[[Iterable[model.Tema]], Iterator[model.Tema]]:
def filter_hidden(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
return filter(lambda t: hidden or not t.hidden, temes)
return filter_hidden
def _apply_limit_offset(limit: int, offset: int) -> Callable[[Iterable[model.Tema]], list[model.Tema]]:
def apply_limit_offset(temes: Iterable[model.Tema]) -> list[model.Tema]:
return list(temes)[offset:offset + limit]
return apply_limit_offset
def busca_temes(query: str, hidden: bool = False, limit: int = 10, offset: int = 0) -> list[model.Tema]:
t0 = time.time()
with get_connection() as con:
result = _thread(
temes_q.get_tema_id_to_ngrams(con).items(),
lambda tema_id_to_ngrams: (build_result(query, entry) for entry in tema_id_to_ngrams),
lambda results: filter(lambda qr: qr.distance <= config.SEARCH_DISTANCE_THRESHOLD, results),
lambda results: sorted(results, key=lambda qr: qr.distance),
lambda results: filter(None, map(lambda qr: temes_q.get_tema_by_id(qr.id, con), results)),
lambda results: filter(lambda t: hidden or not t.hidden, results),
)
result = list(result)[offset:offset + limit]
result = (
FnChain.transform(temes_q.get_tema_id_to_ngrams(con).items()) |
_build_results_fn(query) |
_filter_distance |
_sort_by_distance |
_query_results_to_temes(con) |
_filter_hidden(hidden) |
_apply_limit_offset(limit=limit, offset=offset)
).result()
logger.info(f"Search time: { int((time.time() - t0) * 1000) } ms")
return result

View File

@@ -7,8 +7,10 @@ from folkugat_web.model import temes as model
def create_tema(title: str = "") -> model.Tema:
new_tema = model.Tema(title=title, hidden=False).with_ngrams()
return temes_w.insert_tema(tema=new_tema)
with get_connection() as con:
new_tema = temes_w.insert_tema(tema=model.Tema(title=title, hidden=False), con=con)
temes_w.update_ngrams(tema=new_tema, con=con)
return new_tema
def delete_tema(tema_id: int) -> None:
@@ -21,131 +23,13 @@ def update_title(tema_id: int, title: str) -> model.Tema:
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
new_tema = dataclasses.replace(tema, title=title).with_ngrams()
new_tema = dataclasses.replace(tema, title=title)
temes_w.update_tema(tema=new_tema, con=con)
temes_w.update_ngrams(tema=new_tema, con=con)
return new_tema
def update_lyric(tema_id: int, lyric_id: int, lyric: model.Lyrics) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.lyrics.replace(lyric_id, lyric)
temes_w.update_tema(tema=tema, con=con)
return tema
def delete_lyric(tema_id: int, lyric_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.lyrics.delete(lyric_id)
temes_w.update_tema(tema=tema, con=con)
return tema
def add_lyric(tema_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.lyrics.append(model.Lyrics(
id=None,
title=tema.title,
content="",
))
temes_w.update_tema(tema=tema, con=con)
return tema
def update_link(tema_id: int, link_id: int, link: model.Link) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.links.replace(link_id, link)
temes_w.update_tema(tema=tema, con=con)
return tema
def delete_link(tema_id: int, link_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.links.delete(link_id)
temes_w.update_tema(tema=tema, con=con)
return tema
def add_link(tema_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.links.append(model.Link(
id=None,
title="",
url="",
content_type=model.ContentType.OTHER,
link_type=None,
))
temes_w.update_tema(tema=tema, con=con)
return tema
def update_property(tema_id: int, property_id: int, prop: model.Property) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.properties.replace(property_id, prop)
temes_w.update_tema(tema=tema, con=con)
return tema
def delete_property(tema_id: int, property_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.properties.delete(property_id)
temes_w.update_tema(tema=tema, con=con)
return tema
def add_property(tema_id: int) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
if tema is None:
raise ValueError(f"No tune found with tema_id = {tema_id}!")
tema.properties.append(model.Property(
id=None,
field=model.PropertyField.AUTOR,
value="",
))
temes_w.update_tema(tema=tema, con=con)
return tema
def set_visibility(tema_id: int, hidden: bool) -> model.Tema:
with get_connection() as con:
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)