Skip to content

Commit

Permalink
tests: integration test sync_deck (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinBernstorff authored Dec 13, 2023
2 parents ecb25cf + 070d540 commit a96cd2a
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ class AnkiConnectGateway:
def __init__(
self,
ankiconnect_url: str,
deck_name: str,
base_deck: str,
tmp_read_dir: Path,
tmp_write_dir: Path,
max_deletions_per_run: int,
) -> None:
self.ankiconnect_url = ankiconnect_url
self.deck_name = deck_name
self.deck_name = base_deck
self.tmp_read_dir = tmp_read_dir
self.tmp_write_dir = tmp_write_dir
self.max_deletions_per_run = max_deletions_per_run
Expand Down
4 changes: 2 additions & 2 deletions personal_mnemonic_medium/v2/data_access/test_ankiconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class TestAnkiConnectGateway:
output_path = Path("/output")
gateway = AnkiConnectGateway(
ankiconnect_url=ANKICONNECT_URL,
deck_name="Test deck",
base_deck="Test deck",
tmp_read_dir=Path("/Users/Leisure/ankidecks"),
tmp_write_dir=output_path,
max_deletions_per_run=1,
Expand Down Expand Up @@ -86,7 +86,7 @@ def test_import_package(self):
def test_error_if_deleting_more_than_allowed():
gateway = AnkiConnectGateway(
ankiconnect_url=ANKICONNECT_URL,
deck_name="Test deck",
base_deck="Test deck",
tmp_read_dir=Path("/tmp"),
tmp_write_dir=Path("/tmp"),
max_deletions_per_run=0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ...prompts.base_prompt import DestinationPrompt
from ...prompts.cloze_prompt import ClozeWithoutDoc
from ...prompts.qa_prompt import QAWithoutDoc
from ...utils.hash_cleaned_str import hash_cleaned_str
from ..base_prompt_destination import PromptDestination
from .prompt_converter.anki_prompt_converter import (
AnkiPromptConverter,
Expand Down Expand Up @@ -80,7 +81,9 @@ def _grouped_cards_to_deck(
self, cards: Mapping[str, Sequence[AnkiCard]]
) -> genanki.Deck:
deck_name = next(iter(cards.keys()))
deck = genanki.Deck(name=deck_name, deck_id=deck_name)
deck = genanki.Deck(
name=deck_name, deck_id=hash_cleaned_str(deck_name)
)

for card in cards[deck_name]:
deck.add_note(card.to_genanki_note()) # type: ignore
Expand Down
3 changes: 2 additions & 1 deletion personal_mnemonic_medium/v2/domain/prompts/cloze_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from dataclasses import dataclass

from ..prompt_source.document_ingesters.document import Document
from ..utils.hash_cleaned_str import hash_cleaned_str
from ..utils.int_hash_str import int_hash_str
from .base_prompt import BasePrompt

Expand All @@ -14,7 +15,7 @@ class ClozePrompt(BasePrompt):

@property
def uid(self) -> int:
return int_hash_str(self.text)
return hash_cleaned_str(self.text)

@property
def tags(self) -> Sequence[str]:
Expand Down
7 changes: 2 additions & 5 deletions personal_mnemonic_medium/v2/domain/prompts/qa_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

from attr import dataclass

from personal_mnemonic_medium.v2.domain.utils.int_hash_str import (
int_hash_str,
)

from ..prompt_source.document_ingesters.document import Document
from ..utils.hash_cleaned_str import hash_cleaned_str
from .base_prompt import BasePrompt


Expand All @@ -17,7 +14,7 @@ class QAPrompt(BasePrompt):

@property
def uid(self) -> int:
return int_hash_str(self.question)
return hash_cleaned_str(self.question)

@property
def tags(self) -> Sequence[str]:
Expand Down
2 changes: 1 addition & 1 deletion personal_mnemonic_medium/v2/domain/utils/clean_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ def clean_str(input_str: str) -> str:
"""Clean string before hashing, so changes to spacing, punctuation, newlines etc. do not affect the hash."""
lowered = input_str.lower()

punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`|~"""
cleaned = lowered.translate(
str.maketrans("", "", punctuation)
).replace(" ", "")
Expand Down
13 changes: 13 additions & 0 deletions personal_mnemonic_medium/v2/domain/utils/hash_cleaned_str.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from personal_mnemonic_medium.v2.domain.utils.clean_str import (
clean_str,
)
from personal_mnemonic_medium.v2.domain.utils.int_hash_str import (
int_hash_str,
)


def hash_cleaned_str(input_str: str) -> int:
"""Hash a string after cleaning it."""
cleaned = clean_str(input_str)
hashed = int_hash_str(cleaned)
return hashed
83 changes: 9 additions & 74 deletions personal_mnemonic_medium/v2/presentation/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,13 @@
import typer
from wasabi import Printer

from personal_mnemonic_medium.data_access.exporters.anki.anki_css import (
CARD_MODEL_CSS,
)
from personal_mnemonic_medium.data_access.exporters.anki.globals import (
ANKICONNECT_URL,
)
from personal_mnemonic_medium.v2.sync_deck import sync_deck

from ...main import get_env
from ..data_access.ankiconnect_gateway import AnkiConnectGateway
from ..domain.diff_determiner.base_diff_determiner import (
PromptDiffDeterminer,
)
from ..domain.prompt_destination.anki_connect.ankiconnect_destination import (
AnkiConnectDestination,
)
from ..domain.prompt_destination.anki_connect.prompt_converter.anki_prompt_converter import (
AnkiPromptConverter,
)
from ..domain.prompt_source.document_ingesters.markdown_document_ingester import (
MarkdownDocumentIngester,
)
from ..domain.prompt_source.document_prompt_source import (
DocumentPromptSource,
)
from ..domain.prompt_source.prompt_extractors.cloze_prompt_extractor import (
ClozePromptExtractor,
)
from ..domain.prompt_source.prompt_extractors.qa_prompt_extractor import (
QAPromptExtractor,
)

msg = Printer(timestamp=True)


def _sync_deck(
deck_name: str,
input_dir: Path,
apkg_output_filepath: Path,
max_deletions_per_run: int,
):
source_prompts = DocumentPromptSource(
document_ingester=MarkdownDocumentIngester(
directory=input_dir
),
prompt_extractors=[
QAPromptExtractor(
question_prefix="Q.", answer_prefix="A."
),
ClozePromptExtractor(),
],
).get_prompts()

destination = AnkiConnectDestination(
gateway=AnkiConnectGateway(
ankiconnect_url=ANKICONNECT_URL,
deck_name=deck_name,
tmp_read_dir=input_dir,
tmp_write_dir=apkg_output_filepath,
max_deletions_per_run=max_deletions_per_run,
),
prompt_converter=AnkiPromptConverter(
base_deck=deck_name, card_css=CARD_MODEL_CSS
),
)
destination_prompts = destination.get_all_prompts()

update_commands = PromptDiffDeterminer().sync(
source_prompts=source_prompts,
destination_prompts=destination_prompts,
)

destination.update(commands=update_commands)


def main(
input_dir: Path,
apkg_output_filepath: Path,
Expand Down Expand Up @@ -111,10 +44,11 @@ def main(
environment=get_env(default="None"),
)

_sync_deck(
deck_name=deck_name,
sync_deck(
base_deck=deck_name,
input_dir=input_dir,
apkg_output_filepath=apkg_output_filepath,
apkg_output_dir=apkg_output_filepath,
ankiconnect_read_apkg_from_dir=host_ankiconnect_dir,
max_deletions_per_run=max_deletions_per_run,
)

Expand All @@ -123,10 +57,11 @@ def main(
msg.good(
f"Sync complete, sleeping for {sleep_seconds} seconds"
)
_sync_deck(
deck_name=deck_name,
sync_deck(
base_deck=deck_name,
input_dir=input_dir,
apkg_output_filepath=apkg_output_filepath,
apkg_output_dir=apkg_output_filepath,
ankiconnect_read_apkg_from_dir=host_ankiconnect_dir,
max_deletions_per_run=max_deletions_per_run,
)

Expand Down
70 changes: 70 additions & 0 deletions personal_mnemonic_medium/v2/sync_deck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from pathlib import Path

from personal_mnemonic_medium.data_access.exporters.anki.anki_css import (
CARD_MODEL_CSS,
)

from ..data_access.exporters.anki.globals import ANKICONNECT_URL
from .data_access.ankiconnect_gateway import AnkiConnectGateway
from .domain.diff_determiner.base_diff_determiner import (
PromptDiffDeterminer,
)
from .domain.prompt_destination.anki_connect.ankiconnect_destination import (
AnkiConnectDestination,
)
from .domain.prompt_destination.anki_connect.prompt_converter.anki_prompt_converter import (
AnkiPromptConverter,
)
from .domain.prompt_source.document_ingesters.markdown_document_ingester import (
MarkdownDocumentIngester,
)
from .domain.prompt_source.document_prompt_source import (
DocumentPromptSource,
)
from .domain.prompt_source.prompt_extractors.cloze_prompt_extractor import (
ClozePromptExtractor,
)
from .domain.prompt_source.prompt_extractors.qa_prompt_extractor import (
QAPromptExtractor,
)


def sync_deck(
base_deck: str,
input_dir: Path,
apkg_output_dir: Path,
ankiconnect_read_apkg_from_dir: Path,
max_deletions_per_run: int,
):
source_prompts = DocumentPromptSource(
document_ingester=MarkdownDocumentIngester(
directory=input_dir
),
prompt_extractors=[
QAPromptExtractor(
question_prefix="Q.", answer_prefix="A."
),
ClozePromptExtractor(),
],
).get_prompts()

destination = AnkiConnectDestination(
gateway=AnkiConnectGateway(
ankiconnect_url=ANKICONNECT_URL,
base_deck=base_deck,
tmp_read_dir=ankiconnect_read_apkg_from_dir,
tmp_write_dir=apkg_output_dir,
max_deletions_per_run=max_deletions_per_run,
),
prompt_converter=AnkiPromptConverter(
base_deck=base_deck, card_css=CARD_MODEL_CSS
),
)
destination_prompts = destination.get_all_prompts()

update_commands = PromptDiffDeterminer().sync(
source_prompts=source_prompts,
destination_prompts=destination_prompts,
)

destination.update(commands=update_commands)
33 changes: 33 additions & 0 deletions personal_mnemonic_medium/v2/test_sync_deck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pathlib import Path

import pytest

from personal_mnemonic_medium.v2.sync_deck import sync_deck

from ..data_access.exporters.anki.sync.gateway_utils import (
anki_connect_is_live,
)


@pytest.mark.skipif(
not anki_connect_is_live(),
reason="Tests require a running AnkiConnect server",
)
def test_sync_deck(tmp_path: Path):
with (tmp_path / "test.md").open("w") as f:
f.write(
"""# Test note
Q. Test question?
A. Test answer!
"""
)

sync_deck(
base_deck="Tests::Integration Test deck",
input_dir=tmp_path,
apkg_output_dir=Path("/output"),
ankiconnect_read_apkg_from_dir=Path(
"/Users/Leisure/ankidecks"
),
max_deletions_per_run=1,
)

0 comments on commit a96cd2a

Please sign in to comment.