Skip to content

Commit b008784

Browse files
authored
Merge pull request #114 from opsmill/pog-friendly-error-on-encoding-exception-IHS-52
Return friendly errors on file encoding violations
2 parents 2e04802 + bcedf52 commit b008784

File tree

9 files changed

+57
-15
lines changed

9 files changed

+57
-15
lines changed

.yamllint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ extends: default
44
ignore: |
55
/.venv
66
/examples
7+
tests/unit/sdk/test_data/schema_encoding_error.yml
78
89
rules:
910
new-lines: disable

changelog/102.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CTL: Return friendly error on encoding violations when reading files.

infrahub_sdk/ctl/_file.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from pathlib import Path
2+
3+
from .exceptions import FileNotValidError
4+
5+
6+
def read_file(file_name: Path) -> str:
7+
if not file_name.is_file():
8+
raise FileNotValidError(name=str(file_name), message=f"{file_name} is not a valid file")
9+
try:
10+
with Path.open(file_name, encoding="utf-8") as fobj:
11+
return fobj.read()
12+
except UnicodeDecodeError as exc:
13+
raise FileNotValidError(name=str(file_name), message=f"Unable to read {file_name} with utf-8 encoding") from exc

infrahub_sdk/ctl/repository.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ..ctl.utils import init_logging
1212
from ..graphql import Mutation
1313
from ..schema import InfrahubRepositoryConfig
14+
from ._file import read_file
1415
from .parameters import CONFIG_PARAM
1516

1617
app = AsyncTyper()
@@ -40,11 +41,9 @@ def get_repository_config(repo_config_file: Path) -> InfrahubRepositoryConfig:
4041

4142

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

4646
try:
47-
yaml_data = repo_config_file.read_text()
4847
data = yaml.safe_load(yaml_data)
4948
except yaml.YAMLError as exc:
5049
raise FileNotValidError(name=str(repo_config_file)) from exc

infrahub_sdk/ctl/validate.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44

55
import typer
66
import ujson
7-
import yaml
87
from pydantic import ValidationError
98
from rich.console import Console
10-
from ujson import JSONDecodeError
119

1210
from ..async_typer import AsyncTyper
1311
from ..ctl.client import initialize_client, initialize_client_sync
1412
from ..ctl.exceptions import QueryNotFoundError
1513
from ..ctl.utils import catch_exception, find_graphql_query, parse_cli_vars
1614
from ..exceptions import GraphQLError
1715
from ..utils import get_branch, write_to_file
16+
from ..yaml import SchemaFile
1817
from .parameters import CONFIG_PARAM
18+
from .utils import load_yamlfile_from_disk_and_exit
1919

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

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

4241
client = initialize_client()
4342

4443
try:
45-
client.schema.validate(schema_data)
44+
client.schema.validate(schema_data[0].payload)
4645
except ValidationError as exc:
4746
console.print(f"[red]Schema not valid, found {len(exc.errors())} error(s)")
4847
for error in exc.errors():

infrahub_sdk/yaml.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from enum import Enum
22
from pathlib import Path
3-
from typing import Optional
3+
from typing import Any, Optional
44

55
import yaml
66
from pydantic import BaseModel, Field
77
from typing_extensions import Self
88

9+
from .ctl._file import read_file
910
from .ctl.exceptions import FileNotValidError
1011
from .utils import find_files
1112

@@ -37,7 +38,12 @@ class LocalFile(BaseModel):
3738
class YamlFile(LocalFile):
3839
def load_content(self) -> None:
3940
try:
40-
self.content = yaml.safe_load(self.location.read_text())
41+
self.content = yaml.safe_load(read_file(self.location))
42+
except FileNotValidError as exc:
43+
self.error_message = exc.message
44+
self.valid = False
45+
return
46+
4147
except yaml.YAMLError:
4248
self.error_message = "Invalid YAML/JSON file"
4349
self.valid = False
@@ -94,4 +100,6 @@ def validate_content(self) -> None:
94100

95101

96102
class SchemaFile(YamlFile):
97-
pass
103+
@property
104+
def payload(self) -> dict[str, Any]:
105+
return self.content or {}

tests/unit/ctl/test_validate_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_validate_schema_empty():
2121

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

2626

2727
def test_validate_schema_non_valid():
328 Bytes
Binary file not shown.

tests/unit/sdk/test_yaml.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pathlib import Path
2+
3+
from infrahub_sdk.yaml import YamlFile
4+
5+
here = Path(__file__).parent.resolve()
6+
7+
8+
def test_read_missing_file() -> None:
9+
file = here / "test_data/i_do_not_exist.yml"
10+
yaml_file = YamlFile(location=file)
11+
yaml_file.load_content()
12+
assert not yaml_file.valid
13+
assert yaml_file.error_message == f"{file} is not a valid file"
14+
15+
16+
def test_read_incorrect_encoding() -> None:
17+
file = here / "test_data/schema_encoding_error.yml"
18+
yaml_file = YamlFile(location=file)
19+
yaml_file.load_content()
20+
assert not yaml_file.valid
21+
assert yaml_file.error_message == f"Unable to read {file} with utf-8 encoding"

0 commit comments

Comments
 (0)