Using HMAC with AES modes that do NOT require padding
If I understand correctly, your input to HMAC is the ciphertext $c$, padded with null bytes (to a multiple of 16 bytes):
    # pad to a multiple of 16 bytes
    if self._len_ct &amp; 0x0F:
       self._auth.update(bytes(16 - (self._len_ct &amp; 0x0F)))

As a concrete example, suppose the ciphertext is $c=$ deadbeef00, then you will compute the HMAC tag as $t = $ HMAC(deadbeef0000...00), 16 bytes. But this is also a valid HMAC tag for different ciphertexts $c'=$ deadbeef as well as $c' =$ deadbeef000000 etc. I hope you can see why this breaks the authenticated encryption security property.
I'm not sure why you are padding/encoding the HMAC input in this way, but my guess is you are trying to &quot;serialize&quot; both the ciphertext and associated data into a single string that is given to HMAC.  This is a common pitfall for authentication: you want to authenticate a pair of things $(x,y)$, and so you authenticate the string $x \| y$. But many pairs $(x',y')$ get mapped to the same string  $x\|y$ (think of cases where the length of $x$, $y$ are variable)! In order to properly authenticate, you need an unambiguous encoding of the pair $(x,y)$ into a string. One good way would be to somehow include the length of $x$ as part of this encoding.
edit: also you never seem to use self._cipher?