Perform a Caesar Cipher Shift on a given string
Generally speaking your code looks quite good from my point of view. It's nicely structured, readable and documented. Well done!

Using LOWERCASE = ascii_lowercase + ascii_lowercase in conjunction with n = n % ALPHABET_LENGTH also seems to be a very clever way to implement the shift.

You don't need

if n &lt; 0:
    n = ALPHABET_LENGTH - n


because modulo will handle negative numbers in the same way naturally.

Using LOWERCASE.index(...) to find the position of the letter in the alphabet could be optimized by using some kind of look-up table like so:

LOWERCASE_LUT = {letter: i for i, letter in enumerate(ascii_lowercase)}
UPPERCASE_LUT = {letter: i for i, letter in enumerate(ascii_uppercase)}


Also appending to a string with + in Python is wasteful, because strings are immutable, so each time this is done, a new string will be created. What you'll often find instead is to collect the string parts in a list which is then joined at the end.

All of this leads to the following code:

def encode_string_cr(s: str, n: int) -&gt; str:
    """Perform a Caesar Cipher shift on s leaving punctuation, spaces intact

    :param s: the str on which to perform a Cipher shift
    :type s: str
    :param n: the number of places in the alphabet to shift the letter. 
              Positive for a forward shift, negative for a backwards shift
    :type n: int
    &gt;&gt;&gt; encode_string(s="Caesar Cipher", n=2)
    'Ecguct Ekrjgt'
    &gt;&gt;&gt; encode_string(s="Caesar Cipher", n=54)
    'Ecguct Ekrjgt'
    &gt;&gt;&gt; encode_string(s="Aa Bb", n=0)
    'Aa Bb'
    &gt;&gt;&gt; encode_string(s="Cc Dd", n=-28)
    'Aa Bb'
    """
    if n == 0:
        return s

    n %= ALPHABET_LENGTH

    chars = []    
    for c in s:
        if c in ascii_lowercase:
            i = LOWERCASE_LUT[c]
            encoded_char = LOWERCASE[i + n]
        elif c in ascii_uppercase:
            i = UPPERCASE_LUT[c]
            encoded_char = UPPERCASE[i + n]
        else:
            encoded_char = c
        chars.append(encoded_char)

    return "".join(chars)


This is roughly 1,5x faster than the original code, at the cost of a little bit more memory for those look-up tables.