diff --git a/srcpkgs/python3-tornado/patches/3288-utcnow-deprecation.patch b/srcpkgs/python3-tornado/patches/3288-utcnow-deprecation.patch new file mode 100644 index 00000000000..19e7ff9714e --- /dev/null +++ b/srcpkgs/python3-tornado/patches/3288-utcnow-deprecation.patch @@ -0,0 +1,378 @@ +Taken from https://github.com/tornadoweb/tornado/pull/3288 + +From 4d4d80c1d076dcb4e051c969ae4b66557d3856b8 Mon Sep 17 00:00:00 2001 +From: Ben Darnell +Date: Thu, 8 Jun 2023 22:52:19 -0400 +Subject: [PATCH] *: Adapt to deprecation of datetime utc methods + +Python 3.12 deprecates the utcnow and utcfromtimestamp methods and +discourages the use of naive datetimes to represent UTC. This was +previously the main way that Tornado used datetimes (since it was +the only option available in Python 2 before the introduction +of datetime.timezone.utc in Python 3.2). + +- httpclient_test: Test-only change to test that both kinds of datetimes + are supported in If-Modified-Since (this just calls + httputil.format_timestamp) +- httputil: No functional changes, but format_timestamp's + support for both naive and aware datetimes is now tested. +- locale: format_timestamp now supports aware datetimes (in + addition to the existing support for naive datetimes). +- web: Cookie expirations internally use aware datetimes. + StaticFileHandler.get_modified_time now supports both and the + standard implementation returns aware. + +It feels fragile that "naive" and "aware" datetimes are not distinct +types but subject to data-dependent behavior. This change uses +"aware" datetimes throughout Tornado, but some operations (comparisons +and subtraction) fail with mixed datetime types and if I missed any +in this change may cause errors if naive datetimes were used (where +previously naive datetimes would have been required). But that's +apparently the API we have to work with. +--- + tornado/httputil.py | 3 +- + tornado/locale.py | 12 +++-- + tornado/test/httpclient_test.py | 12 ++++- + tornado/test/httputil_test.py | 26 +++++++++- + tornado/test/locale_test.py | 88 ++++++++++++++++++--------------- + tornado/test/web_test.py | 25 +++++----- + tornado/web.py | 28 +++++++---- + 7 files changed, 126 insertions(+), 68 deletions(-) + +diff --git a/tornado/httputil.py b/tornado/httputil.py +index 9c341d47cc..b21d8046c4 100644 +--- a/tornado/httputil.py ++++ b/tornado/httputil.py +@@ -856,7 +856,8 @@ def format_timestamp( + + The argument may be a numeric timestamp as returned by `time.time`, + a time tuple as returned by `time.gmtime`, or a `datetime.datetime` +- object. ++ object. Naive `datetime.datetime` objects are assumed to represent ++ UTC; aware objects are converted to UTC before formatting. + + >>> format_timestamp(1359312200) + 'Sun, 27 Jan 2013 18:43:20 GMT' +diff --git a/tornado/locale.py b/tornado/locale.py +index 55072af28d..c5526703b1 100644 +--- a/tornado/locale.py ++++ b/tornado/locale.py +@@ -333,7 +333,7 @@ def format_date( + shorter: bool = False, + full_format: bool = False, + ) -> str: +- """Formats the given date (which should be GMT). ++ """Formats the given date. + + By default, we return a relative time (e.g., "2 minutes ago"). You + can return an absolute date string with ``relative=False``. +@@ -343,10 +343,16 @@ def format_date( + + This method is primarily intended for dates in the past. + For dates in the future, we fall back to full format. ++ ++ .. versionchanged:: 6.4 ++ Aware `datetime.datetime` objects are now supported (naive ++ datetimes are still assumed to be UTC). + """ + if isinstance(date, (int, float)): +- date = datetime.datetime.utcfromtimestamp(date) +- now = datetime.datetime.utcnow() ++ date = datetime.datetime.fromtimestamp(date, datetime.timezone.utc) ++ if date.tzinfo is None: ++ date = date.replace(tzinfo=datetime.timezone.utc) ++ now = datetime.datetime.now(datetime.timezone.utc) + if date > now: + if relative and (date - now).seconds < 60: + # Due to click skew, things are some things slightly +diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py +index a71ec0afb6..a41040e64a 100644 +--- a/tornado/test/httpclient_test.py ++++ b/tornado/test/httpclient_test.py +@@ -28,7 +28,7 @@ + from tornado.log import gen_log, app_log + from tornado import netutil + from tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog +-from tornado.test.util import skipOnTravis ++from tornado.test.util import skipOnTravis, ignore_deprecation + from tornado.web import Application, RequestHandler, url + from tornado.httputil import format_timestamp, HTTPHeaders + +@@ -887,7 +887,15 @@ def test_body_setter(self): + self.assertEqual(request.body, utf8("foo")) + + def test_if_modified_since(self): +- http_date = datetime.datetime.utcnow() ++ http_date = datetime.datetime.now(datetime.timezone.utc) ++ request = HTTPRequest("http://example.com", if_modified_since=http_date) ++ self.assertEqual( ++ request.headers, {"If-Modified-Since": format_timestamp(http_date)} ++ ) ++ ++ def test_if_modified_since_naive_deprecated(self): ++ with ignore_deprecation(): ++ http_date = datetime.datetime.utcnow() + request = HTTPRequest("http://example.com", if_modified_since=http_date) + self.assertEqual( + request.headers, {"If-Modified-Since": format_timestamp(http_date)} +diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py +index 8424491d87..aa9b6ee253 100644 +--- a/tornado/test/httputil_test.py ++++ b/tornado/test/httputil_test.py +@@ -13,6 +13,7 @@ + from tornado.escape import utf8, native_str + from tornado.log import gen_log + from tornado.testing import ExpectLog ++from tornado.test.util import ignore_deprecation + + import copy + import datetime +@@ -412,8 +413,29 @@ def test_time_tuple(self): + self.assertEqual(9, len(tup)) + self.check(tup) + +- def test_datetime(self): +- self.check(datetime.datetime.utcfromtimestamp(self.TIMESTAMP)) ++ def test_utc_naive_datetime(self): ++ self.check( ++ datetime.datetime.fromtimestamp( ++ self.TIMESTAMP, datetime.timezone.utc ++ ).replace(tzinfo=None) ++ ) ++ ++ def test_utc_naive_datetime_deprecated(self): ++ with ignore_deprecation(): ++ self.check(datetime.datetime.utcfromtimestamp(self.TIMESTAMP)) ++ ++ def test_utc_aware_datetime(self): ++ self.check( ++ datetime.datetime.fromtimestamp(self.TIMESTAMP, datetime.timezone.utc) ++ ) ++ ++ def test_other_aware_datetime(self): ++ # Other timezones are ignored; the timezone is always printed as GMT ++ self.check( ++ datetime.datetime.fromtimestamp( ++ self.TIMESTAMP, datetime.timezone(datetime.timedelta(hours=-4)) ++ ) ++ ) + + + # HTTPServerRequest is mainly tested incidentally to the server itself, +diff --git a/tornado/test/locale_test.py b/tornado/test/locale_test.py +index ee74cb05e8..a2e0872b8f 100644 +--- a/tornado/test/locale_test.py ++++ b/tornado/test/locale_test.py +@@ -91,45 +91,55 @@ def test_format_date(self): + locale.format_date(date, full_format=True), "April 28, 2013 at 6:35 pm" + ) + +- now = datetime.datetime.utcnow() +- +- self.assertEqual( +- locale.format_date(now - datetime.timedelta(seconds=2), full_format=False), +- "2 seconds ago", +- ) +- self.assertEqual( +- locale.format_date(now - datetime.timedelta(minutes=2), full_format=False), +- "2 minutes ago", +- ) +- self.assertEqual( +- locale.format_date(now - datetime.timedelta(hours=2), full_format=False), +- "2 hours ago", +- ) +- +- self.assertEqual( +- locale.format_date( +- now - datetime.timedelta(days=1), full_format=False, shorter=True +- ), +- "yesterday", +- ) +- +- date = now - datetime.timedelta(days=2) +- self.assertEqual( +- locale.format_date(date, full_format=False, shorter=True), +- locale._weekdays[date.weekday()], +- ) +- +- date = now - datetime.timedelta(days=300) +- self.assertEqual( +- locale.format_date(date, full_format=False, shorter=True), +- "%s %d" % (locale._months[date.month - 1], date.day), +- ) +- +- date = now - datetime.timedelta(days=500) +- self.assertEqual( +- locale.format_date(date, full_format=False, shorter=True), +- "%s %d, %d" % (locale._months[date.month - 1], date.day, date.year), +- ) ++ aware_dt = datetime.datetime.now(datetime.timezone.utc) ++ naive_dt = aware_dt.replace(tzinfo=None) ++ for name, now in {"aware": aware_dt, "naive": naive_dt}.items(): ++ with self.subTest(dt=name): ++ self.assertEqual( ++ locale.format_date( ++ now - datetime.timedelta(seconds=2), full_format=False ++ ), ++ "2 seconds ago", ++ ) ++ self.assertEqual( ++ locale.format_date( ++ now - datetime.timedelta(minutes=2), full_format=False ++ ), ++ "2 minutes ago", ++ ) ++ self.assertEqual( ++ locale.format_date( ++ now - datetime.timedelta(hours=2), full_format=False ++ ), ++ "2 hours ago", ++ ) ++ ++ self.assertEqual( ++ locale.format_date( ++ now - datetime.timedelta(days=1), ++ full_format=False, ++ shorter=True, ++ ), ++ "yesterday", ++ ) ++ ++ date = now - datetime.timedelta(days=2) ++ self.assertEqual( ++ locale.format_date(date, full_format=False, shorter=True), ++ locale._weekdays[date.weekday()], ++ ) ++ ++ date = now - datetime.timedelta(days=300) ++ self.assertEqual( ++ locale.format_date(date, full_format=False, shorter=True), ++ "%s %d" % (locale._months[date.month - 1], date.day), ++ ) ++ ++ date = now - datetime.timedelta(days=500) ++ self.assertEqual( ++ locale.format_date(date, full_format=False, shorter=True), ++ "%s %d, %d" % (locale._months[date.month - 1], date.day, date.year), ++ ) + + def test_friendly_number(self): + locale = tornado.locale.get("en_US") +diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py +index 56900d9a00..fb9c3417b9 100644 +--- a/tornado/test/web_test.py ++++ b/tornado/test/web_test.py +@@ -404,10 +404,10 @@ def test_set_cookie_expires_days(self): + match = re.match("foo=bar; expires=(?P.+); Path=/", header) + assert match is not None + +- expires = datetime.datetime.utcnow() + datetime.timedelta(days=10) +- parsed = email.utils.parsedate(match.groupdict()["expires"]) +- assert parsed is not None +- header_expires = datetime.datetime(*parsed[:6]) ++ expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( ++ days=10 ++ ) ++ header_expires = email.utils.parsedate_to_datetime(match.groupdict()["expires"]) + self.assertTrue(abs((expires - header_expires).total_seconds()) < 10) + + def test_set_cookie_false_flags(self): +@@ -1697,11 +1697,10 @@ def get(self): + + def test_date_header(self): + response = self.fetch("/") +- parsed = email.utils.parsedate(response.headers["Date"]) +- assert parsed is not None +- header_date = datetime.datetime(*parsed[:6]) ++ header_date = email.utils.parsedate_to_datetime(response.headers["Date"]) + self.assertTrue( +- header_date - datetime.datetime.utcnow() < datetime.timedelta(seconds=2) ++ header_date - datetime.datetime.now(datetime.timezone.utc) ++ < datetime.timedelta(seconds=2) + ) + + +@@ -3010,10 +3009,12 @@ def test_xsrf_httponly(self): + match = re.match(".*; expires=(?P.+);.*", header) + assert match is not None + +- expires = datetime.datetime.utcnow() + datetime.timedelta(days=2) +- parsed = email.utils.parsedate(match.groupdict()["expires"]) +- assert parsed is not None +- header_expires = datetime.datetime(*parsed[:6]) ++ expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( ++ days=2 ++ ) ++ header_expires = email.utils.parsedate_to_datetime(match.groupdict()["expires"]) ++ if header_expires.tzinfo is None: ++ header_expires = header_expires.replace(tzinfo=datetime.timezone.utc) + self.assertTrue(abs((expires - header_expires).total_seconds()) < 10) + + +diff --git a/tornado/web.py b/tornado/web.py +index 565140493e..439e02c47b 100644 +--- a/tornado/web.py ++++ b/tornado/web.py +@@ -647,7 +647,9 @@ def set_cookie( + if domain: + morsel["domain"] = domain + if expires_days is not None and not expires: +- expires = datetime.datetime.utcnow() + datetime.timedelta(days=expires_days) ++ expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( ++ days=expires_days ++ ) + if expires: + morsel["expires"] = httputil.format_timestamp(expires) + if path: +@@ -698,7 +700,9 @@ def clear_cookie(self, name: str, **kwargs: Any) -> None: + raise TypeError( + f"clear_cookie() got an unexpected keyword argument '{excluded_arg}'" + ) +- expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) ++ expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta( ++ days=365 ++ ) + self.set_cookie(name, value="", expires=expires, **kwargs) + + def clear_all_cookies(self, **kwargs: Any) -> None: +@@ -2812,12 +2816,12 @@ def should_return_304(self) -> bool: + # content has not been modified + ims_value = self.request.headers.get("If-Modified-Since") + if ims_value is not None: +- date_tuple = email.utils.parsedate(ims_value) +- if date_tuple is not None: +- if_since = datetime.datetime(*date_tuple[:6]) +- assert self.modified is not None +- if if_since >= self.modified: +- return True ++ if_since = email.utils.parsedate_to_datetime(ims_value) ++ if if_since.tzinfo is None: ++ if_since = if_since.replace(tzinfo=datetime.timezone.utc) ++ assert self.modified is not None ++ if if_since >= self.modified: ++ return True + + return False + +@@ -2981,6 +2985,10 @@ def get_modified_time(self) -> Optional[datetime.datetime]: + object or None. + + .. versionadded:: 3.1 ++ ++ .. versionchanged:: 6.4 ++ Now returns an aware datetime object instead of a naive one. ++ Subclasses that override this method may return either kind. + """ + stat_result = self._stat() + # NOTE: Historically, this used stat_result[stat.ST_MTIME], +@@ -2991,7 +2999,9 @@ def get_modified_time(self) -> Optional[datetime.datetime]: + # consistency with the past (and because we have a unit test + # that relies on this), we truncate the float here, although + # I'm not sure that's the right thing to do. +- modified = datetime.datetime.utcfromtimestamp(int(stat_result.st_mtime)) ++ modified = datetime.datetime.fromtimestamp( ++ int(stat_result.st_mtime), datetime.timezone.utc ++ ) + return modified + + def get_content_type(self) -> str: diff --git a/srcpkgs/python3-tornado/patches/3335-utcnow-deprecation.patch b/srcpkgs/python3-tornado/patches/3335-utcnow-deprecation.patch new file mode 100644 index 00000000000..23c210d12f6 --- /dev/null +++ b/srcpkgs/python3-tornado/patches/3335-utcnow-deprecation.patch @@ -0,0 +1,81 @@ +Taken from https://github.com/tornadoweb/tornado/pull/3335 + +From 0bfca3f2d72a0c6e5d3ba17caac88a79e7b4e0dd Mon Sep 17 00:00:00 2001 +From: Thomas Grainger +Date: Tue, 3 Oct 2023 15:08:08 +0100 +Subject: [PATCH] remove calls to utcnow and utcfromtimestamp + +--- + demos/blog/templates/feed.xml | 2 +- + demos/s3server/s3server.py | 16 +++++++++++----- + tornado/web.py | 3 ++- + 3 files changed, 14 insertions(+), 7 deletions(-) + +diff --git a/demos/blog/templates/feed.xml b/demos/blog/templates/feed.xml +index a98826c8d3..4a4a252f60 100644 +--- a/demos/blog/templates/feed.xml ++++ b/demos/blog/templates/feed.xml +@@ -5,7 +5,7 @@ + {% if len(entries) > 0 %} + {{ max(e.updated for e in entries).strftime(date_format) }} + {% else %} +- {{ datetime.datetime.utcnow().strftime(date_format) }} ++ {{ datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None).strftime(date_format) }} + {% end %} + http://{{ request.host }}/ + +diff --git a/demos/s3server/s3server.py b/demos/s3server/s3server.py +index 5c5e6af2ba..104a7c8f81 100644 +--- a/demos/s3server/s3server.py ++++ b/demos/s3server/s3server.py +@@ -138,7 +138,9 @@ def get(self): + buckets.append( + { + "Name": name, +- "CreationDate": datetime.datetime.utcfromtimestamp(info.st_ctime), ++ "CreationDate": datetime.datetime.fromtimestamp( ++ info.st_ctime, datetime.timezone.utc ++ ).replace(tzinfo=None), + } + ) + self.render_xml({"ListAllMyBucketsResult": {"Buckets": {"Bucket": buckets}}}) +@@ -183,9 +185,10 @@ def get(self, bucket_name): + info = os.stat(object_path) + c.update( + { +- "LastModified": datetime.datetime.utcfromtimestamp( +- info.st_mtime +- ), ++ "LastModified": datetime.datetime.fromtimestamp( ++ info.st_mtime, ++ datetime.timezone.utc, ++ ).replace(tzinfo=None), + "Size": info.st_size, + } + ) +@@ -231,7 +234,10 @@ def get(self, bucket, object_name): + info = os.stat(path) + self.set_header("Content-Type", "application/unknown") + self.set_header( +- "Last-Modified", datetime.datetime.utcfromtimestamp(info.st_mtime) ++ "Last-Modified", ++ datetime.datetime.fromtimestamp( ++ info.st_mtime, datetime.timezone.utc ++ ).replace(tzinfo=None), + ) + with open(path, "rb") as object_file: + self.finish(object_file.read()) +diff --git a/tornado/web.py b/tornado/web.py +index 333f736808..4416e2013e 100644 +--- a/tornado/web.py ++++ b/tornado/web.py +@@ -2797,7 +2797,8 @@ def set_headers(self) -> None: + if cache_time > 0: + self.set_header( + "Expires", +- datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time), ++ datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) ++ + datetime.timedelta(seconds=cache_time), + ) + self.set_header("Cache-Control", "max-age=" + str(cache_time)) + diff --git a/srcpkgs/python3-tornado/template b/srcpkgs/python3-tornado/template index 103c4cc4f09..65bc0e55ac8 100644 --- a/srcpkgs/python3-tornado/template +++ b/srcpkgs/python3-tornado/template @@ -1,7 +1,7 @@ # Template file for 'python3-tornado' pkgname=python3-tornado -version=6.3.2 -revision=2 +version=6.3.3 +revision=1 build_style=python3-pep517 hostmakedepends="python3-setuptools python3-wheel" makedepends="python3-devel" @@ -13,7 +13,7 @@ license="Apache-2.0" homepage="http://www.tornadoweb.org/" changelog="https://www.tornadoweb.org/en/stable/releases.html" distfiles="${PYPI_SITE}/t/tornado/tornado-${version}.tar.gz" -checksum=4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba +checksum=e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe do_check() { # There is one instance of test_bind_source_ip (the one for