From 4c74be6a8731a5100bacc832f830b8d9cf1bcf02 Mon Sep 17 00:00:00 2001 From: Abby Austin <38941820+spidertyler2005@users.noreply.github.com> Date: Sat, 18 Jul 2026 22:28:37 -0400 Subject: [PATCH 1/6] small typing changes --- comprehensiveconfig/__init__.py | 33 +++++--------- comprehensiveconfig/spec.py | 81 ++++++--------------------------- 2 files changed, 26 insertions(+), 88 deletions(-) diff --git a/comprehensiveconfig/__init__.py b/comprehensiveconfig/__init__.py index 7e8aec6..5e85411 100644 --- a/comprehensiveconfig/__init__.py +++ b/comprehensiveconfig/__init__.py @@ -1,4 +1,3 @@ -from abc import ABCMeta import os from typing import Any, Self, Type, Union @@ -9,7 +8,7 @@ from .toml import TomlWriter -class _ConfigSpecMeta(type): +class _ConfigSpecMeta(spec.ConfigSectionMeta): """handles getting of attributes""" _WRITER: Type[configio.ConfigurationWriter] | None @@ -22,24 +21,20 @@ def __new__( cls, name, bases, - attrs, + namespace, default_file: str | None = None, # automatically load a specific file writer=None, create_file: bool = False, # create the default file if not exists auto_load: bool = True, **kwargs, ): - cls._DEFAULT_FILE = default_file - cls._WRITER = writer - cls._INST = None - cls._CREATE_FILE = create_file - cls._AUTO_LOAD = auto_load - return super().__new__(cls, name, bases, attrs) - - def __get__(self, instance, owner): - if self._INST is None: - return self - return self._INST + namespace["_INST"] = None + namespace["_DEFAULT_FILE"] = default_file + namespace["_WRITER"] = writer + namespace["_CREATE_FILE"] = create_file + namespace["_AUTO_LOAD"] = auto_load + + return super().__new__(cls, name, bases, namespace) def __getattribute__(self, name): """get attributes from active instance if available""" @@ -58,18 +53,14 @@ def __setattr__(self, name, value): return super().__setattr__(name, value) -class _ConfigSpecABCMeta(spec.ConfigurationFieldABCMeta, _ConfigSpecMeta): - """A combination of ABCMeta and config spec meta""" - - -class ConfigSpec(spec.Section, metaclass=_ConfigSpecABCMeta): +class ConfigSpec(spec.Section, metaclass=_ConfigSpecMeta): @classmethod def __init_subclass__( cls, **kwargs, ): - super().__init_subclass__(**kwargs) + super().__init_subclass__() if not cls._AUTO_LOAD: return @@ -84,6 +75,7 @@ def __init_subclass__( cls._INST = cls(cls._WRITER.load(cls._DEFAULT_FILE)) if not exists and cls._CREATE_FILE: default = cls() + print(cls._WRITER) cls._WRITER.dump(cls._DEFAULT_FILE, default) cls._INST = default if not exists and not cls._CREATE_FILE: @@ -146,7 +138,6 @@ def reset_global(cls): __all__ = [ "ConfigSpec", "_ConfigSpecMeta", - "_ConfigSpecABCMeta", "spec", "validators", "configio", diff --git a/comprehensiveconfig/spec.py b/comprehensiveconfig/spec.py index 9a29671..634b590 100644 --- a/comprehensiveconfig/spec.py +++ b/comprehensiveconfig/spec.py @@ -60,9 +60,6 @@ class BaseConfigurationField(ABC): __slots__ = ("_field_variable", "_parent", "_value") - _parent: Type["BaseConfigurationField"] | None - """The parent to this node""" - _field_variable: None | str """The python variable that this field is attached to""" @@ -91,6 +88,8 @@ class ConfigurationField[T](BaseConfigurationField): "_inline_doc", ) + _parent: "ConfigSectionMeta | None" + """The parent to this node""" _name: None | str """The actual name used inside the configuration This has to be valid for whatever config format you use""" @@ -181,18 +180,19 @@ def __set__(self, instance, value): instance._instance_parent = value -class Section(BaseConfigurationField, metaclass=ConfigurationFieldABCMeta): - """A baseclass for sections to be defined""" - - __slots__ = "_value" - +class ConfigSectionMeta(ConfigurationFieldABCMeta): _FIELDS: dict[str, ConfigurationField] - _SECTIONS: dict[str, Type] - _ALL_FIELDS: dict[str, AnyConfigField | Type] + _SECTIONS: dict[str, "ConfigSectionMeta"] + _ALL_FIELDS: dict[str, "ConfigurationField | ConfigSectionMeta"] _FIELD_NAME_MAP: dict[str, str] """Maps config names to their actual variable names""" _FIELD_VAR_MAP: dict[str, str] """Maps variable names to their actual config names""" + +class Section(BaseConfigurationField, metaclass=ConfigSectionMeta): + """A baseclass for sections to be defined""" + + __slots__ = "_value" _name = SectionName() """The name in the configuration file (chooses between _real_name and _cls_name)""" _cls_name: str @@ -220,14 +220,14 @@ def __init_subclass__(cls, name: str | None = None, **kwargs): cls._SECTIONS = { field_name: field for field_name, field in cls.__dict__.items() - if isinstance(field, type) and Section in field.__mro__ + if isinstance(field, ConfigSectionMeta) and Section in field.__mro__ } cls._ALL_FIELDS = cls._FIELDS | cls._SECTIONS for name, field in cls._ALL_FIELDS.items(): field._field_variable = name if field._name is None: field._name = name - if isinstance(field, type): + if isinstance(field, ConfigSectionMeta): field._cls_parent = cls else: field._parent = cls @@ -330,64 +330,11 @@ def nullable(self): return False -class List[T](ConfigurationField): - """List field""" - - __slots__ = "inner_type" - - _holds: list[T] - - def __init__( - self, - default_value: list[T] = [], - /, - inner_type: AnyConfigField | None = None, - *args, - **kwargs, - ): - self.inner_type = fix_unions(inner_type) - - return super().__init__(default_value, *args, **kwargs) - - def __call__(self, value: list[T]) -> list[T]: - return [self.inner_type(val) for val in value] - - def __get__(self, instance, owner) -> list[T]: - return super().__get__(instance, owner) - - def __set__(self, instance, value: list[T]): - super().__set__(instance, value) - - def _validate_value(self, value: Any, name: str | None = None, /): - super()._validate_value(value) - if not isinstance(value, list): - raise ValueError( - f"Field: {name or self._name}\nValue was not a valid list: {value}" - ) - - match self.inner_type: - case None: - return - case type(): - raise ValueError(self.inner_type) - - case BaseConfigurationField(): - for c, item in enumerate(value): - self.inner_type._validate_value(item, f"{name or self._name}[{c}]") - - -class TableSpec(ConfigurationField, metaclass=ConfigurationFieldABCMeta): +class TableSpec(ConfigurationField, metaclass=ConfigSectionMeta): """A model/Table""" __slots__ = () - _FIELDS: dict[str, AnyConfigField] - _SECTIONS: dict[str, Type] - _ALL_FIELDS: dict[str, AnyConfigField | Type] - _FIELD_NAME_MAP: dict[str, str] - """Maps config names to their actual variable names""" - _FIELD_VAR_MAP: dict[str, str] - """Maps variable names to their actual config names""" _cls_name: str """The actual name in the configuration file""" _cls_has_default: bool @@ -408,7 +355,7 @@ def __init_subclass__(cls, name: str | None = None, **kwargs): cls._SECTIONS = { field_name: field for field_name, field in cls.__dict__.items() - if isinstance(field, type) and Section in field.__mro__ + if isinstance(field, ConfigSectionMeta) and Section in field.__mro__ } cls._ALL_FIELDS = cls._FIELDS | cls._SECTIONS for name, field in cls._ALL_FIELDS.items(): From a65e67426c5c018e10bc988a3979abe3074d7e72 Mon Sep 17 00:00:00 2001 From: Abby Austin <38941820+spidertyler2005@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:26:19 -0400 Subject: [PATCH 2/6] Add a simple work around for type checkers that don't play nice. Also add warning about Mypy issues. --- comprehensiveconfig/__init__.py | 23 ++++++++++++++++++++--- comprehensiveconfig/toml.py | 2 +- docs/source/index.rst | 5 +++-- readme.md | 20 ++++++++++++++++++++ testing.py | 6 ++++-- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/comprehensiveconfig/__init__.py b/comprehensiveconfig/__init__.py index 5e85411..8e4d4b8 100644 --- a/comprehensiveconfig/__init__.py +++ b/comprehensiveconfig/__init__.py @@ -8,6 +8,17 @@ from .toml import TomlWriter +def autoloaded[T: "ConfigSpec"](cls: type[T]) -> T: + """Acts as a temporary fix to issues with autoloaded configuration classes in + certain type checkers. This was originally intended to fix mypy typechecking, + but mypy doesn't actually check class decorators.""" + if not cls._AUTO_LOAD: + raise ValueError("Decorator expects an autoloaded configuration class.") + if cls._INST is None: + raise ValueError("Instance was not loaded somehow.") + return cls + + class _ConfigSpecMeta(spec.ConfigSectionMeta): """handles getting of attributes""" @@ -60,7 +71,7 @@ def __init_subclass__( cls, **kwargs, ): - super().__init_subclass__() + super().__init_subclass__(**kwargs) if not cls._AUTO_LOAD: return @@ -75,7 +86,6 @@ def __init_subclass__( cls._INST = cls(cls._WRITER.load(cls._DEFAULT_FILE)) if not exists and cls._CREATE_FILE: default = cls() - print(cls._WRITER) cls._WRITER.dump(cls._DEFAULT_FILE, default) cls._INST = default if not exists and not cls._CREATE_FILE: @@ -88,7 +98,7 @@ def __init__(self, value: dict[str, Any] | None = None, /): super().__init__(value or self._default_value) @classmethod - def load(cls, file=None, writer=None, /) -> Self: + def load(cls, file=None, writer=None, /, create_file: bool = False) -> Self: file = file or cls._DEFAULT_FILE writer = writer or cls._WRITER @@ -97,6 +107,13 @@ def load(cls, file=None, writer=None, /) -> Self: if file is None: raise Exception("No file specified") + if cls._CREATE_FILE: + exists = os.path.exists(file) + if not exists: + inst = cls() + writer.dump(cls._DEFAULT_FILE, inst) + return inst + return cls(writer.load(file)) def save(self, file=None, writer=None, /): diff --git a/comprehensiveconfig/toml.py b/comprehensiveconfig/toml.py index 2a43c15..6486ab6 100644 --- a/comprehensiveconfig/toml.py +++ b/comprehensiveconfig/toml.py @@ -153,7 +153,7 @@ def dump_field(cls, field: spec.AnyConfigField, value) -> str: return cls.dump_table(table_node, value) case spec.Section(): return "\n".join(cls.dump_section(field)) - case spec.ConfigEnum(_, by_name): + case spec.ConfigEnum(_, __): return cls.dump_enum(field, value) case spec.ConfigurationField(): # magic method to make writing new field types possible diff --git a/docs/source/index.rst b/docs/source/index.rst index f8e0848..c0ace1e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -84,9 +84,10 @@ Module Load a specified file (or load default file with default writer) - .. py:method:: save(file=None, writer=None, /) + .. py:method:: save(file=None, writer=None, /, create_file=False) - Save a specified file (or save default file with default writer) + Save a specified file (or save default file with default writer). + Has the option to create the file if it does exist. .. py:method:: reset() diff --git a/readme.md b/readme.md index 44ae1e7..ebfc715 100644 --- a/readme.md +++ b/readme.md @@ -4,6 +4,26 @@ A simple configuration library that lets you create a pydantic-like model for your configuration. +# Autoloaded Configuration Classes (Typing Issues in Mypy) + +There is one particular pitfall I have yet to develop around. Mypy is missing a few essential checks that improve type checking on autoloaded configuration classes. For the time being, I recommend avoiding using mypy with this project until class-decorator annotations work [(something that hasn't worked since 2017)](https://github.com/python/mypy/issues/3135) or until I can manage to create a mypy plugin to work around the issue. Pyright doesn't seem believe anything is wrong. Pyright is the default typechecker used in vscode's pylance extension. + +If type checking issues occur on your autoloaded config classes, try using this temporary decorator as a fix: +```python +@autoloaded # makes your type checker think `MyConfig` is an instance of itself. (or at least it *should* do that.) +class MyConfig(ConfigSpec, default_file="test.toml", writer=TomlWriter, create_file=True): + ... # impl here +``` + +The other option is to just manually load your configuration. + +```python +class MyConfig(ConfigSpec, auto_load=False): + ... + +config = MyConfig.load("test.toml", TomlWriter, create_file=True) # load and/or create our config file +``` + # Installation `pip install comprehensiveconfig` diff --git a/testing.py b/testing.py index 305dec9..ce83146 100644 --- a/testing.py +++ b/testing.py @@ -1,6 +1,6 @@ from enum import Enum -from comprehensiveconfig import ConfigSpec +from comprehensiveconfig import ConfigSpec, autoloaded from comprehensiveconfig.json import JsonWriter from comprehensiveconfig.spec import ( Boolean, @@ -14,6 +14,7 @@ ConfigEnum, ) from comprehensiveconfig.toml import TomlWriter +import comprehensiveconfig.utility class Example(TableSpec): @@ -27,6 +28,7 @@ class testEnum(Enum): bar = "chicken" +@autoloaded class MyConfigSpec( ConfigSpec, default_file="test.toml", writer=TomlWriter, create_file=True ): @@ -66,7 +68,7 @@ class Credentials(Section, name="Credentials"): print(MyConfigSpec.some_field) print(MyConfigSpec.MySection.other_field) -MyConfigSpec.some_field = 12.2 +MyConfigSpec.some_field = 12.0 print("clump") print(MyConfigSpec.some_field) print(MyConfigSpec.MySection.other_field) From eac43de870c9bc8b2fcd72b44beca1b1c9c007d6 Mon Sep 17 00:00:00 2001 From: Abby Austin <38941820+spidertyler2005@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:51:20 -0400 Subject: [PATCH 3/6] Add better type annotation to `save()` --- comprehensiveconfig/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/comprehensiveconfig/__init__.py b/comprehensiveconfig/__init__.py index 8e4d4b8..bf031ca 100644 --- a/comprehensiveconfig/__init__.py +++ b/comprehensiveconfig/__init__.py @@ -116,7 +116,9 @@ def load(cls, file=None, writer=None, /, create_file: bool = False) -> Self: return cls(writer.load(file)) - def save(self, file=None, writer=None, /): + def save( + self, file=None, writer: Type[configio.ConfigurationWriter] | None = None, / + ): file = file or self._DEFAULT_FILE writer = writer or self._WRITER From fdee02e8098a43ed3ab5066524a30256bc95b40f Mon Sep 17 00:00:00 2001 From: Abby Austin <38941820+spidertyler2005@users.noreply.github.com> Date: Sun, 19 Jul 2026 00:05:44 -0400 Subject: [PATCH 4/6] Change ConfigUnion type annotations + add typechecking only _validate_value function on ConfigSectionMeta --- comprehensiveconfig/spec.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/comprehensiveconfig/spec.py b/comprehensiveconfig/spec.py index 634b590..0eba02d 100644 --- a/comprehensiveconfig/spec.py +++ b/comprehensiveconfig/spec.py @@ -129,7 +129,7 @@ def _validate_value(self, value: Any, name: str | None = None, /): if value is None and not self._nullable: raise ValueError(f'Field, "{name or self._name}", is not nullable') - def __or__(self, value: "type | AnyConfigField") -> "ConfigUnion": + def __or__(self, value: "ConfigSectionMeta | AnyConfigField") -> "ConfigUnion": return ConfigUnion(self, value) def __set_name__(self, owner, name): @@ -189,6 +189,11 @@ class ConfigSectionMeta(ConfigurationFieldABCMeta): _FIELD_VAR_MAP: dict[str, str] """Maps variable names to their actual config names""" + if typing.TYPE_CHECKING: + + def _validate_value(self, value: Any, name: str | None = None, /): ... + + class Section(BaseConfigurationField, metaclass=ConfigSectionMeta): """A baseclass for sections to be defined""" @@ -735,13 +740,13 @@ class ConfigUnion[L, R](ConfigurationField): _holds: L | R - _left_type: AnyConfigField | Type - _right_type: AnyConfigField | Type + _left_type: BaseConfigurationField | ConfigSectionMeta + _right_type: BaseConfigurationField | ConfigSectionMeta def __init__( self, - left_type: AnyConfigField | Type, - right_type: AnyConfigField | Type, + left_type: AnyConfigField | ConfigSectionMeta, + right_type: AnyConfigField | ConfigSectionMeta, *args, **kwargs, ): From f175d170adfaf10f4af12457356172771d88091e Mon Sep 17 00:00:00 2001 From: Abby Austin <38941820+spidertyler2005@users.noreply.github.com> Date: Sun, 19 Jul 2026 00:17:52 -0400 Subject: [PATCH 5/6] Move many attributes of `Section` to `ConfigSectionMeta` --- comprehensiveconfig/spec.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/comprehensiveconfig/spec.py b/comprehensiveconfig/spec.py index 0eba02d..25d7fa9 100644 --- a/comprehensiveconfig/spec.py +++ b/comprehensiveconfig/spec.py @@ -158,7 +158,7 @@ class SectionName: """Descriptor for section names. Chooses between class name and instance name automatically""" - def __get__(self, instance, owner): + def __get__(self, instance, owner) -> str: if instance is None: return object.__getattribute__(owner, "_cls_name") return object.__getattribute__(instance, "_instance_name") @@ -188,9 +188,19 @@ class ConfigSectionMeta(ConfigurationFieldABCMeta): """Maps config names to their actual variable names""" _FIELD_VAR_MAP: dict[str, str] """Maps variable names to their actual config names""" + _cls_parent: "BaseConfigurationField | ConfigSectionMeta | None" + _cls_name: str + """The name of the class""" + _field_variable: str + _has_default: bool + _default_value: dict[str, Any] | _NoDefaultValueT + _sorting_order: int if typing.TYPE_CHECKING: + _name = SectionName() + _parent = SectionParent() + def _validate_value(self, value: Any, name: str | None = None, /): ... @@ -200,15 +210,10 @@ class Section(BaseConfigurationField, metaclass=ConfigSectionMeta): __slots__ = "_value" _name = SectionName() """The name in the configuration file (chooses between _real_name and _cls_name)""" - _cls_name: str - """The name of the class""" _instance_name: str """The actual name in the configuration file""" - _has_default: bool - _default_value: dict[str, Any] | _NoDefaultValueT _parent = SectionParent() _instance_parent: AnyConfigField | None - _cls_parent: AnyConfigField | None _sorting_order = 1 @@ -249,7 +254,9 @@ def __init_subclass__(cls, name: str | None = None, **kwargs): cls._has_default = all(field._has_default for field in cls._ALL_FIELDS.values()) if cls._has_default: cls._default_value = { - field._name: field._default_value for field in cls._ALL_FIELDS.values() + field._name: field._default_value + for field in cls._ALL_FIELDS.values() + if field._name is not None } else: cls._default_value = NoDefaultValue @@ -387,7 +394,9 @@ def __init_subclass__(cls, name: str | None = None, **kwargs): ) if cls._cls_has_default: cls._cls_default_value = { - field._name: field._default_value for field in cls._ALL_FIELDS.values() + field._name: field._default_value + for field in cls._ALL_FIELDS.values() + if field._name is not None } else: cls._cls_default_value = NoDefaultValue From 5b82939e4bec2d2412c818fc715cdedffd57188e Mon Sep 17 00:00:00 2001 From: Abby Austin <38941820+spidertyler2005@users.noreply.github.com> Date: Sun, 19 Jul 2026 00:30:54 -0400 Subject: [PATCH 6/6] overload `__get__` in various places to be more accurate --- comprehensiveconfig/spec.py | 98 +++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/comprehensiveconfig/spec.py b/comprehensiveconfig/spec.py index 25d7fa9..fe52d52 100644 --- a/comprehensiveconfig/spec.py +++ b/comprehensiveconfig/spec.py @@ -5,7 +5,7 @@ import sys from types import UnionType import types -from typing import Any, Protocol, Self, Type, Union +from typing import Any, Protocol, Self, Type, Union, overload, override import typing from comprehensiveconfig.validators import ( @@ -138,7 +138,13 @@ def __set_name__(self, owner, name): if self._name is None: self._name = name - def __get__(self, instance, owner) -> T: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> T: ... + + def __get__(self, instance: "Section | None", owner) -> T | Self: if instance is None: return self # Retrieve the value from the instance's dictionary @@ -193,7 +199,7 @@ class ConfigSectionMeta(ConfigurationFieldABCMeta): """The name of the class""" _field_variable: str _has_default: bool - _default_value: dict[str, Any] | _NoDefaultValueT + _default_value: dict[str | None, Any] | _NoDefaultValueT _sorting_order: int if typing.TYPE_CHECKING: @@ -214,7 +220,7 @@ class Section(BaseConfigurationField, metaclass=ConfigSectionMeta): """The actual name in the configuration file""" _parent = SectionParent() _instance_parent: AnyConfigField | None - + _value: dict[str | None, Any] _sorting_order = 1 @classmethod @@ -395,8 +401,8 @@ def __init_subclass__(cls, name: str | None = None, **kwargs): if cls._cls_has_default: cls._cls_default_value = { field._name: field._default_value - for field in cls._ALL_FIELDS.values() - if field._name is not None + for field in cls._ALL_FIELDS.values() + if field._name is not None } else: cls._cls_default_value = NoDefaultValue @@ -459,7 +465,13 @@ def __call__(self, value: dict[K, V]) -> dict[K, V]: self._validate_value(value, self._name) return {self.key_type(key): self.value_type(val) for key, val in value.items()} - def __get__(self, instance, owner) -> dict[K, V]: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> dict[K, V]: ... + + def __get__(self, instance: "Section | None", owner) -> dict[K, V] | Self: return super().__get__(instance, owner) def __set__(self, instance, value: dict[K, V]): @@ -508,7 +520,13 @@ def __call__(self, value: list[T]) -> list[T]: self._validate_value(value, self._name) return [self.inner_type(val) for val in value] - def __get__(self, instance, owner) -> list[T]: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> list[T]: ... + + def __get__(self, instance: "Section | None", owner) -> list[T] | Self: return super().__get__(instance, owner) def __set__(self, instance, value: list[T]): @@ -539,7 +557,13 @@ class Boolean(ConfigurationField): _holds: bool - def __get__(self, instance, owner) -> bool: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> bool: ... + + def __get__(self, instance: "Section | None", owner) -> bool | Self: return super().__get__(instance, owner) def __set__(self, instance, value: bool): @@ -560,7 +584,13 @@ class Float(ConfigurationField): _holds: float - def __get__(self, instance, owner) -> float: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> float: ... + + def __get__(self, instance: "Section | None", owner) -> float | Self: return super().__get__(instance, owner) def __set__(self, instance, value: float): @@ -581,7 +611,13 @@ class Integer(ConfigurationField): _holds: int - def __get__(self, instance, owner) -> int: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> int: ... + + def __get__(self, instance: "Section | None", owner) -> int | Self: return super().__get__(instance, owner) def __set__(self, instance, value: int): @@ -619,7 +655,13 @@ def __init__( super().__init__(default_value, *args, **kwargs) self._regex_pattern = regex - def __get__(self, instance, owner) -> str: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> str: ... + + def __get__(self, instance: "Section | None", owner) -> str | Self: return super().__get__(instance, owner) def __set__(self, instance, value: str): @@ -687,7 +729,13 @@ def __init__( self._path_type = path_type self._path_validator = path_validator - def __get__(self, instance, owner) -> Path: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> Path: ... + + def __get__(self, instance: "Section | None", owner) -> Path | Self: return super().__get__(instance, owner) def __set__(self, instance, value: str | Path): @@ -772,7 +820,13 @@ def __call__(self, *args, **kwargs): except ValueError: # if left side fails, try the right return self._right_type(*args, **kwargs) - def __get__(self, instance, owner) -> L | R: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> L | R: ... + + def __get__(self, instance: "Section | None", owner) -> L | R | Self: return super().__get__(instance, owner) def __set__(self, instance, value: L | R): @@ -835,7 +889,13 @@ def get_value(self, value: Any): def __call__(self, value: Any): return self.get_value(value) - def __get__(self, instance, owner) -> T: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> T: ... + + def __get__(self, instance: "Section | None", owner) -> T | Self: return super().__get__(instance, owner) def __set__(self, instance, value: T | Any): @@ -898,7 +958,13 @@ def __call__(self, value: Any): return value return self._type.from_config(value) - def __get__(self, instance, owner) -> T: + @overload + def __get__(self, instance: None, owner) -> Self: ... + + @overload + def __get__(self, instance: "Section", owner) -> T: ... + + def __get__(self, instance: "Section | None", owner) -> T | Self: return super().__get__(instance, owner) def __set__(self, instance, value: T | Any):