Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ jobs:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
if: |
always() && !cancelled() &&
!contains(needs.*.result, 'failure') &&
Expand All @@ -144,7 +143,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: "Setup environment"
run: |
pipx install poetry==1.8.5
pipx install poetry==1.8.5 --python python${{ matrix.python-version }}
poetry config virtualenvs.create true --local
pip install invoke toml codecov
- name: "Install Package"
Expand Down
1 change: 1 addition & 0 deletions changelog/251.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix typing for Python 3.9 and remove support for Python 3.13
6 changes: 3 additions & 3 deletions infrahub_sdk/ctl/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from asyncio import run as aiorun
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

import typer
from rich.console import Console
Expand Down Expand Up @@ -50,8 +50,8 @@ def run(
format_json: bool,
list_available: bool,
variables: dict[str, str],
name: str | None = None,
branch: str | None = None,
name: Optional[str] = None,
branch: Optional[str] = None,
) -> None:
"""Locate and execute all checks under the defined path."""

Expand Down
18 changes: 9 additions & 9 deletions infrahub_sdk/ctl/cli_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import platform
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any, Callable, Optional

import jinja2
import typer
Expand Down Expand Up @@ -74,13 +74,13 @@
@catch_exception(console=console)
def check(
check_name: str = typer.Argument(default="", help="Name of the Python check"),
branch: str | None = None,
branch: Optional[str] = None,
path: str = typer.Option(".", help="Root directory"),
debug: bool = False,
format_json: bool = False,
_: str = CONFIG_PARAM,
list_available: bool = typer.Option(False, "--list", help="Show available Python checks"),
variables: list[str] | None = typer.Argument(
variables: Optional[list[str]] = typer.Argument(
None, help="Variables to pass along with the query. Format key=value key=value."
),
) -> None:
Expand All @@ -102,12 +102,12 @@ def check(
@catch_exception(console=console)
async def generator(
generator_name: str = typer.Argument(default="", help="Name of the Generator"),
branch: str | None = None,
branch: Optional[str] = None,
path: str = typer.Option(".", help="Root directory"),
debug: bool = False,
_: str = CONFIG_PARAM,
list_available: bool = typer.Option(False, "--list", help="Show available Generators"),
variables: list[str] | None = typer.Argument(
variables: Optional[list[str]] = typer.Argument(
None, help="Variables to pass along with the query. Format key=value key=value."
),
) -> None:
Expand All @@ -130,13 +130,13 @@ async def run(
debug: bool = False,
_: str = CONFIG_PARAM,
branch: str = typer.Option(None, help="Branch on which to run the script."),
concurrent: int | None = typer.Option(
concurrent: Optional[int] = typer.Option(
None,
help="Maximum number of requests to execute at the same time.",
envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
),
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUB_TIMEOUT"),
variables: list[str] | None = typer.Argument(
variables: Optional[list[str]] = typer.Argument(
None, help="Variables to pass along with the query. Format key=value key=value."
),
) -> None:
Expand Down Expand Up @@ -259,7 +259,7 @@ def _run_transform(
@catch_exception(console=console)
def render(
transform_name: str = typer.Argument(default="", help="Name of the Python transformation", show_default=False),
variables: list[str] | None = typer.Argument(
variables: Optional[list[str]] = typer.Argument(
None, help="Variables to pass along with the query. Format key=value key=value."
),
branch: str = typer.Option(None, help="Branch on which to render the transform."),
Expand Down Expand Up @@ -309,7 +309,7 @@ def render(
@catch_exception(console=console)
def transform(
transform_name: str = typer.Argument(default="", help="Name of the Python transformation", show_default=False),
variables: list[str] | None = typer.Argument(
variables: Optional[list[str]] = typer.Argument(
None, help="Variables to pass along with the query. Format key=value key=value."
),
branch: str = typer.Option(None, help="Branch on which to run the transformation"),
Expand Down
4 changes: 2 additions & 2 deletions infrahub_sdk/ctl/generator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

import typer
from rich.console import Console
Expand All @@ -23,7 +23,7 @@ async def run(
debug: bool,
list_available: bool,
branch: str | None = None,
variables: list[str] | None = None,
variables: Optional[list[str]] = None,
) -> None:
init_logging(debug=debug)
repository_config = get_repository_config(Path(config.INFRAHUB_REPO_CONFIG_FILE))
Expand Down
3 changes: 2 additions & 1 deletion infrahub_sdk/ctl/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from asyncio import run as aiorun
from pathlib import Path
from typing import Optional

import typer
from rich.console import Console
Expand All @@ -26,7 +27,7 @@ def load(
quiet: bool = typer.Option(False, help="No console output"),
_: str = CONFIG_PARAM,
branch: str = typer.Option(None, help="Branch from which to export"),
concurrent: int | None = typer.Option(
concurrent: Optional[int] = typer.Option(
None,
help="Maximum number of requests to execute at the same time.",
envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
Expand Down
5 changes: 3 additions & 2 deletions infrahub_sdk/ctl/repository.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Optional

import typer
import yaml
Expand Down Expand Up @@ -68,7 +69,7 @@ async def add(
name: str,
location: str,
description: str = "",
username: str | None = None,
username: Optional[str] = None,
password: str = "",
ref: str = "",
read_only: bool = False,
Expand Down Expand Up @@ -114,7 +115,7 @@ async def add(

@app.command()
async def list(
branch: str = typer.Option(None, help="Branch on which to list repositories."),
branch: Optional[str] = typer.Option(None, help="Branch on which to list repositories."),
debug: bool = False,
_: str = CONFIG_PARAM,
) -> None:
Expand Down
4 changes: 2 additions & 2 deletions infrahub_sdk/ctl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from collections.abc import Coroutine
from functools import wraps
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, NoReturn, TypeVar
from typing import TYPE_CHECKING, Any, Callable, NoReturn, Optional, TypeVar

import pendulum
import typer
Expand Down Expand Up @@ -145,7 +145,7 @@ def print_graphql_errors(console: Console, errors: list) -> None:
console.print(f"[red]{escape(str(error))}")


def parse_cli_vars(variables: list[str] | None) -> dict[str, str]:
def parse_cli_vars(variables: Optional[list[str]]) -> dict[str, str]:
if not variables:
return {}

Expand Down
3 changes: 2 additions & 1 deletion infrahub_sdk/ctl/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import sys
from pathlib import Path
from typing import Optional

import typer
import ujson
Expand Down Expand Up @@ -57,7 +58,7 @@ async def validate_schema(schema: Path, _: str = CONFIG_PARAM) -> None:
@catch_exception(console=console)
def validate_graphql(
query: str,
variables: list[str] | None = typer.Argument(
variables: Optional[list[str]] = typer.Argument(
None, help="Variables to pass along with the query. Format key=value key=value."
),
debug: bool = typer.Option(False, help="Display more troubleshooting information."),
Expand Down
9 changes: 3 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]

[tool.poetry.dependencies]
python = "^3.9"
python = "^3.9, < 3.13"
pydantic = ">=2.0.0,!=2.0.1,!=2.1.0,<3.0.0"
pydantic-settings = ">=2.0"
graphql-core = ">=3.1,<3.3"
Expand Down Expand Up @@ -248,6 +247,10 @@ max-complexity = 17
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
]

"infrahub_sdk/ctl/**/*.py" = [
"UP007", # Use `X | Y` for type annotations | Required for Typer until we can drop the support for Python 3.9
]

"infrahub_sdk/client.py" = [
##################################################################################################
# Review and change the below later #
Expand Down
11 changes: 0 additions & 11 deletions tests/unit/ctl/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import sys

import pytest
from typer.testing import CliRunner

from infrahub_sdk.ctl.cli import app

runner = CliRunner()

requires_python_310 = pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10 or higher")


@requires_python_310
def test_main_app():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
Expand All @@ -29,29 +23,25 @@ def test_validate_all_groups_have_names():
assert group.name


@requires_python_310
def test_version_command():
result = runner.invoke(app, ["version"])
assert result.exit_code == 0
assert "Python SDK: v" in result.stdout


@requires_python_310
def test_info_command_success(mock_query_infrahub_version, mock_query_infrahub_user):
result = runner.invoke(app, ["info"])
assert result.exit_code == 0
for expected in ["Connection Status", "Python Version", "SDK Version", "Infrahub Version"]:
assert expected in result.stdout, f"'{expected}' not found in info command output"


@requires_python_310
def test_info_command_failure():
result = runner.invoke(app, ["info"])
assert result.exit_code == 0
assert "Connection Error" in result.stdout


@requires_python_310
def test_info_detail_command_success(mock_query_infrahub_version, mock_query_infrahub_user):
result = runner.invoke(app, ["info", "--detail"])
assert result.exit_code == 0
Expand All @@ -65,7 +55,6 @@ def test_info_detail_command_success(mock_query_infrahub_version, mock_query_inf
assert expected in result.stdout, f"'{expected}' not found in detailed info command output"


@requires_python_310
def test_info_detail_command_failure():
result = runner.invoke(app, ["info", "--detail"])
assert result.exit_code == 0
Expand Down
8 changes: 0 additions & 8 deletions tests/unit/ctl/test_repository_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Integration tests for infrahubctl commands."""

import sys
from unittest import mock

import pytest
Expand All @@ -13,8 +12,6 @@

runner = CliRunner()

requires_python_310 = pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10 or higher")


@pytest.fixture
def mock_client() -> mock.Mock:
Expand All @@ -29,7 +26,6 @@ def mock_client() -> mock.Mock:
class TestInfrahubctlRepository:
"""Groups the 'infrahubctl repository' test cases."""

@requires_python_310
@mock.patch("infrahub_sdk.ctl.repository.initialize_client")
def test_repo_no_username_or_password(self, mock_init_client, mock_client) -> None:
"""Case allow no username to be passed in and set it as None rather than blank string that fails."""
Expand Down Expand Up @@ -77,7 +73,6 @@ def test_repo_no_username_or_password(self, mock_init_client, mock_client) -> No
tracker="mutation-repository-create",
)

@requires_python_310
@mock.patch("infrahub_sdk.ctl.repository.initialize_client")
def test_repo_no_username(self, mock_init_client, mock_client) -> None:
"""Case allow no username to be passed in and set it as None rather than blank string that fails."""
Expand Down Expand Up @@ -137,7 +132,6 @@ def test_repo_no_username(self, mock_init_client, mock_client) -> None:
tracker="mutation-repository-create",
)

@requires_python_310
@mock.patch("infrahub_sdk.ctl.repository.initialize_client")
def test_repo_username(self, mock_init_client, mock_client) -> None:
"""Case allow no username to be passed in and set it as None rather than blank string that fails."""
Expand Down Expand Up @@ -199,7 +193,6 @@ def test_repo_username(self, mock_init_client, mock_client) -> None:
tracker="mutation-repository-create",
)

@requires_python_310
@mock.patch("infrahub_sdk.ctl.repository.initialize_client")
def test_repo_readonly_true(self, mock_init_client, mock_client) -> None:
"""Case allow no username to be passed in and set it as None rather than blank string that fails."""
Expand Down Expand Up @@ -260,7 +253,6 @@ def test_repo_readonly_true(self, mock_init_client, mock_client) -> None:
tracker="mutation-repository-create",
)

@requires_python_310
@mock.patch("infrahub_sdk.ctl.repository.initialize_client")
def test_repo_description_commit_branch(self, mock_init_client, mock_client) -> None:
"""Case allow no username to be passed in and set it as None rather than blank string that fails."""
Expand Down
7 changes: 0 additions & 7 deletions tests/unit/ctl/test_validate_app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import sys

import pytest
from typer.testing import CliRunner

Expand All @@ -9,10 +7,7 @@

runner = CliRunner()

requires_python_310 = pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10 or higher")


@requires_python_310
def test_validate_schema_valid():
fixture_file = get_fixtures_dir() / "models" / "valid_model_01.json"

Expand All @@ -21,7 +16,6 @@ def test_validate_schema_valid():
assert "Schema is valid" in result.stdout


@requires_python_310
def test_validate_schema_empty():
fixture_file = get_fixtures_dir() / "models" / "empty.json"

Expand All @@ -30,7 +24,6 @@ def test_validate_schema_empty():
assert "Empty YAML/JSON file" in remove_ansi_color(result.stdout)


@requires_python_310
def test_validate_schema_non_valid():
fixture_file = get_fixtures_dir() / "models" / "non_valid_model_01.json"

Expand Down