| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- from __future__ import annotations
- import inspect
- import re
- import types
- from typing import Any
- def describe(
- article: str | None,
- value: Any,
- name: str | None = None,
- verbose: bool = False,
- capital: bool = False,
- ) -> str:
- """Return string that describes a value
- Parameters
- ----------
- article : str or None
- A definite or indefinite article. If the article is
- indefinite (i.e. "a" or "an") the appropriate one
- will be inferred. Thus, the arguments of ``describe``
- can themselves represent what the resulting string
- will actually look like. If None, then no article
- will be prepended to the result. For non-articled
- description, values that are instances are treated
- definitely, while classes are handled indefinitely.
- value : any
- The value which will be named.
- name : str or None (default: None)
- Only applies when ``article`` is "the" - this
- ``name`` is a definite reference to the value.
- By default one will be inferred from the value's
- type and repr methods.
- verbose : bool (default: False)
- Whether the name should be concise or verbose. When
- possible, verbose names include the module, and/or
- class name where an object was defined.
- capital : bool (default: False)
- Whether the first letter of the article should
- be capitalized or not. By default it is not.
- Examples
- --------
- Indefinite description:
- >>> describe("a", object())
- 'an object'
- >>> describe("a", object)
- 'an object'
- >>> describe("a", type(object))
- 'a type'
- Definite description:
- >>> describe("the", object())
- "the object at '...'"
- >>> describe("the", object)
- 'the object object'
- >>> describe("the", type(object))
- 'the type type'
- Definitely named description:
- >>> describe("the", object(), "I made")
- 'the object I made'
- >>> describe("the", object, "I will use")
- 'the object I will use'
- """
- if isinstance(article, str):
- article = article.lower()
- if not inspect.isclass(value):
- typename = type(value).__name__
- else:
- typename = value.__name__
- if verbose:
- typename = _prefix(value) + typename
- if article == "the" or (article is None and not inspect.isclass(value)):
- if name is not None:
- result = f"{typename} {name}"
- if article is not None:
- return add_article(result, True, capital)
- else:
- return result
- else:
- tick_wrap = False
- if inspect.isclass(value):
- name = value.__name__
- elif isinstance(value, types.FunctionType):
- name = value.__name__
- tick_wrap = True
- elif isinstance(value, types.MethodType):
- name = value.__func__.__name__
- tick_wrap = True
- elif type(value).__repr__ in (
- object.__repr__,
- type.__repr__,
- ): # type:ignore[comparison-overlap]
- name = "at '%s'" % hex(id(value))
- verbose = False
- else:
- name = repr(value)
- verbose = False
- if verbose:
- name = _prefix(value) + name
- if tick_wrap:
- name = name.join("''")
- return describe(article, value, name=name, verbose=verbose, capital=capital)
- elif article in ("a", "an") or article is None:
- if article is None:
- return typename
- return add_article(typename, False, capital)
- else:
- raise ValueError(
- "The 'article' argument should be 'the', 'a', 'an', or None not %r" % article
- )
- def _prefix(value: Any) -> str:
- if isinstance(value, types.MethodType):
- name = describe(None, value.__self__, verbose=True) + "."
- else:
- module = inspect.getmodule(value)
- if module is not None and module.__name__ != "builtins":
- name = module.__name__ + "."
- else:
- name = ""
- return name
- def class_of(value: Any) -> Any:
- """Returns a string of the value's type with an indefinite article.
- For example 'an Image' or 'a PlotValue'.
- """
- if inspect.isclass(value):
- return add_article(value.__name__)
- else:
- return class_of(type(value))
- def add_article(name: str, definite: bool = False, capital: bool = False) -> str:
- """Returns the string with a prepended article.
- The input does not need to begin with a character.
- Parameters
- ----------
- name : str
- Name to which to prepend an article
- definite : bool (default: False)
- Whether the article is definite or not.
- Indefinite articles being 'a' and 'an',
- while 'the' is definite.
- capital : bool (default: False)
- Whether the added article should have
- its first letter capitalized or not.
- """
- if definite:
- result = "the " + name
- else:
- first_letters = re.compile(r"[\W_]+").sub("", name)
- if first_letters[:1].lower() in "aeiou":
- result = "an " + name
- else:
- result = "a " + name
- if capital:
- return result[0].upper() + result[1:]
- else:
- return result
- def repr_type(obj: Any) -> str:
- """Return a string representation of a value and its type for readable
- error messages.
- """
- the_type = type(obj)
- return f"{obj!r} {the_type!r}"
|