def source(
        self,
        fields: Optional[
            Union[
                bool,
                str,
                "InstrumentedField",
                List[Union[str, "InstrumentedField"]],
                Dict[str, List[Union[str, "InstrumentedField"]]],
            ]
        ] = None,
        **kwargs: Any,
    ) -> Self:
        """
        Selectively control how the _source field is returned.

        :arg fields: field name, wildcard string, list of field names or wildcards,
                     or dictionary of includes and excludes
        :arg kwargs: ``includes`` or ``excludes`` arguments, when ``fields`` is ``None``.

        When no arguments are given, the entire document will be returned for
        each hit.  If ``fields`` is a string or list of strings, the field names or field
        wildcards given will be included. If ``fields`` is a dictionary with keys of
        'includes' and/or 'excludes' the fields will be either included or excluded
        appropriately.

        Calling this multiple times with the same named parameter will override the
        previous values with the new ones.

        Example::

            s = Search()
            s = s.source(includes=['obj1.*'], excludes=["*.description"])

            s = Search()
            s = s.source(includes=['obj1.*']).source(excludes=["*.description"])

        """
        s = self._clone()

        if fields and kwargs:
            raise ValueError("You cannot specify fields and kwargs at the same time.")

        @overload
        def ensure_strings(fields: str) -> str: ...

        @overload
        def ensure_strings(fields: "InstrumentedField") -> str: ...

        @overload
        def ensure_strings(
            fields: List[Union[str, "InstrumentedField"]],
        ) -> List[str]: ...

        @overload
        def ensure_strings(
            fields: Dict[str, List[Union[str, "InstrumentedField"]]],
        ) -> Dict[str, List[str]]: ...

        def ensure_strings(
            fields: Union[
                str,
                "InstrumentedField",
                List[Union[str, "InstrumentedField"]],
                Dict[str, List[Union[str, "InstrumentedField"]]],
            ],
        ) -> Union[str, List[str], Dict[str, List[str]]]:
            if isinstance(fields, dict):
                return {k: ensure_strings(v) for k, v in fields.items()}
            elif not isinstance(fields, (str, InstrumentedField)):
                # we assume that if `fields` is not a any of [dict, str,
                # InstrumentedField] then it is an iterable of strings or
                # InstrumentedFields, so we convert them to a plain list of
                # strings
                return [str(f) for f in fields]
            else:
                return str(fields)

        if fields is not None:
            s._source = fields if isinstance(fields, bool) else ensure_strings(fields)  # type: ignore[assignment]
            return s

        if kwargs and not isinstance(s._source, dict):
            s._source = {}

        if isinstance(s._source, dict):
            for key, value in kwargs.items():
                if value is None:
                    try:
                        del s._source[key]
                    except KeyError:
                        pass
                else:
                    s._source[key] = ensure_strings(value)

        return s

        Selectively control how the _source field is returned.

        :arg fields: field name, wildcard string, list of field names or wildcards,
                     or dictionary of includes and excludes
        :arg kwargs: ``includes`` or ``excludes`` arguments, when ``fields`` is ``None``.

        When no arguments are given, the entire document will be returned for
        each hit.  If ``fields`` is a string or list of strings, the field names or field
        wildcards given will be included. If ``fields`` is a dictionary with keys of
        'includes' and/or 'excludes' the fields will be either included or excluded
        appropriately.

        Calling this multiple times with the same named parameter will override the
        previous values with the new ones.

        Example::

            s = Search()
            s = s.source(includes=['obj1.*'], excludes=["*.description"])

            s = Search()
            s = s.source(includes=['obj1.*']).source(excludes=["*.description"])

        
elasticsearch/dsl/search_base.py