Motivation:
I have to use external python library ("SDK") which is just a directory of code (i.e. it's not a package and can't be "installed" in any meaninful way).
So the only way to use it is to add it to system path (sys.path in python or PYTHONPATH in shell) manually.
Also, because its designed for an embedded device, it mostly doesn't work on normal devel workstation (i.e. my laptop) - that is, some things can be imported, some can't.
Also it's not type annotated in any way.
What I want
So I want to have some typing added to my code which calls / uses classes from this library. For example:
from bad_evil_library import BadEvilClass # <- fails
...
def myfunc1(x: BadEvilClass) -> str: # <- not checked
...
def myfunc2() -> BadEvilClass: # <- not checked
....
x = myfunc1(myfunc2()) # <- not checked
...
Which means even though this function is not called, pytest stops on broken import (yes, I know I can use --continue-on-collection-errors), and mypy complains.
So I was thinking - is there some way to make "a dummy type" - i.e. a type which would in fact not represent the real type, but which would still be checked in "my code". Perhaps something like this:
BadEvilClass = DummyType()
...
def myfunc1(x: BadEvilClass) -> str: # <- checked!
...
def myfunc2() -> BadEvilClass: # <- checked!
....
x = myfunc1(myfunc2()) # <- checked!
...
This would be fine, but it won't be checked against the actual library if is available (so I won't be able to detect that BadEvilClass has been renamed, or it lost some method, etc).
What I don't want
I don't want, don't need and am not supposed to solve the problem of importing - that's NOT the issue and it won't help me in any meaningful way.
I'm not looking for ways to "modify" the "SDK" to make it proper python package or how to import it more easily.
In other words: the problem with importing is not what I want to solve, its only a trigger to the real problem, which is: "How to add at least some typing to untyped external classes so I can at least check if they get passed correctly".
What have I tried?
Currently, I created "dummy" types, which are all just Any and I moved all imports into one function returning a structure containing these "dummy" types, like this:
BadEvilClass = Any
WrongCruelClass = Any
@dataclass
class MyInterface:
bad_evil: BadEvilClass
wrong_cruel: WrongCruelClass
def get_my_interface() -> MyInterface:
# setup sys.path..
from bad_evil_library import BadEvilClass
from bad_evil_library import WrongCruelClass
return MyInterface(BadEvilClass(), WrongCruelClass())
Which works (there are MORE than one "bad evil class"), but I have absolutely no type checking, so mistakes like this are considered fine:
def foo(x: BadEvilClass) -> None:
...
my_iface = get_my_interface()
foo(my_iface) # <- I'm passing MyIface, not BadEvilClass!
# and it passes, since BadEvilClass is really "Any"
This is basically the main reason for this question - I want to be able to check if I'm passing the correct types.
Simplified question:
Is there a way to "partialy" type something which sometimes isn't available, so if it is, I get full typing checks advantage, and if it isn't, I get at least checks in my code?