瀏覽代碼

feat: stop supporting python 3.8

trgiangdo 8 月之前
父節點
當前提交
0b53b0c500

+ 1 - 1
.github/workflows/dependencies-management.yml

@@ -22,7 +22,7 @@ jobs:
     timeout-minutes: 20
     strategy:
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         os: [ubuntu-latest]
     permissions:
       contents: write

+ 2 - 2
.github/workflows/overall-tests.yml

@@ -52,7 +52,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         os: [ubuntu-latest, windows-latest, macos-13]
         pipfile-version: ['min', 'max']
     runs-on: ${{ matrix.os }}
@@ -80,7 +80,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         os: [ubuntu-latest, windows-latest, macos-13]
         orchestrator: ['orchestrator_dispatcher', 'standalone']
         pipfile-version: ['min', 'max']

+ 1 - 1
.github/workflows/packaging.yml

@@ -20,7 +20,7 @@ jobs:
     timeout-minutes: 30
     strategy:
       matrix:
-        python-versions: [ '3.8', '3.9', '3.10', '3.11', '3.12']
+        python-versions: ['3.9', '3.10', '3.11', '3.12']
         os: [ubuntu-latest, macos-13, windows-latest]
 
     runs-on: ${{ matrix.os }}

+ 3 - 3
.github/workflows/partial-tests.yml

@@ -23,7 +23,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         os: [ubuntu-latest, windows-latest, macos-13]
     runs-on: ${{ matrix.os }}
     steps:
@@ -143,7 +143,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         os: [ubuntu-latest, windows-latest, macos-13]
     runs-on: ${{ matrix.os }}
     steps:
@@ -186,7 +186,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         os: [ubuntu-latest, windows-latest, macos-13]
     runs-on: ${{ matrix.os }}
     steps:

+ 2 - 2
.github/workflows/publish.yml

@@ -15,7 +15,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/setup-python@v5
         with:
-          python-version: 3.8
+          python-version: 3.9
 
       - name: Extract Github Tag Version
         id: vars
@@ -104,7 +104,7 @@ jobs:
     timeout-minutes: 30
     strategy:
       matrix:
-        python-versions: ['3.8','3.9','3.10', '3.11', '3.12']
+        python-versions: ['3.9','3.10', '3.11', '3.12']
         os: [ubuntu-latest,windows-latest,macos-13]
     runs-on: ${{ matrix.os }}
     steps:

+ 1 - 1
INSTALLATION.md

@@ -5,7 +5,7 @@ There are different ways to install Taipy, depending on how you plan to use it.
 If your goal is to look into the code, modify and improve it, go straight
 to the [source installation](#installing-for-development) section.
 
-Taipy needs your system to have Python 3.8 or above installed.
+Taipy needs your system to have Python 3.9 or above installed.
 
 ## Installing from PyPI
 

+ 0 - 1
Pipfile

@@ -6,7 +6,6 @@ name = "pypi"
 [packages]
 apispec = {extras = ["yaml"], version = "==6.3"}
 apispec-webframeworks = "==0.5.2"
-"backports.zoneinfo" = {version="==0.2.1", markers="python_version < '3.9'", extras=["tzdata"]}
 cookiecutter = "==2.1.1"
 deepdiff = "==6.7.1"
 flask = "==3.0.0"

+ 1 - 1
doc/gui/extension/README.md

@@ -45,7 +45,7 @@ This section explains how to build the custom extension library.
 
 To complete the build of the extension library, we need the following tools:
 
-- Python 3.8 or higher;
+- Python 3.9 or higher;
 - Taipy GUI 2.2 or higher;
 - [Node.js](https://nodejs.org/en/) 18.0 or higher: a JavaScript runtime.<br/>
   This embeds [npm](https://www.npmjs.com/), the Node Package Manager.

+ 1 - 2
doc/gui/extension/pyproject.toml

@@ -7,7 +7,7 @@ version = "1.0.0"
 authors = [ { name = "Taipy" } ]
 description = "A Taipy GUI extension library example."
 readme = { file = "README.md", content-type = "text/markdown" }
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 keywords = [ "taipy" ]
 license = { text = "Apache License 2.0" }
 classifiers = [
@@ -15,7 +15,6 @@ classifiers = [
     # "License :: OSI Approved :: Apache Software License",
     "Natural Language :: English",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",

+ 1 - 2
pyproject.toml

@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "taipy"
 description = "A 360° open-source platform from Python pilots to production-ready web apps."
 readme = "package_desc.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 license = {text = "Apache License 2.0"}
 authors = [{name = "Avaiga", email = "dev@taipy.io"}]
 keywords = ["taipy"]
@@ -15,7 +15,6 @@ classifiers = [
     "License :: OSI Approved :: Apache Software License",
     "Natural Language :: English",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",

+ 1 - 2
taipy/config/pyproject.toml

@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "taipy-config"
 description = "A Taipy package dedicated to easily configure a Taipy application."
 readme = "package_desc.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 license = {text = "Apache License 2.0"}
 authors = [{name = "Avaiga", email = "dev@taipy.io"}]
 keywords = ["taipy-config"]
@@ -15,7 +15,6 @@ classifiers = [
     "License :: OSI Approved :: Apache Software License",
     "Natural Language :: English",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",

+ 2 - 10
taipy/core/_entity/_dag.py

@@ -8,8 +8,8 @@
 # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
+
 import math
-from functools import reduce
 from typing import Any, Dict, List, Tuple
 
 import networkx as nx
@@ -60,7 +60,7 @@ class _DAG:
         if self._width == 1:
             grd_wdt = 1
         else:
-            grd_wdt = self.__lcm(*[len(i) + 1 if len(i) != self._width else len(i) - 1 for i in self._sorted_nodes]) + 1
+            grd_wdt = math.lcm(*[len(i) + 1 if len(i) != self._width else len(i) - 1 for i in self._sorted_nodes]) + 1
         return len(self._sorted_nodes), grd_wdt
 
     def __compute_nodes(self) -> Dict[str, _Node]:
@@ -81,11 +81,3 @@ class _DAG:
 
     def __compute_edges(self, dag) -> List[_Edge]:
         return [_Edge(self.nodes[edge[0].id], self.nodes[edge[1].id]) for edge in dag.edges()]
-
-    @staticmethod
-    def __lcm(*integers) -> int:
-        # Function math.lcm is only implemented for Python 3.9+
-        # For compatibility with Python 3.8 it has been re implemented.
-        if 0 in integers:
-            return 0
-        return reduce(lambda x, y: (x * y) // math.gcd(x, y), integers)

+ 1 - 2
taipy/core/pyproject.toml

@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "taipy-core"
 description = "A Python library to build powerful and customized data-driven back-end applications."
 readme = "package_desc.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 license = {text = "Apache License 2.0"}
 authors = [{name = "Avaiga", email = "dev@taipy.io"}]
 keywords = ["taipy-core"]
@@ -15,7 +15,6 @@ classifiers = [
     "License :: OSI Approved :: Apache Software License",
     "Natural Language :: English",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",

+ 1 - 1
taipy/gui/INSTALLATION.md

@@ -12,7 +12,7 @@ you plan to use it:
 - [Debugging the JavaScript bundle](#debugging-the-javascript-bundle)
 - [Running the tests](#running-the-tests)
 
-Taipy GUI needs your system to have **Python 3.8** or above installed.
+Taipy GUI needs your system to have **Python 3.9** or above installed.
 
 ## Installing the latest release
 

+ 1 - 12
taipy/gui/builder/_element.py

@@ -14,9 +14,7 @@ from __future__ import annotations
 import ast
 import copy
 import inspect
-import io
 import re
-import sys
 import typing as t
 import uuid
 from abc import ABC, abstractmethod
@@ -25,9 +23,6 @@ from types import FrameType, FunctionType
 
 from .._warnings import _warn
 from ..utils import _getscopeattr
-
-if sys.version_info < (3, 9):
-    from ..utils.unparse import _Unparser
 from ._context_manager import _BuilderContextManager
 from ._factory import _BuilderFactory
 from ._utils import _LambdaByName, _python_builtins, _TransformVarToValue
@@ -129,13 +124,7 @@ class _Element(ABC):
             ]
             tree = _TransformVarToValue(self.__calling_frame, args + targets + _python_builtins).visit(lambda_fn)
             ast.fix_missing_locations(tree)
-            if sys.version_info < (3, 9):  # python 3.8 ast has no unparse
-                string_fd = io.StringIO()
-                _Unparser(tree, string_fd)
-                string_fd.seek(0)
-                lambda_text = string_fd.read()
-            else:
-                lambda_text = ast.unparse(tree)
+            lambda_text = ast.unparse(tree)
             lambda_name = f"__lambda_{uuid.uuid4().hex}"
             self._lambdas[lambda_name] = lambda_text
             return f'{{{lambda_name}({", ".join(args)})}}'

+ 1 - 1
taipy/gui/package_desc.md

@@ -35,7 +35,7 @@ you plan to use it:
 - [Debugging the JavaScript bundle](#debugging-the-javascript-bundle)
 - [Running the tests](#running-the-tests)
 
-Taipy GUI needs your system to have **Python 3.8** or above installed.
+Taipy GUI needs your system to have **Python 3.9** or above installed.
 
 ### Installing the latest release
 

+ 1 - 2
taipy/gui/pyproject.toml

@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "taipy-gui"
 description = "Low-code library to create graphical user interfaces on the Web for your Python applications."
 readme = "package_desc.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 license = {text = "Apache License 2.0"}
 authors = [{name = "Avaiga", email = "dev@taipy.io"}]
 keywords = ["taipy-gui"]
@@ -15,7 +15,6 @@ classifiers = [
     "License :: OSI Approved :: Apache Software License",
     "Natural Language :: English",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",

+ 0 - 731
taipy/gui/utils/unparse.py

@@ -1,731 +0,0 @@
-# Copyright 2021-2024 Avaiga Private Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations under the License.
-
-# from https://github.com/python/cpython/blob/3.8/Tools/parser/unparse.py to unparse an ast tree with python < 3.9
-
-import ast
-import io
-import sys
-
-# Large float and imaginary literals get turned into infinities in the AST.
-# We unparse those infinities to INFSTR.
-_INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
-
-
-def _interleave(inter, f, seq):
-    """Call f on each item in seq, calling inter() in between."""
-    seq = iter(seq)
-    try:
-        f(next(seq))
-    except StopIteration:
-        pass
-    else:
-        for x in seq:
-            inter()
-            f(x)
-
-
-class _Unparser:
-    """Methods in this class recursively traverse an AST and
-    output source code for the abstract syntax; original formatting
-    is disregarded."""
-
-    def __init__(self, tree, file=sys.stdout):
-        """Unparser(tree, file=sys.stdout) -> None.
-        Print the source for tree to file."""
-        self.f = file
-        self._indent = 0
-        self.dispatch(tree)
-        print("", file=self.f)
-        self.f.flush()
-
-    def fill(self, text=""):
-        "Indent a piece of text, according to the current indentation level"
-        self.f.write("\n" + "    " * self._indent + text)
-
-    def write(self, text):
-        "Append a piece of text to the current line."
-        self.f.write(text)
-
-    def enter(self):
-        "Print ':', and increase the indentation."
-        self.write(":")
-        self._indent += 1
-
-    def leave(self):
-        "Decrease the indentation level."
-        self._indent -= 1
-
-    def dispatch(self, tree):
-        "Dispatcher function, dispatching tree type T to method _T."
-        if isinstance(tree, list):
-            for t in tree:
-                self.dispatch(t)
-            return
-        meth = getattr(self, "_" + tree.__class__.__name__)
-        meth(tree)
-
-    ############### Unparsing methods ######################
-    # There should be one method per concrete grammar type #
-    # Constructors should be grouped by sum type. Ideally, #
-    # this would follow the order in the grammar, but      #
-    # currently doesn't.                                   #
-    ########################################################
-
-    def _Module(self, tree):
-        for stmt in tree.body:
-            self.dispatch(stmt)
-
-    # stmt
-    def _Expr(self, tree):
-        self.fill()
-        self.dispatch(tree.value)
-
-    def _NamedExpr(self, tree):
-        self.write("(")
-        self.dispatch(tree.target)
-        self.write(" := ")
-        self.dispatch(tree.value)
-        self.write(")")
-
-    def _Import(self, t):
-        self.fill("import ")
-        _interleave(lambda: self.write(", "), self.dispatch, t.names)
-
-    def _ImportFrom(self, t):
-        self.fill("from ")
-        self.write("." * t.level)
-        if t.module:
-            self.write(t.module)
-        self.write(" import ")
-        _interleave(lambda: self.write(", "), self.dispatch, t.names)
-
-    def _Assign(self, t):
-        self.fill()
-        for target in t.targets:
-            self.dispatch(target)
-            self.write(" = ")
-        self.dispatch(t.value)
-
-    def _AugAssign(self, t):
-        self.fill()
-        self.dispatch(t.target)
-        self.write(" " + self.binop[t.op.__class__.__name__] + "= ")
-        self.dispatch(t.value)
-
-    def _AnnAssign(self, t):
-        self.fill()
-        if not t.simple and isinstance(t.target, ast.Name):
-            self.write("(")
-        self.dispatch(t.target)
-        if not t.simple and isinstance(t.target, ast.Name):
-            self.write(")")
-        self.write(": ")
-        self.dispatch(t.annotation)
-        if t.value:
-            self.write(" = ")
-            self.dispatch(t.value)
-
-    def _Return(self, t):
-        self.fill("return")
-        if t.value:
-            self.write(" ")
-            self.dispatch(t.value)
-
-    def _Pass(self, t):
-        self.fill("pass")
-
-    def _Break(self, t):
-        self.fill("break")
-
-    def _Continue(self, t):
-        self.fill("continue")
-
-    def _Delete(self, t):
-        self.fill("del ")
-        _interleave(lambda: self.write(", "), self.dispatch, t.targets)
-
-    def _Assert(self, t):
-        self.fill("assert ")
-        self.dispatch(t.test)
-        if t.msg:
-            self.write(", ")
-            self.dispatch(t.msg)
-
-    def _Global(self, t):
-        self.fill("global ")
-        _interleave(lambda: self.write(", "), self.write, t.names)
-
-    def _Nonlocal(self, t):
-        self.fill("nonlocal ")
-        _interleave(lambda: self.write(", "), self.write, t.names)
-
-    def _Await(self, t):
-        self.write("(")
-        self.write("await")
-        if t.value:
-            self.write(" ")
-            self.dispatch(t.value)
-        self.write(")")
-
-    def _Yield(self, t):
-        self.write("(")
-        self.write("yield")
-        if t.value:
-            self.write(" ")
-            self.dispatch(t.value)
-        self.write(")")
-
-    def _YieldFrom(self, t):
-        self.write("(")
-        self.write("yield from")
-        if t.value:
-            self.write(" ")
-            self.dispatch(t.value)
-        self.write(")")
-
-    def _Raise(self, t):
-        self.fill("raise")
-        if not t.exc:
-            assert not t.cause
-            return
-        self.write(" ")
-        self.dispatch(t.exc)
-        if t.cause:
-            self.write(" from ")
-            self.dispatch(t.cause)
-
-    def _Try(self, t):
-        self.fill("try")
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-        for ex in t.handlers:
-            self.dispatch(ex)
-        if t.orelse:
-            self.fill("else")
-            self.enter()
-            self.dispatch(t.orelse)
-            self.leave()
-        if t.finalbody:
-            self.fill("finally")
-            self.enter()
-            self.dispatch(t.finalbody)
-            self.leave()
-
-    def _ExceptHandler(self, t):
-        self.fill("except")
-        if t.type:
-            self.write(" ")
-            self.dispatch(t.type)
-        if t.name:
-            self.write(" as ")
-            self.write(t.name)
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-
-    def _ClassDef(self, t):
-        self.write("\n")
-        for deco in t.decorator_list:
-            self.fill("@")
-            self.dispatch(deco)
-        self.fill("class " + t.name)
-        self.write("(")
-        comma = False
-        for e in t.bases:
-            if comma:
-                self.write(", ")
-            else:
-                comma = True
-            self.dispatch(e)
-        for e in t.keywords:
-            if comma:
-                self.write(", ")
-            else:
-                comma = True
-            self.dispatch(e)
-        self.write(")")
-
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-
-    def _FunctionDef(self, t):
-        self.__FunctionDef_helper(t, "def")
-
-    def _AsyncFunctionDef(self, t):
-        self.__FunctionDef_helper(t, "async def")
-
-    def __FunctionDef_helper(self, t, fill_suffix):
-        self.write("\n")
-        for deco in t.decorator_list:
-            self.fill("@")
-            self.dispatch(deco)
-        def_str = fill_suffix + " " + t.name + "("
-        self.fill(def_str)
-        self.dispatch(t.args)
-        self.write(")")
-        if t.returns:
-            self.write(" -> ")
-            self.dispatch(t.returns)
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-
-    def _For(self, t):
-        self.__For_helper("for ", t)
-
-    def _AsyncFor(self, t):
-        self.__For_helper("async for ", t)
-
-    def __For_helper(self, fill, t):
-        self.fill(fill)
-        self.dispatch(t.target)
-        self.write(" in ")
-        self.dispatch(t.iter)
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-        if t.orelse:
-            self.fill("else")
-            self.enter()
-            self.dispatch(t.orelse)
-            self.leave()
-
-    def _If(self, t):
-        self.fill("if ")
-        self.dispatch(t.test)
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-        # collapse nested ifs into equivalent elifs.
-        while t.orelse and len(t.orelse) == 1 and isinstance(t.orelse[0], ast.If):
-            t = t.orelse[0]
-            self.fill("elif ")
-            self.dispatch(t.test)
-            self.enter()
-            self.dispatch(t.body)
-            self.leave()
-        # final else
-        if t.orelse:
-            self.fill("else")
-            self.enter()
-            self.dispatch(t.orelse)
-            self.leave()
-
-    def _While(self, t):
-        self.fill("while ")
-        self.dispatch(t.test)
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-        if t.orelse:
-            self.fill("else")
-            self.enter()
-            self.dispatch(t.orelse)
-            self.leave()
-
-    def _With(self, t):
-        self.fill("with ")
-        _interleave(lambda: self.write(", "), self.dispatch, t.items)
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-
-    def _AsyncWith(self, t):
-        self.fill("async with ")
-        _interleave(lambda: self.write(", "), self.dispatch, t.items)
-        self.enter()
-        self.dispatch(t.body)
-        self.leave()
-
-    # expr
-    def _JoinedStr(self, t):
-        self.write("f")
-        string = io.StringIO()
-        self._fstring_JoinedStr(t, string.write)
-        self.write(repr(string.getvalue()))
-
-    def _FormattedValue(self, t):
-        self.write("f")
-        string = io.StringIO()
-        self._fstring_FormattedValue(t, string.write)
-        self.write(repr(string.getvalue()))
-
-    def _fstring_JoinedStr(self, t, write):
-        for value in t.values:
-            meth = getattr(self, "_fstring_" + type(value).__name__)
-            meth(value, write)
-
-    def _fstring_Constant(self, t, write):
-        assert isinstance(t.value, str)
-        value = t.value.replace("{", "{{").replace("}", "}}")
-        write(value)
-
-    def _fstring_FormattedValue(self, t, write):
-        write("{")
-        expr = io.StringIO()
-        _Unparser(t.value, expr)
-        expr = expr.getvalue().rstrip("\n")
-        if expr.startswith("{"):
-            write(" ")  # Separate pair of opening brackets as "{ {"
-        write(expr)
-        if t.conversion != -1:
-            conversion = chr(t.conversion)
-            assert conversion in "sra"
-            write(f"!{conversion}")
-        if t.format_spec:
-            write(":")
-            meth = getattr(self, "_fstring_" + type(t.format_spec).__name__)
-            meth(t.format_spec, write)
-        write("}")
-
-    def _Name(self, t):
-        self.write(t.id)
-
-    def _write_constant(self, value):
-        if isinstance(value, (float, complex)):
-            # Substitute overflowing decimal literal for AST infinities.
-            self.write(repr(value).replace("inf", _INFSTR))
-        else:
-            self.write(repr(value))
-
-    def _Constant(self, t):
-        value = t.value
-        if isinstance(value, tuple):
-            self.write("(")
-            if len(value) == 1:
-                self._write_constant(value[0])
-                self.write(",")
-            else:
-                _interleave(lambda: self.write(", "), self._write_constant, value)
-            self.write(")")
-        elif value is ...:
-            self.write("...")
-        else:
-            if t.kind == "u":
-                self.write("u")
-            self._write_constant(t.value)
-
-    def _List(self, t):
-        self.write("[")
-        _interleave(lambda: self.write(", "), self.dispatch, t.elts)
-        self.write("]")
-
-    def _ListComp(self, t):
-        self.write("[")
-        self.dispatch(t.elt)
-        for gen in t.generators:
-            self.dispatch(gen)
-        self.write("]")
-
-    def _GeneratorExp(self, t):
-        self.write("(")
-        self.dispatch(t.elt)
-        for gen in t.generators:
-            self.dispatch(gen)
-        self.write(")")
-
-    def _SetComp(self, t):
-        self.write("{")
-        self.dispatch(t.elt)
-        for gen in t.generators:
-            self.dispatch(gen)
-        self.write("}")
-
-    def _DictComp(self, t):
-        self.write("{")
-        self.dispatch(t.key)
-        self.write(": ")
-        self.dispatch(t.value)
-        for gen in t.generators:
-            self.dispatch(gen)
-        self.write("}")
-
-    def _comprehension(self, t):
-        if t.is_async:
-            self.write(" async for ")
-        else:
-            self.write(" for ")
-        self.dispatch(t.target)
-        self.write(" in ")
-        self.dispatch(t.iter)
-        for if_clause in t.ifs:
-            self.write(" if ")
-            self.dispatch(if_clause)
-
-    def _IfExp(self, t):
-        self.write("(")
-        self.dispatch(t.body)
-        self.write(" if ")
-        self.dispatch(t.test)
-        self.write(" else ")
-        self.dispatch(t.orelse)
-        self.write(")")
-
-    def _Set(self, t):
-        assert t.elts  # should be at least one element
-        self.write("{")
-        _interleave(lambda: self.write(", "), self.dispatch, t.elts)
-        self.write("}")
-
-    def _Dict(self, t):
-        self.write("{")
-
-        def write_key_value_pair(k, v):
-            self.dispatch(k)
-            self.write(": ")
-            self.dispatch(v)
-
-        def write_item(item):
-            k, v = item
-            if k is None:
-                # for dictionary unpacking operator in dicts {**{'y': 2}}
-                # see PEP 448 for details
-                self.write("**")
-                self.dispatch(v)
-            else:
-                write_key_value_pair(k, v)
-
-        _interleave(lambda: self.write(", "), write_item, zip(t.keys, t.values))
-        self.write("}")
-
-    def _Tuple(self, t):
-        self.write("(")
-        if len(t.elts) == 1:
-            elt = t.elts[0]
-            self.dispatch(elt)
-            self.write(",")
-        else:
-            _interleave(lambda: self.write(", "), self.dispatch, t.elts)
-        self.write(")")
-
-    unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"}
-
-    def _UnaryOp(self, t):
-        self.write("(")
-        self.write(self.unop[t.op.__class__.__name__])
-        self.write(" ")
-        self.dispatch(t.operand)
-        self.write(")")
-
-    binop = {
-        "Add": "+",
-        "Sub": "-",
-        "Mult": "*",
-        "MatMult": "@",
-        "Div": "/",
-        "Mod": "%",
-        "LShift": "<<",
-        "RShift": ">>",
-        "BitOr": "|",
-        "BitXor": "^",
-        "BitAnd": "&",
-        "FloorDiv": "//",
-        "Pow": "**",
-    }
-
-    def _BinOp(self, t):
-        self.write("(")
-        self.dispatch(t.left)
-        self.write(" " + self.binop[t.op.__class__.__name__] + " ")
-        self.dispatch(t.right)
-        self.write(")")
-
-    cmpops = {
-        "Eq": "==",
-        "NotEq": "!=",
-        "Lt": "<",
-        "LtE": "<=",
-        "Gt": ">",
-        "GtE": ">=",
-        "Is": "is",
-        "IsNot": "is not",
-        "In": "in",
-        "NotIn": "not in",  # codespell:ignore
-    }
-
-    def _Compare(self, t):
-        self.write("(")
-        self.dispatch(t.left)
-        for o, e in zip(t.ops, t.comparators):
-            self.write(" " + self.cmpops[o.__class__.__name__] + " ")
-            self.dispatch(e)
-        self.write(")")
-
-    boolops = {ast.And: "and", ast.Or: "or"}
-
-    def _BoolOp(self, t):
-        self.write("(")
-        s = " %s " % self.boolops[t.op.__class__]
-        _interleave(lambda: self.write(s), self.dispatch, t.values)
-        self.write(")")
-
-    def _Attribute(self, t):
-        self.dispatch(t.value)
-        # Special case: 3.__abs__() is a syntax error, so if t.value
-        # is an integer literal then we need to either parenthesize
-        # it or add an extra space to get 3 .__abs__().
-        if isinstance(t.value, ast.Constant) and isinstance(t.value.value, int):
-            self.write(" ")
-        self.write(".")
-        self.write(t.attr)
-
-    def _Call(self, t):
-        self.dispatch(t.func)
-        self.write("(")
-        comma = False
-        for e in t.args:
-            if comma:
-                self.write(", ")
-            else:
-                comma = True
-            self.dispatch(e)
-        for e in t.keywords:
-            if comma:
-                self.write(", ")
-            else:
-                comma = True
-            self.dispatch(e)
-        self.write(")")
-
-    def _Subscript(self, t):
-        self.dispatch(t.value)
-        self.write("[")
-        if isinstance(t.slice, ast.Index) and isinstance(t.slice.value, ast.Tuple) and t.slice.value.elts:
-            if len(t.slice.value.elts) == 1:
-                elt = t.slice.value.elts[0]
-                self.dispatch(elt)
-                self.write(",")
-            else:
-                _interleave(lambda: self.write(", "), self.dispatch, t.slice.value.elts)
-        else:
-            self.dispatch(t.slice)
-        self.write("]")
-
-    def _Starred(self, t):
-        self.write("*")
-        self.dispatch(t.value)
-
-    # slice
-    def _Ellipsis(self, t):
-        self.write("...")
-
-    def _Index(self, t):
-        self.dispatch(t.value)
-
-    def _Slice(self, t):
-        if t.lower:
-            self.dispatch(t.lower)
-        self.write(":")
-        if t.upper:
-            self.dispatch(t.upper)
-        if t.step:
-            self.write(":")
-            self.dispatch(t.step)
-
-    def _ExtSlice(self, t):
-        if len(t.dims) == 1:
-            elt = t.dims[0]
-            self.dispatch(elt)
-            self.write(",")
-        else:
-            _interleave(lambda: self.write(", "), self.dispatch, t.dims)
-
-    # argument
-    def _arg(self, t):
-        self.write(t.arg)
-        if t.annotation:
-            self.write(": ")
-            self.dispatch(t.annotation)
-
-    # others
-    def _arguments(self, t):
-        first = True
-        # normal arguments
-        all_args = t.posonlyargs + t.args
-        defaults = [None] * (len(all_args) - len(t.defaults)) + t.defaults
-        for index, elements in enumerate(zip(all_args, defaults), 1):
-            a, d = elements
-            if first:
-                first = False
-            else:
-                self.write(", ")
-            self.dispatch(a)
-            if d:
-                self.write("=")
-                self.dispatch(d)
-            if index == len(t.posonlyargs):
-                self.write(", /")
-
-        # varargs, or bare '*' if no varargs but keyword-only arguments present
-        if t.vararg or t.kwonlyargs:
-            if first:
-                first = False
-            else:
-                self.write(", ")
-            self.write("*")
-            if t.vararg:
-                self.write(t.vararg.arg)
-                if t.vararg.annotation:
-                    self.write(": ")
-                    self.dispatch(t.vararg.annotation)
-
-        # keyword-only arguments
-        if t.kwonlyargs:
-            for a, d in zip(t.kwonlyargs, t.kw_defaults):
-                if first:
-                    first = False
-                else:
-                    self.write(", ")
-                (self.dispatch(a),)
-                if d:
-                    self.write("=")
-                    self.dispatch(d)
-
-        # kwargs
-        if t.kwarg:
-            if first:
-                first = False
-            else:
-                self.write(", ")
-            self.write("**" + t.kwarg.arg)
-            if t.kwarg.annotation:
-                self.write(": ")
-                self.dispatch(t.kwarg.annotation)
-
-    def _keyword(self, t):
-        if t.arg is None:
-            self.write("**")
-        else:
-            self.write(t.arg)
-            self.write("=")
-        self.dispatch(t.value)
-
-    def _Lambda(self, t):
-        self.write("(")
-        self.write("lambda ")
-        self.dispatch(t.args)
-        self.write(": ")
-        self.dispatch(t.body)
-        self.write(")")
-
-    def _alias(self, t):
-        self.write(t.name)
-        if t.asname:
-            self.write(" as " + t.asname)
-
-    def _withitem(self, t):
-        self.dispatch(t.context_expr)
-        if t.optional_vars:
-            self.write(" as ")
-            self.dispatch(t.optional_vars)

+ 2 - 6
taipy/gui_core/_context.py

@@ -12,16 +12,12 @@
 import datetime
 import json
 import typing as t
+import zoneinfo
 from collections import defaultdict
 from numbers import Number
 from pathlib import Path
 from threading import Lock
 
-try:
-    import zoneinfo
-except ImportError:
-    from backports import zoneinfo  # type: ignore[no-redef]
-
 import pandas as pd
 from dateutil import parser
 
@@ -715,7 +711,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 )
             else:
                 if isinstance(scenarios, (list, tuple)) and len(scenarios) > 1:
-                    base_list = scenarios
+                    base_list = list(scenarios)
                 else:
                     if self.data_nodes_by_owner:
                         owners = scenarios if isinstance(scenarios, (list, tuple)) else [scenarios]

+ 1 - 2
taipy/rest/pyproject.toml

@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "taipy-rest"
 description = "Library to expose taipy-core REST APIs."
 readme = "package_desc.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 license = {text = "Apache License 2.0"}
 authors = [{name = "Avaiga", email = "dev@taipy.io"}]
 keywords = ["taipy-rest"]
@@ -15,7 +15,6 @@ classifiers = [
     "License :: OSI Approved :: Apache Software License",
     "Natural Language :: English",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",

+ 1 - 2
taipy/templates/pyproject.toml

@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "taipy-templates"
 description = "An open-source package holding Taipy application templates."
 readme = "package_desc.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 license = {text = "Apache License 2.0"}
 authors = [{name = "Avaiga", email = "dev@taipy.io"}]
 keywords = ["taipy-templates"]
@@ -15,7 +15,6 @@ classifiers = [
     "License :: OSI Approved :: Apache Software License",
     "Natural Language :: English",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",

+ 0 - 1
tools/packages/pipfiles/Pipfile3.10.max

@@ -58,7 +58,6 @@ version = "==4.2.13"
 "sqlalchemy" = {version="==2.0.30"}
 "toml" = {version="==0.10.2"}
 "boto3" = {version="==1.34.113"}
-"backports.zoneinfo" = {version="==0.2.1", markers="python_version<'3.9'"}
 "cookiecutter" = {version="==2.6.0"}
 "flask" = {version="==3.0.3"}
 "flask-cors" = {version="==5.0.0"}

+ 0 - 1
tools/packages/pipfiles/Pipfile3.11.max

@@ -58,7 +58,6 @@ version = "==4.2.13"
 "sqlalchemy" = {version="==2.0.30"}
 "toml" = {version="==0.10.2"}
 "boto3" = {version="==1.34.113"}
-"backports.zoneinfo" = {version="==0.2.1", markers="python_version<'3.9'"}
 "cookiecutter" = {version="==2.6.0"}
 "flask" = {version="==3.0.3"}
 "flask-cors" = {version="==5.0.0"}

+ 0 - 1
tools/packages/pipfiles/Pipfile3.12.max

@@ -58,7 +58,6 @@ version = "==4.2.13"
 "sqlalchemy" = {version="==2.0.30"}
 "toml" = {version="==0.10.2"}
 "boto3" = {version="==1.34.113"}
-"backports.zoneinfo" = {version="==0.2.1", markers="python_version<'3.9'"}
 "cookiecutter" = {version="==2.6.0"}
 "flask" = {version="==3.0.3"}
 "flask-cors" = {version="==5.0.0"}

+ 0 - 83
tools/packages/pipfiles/Pipfile3.8.max

@@ -1,83 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[dev-packages]
-freezegun = "*"
-ipython = "*"
-ipykernel = "*"
-mkdocs = "*"
-mkdocs-autorefs = "*"
-mkdocs-include-markdown-plugin = "*"
-mkdocs-macros-plugin = "*"
-mkdocs-material = "==7.3.0"
-mkdocs-material-extensions = "*"
-mkdocstrings = "*"
-mongomock = "*"
-requests = "*"
-ruff = "*"
-pandas-stubs = "*"
-playwright = "*"
-pre-commit = "*"
-pyopenssl = "*"
-pytest = "==7.4.3"
-pytest-cov = "*"
-pytest-mock = "*"
-pytest-playwright = "*"
-pytest-timeout = "*"
-python-dotenv = "*"
-testbook = "*"
-tox = "*"
-twine = "*"
-types-flask = "*"
-types-Flask-Cors = "*"
-types-Markdown = "*"
-types-python-dateutil = "*"
-types-pytz = "*"
-types-toml = ">=0.10.0"
-types-tzlocal = "*"
-
-[requires]
-python_version = "3"
-
-[pipenv]
-allow_prereleases = true
-
-[dev-packages.moto]
-extras = [ "s3",]
-version = "==4.2.13"
-
-
-[packages]
-"pyarrow" = {version="==17.0.0"}
-"networkx" = {version="==3.1", markers="python_version<'3.9'"}
-"openpyxl" = {version="==3.1.2"}
-"pandas" = {version="==2.0.3", markers="python_version<'3.9'"}
-"pymongo" = {version="==4.7.2", extras=["srv"]}
-"sqlalchemy" = {version="==2.0.30"}
-"toml" = {version="==0.10.2"}
-"boto3" = {version="==1.34.113"}
-"backports.zoneinfo" = {version="==0.2.1", markers="python_version<'3.9'"}
-"cookiecutter" = {version="==2.6.0"}
-"flask" = {version="==3.0.3"}
-"flask-cors" = {version="==5.0.0"}
-"flask-socketio" = {version="==5.3.6"}
-"markdown" = {version="==3.6"}
-"python-dotenv" = {version="==1.0.1"}
-"pytz" = {version="==2024.1"}
-"tzlocal" = {version="==5.2"}
-"gevent" = {version="==24.2.1"}
-"gevent-websocket" = {version="==0.10.1"}
-"kthread" = {version="==0.2.3"}
-"gitignore-parser" = {version="==0.1.11"}
-"simple-websocket" = {version="==1.0.0"}
-"twisted" = {version="==24.7.0"}
-"deepdiff" = {version="==7.0.1"}
-"flask-restful" = {version="==0.3.10"}
-"passlib" = {version="==1.7.4"}
-"marshmallow" = {version="==3.21.2"}
-"apispec" = {version="==6.6.1", extras=["yaml"]}
-"apispec-webframeworks" = {version="==1.1.0"}
-"watchdog" = {version="==4.0.1"}
-"charset-normalizer" = {version="==3.3.2"}

+ 0 - 1
tools/packages/pipfiles/Pipfile3.9.max

@@ -58,7 +58,6 @@ version = "==4.2.13"
 "sqlalchemy" = {version="==2.0.30"}
 "toml" = {version="==0.10.2"}
 "boto3" = {version="==1.34.113"}
-"backports.zoneinfo" = {version="==0.2.1", markers="python_version<'3.9'"}
 "cookiecutter" = {version="==2.6.0"}
 "flask" = {version="==3.0.3"}
 "flask-cors" = {version="==5.0.0"}

+ 0 - 1
tools/packages/taipy-gui/setup.requirements.txt

@@ -1,4 +1,3 @@
-backports.zoneinfo>=0.2.1,<=0.2.1;python_version<'3.9'
 charset-normalizer>=3.3.2,<=3.3.2
 flask>=3.0.0,<=3.0.3
 flask-cors>=5.0.0,<5.1

+ 0 - 1
tools/packages/taipy/setup.requirements.txt

@@ -1,4 +1,3 @@
-backports.zoneinfo>=0.2.1,<=0.2.1;python_version<'3.9'
 cookiecutter>=2.1.1,<=2.6.0
 taipy-gui
 taipy-rest