The key is being even, notice that $f_{N+k} = f_{k}$, and when you do a circular wrapping of an infinite function you should have $f_{k} = \sum_{i=-\infty}^\infty f((i \cdot N + k) T_s)$, then naturally $f_{N-k} = f_{k}$, in practice you want to choose $f$ and $N$ so that $f$ vanishes before $N/2$, then you are safe to use only one term of the summation that define $f_{k}$.
This holds true for asymmetric signals, the important point is that if the convolution result has more than $N$ non-zero points then then you can only recover the samples that are less than $N$ indices from both ends of the signal.
In other words if you have a signal that is non-zero from $L \le i \le H$ then you can only recover the samples at $i$ such that $H - N < i < L + N$. If you have a response of length not greater than $N$ then you can recover all the outputs. If your response has length $N + d$ you can only recover $N - d$ samples from the output. And by circularly shifting by the inputs by different number of samples you can control which samples of the output will be usable. you can also split your operators in multiple chunks to process an infinite duration signal as in overlap-save method.
An example in python
There is a pair of useful function fftshift
and ifftshift
to perform these operations without unintended code obfuscation.
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import skewnorm;
a = 3
plt.figure(figsize=(6, 6))
# circularly shifted equally spaced grid
t = np.fft.ifftshift(np.linspace(-10, 10, 2048));
# filter
h = skewnorm.pdf(t, a)
# signal without circular shift
x = np.zeros((2048))
x[500:1300] = np.arange(-400, 400);
# apply filter to signal (result is circularly shifted, you can
# recover the signal in the natural order with ifftshift)
y = np.fft.irfft(np.fft.rfft(h) * np.fft.rfft(x)) / len(t)
