First non-repeating letter
IMHO looping twice over the input string is not acceptable for multiple reasons


the string is of unknown size
you invalidate your code for generators


While this might not be necessary for your project you should learn to think like that. So a single pass algorithm should collect all necessary data to answer the question (e. g. the letter in original case).

import sys
assert sys.version_info &gt;= (3, 6)

def non_repeat(s):
    repeated = set()
    candidates = dict()
    for original_case in s:
        lower_case = original_case.lower()
        if lower_case not in repeated:
            if lower_case not in candidates:
                candidates[lower_case] = original_case
            else:
                repeated.add(lower_case)
                del candidates[lower_case]

    if candidates:
        return next(iter(candidates.values()))
    else:
        return ''


This code makes use of the insertion order of a dict which is already implemented in 3.6 and guaranteed in 3.7.



Edit: generator example

Say you want to check a big file that does not fit into memory (for brevity I assume a line fits into memory). Yor write a little character generator and run your algorithm on the generator.

def char_gen(f):
    for line in f:
        for c in line.strip():
            yield c

with open('bigfile.txt') as f:
    print(non_repeat(char_gen(f)))


also you might use the algorithm on a generator expression

print(non_repeat(c for c in "aabbcd" if c != 'c'))