diff --git a/.github/workflows/matrix-tests.yml b/.github/workflows/matrix-tests.yml index cdd982c0..a0747040 100644 --- a/.github/workflows/matrix-tests.yml +++ b/.github/workflows/matrix-tests.yml @@ -29,4 +29,4 @@ jobs: run: | pip install -e .[all] - name: Run tests - run: cd tests && python run.py prod + run: python run_tests.py prod diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cbab18bf..d79bd6c9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,13 +32,12 @@ jobs: - name: Run tests run: | - cd tests - coverage run --parallel-mode run.py prod - DEVELOPMENT=1 coverage run --parallel-mode test_web_api.py + coverage run --parallel-mode run_tests.py prod + DEVELOPMENT=1 coverage run --parallel-mode -m tests.test_web_api coverage combine coverage xml - - run: cd tests && coverage report + - run: coverage report - name: Make sure pre-commit hooks pass uses: pre-commit/action@v3.0.1 @@ -109,7 +108,7 @@ jobs: pip install -e .[all] - name: Run tests on Windows - run: cd tests && python run.py prod + run: python run_tests.py prod - name: Make sure the CLI outputs utf8 on Windows # Note: we're checking something CLI specific, from a prompt, so we don't want to run @@ -156,4 +155,4 @@ jobs: run: pip install httpx - name: unit test the web API - run: python tests/test_web_api.py + run: python -m tests.test_web_api diff --git a/.gitignore b/.gitignore index cb3111ba..14024c79 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ coverage.xml *,cover .hypothesis/ .pytest_cache/ +*tmpdir* # Translations *.mo diff --git a/README.md b/README.md index 1283f836..a717b40a 100644 --- a/README.md +++ b/README.md @@ -132,25 +132,35 @@ To install the latest published version on PyPI: pip install readalongs -To install the current development version, clone the repo and pip install it -locally: +To install the current development version, clone the repo and pip +install it locally, either with [hatch](https://hatch.pypa.io) (this +will open a new shell with the package installed inside it): -```sh -$ git clone https://github.com/ReadAlongs/Studio.git -$ cd Studio -$ pip install -e . -``` + git clone https://github.com/ReadAlongs/Studio.git + cd Studio + hatch shell + +or directly with `pip` (you should be using a [virtual +environment](https://docs.python.org/3/library/venv.html) though!): + + git clone https://github.com/ReadAlongs/Studio.git + cd Studio + pip install -e . ### Verifying your installation Run `readalongs -h` to confirm that installation was successful. If you installed the current development version with Git, you can also run the -full test suite (requires installing the dev dependencies): +full test suite, either with `hatch`: + + hatch test + +or test the installed version (requires installing dev dependencies): pip install 'readalongs[dev]' # if you installed from PyPI, or pip install -e '.[dev]' # if you installed from a local clone - python tests/run.py dev + python run_tests.py dev And you can download our [open samples on GitHub](https://github.com/ReadAlongs/OpenSamples) to run your first alignments. diff --git a/pyproject.toml b/pyproject.toml index 426d8316..1237f21a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,6 +113,9 @@ include = [ [tool.hatch.env] requires = [ "hatch-pip-compile" ] +[tool.hatch.envs.hatch-test] +features = [ "dev" ] + [tool.hatch.envs.prod] features = [ "heroku" ] type = "pip-compile" @@ -132,3 +135,8 @@ ignore_missing_imports = true [tool.isort] profile = "black" + +[tool.pytest] +filterwarnings = [ + "ignore:'audioop' is deprecated and slated for removal in Python 3.13:DeprecationWarning", +] diff --git a/tests/run.py b/run_tests.py similarity index 81% rename from tests/run.py rename to run_tests.py index 701fb668..37b021a9 100755 --- a/tests/run.py +++ b/run_tests.py @@ -19,27 +19,26 @@ import sys from unittest import TestLoader, TestSuite, TextTestRunner -from test_align_cli import TestAlignCli -from test_anchors import TestAnchors -from test_api import TestAlignApi -from test_audio import TestAudio -from test_config import TestConfig -from test_dna_text import TestDNAText -from test_dna_utils import TestDNAUtils -from test_dtd import TestDTD -from test_force_align import TestForceAlignment, TestXHTML -from test_g2p_cli import TestG2pCli -from test_make_xml_cli import TestMakeXMLCli -from test_misc import TestMisc -from test_package_urls import TestPackageURLs -from test_silence import TestSilence -from test_smil import TestSmilUtilities -from test_temp_file import TestTempFile -from test_tokenize_cli import TestTokenizeCli -from test_tokenize_xml import TestTokenizer -from test_web_api import TestWebApi - from readalongs.log import LOGGER +from tests.test_align_cli import TestAlignCli +from tests.test_anchors import TestAnchors +from tests.test_api import TestAlignApi +from tests.test_audio import TestAudio +from tests.test_config import TestConfig +from tests.test_dna_text import TestDNAText +from tests.test_dna_utils import TestDNAUtils +from tests.test_dtd import TestDTD +from tests.test_force_align import TestForceAlignment, TestXHTML +from tests.test_g2p_cli import TestG2pCli +from tests.test_make_xml_cli import TestMakeXMLCli +from tests.test_misc import TestMisc +from tests.test_package_urls import TestPackageURLs +from tests.test_silence import TestSilence +from tests.test_smil import TestSmilUtilities +from tests.test_temp_file import TestTempFile +from tests.test_tokenize_cli import TestTokenizeCli +from tests.test_tokenize_xml import TestTokenizer +from tests.test_web_api import TestWebApi LOADER = TestLoader() diff --git a/tests/test_align_cli.py b/tests/test_align_cli.py index 349b25bd..836dce69 100755 --- a/tests/test_align_cli.py +++ b/tests/test_align_cli.py @@ -9,21 +9,22 @@ import tempfile from os.path import exists, join from pathlib import Path +from typing import Union from unittest import main -from basic_test_case import BasicTestCase from lxml.html import fromstring -from sound_swallower_stub import SoundSwallowerStub from readalongs._version import READALONG_FILE_FORMAT_VERSION, VERSION from readalongs.cli import align, langs +from tests.basic_test_case import BasicTestCase +from tests.sound_swallower_stub import SoundSwallowerStub -def write_file(filename: str, file_contents: str) -> str: +def write_file(filename: Union[str, Path], file_contents: str) -> str: """Write file_contents to file filename, and return its name (filename)""" with open(filename, mode="w", encoding="utf8") as f: f.write(file_contents) - return filename + return str(filename) class TestAlignCli(BasicTestCase): @@ -245,7 +246,7 @@ def test_align_english(self): """Validates that the lexicon-based g2p works for English language alignment""" input_filename = write_file( - join(self.tempdir, "input"), + self.tempdir / "input", "This is some text that we will run through the English lexicon " "grapheme to morpheme approach.", ) @@ -405,7 +406,7 @@ def test_infer_plain_text_or_xml(self): """align -i is obsolete, now we infer plain text vs XML; test that!""" # plain text with guess by contents - infile1 = write_file(join(self.tempdir, "infile1"), "some plain text") + infile1 = write_file(self.tempdir / "infile1", "some plain text") with SoundSwallowerStub("word:0:1"): results = self.runner.invoke( align, @@ -420,7 +421,7 @@ def test_infer_plain_text_or_xml(self): self.assertIn("No input language specified for plain text", results.output) # plain text by extension - infile2 = write_file(join(self.tempdir, "infile2.txt"), "blah blah", ) ) @@ -454,7 +455,7 @@ def test_infer_plain_text_or_xml(self): # XML with guess by contents, but with content error infile4 = write_file( - join(self.tempdir, "infile4"), + self.tempdir / "infile4", "blah blah", ) with SoundSwallowerStub("word:0:1"): @@ -470,7 +471,7 @@ def test_infer_plain_text_or_xml(self): self.assertIn("Error parsing XML", results.output) # XML by file extension - infile5 = write_file(join(self.tempdir, "infile5.readalong"), "Not XML!") + infile5 = write_file(self.tempdir / "infile5.readalong", "Not XML!") with SoundSwallowerStub("word:0:1"): results = self.runner.invoke( align, diff --git a/tests/test_anchors.py b/tests/test_anchors.py index be7045c4..843b7449 100755 --- a/tests/test_anchors.py +++ b/tests/test_anchors.py @@ -7,10 +7,9 @@ from io import StringIO from unittest import main -from basic_test_case import BasicTestCase, silence_c_stderr - from readalongs.align import align_audio from readalongs.log import LOGGER +from tests.basic_test_case import BasicTestCase, silence_c_stderr class TestAnchors(BasicTestCase): diff --git a/tests/test_api.py b/tests/test_api.py index 18353367..52dc96a3 100755 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -4,18 +4,17 @@ Test suite for the API way to call align """ -import os import re from contextlib import redirect_stderr from io import StringIO from unittest import main import click -from basic_test_case import BasicTestCase, silence_logs -from sound_swallower_stub import SoundSwallowerStub from readalongs import api from readalongs.log import LOGGER +from tests.basic_test_case import BasicTestCase, silence_logs +from tests.sound_swallower_stub import SoundSwallowerStub class TestAlignApi(BasicTestCase): @@ -94,7 +93,7 @@ def test_call_make_xml(self): def test_deprecated_prepare(self): with self.assertLogs(LOGGER, level="WARNING") as cm: - api.prepare(self.data_dir / "ej-fra.txt", os.devnull, ("fra",)) + api.prepare(self.data_dir / "ej-fra.txt", self.tempdir / "foo", ("fra",)) self.assertIn("deprecated", "\n".join(cm.output)) sentences_to_convert = [ diff --git a/tests/test_audio.py b/tests/test_audio.py index 5ab69359..307387a2 100755 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -7,8 +7,6 @@ from subprocess import run from unittest import main -from basic_test_case import BasicTestCase - from readalongs.audio_utils import ( extract_section, join_section, @@ -18,6 +16,7 @@ write_audio_to_file, ) from readalongs.log import LOGGER +from tests.basic_test_case import BasicTestCase class TestAudio(BasicTestCase): diff --git a/tests/test_dna_text.py b/tests/test_dna_text.py index e91b64b0..93253900 100755 --- a/tests/test_dna_text.py +++ b/tests/test_dna_text.py @@ -6,12 +6,12 @@ from io import StringIO from unittest import main -from basic_test_case import BasicTestCase from lxml import etree from readalongs.text import tokenize_xml from readalongs.text.add_ids_to_xml import add_ids from readalongs.text.util import parse_xml +from tests.basic_test_case import BasicTestCase class TestDNAText(BasicTestCase): diff --git a/tests/test_force_align.py b/tests/test_force_align.py index 37b0cb2c..d22d623a 100755 --- a/tests/test_force_align.py +++ b/tests/test_force_align.py @@ -12,7 +12,6 @@ from io import StringIO from tempfile import TemporaryDirectory -from basic_test_case import BasicTestCase, silence_c_stderr from lxml import etree from soundswallower import get_model_path @@ -25,6 +24,7 @@ from readalongs.log import LOGGER from readalongs.portable_tempfile import PortableNamedTemporaryFile from readalongs.text.util import load_txt, load_xml, save_xml +from tests.basic_test_case import BasicTestCase, silence_c_stderr class TestForceAlignment(BasicTestCase): diff --git a/tests/test_g2p_cli.py b/tests/test_g2p_cli.py index bf9d800e..544d1da2 100755 --- a/tests/test_g2p_cli.py +++ b/tests/test_g2p_cli.py @@ -8,16 +8,16 @@ from io import StringIO from unittest import main -from basic_test_case import BasicTestCase from lxml import etree -from sound_swallower_stub import SoundSwallowerStub -from test_make_xml_cli import updateFormatVersion, updateStudioVersion from readalongs.align import align_audio from readalongs.cli import align, g2p, make_xml, tokenize from readalongs.log import LOGGER from readalongs.text.convert_xml import convert_xml from readalongs.text.util import parse_xml +from tests.basic_test_case import BasicTestCase +from tests.sound_swallower_stub import SoundSwallowerStub +from tests.test_make_xml_cli import updateFormatVersion, updateStudioVersion def run_convert_xml(input_string): diff --git a/tests/test_make_xml_cli.py b/tests/test_make_xml_cli.py index c14df446..1c4c0fa9 100755 --- a/tests/test_make_xml_cli.py +++ b/tests/test_make_xml_cli.py @@ -8,12 +8,11 @@ from shutil import copyfile from unittest import main -from basic_test_case import BasicTestCase - # from readalongs.log import LOGGER from readalongs._version import READALONG_FILE_FORMAT_VERSION, VERSION from readalongs.align_utils import create_input_ras, create_ras_from_text from readalongs.cli import align, make_xml +from tests.basic_test_case import BasicTestCase def updateFormatVersion(input): diff --git a/tests/test_misc.py b/tests/test_misc.py index 85df66d6..0aa342a0 100755 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -8,10 +8,8 @@ from unittest import main import click -from basic_test_case import BasicTestCase from lxml import etree from pep440 import is_canonical -from test_dna_utils import segments_from_pairs from readalongs._version import READALONG_FILE_FORMAT_VERSION, VERSION from readalongs.align import split_silences @@ -28,6 +26,8 @@ save_xml, ) from readalongs.util import JoinerCallbackForClick +from tests.basic_test_case import BasicTestCase +from tests.test_dna_utils import segments_from_pairs class TestMisc(BasicTestCase): diff --git a/tests/test_package_urls.py b/tests/test_package_urls.py index 7fd41f22..6247bd14 100755 --- a/tests/test_package_urls.py +++ b/tests/test_package_urls.py @@ -3,13 +3,13 @@ from unittest import main import requests -from basic_test_case import BasicTestCase, silence_logs from readalongs.text.make_package import ( FONTS_BUNDLE_URL, JS_BUNDLE_URL, fetch_bundle_file, ) +from tests.basic_test_case import BasicTestCase, silence_logs class TestPackageURLs(BasicTestCase): diff --git a/tests/test_silence.py b/tests/test_silence.py index fb23d81a..a1bcff95 100755 --- a/tests/test_silence.py +++ b/tests/test_silence.py @@ -5,11 +5,11 @@ import os from unittest import main -from basic_test_case import BasicTestCase from pydub import AudioSegment from readalongs.cli import align from readalongs.text.util import load_xml +from tests.basic_test_case import BasicTestCase class TestSilence(BasicTestCase): diff --git a/tests/test_smil.py b/tests/test_smil.py index 4217b7c3..8bd08fa4 100644 --- a/tests/test_smil.py +++ b/tests/test_smil.py @@ -7,9 +7,8 @@ from textwrap import dedent from unittest import main -from basic_test_case import BasicTestCase - from readalongs.text.make_smil import make_smil, parse_smil +from tests.basic_test_case import BasicTestCase class TestSmilUtilities(BasicTestCase): diff --git a/tests/test_tokenize_cli.py b/tests/test_tokenize_cli.py index 31e20c36..9618dd93 100755 --- a/tests/test_tokenize_cli.py +++ b/tests/test_tokenize_cli.py @@ -6,9 +6,8 @@ import os from unittest import main -from basic_test_case import BasicTestCase - from readalongs.cli import make_xml, tokenize +from tests.basic_test_case import BasicTestCase # from readalongs.log import LOGGER diff --git a/tests/test_web_api.py b/tests/test_web_api.py index c8ba4c20..ec9327cd 100755 --- a/tests/test_web_api.py +++ b/tests/test_web_api.py @@ -10,8 +10,6 @@ from unittest import main from unittest.mock import patch -from basic_test_case import BasicTestCase - from readalongs._version import READALONG_FILE_FORMAT_VERSION, VERSION from readalongs.log import LOGGER from readalongs.text.add_ids_to_xml import add_ids @@ -20,6 +18,7 @@ from readalongs.text.util import parse_xml from readalongs.util import get_langs from readalongs.web_api import OutputFormat, create_grammar, web_api_app +from tests.basic_test_case import BasicTestCase class TestWebApi(BasicTestCase):