Example #1
0
def test_set_sorting_fallback():
    set_ = set((None, 1))
    description = inspect.object_description(set_)
    if PY3:
        assert description in ("{1, None}", "{None, 1}")
    else:
        assert description in ("set([1, None])", "set([None, 1])")
Example #2
0
def test_frozenset_sorting_fallback():
    frozenset_ = frozenset((None, 1))
    description = inspect.object_description(frozenset_)
    if PY3:
        assert description in ("frozenset({1, None})", "frozenset({None, 1})")
    else:
        assert description in ("frozenset([1, None])", "frozenset([None, 1])")
Example #3
0
def test_set_sorting():
    set_ = set("gfedcba")
    description = inspect.object_description(set_)
    if PY3:
        assert description == "{'a', 'b', 'c', 'd', 'e', 'f', 'g'}"
    else:
        assert description == "set(['a', 'b', 'c', 'd', 'e', 'f', 'g'])"
Example #4
0
def restify(cls: Optional[Type],
            mode: str = 'fully-qualified-except-typing') -> str:
    """Convert python class to a reST reference.

    :param mode: Specify a method how annotations will be stringified.

                 'fully-qualified-except-typing'
                     Show the module name and qualified name of the annotation except
                     the "typing" module.
                 'smart'
                     Show the name of the annotation.
    """
    from sphinx.ext.autodoc.mock import ismock, ismockmodule  # lazy loading
    from sphinx.util import inspect  # lazy loading

    if mode == 'smart':
        modprefix = '~'
    else:
        modprefix = ''

    try:
        if cls is None or cls is NoneType:
            return ':py:obj:`None`'
        elif cls is Ellipsis:
            return '...'
        elif isinstance(cls, str):
            return cls
        elif ismockmodule(cls):
            return ':py:class:`%s%s`' % (modprefix, cls.__name__)
        elif ismock(cls):
            return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__,
                                            cls.__name__)
        elif is_invalid_builtin_class(cls):
            return ':py:class:`%s%s`' % (modprefix,
                                         INVALID_BUILTIN_CLASSES[cls])
        elif inspect.isNewType(cls):
            if sys.version_info > (3, 10):
                # newtypes have correct module info since Python 3.10+
                return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__,
                                                cls.__name__)
            else:
                return ':py:class:`%s`' % cls.__name__
        elif UnionType and isinstance(cls, UnionType):
            if len(cls.__args__) > 1 and None in cls.__args__:
                args = ' | '.join(restify(a, mode) for a in cls.__args__ if a)
                return 'Optional[%s]' % args
            else:
                return ' | '.join(restify(a, mode) for a in cls.__args__)
        elif cls.__module__ in ('__builtin__', 'builtins'):
            if hasattr(cls, '__args__'):
                return ':py:class:`%s`\\ [%s]' % (
                    cls.__name__,
                    ', '.join(restify(arg, mode) for arg in cls.__args__),
                )
            else:
                return ':py:class:`%s`' % cls.__name__
        else:
            return _restify_py37(cls, mode)
    except (AttributeError, TypeError):
        return inspect.object_description(cls)
Example #5
0
def test_set_sorting_fallback():
    set_ = set((None, 1))
    description = inspect.object_description(set_)
    if PY3:
        assert description in ("{1, None}", "{None, 1}")
    else:
        assert description in ("set([1, None])", "set([None, 1])")
Example #6
0
def test_frozenset_sorting():
    frozenset_ = frozenset("gfedcba")
    description = inspect.object_description(frozenset_)
    if PY3:
        assert description == "frozenset({'a', 'b', 'c', 'd', 'e', 'f', 'g'})"
    else:
        assert description == "frozenset(['a', 'b', 'c', 'd', 'e', 'f', 'g'])"
Example #7
0
def test_set_sorting():
    set_ = set("gfedcba")
    description = inspect.object_description(set_)
    if PY3:
        assert description == "{'a', 'b', 'c', 'd', 'e', 'f', 'g'}"
    else:
        assert description == "set(['a', 'b', 'c', 'd', 'e', 'f', 'g'])"
def test_dict_customtype():
    class CustomType:
        def __init__(self, value):
            self._value = value

        def __repr__(self):
            return "<CustomType(%r)>" % self._value

    dictionary = {CustomType(2): 2, CustomType(1): 1}
    description = inspect.object_description(dictionary)
    # Type is unsortable, just check that it does not crash
    assert "<CustomType(2)>: 2" in description
def test_dict_customtype():
    class CustomType:
        def __init__(self, value):
            self._value = value

        def __repr__(self):
            return "<CustomType(%r)>" % self._value

    dictionary = {CustomType(2): 2, CustomType(1): 1}
    description = inspect.object_description(dictionary)
    # Type is unsortable, just check that it does not crash
    assert "<CustomType(2)>: 2" in description
    def add_directive_header(self, sig: str):
        """
		Add the directive's header.

		:param sig:
		"""

        sourcename = self.get_sourcename()

        no_value = self.options.get("no-value", False)
        no_type = self.options.get("no-type", False)

        if not self.options.get("annotation", ''):
            ClassLevelDocumenter.add_directive_header(self, sig)

            # data descriptors do not have useful values
            if not no_value and not self._datadescriptor:
                if "value" in self.options:
                    self.add_line("   :value: " + self.options["value"],
                                  sourcename)
                else:
                    with suppress(ValueError):
                        if self.object is not INSTANCEATTR:
                            objrepr = object_description(self.object)
                            self.add_line("   :value: " + objrepr, sourcename)

            self.add_line('', sourcename)

            if not no_type:
                if "type" in self.options:
                    self.add_line(type_template % self.options["type"],
                                  sourcename)
                else:
                    # obtain type annotation for this attribute
                    the_type = get_variable_type(self)
                    if not the_type.strip():
                        obj_type = type(self.object)

                        if obj_type is object:
                            return

                        try:
                            the_type = format_annotation(obj_type)
                        except Exception:
                            return

                    line = type_template % the_type
                    self.add_line(line, sourcename)

        else:
            super().add_directive_header(sig)
    def add_directive_header(self, sig: str):
        """
		Add the directive's header.

		:param sig:
		"""

        sourcename = self.get_sourcename()

        no_value = self.options.get("no-value", False)
        no_type = self.options.get("no-type", False)

        if not self.options.get("annotation", ''):
            ModuleLevelDocumenter.add_directive_header(self, sig)

            if not no_value:
                if "value" in self.options:
                    self.add_line(f"   :value: {self.options['value']}",
                                  sourcename)
                else:
                    with suppress(ValueError):
                        if self.object is not UNINITIALIZED_ATTR:
                            objrepr = object_description(self.object)
                            self.add_line(f"   :value: {objrepr}", sourcename)

            self.add_line('', sourcename)

            if not no_type:
                if "type" in self.options:
                    the_type = self.options["type"]
                else:
                    # obtain type annotation for this data
                    the_type = get_variable_type(self)
                    if not the_type.strip():
                        obj_type = type(self.object)

                        if obj_type is object:
                            return

                        try:
                            the_type = format_annotation(obj_type)
                        except Exception:
                            return

                line = type_template % the_type
                self.add_line(line, sourcename)

        else:
            super().add_directive_header(sig)
Example #12
0
 def add_directive_header(self, sig):
     MatClassLevelDocumenter.add_directive_header(self, sig)
     if not self.options.annotation:
         if not self._datadescriptor:
             try:
                 objrepr = object_description(self.object.default)  # display default
             except ValueError:
                 pass
             else:
                 self.add_line('   :annotation: = ' + objrepr, '<autodoc>')
     elif self.options.annotation is SUPPRESS:
         pass
     else:
         self.add_line('   :annotation: %s' % self.options.annotation,
                       '<autodoc>')
Example #13
0
 def add_directive_header(self, sig):
     MatClassLevelDocumenter.add_directive_header(self, sig)
     if not self.options.annotation:
         if not self._datadescriptor:
             try:
                 objrepr = object_description(self.object.default)  # display default
             except ValueError:
                 pass
             else:
                 self.add_line(u'   :annotation: = ' + objrepr, '<autodoc>')
     elif self.options.annotation is SUPPRESS:
         pass
     else:
         self.add_line(u'   :annotation: %s' % self.options.annotation,
                       '<autodoc>')
Example #14
0
def add_directive_header(self, sig):
    ModuleLevelDocumenter.add_directive_header(self, sig)
    if not self.options.annotation:
        try:
            objrepr = object_description(self.object)

            # PATCH: truncate the value if longer than 50 characters
            if len(objrepr) > 50:
                objrepr = objrepr[:50] + "..."

        except ValueError:
            pass
        else:
            self.add_line(u'   :annotation: = ' + objrepr, '<autodoc>')
    elif self.options.annotation is SUPPRESS:
        pass
    else:
        self.add_line(u'   :annotation: %s' % self.options.annotation,
                      '<autodoc>')
	def add_directive_header(self, sig: str) -> None:  # pragma: no cover (<Py37)
		"""
		Add the directive header and options to the generated content.
		"""

		super().add_directive_header(sig)
		sourcename = self.get_sourcename()
		if not self.options.annotation:
			# obtain annotation for this data
			try:
				annotations = get_type_hints(self.parent)
			except NameError:
				# Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
				annotations = safe_getattr(self.parent, "__annotations__", {})
			except TypeError:
				annotations = {}
			except KeyError:
				# a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084)
				annotations = {}
			except AttributeError:
				# AttributeError is raised on 3.5.2 (fixed by 3.5.3)
				annotations = {}

			if self.objpath[-1] in annotations:
				objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
				self.add_line("   :type: " + objrepr, sourcename)
			else:
				key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
				if self.analyzer and key in self.analyzer.annotations:
					self.add_line("   :type: " + self.analyzer.annotations[key], sourcename)

			try:
				if self.object is UNINITIALIZED_ATTR:
					pass
				else:
					objrepr = object_description(self.object)
					self.add_line("   :value: " + objrepr, sourcename)
			except ValueError:
				pass
		elif self.options.annotation is SUPPRESS:
			pass
		else:
			self.add_line("   :annotation: %s" % self.options.annotation, sourcename)
Example #16
0
def add_directive_header(self, sig):
    # type: (unicode) -> None
    ModuleLevelDocumenter.add_directive_header(self, sig)
    sourcename = self.get_sourcename()
    if not self.options.annotation:
        try:
            objrepr = object_description(self.object)

            # PATCH: truncate the value if longer than 50 characters
            if len(objrepr) > 50:
                objrepr = objrepr[:50] + "..."
        except ValueError:
            pass
        else:
            self.add_line(u'   :annotation: = ' + objrepr, sourcename)
    elif self.options.annotation is SUPPRESS:
        pass
    else:
        self.add_line(u'   :annotation: %s' % self.options.annotation,
                      sourcename)
Example #17
0
def add_directive_header(self, sig):
    auto.ModuleLevelDocumenter.add_directive_header(self, sig)
    if not self.options.annotation:
        try:
            # objrepr = inspect.safe_repr(self.object)
            objrepr = inspect.object_description(self.object)

            # PATCH: truncate the value if longer than length_limit characters
            if len(objrepr) > length_limit:
                objrepr = objrepr[:length_limit] + "..."

        except ValueError:
            pass
        else:
            self.add_line(u'   :annotation: = ' + objrepr, '<autodoc>')
    elif self.options.annotation is auto.SUPPRESS:
        pass
    else:
        self.add_line(u'   :annotation: %s' % self.options.annotation,
                      '<autodoc>')
Example #18
0
def restify(cls: Optional[Type]) -> str:
    """Convert python class to a reST reference."""
    from sphinx.util import inspect  # lazy loading

    try:
        if cls is None or cls is NoneType:
            return ':py:obj:`None`'
        elif cls is Ellipsis:
            return '...'
        elif isinstance(cls, str):
            return cls
        elif cls in INVALID_BUILTIN_CLASSES:
            return ':py:class:`%s`' % INVALID_BUILTIN_CLASSES[cls]
        elif inspect.isNewType(cls):
            if sys.version_info > (3, 10):
                # newtypes have correct module info since Python 3.10+
                print(cls, type(cls), dir(cls))
                return ':py:class:`%s.%s`' % (cls.__module__, cls.__name__)
            else:
                return ':py:class:`%s`' % cls.__name__
        elif UnionType and isinstance(cls, UnionType):
            if len(cls.__args__) > 1 and None in cls.__args__:
                args = ' | '.join(restify(a) for a in cls.__args__ if a)
                return 'Optional[%s]' % args
            else:
                return ' | '.join(restify(a) for a in cls.__args__)
        elif cls.__module__ in ('__builtin__', 'builtins'):
            if hasattr(cls, '__args__'):
                return ':py:class:`%s`\\ [%s]' % (
                    cls.__name__,
                    ', '.join(restify(arg) for arg in cls.__args__),
                )
            else:
                return ':py:class:`%s`' % cls.__name__
        else:
            if sys.version_info >= (3, 7):  # py37+
                return _restify_py37(cls)
            else:
                return _restify_py36(cls)
    except (AttributeError, TypeError):
        return inspect.object_description(cls)
def test_set_sorting_fallback():
    set_ = {None, 1}
    description = inspect.object_description(set_)
    assert description in ("{1, None}", "{None, 1}")
def test_dictionary_sorting():
    dictionary = {"c": 3, "a": 1, "d": 2, "b": 4}
    description = inspect.object_description(dictionary)
    assert description == "{'a': 1, 'b': 4, 'c': 3, 'd': 2}"
Example #21
0
 def lazy_object_description(object):
     return object_description(lazy_repr(object))
Example #22
0
 def get_doc(self, encoding=None, ignore=1):
     """Override to treat the data value as the docstring. """
     from sphinx.util.docstrings import prepare_docstring
     from sphinx.util.inspect import object_description
     docstring = object_description(self.object)
     return [prepare_docstring(docstring, ignore)]
Example #23
0
def formatargspec(function,
                  args,
                  varargs=None,
                  varkw=None,
                  defaults=None,
                  kwonlyargs=(),
                  kwonlydefaults={},
                  annotations={}):
    # type: (Callable, Tuple[str, ...], str, str, Any, Tuple, Dict, Dict[str, Any]) -> str
    """Return a string representation of an ``inspect.FullArgSpec`` tuple.

    An enhanced version of ``inspect.formatargspec()`` that handles typing
    annotations better.
    """
    warnings.warn(
        'formatargspec() is now deprecated.  '
        'Please use sphinx.util.inspect.Signature instead.',
        RemovedInSphinx20Warning)

    def format_arg_with_annotation(name):
        # type: (str) -> str
        if name in annotations:
            return '%s: %s' % (name, format_annotation(get_annotation(name)))
        return name

    def get_annotation(name):
        # type: (str) -> str
        value = annotations[name]
        if isinstance(value, string_types):
            return introspected_hints.get(name, value)
        else:
            return value

    introspected_hints = (
        typing.get_type_hints(function)  # type: ignore
        if typing and hasattr(function, '__code__') else {})

    fd = StringIO()
    fd.write('(')

    formatted = []
    defaults_start = len(args) - len(defaults) if defaults else len(args)

    for i, arg in enumerate(args):
        arg_fd = StringIO()
        if isinstance(arg, list):
            # support tupled arguments list (only for py2): def foo((x, y))
            arg_fd.write('(')
            arg_fd.write(format_arg_with_annotation(arg[0]))
            for param in arg[1:]:
                arg_fd.write(', ')
                arg_fd.write(format_arg_with_annotation(param))
            arg_fd.write(')')
        else:
            arg_fd.write(format_arg_with_annotation(arg))
            if defaults and i >= defaults_start:
                arg_fd.write(' = ' if arg in annotations else '=')
                arg_fd.write(object_description(
                    defaults[i - defaults_start]))  # type: ignore
        formatted.append(arg_fd.getvalue())

    if varargs:
        formatted.append('*' + format_arg_with_annotation(varargs))

    if kwonlyargs:
        if not varargs:
            formatted.append('*')

        for kwarg in kwonlyargs:
            arg_fd = StringIO()
            arg_fd.write(format_arg_with_annotation(kwarg))
            if kwonlydefaults and kwarg in kwonlydefaults:
                arg_fd.write(' = ' if kwarg in annotations else '=')
                arg_fd.write(object_description(
                    kwonlydefaults[kwarg]))  # type: ignore
            formatted.append(arg_fd.getvalue())

    if varkw:
        formatted.append('**' + format_arg_with_annotation(varkw))

    fd.write(', '.join(formatted))
    fd.write(')')

    if 'return' in annotations:
        fd.write(' -> ')
        fd.write(format_annotation(get_annotation('return')))

    return fd.getvalue()
Example #24
0
def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
                  kwonlyargs=(), kwonlydefaults={}, annotations={}):
    # type: (Callable, Tuple[str, ...], str, str, Any, Tuple, Dict, Dict[str, Any]) -> str
    """Return a string representation of an ``inspect.FullArgSpec`` tuple.

    An enhanced version of ``inspect.formatargspec()`` that handles typing
    annotations better.
    """
    warnings.warn('formatargspec() is now deprecated.  '
                  'Please use sphinx.util.inspect.Signature instead.',
                  RemovedInSphinx20Warning)

    def format_arg_with_annotation(name):
        # type: (str) -> str
        if name in annotations:
            return '%s: %s' % (name, format_annotation(get_annotation(name)))
        return name

    def get_annotation(name):
        # type: (str) -> str
        value = annotations[name]
        if isinstance(value, string_types):
            return introspected_hints.get(name, value)
        else:
            return value

    introspected_hints = (typing.get_type_hints(function)  # type: ignore
                          if typing and hasattr(function, '__code__') else {})

    fd = StringIO()
    fd.write('(')

    formatted = []
    defaults_start = len(args) - len(defaults) if defaults else len(args)

    for i, arg in enumerate(args):
        arg_fd = StringIO()
        if isinstance(arg, list):
            # support tupled arguments list (only for py2): def foo((x, y))
            arg_fd.write('(')
            arg_fd.write(format_arg_with_annotation(arg[0]))
            for param in arg[1:]:
                arg_fd.write(', ')
                arg_fd.write(format_arg_with_annotation(param))
            arg_fd.write(')')
        else:
            arg_fd.write(format_arg_with_annotation(arg))
            if defaults and i >= defaults_start:
                arg_fd.write(' = ' if arg in annotations else '=')
                arg_fd.write(object_description(defaults[i - defaults_start]))  # type: ignore
        formatted.append(arg_fd.getvalue())

    if varargs:
        formatted.append('*' + format_arg_with_annotation(varargs))

    if kwonlyargs:
        if not varargs:
            formatted.append('*')

        for kwarg in kwonlyargs:
            arg_fd = StringIO()
            arg_fd.write(format_arg_with_annotation(kwarg))
            if kwonlydefaults and kwarg in kwonlydefaults:
                arg_fd.write(' = ' if kwarg in annotations else '=')
                arg_fd.write(object_description(kwonlydefaults[kwarg]))  # type: ignore
            formatted.append(arg_fd.getvalue())

    if varkw:
        formatted.append('**' + format_arg_with_annotation(varkw))

    fd.write(', '.join(formatted))
    fd.write(')')

    if 'return' in annotations:
        fd.write(' -> ')
        fd.write(format_annotation(get_annotation('return')))

    return fd.getvalue()
Example #25
0
 def lazy_object_description(object):
     return object_description(lazy_repr(object))
Example #26
0
def process_docstring(app: Sphinx, what: str, name: str, obj: Any,
                      options: Any, lines: List[str]) -> None:
    """Process docstring after Sphinx.

    See `autodoc-process-docstring <https://www.sphinx-doc.org/en/master/
    usage/extensions/autodoc.html#event-autodoc-process-docstring>`_
    """

    # original_obj = obj
    if isinstance(obj, property):
        obj = obj.fget

    if not callable(obj):
        return

    if inspect.isclass(obj):
        obj = getattr(obj, '__init__', getattr(obj, '__new__', None))
        # obj = getattr(obj, '__init__')

    obj = inspect.unwrap(obj)

    rm_first_arg = what in ['method', 'property', 'class'
                            ] and not isstaticmethod(obj)
    first_argname = next(iter(Signature(
        unwrap_all(obj)).parameters)) if rm_first_arg else None
    if first_argname and first_argname.endswith('_'):
        first_argname = '{}\\_'.format(first_argname[:-1])

    default_args = get_default_args(obj)
    for argname, (default, is_keyword_only) in default_args.items():

        # what if default has \
        default = ':code:`{}`'.format(object_description(default))

        # TODO
        # should be arguments
        strip = app.config.docstring_default_arg_strip_matching
        docstring_default_arg_parenthesis = False

        # Search for parameters
        # TODO Test case: empty param
        searchfor = [
            ':{} {}:'.format(field, argname) for field in param_fields
        ]
        param_found, param_start, param_end, param_matched, param_text = (
            match_field(lines,
                        searchfor,
                        include_blank=app.config.
                        docstring_default_arg_after_directives))

        if param_found:

            if app.config.docstring_default_arg_substitution not in ' '.join(
                    param_text):

                # Extracts all the flags
                for head, tail in app.config.docstring_default_arg_flags:
                    tail_found, is_end, t_start = rfind_substring_in_paragraph(
                        param_text, tail, strip, app.config.
                        docstring_default_arg_flags_multiline_matching)[:3]
                    if tail_found and is_end:
                        head_found, _, h_start, h_end = (
                            rfind_substring_in_paragraph(
                                param_text, head, strip, app.config.
                                docstring_default_arg_flags_multiline_matching)
                        )
                        if head_found:
                            # what if default has \
                            if h_end[0] == t_start[0]:
                                default = param_text[
                                    h_end[0]][h_end[1]:t_start[1]]
                            else:
                                default = ' '.join(
                                    [param_text[h_end[0]][h_end[1]:]] +
                                    param_text[h_end[0] + 1:t_start[0]] +
                                    [param_text[t_start[0]][:t_start[1]]])
                            if strip:
                                default = default.strip()
                            lines[param_start + h_start[0]] = (
                                lines[param_start +
                                      h_start[0]][:len(param_matched) + 1 +
                                                  h_start[1]])
                            del lines[param_start + h_start[0] + 1:param_end]
                            param_end = param_start + h_start[0] + 1
                            break

                if strip:
                    lines[param_end - 1] = rstrip_min(lines[param_end - 1],
                                                      len(param_matched) + 1)

                if docstring_default_arg_parenthesis:
                    raise NotImplementedError
                else:
                    # To prevent insertion into Note directives or so
                    lines.insert(
                        param_end, ' ' * len(param_matched) + ' {} {}'.format(
                            app.config.docstring_default_arg_substitution,
                            default))
        elif app.config.always_document_default_args and (
                not rm_first_arg or argname != first_argname):

            # Since ``kwargs`` (no default args) might come
            # after ``argname``, it will not be in ``default_args``.
            # Need to generate the full args list.
            next_start, next_type = find_next_arg(lines, get_args(obj),
                                                  argname)

            if docstring_default_arg_parenthesis:
                raise NotImplementedError
            else:
                lines.insert(
                    next_start, ':{} {}: {} {}'.format(
                        'keyword' if is_keyword_only and
                        (next_type is None or next_type in kw_fields) else
                        'param', argname,
                        app.config.docstring_default_arg_substitution,
                        default))

        # Search for type
        type_found, type_start, type_end, type_matched, type_text = (
            match_field(
                lines,
                [':{} {}:'.format(field, argname) for field in type_fields],
                include_blank=False))

        if type_found:
            type_text = ' '.join(type_text)
            if strip:
                type_text = type_text.rstrip()
                lines[type_end - 1] = rstrip_min(lines[type_end - 1],
                                                 len(type_matched) + 1)
            if not type_text.endswith('optional'):
                if not type_text.strip():
                    lines[type_start] = '{} optional'.format(type_matched)
                elif '`' in type_text:
                    # TODO check \` escape
                    lines[type_end - 1] += ', *optional*'
                else:
                    # Do not insert newline to prevent whitespace before ','
                    lines[type_end - 1] += ', optional'
        elif param_found or app.config.always_document_default_args and (
                not rm_first_arg or argname != first_argname):
            # insert type before param
            param_start, param_type = find_curr_arg(lines, get_args(obj),
                                                    argname)
            assert any(lines[param_start].startswith(search)
                       for search in searchfor)
            lines.insert(
                param_start, ':{}type {}: optional'.format(
                    'kw' if param_type in kw_fields else '', argname))
def test_frozenset_sorting_fallback():
    frozenset_ = frozenset((None, 1))
    description = inspect.object_description(frozenset_)
    assert description in ("frozenset({1, None})", "frozenset({None, 1})")
def test_frozenset_sorting():
    frozenset_ = frozenset("gfedcba")
    description = inspect.object_description(frozenset_)
    assert description == "frozenset({'a', 'b', 'c', 'd', 'e', 'f', 'g'})"
def test_set_sorting_fallback():
    set_ = set((None, 1))
    description = inspect.object_description(set_)
    assert description in ("{1, None}", "{None, 1}")
def test_object_description_enum():
    class MyEnum(enum.Enum):
        FOO = 1
        BAR = 2

    assert inspect.object_description(MyEnum.FOO) == "MyEnum.FOO"
def test_dictionary_sorting():
    dictionary = {"c": 3, "a": 1, "d": 2, "b": 4}
    description = inspect.object_description(dictionary)
    assert description == "{'a': 1, 'b': 4, 'c': 3, 'd': 2}"