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
1 change: 1 addition & 0 deletions .yamllint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extends: default
ignore: |
/.venv
/examples
tests/unit/sdk/test_data/schema_encoding_error.yml

rules:
new-lines: disable
Expand Down
1 change: 1 addition & 0 deletions changelog/102.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CTL: Return friendly error on encoding violations when reading files.
13 changes: 13 additions & 0 deletions infrahub_sdk/ctl/_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pathlib import Path

from .exceptions import FileNotValidError


def read_file(file_name: Path) -> str:
if not file_name.is_file():
raise FileNotValidError(name=str(file_name), message=f"{file_name} is not a valid file")
try:
with Path.open(file_name, encoding="utf-8") as fobj:
return fobj.read()
except UnicodeDecodeError as exc:
raise FileNotValidError(name=str(file_name), message=f"Unable to read {file_name} with utf-8 encoding") from exc
5 changes: 2 additions & 3 deletions infrahub_sdk/ctl/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ..ctl.utils import init_logging
from ..graphql import Mutation
from ..schema import InfrahubRepositoryConfig
from ._file import read_file
from .parameters import CONFIG_PARAM

app = AsyncTyper()
Expand Down Expand Up @@ -40,11 +41,9 @@


def load_repository_config_file(repo_config_file: Path) -> dict:
if not repo_config_file.is_file():
raise FileNotFoundError(repo_config_file)
yaml_data = read_file(file_name=repo_config_file)

Check warning on line 44 in infrahub_sdk/ctl/repository.py

View check run for this annotation

Codecov / codecov/patch

infrahub_sdk/ctl/repository.py#L44

Added line #L44 was not covered by tests

try:
yaml_data = repo_config_file.read_text()
data = yaml.safe_load(yaml_data)
except yaml.YAMLError as exc:
raise FileNotValidError(name=str(repo_config_file)) from exc
Expand Down
15 changes: 7 additions & 8 deletions infrahub_sdk/ctl/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@

import typer
import ujson
import yaml
from pydantic import ValidationError
from rich.console import Console
from ujson import JSONDecodeError

from ..async_typer import AsyncTyper
from ..ctl.client import initialize_client, initialize_client_sync
from ..ctl.exceptions import QueryNotFoundError
from ..ctl.utils import catch_exception, find_graphql_query, parse_cli_vars
from ..exceptions import GraphQLError
from ..utils import get_branch, write_to_file
from ..yaml import SchemaFile
from .parameters import CONFIG_PARAM
from .utils import load_yamlfile_from_disk_and_exit

app = AsyncTyper()
console = Console()
Expand All @@ -33,16 +33,15 @@
async def validate_schema(schema: Path, _: str = CONFIG_PARAM) -> None:
"""Validate the format of a schema file either in JSON or YAML"""

try:
schema_data = yaml.safe_load(schema.read_text()) or {}
except JSONDecodeError as exc:
console.print("[red]Invalid JSON file")
raise typer.Exit(1) from exc
schema_data = load_yamlfile_from_disk_and_exit(paths=[schema], file_type=SchemaFile, console=console)
if not schema_data:
console.print(f"[red]Unable to find {schema}")
raise typer.Exit(1)

Check warning on line 39 in infrahub_sdk/ctl/validate.py

View check run for this annotation

Codecov / codecov/patch

infrahub_sdk/ctl/validate.py#L38-L39

Added lines #L38 - L39 were not covered by tests

client = initialize_client()

try:
client.schema.validate(schema_data)
client.schema.validate(schema_data[0].payload)
except ValidationError as exc:
console.print(f"[red]Schema not valid, found {len(exc.errors())} error(s)")
for error in exc.errors():
Expand Down
14 changes: 11 additions & 3 deletions infrahub_sdk/yaml.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from enum import Enum
from pathlib import Path
from typing import Optional
from typing import Any, Optional

import yaml
from pydantic import BaseModel, Field
from typing_extensions import Self

from .ctl._file import read_file
from .ctl.exceptions import FileNotValidError
from .utils import find_files

Expand Down Expand Up @@ -37,7 +38,12 @@ class LocalFile(BaseModel):
class YamlFile(LocalFile):
def load_content(self) -> None:
try:
self.content = yaml.safe_load(self.location.read_text())
self.content = yaml.safe_load(read_file(self.location))
except FileNotValidError as exc:
self.error_message = exc.message
self.valid = False
return

except yaml.YAMLError:
self.error_message = "Invalid YAML/JSON file"
self.valid = False
Expand Down Expand Up @@ -94,4 +100,6 @@ def validate_content(self) -> None:


class SchemaFile(YamlFile):
pass
@property
def payload(self) -> dict[str, Any]:
return self.content or {}
2 changes: 1 addition & 1 deletion tests/unit/ctl/test_validate_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_validate_schema_empty():

result = runner.invoke(app=app, args=["schema", str(fixture_file)])
assert result.exit_code == 1
assert "'version' | Field required (missing)" in remove_ansi_color(result.stdout)
assert "Empty YAML/JSON file" in remove_ansi_color(result.stdout)


def test_validate_schema_non_valid():
Expand Down
Binary file added tests/unit/sdk/test_data/schema_encoding_error.yml
Binary file not shown.
21 changes: 21 additions & 0 deletions tests/unit/sdk/test_yaml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pathlib import Path

from infrahub_sdk.yaml import YamlFile

here = Path(__file__).parent.resolve()


def test_read_missing_file() -> None:
file = here / "test_data/i_do_not_exist.yml"
yaml_file = YamlFile(location=file)
yaml_file.load_content()
assert not yaml_file.valid
assert yaml_file.error_message == f"{file} is not a valid file"


def test_read_incorrect_encoding() -> None:
file = here / "test_data/schema_encoding_error.yml"
yaml_file = YamlFile(location=file)
yaml_file.load_content()
assert not yaml_file.valid
assert yaml_file.error_message == f"Unable to read {file} with utf-8 encoding"