You can use Pillow:
from PIL import Image
filename = r'logo.png'
img = Image.open(filename)
img.save('logo.ico')
Optionally, you may specify the icon sizes you want:
icon_sizes = [(16,16), (32, 32), (48, 48), (64,64)]
img.save('logo.ico', sizes=icon_sizes)
The Pillow docs say that by default it will generate sizes
[(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (255, 255)]
and any size bigger than the original size or 255 will be ignored.
Yes, it is in the Read-only section of the docs, but it works to some extent.