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

Make click.Context generic for obj #2493

Open
Viicos opened this issue Apr 16, 2023 · 4 comments · May be fixed by #2663
Open

Make click.Context generic for obj #2493

Viicos opened this issue Apr 16, 2023 · 4 comments · May be fixed by #2663

Comments

@Viicos
Copy link

Viicos commented Apr 16, 2023

To add some type safety to the obj attribute of the Context class, I was thinking maybe it could be made generic with respect to obj attribute. It could then be used this way (with a TypedDict but it could be any user defined object):

class ContextObj(TypedDict):
    attr: int

def subcommand(
    ctx: click.Context[ContextObj],
    ...
) -> None:
    reveal_type(ctx.obj["attr"])  # Revealed type is "int"

That might be a bit cumbersome, so I'd understand if this is rejected (but I'm open to alternatives). Otherwise, I will be happy to implement this

@thehale
Copy link

thehale commented Dec 26, 2023

I would welcome a generic as described by the OP. However, I would discourage allowing "any user defined object" since the click docs clearly indicate that ctx.obj is a dict type. As such, I wouldn't want the ability for ctx.obj to be, for example, a list.


Alternatively, the typing could allow any subclass of click.Context. That way code like the following could be used to define custom context schemas.

import click
from typing import TypedDict

class MyContextObj(TypedDict):
  foo: int
  bar: str

class MyContext(click.Context)
  obj: MyContextObj

@click.command()
@click.pass_context
def my_command(ctx: MyContext):
  ctx.obj # Type checkers can tell that this has keys `foo: int` and `bar: str`

Unfortunately, this code currently throws the following typing error when checked by mypy:

Argument 1 to "pass_context" has incompatible type "Callable[[MyContext, Iterable[str]], Any]"; expected "Callable[[Context, Iterable[str]], Any]"

@Viicos
Copy link
Author

Viicos commented Dec 26, 2023

Pleasantly surprised to see some people are also looking for this. I'll look into this this week and will come with a PR that will hopefully be accepted.

@Viicos
Copy link
Author

Viicos commented Jan 6, 2024

Unfortunately, this code currently throws the following typing error when checked by mypy:

This can be fixed by using a TypeVar in the pass_context signature, however I don't think this is a great idea, as this isn't type safe: at runtime, ctx is still an instance of Context.

the click docs clearly indicate that ctx.obj is a dict type.

I couldn't find anything stating this. Do you know in which section of the docs this is described? The type hint for obj is Any, so I think you can allow any object.

@Viicos Viicos linked a pull request Jan 6, 2024 that will close this issue
6 tasks
@thehale
Copy link

thehale commented Jan 7, 2024

the click docs clearly indicate that ctx.obj is a dict type.

I couldn't find anything stating this. Do you know in which section of the docs this is described? The type hint for obj is Any, so I think you can allow any object.

You appear to be correct. I was looking at the code example for Nested Handling and Contexts which includes an assertion that ctx.obj is a dict. That said, upon closer look it appears that one could assert that ctx.obj is indeed any type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants