The MCU on the Uno R3 comes with a specific bootloader (Optiboot) and
fuse configuration (external clock, clock prescaler disabled, bootloader
enabled...). You may find some ATmega328Ps on the market that have been
specifically configured as drop-in replacements for the one on an Uno.
Those should “just work”. If the ones you bought come with a different
The Optiboot version 7 has a do_spm function which can be called from application. The Optiboot repository contains an example for the use of this function.
SPM is the AVR CPU instruction to write to flash memory. The Optiboot wraps this in a function. A pointer to this function is put in a 'vector table' at the beginning of the bootloader. The first pointer ...
This isn't the exact answer but it helps.
Uploading without avrdude is complicated and generally slow. The best solution to not using the IDE is to open and close a port then use the avrdude command that the ide usually uses.
from serial import Serial
from time import sleep
from subprocess import run
while input('Press enter to upload ') == '':