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

Allow variadic generics #193

Open
NYKevin opened this Issue Mar 24, 2016 · 51 comments

Comments

Projects
None yet
7 participants
@NYKevin

NYKevin commented Mar 24, 2016

C++11 recently introduced the notion of variadic templates, which I believe Python could benefit from in a simplified form.

The idea is that you can have a generic class with a variable number of type variables, like typing.Tuple[] has. Here is a real-world variadic-generic class which is not Tuple; it is variadic in TagVar. As you can see, TagVar only appears in tuple contexts. Those tuples are sometimes heterogenous (Caution: annotations are a homegrown 3.4-compatible mishmash of nonsense), so repeating TagVar as shown is actually incorrect (but the closest approximation I could find).

Here's one possible syntax:

class MultiField(AbstractField[GetSetVar], Generic[(*TagVar,)]):
    def __init__(self, nbt_names: ty.Sequence[str], *, default:
                 GetSetVar=None) -> None:
        ...

    @abc.abstractmethod
    def to_python(self, *tags: (*TagVar,)) -> GetSetVar:
        ...

    @abc.abstractmethod
    def from_python(self, value: GetSetVar) -> ty.Tuple[(*TagVar,)]:
        ...

This is syntactically valid in Python 3.5 (if a bit ugly with the parentheses and trailing comma, which cannot be omitted without language changes), but doesn't currently work because type variables are not sequences and cannot be unpacked. It could be implemented by adding something like this to the TypeVar class:

def __iter__(self):
    yield StarredTypeVar(self)

StarredTypeVar would be a wrapper class that prefixes the repr with a star and delegates all other functionality to the wrapped TypeVar.

Of course, syntax isn't everything; I'd be fine with any syntax that lets me do this. The other immediately obvious syntax is to follow the TypeVar with an ellipsis, which conveniently does not require changes to typing.py. However, that might require disambiguation in some contexts (particularly since Tuple is likely to be involved with these classes).

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Mar 24, 2016

Hmm... why not make this a special property of type variables.Tthen you wouldn't need the funny syntax, you could just use a type variable that has a keyword saying it is variadic.

But maybe the bigger question is how exactly we should type check this.

@NYKevin

This comment has been minimized.

NYKevin commented Mar 24, 2016

Hmm... why not make this a special property of type variables.Tthen you wouldn't need the funny syntax, you could just use a type variable that has a keyword saying it is variadic.

This isn't obviously objectionable, but for the following reasons I'm not sure it's actually a good idea. First, I would like to point out that I failed to notice a third possible syntax, which doesn't require the trailing comma:

class MultiField(AbstractField[GetSetVar], Generic[[*TagVar]]):
    def __init__(self, nbt_names: ty.Sequence[str], *, default:
                 GetSetVar=None) -> None:
        ...

    @abc.abstractmethod
    def to_python(self, *tags: [*TagVar]) -> GetSetVar:
        ...

    @abc.abstractmethod
    def from_python(self, value: GetSetVar) -> ty.Tuple[[*TagVar]]:
        ...

I'll return to this syntax in a moment.

But maybe the bigger question is how exactly we should type check this.

We should enforce that the variable only appears as the sole argument to Tuple (with a trailing ellipsis?), as a variable of a generic class, possibly (?) in the instantiation of a generic class other than Tuple, or as the type of a variadic parameter (*args, **kwargs). The generic class case is complicated, however, because there might be multiple variadic type variables in play under multiple inheritance. Even if there aren't, it could be difficult to parse.

We can overcome both problems by requiring specialized syntax when the generic class is subclassed or instantiated:

class UUIDField(MultiField[uuid.UUID, [tags.LongTag, tags.LongTag]]):
    ...

Note the extra pair of brackets. They indicate where the variadic typevar begins and ends, which removes any ambiguity when there are multiple variadic typevars, and simplifies parsing when there are non-variadic typevars in the same class (as in this case).

For reasons of uniformity, I would recommend we use the bracket syntax I showed above rather than making variadic-ness a property of the type variable. That makes instantiation and declaration look more like one another, and seems more intuitive to me.

@JukkaL

This comment has been minimized.

Contributor

JukkaL commented Mar 26, 2016

Variadic generics would likely be useful at least occasionally. It would make sense to generalize them also to argument types in Callable. I remember that there are a few places in the std lib stubs where these would have let us have a more precise type for a library function. The canonical example might be something like this (in my counter-proposal being variadic is a property of a type variable, as it seems more readable to me):

Ts = TypeVar('Ts', variadic=True)
RT = TypeVar('RT')

def call_async(function: Callable[Ts, RT], args: Tuple[Ts]) -> Future[RT]:
    ...

In my proposal a variadic type variable Ts would be valid in at least these contexts:

  • As Tuple[Ts] (the only argument to Tuple)
  • As Callable[Ts, X] (the first argument to Callable)
  • As type of *args, such as *args: Ts

It's less obvious how to generalize this to user-defined generic types. Maybe like this:

class C(Generic[Ts]):
    def __init__(self, *x: Ts) -> None: ...

c = C(1, '')  # type: C[int, str]   # ok!

The limitation would be that in order to have both variadic and non-variadic arguments, you'd have to create a dummy wrapper type for the variadic args:

class ArgTypes(Generic[Ts]): pass   # dummy wrapper type

class C(Generic[T, ArgTypes[Ts]]):
    def __init__(self, t: T, *args: Ts) -> None: ...

The apparent awkwardness of the above example probably wouldn't matter much as I expect this use case to be very rare.

There are additional implementation details that a type checker should probably get right to make this useful, but I'm not even trying to enumerate them here. The implementation would likely be tricky, but probably not excessively so.

I'm still unconvinced that this is a very important feature -- look at how long it took C++ to pick up this feature. I'd propose looking for more use cases to justify variadic generics and once we have a sufficient number of use cases collected here, we'd send this to python-ideas@ for discussion.

@JukkaL

This comment has been minimized.

Contributor

JukkaL commented Apr 5, 2016

Examples where these could be useful:

  • zip
  • contextlib.contextmanager
@sixolet

This comment has been minimized.

sixolet commented Jul 25, 2016

@JukkaL There's one extension to your proposal that would solve a problem I've been trying to figure out: I have some Query objects, which vary in the types of results they produce. I also have an execute function, which takes in some Querys and would like to return a tuple, with one result per Query it was passed.

R = TypeVar('R')
class Query(Generic[R]):
   ...

Rs = TypeVar('Rs', variadic=True)
Qs = Rs.map(Query) 

def execute(*args: Qs) -> Tuple[Rs]:
   ...

I am not at all wed to this syntax for mapping over variadic types variables, but I want to be able to do it, and I think it should be a thing we should consider when considering variadic type variables.

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Jul 25, 2016

It's not very clear from that example that execute() returns a tuple. I think Jukka's proposal would have you write Tuple[Rs] for the return type. I also wish the map() functionality was expressible as part of the signature of execute() rather than in the definition of Qs.

Anyway, this would be shorthand for an infinite sequence of definitions for execute(), like this, right?

R = TypeVar('R')
R1 = TypeVar('R1')
R2 = TypeVar('R2')
R3 = TypeVar('R3')
# etc. until R999
@overload
def execute(q1: Query[R]) -> R: ...
@overload
def execute(q1: Query[R1], q2: Query[R2]) -> Tuple[R1, R2]: ...
@overload
def execute(q1: Query[R1], q2: Query[R2], q3: Query[R3]) -> Tuple[R1, R2, R3]: ...
# etc. until a total of 999 variants

NOTE: The special case for the first overload is not part of the special semantics for variadic type variables; instead there could be two overload variants, one for a single query, one for 2 or more.

@sixolet

This comment has been minimized.

sixolet commented Jul 25, 2016

Yes, execute returns a Tuple[Rs] and I simply was posting too quickly to check my work, apologies.

@sixolet

This comment has been minimized.

sixolet commented Jul 25, 2016

And yes, this would be exactly that infinite series of overloads.

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

Making the type mapping in the argument list would be nice, but requires some care as to exactly where you are parameterizing a type over each element of your variadic type variable, and where you are parameterizing a type using all elements of your variadic type variable.

For example, I'd love to be able to write Query[Rs] and mean "a variadic type variable that is a query type for each result type in Rs" but then I want to also be able to write something like Tuple[Rs] meaning "a tuple of every return type in Rs".

One possibility is, borrowing some stars and parens from @NYKevin to have, I think, somewhat of a different meaning:

def execute(*args: Query[Rs]) -> Tuple[(*Rs,)]:
    ...
@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Jul 26, 2016

So maybe the notation ought to reflect that, and we should be able to write

Rn = TypeVar('Rn', variadic=True)
def execute(*q1: Query[Rn.one]) -> Tuple[Rn.all]: ...

Where .one and .all try to give hints on how to expand these.

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

Ooh, I like not having it be some kind of obtuse operator but rather english words. Consider each as a possibility to mean what you're using one for above?

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

def execute_tuple(queries: Tuple[Query[Rs.each].all]) -> Tuple[Rs.all]: ...

Unless, say, .all is the default, and you don't have to say it, and you have to say .each at every layer you mean to map the types along.

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

Here's "all is the default, you have to specify each"

def execute_tuple(queries: Tuple[Query[Rs.each]]) -> Tuple[Rs]: ...

Here's "each is the default, you have to specify all"

def execute_tuple(queries: Tuple[Query[Rs].all]) -> Tuple[Rs.all]: ...
@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Jul 26, 2016

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

For another example, here's map, using "each is the default" form, but where specifically for a *args construction it knows you mean all when you say that *args is of a variadic type variable:

As = TypeVar('As', variadic=True)
R = TypeVar('R')
def map(f: Callable[[As.all], R], *args: Iterable[As]) -> Iterable[R]
  ...

And here's using "all is the default" form:

As = TypeVar('As', variadic=True)
R = TypeVar('R')
def map(f: Callable[[As], R], *args: Iterable[As.each]) -> Iterable[R]
  ...
@JukkaL

This comment has been minimized.

Contributor

JukkaL commented Jul 26, 2016

I think that in each location, only one of Ts.one or Ts.all can be valid, so we could automatically infer the right variant. We could then write the example like this:

Ts = TypeVar('Ts', variadic=True)

def execute(*q: Query[Ts]) -> Tuple[Ts]: ...

Here's the map example:

As = TypeVar('As', variadic=True)
R = TypeVar('R')

def map(f: Callable[[As], R], *args: Iterable[As]) -> Iterable[R]: ...

Note that Iterable[As.all] doesn't make sense, since Iterable takes only a single argument.

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

@JukkaL It might be a contrived example, but what about this (written with super-explicit each/all notation, to be clear about where you might find ambiguity):

Ts = TypeVar('Ts', variadic=True)

def make_lots_of_unary_tuples(*scalars: Ts) -> Tuple[Tuple[Ts.each].all]:
    return tuple(tuple([s]) for s in scalars)
@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

Vs this:

Ts = TypeVar('Ts', variadic=True)

def make_a_tuple_wrapped_in_a_tuple(*scalars: Ts) -> Tuple[Tuple[Ts.all]]:
    return tuple([tuple(scalars)])
@JukkaL

This comment has been minimized.

Contributor

JukkaL commented Jul 26, 2016

The types aren't quite right in your examples, since tuple(s) isn't valid for an arbitrary object s and tuple(tuple(scalars)) is the same as tuple(scalars), so the tuples won't be nested in the second example. Can you tweak the examples to work?

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

So the rule is that parameterizing something by a variadic type either results in another variadic type which represents one instance of the parametrized type per captured type variable in the variadic (each), or results in a non-variadic type, where the variadic type is interpreted as an expanded series of type parameters. You're right that most places only one of these makes sense, but wherever you could have a variadic number of type parameters you could technically use either (even if most reasonable use cases mean to expand the variadic type).

Yeah, I'll tweak the examples.

@JukkaL

This comment has been minimized.

Contributor

JukkaL commented Jul 26, 2016

Yeah, those look reasonable, though pretty contrived :-). I'm still not convinced that .all / .each are necessary, as we could also reject ambiguous code where both could be valid, at least if all the use cases we can come up with are silly enough. Single-item tuples seem like a pretty rare use case, but single-argument callables might be a more reasonable case which has the same issue. Even then you'd probably need nested callables, which are not very common.

Another thing to consider is type checking code that uses variadic type variables. Supporting them in stubs only could be much easier than supporting them in function bodies.

@sixolet

This comment has been minimized.

sixolet commented Jul 26, 2016

Another exploration of the design space of syntax: TypeVar('Ts', variadic=True) vs. VariadicTypeVar('Ts')

@sixolet

This comment has been minimized.

sixolet commented Jul 29, 2016

Ok, here I am a couple days later with a partially-working implementation under my belt. Here's what I think will work (I'm reëxplaining a decent amount here, and it's a wall of text, but bear with me):

Syntax

You create a variadic type variable with @JukkaL 's syntax: Ts = TypeVar('Ts', variadic=True) means "some series of types T_0, T_1 .. T_n".

Variadic type variables are contagious; when you use one as a type argument to some other type, it too becomes variadic: List[Ts] now means "some series of types List[T_0], List[T_1] .. List[T_n]". Each variadic type contains exactly one (for now, we can think about more later) variadic type variable it its tree of types-with-arguments.

To expand a variadic type in function arguments, give the variadic type as the type of a *args parameter. For example, def do_stuff(*args: Ts) -> None: ... says that the arguments to do_stuff, because Ts is variadic, are not uniformly the same (joined) type, but rather they each get their own type variable, however many are needed.

Without a return type, that particular type signature is not particularly useful. Aside from *args, there are two other places you can expand variadic types:

  • Tuple[Ts, ...], because Ts is variadic, is on expansion a particular tuple type with however many type variables Ts needs to expand to, Tuple[T_0, T_1, .. T_n]. Similarly, Tuple[List[Ts]] becomes Tuple[List[T_0], List[T_1] .. List[T_n]]. Note that this preserves the notion that for a non-variadic type, the meaning of the syntax is a uniform but unknown-length series of that type, but for a variadic type, the meaning is a series of particular types, each with a bindable type variable at its core.
  • Callable[[Ts, ...], R] is on expansion a callable taking as arguments however many variables Ts needs to expand to, Callable[[T_0, T_1 .. T_n], R]

Examples

With this syntax, here are the discussed use-case signatures (I think):

Ts = TypeVar('Ts', variadic=True)
R = TypeVar('R')

def zip(*args: Iterable[Ts]) -> List[Tuple[Ts, ...]]: ...

def map(fn: Callable[[Ts, ...], R], *args: Iterable[Ts]) -> List[R]: ...

def execute_all(*args: Query[Ts]) -> Tuple[Ts, ...]: ...

Some functions take some positional fixed arguments and some variadic arguments. For example, here's a version of map that expects a function that also takes an index:

def map_with_idx(fn: Callable[[int, Ts, ...], R], *args: Iterable[Ts]) -> List[R]:
    results = []  # type: List[R]
    for i, rotated_args in enumerate(zip(*args)):
        results.append(fn(i, *rotated_args))
    return results

(By extension, Tuple[int, Ts, ...] could also mean Tuple[int, T_0, T_1 .. T_n]. I don't consider it quite as important, but it's nice symmetry.)

Details: Function body typechecking

To typecheck the function body, there is very little we can conclude about the expansion of a variadic type -- the implementation of nearly any non-trivial function will almost certainly iterate over its *args at some point, and making next() return a different type for each iteration of the loop sounds too complicated. We can, however, replace all instances of our variadic type variable with Any while typechecking the body, and get a reasonably satisfactory result:

# This is how the typechecker sees it when checking the body
def map_with_idx(fn: Callable[[int, Any, ...], R], *args: Iterable[Any]) -> List[R]:
    results = []  # type: List[R]
    for i, rotated_args in enumerate(zip(*args)):
        results.append(fn(i, *rotated_args))
    return results

Details: Splatting arguments in

In the implementation of map_with_idx above, we're calling zip(*args), and for all the typechecker knows, args is of type Iterable[Iterable[Any]]. In this case, we don't actually have any known number of arguments at the call site to fill in an appropriate number of type variables -- so we replace the variadic type variable with Any again. To typecheck this particular call to zip where we can't calculate the number of substitutions, the variadic type expansion phase tells later phases to use the signature def zip(*args: Iterable[Any]) -> Tuple[Any, ...]

The true meaning of ... in a comma-separated series of types is something like:

  • If the preceding type is variadic, and we have a valid substitution of a series of type variables handy, make the substitution.
  • If the preceding type is variadic, and we have no such valid substitution, treat the replace the variadic type variable in the preceding type with its bound (if no bound, Any), making it non-variadic, and continue to the next bullet point.
  • If the preceding type is non-variadic, treat it as an arbitrary-length sequence of that type.

Details: Implementation!!!

I implemented part of what I described. I might actually get to a little more tomorrow. What I've got so far:

  • Knows how to look at *args to count how many variables to substitute
  • Knows how to substitute that many type variables inTuple
  • Knows how to do Any-replacement to typecheck function bodies and splats, but not yet the more accurate bound-replacement.
  • Can successfully typecheck my execute example, and I think zip, but not yet map because I haven't gotten to Callable yet.

The buried lede:

python/mypy@master...sixolet:variadic-types

@sixolet

This comment has been minimized.

sixolet commented Jul 30, 2016

The link to the implementation above should now support full typechecking at the callsite of the following functions, as long as you don't splat args into them (and a fallback to some kind of reasonable use of Any if you do splat args into them):

Ts = TypeVar('Ts', variadic=True)
R = TypeVar('R')

def my_zip(*args: Iterable[Ts]) -> Iterator[Tuple[Ts, ...]]:
    iterators = [iter(arg) for arg in args]
    while True:
        yield tuple(next(it) for it in iterators)

def make_check(*args: Ts) -> Callable[[Ts, ...], bool]:
    """Return a function to check whether its arguments are the same as this function's args"""
    def ret(*args2: Ts) -> bool:
        if len(args) != len(args2):
            return False
        for a, b in zip(args, args2):
            if a != b:
                return False
        return True
    return ret

def my_map(f: Callable[[Ts, ...], R], *args: Iterable[Ts]) -> Iterator[R]:
    for parameters in zip(*args):
        yield f(*parameters)
@sixolet

This comment has been minimized.

sixolet commented Jul 30, 2016

(It's not pull-request-ready by any means, but all the type system features are there, just not all the error messages and not-crashing-if-you-do-something-I-didn't-think-of)

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Jul 30, 2016

FWIW, just to play devil's advocate, here's a more pedestrian approach: https://gist.github.com/gvanrossum/86beaced733b7dbf2d034e56edb8d37e

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Jul 31, 2016

Well, *Xs is valid Python syntax only inside a list, tuple or set literal, and only in Python 3.5. And that does not include subscripts, so while you could indeed write Callable[[*Xs], R], you couldn't write Tuple[*Xs]. So it's far from ideal.

@NYKevin

This comment has been minimized.

NYKevin commented Jul 31, 2016

It could, at least, have a plausible non-type-hinting meaning:

foo[a, b, *rest] == foo[(a, b, *rest)]

Then you just have to leave the door open for foo[*rest] == foo[(*rest,)] == foo[tuple(rest)]. But this would need to be a whole new PEP and I've no idea if it would make it into 3.6 or if it's useful enough to bother with.

OTOH, if you're doing Expand[Xs] anyway, [*Xs] might be more concise (compare Tuple[Expand[Xs]] to Tuple[[*Xs]] or (with the above Optional[PEP]) just Tuple[*Xs]).

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Jul 31, 2016

If we allowed everything with a plausible meaning the language would quickly degenerate into perl. And more concise is not automatically better. The use case here is rare enough that I care more about being able to look up uncommon syntax than about being able to type it fast.

@sixolet

This comment has been minimized.

sixolet commented Aug 2, 2016

Ok, @ilevkivskyi, I wrote a shot at an answer about how to deal with the types of higher-order functions in full generality over at the thread on it. #239 (comment)

It involves some complication. I think I wrote up enough that you can get the idea, but I'm happy to clarify any bits left unclear, and I still have to provide some examples to show how *args and **kwargs interactions would work.

gvanrossum added a commit to python/mypy that referenced this issue Aug 2, 2016

gvanrossum added a commit to python/typeshed that referenced this issue Aug 2, 2016

Fix signature of nested() -- it actually returns a list of values.
To be on the conservative side I'm making it an Iterable of values;
this should be good enough for common usages.

The types of the values actually correspond to the types of the
argument ContextManagers, but a proper signature would require
variadic type variables (python/typing#193),
which isn't worth waiting for. :-)
@sixolet

This comment has been minimized.

sixolet commented Feb 18, 2017

Resurrecting this thread a little! To add to the design space for how to show the expansion of a variadic type variable in an argument list or tuple:

Ts = TypeVar('Ts', variadic=True)
R = TypeVar('R')

def my_zip(*args: Iterable[T] for T in Ts) -> Iterator[Tuple[T for T in Ts]]:
    ...

def my_map(f: Callable[[T for T in Ts], R], *args: Iterable[T] for T in Ts) -> Iterator[R]:
    ...
@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Feb 19, 2017

Didya check those aren't syntax errors? They may need uglyfying parens.

@sixolet

This comment has been minimized.

sixolet commented Feb 19, 2017

Oh damn. Yes, they need uglifying parens. That kind of kills half the beauty. The other half is that it's clear exactly which variadic type var you'd like to expand, which was bothering me (in the presence of more than one -- I was going to disallow it)

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Feb 21, 2017

Another issue is that it won't be possible to find out the type at runtime, I think (or at least a tour de force).

@sixolet

This comment has been minimized.

sixolet commented Feb 21, 2017

Because the runtime object is going to be a generator?

I do like the idea of being explicit about which type var to expand. That's possible in the Expand(Foo[Ts], Ts) syntax, but not so much with the ... syntax.

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented May 16, 2017

In the meantime, a hacky workaround could be made to work easily: https://gist.github.com/gvanrossum/4232a4cdad00bd92a7a64cf3e2795820

@ilevkivskyi

This comment has been minimized.

Collaborator

ilevkivskyi commented May 17, 2017

In the meantime, a hacky workaround could be made to work easily:

And one can use this in an automated way that you proposed earlier.

@shoyer

This comment has been minimized.

shoyer commented Dec 11, 2017

To note another use-case, variadic generics would be quite useful for typing shapes for multi-dimensional arrays (#513, #516), e.g., Shape[N] and Shape[N, M].

@seaders

This comment has been minimized.

seaders commented Jul 21, 2018

The use case I'm looking for is very similar to @sixolet's, with sqlalchemy. With that, you're able to do a query like Base.query.with_entities(User, Role).all(), which returns List[Tuple[User, Role]], but isn't properly annotated, so is only identified as a list. This is an issue for a ton of sqlalchemy queries, but I've been able to get around them with shortcuts so far, but not this one.

For my use case, I have a function in Base,

@classmethod
def qwith_entities(cls, models: T) -> List[T]:
    return cls.query.with_entities(*models)

As above, with Base.qwith_entities((User, Role)), this correctly returns List[Tuple[User, Role]], but it's typehinted as List[Tuple[Type[User], Tupe[Role]]]. In my situation, I'd like to annotate models as something similar that's been described already, a tuple of multiple types, and "unpack" those types for the return value. Something like,

@classmethod
def qwith_entities(cls, models: Tuple[..., Type[T]]) -> List[Tuple[..., T]]:
    return cls.query.with_entities(*models)

I'm not at all wed to this syntax either, and do have a solution working via overloads, but would like to be able to do this without the need for them.

@gvanrossum

This comment has been minimized.

Member

gvanrossum commented Jul 21, 2018

@ilevkivskyi

This comment has been minimized.

Collaborator

ilevkivskyi commented Jul 21, 2018

@seaders I was going to use overloads for such scenarios in my SQLAlchemy stubs. It is unlikely that mypy will support variadic generics this year. Btw, we could join our efforts in writing SQLAlchemy stubs, I am going to publish them on GitHub when I am back from vacation.

@seaders

This comment has been minimized.

seaders commented Jul 21, 2018

@ilevkivskyi yeah, absolutely. I've done a load of work on generic-y helpers for my own app (the entire design app is very paired with the design of the dB), and I'd be more than happy to contribute them back to the community.

I'll start separating them out from my main codebase, and you can just shout me whenever's good for you.

@ilevkivskyi

This comment has been minimized.

Collaborator

ilevkivskyi commented Sep 4, 2018

@seaders If you are still interested we are working on SQLAlchemy stubs (and soon some mypy plugins) at https://github.com/dropbox/sqlalchemy-stubs. It is still far from complete, so your help will be appreciated. You can look at issues and PRs to see what are the plans and what you can add.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment