Description
Hello! I'm not sure if this is a bug report (probably not), support request (definitely), a request for a missing feature (probably, though what, exactly, I'm not sure), or just a bit of an "experience report", but I'm struggling a bit with TypedDict while trying to lock down our WSGI environ
vars.
It seems that this use case isn't covered particularly well by the current implementation, but I'm not sure if that's just because I'm just not "holding it right". Everything was going swimmingly until I bumped into the WSGI environ
keys, then things started to unravel. It doesn't seem to be possible to declare wsgi.input
as a key using the class-based syntax:
class Environ(TypedDict):
wsgi.input: str
That makes sense, wsgi.input
isn't syntactically legal... So my first instinct was to quote it, just in case, but nah, "illegal target for annotation":
class Environ(TypedDict):
"wsgi.input": str
Oh well, it was worth a try, but I knew up front it was a long shot! So then I tried out the alternative syntax, which seems to do the trick nicely:
Environ = TypedDict("Environ", {"wsgi.input": Reader})
Except now I have to find a way to represent my application-specific headers, and I'd like to keep them separate from the core definitions because I have a few applications that need to make use of them. The PEP says that there's inheritance, which would solve the problem perfectly. My first thought was that TypedDict
would mirror the syntax for type()
and allow me to specify bases
, but another quick glance at the PEP says it's only available if I'm using the class syntax. I thought maybe I could just re-use the dict, but I ran into #4128:
_environ_keys = {"wsgi.input": Reader}
Environ = TypedDict("Environ", _environ_keys)
# BORK! TypedDict() expects a dictionary literal as the second argument
MyEnviron = TypedDict("MyEnviron", {**_environ_keys, "HTTP_X_FLEEB": Optional[str]})
That was the end of that thread for now, I went back to just rolling it all into one big TypedDict
declared using the alternative syntax; I'll worry about re-use later.
The last thing I ran into was the problem with accepting "extra keys", outlined in #4617, which prevents me from being able to have a fallback "catch-all" for the HTTP_
variables documented here
Variables corresponding to the client-supplied HTTP request headers (i.e., variables whose names begin with HTTP_). The presence or absence of these variables should correspond with the presence or absence of the appropriate HTTP header in the request.
There's a lot of these, of course! It's HTTP headers we're talking about, after all! I'm not really sure if "extra keys" would solve that problem, but I thought I should add it just to make the story complete.
I'm sort of a bit stuck - I've got a lot of WSGI stuff here that I can't really do much with type-wise unless I go through and unwrap environ
into a dataclass, but some of this stuff is performance sensitive (we use Django for things that aren't and plain ol' WSGI for things that are, which works great!) and a whole dataclass conversion step is not going to keep us inside our performance budgets.
I'll keep pushing it around the page for a little while longer and see what other approaches I can find but it'd be great if there was a way to support WSGI a bit more thoroughly. environ
can be a bit amorphous and TypedDict could be a huge help at making things a bit more resilient and safe to work with.
Thank you!
EDIT: I did keep pushing things around the page. Here's what I currently have (still needs a lot more work, of course): https://github.com/shabbyrobe/wsgitypes. This lets me type the bulk of our WSGI applications; I'll keep adding to it as I find more bits.
I found a way around the inheritance issue: I had been presuming (incorrectly, as it turns out) that trying to inherit a TypedDict declared using the alternative syntax wouldn't work, but it works just fine! For example:
Foo = TypedDict("Foo", {"one": str})
Bar = TypedDict("Bar", {"two": str})
class Baz(Foo, Bar):
pass
yep = {"one": "yep", "two": "yep"}
This seemed to allow the field-combining behavior I was looking for, so I'll run with it for now.