From d4c507ed605d202da4ebb09bd61b8f4475b78c1f Mon Sep 17 00:00:00 2001 From: Zhiyuan Chen Date: Fri, 29 Dec 2023 10:21:00 +0000 Subject: [PATCH] fix merge property in rare cases Signed-off-by: Zhiyuan Chen --- chanfig/config.py | 5 +---- chanfig/flat_dict.py | 36 ++++++++++++++++-------------------- chanfig/functional.py | 4 ++-- chanfig/nested_dict.py | 21 ++++++++++----------- chanfig/registry.py | 4 ++-- chanfig/utils.py | 24 ++++++++++-------------- chanfig/variable.py | 2 +- 7 files changed, 42 insertions(+), 54 deletions(-) diff --git a/chanfig/config.py b/chanfig/config.py index 99549ba8..0d17fef9 100755 --- a/chanfig/config.py +++ b/chanfig/config.py @@ -20,10 +20,7 @@ from functools import wraps from typing import Any -try: - from typing import Self -except ImportError: - from typing_extensions import Self +from typing_extensions import Self from .nested_dict import NestedDict from .parser import ConfigParser diff --git a/chanfig/flat_dict.py b/chanfig/flat_dict.py index a3093db9..b6f50c4e 100644 --- a/chanfig/flat_dict.py +++ b/chanfig/flat_dict.py @@ -28,11 +28,7 @@ from typing import IO, Any from warnings import warn -try: - from typing import Self -except ImportError: - from typing_extensions import Self - +from typing_extensions import Self from yaml import dump as yaml_dump from yaml import load as yaml_load @@ -379,7 +375,7 @@ def getattr(self, name: str, default: Any = Null) -> Any: return self.__dict__[name] if name in self.__class__.__dict__: return self.__class__.__dict__[name] - return super().getattr(name, default) # type: ignore + return super().getattr(name, default) # type: ignore[misc] except AttributeError: if default is not Null: return default @@ -466,7 +462,7 @@ def hasattr(self, name: str) -> bool: try: if name in self.__dict__ or name in self.__class__.__dict__: return True - return super().hasattr(name) # type: ignore + return super().hasattr(name) # type: ignore[misc] except AttributeError: return False @@ -519,7 +515,7 @@ def from_dict(cls, obj: Mapping | Sequence) -> Any: # pylint: disable=R0911 if obj is None: return cls() if issubclass(cls, FlatDict): - cls = cls.empty # type: ignore # pylint: disable=W0642 + cls = cls.empty # type: ignore[assignment] # pylint: disable=W0642 if isinstance(obj, Mapping): return cls(obj) if isinstance(obj, Sequence): @@ -710,7 +706,7 @@ def merge(self, *args: Any, overwrite: bool = True, **kwargs: Any) -> Self: if len(args) == 1: args = args[0] if isinstance(args, (PathLike, str, bytes)): - args = self.load(args) # type: ignore + args = self.load(args) # type: ignore[assignment] warn( "merge file is deprecated and maybe removed in a future release. Use `merge_from_file` instead.", PendingDeprecationWarning, @@ -850,7 +846,7 @@ def difference(self, other: Mapping | Iterable | PathStr) -> Self: if not isinstance(other, Iterable): raise TypeError(f"`other={other}` should be of type Mapping, Iterable or PathStr, but got {type(other)}.") return self.empty( - **{key: value for key, value in other if key not in self or self[key] != value} # type: ignore + **{key: value for key, value in other if key not in self or self[key] != value} # type: ignore[misc] ) def diff(self, other: Mapping | Iterable | PathStr, *args: Any, **kwargs: Any) -> Self: @@ -1029,7 +1025,7 @@ def clone(self, memo: Mapping | None = None) -> Self: return self.deepcopy(memo=memo) def save( # pylint: disable=W1113 - self, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore + self, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore[assignment] ) -> None: r""" Save `FlatDict` to file. @@ -1060,13 +1056,13 @@ def save( # pylint: disable=W1113 method = splitext(file)[-1][1:] extension = method.lower() if extension in YAML: - return self.yaml(file=file, *args, **kwargs) # type: ignore + return self.yaml(file=file, *args, **kwargs) # type: ignore[misc] # noqa: B026 if extension in JSON: - return self.json(file=file, *args, **kwargs) # type: ignore + return self.json(file=file, *args, **kwargs) # type: ignore[misc] # noqa: B026 raise TypeError(f"`file={file!r}` should be in {JSON} or {YAML}, but got {extension}.") def dump( # pylint: disable=W1113 - self, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore + self, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore[assignment] ) -> None: r""" Alias of [`save`][chanfig.FlatDict.save]. @@ -1075,7 +1071,7 @@ def dump( # pylint: disable=W1113 @classmethod def load( # pylint: disable=W1113 - cls, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore + cls, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore[assignment] ) -> Self: """ Load `FlatDict` from file. @@ -1149,7 +1145,7 @@ def from_json(cls, file: File, *args: Any, **kwargs: Any) -> Self: with cls.open(file) as fp: # pylint: disable=C0103 if isinstance(file, (IOBase, IO)): - return cls.from_jsons(fp.getvalue(), *args, **kwargs) # type: ignore + return cls.from_jsons(fp.getvalue(), *args, **kwargs) # type: ignore[union-attr] return cls.from_jsons(fp.read(), *args, **kwargs) def jsons(self, *args: Any, **kwargs: Any) -> str: @@ -1222,7 +1218,7 @@ def from_yaml(cls, file: File, *args: Any, **kwargs: Any) -> Self: kwargs.setdefault("Loader", YamlLoader) with cls.open(file) as fp: # pylint: disable=C0103 if isinstance(file, (IOBase, IO)): - return cls.from_yamls(fp.getvalue(), *args, **kwargs) # type: ignore + return cls.from_yamls(fp.getvalue(), *args, **kwargs) # type: ignore[union-attr] return cls.from_dict(yaml_load(fp, *args, **kwargs)) def yamls(self, *args: Any, **kwargs: Any) -> str: @@ -1304,11 +1300,11 @@ def open(file: File, *args: Any, encoding: str = "utf-8", **kwargs: Any) -> Gene yield file elif isinstance(file, (PathLike, str, bytes)): try: - file = open(file, *args, encoding=encoding, **kwargs) # type: ignore # noqa: SIM115 - yield file # type: ignore + file = open(file, *args, encoding=encoding, **kwargs) # type: ignore[call-overload] # noqa: SIM115 + yield file # type: ignore[misc] finally: with suppress(Exception): - file.close() # type: ignore + file.close() # type: ignore[union-attr] else: raise TypeError(f"expected str, bytes, os.PathLike, IO or IOBase, not {type(file).__name__}") diff --git a/chanfig/functional.py b/chanfig/functional.py index 44a38d98..b3e8626d 100644 --- a/chanfig/functional.py +++ b/chanfig/functional.py @@ -28,7 +28,7 @@ def save( # pylint: disable=W1113 - obj, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore + obj, file: File, method: str = None, *args: Any, **kwargs: Any # type: ignore[assignment] ) -> None: r""" Save `FlatDict` to file. @@ -62,7 +62,7 @@ def save( # pylint: disable=W1113 if method is None: if isinstance(file, IOBase): raise ValueError("`method` must be specified when saving to IO.") - method = splitext(file)[-1][1:] # type: ignore + method = splitext(file)[-1][1:] extension = method.lower() if extension in YAML: with FlatDict.open(file, mode="w") as fp: # pylint: disable=C0103 diff --git a/chanfig/nested_dict.py b/chanfig/nested_dict.py index ecfbae3e..54eeaea8 100644 --- a/chanfig/nested_dict.py +++ b/chanfig/nested_dict.py @@ -22,18 +22,15 @@ from os import PathLike from typing import Any +from typing_extensions import Self + try: from functools import cached_property # pylint: disable=C0412 except ImportError: try: - from backports.cached_property import cached_property # type: ignore + from backports.cached_property import cached_property # type: ignore[no-redef] except ImportError: - cached_property = property # type: ignore # pylint: disable=C0103 - -try: - from typing import Self -except ImportError: - from typing_extensions import Self + cached_property = property # type: ignore[misc, assignment] # pylint: disable=C0103 from .default_dict import DefaultDict from .flat_dict import FlatDict @@ -698,8 +695,7 @@ def _merge(this: FlatDict, that: Iterable, overwrite: bool = True) -> Mapping: return this if isinstance(that, Mapping): that = that.items() - context = this.converting() if isinstance(this, NestedDict) else nullcontext() - with context: + with this.converting() if isinstance(this, NestedDict) else nullcontext(): for key, value in that: if key in this and isinstance(this[key], Mapping): if isinstance(value, Mapping): @@ -709,8 +705,11 @@ def _merge(this: FlatDict, that: Iterable, overwrite: bool = True) -> Mapping: this.set(key, value) else: this[key] = value - elif key in dir(this) and isinstance(getattr(this.__class__, key), (property, cached_property)): - getattr(this, key).merge(value, overwrite=overwrite) + elif key in dir(this) and isinstance(getattr(this.__class__, key, None), (property, cached_property)): + if isinstance(getattr(this, key, None), FlatDict): + getattr(this, key).merge(value, overwrite=overwrite) + else: + setattr(this, key, value) elif overwrite or key not in this: if isinstance(this, NestedDict): this.set(key, value) diff --git a/chanfig/registry.py b/chanfig/registry.py index 530a26eb..82d54cdc 100644 --- a/chanfig/registry.py +++ b/chanfig/registry.py @@ -247,8 +247,8 @@ def build(self, name: str | Mapping, *args: Any, **kwargs: Any) -> Any: if isinstance(name, Mapping): name = deepcopy(name) - name, kwargs = name.pop("name"), dict(name, **kwargs) # type: ignore - return self.init(self.lookup(name), *args, **kwargs) # type: ignore + name, kwargs = name.pop("name"), dict(name, **kwargs) # type: ignore[attr-defined, arg-type] + return self.init(self.lookup(name), *args, **kwargs) # type: ignore[arg-type] GlobalRegistry = Registry() diff --git a/chanfig/utils.py b/chanfig/utils.py index 83a62ae6..aaa5c186 100644 --- a/chanfig/utils.py +++ b/chanfig/utils.py @@ -24,21 +24,17 @@ from os import PathLike from re import compile, findall # pylint: disable=W0622 from types import GetSetDescriptorType, ModuleType -from typing import IO, Any, Union, no_type_check +from typing import IO, Any, Union, get_args, get_origin, no_type_check +import typing_extensions from yaml import SafeDumper, SafeLoader -try: # python 3.8+ - import typing_extensions as typing -except ImportError: - import typing # type: ignore - try: # python 3.10+ from types import UnionType # pylint: disable=C0412 except ImportError: - UnionType = Union # type: ignore + UnionType = Union # type: ignore[misc, assignment] -GLOBAL_NS = {k: v for k, v in typing.__dict__.items() if not k.startswith("_")} +GLOBAL_NS = {k: v for k, v in typing_extensions.__dict__.items() if not k.startswith("_")} PY310_PLUS = sys.version_info >= (3, 10) PathStr = Union[PathLike, str, bytes] @@ -191,7 +187,7 @@ def get_annotations( # pylint: disable=all @no_type_check def isvalid(data: Any, expected_type: type) -> bool: - expected_origin = typing.get_origin(expected_type) + expected_origin = get_origin(expected_type) if expected_origin not in ( Callable, GetSetDescriptorType, @@ -200,20 +196,20 @@ def isvalid(data: Any, expected_type: type) -> bool: None, ): if issubclass(expected_origin, Sequence): - inner_type = typing.get_args(expected_type)[0] + inner_type = get_args(expected_type)[0] return isinstance(data, expected_origin) and all(isinstance(item, inner_type) for item in data) if issubclass(expected_origin, Mapping): - key_type, value_type = typing.get_args(expected_type) + key_type, value_type = get_args(expected_type) return isinstance(data, expected_origin) and all( isinstance(key, key_type) and isinstance(value, value_type) for key, value in data.items() ) raise TypeError(f"Expected type {expected_type} is not supported.") if expected_origin is UnionType and not PY310_PLUS: - return any(isinstance(data, inner_type) for inner_type in typing.get_args(expected_type)) + return any(isinstance(data, inner_type) for inner_type in get_args(expected_type)) return isinstance(data, expected_type) -class Dict(type(dict)): # type: ignore +class Dict(type(dict)): # type: ignore[misc] def __call__(cls, *args: Any, **kwargs: Any) -> Any: instance = super().__call__(*args, **kwargs) instance.__post_init__() @@ -230,7 +226,7 @@ class Singleton(type): def __call__(cls, *args: Any, **kwargs: Any): if cls not in cls.__instances__: - cls.__instances__[cls] = super().__call__(*args, **kwargs) # type: ignore + cls.__instances__[cls] = super().__call__(*args, **kwargs) # type: ignore[index] return cls.__instances__[cls] diff --git a/chanfig/variable.py b/chanfig/variable.py index c68945f6..f3e14150 100644 --- a/chanfig/variable.py +++ b/chanfig/variable.py @@ -118,7 +118,7 @@ def __init__( # pylint: disable=R0913 self._required = required self._help = help - @property # type: ignore + @property # type: ignore[misc] def __class__(self) -> type: return self.value.__class__ if self.wrap_type else type(self)