some app.typing union types not working when importing annotations from future
The SeqsCollectionType, UnalignedSeqsType don't get resolved by typing.get_constraint_names() when from __future__ import annotations is invoked.
We should detect this setting and either change algorithm for resolving types or raise an exception.
Here's a demo.
# from __future__ import annotations
from cogent3.app.composable import define_app
from cogent3.app.typing import SeqsCollectionType
from cogent3.core.alignment import ArrayAlignment
@define_app
def make_coll(data: dict[str, str]) -> SeqsCollectionType:
return ArrayAlignment(data)
@define_app
def array_align_to_fasta(aln: ArrayAlignment) -> str:
return aln.to_fasta()
# the following works with the from future line commented out, but fails with TypeError if it's active
app = make_coll() + array_align_to_fasta()
So, the problem with this of course is that the from __future__ import annotations stringifys the type hints. This can be resolved through using typing.get_type_hints rather than inspect in composable.py's _get_raw_hints function (though might take some time for me to properly verify).
However, should this example actually proceed without error? For instance, it is currently allowable for make_coll to return something of type SequenceCollection or Alignment in addition to ArrayAlignment but array_align_to_fasta only accepts something of type ArrayAlignment. i.e. shouldn't the first app's output type/s be a subset of the second app's allowable input types?
Yes because
AlignedSeqsType = TypeVar("AlignedSeqsType", "Alignment", "ArrayAlignment") # this
UnalignedSeqsType = TypeVar("UnalignedSeqsType", bound="SequenceCollection")
SeqsCollectionType = Union[AlignedSeqsType, UnalignedSeqsType] # is part of this
Perhaps the thing to do for now is to raise detect this case and raise an exception about not currently future proof? (pun intended)
Yes because
AlignedSeqsType = TypeVar("AlignedSeqsType", "Alignment", "ArrayAlignment") # this UnalignedSeqsType = TypeVar("UnalignedSeqsType", bound="SequenceCollection") SeqsCollectionType = Union[AlignedSeqsType, UnalignedSeqsType] # is part of this
Just to follow up on this, so this simpler case shouldn't fail? (it currently doesn't though other type checking tools would complain)
from cogent3.app.composable import define_app
@define_app
def make_num(num: int) -> int | str:
if num >= 0:
return num
return "negative " + str(num)
@define_app
def add_one(num: int) -> int:
return num + 1
app = make_num() + add_one()
While the app would work for non-negative numbers, int | str is not part of int and tools like mypy would complain about the non-app variant add_one(make_num(3)).
Perhaps the thing to do for now is to raise detect this case and raise an exception about not currently future proof? (pun intended)
Probably a good idea for now. Though I wonder if another library may suit our needs, typing in python can be quite a complex (and frequently updated between versions) beast
In terms of your example above, our type introspection code only looks at the types as a set and asks whether there is an intersection. We have to rely on developers making coherent choices about their type hints.
Looking for a third-party library to do this is a great idea.
fixed in #1931