Adding support for a Pick type
Hi everyone! Recently I felt the need for a `Pick` type in Python. The name is "inspired" from TypeScript's `Pick` type (see https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-key...). Here's an example of how it work, let' say that we have a `User` model that represents a table in a database and we have a `fetch_user` function that returns a `User` with only `id` and `name`. Here's how the code could look like: ```python from typing import Pick class User: id: int name: str password: str email: str def fetch_user(name: str) -> Pick[User, "id" | "name"]: return fetch_from_db("user", name=name, fields=["id", "name"]) ``` Note: I'm making use of the union syntax on strings, which I don't think it is supported yet. I've done so in order to keep the code a bit simpler. I wanted to see what's the feeling for a feature like this. I think it might be quite useful in the scenario I described above, I guess it could make some ORM's code more type safe 😊 I can try to write a PEP for this and if anyone is interested in helping I'd happy to work with you 😊
Thanks, this could be a useful addition to the type system! However, I'm
not clear yet on exactly what you're proposing: some questions below.
El jue, 10 feb 2022 a las 9:40, Patrick Arminio (
Hi everyone!
Recently I felt the need for a `Pick` type in Python. The name is "inspired" from TypeScript's `Pick` type (see https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-key... ).
Here's an example of how it work, let' say that we have a `User` model that represents a table in a database and we have a `fetch_user` function that returns a `User` with only `id` and `name`.
Here's how the code could look like:
```python from typing import Pick
class User: id: int name: str password: str email: str
def fetch_user(name: str) -> Pick[User, "id" | "name"]: return fetch_from_db("user", name=name, fields=["id", "name"])
What would this mean exactly? In TypeScript it's easier because (simplifying a bit) everything is a TypedDict, but here it looks like you're using a concrete class, not a TypedDict. How would you write the return type without Pick?
```
Note: I'm making use of the union syntax on strings, which I don't think it is supported yet. I've done so in order to keep the code a bit simpler.
That might be a hard sell because the way things currently are, it should
mean adding str.__or__ everywhere, even outside of a typing context.
I wanted to see what's the feeling for a feature like this. I think it might be quite useful in the scenario I described above, I guess it could make some ORM's code more type safe 😊
I can try to write a PEP for this and if anyone is interested in helping I'd happy to work with you 😊 _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: jelle.zijlstra@gmail.com
Answers to Jelle, Stefane and Tin below :)
*On Thu, 10 Feb 2022 at 11:54, Jelle Zijlstra
Thanks, this could be a useful addition to the type system! However, I'm not clear yet on exactly what you're proposing: some questions below.
El jue, 10 feb 2022 a las 9:40, Patrick Arminio (< patrick.arminio@gmail.com>) escribió:
```python from typing import Pick
class User: id: int name: str password: str email: str
def fetch_user(name: str) -> Pick[User, "id" | "name"]: return fetch_from_db("user", name=name, fields=["id", "name"])
What would this mean exactly? In TypeScript it's easier because (simplifying a bit) everything is a TypedDict, but here it looks like you're using a concrete class, not a TypedDict. How would you write the return type without Pick?
Yes, that was my main concern. For the use case I care about I'd type it as this: ```python class FetchUserResult: id: int name: str ``` This is because I don't want users to do `fetch_user('patrick').email` as it would trigger another database query (at least with django) For reference django returns an object of the same class, and then does an additional query when the we ask for more data ```python
print(Thread.objects.all().only('received_at').query) SELECT "database_thread"."id", "database_thread"."received_at" FROM "database_thread" type(Thread.objects.all().only('received_at')[0])
Thread.objects.all().only('received_at')[0].__dict__ {'_state': , 'id': '17e5fa2d6841d586', 'received_at': datetime.datetime(2022, 1, 15, 21, 26, 51, tzinfo=datetime.timezone.utc)} Thread.objects.all().only('received_at')[0].subject 'A test subject'
>
>
>> ```
>>
>> Note: I'm making use of the union syntax on strings, which I don't think
>> it is supported yet. I've done so in order to keep the code a bit simpler.
>>
>> That might be a hard sell because the way things currently are, it should
> mean adding str.__or__ everywhere, even outside of a typing context.
>
Yes, I guess we could leverage Literal for the time being :)
*On Thu, 10 Feb 2022 at 11:56, Stéfane Fermigier <sf@fermigier.com
<sf@fermigier.com>> wrote:*
> I like the overall idea, it relates to code I have written in the past.
>
> 2 questions:
>
>
> 1. Why stop at “Pick” ? I wasn’t aware of
> https://www.typescriptlang.org/docs/handbook/utility-types.html but it
> seems there are other similar constructs that could be useful (ex: “Omit”
> is actually the opposite of “Pick”).
>
>
I agree, we shouldn't stop at Pick, but I think we should first check if
Pick makes sense for how python works and then implement some of the others
😊
>
> 1. Are there any actual ORMs or forms/data/json validation frameworks
> that could be plugged into the code examples you’ve provided ?
>
> Not sure if this is what you're asking for, but Django's ORM allows to
select only some fields, and I guess other ORMs do that too.
*On Thu, 10 Feb 2022 at 12:00, Tin Tvrtković <tinchester@gmail.com
<tinchester@gmail.com>> wrote:*
> Hi,
>
> you might be interested in https://github.com/python/mypy/pull/11794,
> which was recently merged to Mypy. I've implemented this exactly with typed
> projections in mind. It's not supposed to return a new class though, just
> tuples. Here's how this would look like:
>
> ```python
> from attrs import define, fields as f
>
> @define
> class User:
> id: int
> name: str
> password: str
> email: str
>
> def fetch_user(name: str) -> tuple[int, str]:
> return fetch_from_db(User, name=name, fields=[f(User).id,
> f(User).name])
> ```
> There's probably an even better version with TypedDicts or namedtuples
> possible but that was beyond my abilities, and tuples are good enough for
> now. I suppose your example could boil down to creating and returning a
> namedtuple with fields taken from a class.
>
Yes, namedtuples could be good!
My issues with this approach are that:
1. it is a plugin, so it won't work with pyright or other type checkers
2. returning a namedtuple will probably involve writing the namedtuple
definition, which is something I wanted to avoid
3. you need to keep the return type in sync with the field types
--
Patrick Arminio
I like the overall idea, it relates to code I have written in the past.
2 questions:
1. Why stop at “Pick” ? I wasn’t aware of
https://www.typescriptlang.org/docs/handbook/utility-types.html but it
seems there are other similar constructs that could be useful (ex: “Omit”
is actually the opposite of “Pick”).
2. Are there any actual ORMs or forms/data/json validation frameworks
that could be plugged into the code examples you’ve provided ?
S.
On 10 Feb 2022 at 18:40:11, Patrick Arminio
Hi everyone!
Recently I felt the need for a `Pick` type in Python. The name is "inspired" from TypeScript's `Pick` type (see https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-key... ).
Here's an example of how it work, let' say that we have a `User` model that represents a table in a database and we have a `fetch_user` function that returns a `User` with only `id` and `name`.
Here's how the code could look like:
```python from typing import Pick
class User: id: int name: str password: str email: str
def fetch_user(name: str) -> Pick[User, "id" | "name"]: return fetch_from_db("user", name=name, fields=["id", "name"])
```
Note: I'm making use of the union syntax on strings, which I don't think it is supported yet. I've done so in order to keep the code a bit simpler.
I wanted to see what's the feeling for a feature like this. I think it might be quite useful in the scenario I described above, I guess it could make some ORM's code more type safe 😊
I can try to write a PEP for this and if anyone is interested in helping I'd happy to work with you 😊 _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: sf@fermigier.com
-- Stefane Fermigier - http://fermigier.com/ - http://twitter.com/sfermigier - http://linkedin.com/in/sfermigier Founder & CEO, Abilian - Enterprise Social Software - http://www.abilian.com/ Co-Founder & Co-Chairman, National Council for Free & Open Source Software (CNLL) - http://cnll.fr/ Co-Founder & Chairman, Association Professionnelle Européenne du Logiciel Libre (APELL) - https://www.apell.info/ Co-Founder & Spokesperson, European Cloud Industrial Alliance (EUCLIDIA) - https://www.euclidia.eu/ Founder, PyParis & PyData Paris - http://pyparis.org/ & http://pydata.fr/
Hi,
you might be interested in https://github.com/python/mypy/pull/11794, which
was recently merged to Mypy. I've implemented this exactly with typed
projections in mind. It's not supposed to return a new class though, just
tuples. Here's how this would look like:
```python
from attrs import define, fields as f
@define
class User:
id: int
name: str
password: str
email: str
def fetch_user(name: str) -> tuple[int, str]:
return fetch_from_db(User, name=name, fields=[f(User).id, f(User).name])
```
There's probably an even better version with TypedDicts or namedtuples
possible but that was beyond my abilities, and tuples are good enough for
now. I suppose your example could boil down to creating and returning a
namedtuple with fields taken from a class.
On Thu, Feb 10, 2022 at 6:40 PM Patrick Arminio
Hi everyone!
Recently I felt the need for a `Pick` type in Python. The name is "inspired" from TypeScript's `Pick` type (see https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-key... ).
Here's an example of how it work, let' say that we have a `User` model that represents a table in a database and we have a `fetch_user` function that returns a `User` with only `id` and `name`.
Here's how the code could look like:
```python from typing import Pick
class User: id: int name: str password: str email: str
def fetch_user(name: str) -> Pick[User, "id" | "name"]: return fetch_from_db("user", name=name, fields=["id", "name"])
```
Note: I'm making use of the union syntax on strings, which I don't think it is supported yet. I've done so in order to keep the code a bit simpler.
I wanted to see what's the feeling for a feature like this. I think it might be quite useful in the scenario I described above, I guess it could make some ORM's code more type safe 😊
I can try to write a PEP for this and if anyone is interested in helping I'd happy to work with you 😊 _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: tinchester@gmail.com
I like the idea. 1. This would presumably generate a new (data)class, which would contain a subset of fields from another (data)class. You didn't decorate `User` with `@dataclass`; was this omission intentional? 2. It would be useful to show how such a function would return such a `Pick(...)` value. Presumably you would create a type alias above the function, then use it to initialize the instance with `id` and `name` parameters? 3. Do you think this could be made to work with `TypedDict` as well? 4. Something similiar I've developed, which I would be happy to replace or supplement with Pick: https://github.com/fondat/fondat-core/blob/main/fondat/data.py#L88. Paul On Thu, 2022-02-10 at 17:40 +0000, Patrick Arminio wrote:
Hi everyone!
Recently I felt the need for a `Pick` type in Python. The name is "inspired" from TypeScript's `Pick` type (see
https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-key...
).
Here's an example of how it work, let' say that we have a `User` model that represents a table in a database and we have a `fetch_user` function that returns a `User` with only `id` and `name`.
Here's how the code could look like:
```python from typing import Pick
class User: id: int name: str password: str email: str
def fetch_user(name: str) -> Pick[User, "id" | "name"]: return fetch_from_db("user", name=name, fields=["id", "name"])
```
Note: I'm making use of the union syntax on strings, which I don't think it is supported yet. I've done so in order to keep the code a bit simpler.
I wanted to see what's the feeling for a feature like this. I think it might be quite useful in the scenario I described above, I guess it could make some ORM's code more type safe 😊
I can try to write a PEP for this and if anyone is interested in helping I'd happy to work with you 😊 _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: pbryan@anode.ca
On Thu, 10 Feb 2022 at 18:46, Paul Bryan
I like the idea.
1. This would presumably generate a new (data)class, which would contain a subset of fields from another (data)class. You didn't decorate `User` with `@dataclass`; was this omission intentional?
Yes, I don't think this should work just for dataclasses or similar, see the example with the Django model :)
2. It would be useful to show how such a function would return such a `Pick(...)` value. Presumably you would create a type alias above the function, then use it to initialize the instance with `id` and `name` parameters?
Good question! We could do as you suggested something like this: ```python ExampleResponse = Pick[User, "id", "name"] ExampleResponse(id="123", name="123") ``` but I'm not 100% sure we should support this as it will increase the complexity quite a bit :) For example the Django ORM returns an object that has only some field instantiated, with dataclasses you could for example set all the other fields as `None`
3. Do you think this could be made to work with `TypedDict` as well?
Yes I think so, `TypedDict`s should be easier to implement than classes :) 4. Something similiar I've developed, which I would be happy to replace or
supplement with Pick: https://github.com/fondat/fondat-core/blob/main/fondat/data.py#L88.
Ah neat! I guess the new signature could look like this: ```python def derive_datacls( cls_name: str, dataclass: T, *, include: set[Literal[str]] = None, exclude: set[str] = None, append: Iterable[Union[tuple[str, type], tuple[str, type, dataclasses.Field]]] = None, optional: Union[set[str], bool] = False, **kwargs, ) -> Pick[T, include]: ``` and in the future it could also use `Omit` :) -- Patrick Arminio
I have doubts that you could create a Pick function that would just work with any class, presuming it would generate a type, and that type would need to be initializable, accessible, introspectable, etc. Perhaps I've misunderstood your intention; it could be you're looking to generate some kind of protocol, and the implementation that returns a value would need to be responsible for the actual implementation of the type? On Thu, 2022-02-10 at 19:41 -0600, Patrick Arminio wrote:
On Thu, 10 Feb 2022 at 18:46, Paul Bryan
wrote: I like the idea.
1. This would presumably generate a new (data)class, which would contain a subset of fields from another (data)class. You didn't decorate `User` with `@dataclass`; was this omission intentional?
Yes, I don't think this should work just for dataclasses or similar, see the example with the Django model :)
2. It would be useful to show how such a function would return such a `Pick(...)` value. Presumably you would create a type alias above the function, then use it to initialize the instance with `id` and `name` parameters?
Good question! We could do as you suggested something like this:
```python ExampleResponse = Pick[User, "id", "name"]
ExampleResponse(id="123", name="123") ```
but I'm not 100% sure we should support this as it will increase the complexity quite a bit :)
For example the Django ORM returns an object that has only some field instantiated, with dataclasses you could for example set all the other fields as `None`
3. Do you think this could be made to work with `TypedDict` as well?
Yes I think so, `TypedDict`s should be easier to implement than classes :)
4. Something similiar I've developed, which I would be happy to replace or supplement with Pick: https://github.com/fondat/fondat-core/blob/main/fondat/data.py#L88 .
Ah neat! I guess the new signature could look like this:
```python def derive_datacls( cls_name: str, dataclass: T, *, include: set[Literal[str]] = None, exclude: set[str] = None, append: Iterable[Union[tuple[str, type], tuple[str, type, dataclasses.Field]]] = None, optional: Union[set[str], bool] = False, **kwargs, ) -> Pick[T, include]: ```
and in the future it could also use `Omit` :)
On Thu, 10 Feb 2022 at 20:00, Paul Bryan
I have doubts that you could create a Pick function that would just work with any class, presuming it would generate a type, and that type would need to be initializable, accessible, introspectable, etc. Perhaps I've misunderstood your intention; it could be you're looking to generate some kind of protocol, and the implementation that returns a value would need to be responsible for the actual implementation of the type?
yes, I think that would be more appropriate. The use cases I was thinking of don’t need to use a class that’s generated by using Pick :) -- Patrick Arminio
(1) On 2/10/22 12:40 PM, Patrick Arminio wrote:
Recently I felt the need for a `Pick` type in Python. The name is "inspired" from TypeScript's `Pick` type (see https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-key...).
Very interesting. I imagine you could apply a Pick[] type constructor to a {NamedTuple, @dataclass, TypedDict, Protocol}-based type in order to get a new type of the same flavor. (So if you had a Movie type that was a TypedDict, a Pick[] applied to Movie would generate a new *TypedDict* type.) (2) Your example of using Pick[] applied to a Django Model class I also find to be inspiring. In that case Pick[] is being applied to a plain-old-object type and presumably returns a Protocol-based type. As a Django user I'm fascinated by the ability to prevent extra database queries using the type system in the way described here. (3a)
Note: I'm making use of the union syntax on strings, which I don't think it is supported yet. I've done so in order to keep the code a bit simpler.
That might be a hard sell because the way things currently are, it should mean adding str.__or__ everywhere, even outside of a typing context.
Yes, I guess we could leverage Literal for the time being :)
I'd advocate instead having a syntax similar to how Callable[] takes argument types in a bracketed list. So you might write: ``` Pick[User, ["id", "name"]] ``` (However I could also see the argument that `Pick[User, Literal["id", "name"]]` would be more consistent with the current syntax, even as it introduces redundant always-the-same "Literal" noise.) (3b) The preceding syntax feels natural to me if you were to additionally support the kind of metaprogramming you suggested here:
```python def derive_datacls( cls_name: str, dataclass: T, *, include: set[Literal[str]] = None, exclude: set[str] = None, append: Iterable[Union[tuple[str, type], tuple[str, type, dataclasses.Field]]] = None, optional: Union[set[str], bool] = False, **kwargs, ) -> Pick[T, include]: ```
and in the future it could also use `Omit` :)
However I will note that you probably want to wrap "include" in some kind of bracketed syntax rather than leaving it as a bare string. The usage here looks like the use of a Dependent Type, while could be pretty tricky to specify and introduce... You might consider leaving that kind of metaprogramming to a separate future PEP. <aside> The spelling for a Dependent Type I recall seeing as Π[] in academic papers, I think? If you wrote it out in English it might be Pi[], so `Pi["include"]` in your example above. However Pi[] is a pretty unintuitive name to say the least... </aside> Again, perhaps it would be worth leaving the Dependent Type can-of-worms to a future PEP... -- David Foster | Seattle, WA, USA Contributor to TypedDict support for mypy
Answers below :)
On Sun, 13 Feb 2022 at 13:07, David Foster
(1) On 2/10/22 12:40 PM, Patrick Arminio wrote:
Recently I felt the need for a `Pick` type in Python. The name is "inspired" from TypeScript's `Pick` type (see https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-key... ).
Very interesting. I imagine you could apply a Pick[] type constructor to a {NamedTuple, @dataclass, TypedDict, Protocol}-based type in order to get a new type of the same flavor. (So if you had a Movie type that was a TypedDict, a Pick[] applied to Movie would generate a new *TypedDict* type.)
(2) Your example of using Pick[] applied to a Django Model class I also find to be inspiring. In that case Pick[] is being applied to a plain-old-object type and presumably returns a Protocol-based type.
Yes, I think this would be the best option. I'm not sure it makes a lot of sense to generate a "copy" of the "picked" class.
As a Django user I'm fascinated by the ability to prevent extra database queries using the type system in the way described here.
Yes, I think this would be a great addition. I know there's some projects like django seal that help with this too, but it would be nice to be able to do this statically
(3a)
Note: I'm making use of the union syntax on strings, which I don't think it is supported yet. I've done so in order to keep the code a bit simpler.
That might be a hard sell because the way things currently are, it should mean adding str.__or__ everywhere, even outside of a typing context.
Yes, I guess we could leverage Literal for the time being :)
I'd advocate instead having a syntax similar to how Callable[] takes argument types in a bracketed list. So you might write:
``` Pick[User, ["id", "name"]] ```
(However I could also see the argument that `Pick[User, Literal["id", "name"]]` would be more consistent with the current syntax, even as it introduces redundant always-the-same "Literal" noise.)
I'll spend some time tinkering with the syntax. I wanted to also find a syntax that might work with nested attributes, but I think it might be difficult to find a syntax that's pythonic enough
(3b) The preceding syntax feels natural to me if you were to additionally support the kind of metaprogramming you suggested here:
```python def derive_datacls( cls_name: str, dataclass: T, *, include: set[Literal[str]] = None, exclude: set[str] = None, append: Iterable[Union[tuple[str, type], tuple[str, type, dataclasses.Field]]] = None, optional: Union[set[str], bool] = False, **kwargs, ) -> Pick[T, include]: ```
and in the future it could also use `Omit` :)
However I will note that you probably want to wrap "include" in some kind of bracketed syntax rather than leaving it as a bare string. The usage here looks like the use of a Dependent Type, while could be pretty tricky to specify and introduce... You might consider leaving that kind of metaprogramming to a separate future PEP.
Good point! -- Patrick Arminio
Seems to me there are some unresolved questions about what such a feature would actually mean and on what set of types it would work. I'm not a type systems expert and maybe I missed something, though. One dimension is basically, does Pick[...] mean a concrete, normal runtime type or is it an abstract type that works like a Protocol (and maybe actually is one)? And furthermore, what are the rules for using a Pick type in either a "giving" role (e.g. how does a function construct a value of the right type if its return is annotated as Pick[User, "id" | "name"]) or a "receiving" role (if a function has a param annotated with that type, what is it allowed to do with that variable?) A major constraint here is that Python classes - unlike TS's which are similar to TypedDicts - contain not only data attributes, but also methods. If you try to define a concrete type that leaves out some members, you may get a "broken" class since methods can call arbitrary code that depends on those members. e.g. for the User example, say that "id" is just a hash key defined as a property def id(self): hash((self.name, self.password, self.email)). Then it's not possible to construct a concrete type that omits eg "password" attr, since the id attribute will then error. Or if Pick always functions as a Protocol constructed on the fly from the given type's interface (that on its own would be an interesting PEP-worthy topic), how useful is that in e.g. the database example, since any attribute access allowed by the Protocol could still rely on data that needs a new query? The only version where this proposal's meaning is clear to me are if Pick always gives a Protocol and/or is restricted to special types that never include methods, like TypedDicts or NamedTuples.
On Sun, 13 Feb 2022 at 16:51,
Seems to me there are some unresolved questions about what such a feature would actually mean and on what set of types it would work. I'm not a type systems expert and maybe I missed something, though. One dimension is basically, does Pick[...] mean a concrete, normal runtime type or is it an abstract type that works like a Protocol (and maybe actually is one)? And furthermore, what are the rules for using a Pick type in either a "giving" role (e.g. how does a function construct a value of the right type if its return is annotated as Pick[User, "id" | "name"]) or a "receiving" role (if a function has a param annotated with that type, what is it allowed to do with that variable?) A major constraint here is that Python classes - unlike TS's which are similar to TypedDicts - contain not only data attributes, but also methods. If you try to define a concrete type that leaves out some members, you may get a "broken" class since methods can call arbitrary code that depends on those members. e.g. for the User example, say that "id" is just a hash key defined as a property def id(self): hash(( self.name, self.password, self.email)). Then it's not possible to construct a concrete type that omits eg "password" attr, since the id attribute will then error. Or if Pick always functions as a Protocol constructed on the fly from the given type's interface (that on its own would be an interesting PEP-worthy topic), how useful is that in e.g. the database example, since any attribute access allowed by the Protocol could still rely on data that needs a new query? The only version where this proposal's meaning is clear to me are if Pick always gives a Protocol and/or is restricted to special types that never include methods, like TypedDicts or NamedTuples.
I'd say it would return a protocol. The use cases I can think of are for returning (or requiring) classes with only a subset of properties :) not sure it would make a lot of sense picking a method :)
_______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: patrick.arminio@gmail.com
-- Patrick Arminio
Pondering Pick type semantics it's very similar to type level dictionary/map. There was a discussion in past on numpy's need for a way to handle families or related overloads for data type promotion rules. One way of encoding promotion rules is a dictionary from pair of types (np.int32, np.int64) to a value (another type) np.int32. Pick here is pretty similar with main difference being you are using field names (effectively literal string) for keys. Using your example, class User: id: int name: str password: str email: str you could also express this as, User = TypeMap("User", {Literal["id"]: int, Literal["name"]: str, Literal["password"]: str, Literal["email"]: str}) For readability you could maybe have a rule that a string key be treated as equivalent of Literal to let you write, User = TypeMap("User", {"id": int, "name": str, "password": str, "email": str}) The rule would need to be done with care though to avoid ambiguity with quoted types that use quotes for runtime issues (similar to from __future__ import annotations need). For numpy example it would look like, PromotionRules = TypeMap("PromotionRules", {(np.int32, np.int64): np.int64, (np.float32, np.float64): np.float64, ...} So main generalization is TypeMap has keys that are types or tuples of types. Using the fetch_user example with TypeMap it looks like, def fetch_user(name: str) -> Pick[User, Literal["id"] | Literal["name]]: ... with rules that first argument of Pick must be a TypeMap, the second argument must be a type or union of types or type variable with bound possible key types and each type must be available as a key in TypeMap. So, def fetch_user(name: str) -> Pick[User, Literal["phone"]]: ... would be a type error of "phone" not found as a key in User. For concrete types Pick[TypeMap, type] the meaning would be just TypeMap[type]. So as an example, Pick[User, Literal["id"]] would mean User[Literal["id"]] -> int. For unions you would take the union for each key so, Pick[User, Literal["id"] | Literal["name"]] -> User[Literal["id"]] | User[Literal["name"] -> int | str. Typevars are needed for situations like numpy data rules. A simple example would be, def add_arrays(array1: np.ndarray[DType1], array2: np.ndarray[DType2]) -> np.ndarray[Pick[PromotionRules, DType1, DType2]]: ... One difference here is because PromotionRules works on pair of types it would need two arguments. The add_arrays would be equivalent to, @overload def add_arrays(array1: np.ndarray[np.int32], array2: np.ndarray[np.int64]) -> np.ndarray[np.int64]: ... @overload def add_arrays(array1: np.ndarray[np.float32], array2: np.ndarray[np.float64]) -> np.ndarray[np.float64]: ... Semantically a type variables used with TypeMaps just loop through each of the keys in the typemap and produce one overload per key. This would make it useful for any family of overloads shared across multiple functions. If you have more then one function doing overloads in a similar manner with certain type rules then it's possible a typemap would simplify it. I recently had a function with a lot of type relationships and wrote 3 overloads documenting main rules, but gave up on other rules as it would have led to a lot more overloads (likely 10-20). With TypeMaps each relationship group can be encoded together and avoid that issue. This avoids all issues of class/protocol argument to Pick and what it means. This requires that argument to Pick is always a TypeMap which is basically just a dict[tuple[type, ...], type] with maybe rule that length of tuples be fixed for a given TypeMap. For tuple of length one case (User) I just did dict[type, type] to make it more readable.
participants (8)
-
asafspades@gmail.com
-
David Foster
-
Jelle Zijlstra
-
Mehdi2277
-
Patrick Arminio
-
Paul Bryan
-
Stéfane Fermigier
-
Tin Tvrtković