Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

In my open-source library, we've swapped over to use Pydantic for our dataclasses. However, I really want to preserve type hint info, so a lot of our parent methods look like:

parent.add_config(Config(...))

When I think it would be more elegant to have something like:

parent.add_config(...)

Where the parent then makes the Config obejct itself. But if we implement this right now, the user looses all the type hint info as to what they can pass in, unless we manually duplicate docstring and all the fields from Config, which isn't ideal. I'm trying to see if there's anything we can do it use python 3.12's Unpack kwarg type hint.

Here's a tiny example to show what I mean.

from pydantic import BaseModel
from typing_extensions import Unpack


class Person(BaseModel):
    name: str
    age: int


# ERR: Expected TypeDict argument for Unpack
def person_factory(**kwargs: Unpack[Person]):
    return Person(**kwargs)


if __name__ == "__main__":
    steve = person_factory(name="Steve", age=42)

Obviously this doesn't work. TypeDict != dataclass. But I figured, does anyone have any ideas/tricks/something else that might make this factory method type hinting work properly?

You must be logged in to vote

Replies: 5 comments

Comment options

Have also raised with the official python typing repo: python/typing#1495

You must be logged in to vote
0 replies
Comment options

I am encountering a similar scenario and I would really like a solution to this as well.

I have a wrapper class around some data querying and parsing methods. However, it is at this moment quite verbose to invoke.

current code:

# <main.py>

from .lib import fetcher
from .lib import QueryParams

result = fetcher(
    query_params = QueryParams(
        chicken = 'good',
        durian = 'bad',
    ),
    logger = logger,
)

#<lib.py>
class QueryParams: ...
class ParsedData: ...

def queryer(query_params: QueryParams, logger: Logger) -> Response:
    ...
def parser(response: Response, logger: Logger) -> ParsedData:
    ...

class Fetcher(Generic[QueryParams, ParsedData]):
    def __call__(self, *, query_params: QueryParams, logger: Logger) -> ParsedData:
        ...

fetcher = Fetcher(queryer, parser)

Ideally, it should be invoked like this (with type hinting for fetcher):

# <main.py>

from .lib import fetcher

result = fetcher(
    chicken = 'good',
    durian = 'bad',
    logger = logger,
)

#<lib.py>
class QueryParams: ...
class ParsedData: ...

def queryer(query_params: QueryParams, logger: Logger) -> Response:
    ...
def parser(response: Response, logger: Logger) -> ParsedData:
    ...

class Fetcher(Generic[QueryParams, ParsedData]):
    def __call__(self, *, logger: Logger, **kwargs: Unpack[QueryParams]) -> ParsedData:
        query_params = QueryParams(**kwargs)
        ...

fetcher = Fetcher(queryer, parser)

Can anybody suggest a good way to implement this?

You must be logged in to vote
0 replies
Comment options

After reading many comments and ongoing work, I think the parent.add_config(Config(...)) is still the "less worse" way to do it, until there is a proper way to handle it.
But as far as I know, it seems there is currently now way to transform a Pydantic BaseModel to a TypedDict, without the type checker going crazy.

You must be logged in to vote
0 replies
Comment options

I've ran into a similar issue and I don't think it's possible right now due to this: https://peps.python.org/pep-0612/#concatenating-keyword-parameters. From my understanding it's not possible to 'reshuffle' kwargs.

You can however prepend positional args and get type checking (different use case, but could be insightful):
#2419 (comment)

You must be logged in to vote
0 replies
Comment options

Here is one way to do it:

from typing import Any, Callable, ParamSpec, TypeVar

from pydantic import BaseModel


class Person(BaseModel):
    name: str
    age: int


P = ParamSpec('P')
R = TypeVar('R')


def factory(cls: Callable[P, R], /) -> Callable[[Callable[P, R]], Callable[P, R]]:
    def inner(func):
        return func

    return inner


@factory(Person)
def person_factory(**kwargs: Any) -> Person:
    return Person(**kwargs)

reveal_type(person_factory)  # (*, name: str, age: int) -> Person
You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.