4 ms·
TypedDicts are enormously helpful in defining args a function takes. You can’t do that with either dataclasses / pydantic without passing instantiated objects a
by serjester 2y ago
TypedDicts are enormously helpful in defining args a function takes. You can’t do that with either dataclasses / pydantic without passing instantiated objects as args - which is really cumbersome.
- Spivak 2y agoI actually have a function for this! I use it all the time and it's super helpful. T = TypeVar("T") U = TypeVar("U") V = TypeVar("V") P = ParamSpec("P") def modelargs(model: Callable[P, U]): def _modelargs(func: Callable[[T], V]) -> Callable[P, V]: def __modelargs(*args: P.args, **kwargs: P.kwargs) -> V: return func(model(*args, **kwargs)) # type: ignore return __modelargs # type: ignore return _modelargs class MyModel(BaseModel): foo: str bar: int = 4 @modelargs(MyModel) def test_func(model: MyModel): print(model.foo, model.bar) return 4 test_func(foo="Hello", bar=20) # -> prints Hello 20 If you look in your editor you'll see that the type signature for test_func is `(*, foo: str, bar: int = 4) -> int`. It's unfortunate that you have to write the model type twice but in exchange you don't have to write the args twice.
- rattray 2y agoInteresting! How does that work for nested properties?
- FreakLegion 2y agoI think serjester was talking about PEP 692 for typing kwargs with TypedDicts. Your recipe is a bit different. Pydantic is targeting other use cases. The point of TypedDicts is compile-time safety without run-time overhead. Pydantic is useful for a lot of things, but performance isn't exactly its strong suit (written as of 2.9.2, I was just revisiting it earlier this week). Anyway, in the same spirit of function signature hacking, I've found the following useful for "inheriting" them: _T = TypeVar("_T", bound=Callable) def inherit_signature(_function: _T) -> Callable[..., _T]: return lambda f: f # Requests for example has some long signatures (via typeshed). class CustomSession(requests.Session): @inherit_signature(requests.Session.post) def post(self, url: str, *args: Any, **kwargs: Any) -> requests.Response: ... And now for CustomSession.post the editor sees: def post( self: Session, url: str | bytes, data: _Data | None = None, json: Any | None = None, *, params: _Params | None = ..., headers: _HeadersUpdateMapping | None = ..., cookies: RequestsCookieJar | _TextMapping | None = ..., files: _Files | None = ..., auth: _Auth | None = ..., timeout: _Timeout | None = ..., allow_redirects: bool = ..., proxies: _TextMapping | None = ..., hooks: _HooksInput | None = ..., stream: bool | None = ..., verify: _Verify | None = ..., cert: _Cert | None = ... ) -> Response