# Copyright (C) 2012-2025 Ben Kurtovic <ben.kurtovic@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
This module contains the :class:`.StringMixIn` type, which implements the
interface for the ``str`` type in a dynamic manner.
"""
from __future__ import annotations
import sys
from collections.abc import Iterable, Iterator, Mapping
from typing import Any, SupportsIndex
__all__ = ["StringMixIn"]
def inheritdoc(method):
"""Set __doc__ of *method* to __doc__ of *method* in its parent class.
Since this is used on :class:`.StringMixIn`, the "parent class" used is
``str``. This function can be used as a decorator.
"""
method.__doc__ = getattr(str, method.__name__).__doc__
return method
[docs]
class StringMixIn:
"""Implement the interface for ``str`` in a dynamic manner.
To use this class, inherit from it and override the :meth:`__str__` method
to return the string representation of the object. The various string
methods will operate on the value of :meth:`__str__` instead of the
immutable ``self`` like the regular ``str`` type.
"""
# This is based on collections.UserString, but:
# - Requires overriding __str__ instead of setting .data
# - Returns new strings as strs instead of StringMixIns
__slots__ = ()
def __str__(self) -> str:
raise NotImplementedError()
def __bytes__(self) -> bytes:
return bytes(str(self), sys.getdefaultencoding())
def __repr__(self) -> str:
return repr(str(self))
def __lt__(self, other: str | StringMixIn) -> bool:
return str(self) < other
def __le__(self, other: str | StringMixIn) -> bool:
return str(self) <= other
def __eq__(self, other: Any) -> bool:
return str(self) == other
def __ne__(self, other: Any) -> bool:
return str(self) != other
def __gt__(self, other: str | StringMixIn) -> bool:
return str(self) > other
def __ge__(self, other: str | StringMixIn) -> bool:
return str(self) >= other
def __bool__(self) -> bool:
return bool(str(self))
def __len__(self) -> int:
return len(str(self))
def __iter__(self) -> Iterator[str]:
yield from str(self)
def __getitem__(self, key: SupportsIndex | slice) -> str:
return str(self)[key]
def __reversed__(self) -> Iterator[str]:
return reversed(str(self))
def __contains__(self, item: Any) -> bool:
return str(item) in str(self)
[docs]
@inheritdoc
def capitalize(self) -> str:
return str(self).capitalize()
[docs]
@inheritdoc
def casefold(self) -> str:
return str(self).casefold()
[docs]
@inheritdoc
def center(self, width: int, fillchar: str = " ") -> str:
return str(self).center(width, fillchar)
[docs]
@inheritdoc
def count(
self,
sub: str | StringMixIn,
start: SupportsIndex | None = None,
end: SupportsIndex | None = None,
) -> int:
if isinstance(sub, StringMixIn):
sub = str(sub)
return str(self).count(sub, start, end)
[docs]
@inheritdoc
def encode(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
return str(self).encode(encoding, errors)
[docs]
@inheritdoc
def endswith(
self,
suffix: str | tuple[str, ...],
start: SupportsIndex | None = None,
end: SupportsIndex | None = None,
) -> bool:
return str(self).endswith(suffix, start, end)
[docs]
@inheritdoc
def expandtabs(self, tabsize: int = 8) -> str:
return str(self).expandtabs(tabsize)
[docs]
@inheritdoc
def find(
self,
sub: str | StringMixIn,
start: SupportsIndex | None = None,
end: SupportsIndex | None = None,
) -> int:
if isinstance(sub, StringMixIn):
sub = str(sub)
return str(self).find(sub, start, end)
[docs]
@inheritdoc
def index(
self,
sub: str,
start: SupportsIndex | None = None,
end: SupportsIndex | None = None,
) -> int:
return str(self).index(sub, start, end)
[docs]
@inheritdoc
def isalpha(self) -> bool:
return str(self).isalpha()
[docs]
@inheritdoc
def isalnum(self) -> bool:
return str(self).isalnum()
[docs]
@inheritdoc
def isascii(self) -> bool:
return str(self).isascii()
[docs]
@inheritdoc
def isdecimal(self) -> bool:
return str(self).isdecimal()
[docs]
@inheritdoc
def isdigit(self) -> bool:
return str(self).isdigit()
[docs]
@inheritdoc
def isidentifier(self) -> bool:
return str(self).isidentifier()
[docs]
@inheritdoc
def islower(self) -> bool:
return str(self).islower()
[docs]
@inheritdoc
def isnumeric(self) -> bool:
return str(self).isnumeric()
[docs]
@inheritdoc
def isprintable(self) -> bool:
return str(self).isprintable()
[docs]
@inheritdoc
def isspace(self) -> bool:
return str(self).isspace()
[docs]
@inheritdoc
def istitle(self) -> bool:
return str(self).istitle()
[docs]
@inheritdoc
def isupper(self) -> bool:
return str(self).isupper()
[docs]
@inheritdoc
def join(self, seq: Iterable[str]) -> str:
return str(self).join(seq)
[docs]
@inheritdoc
def ljust(self, width: int, fillchar: str = " ") -> str:
return str(self).ljust(width, fillchar)
[docs]
@inheritdoc
def lower(self) -> str:
return str(self).lower()
[docs]
@inheritdoc
def lstrip(self, chars: str | None = None) -> str:
return str(self).lstrip(chars)
maketrans = str.maketrans
[docs]
@inheritdoc
def partition(self, sep: str) -> tuple[str, str, str]:
return str(self).partition(sep)
[docs]
@inheritdoc
def removeprefix(self, prefix: str | StringMixIn, /) -> str:
if isinstance(prefix, StringMixIn):
prefix = str(prefix)
return str(self).removeprefix(prefix)
[docs]
@inheritdoc
def removesuffix(self, suffix: str | StringMixIn, /) -> str:
if isinstance(suffix, StringMixIn):
suffix = str(suffix)
return str(self).removesuffix(suffix)
[docs]
@inheritdoc
def replace(
self,
old: str | StringMixIn,
new: str | StringMixIn,
/,
count: SupportsIndex = -1,
):
if isinstance(old, StringMixIn):
old = str(old)
if isinstance(new, StringMixIn):
new = str(new)
return str(self).replace(old, new, count)
[docs]
@inheritdoc
def rfind(
self,
sub: str | StringMixIn,
start: SupportsIndex | None = None,
end: SupportsIndex | None = None,
) -> int:
if isinstance(sub, StringMixIn):
sub = str(sub)
return str(self).rfind(sub, start, end)
[docs]
@inheritdoc
def rindex(
self,
sub: str,
start: SupportsIndex | None = None,
end: SupportsIndex | None = None,
) -> int:
return str(self).rindex(sub, start, end)
[docs]
@inheritdoc
def rjust(self, width: int, fillchar: str = " ") -> str:
return str(self).rjust(width, fillchar)
[docs]
@inheritdoc
def rpartition(self, sep: str) -> tuple[str, str, str]:
return str(self).rpartition(sep)
[docs]
@inheritdoc
def rstrip(self, chars: str | None = None) -> str:
return str(self).rstrip(chars)
[docs]
@inheritdoc
def split(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]:
return str(self).split(sep, maxsplit)
[docs]
@inheritdoc
def rsplit(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]:
return str(self).rsplit(sep, maxsplit)
[docs]
@inheritdoc
def splitlines(self, keepends: bool = False) -> list[str]:
return str(self).splitlines(keepends)
[docs]
@inheritdoc
def startswith(
self,
prefix: str | tuple[str, ...],
start: SupportsIndex | None = None,
end: SupportsIndex | None = None,
) -> bool:
return str(self).startswith(prefix, start, end)
[docs]
@inheritdoc
def strip(self, chars: str | None = None) -> str:
return str(self).strip(chars)
[docs]
@inheritdoc
def swapcase(self) -> str:
return str(self).swapcase()
[docs]
@inheritdoc
def title(self) -> str:
return str(self).title()
[docs]
@inheritdoc
def translate(self, *args: Any) -> str:
return str(self).translate(*args)
[docs]
@inheritdoc
def upper(self) -> str:
return str(self).upper()
[docs]
@inheritdoc
def zfill(self, width: int) -> str:
return str(self).zfill(width)