Add an arg_alias decorator. - #83
Conversation
Many scverse functions accept an axis argument that can be one of 0, "obs", 1, and "var", where "obs" is an alias for 0 and "var" is an alias for 1. The function then usually performs some conversion and checking internally and proceeds with one canonical representation (usually 0 and 1). This decorator generalizes this concept to arbitrary sets of values and aliases. The rules are encoded in the type hint for the aliased argument. If there is only a single set of aliases, the type hint must be a Literal with the canonical representation as first argument followed by its aliases. If there are multiple sets of aliases, that is multiple semantically different values that the function accepts, as in the axis example above, the type hint must be a Union of Literal s, where each Literal follows the same rules as above: The canonical representation is the first argument followed by its aliases.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #83 +/- ##
==========================================
+ Coverage 92.77% 93.03% +0.26%
==========================================
Files 11 12 +1
Lines 609 675 +66
==========================================
+ Hits 565 628 +63
- Misses 44 47 +3
🚀 New features to boost your workflow:
|
flying-sheep
left a comment
There was a problem hiding this comment.
Smart idea!
But interpreting annotations is super hairy, so I think
- the test matrix should expand to all supported Python versions (3.12, 3.13, 3.14)
- parametrize tests so functions with string annotations (like e.g. via
from __future__ import annotations) and live objects are tested - see below
| hint = get_type_hints(func)[argname] | ||
| if get_origin(hint) is Literal: | ||
| sets = (hint,) | ||
| elif get_origin(hint) is Union: |
There was a problem hiding this comment.
this line needs to be tested against both typing.Unions and types.UnionTypes.
There was a problem hiding this comment.
In Python 3.14 types.UnionType is an alias for typing.Union. In Python 3.12 and 3.13, Literal | Literal always produces typing.Union.
There was a problem hiding this comment.
In Python 3.12 and 3.13,
Literal | Literalalways producestyping.Union.
$ uvx -p 3.13 python -c "print(type(int | str))"
<class 'types.UnionType'>I think only with from __future__ import annotations?
There was a problem hiding this comment.
No, even without, but only for Literal and other generics: Literal returns a _LiteralGenericAlias, which is a subclass of GenericAlias which defines __or__ and __ror__.
|
The test matrix already covers 3.12, 3.13, and 3.14. I've expanded the tests to cover string annotations, I'm not sure what you mean by live objects. |
|
Sorry I was behind on this, but now I understand it. There are three ways annotations are evaluated: Let’s take
From Python 3.14 on, So we need to test if what we use ( |
|
It was my understanding that |
|
Yup, but not stringifying them changed in 3.14 (maybe too subtly to make a difference but still). I need to understand why the |
|
Sure, but that should be covered by the 3.14 tests with the |
Many scverse functions accept an axis argument that can be one of
0,"obs",1, and"var", where"obs"is an alias for0and"var"is an alias for1. The function then usually performs some conversion and checking internally and proceeds with one canonical representation (usually0and1). This decorator generalizes this concept to arbitrary sets of values and aliases.The rules are encoded in the type hint for the aliased argument. If there is only a single set of aliases, the type hint must be a
Literalwith the canonical representation as first argument followed by its aliases. If there are multiple sets of aliases, that is multiple semantically different values that the function accepts, as in the axis example above, the type hint must be aUnionofLiterals, where eachLiteralfollows the same rules as above: The canonical representation is the first argument followed by its aliases.