Is there some way I can use a hyphen in the name of a keyword argument as in the example below?
set_cookie("cookieName", "cookieValue", Max-Age=0, Expires="")
Is there some way I can use a hyphen in the name of a keyword argument as in the example below?
set_cookie("cookieName", "cookieValue", Max-Age=0, Expires="")
No. Hyphens are never valid characters in Python names. That includes function names, variable names, and argument names. Use an underscore instead.
Really, you should just follow the PEP 8 naming conventions; theyāre standard across most Python code. That means for argument names, you should use all lowercase with underscores for spaces.
You could have the cookie attributes as a dictionary instead.
That is unfortunate because in the case of http cookies I want to pass optional attributes; one of which may be āMax-Ageā.
options = {
"Max-Age": 0,
"Expires": ""
}
set_cookie("cookieName", "cookieValue", **options)
@elis.byberi I really wouldnāt do this. **kwargs is intended to let you pass keyword arguments, so it seems like a bad idea to expect things that arenāt valid keyword arguments.
If you adopted this signature, anyone using set_cookie would have to create a dictionary and unpack it. You might as well save them the step of unpacking by taking a dictionary argument directly.
Yes, I did unpack it erroneously.
options = {
"Max-Age": 0,
"Expires": ""
}
set_cookie("cookieName", "cookieValue", options) # not **options
You will need to format the cookie string later, so it is easier to pass the optional values in a dictionary and simply iterate over them, or use a similar approach.
My workaround for this is usually to allow both. Have an optional positional argument that takes a dictionary context, then allow **kwargs as overrides.
def set_cookie(__cookie: CookieTD | None = None, /, **kwargs: Unpack[CookieTD]):
opts = __cookie or {}
opts.update(kwargs)
... # do something with the resolved cookie options
Note that you will likely need to use a deepcopy if any of the options are mutable containers (list, set, dict, etc), but this pattern has been pretty useful for me in a ton of cases. Especially when Iām needing to allow per function/method overrides of a whole giant set of parameters. This lets me create some defaults that can be passed as the positional, then allow small changes to be passed as arbitrary keyword arguments.
Plus if the API has something that isnāt a valid name, you can still pass it by unpacking it from a dictionary (which is annoying, but better in my opinion than renaming stuff and causing confusion later).
Passing invalid names to **kwargs is a bit of a code smell, I know, but at runtime theyāre just treated as a normal dictionary, so itās up to you if you want to ignore it or not.
Itās explicitly ok. From 6. Expressions ā Python 3.14.6 documentation :
When
**expressionis used, each key in this mapping must be a string. ⦠A key need not be a Python identifier (e.g."max-temp °F"is acceptable, although it will not match any formal parameter that could be declared).
Of course, there canāt be a matching parameter name (in the normal way, at least), so clearly the function signature has to have a ** formal parameter.
The ability to provide arguments by name in a call, using names not explicitly declared in the signature and the id=value syntax, is really just a convenience for loading a dictionary to give to the ** argument of a call. Non-identifier keys turn up in some numerical libraries.
In this particular context, it might be worth looking at http.cookies ā HTTP state management ā Python 3.14.6 documentation and passing one of those.
The hyphen means subtract so itās not usable in a name.
a = b-c
So Max-Age is Max subtract Age.
Thatās what I was referring to as the ācode smellā. Mainly since I was using an unpacked TypedDict to hint the kwargs. Which means the user will possibly get confusing hints. I donāt think this is really an issue if you provide an alternative name for that situation or just require them to unpack a dictionary.
Iāmve had a similar problem, where Json usually uses a-b when python uses a_b. I just created a dictutils module for my project, which (amongst other stuff) contained following code for converting Json dictionaries to Python dictionaries:
import typing
def _j2p(key: str) -> str:
return key.replace("-", "_")
def json2py(mapping: dict[str, typing.Any], *, recursive: bool = True) -> dict[str, typing.Any]:
new: dict[str, typing.Any] = {}
for key, value in mapping.items():
key = _j2p(key)
if isinstance(value, dict) and recursive:
value = json2py(value, recursive=recursive) # type: ignore
new[key] = value
return new
Then, you could do something like:
options = {
"Max-Age": 0,
"Expires": ""
}
set_cookie("cookieName", "cookieValue", **json2py(options)) # Will not account for PEP-8 upper/lowercasing (e.g. max_age, expires)
Sadly though, some typing-information will be lost (although you could use dataclass like objects to get them back).
Since TypedDict is both one of the most useful and most special cased typing constructs, Iām never too worried about # type: ignore ing stuff that strips the hints. The usefulness of TypedDict exists only at lint time, so if I know that something transforms the dictionary in a compliant way Iāll just force it.
from typing import Any, TypedDict, assert_type
class Opts(TypedDict):
max_age: int
expires: str
def json2py[M](val: dict[str, Any], td: type[M] = dict, *, recursive: bool = True) -> M:
return {
key.replace('-', '_').lower(): json2py(val) if recursive else val
for key, val in val.items()
} if isinstance(val, dict) else val # type: ignore
converted = json2py({'Max-Age': 0, 'Expires': ''}, Opts)
assert_type(converted, Opts)
That oneās an absolute mess, but you can tuck it away deep in your private API so types can still be handled correctly in your public API.