Symbol timing recovery in Python
Phase offset and timing offset are two different things with two independent tracking loops to resolve. A Phase Lock loop eliminates carrier phase offset (and a frequency offset is a carrier offset changing with time). We can have phase offset with no timing offset, and we can have timing offset with no phase offset.
Once carrier offset is removed, I recommend creating an upsampled eye diagram to evaluate the signal quality and see if further timing recovery is all that is needed. The diagrams below specific to 16 QAM illustrate the difference between carrier offset and timing offset:
Here is the eye diagram of the real portion of the 16 QAM signal, where we see the four levels and we see the signal is sampled at 4 samples per symbol. The signal quality is excellent as given by the wide eye openings and small variation at the ideal sampling locations. However given the present sampling locations there is a clear timing offset where further timing recovery is needed. There is no phase offset in this example.

For comparison, after timing recovery the eye diagram looks like the following:

If we plot only the samples at the correct sampling locations (one sample per symbol) on an an IQ diagram to get the constellation, the above case including both real and imaginary values would be at the orange dots in the following graphic:

If there was a phase error in contrast to timing offset, the entire constellation would be rotated by the phase error, demonstrating phase error with no time error:

Note that if there is phase error even with no timing error the eye diagram will appear very corrupted still (often there is a phase rotation due to frequency offset which makes the eye unrecognizable, these must be corrected first before evaluating signal fidelity with an  eye diagram). Below shows the same eye as was done above for 16 QAM with no timing error, but with the phase offset as shown in the previous constellation:

A Gardner Loop is my recommended go-to for timing offset correction in that it can converge and remove timing errors even in the presence of very large carrier offsets. Then the carrier offset can be determined by simply measuring phase rotation from symbol to symbol using the correct sampling points as determined by the Gardner timing recovery loop.
More details on the Gardner Loop are provided here:
Gardner Timing Recovery for Repeated Symbols
And more details on Carrier Recovery are provided here:
High modulation index PSK - carrier recovery