jaraco / jaraco/transformers

Most could be done already, but how to handle double type annotations?

Open
#1 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
Jupyter Notebook
Stars
1
Forks
3
PR merge metrics
No merged PRs in 30d

Description

I'm not in favor or against of this, just a couple of comments in case that's useful.

As the first and main use case for the annotation syntax is type annotations/hints to power better tooling, type checks (e.g. `mypy`), and autocompletion in editors, I guess it would probably have to keep working correctly, so, the annotation would have to be updated (e.g. using `Annotated`) or all editors and linters would have to be updated to support a function as an annotation with new behavior.

But then, apart from that, most of the objective could be achieved with the current state of the language and a third-party library, it could look like:

```python
@transform_annotations
def do_something(a: NoneIsZeroInt, b: float) -> str:
"""Do 'something' with `a' and `b`"""
return (a ** 2) + b
```

or

```python
@transform_annotations
def do_something(a: Annotated[int, Convert(none_is_zero)], b: float) -> str:
"""Do 'something' with `a' and `b`"""
return (a ** 2) + b
```

## Similar use cases

in fact, that's very close to what can be done with [*pydantic*'s Validator decorator](https://pydantic-docs.helpmanual.io/usage/validation_decorator/). It currently doesn't do all what is asked here, but it could (or another library could).

It's also similar to how [dependencies are used in FastAPI](https://fastapi.tiangolo.com/tutorial/dependencies/#first-steps), with a function that process the input before passing it as an argument to another function.

All these examples use the type annotations to avoid extra code duplication with the same information, avoiding the problem of stacked decorators, names out of sync, etc. In fact, that's pretty much all [Typer does on top of Click](https://typer.tiangolo.com/tutorial/).

## Underlying problem - double annotations

But, although most of the objective could be satisfied with the current state of the language, there's a very particular caveat that wouldn't be solvable right now.

Something like `none_is_zero` implies **two type annotations/metadata for the same parameter**.

1. So, when some code **calls** `do_something` like:

```python
do_something(a=None, b=3.5)
```

the editor/linter would _hopefully_ not complain about `a=None`, so it would interpret the signature as:

```python
def do_something(a: Optional[int], b: float) -> str: ...
```

2. But for code **inside** the function:

```python
def do_something(a: Optional[int], b: float) -> str:
return (a ** 2) + b
```

the editor/linter would _hopefully_ not complain that `a could be None` in `(a ** 2) + b`. So, in that case, the editor/linter would interpret the signature as:

```python
def do_something(a: int, b: float) -> str: ...
```

**Note**: This is normally not a problem for functions that are not called directly by the developer's code (i.e. not a problem for frameworks/libraries).

But then, what I imagine would be the way to achieve that double signature with type annotations would probably be something like:

```python
def do_something(a: Converted[Optional[int], int], b: float) -> Converted[float, str]: ...
```

...with another name for `Converted`, of course.

But that's not thinking yet about _what_ would convert the types, only thiking about the annotations and tooling. Having the actual convertor it would probably look like:

```python
@transform_annotations
def do_something(
a: Annotated[Converted[Optional[int], int], Convert(none_is_zero)],
b: float,
) -> Annotated[Converted[float, str], Convert(make_str)]:
return (a ** 2) + b
```

But I imagine that `Converted` and `Annotated` type would probably be too complex/abstract to understand in general.

A function, as the original proposal, has the nice feature that it already has an input and output types, and makes all that code much simpler, but I haven't seen something like that in the standard library, and I imagine it could be more difficult for tooling to support it.

---

Now, related to that, Eric Traut (Pyright's author) is working on a soon-to-be PEP-proposal to declare "dataclass-like semantics", for tools like Attrs and pydantic to have better tooling support, without requiring external plugins. And one of the main points of discussion has been around that [double type annotation/metadata](https://github.com/microsoft/pyright/discussions/1782#discussioncomment-653909) and how it could be handled.

I don't have an opinion on this, and I don't see a way to solve it in a clear way that is generalizable enough while also understandable for developers, and not too different from what's already there.

But if you are interested in this, it's probably worth also checking that discussion, as the underlying key problem is more or less the same of double type annotations outside and inside.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the issue's double type annotation examples and the linked Pyright discussion on dataclass-like semantics. Compare the proposed annotation and transformation approaches with Python's current typing behavior, then define a clear, generalizable design and acceptance criteria for tooling and runtime conversion.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.