Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-jdatetime for openSUSE:Factory checked in at 2025-01-22 16:39:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jdatetime (Old) and /work/SRC/openSUSE:Factory/.python-jdatetime.new.5589 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jdatetime" Wed Jan 22 16:39:25 2025 rev:9 rq:1239519 version:5.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-jdatetime/python-jdatetime.changes 2024-11-21 15:19:39.236013423 +0100 +++ /work/SRC/openSUSE:Factory/.python-jdatetime.new.5589/python-jdatetime.changes 2025-01-22 16:39:26.701663465 +0100 @@ -1,0 +2,9 @@ +Wed Jan 22 12:35:47 UTC 2025 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 5.1.0 + * Fix compare datetime with diffrent timezones #159 + * Drop Python 3.8 support + * Add support for Python 3.13 +- Drop py313-support.patch, merged upstream + +------------------------------------------------------------------- Old: ---- py313-support.patch v5.0.0.tar.gz New: ---- v5.1.0.tar.gz BETA DEBUG BEGIN: Old: * Add support for Python 3.13 - Drop py313-support.patch, merged upstream BETA DEBUG END: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jdatetime.spec ++++++ --- /var/tmp/diff_new_pack.c2kmij/_old 2025-01-22 16:39:27.617701399 +0100 +++ /var/tmp/diff_new_pack.c2kmij/_new 2025-01-22 16:39:27.621701565 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-jdatetime # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,14 +18,13 @@ %{?sle15_python_module_pythons} Name: python-jdatetime -Version: 5.0.0 +Version: 5.1.0 Release: 0 Summary: Jalali datetime binding for python License: Python-2.0 Group: Development/Languages/Python URL: https://github.com/slashmili/python-jalali Source: https://github.com/slashmili/python-jalali/archive/v%{version}.tar.gz -Patch1: https://github.com/slashmili/python-jalali/commit/ac6c2052e41462714431946cf13cee28967082b4.patch#/py313-support.patch BuildRequires: %{python_module jalali-core} BuildRequires: %{python_module pip} BuildRequires: %{python_module pytzdata} ++++++ v5.0.0.tar.gz -> v5.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-jalali-5.0.0/.github/workflows/test.yml new/python-jalali-5.1.0/.github/workflows/test.yml --- old/python-jalali-5.0.0/.github/workflows/test.yml 2024-03-26 10:39:26.000000000 +0100 +++ new/python-jalali-5.1.0/.github/workflows/test.yml 2025-01-13 12:03:02.000000000 +0100 @@ -12,7 +12,7 @@ strategy: matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] name: OS ${{ matrix.os}} - Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v2 @@ -37,7 +37,7 @@ - name: Setup python uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.9" architecture: x64 - name: Install dependencies run: | @@ -54,7 +54,7 @@ - name: Setup python uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.9" architecture: x64 - name: Install dependencies run: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-jalali-5.0.0/CHANGELOG.md new/python-jalali-5.1.0/CHANGELOG.md --- old/python-jalali-5.0.0/CHANGELOG.md 2024-03-26 10:39:26.000000000 +0100 +++ new/python-jalali-5.1.0/CHANGELOG.md 2025-01-13 12:03:02.000000000 +0100 @@ -1,5 +1,16 @@ # Changelog +## [5.1.0] - 2025-01-13 + +### Fixed + +* Fix compare datetime with diffrent timezones #159 + +### Add +* Drop Python 3.8 support +* Add support for Python 3.13 + + ## [5.0.0] - 2024-03-26 ### Add diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-jalali-5.0.0/appveyor.yml new/python-jalali-5.1.0/appveyor.yml --- old/python-jalali-5.0.0/appveyor.yml 2024-03-26 10:39:26.000000000 +0100 +++ new/python-jalali-5.1.0/appveyor.yml 2025-01-13 12:03:02.000000000 +0100 @@ -2,7 +2,6 @@ environment: matrix: - - PYTHON: "C:\\Python38" - PYTHON: "C:\\Python39" APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON: "C:\\Python310" @@ -11,12 +10,14 @@ APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON: "C:\\Python312" APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - PYTHON: "C:\\Python313" + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" install: - - py -m pip install tzdata + - py -m pip install . tzdata test_script: - py -m unittest discover tests diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-jalali-5.0.0/jdatetime/__init__.py new/python-jalali-5.1.0/jdatetime/__init__.py --- old/python-jalali-5.0.0/jdatetime/__init__.py 2024-03-26 10:39:26.000000000 +0100 +++ new/python-jalali-5.1.0/jdatetime/__init__.py 2025-01-13 12:03:02.000000000 +0100 @@ -16,7 +16,7 @@ from jalali_core import GregorianToJalali, JalaliToGregorian, j_days_in_month -__VERSION__ = "5.0.0" +__VERSION__ = "5.1.0" MINYEAR = 1 MAXYEAR = 9377 @@ -576,37 +576,13 @@ return fmt % getattr(self, attr)() def _strftime_p(self): - try: - if self.hour >= 12: - return self.j_ampm['PM'] - return self.j_ampm['AM'] - except Exception: - return self.j_ampm['AM'] + return self.j_ampm['AM'] def _strftime_z(self): - try: - sign = "+" - diff = self.utcoffset() - diff_sec = diff.seconds - if diff.days > 0 or diff.days < -1: - raise ValueError( - "tzinfo.utcoffset() returned big time delta! ; must be in -1439 .. 1439" - ) - if diff.days != 0: - sign = "-" - diff_sec = (1 * 24 * 60 * 60) - diff_sec - tmp_min = diff_sec / 60 - diff_hour = tmp_min / 60 - diff_min = tmp_min % 60 - return '%s%02.d%02.d' % (sign, diff_hour, diff_min) - except AttributeError: - return '' + return '' def _strftime_cap_z(self): - if hasattr(self, 'tzname') and self.tzname() is not None: - return self.tzname() - else: - return '' + return '' def _strftime_c(self): return self.strftime("%a %b %d %H:%M:%S %Y") @@ -1066,104 +1042,63 @@ """x.__eq__(y) <==> x==y""" if other_datetime is None: return False - if isinstance(other_datetime, py_datetime.datetime): - return self.__eq__(datetime.fromgregorian(datetime=other_datetime)) - if not isinstance(other_datetime, datetime): + + other_locale = other_datetime.locale if isinstance(other_datetime, datetime) else get_locale() + if self.locale != other_locale: + return False + + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() + + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - if ( - self.year == other_datetime.year and - self.month == other_datetime.month and - self.day == other_datetime.day and - self.locale == other_datetime.locale - ): - return ( - self.timetz() == other_datetime.timetz() and - self.microsecond == other_datetime.microsecond - ) - return False + + return self.togregorian() == other_datetime def __ge__(self, other_datetime): """x.__ge__(y) <==> x>=y""" - if isinstance(other_datetime, py_datetime.datetime): - return self.__ge__(datetime.fromgregorian(datetime=other_datetime)) - if not isinstance(other_datetime, datetime): + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() + + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - return ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - ) >= ( - other_datetime.year, - other_datetime.month, - other_datetime.day, - other_datetime.hour, - other_datetime.minute, - other_datetime.second, - other_datetime.microsecond, - ) + return self.togregorian() >= other_datetime def __gt__(self, other_datetime): """x.__gt__(y) <==> x>y""" - if isinstance(other_datetime, py_datetime.datetime): - return self.__gt__(datetime.fromgregorian(datetime=other_datetime)) - if not isinstance(other_datetime, datetime): - return NotImplemented + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() - return ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond - ) > ( - other_datetime.year, - other_datetime.month, - other_datetime.day, - other_datetime.hour, - other_datetime.minute, - other_datetime.second, - other_datetime.microsecond, - ) + if not isinstance(other_datetime, py_datetime.datetime): + return NotImplemented - def __hash__(self): - """x.__hash__() <==> hash(x)""" - gdt = self.togregorian() - return gdt.__hash__() + return self.togregorian() > other_datetime def __le__(self, other_datetime): """x.__le__(y) <==> x<=y""" - if isinstance(other_datetime, py_datetime.datetime): - return self.__le__(datetime.fromgregorian(datetime=other_datetime)) - if not isinstance(other_datetime, datetime): + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() + + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - return not self.__gt__(other_datetime) + return self.togregorian() <= other_datetime def __lt__(self, other_datetime): """x.__lt__(y) <==> x<y""" - if isinstance(other_datetime, py_datetime.datetime): - return self.__lt__(datetime.fromgregorian(datetime=other_datetime)) - if not isinstance(other_datetime, datetime): - return NotImplemented - return not self.__ge__(other_datetime) + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() - def __ne__(self, other_datetime): - """x.__ne__(y) <==> x!=y""" - if other_datetime is None: - return True - if isinstance(other_datetime, py_datetime.datetime): - return self.__ne__(datetime.fromgregorian(datetime=other_datetime)) - if not isinstance(other_datetime, datetime): + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - return not self.__eq__(other_datetime) + return self.togregorian() < other_datetime + + def __hash__(self): + """x.__hash__() <==> hash(x)""" + gdt = self.togregorian() + return gdt.__hash__() @staticmethod def fromgregorian(**kw): @@ -1340,3 +1275,29 @@ gmtoff_fraction = -gmtoff_fraction timezone = py_datetime.timezone(timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)) return timezone + + def _strftime_p(self): + if self.hour >= 12: + return self.j_ampm['PM'] + return self.j_ampm['AM'] + + def _strftime_z(self): + diff = self.utcoffset() + if diff is None: + return '' + sign = '+' + diff_sec = diff.seconds + if diff.days > 0 or diff.days < -1: + raise ValueError( + 'tzinfo.utcoffset() returned big time delta! ; must be in -1439 .. 1439' + ) + if diff.days != 0: + sign = '-' + diff_sec = (1 * 24 * 60 * 60) - diff_sec + tmp_min = diff_sec / 60 + diff_hour = tmp_min / 60 + diff_min = tmp_min % 60 + return '%s%02.d%02.d' % (sign, diff_hour, diff_min) + + def _strftime_cap_z(self): + return self.tzname() or '' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-jalali-5.0.0/setup.py new/python-jalali-5.1.0/setup.py --- old/python-jalali-5.0.0/setup.py 2024-03-26 10:39:26.000000000 +0100 +++ new/python-jalali-5.1.0/setup.py 2025-01-13 12:03:02.000000000 +0100 @@ -4,7 +4,7 @@ setup( name='jdatetime', - version='5.0.0', + version='5.1.0', packages=['jdatetime', ], license='Python Software Foundation License', keywords='Jalali implementation of Python datetime', @@ -14,18 +14,18 @@ description=("Jalali datetime binding for python"), url="https://github.com/slashmili/python-jalali", long_description=open('README').read(), - python_requires=">=3.8", + python_requires=">=3.9", install_requires=["jalali-core>=1.0"], classifiers=[ "Intended Audience :: Developers", "Intended Audience :: System Administrators", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development", ], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-jalali-5.0.0/tests/test_jdatetime.py new/python-jalali-5.1.0/tests/test_jdatetime.py --- old/python-jalali-5.0.0/tests/test_jdatetime.py 2024-03-26 10:39:26.000000000 +0100 +++ new/python-jalali-5.1.0/tests/test_jdatetime.py 2025-01-13 12:03:02.000000000 +0100 @@ -6,6 +6,7 @@ import threading import time from unittest import TestCase, skipIf, skipUnless +from zoneinfo import ZoneInfo import jdatetime @@ -16,17 +17,6 @@ except ImportError: greenlet_installed = False -try: - import zoneinfo - if platform.system() == 'Windows': - # Windows systems, do not have system time zone data available - # therefore tzdata is required. See: - # https://docs.python.org/3/library/zoneinfo.html#data-sources - import tzinfo as _ # noqa -except ImportError: - zoneinfo = None - - from tests import load_pickle @@ -446,54 +436,12 @@ with self.assertRaises(ValueError, msg=msg): jdatetime.datetime.strptime(date_string, date_format) - def test_datetime_eq(self): - date_string = "1363-6-6 12:13:14" - date_format = "%Y-%m-%d %H:%M:%S" - - dt1 = jdatetime.datetime.strptime(date_string, date_format) - - date_string = "1364-6-6 12:13:14" - dt2 = jdatetime.datetime.strptime(date_string, date_format) - - self.assertNotEqual(dt2, dt1) - - def test_datetime_eq_now(self): - import time - dt1 = jdatetime.datetime.now() - time.sleep(0.1) - dt2 = jdatetime.datetime.now() - self.assertNotEqual(dt2, dt1) - def test_timetz(self): teh = TehranTime() dt_gmt = datetime.datetime(2015, 6, 27, 1, 2, 3, tzinfo=teh) self.assertEqual("01:02:03+03:30", dt_gmt.timetz().__str__()) - def test_datetime_eq_diff_tz(self): - gmt = GMTTime() - teh = TehranTime() - - dt_gmt = datetime.datetime(2015, 6, 27, 0, 0, 0, tzinfo=gmt) - dt_teh = datetime.datetime(2015, 6, 27, 3, 30, 0, tzinfo=teh) - self.assertEqual(dt_teh, dt_gmt, "In standrd python datetime, __eq__ considers timezone") - - jdt_gmt = jdatetime.datetime(1389, 2, 17, 0, 0, 0, tzinfo=gmt) - - jdt_teh = jdatetime.datetime(1389, 2, 17, 3, 30, 0, tzinfo=teh) - - self.assertEqual(jdt_teh, jdt_gmt) - - def test_datetimes_with_different_locales_are_not_equal(self): - dt_en = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='en_US') - dt_fa = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='fa_IR') - self.assertNotEqual(dt_en, dt_fa) - - def test_datetimes_with_different_locales_inequality_works(self): - dt_en = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='en_US') - dt_fa = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='fa_IR') - self.assertTrue(dt_en != dt_fa) - def test_fromgregorian_accepts_named_argument_of_date_and_locale(self): gd = datetime.date(2018, 7, 14) jdt = jdatetime.datetime.fromgregorian(date=gd, locale='nl_NL') @@ -645,7 +593,7 @@ if platform.system() == 'Windows': locale.setlocale(locale.LC_ALL, 'English_United States') else: - locale.resetlocale() + locale.setlocale(locale.LC_ALL, '') def test_with_fa_locale(self): self.set_fa_locale() @@ -754,9 +702,8 @@ self.assertEqual(milliseconds, '1398-04-11T11:06:05.123') self.assertEqual(microseconds, '1398-04-11T11:06:05.123456') - @skipIf(zoneinfo is None, "ZoneInfo not supported!") def test_zoneinfo_as_timezone(self): - tzinfo = zoneinfo.ZoneInfo('Asia/Tehran') + tzinfo = ZoneInfo('Asia/Tehran') jdt = jdatetime.datetime(1398, 4, 11, 11, 6, 5, 123456, tzinfo=tzinfo) self.assertEqual(str(jdt), '1398-04-11 11:06:05.123456+0430') @@ -769,6 +716,130 @@ self.assertEqual(dt, jdatetime.datetime(1400, 10, 11, 1, 2, 3, 30)) +class TestJdatetimeComparison(TestCase): + # __eq__ + def test_eq_datetime(self): + date_string = "1363-6-6 12:13:14" + date_format = "%Y-%m-%d %H:%M:%S" + + dt1 = jdatetime.datetime.strptime(date_string, date_format) + + date_string = "1364-6-6 12:13:14" + dt2 = jdatetime.datetime.strptime(date_string, date_format) + + self.assertNotEqual(dt2, dt1) + + def test_eq_datetime_now(self): + import time + dt1 = jdatetime.datetime.now() + time.sleep(0.1) + dt2 = jdatetime.datetime.now() + self.assertNotEqual(dt2, dt1) + + def test_eq_datetime_diff_tz(self): + gmt = GMTTime() + teh = TehranTime() + + dt_gmt = datetime.datetime(2015, 6, 27, 0, 0, 0, tzinfo=gmt) + dt_teh = datetime.datetime(2015, 6, 27, 3, 30, 0, tzinfo=teh) + self.assertEqual(dt_teh, dt_gmt, "In standrd python datetime, __eq__ considers timezone") + + jdt_gmt = jdatetime.datetime(1389, 2, 17, 0, 0, 0, tzinfo=gmt) + jdt_teh = jdatetime.datetime(1389, 2, 17, 3, 30, 0, tzinfo=teh) + self.assertEqual(jdt_teh, jdt_gmt) + + def test_eq_datetimes_with_different_locales_are_not_equal(self): + dt_en = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='en_US') + dt_fa = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='fa_IR') + self.assertNotEqual(dt_en, dt_fa) + self.assertNotEqual(dt_fa, dt_en) + + def test_eq_with_none(self): + dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, locale='fa_IR') + self.assertFalse(dt1.__eq__(None)) + + def test_eq_with_not_implemented(self): + dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, locale='fa_IR') + dt2 = "not a datetime object" + self.assertFalse(dt1 == dt2) + + # __ge__ + def test_ge_with_same_datetime(self): + dt1 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) + + self.assertTrue(dt1 >= dt2) + + def test_ge_with_greater_datetime(self): + dt1 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 7, 12, 0, 0) + + self.assertTrue(dt1 >= dt2) + + def test_ge_with_lesser_datetime(self): + dt1 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 9, 12, 0, 0) + + self.assertFalse(dt1 >= dt2) + + # __gt__ + def test_gt_with_same_datetime(self): + dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) + dt2 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) + + self.assertFalse(dt1 > dt2) + + def test_gt_with_greater_datetime(self): + dt1 = jdatetime.datetime(2023, 10, 1, 12, 0, 0) + dt2 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) + + self.assertTrue(dt1 > dt2) + + def test_gt_with_lesser_datetime(self): + dt1 = jdatetime.datetime(2023, 9, 29, 12, 0, 0) + dt2 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) + + self.assertFalse(dt1 > dt2) + + # __le__ + def test_le_with_same_datetime(self): + dt1 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + + self.assertTrue(dt1 <= dt2) + + def test_le_with_greater_datetime(self): + dt1 = jdatetime.datetime(1402, 7, 2, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + + self.assertFalse(dt1 <= dt2) + + def test_le_with_lesser_datetime(self): + dt1 = jdatetime.datetime(1402, 6, 30, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + + self.assertTrue(dt1 <= dt2) + + # __lt__ + def test_lt_with_same_datetime(self): + dt1 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + + self.assertFalse(dt1 < dt2) + + def test_lt_with_greater_datetime(self): + dt1 = jdatetime.datetime(1402, 7, 2, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + + self.assertFalse(dt1 < dt2) + + def test_lt_with_lesser_datetime(self): + dt1 = jdatetime.datetime(1402, 6, 30, 12, 0, 0) + dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) + + self.assertTrue(dt1 < dt2) + + class TestJdatetimeGetSetLocale(TestCase): @staticmethod def record_thread_locale(record, event, locale): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-jalali-5.0.0/tox.ini new/python-jalali-5.1.0/tox.ini --- old/python-jalali-5.0.0/tox.ini 2024-03-26 10:39:26.000000000 +0100 +++ new/python-jalali-5.1.0/tox.ini 2025-01-13 12:03:02.000000000 +0100 @@ -1,16 +1,16 @@ [tox] envlist = - py{38,39,310,311,312} + py{39,310,311,312,313} flake8 isort [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 [testenv] deps =