diff --git a/Do-not-turn-DeprecationWarning-into-Exception.patch b/Do-not-turn-DeprecationWarning-into-Exception.patch index 7633f35..af56162 100644 --- a/Do-not-turn-DeprecationWarning-into-Exception.patch +++ b/Do-not-turn-DeprecationWarning-into-Exception.patch @@ -1,11 +1,12 @@ -diff -Nur tornado-6.0.4/tornado/test/runtests.py tornado-6.0.4-new/tornado/test/runtests.py ---- tornado-6.0.4/tornado/test/runtests.py 2020-03-02 20:21:37.000000000 +0100 -+++ tornado-6.0.4-new/tornado/test/runtests.py 2020-09-14 09:21:31.818678680 +0200 -@@ -126,7 +126,6 @@ - # Tornado generally shouldn't use anything deprecated, but some of - # our dependencies do (last match wins). - warnings.filterwarnings("ignore", category=DeprecationWarning) -- warnings.filterwarnings("error", category=DeprecationWarning, module=r"tornado\..*") - warnings.filterwarnings("ignore", category=PendingDeprecationWarning) - warnings.filterwarnings( - "error", category=PendingDeprecationWarning, module=r"tornado\..*" +diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py +index 6075b1e..dc4fb89 100644 +--- a/tornado/test/runtests.py ++++ b/tornado/test/runtests.py +@@ -123,7 +123,6 @@ def main(): + # Tornado generally shouldn't use anything deprecated, but some of + # our dependencies do (last match wins). + warnings.filterwarnings("ignore", category=DeprecationWarning) +- warnings.filterwarnings("error", category=DeprecationWarning, module=r"tornado\..*") + warnings.filterwarnings("ignore", category=PendingDeprecationWarning) + warnings.filterwarnings( + "error", category=PendingDeprecationWarning, module=r"tornado\..*" diff --git a/python-tornado-Python-3.12.patch b/python-tornado-Python-3.12.patch new file mode 100644 index 0000000..8ff07b1 --- /dev/null +++ b/python-tornado-Python-3.12.patch @@ -0,0 +1,404 @@ +diff --git a/requirements.txt b/requirements.txt +index 349db21..e96c870 100644 +--- a/requirements.txt ++++ b/requirements.txt +@@ -12,7 +12,7 @@ black==22.12.0 + # via -r requirements.in + build==0.10.0 + # via pip-tools +-cachetools==5.2.1 ++cachetools==5.3.1 + # via tox + certifi==2022.12.7 + # via requests +@@ -32,7 +32,7 @@ docutils==0.17.1 + # via + # sphinx + # sphinx-rtd-theme +-filelock==3.9.0 ++filelock==3.12.0 + # via + # tox + # virtualenv +@@ -54,7 +54,7 @@ mypy-extensions==0.4.3 + # via + # black + # mypy +-packaging==23.0 ++packaging==23.1 + # via + # build + # pyproject-api +@@ -64,7 +64,7 @@ pathspec==0.10.3 + # via black + pip-tools==6.12.1 + # via -r requirements.in +-platformdirs==2.6.2 ++platformdirs==3.5.1 + # via + # black + # tox +@@ -77,7 +77,7 @@ pyflakes==3.0.1 + # via flake8 + pygments==2.14.0 + # via sphinx +-pyproject-api==1.5.0 ++pyproject-api==1.5.1 + # via tox + pyproject-hooks==1.0.0 + # via build +@@ -115,7 +115,7 @@ tomli==2.0.1 + # mypy + # pyproject-api + # tox +-tox==4.3.5 ++tox==4.6.0 + # via -r requirements.in + types-pycurl==7.45.2.0 + # via -r requirements.in +@@ -123,7 +123,7 @@ typing-extensions==4.4.0 + # via mypy + urllib3==1.26.14 + # via requests +-virtualenv==20.17.1 ++virtualenv==20.23.0 + # via tox + wheel==0.38.4 + # via pip-tools +diff --git a/tornado/httputil.py b/tornado/httputil.py +index 9c341d4..b21d804 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 55072af..c552670 100644 +--- a/tornado/locale.py ++++ b/tornado/locale.py +@@ -333,7 +333,7 @@ class Locale(object): + 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 @@ class Locale(object): + + 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 a71ec0a..a41040e 100644 +--- a/tornado/test/httpclient_test.py ++++ b/tornado/test/httpclient_test.py +@@ -28,7 +28,7 @@ from tornado.iostream import IOStream + 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 @@ class HTTPRequestTestCase(unittest.TestCase): + 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 8424491..aa9b6ee 100644 +--- a/tornado/test/httputil_test.py ++++ b/tornado/test/httputil_test.py +@@ -13,6 +13,7 @@ from tornado.httputil import ( + 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 @@ class FormatTimestampTest(unittest.TestCase): + 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 ee74cb0..a2e0872 100644 +--- a/tornado/test/locale_test.py ++++ b/tornado/test/locale_test.py +@@ -91,45 +91,55 @@ class EnglishTest(unittest.TestCase): + 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 c2d057c..a9bce04 100644 +--- a/tornado/test/web_test.py ++++ b/tornado/test/web_test.py +@@ -404,10 +404,10 @@ class CookieTest(WebTestCase): + 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 @@ class DateHeaderTest(SimpleHandlerTestCase): + + 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 @@ class XSRFCookieKwargsTest(SimpleHandlerTestCase): + 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 5651404..439e02c 100644 +--- a/tornado/web.py ++++ b/tornado/web.py +@@ -647,7 +647,9 @@ class RequestHandler(object): + 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 @@ class RequestHandler(object): + 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 @@ class StaticFileHandler(RequestHandler): + # 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 @@ class StaticFileHandler(RequestHandler): + 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 @@ class StaticFileHandler(RequestHandler): + # 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/python-tornado.spec b/python-tornado.spec index e6864dc..975ba89 100644 --- a/python-tornado.spec +++ b/python-tornado.spec @@ -11,7 +11,7 @@ ideal for real-time web services.} Name: python-%{srcname} Version: 6.3.2 -Release: 3%{?dist} +Release: 4%{?dist} Summary: Scalable, non-blocking web server and tools License: Apache-2.0 @@ -21,6 +21,9 @@ Source0: https://github.com/tornadoweb/tornado/archive/v%{version}/%{srcn # Do not turn DeprecationWarning in tornado module into Exception # fixes FTBFS with Python 3.8 Patch1: Do-not-turn-DeprecationWarning-into-Exception.patch +# Fixes for Python 3.12 - rebased +# https://github.com/tornadoweb/tornado/pull/3288 +Patch2: python-tornado-Python-3.12.patch BuildRequires: gcc BuildRequires: python3-devel @@ -66,6 +69,9 @@ export TRAVIS=true %doc demos %changelog +* Thu Jun 22 2023 Orion Poplawski - 6.3.2-4 +- Add upstream patch for Python 3.12 support + * Tue Jun 13 2023 Python Maint - 6.3.2-3 - Rebuilt for Python 3.12