Yes, IMHO type annotations should be mandatory for everything that isn't a quickly thrown together draft script. If you are interested in more details about typing options and best practices in Python, I recommend starting with PEP 484 and the typing module.
Regarding cx_Oracle, it seems it is written entirely in C, so there are no real Python signatures available for all their functions. I could not find any stub files either.
You also did not explain, where that oracleEngine argument is supposed to come from, so the best I can do is guess that you are referring to the object returned by the context manager cx_Oracle.connect, which in turn should be an instance of the cx_Oracle.Connection class. So this is what I would use to annotate that function parameter.
In general, are there any tips or tricks for type hinting with types that aren't built-in core python data types?
Types are generally annotated with concrete classes or with generic types/type variables. Keep in mind that what you refer to as "built-in core python data types" are also nothing but classes. Everything in Python is an object.
So if you want to pass the return value of sqlalchemy.create_engine to a function, that function parameter should be annotated with the Engine class, as you already did.
... is it a bad practice to give imported type hints an alias [...]?
Not at all. If aliasing objects on import improves readability or better conveys contextual meaning, I would say it is encouraged.
The only thing is that I would suggest sticking to PEP 8, unless you have very good reasons not to. (See my code example below.)
As a matter of fact, it can sometimes be helpful to explicitly use TypeAlias, if you have long (maybe even qualified) class names that you repeat multiple times in type annotations, such as pd.DataFrame.
All together, I would suggest to modify your example code like this:
from typing import TypeAlias
import pandas as pd
from cx_Oracle import Connection as OracleConn
from sqlalchemy.engine.base import Engine as SQLAEngine
DF: TypeAlias = pd.DataFrame
def pull_data(oracle_conn: OracleConn, sqla_engine: SQLAEngine) -> DF:
# do stuff
...
Keep in mind that I made a few assumptions and guesses regarding the Oracle types. I also noticed that they mention very explicitly in their documentation that cx_Oracle is being replaced by python-oracledb. The latter also seems to support typing much better at first glance. So that may be worth upgrading to.
Hope this helps.