Skip to content

Commit

Permalink
Rearrange code and minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
ClausHolbechArista committed Oct 22, 2024
1 parent 9a28690 commit 6de1bee
Show file tree
Hide file tree
Showing 12 changed files with 4,547 additions and 443 deletions.
2,862 changes: 2,839 additions & 23 deletions python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py

Large diffs are not rendered by default.

1,286 changes: 1,275 additions & 11 deletions python-avd/pyavd/_eos_designs/schema/__init__.py

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions python-avd/pyavd/_schema/coerce_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright (c) 2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
from __future__ import annotations

from collections.abc import Mapping, Sequence
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar

from .constants import ACCEPTED_COERCION_MAP

if TYPE_CHECKING:
from typing import TypeVar

T = TypeVar("T")
TT = TypeVar("TT")


def nullifiy_class(cls: type) -> type:
"""
Returns a subclass of the given class which has the extra attribute '_is_nullified'.
This class is used when the input for a dict or a list is None/null/none,
to be able to signal to the deepmerge/inherit methods that this is not the same as an unset variable.
"""

class NullifiedCls(cls):
_is_nullified: ClassVar[bool] = True

return NullifiedCls


def coerce_type(value: Any, target_type: type[T], list_items_type: type[TT] | None = None) -> T | list[TT]:
"""
Return a coerced variant of the given value to the target_type or for lists a list of the the list_items_type.
If the value is already of the correct type the value will be returned untouched.
If coercion cannot be done, the original value will be returned. The calling function should catch the wrong type if necessary.
"""
if value is None and (target_type is list or hasattr(target_type, "_is_avd_class")):
# None values are sometimes used to overwrite inherited profiles.
# This ensures we still follow the type hint of the class.
return nullifiy_class(target_type)()

# Special handling for lists since we need to check every item
if target_type is list:
if not isinstance(value, list) or list_items_type is None:
# Wrong type so we cannot coerce or just expecting a plain list so nothing to coerce.
return value

# We got a type with items types like list[str] so we coerce every list item accordingly and return as a new list.
return [coerce_type(item_value, list_items_type) for item_value in value]

if target_type is Any or isinstance(value, target_type):
pass

elif target_type in ACCEPTED_COERCION_MAP and isinstance(value, ACCEPTED_COERCION_MAP[target_type]):
try:
return target_type(value)
except ValueError:
# Returns original value (too many returns triggers linting violation)
pass

# Identify subclass of AvdModel without importing AvdModel (circular import)
elif hasattr(target_type, "_is_avd_model") and isinstance(value, Mapping):
return target_type._from_dict(value)

# Identify subclass of AvdIndexedList without importing AvdIndexedList (circular import)
elif hasattr(target_type, "_is_avd_collection") and isinstance(value, Sequence):
return target_type._from_list(value)

# Giving up and just returning the original value.
return value
269 changes: 0 additions & 269 deletions python-avd/pyavd/_schema/loader.py

This file was deleted.

3 changes: 3 additions & 0 deletions python-avd/pyavd/_schema/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
24 changes: 24 additions & 0 deletions python-avd/pyavd/_schema/models/avd_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
from __future__ import annotations

from copy import deepcopy
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing_extensions import Self


class AvdBase:
_is_avd_class: bool = True

def __eq__(self, other: object) -> bool:
"""Compare two instances of AvdBase by comparing their repr."""
if isinstance(other, self.__class__):
return repr(self) == repr(other)
return super().__eq__(other)

def _deepcopy(self) -> Self:
"""Return a copy including all nested models."""
return deepcopy(self)
Loading

0 comments on commit 6de1bee

Please sign in to comment.