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"
+)