Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support str and datetime on expires parameter on the set_cookie method #1908

Merged
merged 23 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6b98839
Adds new cookie expire type. Adds function that returns the string if…
Oct 8, 2022
563cb05
Adding tests for datetime expiration and cookie expire format
Oct 8, 2022
c66ca24
Implements PR change suggestions
Oct 13, 2022
8af9d46
Updates test that used str input back to int input
Oct 13, 2022
f9881ba
Merge branch 'master' into issue-1878-fix-cookie-typing
oskipa Oct 13, 2022
5bf86d5
Updates date formatting comments to point to RFCs. Removes expires fr…
Oct 20, 2022
1303802
Deletes extra test
Oct 20, 2022
432358a
Merge branch 'master' into issue-1878-fix-cookie-typing
Oct 21, 2022
d7859be
Merge branch 'master' into issue-1878-fix-cookie-typing
oskipa Nov 1, 2022
af7d3cc
Merge branch 'master' into issue-1878-fix-cookie-typing
oskipa Nov 2, 2022
caed475
Merge branch 'master' into issue-1878-fix-cookie-typing
oskipa Nov 7, 2022
b8e7931
Merge branch 'master' into issue-1878-fix-cookie-typing
Kludex Dec 12, 2022
0433643
simplify code
Kludex Dec 17, 2022
09447b7
Merge branch 'master' into issue-1878-fix-cookie-typing
Kludex Feb 4, 2023
6db2870
refactor: improve tests
Kludex Feb 4, 2023
7eacc85
readd expires on delete cookie method
Kludex Feb 4, 2023
d6b28a7
docs: add note about datetime support on expires
Kludex Feb 4, 2023
d5199ac
tests: use time-machine on test suite
Kludex Feb 4, 2023
2fda4aa
Merge branch 'master' into issue-1878-fix-cookie-typing
Kludex Feb 4, 2023
da8faba
Use monkeypatch instead of time-machine
Kludex Feb 6, 2023
65ccf47
Merge branch 'master' into issue-1878-fix-cookie-typing
Kludex Feb 6, 2023
727f9dc
Merge branch 'master' into issue-1878-fix-cookie-typing
Kludex Feb 6, 2023
b2b6f91
Merge branch 'master' into issue-1878-fix-cookie-typing
Kludex Feb 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions starlette/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import stat
import sys
import typing
from datetime import datetime, timezone
from email.utils import formatdate
from functools import partial
from mimetypes import guess_type as mimetypes_guess_type
Expand Down Expand Up @@ -35,6 +36,12 @@ def guess_type(
return mimetypes_guess_type(url, strict)


# Format based on MDN HTTP documentation on Set-Cookie and Date format
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date
Kludex marked this conversation as resolved.
Show resolved Hide resolved
COOKIE_DATETIME_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"


class Response:
media_type = None
charset = "utf-8"
Expand Down Expand Up @@ -105,7 +112,7 @@ def set_cookie(
key: str,
value: str = "",
max_age: typing.Optional[int] = None,
expires: typing.Optional[int] = None,
expires: typing.Optional[typing.Union[datetime, int]] = None,
path: str = "/",
domain: typing.Optional[str] = None,
secure: bool = False,
Expand All @@ -117,7 +124,10 @@ def set_cookie(
if max_age is not None:
cookie[key]["max-age"] = max_age
if expires is not None:
cookie[key]["expires"] = expires
if isinstance(expires, datetime):
cookie[key]["expires"] = expires.strftime(COOKIE_DATETIME_FORMAT)
else:
cookie[key]["expires"] = expires
if path is not None:
cookie[key]["path"] = path
if domain is not None:
Expand Down Expand Up @@ -148,7 +158,7 @@ def delete_cookie(
self.set_cookie(
key,
max_age=0,
expires=0,
expires=datetime.now(timezone.utc),
florimondmanca marked this conversation as resolved.
Show resolved Hide resolved
path=path,
domain=domain,
secure=secure,
Expand Down
58 changes: 58 additions & 0 deletions tests/test_responses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import os
import re
from datetime import datetime, timedelta, timezone
from http.cookies import SimpleCookie

import anyio
import pytest
Expand Down Expand Up @@ -309,6 +312,61 @@ async def app(scope, receive, send):
assert response.text == "Hello, world!"


def test_set_cookie_with_expire_datetime(test_client_factory):
async def app(scope, receive, send):
response = Response("Hello, world!", media_type="text/plain")
now = datetime.now(timezone.utc)
expires = now + timedelta(minutes=1)
response.set_cookie(
"mycookie",
"myvalue",
max_age=10,
expires=expires,
path="/",
domain="localhost",
secure=True,
httponly=True,
samesite="none",
Kludex marked this conversation as resolved.
Show resolved Hide resolved
)
await response(scope, receive, send)

client = test_client_factory(app)
response = client.get("/")
cookie: SimpleCookie = SimpleCookie(response.headers.get("set-cookie"))

assert cookie["mycookie"]
florimondmanca marked this conversation as resolved.
Show resolved Hide resolved


def test_set_cookie_expire_format(test_client_factory):
async def app(scope, receive, send):
response = Response("Hello, world!", media_type="text/plain")
now = datetime.now(timezone.utc)
expires = now + timedelta(minutes=1)
response.set_cookie(
"mycookie",
"myvalue",
max_age=10,
expires=expires,
path="/",
domain="localhost",
secure=True,
httponly=True,
samesite="none",
)
await response(scope, receive, send)

client = test_client_factory(app)
response = client.get("/")
cookie: SimpleCookie = SimpleCookie(response.headers.get("set-cookie"))

expires = cookie["mycookie"]["expires"]

# Date format spec from
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date
pattern = r"\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2}"
assert re.search(pattern, expires)


def test_delete_cookie(test_client_factory):
async def app(scope, receive, send):
request = Request(scope, receive)
Expand Down