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
14 changes: 12 additions & 2 deletions pyaml/validation/schema_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import inspect
import logging
import types
from abc import ABC
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from enum import Enum
Expand All @@ -22,6 +23,15 @@

RESERVED_CONFIGURATION_FIELDS = {"class_path"}

# Types that should not be included in the registry when
# registing classes by walking a class's MRO.
EXCLUDED_TYPES: set[object] = {
object,
BaseModel,
ConfigurationSchema,
ABC,
}

SUPPORTED_TYPES = (
int,
str,
Expand Down Expand Up @@ -80,7 +90,7 @@ def _extract_source_bases(source: type) -> list[type]:
bases: list[type] = []

for base in source.__mro__[1:]:
if base in (object, BaseModel, ConfigurationSchema):
if base in EXCLUDED_TYPES:
continue
bases.append(base)

Expand Down Expand Up @@ -222,7 +232,7 @@ def _resolve_annotation(annotation: Any) -> Any:
generic types are resolved recursively into equivalent type hints.
"""

if annotation is inspect._empty:
if annotation is inspect._empty or annotation is Any:
return Any

if annotation is None:
Expand Down
20 changes: 20 additions & 0 deletions tests/validation/test_schema_builder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests of the schema builder."""

import inspect
from abc import ABC
from collections.abc import Generator
from enum import Enum
from typing import Annotated, Any, Literal, get_args, get_origin
Expand All @@ -11,6 +12,7 @@
from pyaml.validation.configuration_models import ConfigurationSchema
from pyaml.validation.registry import SchemaRegistry
from pyaml.validation.schema_builder import (
EXCLUDED_TYPES,
_configuration_schema_from_basemodel,
_field_definition_from_field_info,
_fields_from_constructor_signature,
Expand Down Expand Up @@ -189,6 +191,7 @@ def __init__(self, class_path: str):

def test_resolve_annotation():
assert _resolve_annotation(inspect._empty) is Any
assert _resolve_annotation(Any) is Any
assert _resolve_annotation(None) is type(None)
assert _resolve_annotation(int) is int
assert _resolve_annotation(Color) is Color
Expand All @@ -206,6 +209,23 @@ def test_resolve_annotation():
assert get_args(annotated) == (int, "meta")


def test_excluded_framework_bases_are_not_registered():
class FrameworkConfig(ABC):
pass

class UserConfig(FrameworkConfig):
pass

EXCLUDED_TYPES.add(FrameworkConfig)
try:
generate_configuration_schema(UserConfig)
registry = SchemaRegistry()
assert f"{FrameworkConfig.__module__}.{FrameworkConfig.__name__}" not in registry
assert f"{UserConfig.__module__}.{UserConfig.__name__}" in registry
finally:
EXCLUDED_TYPES.remove(FrameworkConfig)


def test_resolve_annotation_rejects_forward_reference():
with pytest.raises(TypeError, match="Forward references"):
_resolve_annotation("SomeClass")
Expand Down
Loading