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

Commit 0a63a6e

Browse filesBrowse files
oskipaHugo EstradaKludexflorimondmanca
authored
Support str and datetime on expires parameter on the set_cookie method (#1908)
Co-authored-by: Hugo Estrada <hugoestrada@cal.berkeley.edu> Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com> Co-authored-by: Florimond Manca <florimond.manca@protonmail.com>
1 parent 94a22b8 commit 0a63a6e
Copy full SHA for 0a63a6e

File tree

3 files changed

+47
-5
lines changed
Filter options

3 files changed

+47
-5
lines changed

‎docs/responses.md

Copy file name to clipboardExpand all lines: docs/responses.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Signature: `Response.set_cookie(key, value, max_age=None, expires=None, path="/"
3636
* `key` - A string that will be the cookie's key.
3737
* `value` - A string that will be the cookie's value.
3838
* `max_age` - An integer that defines the lifetime of the cookie in seconds. A negative integer or a value of `0` will discard the cookie immediately. `Optional`
39-
* `expires` - An integer that defines the number of seconds until the cookie expires. `Optional`
39+
* `expires` - Either an integer that defines the number of seconds until the cookie expires, or a datetime. `Optional`
4040
* `path` - A string that specifies the subset of routes to which the cookie will apply. `Optional`
4141
* `domain` - A string that specifies the domain for which the cookie is valid. `Optional`
4242
* `secure` - A bool indicating that the cookie will only be sent to the server if request is made using SSL and the HTTPS protocol. `Optional`

‎starlette/responses.py

Copy file name to clipboardExpand all lines: starlette/responses.py
+7-3Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import stat
55
import sys
66
import typing
7-
from email.utils import formatdate
7+
from datetime import datetime
8+
from email.utils import format_datetime, formatdate
89
from functools import partial
910
from mimetypes import guess_type as mimetypes_guess_type
1011
from urllib.parse import quote
@@ -105,7 +106,7 @@ def set_cookie(
105106
key: str,
106107
value: str = "",
107108
max_age: typing.Optional[int] = None,
108-
expires: typing.Optional[int] = None,
109+
expires: typing.Optional[typing.Union[datetime, str, int]] = None,
109110
path: str = "/",
110111
domain: typing.Optional[str] = None,
111112
secure: bool = False,
@@ -117,7 +118,10 @@ def set_cookie(
117118
if max_age is not None:
118119
cookie[key]["max-age"] = max_age
119120
if expires is not None:
120-
cookie[key]["expires"] = expires
121+
if isinstance(expires, datetime):
122+
cookie[key]["expires"] = format_datetime(expires, usegmt=True)
123+
else:
124+
cookie[key]["expires"] = expires
121125
if path is not None:
122126
cookie[key]["path"] = path
123127
if domain is not None:

‎tests/test_responses.py

Copy file name to clipboardExpand all lines: tests/test_responses.py
+39-1Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import datetime as dt
12
import os
3+
import time
4+
from http.cookies import SimpleCookie
25

36
import anyio
47
import pytest
@@ -288,7 +291,11 @@ def test_file_response_with_inline_disposition(tmpdir, test_client_factory):
288291
assert response.headers["content-disposition"] == expected_disposition
289292

290293

291-
def test_set_cookie(test_client_factory):
294+
def test_set_cookie(test_client_factory, monkeypatch):
295+
# Mock time used as a reference for `Expires` by stdlib `SimpleCookie`.
296+
mocked_now = dt.datetime(2100, 1, 22, 12, 0, 0, tzinfo=dt.timezone.utc)
297+
monkeypatch.setattr(time, "time", lambda: mocked_now.timestamp())
298+
292299
async def app(scope, receive, send):
293300
response = Response("Hello, world!", media_type="text/plain")
294301
response.set_cookie(
@@ -307,6 +314,37 @@ async def app(scope, receive, send):
307314
client = test_client_factory(app)
308315
response = client.get("/")
309316
assert response.text == "Hello, world!"
317+
assert (
318+
response.headers["set-cookie"]
319+
== "mycookie=myvalue; Domain=localhost; expires=Fri, 22 Jan 2100 12:00:10 GMT; "
320+
"HttpOnly; Max-Age=10; Path=/; SameSite=none; Secure"
321+
)
322+
323+
324+
@pytest.mark.parametrize(
325+
"expires",
326+
[
327+
pytest.param(
328+
dt.datetime(2100, 1, 22, 12, 0, 10, tzinfo=dt.timezone.utc), id="datetime"
329+
),
330+
pytest.param("Fri, 22 Jan 2100 12:00:10 GMT", id="str"),
331+
pytest.param(10, id="int"),
332+
],
333+
)
334+
def test_expires_on_set_cookie(test_client_factory, monkeypatch, expires):
335+
# Mock time used as a reference for `Expires` by stdlib `SimpleCookie`.
336+
mocked_now = dt.datetime(2100, 1, 22, 12, 0, 0, tzinfo=dt.timezone.utc)
337+
monkeypatch.setattr(time, "time", lambda: mocked_now.timestamp())
338+
339+
async def app(scope, receive, send):
340+
response = Response("Hello, world!", media_type="text/plain")
341+
response.set_cookie("mycookie", "myvalue", expires=expires)
342+
await response(scope, receive, send)
343+
344+
client = test_client_factory(app)
345+
response = client.get("/")
346+
cookie: SimpleCookie = SimpleCookie(response.headers.get("set-cookie"))
347+
assert cookie["mycookie"]["expires"] == "Fri, 22 Jan 2100 12:00:10 GMT"
310348

311349

312350
def test_delete_cookie(test_client_factory):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.