Source code for typed_envs._env_var

from collections.abc import Hashable
from functools import lru_cache
from typing import Any, ClassVar, Generic, TypeVar, cast, final

from typing_extensions import override

T = TypeVar("T")


# NOTE: though this lib does create specialized subclasses, users should consider this class as @final
[docs] @final class EnvironmentVariable(Generic[T]): """ Base class for creating custom wrapper subclasses on the fly. Note: This is just a base class used to create custom wrapper subclasses on the fly. You must never initialize these directly. You must use the :func:`create_env` function either on the main module or on an :class:`EnvVarFactory` to initialize your env vars. Example: Useful for enhancing type hints and __repr__ of variables that hold values you set with env vars. Note: This is just a helper class to create custom wrapper classes on the fly. You should never initialize these directly. Functionally, :class:`EnvironmentVariable` objects will work exactly the same as any instance of specified `typ`. In the example below, `some_var` can be used just like any other `int` object. .. code-block:: python import typed_envs some_var = typed_envs.create_env("SET_WITH_THIS_ENV", int, 10) >>> isinstance(some_var, int) True >>> isinstance(some_var, EnvironmentVariable) True There are only 2 differences between `some_var` and `int(10)`: - `some_var` will properly type check as an instance of both `int` and :class:`EnvironmentVariable` - `some_var.__repr__()` will include contextual information about the :class:`EnvironmentVariable`. .. code-block:: python >>> some_var EnvironmentVariable[int](name=`SET_WITH_THIS_ENV`, default_value=10, using_default=True) >>> str(some_var) "10" >>> some_var + 5 15 >>> 20 / some_var 2 See Also: :func:`create_env`, :class:`EnvVarFactory` """ # TODO: give these docstrings _default_value: Any _using_default: bool _env_name: str _init_arg0: Any __args__: ClassVar[type[object]] __origin__: ClassVar[type["EnvironmentVariable[Any]"]]
[docs] def __init__(self, *args: Any, **kwargs: Any) -> None: if type(self) is EnvironmentVariable: raise RuntimeError( "You should not initialize these directly, please use the factory" ) try: super().__init__(*args, **kwargs) except TypeError as e: expected = "object.__init__() takes exactly one argument (the instance to initialize)" if str(e) != expected: raise
[docs] @override def __str__(self) -> str: base_type = type(self).__args__ string_from_base = base_type.__str__(self) # NOTE: If this returns True, base type's `__str__` method calls `__repr__` and our custom `__repr__` breaks it. if string_from_base == repr(self): # We broke it but it's all good, we can fix it with some special case logic. return str(bool(self)) if base_type is bool else base_type.__repr__(self) return ( base_type.__str__(self) if "object at 0x" in string_from_base else string_from_base )
[docs] @override def __repr__(self) -> str: base_type = type(self).__args__ if self._using_default: return "EnvironmentVariable[{}](name=`{}`, default_value={}, using_default=True)".format( base_type.__qualname__, self._env_name, self._default_value, ) else: return "EnvironmentVariable[{}](name=`{}`, default_value={}, current_value={})".format( base_type.__qualname__, self._env_name, self._default_value, self._init_arg0, )
[docs] def __class_getitem__(cls, type_arg: type[T]) -> type["EnvironmentVariable[T]"]: """ Returns a mixed subclass of `type_arg` and :class:`EnvironmentVariable` that does 2 things: - modifies the __repr__ method so its clear an object's value was set with an env var while when inspecting variables - ensures the instance will type check as an :class:`EnvironmentVariable` object without losing information about its actual type Aside from these two things, subclass instances will function exactly the same as any other instance of `typ`. """ if cls is EnvironmentVariable: return _build_subclass(type_arg) # type: ignore [arg-type] return cast( type["EnvironmentVariable[T]"], super().__class_getitem__(type_arg), # type: ignore [misc] )
# helpers for mypy
[docs] def __int__(self) -> int: return cast(int, NotImplemented)
@lru_cache(maxsize=None) def _build_subclass(type_arg: type[T]) -> type["EnvironmentVariable[T]"]: """ Returns a mixed subclass of `type_arg` and :class:`EnvironmentVariable` that does 2 things: - modifies the __repr__ method so its clear an object's value was set with an env var while when inspecting variables - ensures the instance will type check as an :class:`EnvironmentVariable` object without losing information about its actual type Aside from these two things, subclass instances will function exactly the same as any other instance of `typ`. """ from typed_envs import _typed return cast( type["EnvironmentVariable[T]"], _typed.build_subclass(cast(Hashable, type_arg)), )