We can do this by tricking the Python command into starting an interactive session. This can be done with unbuffer, usually provided alongside the expect tool in Linux distros. This approach is pretty general, and will work for all sorts of programs which behave differently when they're called interactively.
The following command will start a REPL, even though the (currently empty) input is coming from a pipe:
$ printf '' | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
A couple of annoying behaviours make this work a little differently to an interactive session. Firstly, unbuffer will exit as soon as it encounters EOF, so we need a small delay to ensure Python has enough time to start up:
$ (sleep 1; printf '') | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Notice that this time we managed to get a >>> prompt.
Secondly, our input commands won't get echoed as part of the output. For example:
$ (sleep 1; printf 'print("a" * 10)\n'; sleep 1) | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> aaaaaaaaaa
>>>
Notice that our input print("a" * 10)\n doesn't appear after the >>> prompt, even though the result does get printed.
We can fix this using tee to duplicate our commands to both stdout and to the REPL (which we execute using process substitution):
$ (sleep 1; printf 'print("a" * 10)\n'; sleep 1) | tee >(unbuffer -p python3)
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("a" * 10)
aaaaaaaaaa
>>>
This seems to behave properly, but we have to put delays between each line. Here's a script which will do that automatically, reading lines from stdin:
#!/usr/bin/env bash
set -e
cat | (sleep 1; while IFS='' read -r LINE
do
 sleep 0.2
 echo "$LINE"
done; sleep 1) | tee >(unbuffer -p python3)
This seems to do the job (I'm using printf but this would work equally well with a separate file; note that the REPL needs two newlines to execute an indented block, just like in an interactive session):
$ printf 'if True:\n  print("hello")\nelse:\n  print("world")\n\n12345\n' | ./repl.sh
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> if True:
...   print("hello")
... else:
...   print("world")
...
hello
>>> 12345
12345
>>>
We can pipe this through standard text processing tools like head and tail if you want to remove the final >>> and the startup noise, e.g.
$ printf 'if True:\n  print("hello")\nelse:\n  print("world")\n\n12345\n' | ./repl.sh | tail -n+4 | head -n-1
>>> if True:
...   print("hello")
... else:
...   print("world")
...
hello
>>> 12345
12345