Package optmagic

Written by Lucas Sinclair. MIT Licensed. Contact at www.sinclair.bio

Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Written by Lucas Sinclair.
MIT Licensed.
Contact at www.sinclair.bio
"""

# Constants #
__version__ = '1.1.0'

# Built-in modules #
import sys, argparse, types, inspect, functools, os.path

# Internal modules #
from optmagic.argument import Argument
from optmagic.pytest_action import PytestAction

# Third party modules #
import docstring_parser

###############################################################################
class OptMagic:
    """
    This class enables you to create simple command line interfaces starting
    with a class or a function that you want to expose directly to the shell.
    See documentation at https://github.com/xapple/optmagic/
    This project is similar in some ways to https://www.pyinvoke.org/
    """

    def __init__(self, function_or_class):
        """
        Args:

            function_or_class: You can pass either a class object or a function
                               object as the only parameter to OptMagic.

        Other:

            For debugging, you can set the special attribute `optmagic_argv`
            to a string of your choosing which will cause sys.argv to be
            ignored.
        """
        self.obj = function_or_class

    def __repr__(self):
        """A simple representation of this object to avoid memory addresses."""
        return "<%s object on '%s'>" % (self.__class__.__name__, self.obj)

    #----------------------------- Properties --------------------------------#
    @functools.cached_property
    def type(self):
        # Determine the type of the object to expose #
        if isinstance(self.obj, type):                 return 'class'
        elif isinstance(self.obj, types.FunctionType): return 'function'
        # Otherwise, raise an exception #
        else:
            msg = "OptMagic should be called with a function or a class but" \
                  " not with `%s`." % self.obj
            raise ValueError(msg)

    @functools.cached_property
    def func(self):
        # If it's a class we want to target the constructor #
        if self.type == 'class':    return self.obj.__init__
        if self.type == 'function': return self.obj

    @functools.cached_property
    def docstring(self):
        """
        If you use `inspect.getdoc(self.func)` it returns something slightly
        different.
        """
        return self.func.__doc__

    @functools.cached_property
    def sig(self):
        return inspect.signature(self.obj)

    @functools.cached_property
    def sub_docs(self):
        """
        The `docstring_parser` module is able to parse 'numpydoc' style
        docstrings amongst others formats.
        """
        return {param.arg_name: param.description
                for param in docstring_parser.parse(self.docstring).params}

    @functools.cached_property
    def arguments(self):
        # Create all Argument objects #
        result = [Argument(self,
                           param.name,
                           param.default,
                           self.sub_docs[param.name])
                  for param in self.sig.parameters.values()]
        # Return #
        return result

    #----------------------------- Parameters --------------------------------#
    @functools.cached_property
    def child_module(self):
        """The sub-module from which the object is coming from."""
        return inspect.getmodule(self.obj)

    @functools.cached_property
    def base_module(self):
        """The parent package from which the object is coming from."""
        name = self.child_module.__name__.split('.')[0]
        return __import__(name)

    @functools.cached_property
    def base_path(self):
        """The location of the package on the filesystem."""
        return os.path.dirname(inspect.getfile(self.base_module)) + '/'

    @functools.cached_property
    def prog_string(self):
        """
        This is the name of program that appears at the top of the help string.
        By default it should be name of the module from which the object comes
        from.
        """
        return self.base_module.__name__

    @functools.cached_property
    def usage_string(self):
        """
        This is the short description of the program that appears at the very
        top of the help string and summarizes all options.
        """
        return None

    @functools.cached_property
    def title_string(self):
        """
        A string that appears at the top of the help message.
        Just after the usage summary.
        """
        return self.base_module.__doc__

    @functools.cached_property
    def epilog_string(self):
        """
        This is an extra string that appears at the very end of the help
        message. We want to display the URL to the project page.
        """
        # Initialize #
        url = None
        # Search the child module #
        if hasattr(self.child_module, 'project_url'):
            url = self.child_module.project_url
        # Search the parent module #
        if hasattr(self.base_module, 'project_url'):
            url = self.base_module.project_url
        # Make the message #
        if url is not None:
            msg = "More information at " + url
            msg = '-'*75 + '\n| ' + msg + '\n' + '-'*75
            # Remove a new line from the last required argument #
            last_req = [arg for arg in self.arguments if not arg.has_default]
            if last_req: last_req[-1].help = last_req[-1].help[:-1]
            # Return #
            return msg

    @functools.cached_property
    def version_string(self):
        """The string returned when invoked with the '-v' option"""
        # Initialize #
        version = self.prog_string
        # Search for a version number #
        if hasattr(self.base_module, '__version__'):
            version += " version " + self.base_module.__version__
        elif hasattr(self.child_module, '__version__'):
            version += " version " + self.base_module.__version__
        # Return #
        return version

    #------------------------------- Objects ---------------------------------#
    @functools.cached_property
    def options(self):
        """
        An even better formatter class could be constructed based on:
        https://stackoverflow.com/a/65891304
        """
        # Formatter #
        from argparse import RawTextHelpFormatter
        # Options for the parser #
        return dict(prog            = self.prog_string,
                    usage           = self.usage_string,
                    description     = self.title_string,
                    epilog          = self.epilog_string,
                    allow_abbrev    = True,
                    add_help        = False,
                    formatter_class = RawTextHelpFormatter)

    @functools.cached_property
    def parser(self):
        # Create the parser #
        parser = argparse.ArgumentParser(**self.options)
        # Capitalize groups #
        parser._positionals.title = 'Positional arguments'
        parser._optionals.title   = 'Optional arguments'
        # Add a special group for required arguments #
        # See https://stackoverflow.com/questions/24180527
        if any([not arg.has_default for arg in self.arguments]):
            required = parser.add_argument_group('Required arguments')
        else:
            required = None
        # Iterate over arguments and offer up both groups #
        for arg in self.arguments: arg.add_arg(parser, required)
        # Add the version action #
        parser.add_argument('--version', '-v', action='version',
                            version=self.version_string,
                            help="Show program's version number and exit.")
        # Add the help action #
        parser.add_argument('--help', '-h', action='help',
                            default=argparse.SUPPRESS,
                            help='Show this help message and exit.')
        # Add the pytest action #
        parser.add_argument('--pytest', action=PytestAction,
                            help='Run the test suite and exit.',
                            default=self.base_path)
        # Return #
        return parser

    @functools.cached_property
    def parsed_args(self):
        # Check for debug mode #
        if hasattr(self, 'optmagic_argv'):
            import shlex
            argument_list = shlex.split(self.optmagic_argv)
            return self.parser.parse_args(argument_list)
        # Otherwise, call the parser normally #
        return self.parser.parse_args()

    @functools.cached_property
    def kwargs(self):
        return vars(self.parsed_args)

    #------------------------------- Methods ---------------------------------#
    def __call__(self, *extra_args, **extra_kwargs):
        # Call if it's a function #
        if self.type == 'function':
            return self.func(**self.kwargs)
        # Call if it's a class #
        if self.type == 'class':
            instance = self.obj(**self.kwargs)
            return instance(*extra_args, **extra_kwargs)

    #------------------------------- Extras ----------------------------------#
    @functools.cached_property
    def markdown(self):
        """
        #TODO
        Return a markdown version of the help string.
        Beware, this actually creates a file somewhere on the filesystem!
        """
        import argmark
        return argmark.md_help(self.parser)

###############################################################################
# The code below is used for debugging purposes
# It uses the test case found in the 'test/' directory.

# You can call it like this:
#   $ python3 -m optmagic --name=hello
#
# Or via ipython for interactiveness:
#   $ ipython3 -i -- __init__.py --name hello
#
# Or via the executable:
#   $ test/expose_the_class.py -n hello --max_speed 130
#
# Or inside ipython with these commands:
#    from optmagic import OptMagic; from simple_car_class import Car;
#    self = OptMagic(Car); self.optmagic_argv = "--name=hello"; self()

if __name__ == '__main__':
    # Get the current directory of this python script #
    this_file = (inspect.stack()[0])[1]
    this_dir  = os.path.dirname(this_file)
    # Get the path of our test class #
    class_file = this_dir + '/test/simple_car_class.py'
    # Import it #
    from importlib.machinery import SourceFileLoader
    module = SourceFileLoader("simple_car", class_file).load_module()
    # Create an object #
    self = OptMagic(module.Car)
    # Forward the arguments #
    self.optmagic_argv = ' '.join(sys.argv[1:])
    # Call #
    self()

Sub-modules

optmagic.argument

Written by Lucas Sinclair. MIT Licensed. Contact at www.sinclair.bio

optmagic.pytest_action

Written by Lucas Sinclair. MIT Licensed. Contact at www.sinclair.bio

optmagic.tests

Classes

class OptMagic (function_or_class)

This class enables you to create simple command line interfaces starting with a class or a function that you want to expose directly to the shell. See documentation at https://github.com/xapple/optmagic/ This project is similar in some ways to https://www.pyinvoke.org/

Args

function_or_class
You can pass either a class object or a function object as the only parameter to OptMagic.

Other

For debugging, you can set the special attribute optmagic_argv to a string of your choosing which will cause sys.argv to be ignored.

Expand source code
class OptMagic:
    """
    This class enables you to create simple command line interfaces starting
    with a class or a function that you want to expose directly to the shell.
    See documentation at https://github.com/xapple/optmagic/
    This project is similar in some ways to https://www.pyinvoke.org/
    """

    def __init__(self, function_or_class):
        """
        Args:

            function_or_class: You can pass either a class object or a function
                               object as the only parameter to OptMagic.

        Other:

            For debugging, you can set the special attribute `optmagic_argv`
            to a string of your choosing which will cause sys.argv to be
            ignored.
        """
        self.obj = function_or_class

    def __repr__(self):
        """A simple representation of this object to avoid memory addresses."""
        return "<%s object on '%s'>" % (self.__class__.__name__, self.obj)

    #----------------------------- Properties --------------------------------#
    @functools.cached_property
    def type(self):
        # Determine the type of the object to expose #
        if isinstance(self.obj, type):                 return 'class'
        elif isinstance(self.obj, types.FunctionType): return 'function'
        # Otherwise, raise an exception #
        else:
            msg = "OptMagic should be called with a function or a class but" \
                  " not with `%s`." % self.obj
            raise ValueError(msg)

    @functools.cached_property
    def func(self):
        # If it's a class we want to target the constructor #
        if self.type == 'class':    return self.obj.__init__
        if self.type == 'function': return self.obj

    @functools.cached_property
    def docstring(self):
        """
        If you use `inspect.getdoc(self.func)` it returns something slightly
        different.
        """
        return self.func.__doc__

    @functools.cached_property
    def sig(self):
        return inspect.signature(self.obj)

    @functools.cached_property
    def sub_docs(self):
        """
        The `docstring_parser` module is able to parse 'numpydoc' style
        docstrings amongst others formats.
        """
        return {param.arg_name: param.description
                for param in docstring_parser.parse(self.docstring).params}

    @functools.cached_property
    def arguments(self):
        # Create all Argument objects #
        result = [Argument(self,
                           param.name,
                           param.default,
                           self.sub_docs[param.name])
                  for param in self.sig.parameters.values()]
        # Return #
        return result

    #----------------------------- Parameters --------------------------------#
    @functools.cached_property
    def child_module(self):
        """The sub-module from which the object is coming from."""
        return inspect.getmodule(self.obj)

    @functools.cached_property
    def base_module(self):
        """The parent package from which the object is coming from."""
        name = self.child_module.__name__.split('.')[0]
        return __import__(name)

    @functools.cached_property
    def base_path(self):
        """The location of the package on the filesystem."""
        return os.path.dirname(inspect.getfile(self.base_module)) + '/'

    @functools.cached_property
    def prog_string(self):
        """
        This is the name of program that appears at the top of the help string.
        By default it should be name of the module from which the object comes
        from.
        """
        return self.base_module.__name__

    @functools.cached_property
    def usage_string(self):
        """
        This is the short description of the program that appears at the very
        top of the help string and summarizes all options.
        """
        return None

    @functools.cached_property
    def title_string(self):
        """
        A string that appears at the top of the help message.
        Just after the usage summary.
        """
        return self.base_module.__doc__

    @functools.cached_property
    def epilog_string(self):
        """
        This is an extra string that appears at the very end of the help
        message. We want to display the URL to the project page.
        """
        # Initialize #
        url = None
        # Search the child module #
        if hasattr(self.child_module, 'project_url'):
            url = self.child_module.project_url
        # Search the parent module #
        if hasattr(self.base_module, 'project_url'):
            url = self.base_module.project_url
        # Make the message #
        if url is not None:
            msg = "More information at " + url
            msg = '-'*75 + '\n| ' + msg + '\n' + '-'*75
            # Remove a new line from the last required argument #
            last_req = [arg for arg in self.arguments if not arg.has_default]
            if last_req: last_req[-1].help = last_req[-1].help[:-1]
            # Return #
            return msg

    @functools.cached_property
    def version_string(self):
        """The string returned when invoked with the '-v' option"""
        # Initialize #
        version = self.prog_string
        # Search for a version number #
        if hasattr(self.base_module, '__version__'):
            version += " version " + self.base_module.__version__
        elif hasattr(self.child_module, '__version__'):
            version += " version " + self.base_module.__version__
        # Return #
        return version

    #------------------------------- Objects ---------------------------------#
    @functools.cached_property
    def options(self):
        """
        An even better formatter class could be constructed based on:
        https://stackoverflow.com/a/65891304
        """
        # Formatter #
        from argparse import RawTextHelpFormatter
        # Options for the parser #
        return dict(prog            = self.prog_string,
                    usage           = self.usage_string,
                    description     = self.title_string,
                    epilog          = self.epilog_string,
                    allow_abbrev    = True,
                    add_help        = False,
                    formatter_class = RawTextHelpFormatter)

    @functools.cached_property
    def parser(self):
        # Create the parser #
        parser = argparse.ArgumentParser(**self.options)
        # Capitalize groups #
        parser._positionals.title = 'Positional arguments'
        parser._optionals.title   = 'Optional arguments'
        # Add a special group for required arguments #
        # See https://stackoverflow.com/questions/24180527
        if any([not arg.has_default for arg in self.arguments]):
            required = parser.add_argument_group('Required arguments')
        else:
            required = None
        # Iterate over arguments and offer up both groups #
        for arg in self.arguments: arg.add_arg(parser, required)
        # Add the version action #
        parser.add_argument('--version', '-v', action='version',
                            version=self.version_string,
                            help="Show program's version number and exit.")
        # Add the help action #
        parser.add_argument('--help', '-h', action='help',
                            default=argparse.SUPPRESS,
                            help='Show this help message and exit.')
        # Add the pytest action #
        parser.add_argument('--pytest', action=PytestAction,
                            help='Run the test suite and exit.',
                            default=self.base_path)
        # Return #
        return parser

    @functools.cached_property
    def parsed_args(self):
        # Check for debug mode #
        if hasattr(self, 'optmagic_argv'):
            import shlex
            argument_list = shlex.split(self.optmagic_argv)
            return self.parser.parse_args(argument_list)
        # Otherwise, call the parser normally #
        return self.parser.parse_args()

    @functools.cached_property
    def kwargs(self):
        return vars(self.parsed_args)

    #------------------------------- Methods ---------------------------------#
    def __call__(self, *extra_args, **extra_kwargs):
        # Call if it's a function #
        if self.type == 'function':
            return self.func(**self.kwargs)
        # Call if it's a class #
        if self.type == 'class':
            instance = self.obj(**self.kwargs)
            return instance(*extra_args, **extra_kwargs)

    #------------------------------- Extras ----------------------------------#
    @functools.cached_property
    def markdown(self):
        """
        #TODO
        Return a markdown version of the help string.
        Beware, this actually creates a file somewhere on the filesystem!
        """
        import argmark
        return argmark.md_help(self.parser)

Instance variables

var arguments
Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var base_module

The parent package from which the object is coming from.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var base_path

The location of the package on the filesystem.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var child_module

The sub-module from which the object is coming from.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var docstring

If you use inspect.getdoc(self.func) it returns something slightly different.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var epilog_string

This is an extra string that appears at the very end of the help message. We want to display the URL to the project page.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var func
Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var kwargs
Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var markdown

TODO

Return a markdown version of the help string. Beware, this actually creates a file somewhere on the filesystem!

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var options

An even better formatter class could be constructed based on: https://stackoverflow.com/a/65891304

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var parsed_args
Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var parser
Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var prog_string

This is the name of program that appears at the top of the help string. By default it should be name of the module from which the object comes from.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var sig
Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var sub_docs

The docstring_parser module is able to parse 'numpydoc' style docstrings amongst others formats.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var title_string

A string that appears at the top of the help message. Just after the usage summary.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var type
Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var usage_string

This is the short description of the program that appears at the very top of the help string and summarizes all options.

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val
var version_string

The string returned when invoked with the '-v' option

Expand source code
def __get__(self, instance, owner=None):
    if instance is None:
        return self
    if self.attrname is None:
        raise TypeError(
            "Cannot use cached_property instance without calling __set_name__ on it.")
    try:
        cache = instance.__dict__
    except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
        msg = (
            f"No '__dict__' attribute on {type(instance).__name__!r} "
            f"instance to cache {self.attrname!r} property."
        )
        raise TypeError(msg) from None
    val = cache.get(self.attrname, _NOT_FOUND)
    if val is _NOT_FOUND:
        with self.lock:
            # check if another thread filled cache while we awaited lock
            val = cache.get(self.attrname, _NOT_FOUND)
            if val is _NOT_FOUND:
                val = self.func(instance)
                try:
                    cache[self.attrname] = val
                except TypeError:
                    msg = (
                        f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                        f"does not support item assignment for caching {self.attrname!r} property."
                    )
                    raise TypeError(msg) from None
    return val