def get_cursor(cluster: Cluster, group: Optional[int], connect_parameters: Dict[str, Any],
               role: Optional[CtlPostgresqlRole] = None,
               member_name: Optional[str] = None) -> Union['cursor', 'Cursor[Any]', None]:
    """Get a cursor object to execute queries against a member that has the given *role* or *member_name*.

    .. note::
        Besides what is passed through *connect_parameters*, this function also sets the following parameters:
            * ``fallback_application_name``: as ``Patroni ctl``;
            * ``connect_timeout``: as ``5``.

    :param cluster: the Patroni cluster.
    :param group: filter which Citus group we should get members to create a cursor against. If ``None`` consider
        members from all groups.
    :param connect_parameters: database connection parameters.
    :param role: role to filter members. See :func:`get_all_members` for available options.
    :param member_name: if specified, then besides having the given *role*, the cluster member's name should be
        *member_name*.

    :returns: a cursor object to execute queries against the database. Can be either:

        * A :class:`psycopg.Cursor` if using :mod:`psycopg`; or
        * A :class:`psycopg2.extensions.cursor` if using :mod:`psycopg2`;
        * ``None`` if not able to get a cursor that attendees *role* and *member_name*.
    """
    member = get_any_member(cluster, group, role=role, member=member_name)
    if member is None:
        return None

    params = member.conn_kwargs(connect_parameters)
    params.update({'fallback_application_name': 'Patroni ctl', 'connect_timeout': '5'})
    if 'dbname' in connect_parameters:
        params['dbname'] = connect_parameters['dbname']
    else:
        params.pop('dbname')

    from . import psycopg
    conn = psycopg.connect(**params)
    cursor = conn.cursor()
    # If we want ``any`` node we are fine to return the cursor. ``None`` is similar to ``any`` at this point, as it's
    # been dealt with through :func:`get_any_member`.
    # If we want the Patroni leader node, :func:`get_any_member` already checks that for us
    if role in (None, CtlPostgresqlRole.ANY, CtlPostgresqlRole.LEADER):
        return cursor

    # If we want something other than ``any`` or ``leader``, then we do not rely only on the DCS information about
    # members, but rather double check the underlying Postgres status.
    cursor.execute('SELECT pg_catalog.pg_is_in_recovery()')
    row = cursor.fetchone()
    in_recovery = not row or row[0]

    if in_recovery and\
        role in (CtlPostgresqlRole.REPLICA, CtlPostgresqlRole.STANDBY, CtlPostgresqlRole.STANDBY_LEADER) or\
            not in_recovery and role == CtlPostgresqlRole.PRIMARY:
        return cursor

    conn.close()

    return None
Get a cursor object to execute queries against a member that has the given *role* or *member_name*.

    .. note::
        Besides what is passed through *connect_parameters*, this function also sets the following parameters:
            * ``fallback_application_name``: as ``Patroni ctl``;
            * ``connect_timeout``: as ``5``.

    :param cluster: the Patroni cluster.
    :param group: filter which Citus group we should get members to create a cursor against. If ``None`` consider
        members from all groups.
    :param connect_parameters: database connection parameters.
    :param role: role to filter members. See :func:`get_all_members` for available options.
    :param member_name: if specified, then besides having the given *role*, the cluster member's name should be
        *member_name*.

    :returns: a cursor object to execute queries against the database. Can be either:

        * A :class:`psycopg.Cursor` if using :mod:`psycopg`; or
        * A :class:`psycopg2.extensions.cursor` if using :mod:`psycopg2`;
        * ``None`` if not able to get a cursor that attendees *role* and *member_name*.
    
patroni/ctl.py