Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor conda-forge.yml linting logic, hint about extra fields #1900

Closed
wants to merge 13 commits into from
1 change: 0 additions & 1 deletion conda_smithy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import sys
import time
import argparse
import io
import tempfile

from textwrap import dedent
Expand Down
2 changes: 1 addition & 1 deletion conda_smithy/configure_feedstock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2697,7 +2697,7 @@ def main(
import argparse

parser = argparse.ArgumentParser(
description=("Configure a feedstock given " "a conda-forge.yml file.")
description="Configure a feedstock given a conda-forge.yml file."
)
parser.add_argument(
"forge_file_directory",
Expand Down
87 changes: 87 additions & 0 deletions conda_smithy/lint_forge_yml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import json
from inspect import cleandoc
from textwrap import indent
from typing import List

import jsonschema
from pydantic import BaseModel

from conda_smithy.linting_types import Linter, LintsHints
from conda_smithy.schema import ConfigModel
from conda_smithy.validate_schema import validate_json_schema


def _format_validation_msg(error: jsonschema.ValidationError):
"""Use the data on the validation error to generate improved reporting.

If available, get the help URL from the first level of the JSON path:

$(.top_level_key.2nd_level_key)
"""
help_url = "https://conda-forge.org/docs/maintainer/conda_forge_yml"
path = error.json_path.split(".")
descriptionless_schema = {}
subschema_text = ""

if error.schema:
descriptionless_schema = {
k: v for (k, v) in error.schema.items() if k != "description"
}

if len(path) > 1:
help_url += f"""/#{path[1].split("[")[0].replace("_", "-")}"""
subschema_text = json.dumps(descriptionless_schema, indent=2)

return cleandoc(
f"""
In conda-forge.yml: [`{error.json_path}`]({help_url}) `=` `{error.instance}`.
{indent(error.message, " " * 12 + "> ")}
<details>
<summary>Schema</summary>

```json
{indent(subschema_text, " " * 12)}
```

</details>
"""
)


def lint_validate_json(forge_yaml: dict) -> LintsHints:
validation_lints, validation_hints = validate_json_schema(forge_yaml)

lints = [_format_validation_msg(lint) for lint in validation_lints]
hints = [_format_validation_msg(hint) for hint in validation_hints]

return LintsHints(lints, hints)


def lint_extra_fields(
forge_yaml: dict,
) -> LintsHints:
"""
Identify unexpected keys in the conda-forge.yml file.
This only works if extra="allow" is set in the Pydantic sub-model where the unexpected key is found.
"""

config = ConfigModel.model_validate(forge_yaml)
hints = []

def _find_extra_fields(model: BaseModel, prefix=""):
for extra_field in (model.__pydantic_extra__ or {}).keys():
hints.append(f"Unexpected key {prefix + extra_field}")

for field, value in model:
if isinstance(value, BaseModel):
_find_extra_fields(value, f"{prefix + field}.")

_find_extra_fields(config)

return LintsHints(hints=hints)


FORGE_YAML_LINTERS: List[Linter] = [
lint_validate_json,
lint_extra_fields,
]
Loading
Loading