# Buggy code
def add(connect_spec, dn, attributes):
    """Add an entry to an LDAP database.

    :param connect_spec:
        See the documentation for the ``connect_spec`` parameter for
        :py:func:`connect`.

    :param dn:
        Distinguished name of the entry.

    :param attributes:
        Non-empty dict mapping each of the new entry's attributes to a
        non-empty iterable of values.

    :returns:
        ``True`` if successful, raises an exception otherwise.

    CLI example:

    .. code-block:: bash

        salt '*' ldap3.add "{
            'url': 'ldaps://ldap.example.com/',
            'bind': {
                'method': 'simple',
                'password': 'secret',
            },
        }" "dn='dc=example,dc=com'" "attributes={'example': 'values'}"
    """
    l = connect(connect_spec)
    # convert the "iterable of values" to lists in case that's what
    # addModlist() expects (also to ensure that the caller's objects
    # are not modified)
    attributes = dict(((attr, list(vals)) for attr, vals in six.iteritems(attributes)))
    log.info("adding entry: dn: %s attributes: %s", repr(dn), repr(attributes))

    if "unicodePwd" in attributes:
        attributes["unicodePwd"] = [
            _format_unicode_password(x) for x in attributes["unicodePwd"]
        ]

    modlist = salt.utils.data.decode(
        ldap.modlist.addModlist(attributes), to_str=True, preserve_tuples=True
    )
    try:
        l.c.add_s(dn, modlist)
    except ldap.LDAPError as e:
        _convert_exception(e)
    return True


# Traceback (TypeError)
Comment: An exception occurred in this state: Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/salt/state.py", line 1905, in call
**cdata['kwargs'])
File "/usr/local/lib/python3.6/site-packages/salt/loader.py", line 1830, in wrapper
return f(*args, **kwargs)
File "/usr/local/lib/python3.6/site-packages/salt/states/ldap.py", line 327, in managed
__salt__['ldap3.change'](l, dn, o, n)
File "/usr/local/lib/python3.6/site-packages/salt/modules/ldap3.py", line 595, in change
l.c.modify_s(dn, modlist)
File "/usr/local/lib/python3.6/site-packages/ldap/ldapobject.py", line 600, in modify_s
return self.modify_ext_s(dn,modlist,None,None)
File "/usr/local/lib/python3.6/site-packages/ldap/ldapobject.py", line 572, in modify_ext_s
msgid = self.modify_ext(dn,modlist,serverctrls,clientctrls)
File "/usr/local/lib/python3.6/site-packages/ldap/ldapobject.py", line 569, in modify_ext
return self._ldap_call(self._l.modify_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
File "/usr/local/lib/python3.6/site-packages/ldap/ldapobject.py", line 294, in _ldap_call
result = func(*args,**kwargs)
TypeError: ('expected a byte string in the list', 'tao')

# Fixed code
def add(connect_spec, dn, attributes):
    """Add an entry to an LDAP database.

    :param connect_spec:
        See the documentation for the ``connect_spec`` parameter for
        :py:func:`connect`.

    :param dn:
        Distinguished name of the entry.

    :param attributes:
        Non-empty dict mapping each of the new entry's attributes to a
        non-empty iterable of values.

    :returns:
        ``True`` if successful, raises an exception otherwise.

    CLI example:

    .. code-block:: bash

        salt '*' ldap3.add "{
            'url': 'ldaps://ldap.example.com/',
            'bind': {
                'method': 'simple',
                'password': 'secret',
            },
        }" "dn='dc=example,dc=com'" "attributes={'example': 'values'}"
    """
    l = connect(connect_spec)
    # convert the "iterable of values" to lists in case that's what
    # addModlist() expects (also to ensure that the caller's objects
    # are not modified)
    attributes = dict(
        (
            (attr, salt.utils.data.encode(list(vals)))
            for attr, vals in six.iteritems(attributes)
        )
    )
    log.info("adding entry: dn: %s attributes: %s", repr(dn), repr(attributes))

    if "unicodePwd" in attributes:
        attributes["unicodePwd"] = [
            _format_unicode_password(x) for x in attributes["unicodePwd"]
        ]

    modlist = (ldap.modlist.addModlist(attributes),)
    try:
        l.c.add_s(dn, modlist)
    except ldap.LDAPError as e:
        _convert_exception(e)
    return True