Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paramspec Mapped Types #1506

Open
cjntaylor opened this issue Nov 10, 2023 · 4 comments
Open

Paramspec Mapped Types #1506

cjntaylor opened this issue Nov 10, 2023 · 4 comments
Labels
topic: other Other topics not covered

Comments

@cjntaylor
Copy link

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 😅

@cjntaylor cjntaylor added the topic: other Other topics not covered label Nov 10, 2023
@cjntaylor cjntaylor changed the title Modifying paramspec types Paramspec Mapped Types Nov 10, 2023
@cjntaylor
Copy link
Author

Updated the title because I finally managed to search-grep this, which is what this is called in typescript: Mapped Types

@erictraut
Copy link
Collaborator

No, there's currently no equivalent of mapped types in the Python type system. There has been some discussion about adding them, but nothing has been formally spec'ed yet.

@cjntaylor
Copy link
Author

Thanks, that's very helpful (albeit a little disappointing). Can you point me at where the discussion is happening (if it's online) so I can keep an eye on that?

@erictraut
Copy link
Collaborator

It has come up a few times... Early versions of PEP 646 proposed a Map operator that worked only on TypeVarTuple. More recently, this discussion thread talked about it in terms of TypedDict. I don't think anyone has thought about how it would apply to ParamSpecs. Function signatures in Python are significantly more complex than they are in TypeScript (e.g. parameters can be positional-only, keyword-only, or a combination of the two), so simply copying TypeScript's behavior won't be possible here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: other Other topics not covered
Projects
None yet
Development

No branches or pull requests

2 participants