python / python/typing

Paramspec Mapped Types

Open
#1,506 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

topic: other
Dominant language
Python
Stars
1.8k
Forks
302
Avg merge
23h
Merged PRs (30d)
8

Description

Apologies if this has already been asked/solved - I can't figure out exactly what this is called so it's very difficult to search for.

I'm writing a simple strongly typed dependency system, based on function closures. I have a decorator function that has this typing:

# This is a stand-in for a full class implementation that has a __call__ method matching
# Callable[P, Awaitable[R]] - I'm just omitting it for brevity
type CallableObject[**P, R] = Callable[P, Awaitable[R]]

def dependency[**P, R](*args: P.args, **kwargs: P.kwargs) -> Callable[
    [Callable[P, Awaitable[R]] | Callable[P, R]], CallableObject
]:
    ...

This works to a degree, but, I want to transform the arguments in P to also accept functions that return the arguments type or an Awaitable of that type. For example:

def one() -> int:
    return 1

def two() -> int:
    return 2

@dependency()
def three(one: int, two: int) -> int:
    return one + two

# I want the decorated three to have this signature
def decorated_three(one: int | Callable[..., Awaitable[int] | int], two: int | Callable[..., Awaitable[int] | int]) -> int:
    ...

Essentially, I want to apply a mapping to every arg and kwarg in P that transforms it into P | Callable[..., Awaitable[P] | P]. Is this possible with the current grammar?

As a reference, I wrote an example that does what I'm attempting to do in typescript, where you can unpack what they call "type tuples" using the keyof syntax to mutate their values:

function dependency<P extends unknown[], R>(wrap: (...args: P) => R): (...args: {[V in keyof P]: P[V] | ((...args: any) => P[V] | Promise<P[V]>)}) => R {
    return wrap as any
}

function one(): number { return 1 }

function two(): number { return 2 }

function three(one: number, two: number): number { return one + two }

// Has type: const decorated_three: (one: number | ((...args: any) => number | Promise<number>), two: number | ((...args: any) => number | Promise<number>)) => number;
const decorated_three = dependency(three)

(Playground where you can see / verify the typing works as expected)

Apologies for dragging in another type system/language - I'm just looking for the python equivalent (if it exists).

Appreciate the help in advance. If this is a duplicate, please close it and mark it with the correct issue - I just couldn't find it 😅

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the ParamSpec examples and the requested decorated signature in the issue, then review the current Python typing grammar for parameter packs and callable types. Compare the desired per-parameter transformation with the linked TypeScript mapped-tuple example; done means establishing whether the grammar can express it and documenting the supported equivalent or limitation.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
developer-experience
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.