-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rearrange code and minor improvements
- Loading branch information
1 parent
9a28690
commit 6de1bee
Showing
12 changed files
with
4,547 additions
and
443 deletions.
There are no files selected for viewing
2,862 changes: 2,839 additions & 23 deletions
2,862
python-avd/pyavd/_eos_cli_config_gen/schema/__init__.py
Large diffs are not rendered by default.
Oops, something went wrong.
1,286 changes: 1,275 additions & 11 deletions
1,286
python-avd/pyavd/_eos_designs/schema/__init__.py
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.