From 452d2359ea9fff9a664914fdc6e194cbf788fa0d Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Mon, 7 Jul 2025 15:04:33 +0200 Subject: [PATCH 01/16] [IMP] update 0.1.8 --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3c165b..6c52a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,9 @@ packages = ["src"] readme = "README.md" requires-python = ">= 3.0" dependencies = [ - "jinja2>=3.1.6", - "pyyaml>=6.0.2", - "rich>=14.0.0", + "jinja2", + "pyyaml", + "rich", ] [build-system] From 9f0c2d41385d3e7fd3b03ab20175325e91d9f71d Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Mon, 7 Jul 2025 15:04:47 +0200 Subject: [PATCH 02/16] [IMP] update 0.1.8 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4950f81..1d259e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed - Minimum Python version is now 3.10. +- Remove packages version in `pyproject.toml` to use the latest versions. ### Fixed - Change poetry to rye in the scripts for building and testing the package. From 17985b01b9d1d56ae20d274036ba586314ce0fc3 Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 10:45:40 +0200 Subject: [PATCH 03/16] [IMP] make it compatible with Python 3.6 --- .github/workflows/actions.yml | 9 ++++++--- .gitignore | 1 - .pre-commit-config.yaml | 2 +- CHANGELOG.md | 2 +- docs/docs/en/contributing.md | 6 +++--- docs/docs/en/quick-start.md | 6 ++++++ docs/docs/en/user-guide/output.md | 8 ++++---- docs/docs/es/contributing.md | 6 +++--- docs/docs/es/quick-start.md | 6 ++++++ fire/main.py | 14 ++++++++++++++ pyproject.toml | 2 +- src/clifire/application.py | 11 ++++++----- src/clifire/command.py | 5 +++-- src/clifire/config.py | 8 ++++---- src/clifire/out.py | 14 ++++++++------ src/clifire/template.py | 3 ++- 16 files changed, 68 insertions(+), 35 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 4fcc0b5..40b4f8b 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -33,22 +33,25 @@ jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.6', '3.12'] steps: - name: Checkout code uses: actions/checkout@v3 - - name: Set up Python + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: - python-version: '3.12' + python-version: ${{ matrix.python-version }} - name: Install Rye run: | curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_TOOLCHAIN_VERSION: '3.12' + RYE_TOOLCHAIN_VERSION: ${{ matrix.python-version }} RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' diff --git a/.gitignore b/.gitignore index 3120337..71adf55 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ __pycache__/ .Python build/ dist/ -poetry.lock # Coverage htmlcov/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d9fb3f6..4ae46de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,7 +50,7 @@ repos: hooks: - id: pyupgrade name: Automatically upgrade syntax for newer versions of the language - args: ["--py310-plus", "--keep-runtime-typing", "--keep-percent-format"] + args: ["--py36-plus", "--keep-runtime-typing", "--keep-percent-format"] - repo: local hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d259e4..08632ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added ### Changed -- Minimum Python version is now 3.10. +- Minimum Python version is now 3.6. - Remove packages version in `pyproject.toml` to use the latest versions. ### Fixed diff --git a/docs/docs/en/contributing.md b/docs/docs/en/contributing.md index 67e3763..0abf543 100644 --- a/docs/docs/en/contributing.md +++ b/docs/docs/en/contributing.md @@ -40,14 +40,14 @@ There are several ways you can help: #### **Run the Tests:** Make sure all tests pass using: ```bash - poetry run pytest + rye run pytest ``` Check the coverage with: ```bash - poetry run coverage run -m pytest && poetry run coverage html + rye run coverage run -m pytest && rye run coverage html ``` - You can also use `poetry run fire coverage` to run the tests and generate the coverage report. + You can also use `rye run fire coverage` to run the tests and generate the coverage report. #### **Submit a Pull Request:** Once you are satisfied with your changes, submit a *pull request* to the main branch of the repository. Describe in detail what you have changed and the motivation behind it. diff --git a/docs/docs/en/quick-start.md b/docs/docs/en/quick-start.md index 9c3b26e..8ae791d 100644 --- a/docs/docs/en/quick-start.md +++ b/docs/docs/en/quick-start.md @@ -18,6 +18,12 @@ pip install clifire poetry add clifire ``` +### Using Rye + +```bash +rye add clifire +``` + ## Basic Usage CliFire allows you to define commands using decorators or classes. Here’s an example using a decorator to greet the user: diff --git a/docs/docs/en/user-guide/output.md b/docs/docs/en/user-guide/output.md index 671a6b8..b964be6 100644 --- a/docs/docs/en/user-guide/output.md +++ b/docs/docs/en/user-guide/output.md @@ -49,10 +49,10 @@ Below are the most commonly used functions: out.var_dump(sample_dict) ``` -- **`out.LiveText`** - This is a class that allows you to update text in real time in the terminal. It is useful for displaying progress bars or counters that update dynamically. +- **`out.live`** + This is a method that allows you to update text in real time in the terminal. It is useful for displaying progress bars or counters that update dynamically. ```python - live_text = out.LiveText("Starting...") + live_text = out.live("Starting...") live_text.info("Process running") live_text.warn("Retrying operation") live_text.success("Operation completed", end=False) @@ -100,7 +100,7 @@ class OutCommand(command.Command): print('') print('Live text') print('-' * 80) - live_text = out.LiveText("Starting...") + live_text = out.live("Starting...") time.sleep(1) live_text.info("Process running") time.sleep(1) diff --git a/docs/docs/es/contributing.md b/docs/docs/es/contributing.md index a2c3c42..ffa60ca 100644 --- a/docs/docs/es/contributing.md +++ b/docs/docs/es/contributing.md @@ -40,14 +40,14 @@ Existen varias maneras de ayudar: #### **Ejecuta los tests:** Asegúrate de que todos los tests pasan con: ```bash - poetry run pytest + rye run pytest ``` Comprueba la cobertura: ```bash - poetry run coverage run -m pytest && poetry run coverage html + rye run coverage run -m pytest && rye run coverage html ``` - También puedes usar `poetry run fire coverage` para ejecutar los tests y generar el informe de cobertura. + También puedes usar `rye run fire coverage` para ejecutar los tests y generar el informe de cobertura. #### **Realiza un Pull Request:** Una vez que estés satisfecho con tus cambios, realiza un *pull request* a la rama principal del repositorio. Describe detalladamente lo que has cambiado y la motivación detrás de ello. diff --git a/docs/docs/es/quick-start.md b/docs/docs/es/quick-start.md index 7d28c22..1d66016 100644 --- a/docs/docs/es/quick-start.md +++ b/docs/docs/es/quick-start.md @@ -18,6 +18,12 @@ pip install clifire poetry add clifire ``` +### Usando Rye + +```bash +rye add clifire +``` + ## Uso Básico CliFire te permite definir comandos mediante decoradores o clases. Aquí tienes un ejemplo utilizando un decorador para saludar al usuario: diff --git a/fire/main.py b/fire/main.py index f30e078..aee62c8 100644 --- a/fire/main.py +++ b/fire/main.py @@ -60,6 +60,20 @@ def build(cmd): cmd.app.fire('doc build') +@command.fire +def publish(cmd): + ''' + Publish then package + ''' + live = out.LiveText('Puiblising ...') + res = cmd.app.shell('rye publish -y', capture_output=False) + if res: + live.success('Published') + else: + live.error('Error on publish package') + return 1 + + @command.fire def coverage(cmd): ''' diff --git a/pyproject.toml b/pyproject.toml index 6c52a33..992a13a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ authors = [ ] packages = ["src"] readme = "README.md" -requires-python = ">= 3.0" +requires-python = ">= 3.6" dependencies = [ "jinja2", "pyyaml", diff --git a/src/clifire/application.py b/src/clifire/application.py index 3e7ff2b..24a8c6b 100644 --- a/src/clifire/application.py +++ b/src/clifire/application.py @@ -2,6 +2,7 @@ import shlex import subprocess import sys +from typing import Any, Dict, List, Type from clifire import command, commands, config, out, result, template @@ -13,10 +14,10 @@ def __init__( self, name: str = '', version: str = '0.0.1 alpha', - context: dict = None, + context: Dict[str, Any] = None, option_verbose: bool = True, option_ansi: bool = True, - config_files: list = None, + config_files: List[str] = None, config_create: bool = False, command_help=commands.help.CommandHelp, command_version=commands.version.CommandVersion, @@ -103,14 +104,14 @@ def get_option(self, name: str, default=None): return default return self.options[name][1] - def add_command(self, cls: command.Command): + def add_command(self, cls: Type[command.Command]): if not cls._name: raise command.CommandException( f'The command {cls} has no name, please set _name var in class' ) self.commands[cls._name] = cls - def add_commands(self, commands: list): + def add_commands(self, commands: List[Type[command.Command]]): for cmd in commands: self.add_command(cmd) @@ -220,7 +221,7 @@ def shell( os.chdir(pwd) @classmethod - def path(cls, *args: list[str]) -> str: + def path(cls, *args: List[str]) -> str: if len(args) == 0: args = (os.getcwd(),) exapnd_path = os.path.join( diff --git a/src/clifire/command.py b/src/clifire/command.py index 739f04f..db4a6f8 100644 --- a/src/clifire/command.py +++ b/src/clifire/command.py @@ -1,6 +1,7 @@ import inspect import re import shlex +from typing import List, Optional, Type, Union from clifire import out @@ -68,8 +69,8 @@ def __init__( pos: int = False, help: str = '', default: str = None, - alias: str | list[str] | None = None, - force_type: type = None, + alias: Union[str, List[str], None] = None, + force_type: Optional[Type] = None, ): self.name = 'unknow' self.pos = pos diff --git a/src/clifire/config.py b/src/clifire/config.py index 9889186..c2a47b8 100644 --- a/src/clifire/config.py +++ b/src/clifire/config.py @@ -1,4 +1,5 @@ import os +from typing import Any, Dict, List import yaml from clifire import out @@ -6,7 +7,7 @@ class Config: @classmethod - def get_config(cls, files: list, create: bool = False, **kwargs): + def get_config(cls, files: List[str], create: bool = False, **kwargs): def path(*args) -> str: expand_path = os.path.join( *(a.replace('~', os.path.expanduser('~')) for a in args) @@ -24,8 +25,7 @@ def path(*args) -> str: config.read() return config out.debug2('Not exist') - file = path(files[0]) - config = Config(config_file=config_file, **kwargs) + config = Config(config_file=path(files[0]), **kwargs) if create: config.write() return config @@ -85,7 +85,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return self.__str__() - def _safe_dict(self, data_dict: dict): + def _safe_dict(self, data_dict: Dict[str, Any]): return {k: v for k, v in data_dict.items() if not k.startswith('_')} def read(self): diff --git a/src/clifire/out.py b/src/clifire/out.py index ed62383..f01d435 100644 --- a/src/clifire/out.py +++ b/src/clifire/out.py @@ -3,7 +3,7 @@ import sys import threading import time -from typing import Any +from typing import Any, Dict, List, Optional, Tuple, Union from rich import traceback from rich.console import Console @@ -133,13 +133,13 @@ def live(text: str = '', refresh_per_second: int = 10): def table( - data: list[dict[str, Any]], + data: List[Dict[str, Any]], title: str = '', border: bool = True, show_header: bool = True, - style_cols: dict[str, str] = None, - padding: tuple = None, - style: str = None, + style_cols: Optional[Union[Dict[str, str], str]] = None, + padding: Optional[Tuple] = None, + style: Optional[str] = None, ): if not data: return @@ -220,7 +220,9 @@ def var_dump(var) -> None: CONSOLE.print(var, highlight=True) -def ask(text: str, choices: list[str] = ['y', 'n']): # noqa: B006 +def ask(text: str, choices: Optional[List[str]] = False): + if choices is False: + choices = ['y', 'n'] return Prompt.ask( text, choices=choices, default=choices[0] if choices else None ) diff --git a/src/clifire/template.py b/src/clifire/template.py index 025c291..b5a9c06 100644 --- a/src/clifire/template.py +++ b/src/clifire/template.py @@ -1,5 +1,6 @@ import os import re +from typing import List import jinja2 from clifire import application @@ -12,7 +13,7 @@ def __init__(self, template_folder: str): loader=jinja2.FileSystemLoader(template_folder) ) - def path(self, *args: list[str]) -> str: + def path(self, *args: List[str]) -> str: return application.App.current_app.path(self.template_folder, *args) def render(self, template, **args): From b85522f4bb02de53cd97f15910d26b1df45a3a35 Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 10:49:07 +0200 Subject: [PATCH 04/16] [FIX] Github actions work with 3.6 --- .github/workflows/actions.yml | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 40b4f8b..21be087 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -46,7 +46,15 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install Rye + - name: Install dependencies (Python 3.6) + if: matrix.python-version == '3.6' + run: | + python -m pip install --upgrade pip + python -m pip install pytest coverage + python -m pip install -e . + + - name: Install Rye (Python 3.12+) + if: matrix.python-version != '3.6' run: | curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH @@ -55,15 +63,30 @@ jobs: RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - - name: Install dependencies + - name: Install dependencies (Python 3.12+) + if: matrix.python-version != '3.6' run: | rye sync --all-features - - name: Run unit tests - run: rye run coverage run -m pytest + - name: Run unit tests (Python 3.6) + if: matrix.python-version == '3.6' + run: | + python -m coverage run -m pytest + + - name: Run unit tests (Python 3.12+) + if: matrix.python-version != '3.6' + run: | + rye run coverage run -m pytest - - name: Generate coverage report - run: rye run coverage xml + - name: Generate coverage report (Python 3.6) + if: matrix.python-version == '3.6' + run: | + python -m coverage xml + + - name: Generate coverage report (Python 3.12+) + if: matrix.python-version != '3.6' + run: | + rye run coverage xml - name: Upload coverage to Coveralls env: From 3ff7217b1b91fe3bab22f2341d95253d395e190e Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 10:51:05 +0200 Subject: [PATCH 05/16] [FIX] Github actions work with 3.6 --- .github/workflows/actions.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 21be087..468d50c 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -26,6 +26,7 @@ jobs: - name: Install dependencies run: | + rye pin cpython@3.12.2 rye sync --all-features - name: Run pre-commit From b7410089f5a9ce1b49d6a3ab118c753577512de9 Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 11:09:30 +0200 Subject: [PATCH 06/16] [FIX] Github actions work with 3.6 --- .github/workflows/actions.yml | 104 +++++++++++++++------------------- 1 file changed, 46 insertions(+), 58 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 468d50c..44eccbd 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -32,66 +32,54 @@ jobs: - name: Run pre-commit run: rye run pre-commit run --all-files - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.6', '3.12'] - + test-legacy: + runs-on: ubuntu-22.04 steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies (Python 3.6) - if: matrix.python-version == '3.6' - run: | - python -m pip install --upgrade pip - python -m pip install pytest coverage - python -m pip install -e . - - - name: Install Rye (Python 3.12+) - if: matrix.python-version != '3.6' - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_TOOLCHAIN_VERSION: ${{ matrix.python-version }} - RYE_VERSION: '0.44.0' - RYE_INSTALL_OPTION: '--yes' - - - name: Install dependencies (Python 3.12+) - if: matrix.python-version != '3.6' - run: | - rye sync --all-features - - - name: Run unit tests (Python 3.6) - if: matrix.python-version == '3.6' - run: | - python -m coverage run -m pytest + - name: Checkout code + uses: actions/checkout@v4 - - name: Run unit tests (Python 3.12+) - if: matrix.python-version != '3.6' - run: | - rye run coverage run -m pytest + - name: Set up Python 3.6 + uses: actions/setup-python@v4 + with: + python-version: '3.6' - - name: Generate coverage report (Python 3.6) - if: matrix.python-version == '3.6' - run: | - python -m coverage xml + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest coverage coveralls + pip install -e . - - name: Generate coverage report (Python 3.12+) - if: matrix.python-version != '3.6' - run: | - rye run coverage xml + - name: Run unit tests + run: coverage run -m pytest - - name: Upload coverage to Coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} - run: | - pip install coveralls - coveralls + test-modern: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python for Rye + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run unit tests + run: rye run coverage run -m pytest + + - name: Generate coverage report + run: rye run coverage xml + + - name: Upload coverage to Coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: rye run coveralls From 68328f3375f73ea5ad8cbd0235de9a27e9f805dd Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 11:11:20 +0200 Subject: [PATCH 07/16] [FIX] Github actions work with 3.6 --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 44eccbd..4860114 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -38,10 +38,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python 3.6 + - name: Set up Python 3.6.7 uses: actions/setup-python@v4 with: - python-version: '3.6' + python-version: '3.6.7' - name: Install dependencies run: | From 2833c2586e5a18ec1bce89625bfba6b6e71db393 Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 11:14:50 +0200 Subject: [PATCH 08/16] [FIX] Github actions work with 3.6 --- .github/workflows/actions.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 4860114..ffae232 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -38,10 +38,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python 3.6.7 - uses: actions/setup-python@v4 + - name: Set up Python 3.6.7 with pyenv + uses: gabrielfalcao/pyenv-action@v13 with: - python-version: '3.6.7' + default: 3.6.7 + command: pip install -U pip - name: Install dependencies run: | @@ -71,7 +72,9 @@ jobs: RYE_INSTALL_OPTION: '--yes' - name: Install dependencies - run: rye sync --all-features + run: | + rye pin cpython@3.12.2 + rye sync --all-features - name: Run unit tests run: rye run coverage run -m pytest From 06f5801309ea736aacdc23b120beb12ec65862ac Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 11:20:58 +0200 Subject: [PATCH 09/16] [FIX] Github actions work with 3.6 --- .github/workflows/actions.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index ffae232..1d613a0 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -33,17 +33,12 @@ jobs: run: rye run pre-commit run --all-files test-legacy: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest + container: python:3.6.15-slim steps: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python 3.6.7 with pyenv - uses: gabrielfalcao/pyenv-action@v13 - with: - default: 3.6.7 - command: pip install -U pip - - name: Install dependencies run: | python -m pip install --upgrade pip @@ -51,7 +46,7 @@ jobs: pip install -e . - name: Run unit tests - run: coverage run -m pytest + run: python -m pytest test-modern: runs-on: ubuntu-latest @@ -85,4 +80,6 @@ jobs: - name: Upload coverage to Coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} - run: rye run coveralls + run: | + pip install coveralls + coveralls From f6f2f6b81734fabd4278f6b459f4503743021f97 Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 12:32:06 +0200 Subject: [PATCH 10/16] [FIX] Github actions work with 3.6 --- .isort.cfg | 2 +- setup.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 setup.py diff --git a/.isort.cfg b/.isort.cfg index c740516..73f0b7b 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,2 +1,2 @@ [settings] -known_third_party = clifire,jinja2,pytest,rich,yaml +known_third_party = clifire,jinja2,pytest,rich,setuptools,yaml diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bb32ce9 --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +# setup.py (for compatibility with Python 3.6) +from setuptools import find_packages, setup + +setup( + name='clifire', + version='0.1.8', + packages=find_packages(where='src'), + package_dir={'': 'src'}, + install_requires=[ + 'jinja2', + 'pyyaml', + 'rich', + ], +) From 14995f49d27c58f646cb1176c14c5fee3cfb8fba Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 8 Jul 2025 12:39:39 +0200 Subject: [PATCH 11/16] [FIX] Github actions work with 3.6 --- .github/workflows/actions.yml | 23 +++++++++++++++++++++-- .isort.cfg | 2 +- setup.py | 14 -------------- 3 files changed, 22 insertions(+), 17 deletions(-) delete mode 100644 setup.py diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 1d613a0..fd56329 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -39,10 +39,29 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Create compatible setup environment + run: | + mv pyproject.toml pyproject.toml.bak + cat > setup.py << 'EOF' + from setuptools import setup, find_packages + + setup( + name='clifire', + version='0.1.8', + packages=find_packages(where='src'), + package_dir={'': 'src'}, + install_requires=[ + 'rich<13.0.0', + 'pyyaml', + 'jinja2<3.1.0', + ], + ) + EOF + - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install pytest coverage coveralls + python -m pip install --upgrade "pip<21.3.2" + pip install "pytest<7.1.0" "coverage<7.0.0" coveralls pip install -e . - name: Run unit tests diff --git a/.isort.cfg b/.isort.cfg index 73f0b7b..c740516 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,2 +1,2 @@ [settings] -known_third_party = clifire,jinja2,pytest,rich,setuptools,yaml +known_third_party = clifire,jinja2,pytest,rich,yaml diff --git a/setup.py b/setup.py deleted file mode 100644 index bb32ce9..0000000 --- a/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -# setup.py (for compatibility with Python 3.6) -from setuptools import find_packages, setup - -setup( - name='clifire', - version='0.1.8', - packages=find_packages(where='src'), - package_dir={'': 'src'}, - install_requires=[ - 'jinja2', - 'pyyaml', - 'rich', - ], -) From 5115b7121c1117ca6aa5809a9ce656405cb98fbe Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 15 Jul 2025 10:19:12 +0200 Subject: [PATCH 12/16] [IMP] version 0.1.8 --- .github/workflows/actions.yml | 39 +++++++---------- .pre-commit-config.yaml | 2 +- .python-version | 1 + CHANGELOG.md | 9 +++- Dockerfile | 13 ++++++ fire/test.py | 23 ++++++++++ src/clifire/application.py | 3 ++ src/clifire/out.py | 79 ++++++++++++++++++++++++++--------- tests/test_app.py | 27 +++++++++++- tests/test_output.py | 64 ++++++++++++++++++++++++++-- 10 files changed, 209 insertions(+), 51 deletions(-) create mode 100644 .python-version create mode 100644 Dockerfile create mode 100644 fire/test.py diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index fd56329..5abc06a 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -32,42 +32,33 @@ jobs: - name: Run pre-commit run: rye run pre-commit run --all-files - test-legacy: + test-python-3_8: runs-on: ubuntu-latest - container: python:3.6.15-slim steps: - name: Checkout code uses: actions/checkout@v4 - - name: Create compatible setup environment + - name: Set up Python for Rye + uses: actions/setup-python@v4 + with: + python-version: '3.8.2' + + - name: Install Rye run: | - mv pyproject.toml pyproject.toml.bak - cat > setup.py << 'EOF' - from setuptools import setup, find_packages - - setup( - name='clifire', - version='0.1.8', - packages=find_packages(where='src'), - package_dir={'': 'src'}, - install_requires=[ - 'rich<13.0.0', - 'pyyaml', - 'jinja2<3.1.0', - ], - ) - EOF + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_INSTALL_OPTION: '--yes' - name: Install dependencies run: | - python -m pip install --upgrade "pip<21.3.2" - pip install "pytest<7.1.0" "coverage<7.0.0" coveralls - pip install -e . + rye pin cpython@3.8.2 + rye sync --all-features - name: Run unit tests - run: python -m pytest + run: rye run python -m pytest - test-modern: + test-python-3_12: runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ae46de..d4adc69 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,7 +50,7 @@ repos: hooks: - id: pyupgrade name: Automatically upgrade syntax for newer versions of the language - args: ["--py36-plus", "--keep-runtime-typing", "--keep-percent-format"] + args: ["--py38-plus", "--keep-runtime-typing", "--keep-percent-format"] - repo: local hooks: diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..56bb660 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 08632ff..ad00067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [0.1.8] - 2025-07-07 ### Added +- Handle program interruption with `KeyboardInterrupt` to allow canceling the execution of a command. +- Added an `icon` argument to the `critical`, `error`, `warning`, and `success` methods in `out` to display an icon. +- Added the same `icon` argument to the `error`, `warning`, and `success` methods of `live`. +- Added a Dockerfile to test the library with Python 3.8. ### Changed - Minimum Python version is now 3.6. -- Remove packages version in `pyproject.toml` to use the latest versions. +- Set the Python version for development to 3.12.7. +- Removed package versions in `pyproject.toml` to use the latest versions. ### Fixed -- Change poetry to rye in the scripts for building and testing the package. +- Changed poetry to rye in the scripts for building and testing the package. ## [0.1.7] - 2025-07-06 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9087bc4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.8.2-slim + +WORKDIR /app + +COPY pyproject.toml README.md src ./ + +RUN sed -i 's/coverage = ">=7.9.1"/coverage = ">=6.0.0,<7.0.0"/' pyproject.toml + +RUN pip install --upgrade pip && \ + pip install pytest "coverage<7.0.0" && \ + pip install -e . + +CMD ["bash", "-c", "python -m pytest -v"] diff --git a/fire/test.py b/fire/test.py new file mode 100644 index 0000000..4c4a701 --- /dev/null +++ b/fire/test.py @@ -0,0 +1,23 @@ +from clifire import command + + +@command.fire +def test_legacy(cmd, _build: bool = False): + ''' + Launch tests in docker image with Python 3.8.2 + ''' + + def docker_build(): + cmd.app.shell( + 'docker build -t clifire-py38-tests .', capture_output=False + ) + + if _build: + docker_build() + elif 'clifire-py38-tests' not in cmd.app.shell('docker images').stdout: + docker_build() + volumen_str = '-v ./src:/app/src -v ./tests:/app/tests' + cmd.app.shell( + f'docker run --rm {volumen_str} clifire-py38-tests', + capture_output=False, + ) diff --git a/src/clifire/application.py b/src/clifire/application.py index 24a8c6b..2b180a6 100644 --- a/src/clifire/application.py +++ b/src/clifire/application.py @@ -187,6 +187,9 @@ def fire(self, command_line: str = None): out.critical(e, code=30) except command.FieldException as e: out.critical(e, code=40) + except KeyboardInterrupt: + out.error('Keyboard interrupt!') + raise @classmethod def shell( diff --git a/src/clifire/out.py b/src/clifire/out.py index f01d435..18ec577 100644 --- a/src/clifire/out.py +++ b/src/clifire/out.py @@ -29,6 +29,10 @@ COLOR_WARN = 'yellow' COLOR_ERROR = 'red' +ICON_SUCCESS = '✓' +ICON_ERROR = '✗' +ICON_WARN = '▲' + def setup(ansi: bool = True): if ansi: @@ -80,9 +84,9 @@ def _start(self): self._live.update(self._text) def start(self): + self._running = True if self._thread and self._thread.is_alive(): return - self._running = True self._thread = threading.Thread(target=self._start, daemon=True) self._thread.start() @@ -91,32 +95,41 @@ def cancel(self): self.stop() def stop(self): - self._running = False if self._text != '': CONSOLE.print(self._text) + self._running = False if self._thread and self._thread.is_alive(): - self._live.transient = True + if self._live: + self._live.transient = True self._text = text_color('') self._thread.join(timeout=1.0) - self._live.update(self._text) + if self._live: + self._live.update(self._text) + self._live.stop() def info(self, text: str, end=False): self._text = text_color(text, color=COLOR_INFO) if end: self.stop() - def warn(self, text: str, end=False): - self._text = text_color(text, color=COLOR_WARN) + def warn(self, text: str, end=False, icon: bool = False): + self._text = text_color( + text, color=COLOR_WARN, icon=ICON_WARN, force_icon=icon + ) if end: self.stop() - def success(self, text: str, end=True): - self._text = text_color(text, color=COLOR_SUCCESS) + def success(self, text: str, end=True, icon: bool = False): + self._text = text_color( + text, color=COLOR_SUCCESS, icon=ICON_SUCCESS, force_icon=icon + ) if end: self.stop() - def error(self, text: str, end=True): - self._text = text_color(text, color=COLOR_ERROR) + def error(self, text: str, end=True, icon: bool = False): + self._text = text_color( + text, color=COLOR_ERROR, icon=ICON_ERROR, force_icon=icon + ) if end: self.stop() @@ -127,6 +140,10 @@ def error(self, text: str, end=True): def live(text: str = '', refresh_per_second: int = 10): global _current_live if _current_live: + _current_live.info(text) + _current_live.refresh_per_second = refresh_per_second + if not _current_live.is_alive: + _current_live.start() return _current_live _current_live = LiveText(text, refresh_per_second=refresh_per_second) return _current_live @@ -171,28 +188,43 @@ def ansi_clean(text: str) -> str: return re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', text) -def text_color(text: str, color: str = COLOR_NORMAL) -> str: +def text_color( + text: str, + color: str = COLOR_NORMAL, + icon: str = None, + force_icon: bool = False, +) -> str: + text = str(text) + if icon and not text.startswith(icon): + if CONSOLE.no_color is True or force_icon: + text = f'{icon} {text}' return f'[{color}]{text}[/{color}]' -def _print(text: str, color: str = COLOR_NORMAL) -> None: - CONSOLE.print(text_color(text, color=color)) +def _print( + text: str, + color: str = COLOR_NORMAL, + icon: str = None, + force_icon: bool = False, +) -> None: + text = text_color(text, color=color, icon=icon, force_icon=force_icon) + CONSOLE.print(text) def info(text: str) -> None: _print(text, COLOR_INFO) -def success(text: str) -> None: - _print(text, COLOR_SUCCESS) +def success(text: str, icon: bool = False) -> None: + _print(text, color=COLOR_SUCCESS, icon=ICON_SUCCESS, force_icon=icon) -def warn(text: str) -> None: - _print(text, COLOR_WARN) +def warn(text: str, icon: bool = False) -> None: + _print(text, color=COLOR_WARN, icon=ICON_WARN, force_icon=icon) -def error(text: str) -> None: - _print(text, COLOR_ERROR) +def error(text: str, icon: bool = False) -> None: + _print(text, color=COLOR_ERROR, icon=ICON_ERROR, force_icon=icon) def critical(text: str, code: int = 1) -> None: @@ -220,9 +252,16 @@ def var_dump(var) -> None: CONSOLE.print(var, highlight=True) -def ask(text: str, choices: Optional[List[str]] = False): +def ask(text: str, choices: List[str] = False): if choices is False: choices = ['y', 'n'] return Prompt.ask( text, choices=choices, default=choices[0] if choices else None ) + + +def rule(text: str) -> None: + global _current_live + if _current_live: + _current_live.info(text) + CONSOLE.rule(f'[bold blue]{text}', align='left', style='blue') diff --git a/tests/test_app.py b/tests/test_app.py index c4314ae..428d21d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,10 +1,11 @@ import os import subprocess import sys +import time from unittest.mock import patch import pytest -from clifire import application, out +from clifire import application, command, out def output(capsys): @@ -12,6 +13,30 @@ def output(capsys): return out.ansi_clean(captured.out) +def test_app_keyboard_interrupt(capsys): + class CommandWait(command.Command): + _name = 'wait' + + def fire(self): + try: + time.sleep(1) + except KeyboardInterrupt: + out.info('KeyboardInterrupt received') + raise + + app = application.App() + app.add_command(CommandWait) + + with patch('time.sleep', side_effect=KeyboardInterrupt): + with pytest.raises(KeyboardInterrupt): + app.fire('wait') + + printed = output(capsys) + assert 'KeyboardInterrupt received' in printed + assert 'End command' not in printed + assert 'Keyboard interrupt!' in printed + + def test_app_global_option_no_ansi(capsys): app = application.App(name='sample', version='1.0.99') assert app.get_option('no_ansi') is False diff --git a/tests/test_output.py b/tests/test_output.py index 32a4b82..a9ab96c 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -195,9 +195,9 @@ def test_var_dump(capsys): } ) printed = output(capsys) - assert "\n 'number': 1," in printed - assert "\n 'list': ['a', 'b', 'c']," in printed - assert "\n 'dict': {'key a': 'value a'," in printed + assert "'number': 1," in printed + assert "'list': ['a', 'b', 'c']," in printed + assert "'dict': {'key a': 'value a'," in printed def test_ask(capsys, monkeypatch): @@ -221,3 +221,61 @@ def test_ask(capsys, monkeypatch): printed = output(capsys) assert 'Continue?' in printed assert '[yes/no]' in printed + + +def test_rule(capsys): + out.rule('My rule') + printed = output(capsys) + assert 'My rule' in printed + assert '─' in printed + + out.rule('My rule') + printed = output(capsys) + assert 'My rule' in printed + assert '─' in printed + + live = out.live('Message in live') + assert 'Message in live' in live._text + out.rule('My rule') + assert 'My rule' in live._text + printed = output(capsys) + assert 'My rule' in printed + live.cancel() + printed = output(capsys) + assert 'My rule' not in printed + + +def test_no_ansi(capsys): + out.setup(ansi=False) + assert out.CONSOLE.no_color is True + out.success('Success') + assert '✓ Success' in output(capsys) + out.warn('Warn') + assert '▲ Warn' in output(capsys) + out.error('Error') + assert '✗ Error' in output(capsys) + + live = out.live('Message in live') + assert 'Message in live' in live._text + live.success('Success', end=False) + time.sleep(1) + assert '✓ Success' in live._text + live.error('Error', end=False) + assert '✗ Error' in live._text + live.cancel() + + output(capsys) + out.setup(ansi=True) + assert out.CONSOLE.no_color is False + out.success('Success') + assert '✓' not in output(capsys) + out.warn('Warn') + assert '▲' not in output(capsys) + out.error('Error') + assert '✗' not in output(capsys) + out.success('Success', icon=True) + assert '✓ Success' in output(capsys) + out.warn('Warn', icon=True) + assert '▲ Warn' in output(capsys) + out.error('Error', icon=True) + assert '✗ Error' in output(capsys) From ab4be2c1d26581da7c810176b7c1ee2c0d7bd570 Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 15 Jul 2025 13:30:13 +0200 Subject: [PATCH 13/16] [IMP] update github action --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 5abc06a..30d569e 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -33,7 +33,7 @@ jobs: run: rye run pre-commit run --all-files test-python-3_8: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout code uses: actions/checkout@v4 From 8c8a424d6030bc7565fe5332cf213bb84b18938a Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 15 Jul 2025 19:01:02 +0200 Subject: [PATCH 14/16] [IMP] docker for tests in python 3.8 --- Dockerfile | 26 ++++++++++++++++++-------- fire/main.py | 9 +++++++++ fire/test.py | 19 ++++++++++++++----- pyproject.toml | 14 +++++++------- tests/test_sample_fire.py | 3 ++- 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9087bc4..8877546 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,23 @@ -FROM python:3.8.2-slim +FROM ubuntu:latest +ARG PYTHON_VERSION=3.8.2 -WORKDIR /app +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* -COPY pyproject.toml README.md src ./ +ENV RYE_HOME="/root/.rye" +ENV RYE_INSTALL_OPTION="--yes" +ENV RYE_NO_AUTO_INSTALL="true" +RUN curl -sSf https://rye.astral.sh/get | bash +ENV PATH="/root/.rye/shims:${PATH}" -RUN sed -i 's/coverage = ">=7.9.1"/coverage = ">=6.0.0,<7.0.0"/' pyproject.toml +WORKDIR /app +COPY pyproject.toml README.md ./ +COPY src ./src +COPY tests ./tests +COPY fire ./fire -RUN pip install --upgrade pip && \ - pip install pytest "coverage<7.0.0" && \ - pip install -e . +RUN rye pin cpython@${PYTHON_VERSION} && \ + rye sync --all-features -CMD ["bash", "-c", "python -m pytest -v"] +CMD ["rye", "run", "fire", "tests"] diff --git a/fire/main.py b/fire/main.py index aee62c8..5a55b2e 100644 --- a/fire/main.py +++ b/fire/main.py @@ -89,3 +89,12 @@ def precommit(cmd): Launch pre-commit ''' cmd.app.shell('pre-commit run --all-files', capture_output=False) + + +@command.fire +def tests(cmd): + ''' + Launch tests + ''' + path = cmd.app.path(os.path.dirname(__file__), '..') + cmd.app.shell('rye run python -m pytest', capture_output=False, path=path) diff --git a/fire/test.py b/fire/test.py index 4c4a701..e9ce8cc 100644 --- a/fire/test.py +++ b/fire/test.py @@ -2,22 +2,31 @@ @command.fire -def test_legacy(cmd, _build: bool = False): +def test_legacy(cmd, _build: bool = False, python_version: str = '3.8.2'): ''' - Launch tests in docker image with Python 3.8.2 + Launch tests in docker image with specified Python version + + Args: + python_version: Python version to use for the tests. (default: '3.8.2') + _build: Force build the Docker image before running tests. ''' + image_name = f'clifire-py{python_version}' + def docker_build(): cmd.app.shell( - 'docker build -t clifire-py38-tests .', capture_output=False + f'docker build --build-arg PYTHON_VERSION={python_version} ' + f'-t {image_name} .', + capture_output=False, ) if _build: docker_build() - elif 'clifire-py38-tests' not in cmd.app.shell('docker images').stdout: + elif image_name not in cmd.app.shell('docker images').stdout: docker_build() + volumen_str = '-v ./src:/app/src -v ./tests:/app/tests' cmd.app.shell( - f'docker run --rm {volumen_str} clifire-py38-tests', + f'docker run --rm {volumen_str} {image_name}', capture_output=False, ) diff --git a/pyproject.toml b/pyproject.toml index 992a13a..9b63a0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,13 +21,13 @@ build-backend = "hatchling.build" [tool.rye] managed = true dev-dependencies = [ - "coverage>=7.9.1", - "mkdocs-material>=9.6.14", - "mkdocs-static-i18n>=1.3.0", - "mkdocs>=1.6.1", - "pre-commit>=4.2.0", - "pytest>=8.4.0", - "black-with-tabs>=22.10.0", + "coverage", + "mkdocs-material", + "mkdocs-static-i18n", + "mkdocs", + "pre-commit", + "pytest", + "black-with-tabs", ] [tool.hatch.metadata] diff --git a/tests/test_sample_fire.py b/tests/test_sample_fire.py index 9343658..a3734a4 100644 --- a/tests/test_sample_fire.py +++ b/tests/test_sample_fire.py @@ -127,7 +127,8 @@ def test_fire_group_help(capsys): app.fire('help ab ef') printed = output(capsys) - assert 'ab ef' not in printed + assert 'ab [options]' in printed + assert 'ab ef [options]' not in printed assert 'doc_ab_ef_gh' in printed app.fire('help zz') From 8a9dab273cdb25203f4ca2587b760493f3fcdd68 Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 15 Jul 2025 19:05:24 +0200 Subject: [PATCH 15/16] [IMP] simplify github actions --- .github/workflows/actions.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 30d569e..1f39f3a 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -5,7 +5,6 @@ on: [push, pull_request] jobs: pre-commit: runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v3 @@ -38,11 +37,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python for Rye - uses: actions/setup-python@v4 - with: - python-version: '3.8.2' - - name: Install Rye run: | curl -sSf https://rye.astral.sh/get | bash @@ -56,7 +50,7 @@ jobs: rye sync --all-features - name: Run unit tests - run: rye run python -m pytest + run: rye run fire tests test-python-3_12: runs-on: ubuntu-latest @@ -81,11 +75,8 @@ jobs: rye pin cpython@3.12.2 rye sync --all-features - - name: Run unit tests - run: rye run coverage run -m pytest - - - name: Generate coverage report - run: rye run coverage xml + - name: Run unit tests with coverage + run: rye run fire coverage - name: Upload coverage to Coveralls env: From 78a54f0f5eb65a4ad60225b375f7ee15b0c352ae Mon Sep 17 00:00:00 2001 From: Roberto Lizana Date: Tue, 15 Jul 2025 19:08:08 +0200 Subject: [PATCH 16/16] [IMP] simplify github actions --- .github/workflows/actions.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 1f39f3a..c46d756 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -32,11 +32,16 @@ jobs: run: rye run pre-commit run --all-files test-python-3_8: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Python for Rye + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install Rye run: | curl -sSf https://rye.astral.sh/get | bash