diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 91c02b7d36..db194b150b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,11 +14,23 @@ on: jobs: test: - runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + os: [ ubuntu-latest, windows-latest, macos-latest ] + python-version: [ "3.12" ] + include: + - os: ubuntu-latest + python-version: "3.7" + - os: macos-latest + python-version: "3.8" + - os: windows-latest + python-version: "3.9" + - os: ubuntu-latest + python-version: "3.10" + - os: macos-latest + python-version: "3.11" fail-fast: false + runs-on: ${{ matrix.os }} steps: - name: Dump GitHub context env: @@ -33,6 +45,7 @@ jobs: # cache: "pip" # cache-dependency-path: pyproject.toml - uses: actions/cache@v3 + if: ${{ runner.os != 'macOS' }} id: cache with: path: ${{ env.pythonLocation }} @@ -52,7 +65,7 @@ jobs: - name: Store coverage files uses: actions/upload-artifact@v4 with: - name: coverage-${{ matrix.python-version }} + name: coverage-${{ runner.os }}-${{ matrix.python-version }} path: coverage coverage-combine: diff --git a/docs/css/custom.css b/docs/css/custom.css index 954b0cf485..65265a5389 100644 --- a/docs/css/custom.css +++ b/docs/css/custom.css @@ -8,6 +8,10 @@ white-space: pre-wrap; } +.termy .linenos { + display: none; +} + a.external-link::after { /* \00A0 is a non-breaking space to make the mark be on the same line as the link @@ -21,3 +25,7 @@ a.internal-link::after { */ content: "\00A0↪"; } + +.shadow { + box-shadow: 5px 5px 10px #999; +} diff --git a/docs/js/custom.js b/docs/js/custom.js index 45243eb7ef..0dda9fdd54 100644 --- a/docs/js/custom.js +++ b/docs/js/custom.js @@ -1,105 +1,113 @@ -document.querySelectorAll(".use-termynal").forEach(node => { - node.style.display = "block"; - new Termynal(node, { - lineDelay: 500 +function setupTermynal() { + document.querySelectorAll(".use-termynal").forEach(node => { + node.style.display = "block"; + new Termynal(node, { + lineDelay: 500 + }); }); -}); -const progressLiteralStart = "---> 100%"; -const promptLiteralStart = "$ "; -const customPromptLiteralStart = "# "; -const termynalActivateClass = "termy"; -let termynals = []; + const progressLiteralStart = "---> 100%"; + const promptLiteralStart = "$ "; + const customPromptLiteralStart = "# "; + const termynalActivateClass = "termy"; + let termynals = []; -function createTermynals() { - document - .querySelectorAll(`.${termynalActivateClass} .highlight`) - .forEach(node => { - const text = node.textContent; - const lines = text.split("\n"); - const useLines = []; - let buffer = []; - function saveBuffer() { - if (buffer.length) { - let isBlankSpace = true; - buffer.forEach(line => { - if (line) { - isBlankSpace = false; + function createTermynals() { + document + .querySelectorAll(`.${termynalActivateClass} .highlight code`) + .forEach(node => { + const text = node.textContent; + const lines = text.split("\n"); + const useLines = []; + let buffer = []; + function saveBuffer() { + if (buffer.length) { + let isBlankSpace = true; + buffer.forEach(line => { + if (line) { + isBlankSpace = false; + } + }); + dataValue = {}; + if (isBlankSpace) { + dataValue["delay"] = 0; } - }); - dataValue = {}; - if (isBlankSpace) { - dataValue["delay"] = 0; - } - if (buffer[buffer.length - 1] === "") { - // A last single
won't have effect - // so put an additional one - buffer.push(""); + if (buffer[buffer.length - 1] === "") { + // A last single
won't have effect + // so put an additional one + buffer.push(""); + } + const bufferValue = buffer.join("
"); + dataValue["value"] = bufferValue; + useLines.push(dataValue); + buffer = []; } - const bufferValue = buffer.join("
"); - dataValue["value"] = bufferValue; - useLines.push(dataValue); - buffer = []; } - } - for (let line of lines) { - if (line === progressLiteralStart) { - saveBuffer(); - useLines.push({ - type: "progress" - }); - } else if (line.startsWith(promptLiteralStart)) { - saveBuffer(); - const value = line.replace(promptLiteralStart, "").trimEnd(); - useLines.push({ - type: "input", - value: value - }); - } else if (line.startsWith("// ")) { - saveBuffer(); - const value = "💬 " + line.replace("// ", "").trimEnd(); - useLines.push({ - value: value, - class: "termynal-comment", - delay: 0 - }); - } else if (line.startsWith(customPromptLiteralStart)) { - saveBuffer(); - const promptStart = line.indexOf(promptLiteralStart); - if (promptStart === -1) { - console.error("Custom prompt found but no end delimiter", line) + for (let line of lines) { + if (line === progressLiteralStart) { + saveBuffer(); + useLines.push({ + type: "progress" + }); + } else if (line.startsWith(promptLiteralStart)) { + saveBuffer(); + const value = line.replace(promptLiteralStart, "").trimEnd(); + useLines.push({ + type: "input", + value: value + }); + } else if (line.startsWith("// ")) { + saveBuffer(); + const value = "💬 " + line.replace("// ", "").trimEnd(); + useLines.push({ + value: value, + class: "termynal-comment", + delay: 0 + }); + } else if (line.startsWith(customPromptLiteralStart)) { + saveBuffer(); + const promptStart = line.indexOf(promptLiteralStart); + if (promptStart === -1) { + console.error("Custom prompt found but no end delimiter", line) + } + const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "") + let value = line.slice(promptStart + promptLiteralStart.length); + useLines.push({ + type: "input", + value: value, + prompt: prompt + }); + } else { + buffer.push(line); } - const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "") - let value = line.slice(promptStart + promptLiteralStart.length); - useLines.push({ - type: "input", - value: value, - prompt: prompt - }); - } else { - buffer.push(line); } - } - saveBuffer(); - const div = document.createElement("div"); - node.replaceWith(div); - const termynal = new Termynal(div, { - lineData: useLines, - noInit: true, - lineDelay: 500 + saveBuffer(); + const div = document.createElement("div"); + node.replaceWith(div); + const termynal = new Termynal(div, { + lineData: useLines, + noInit: true, + lineDelay: 500 + }); + termynals.push(termynal); }); - termynals.push(termynal); + } + + function loadVisibleTermynals() { + termynals = termynals.filter(termynal => { + if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { + termynal.init(); + return false; + } + return true; }); + } + window.addEventListener("scroll", loadVisibleTermynals); + createTermynals(); + loadVisibleTermynals(); } -function loadVisibleTermynals() { - termynals = termynals.filter(termynal => { - if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { - termynal.init(); - return false; - } - return true; - }); +async function main() { + setupTermynal() } -window.addEventListener("scroll", loadVisibleTermynals); -createTermynals(); -loadVisibleTermynals(); + +main() diff --git a/docs/release-notes.md b/docs/release-notes.md index 932ab22188..d9c26e247f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,7 +1,15 @@ ## Latest Changes +### Features + +* ✨ Add support for Python 3.12, tests in CI and official marker. PR [#807](https://github.com/tiangolo/typer/pull/807) by [@ivantodorovich](https://github.com/ivantodorovich). + ### Internal +* 🔨 Update docs Termynal scripts to not include line nums for local dev. PR [#882](https://github.com/tiangolo/typer/pull/882) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump black from 23.3.0 to 24.3.0. PR [#837](https://github.com/tiangolo/typer/pull/837) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump pillow from 10.1.0 to 10.3.0. PR [#836](https://github.com/tiangolo/typer/pull/836) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ✅ Add CI configs to run tests on Windows and MacOS. PR [#824](https://github.com/tiangolo/typer/pull/824) by [@svlandeg](https://github.com/svlandeg). * 👷 Update GitHub Actions to upload and download artifacts. PR [#829](https://github.com/tiangolo/typer/pull/829) by [@tiangolo](https://github.com/tiangolo). * 👷 Tweak CI for test-redistribute, add needed env vars for slim. PR [#827](https://github.com/tiangolo/typer/pull/827) by [@tiangolo](https://github.com/tiangolo). * ✅ Generalize test suite to run on Windows. PR [#810](https://github.com/tiangolo/typer/pull/810) by [@svlandeg](https://github.com/svlandeg). diff --git a/pyproject.toml b/pyproject.toml index 32a52a9a8d..fe63356032 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "License :: OSI Approved :: MIT License", ] dependencies = [ @@ -130,6 +131,7 @@ omit = [ "typer/_typing.py" ] context = '${CONTEXT}' +relative_files = true [tool.coverage.report] exclude_lines = [ diff --git a/requirements-docs.txt b/requirements-docs.txt index bad2afad2a..a85c03c87d 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -8,10 +8,10 @@ pyyaml >=5.3.1,<7.0.0 # For Material for MkDocs, Chinese search jieba==0.42.1 # For image processing by Material for MkDocs -pillow==10.1.0 +pillow==10.3.0 # For image processing by Material for MkDocs cairosvg==2.7.0 mkdocstrings[python]==0.23.0 griffe-typingdoc==0.2.2 # For griffe, it formats with black -black==23.3.0 +black==24.3.0 diff --git a/tests/test_completion/test_completion.py b/tests/test_completion/test_completion.py index d9115b8e77..703373b226 100644 --- a/tests/test_completion/test_completion.py +++ b/tests/test_completion/test_completion.py @@ -5,7 +5,10 @@ from docs_src.commands.index import tutorial001 as mod +from ..utils import needs_linux + +@needs_linux def test_show_completion(): result = subprocess.run( [ @@ -20,6 +23,7 @@ def test_show_completion(): assert "_TUTORIAL001.PY_COMPLETE=complete_bash" in result.stdout +@needs_linux def test_install_completion(): bash_completion_path: Path = Path.home() / ".bashrc" text = "" diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial004.py b/tests/test_tutorial/test_commands/test_help/test_tutorial004.py index 20127fc6b3..9d67b68f64 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial004.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial004.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -55,5 +56,6 @@ def test_script(): [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial004_an.py b/tests/test_tutorial/test_commands/test_help/test_tutorial004_an.py index f445f89c6e..fbc9fd28db 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial004_an.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial004_an.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -55,5 +56,6 @@ def test_script(): [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial005.py b/tests/test_tutorial/test_commands/test_help/test_tutorial005.py index 2cf6de837e..8d62cabdae 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial005.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial005.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -56,5 +57,6 @@ def test_script(): [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial005_an.py b/tests/test_tutorial/test_commands/test_help/test_tutorial005_an.py index 002caf4459..65335e6176 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial005_an.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial005_an.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -56,5 +57,6 @@ def test_script(): [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial006.py b/tests/test_tutorial/test_commands/test_help/test_tutorial006.py index d9da8331cb..d645164b57 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial006.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial006.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -47,5 +48,6 @@ def test_script(): [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial007.py b/tests/test_tutorial/test_commands/test_help/test_tutorial007.py index 1f320d7f3c..f262c251f5 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial007.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial007.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -51,5 +52,6 @@ def test_script(): [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial007_an.py b/tests/test_tutorial/test_commands/test_help/test_tutorial007_an.py index 2acb91367c..1a8c3d60a7 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial007_an.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial007_an.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -51,5 +52,6 @@ def test_script(): [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py index 3182d7b4ed..96a64779bd 100644 --- a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py +++ b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py @@ -18,7 +18,7 @@ def test_main(tmpdir): if binary_file.exists(): # pragma: no cover binary_file.unlink() result = runner.invoke(app, ["--file", f"{binary_file}"]) - text = binary_file.read_text() + text = binary_file.read_text(encoding="utf-8") binary_file.unlink() assert result.exit_code == 0 assert "Binary file written" in result.output diff --git a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004_an.py b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004_an.py index e222d7160c..7cf0ecf498 100644 --- a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004_an.py +++ b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004_an.py @@ -18,7 +18,7 @@ def test_main(tmpdir): if binary_file.exists(): # pragma: no cover binary_file.unlink() result = runner.invoke(app, ["--file", f"{binary_file}"]) - text = binary_file.read_text() + text = binary_file.read_text(encoding="utf-8") binary_file.unlink() assert result.exit_code == 0 assert "Binary file written" in result.output diff --git a/tests/utils.py b/tests/utils.py index 17d6de906c..9b503ff799 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -5,3 +5,7 @@ needs_py310 = pytest.mark.skipif( sys.version_info < (3, 10), reason="requires python3.10+" ) + +needs_linux = pytest.mark.skipif( + not sys.platform.startswith("linux"), reason="Test requires Linux" +)