Quellcode durchsuchen

Scenario viewer update (#1803)

* Scenario viewer update
resolves #1669
 resolves a few mypy/ruff "errors" in python as well

* sometimes _is_deletable breaks because Job is None

* comments and Mui 7 preparation

* mypy

* ruff

* doc embryo

* fix test

* filters

* JR' comment + Mui update

* fab's comment

* job_id cannot be empty/None in the exception block

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide vor 8 Monaten
Ursprung
Commit
6c2df92707

+ 52 - 96
frontend/taipy-gui/package-lock.json

@@ -87,26 +87,6 @@
         "webpack-cli": "^5.0.0"
       }
     },
-    "node_modules/@75lb/deep-merge": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@75lb/deep-merge/-/deep-merge-1.1.2.tgz",
-      "integrity": "sha512-08K9ou5VNbheZFxM5tDWoqjA3ImC50DiuuJ2tj1yEPRfkp8lLLg6XAaJ4On+a0yAXor/8ay5gHnAIshRM44Kpw==",
-      "dependencies": {
-        "lodash": "^4.17.21",
-        "typical": "^7.1.1"
-      },
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
-    "node_modules/@75lb/deep-merge/node_modules/typical": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz",
-      "integrity": "sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g==",
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
     "node_modules/@adobe/css-tools": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz",
@@ -1912,18 +1892,18 @@
       "dev": true
     },
     "node_modules/@mui/core-downloads-tracker": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.0.tgz",
-      "integrity": "sha512-covEnIn/2er5YdtuukDRA52kmARhKrHjOvPsyTFMQApZdrTBI4h8jbEy2mxZqwMwcAFS9coonQXnEZKL1rUNdQ==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.1.tgz",
+      "integrity": "sha512-VdQC1tPIIcZAnf62L2M1eQif0x2vlKg3YK4kGYbtijSH4niEgI21GnstykW1vQIs+Bc6L+Hua2GATYVjilJ22A==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/mui-org"
       }
     },
     "node_modules/@mui/icons-material": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.0.tgz",
-      "integrity": "sha512-HxfB0jxwiMTYMN8gAnYn3avbF1aDrqBEuGIj6JDQ3YkLl650E1Wy8AIhwwyP47wdrv0at9aAR0iOO6VLb74A9w==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.1.tgz",
+      "integrity": "sha512-sy/YKwcLPW8VcacNP2uWMYR9xyWuwO9NN9FXuGEU90bRshBXj8pdKk+joe3TCW7oviVS3zXLHlc94wQ0jNsQRQ==",
       "dependencies": {
         "@babel/runtime": "^7.25.6"
       },
@@ -1935,7 +1915,7 @@
         "url": "https://opencollective.com/mui-org"
       },
       "peerDependencies": {
-        "@mui/material": "^6.1.0",
+        "@mui/material": "^6.1.1",
         "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
         "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
       },
@@ -1946,15 +1926,15 @@
       }
     },
     "node_modules/@mui/material": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.0.tgz",
-      "integrity": "sha512-4MJ46vmy1xbm8x+ZdRcWm8jEMMowdS8pYlhKQzg/qoKhOcLhImZvf2Jn6z9Dj6gl+lY+C/0MxaHF/avAAGys3Q==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.1.tgz",
+      "integrity": "sha512-b+eULldTqtqTCbN++2BtBWCir/1LwEYw+2mIlOt2GiEUh1EBBw4/wIukGKKNt3xrCZqRA80yLLkV6tF61Lq3cA==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/core-downloads-tracker": "^6.1.0",
-        "@mui/system": "^6.1.0",
-        "@mui/types": "^7.2.16",
-        "@mui/utils": "^6.1.0",
+        "@mui/core-downloads-tracker": "^6.1.1",
+        "@mui/system": "^6.1.1",
+        "@mui/types": "^7.2.17",
+        "@mui/utils": "^6.1.1",
         "@popperjs/core": "^2.11.8",
         "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
@@ -1973,7 +1953,7 @@
       "peerDependencies": {
         "@emotion/react": "^11.5.0",
         "@emotion/styled": "^11.3.0",
-        "@mui/material-pigment-css": "^6.1.0",
+        "@mui/material-pigment-css": "^6.1.1",
         "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
         "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
         "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1994,12 +1974,12 @@
       }
     },
     "node_modules/@mui/private-theming": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.0.tgz",
-      "integrity": "sha512-+L5qccs4gwsR0r1dgjqhN24QEQRkqIbfOdxILyMbMkuI50x6wNyt9XrV+J3WtjtZTMGJCrUa5VmZBE6OEPGPWA==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.1.tgz",
+      "integrity": "sha512-JlrjIdhyZUtewtdAuUsvi3ZnO0YS49IW4Mfz19ZWTlQ0sDGga6LNPVwHClWr2/zJK2we2BQx9/i8M32rgKuzrg==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/utils": "^6.1.0",
+        "@mui/utils": "^6.1.1",
         "prop-types": "^15.8.1"
       },
       "engines": {
@@ -2020,9 +2000,9 @@
       }
     },
     "node_modules/@mui/styled-engine": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.0.tgz",
-      "integrity": "sha512-MZ+vtaCkjamrT41+b0Er9OMenjAtP/32+L6fARL9/+BZKuV2QbR3q3TmavT2x0NhDu35IM03s4yKqj32Ziqnyg==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.1.tgz",
+      "integrity": "sha512-HJyIoMpFb11fnHuRtUILOXgq6vj4LhIlE8maG4SwP/W+E5sa7HFexhnB3vOMT7bKys4UKNxhobC8jwWxYilGsA==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@emotion/cache": "^11.13.1",
@@ -2052,15 +2032,15 @@
       }
     },
     "node_modules/@mui/system": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.0.tgz",
-      "integrity": "sha512-NumkGDqT6EdXfcoFLYQ+M4XlTW5hH3+aK48xAbRqKPXJfxl36CBt4DLduw/Voa5dcayGus9T6jm1AwU2hoJ5hQ==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.1.tgz",
+      "integrity": "sha512-PaYsCz2tUOcpu3T0okDEsSuP/yCDIj9JZ4Tox1JovRSKIjltHpXPsXZSGr3RiWdtM1MTQMFMCZzu0+CKbyy+Kw==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/private-theming": "^6.1.0",
-        "@mui/styled-engine": "^6.1.0",
-        "@mui/types": "^7.2.16",
-        "@mui/utils": "^6.1.0",
+        "@mui/private-theming": "^6.1.1",
+        "@mui/styled-engine": "^6.1.1",
+        "@mui/types": "^7.2.17",
+        "@mui/utils": "^6.1.1",
         "clsx": "^2.1.1",
         "csstype": "^3.1.3",
         "prop-types": "^15.8.1"
@@ -2091,9 +2071,9 @@
       }
     },
     "node_modules/@mui/types": {
-      "version": "7.2.16",
-      "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.16.tgz",
-      "integrity": "sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==",
+      "version": "7.2.17",
+      "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.17.tgz",
+      "integrity": "sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==",
       "peerDependencies": {
         "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
       },
@@ -2104,12 +2084,12 @@
       }
     },
     "node_modules/@mui/utils": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.0.tgz",
-      "integrity": "sha512-oT8ZzMISRUhTVpdbYzY0CgrCBb3t/YEdcaM13tUnuTjZ15pdA6g5lx15ZJUdgYXV6PbJdw7tDQgMEr4uXK5TXQ==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.1.tgz",
+      "integrity": "sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/types": "^7.2.16",
+        "@mui/types": "^7.2.17",
         "@types/prop-types": "^15.7.12",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -2954,9 +2934,9 @@
       }
     },
     "node_modules/@types/estree": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
-      "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
     },
     "node_modules/@types/estree-jsx": {
       "version": "1.0.5",
@@ -4561,9 +4541,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001660",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
-      "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
+      "version": "1.0.30001662",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz",
+      "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==",
       "funding": [
         {
           "type": "opencollective",
@@ -5080,13 +5060,13 @@
       }
     },
     "node_modules/command-line-usage": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.1.tgz",
-      "integrity": "sha512-NCyznE//MuTjwi3y84QVUGEOT+P5oto1e1Pk/jFPVdPPfsG03qpTIl3yw6etR+v73d0lXsoojRpvbru2sqePxQ==",
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz",
+      "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
       "dependencies": {
         "array-back": "^6.2.2",
         "chalk-template": "^0.4.0",
-        "table-layout": "^3.0.0",
+        "table-layout": "^4.1.0",
         "typical": "^7.1.1"
       },
       "engines": {
@@ -6135,9 +6115,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.24",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.24.tgz",
-      "integrity": "sha512-0x0wLCmpdKFCi9ulhvYZebgcPmHTkFVUfU2wzDykadkslKwT4oAmDTHEKLnlrDsMGZe4B+ksn8quZfZjYsBetA=="
+      "version": "1.5.25",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz",
+      "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g=="
     },
     "node_modules/element-size": {
       "version": "1.1.1",
@@ -14757,14 +14737,6 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
     },
-    "node_modules/stream-read-all": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/stream-read-all/-/stream-read-all-3.0.1.tgz",
-      "integrity": "sha512-EWZT9XOceBPlVJRrYcykW8jyRSZYbkb/0ZK36uLEmoWVO5gxBOnntNTseNzfREsqxqdfEGQrD8SXQ3QWbBmq8A==",
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/stream-shift": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
@@ -15125,21 +15097,13 @@
       "dev": true
     },
     "node_modules/table-layout": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-3.0.2.tgz",
-      "integrity": "sha512-rpyNZYRw+/C+dYkcQ3Pr+rLxW4CfHpXjPDnG7lYhdRoUcZTUt+KEsX+94RGp/aVp/MQU35JCITv2T/beY4m+hw==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
+      "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
       "dependencies": {
-        "@75lb/deep-merge": "^1.1.1",
         "array-back": "^6.2.2",
-        "command-line-args": "^5.2.1",
-        "command-line-usage": "^7.0.0",
-        "stream-read-all": "^3.0.1",
-        "typical": "^7.1.1",
         "wordwrapjs": "^5.1.0"
       },
-      "bin": {
-        "table-layout": "bin/cli.js"
-      },
       "engines": {
         "node": ">=12.17"
       }
@@ -15152,14 +15116,6 @@
         "node": ">=12.17"
       }
     },
-    "node_modules/table-layout/node_modules/typical": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz",
-      "integrity": "sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g==",
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
     "node_modules/tapable": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",

+ 2 - 2
frontend/taipy-gui/src/components/Taipy/Chat.tsx

@@ -406,7 +406,7 @@ const Chat = (props: ChatProps) => {
                         label={`message (${senderId})`}
                         disabled={!active}
                         onKeyDown={handleAction}
-                        InputProps={{
+                        slotProps={{input: {
                             endAdornment: (
                                 <InputAdornment position="end">
                                     <IconButton
@@ -419,7 +419,7 @@ const Chat = (props: ChatProps) => {
                                     </IconButton>
                                 </InputAdornment>
                             ),
-                        }}
+                        }}}
                         sx={inputSx}
                     />
                 ) : null}

+ 48 - 52
frontend/taipy-gui/src/components/Taipy/Input.tsx

@@ -260,45 +260,58 @@ const Input = (props: TaipyInputProps) => {
         (event: React.MouseEvent<HTMLButtonElement>) => event.preventDefault(),
         []
     );
-    const muiInputProps = useMemo(
+    const inputProps = useMemo(
         () =>
-            type == "password"
+            type == "number"
                 ? {
-                      endAdornment: (
-                          <IconButton
-                              aria-label="toggle password visibility"
-                              onClick={handleClickShowPassword}
-                              onMouseDown={handleMouseDownPassword}
-                              edge="end"
-                          >
-                              {showPassword ? <VisibilityOff /> : <Visibility />}
-                          </IconButton>
-                      ),
+                      htmlInput: {
+                          step: step ? step : 1,
+                          min: min,
+                          max: max,
+                      },
+                      input: {
+                          endAdornment: (
+                              <div style={verticalDivStyle}>
+                                  <IconButton
+                                      aria-label="Increment value"
+                                      size="small"
+                                      onMouseDown={handleUpStepperMouseDown}
+                                  >
+                                      <ArrowDropUpIcon fontSize="inherit" />
+                                  </IconButton>
+                                  <IconButton
+                                      aria-label="Decrement value"
+                                      size="small"
+                                      onMouseDown={handleDownStepperMouseDown}
+                                  >
+                                      <ArrowDropDownIcon fontSize="inherit" />
+                                  </IconButton>
+                              </div>
+                          ),
+                      },
                   }
-                : type == "number"
-                  ? {
-                        endAdornment: (
-                            <div style={verticalDivStyle}>
-                                <IconButton
-                                    aria-label="Increment value"
-                                    size="small"
-                                    onMouseDown={handleUpStepperMouseDown}
-                                >
-                                    <ArrowDropUpIcon fontSize="inherit" />
-                                </IconButton>
-                                <IconButton
-                                    aria-label="Decrement value"
-                                    size="small"
-                                    onMouseDown={handleDownStepperMouseDown}
-                                >
-                                    <ArrowDropDownIcon fontSize="inherit" />
-                                </IconButton>
-                            </div>
-                        ),
-                    }
-                  : undefined,
+                : type == "password"
+                ? {
+                      htmlInput: { autoComplete: "current-password" },
+                      input: {
+                          endAdornment: (
+                              <IconButton
+                                  aria-label="toggle password visibility"
+                                  onClick={handleClickShowPassword}
+                                  onMouseDown={handleMouseDownPassword}
+                                  edge="end"
+                              >
+                                  {showPassword ? <VisibilityOff /> : <Visibility />}
+                              </IconButton>
+                          ),
+                      },
+                  }
+                : undefined,
         [
             type,
+            step,
+            min,
+            max,
             showPassword,
             handleClickShowPassword,
             handleMouseDownPassword,
@@ -307,20 +320,6 @@ const Input = (props: TaipyInputProps) => {
         ]
     );
 
-    const inputProps = useMemo(
-        () =>
-            type == "number"
-                ? {
-                      step: step ? step : 1,
-                      min: min,
-                      max: max,
-                  }
-                : type == "password"
-                  ? { autoComplete: "current-password" }
-                  : undefined,
-        [type, step, min, max]
-    );
-
     useEffect(() => {
         if (props.value !== undefined) {
             setValue(props.value);
@@ -337,10 +336,7 @@ const Input = (props: TaipyInputProps) => {
                 className={className}
                 type={showPassword && type == "password" ? "text" : type}
                 id={id}
-                slotProps={{
-                    htmlInput: inputProps,
-                    input: muiInputProps,
-                }}
+                slotProps={inputProps}
                 label={props.label}
                 onChange={handleInput}
                 disabled={!active}

+ 5 - 6
frontend/taipy-gui/src/components/Taipy/Login.tsx

@@ -48,7 +48,7 @@ const closeSx: SxProps<Theme> = {
     alignSelf: "start",
 };
 const titleSx = { m: 0, p: 2, display: "flex", paddingRight: "0.1em" };
-const userProps = { autoComplete: "username" };
+const userProps = { htmlInput: { autoComplete: "username" }};
 const pwdProps = { autoComplete: "current-password" };
 
 const Login = (props: LoginProps) => {
@@ -97,7 +97,7 @@ const Login = (props: LoginProps) => {
         []
     );
     const passwordProps = useMemo(
-        () => ({
+        () => ({input: {
             endAdornment: (
                 <InputAdornment position="end">
                     <IconButton
@@ -110,7 +110,7 @@ const Login = (props: LoginProps) => {
                     </IconButton>
                 </InputAdornment>
             ),
-        }),
+        }, htmlInput: pwdProps}),
         [showPassword, handleClickShowPassword, handleMouseDownPassword]
     );
 
@@ -145,7 +145,7 @@ const Login = (props: LoginProps) => {
                     onChange={changeInput}
                     data-input="user"
                     onKeyDown={handleEnter}
-                    inputProps={userProps}
+                    slotProps={userProps}
                 ></TextField>
                 <TextField
                     variant="outlined"
@@ -159,8 +159,7 @@ const Login = (props: LoginProps) => {
                     onChange={changeInput}
                     data-input="password"
                     onKeyDown={handleEnter}
-                    inputProps={pwdProps}
-                    InputProps={passwordProps}
+                    slotProps={passwordProps}
                 />
                 <DialogContentText>{message || defaultMessage}</DialogContentText>
             </DialogContent>

+ 45 - 45
frontend/taipy/package-lock.json

@@ -660,18 +660,18 @@
       "dev": true
     },
     "node_modules/@mui/core-downloads-tracker": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.0.tgz",
-      "integrity": "sha512-covEnIn/2er5YdtuukDRA52kmARhKrHjOvPsyTFMQApZdrTBI4h8jbEy2mxZqwMwcAFS9coonQXnEZKL1rUNdQ==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.1.tgz",
+      "integrity": "sha512-VdQC1tPIIcZAnf62L2M1eQif0x2vlKg3YK4kGYbtijSH4niEgI21GnstykW1vQIs+Bc6L+Hua2GATYVjilJ22A==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/mui-org"
       }
     },
     "node_modules/@mui/icons-material": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.0.tgz",
-      "integrity": "sha512-HxfB0jxwiMTYMN8gAnYn3avbF1aDrqBEuGIj6JDQ3YkLl650E1Wy8AIhwwyP47wdrv0at9aAR0iOO6VLb74A9w==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.1.tgz",
+      "integrity": "sha512-sy/YKwcLPW8VcacNP2uWMYR9xyWuwO9NN9FXuGEU90bRshBXj8pdKk+joe3TCW7oviVS3zXLHlc94wQ0jNsQRQ==",
       "dependencies": {
         "@babel/runtime": "^7.25.6"
       },
@@ -683,7 +683,7 @@
         "url": "https://opencollective.com/mui-org"
       },
       "peerDependencies": {
-        "@mui/material": "^6.1.0",
+        "@mui/material": "^6.1.1",
         "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
         "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
       },
@@ -694,15 +694,15 @@
       }
     },
     "node_modules/@mui/material": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.0.tgz",
-      "integrity": "sha512-4MJ46vmy1xbm8x+ZdRcWm8jEMMowdS8pYlhKQzg/qoKhOcLhImZvf2Jn6z9Dj6gl+lY+C/0MxaHF/avAAGys3Q==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.1.tgz",
+      "integrity": "sha512-b+eULldTqtqTCbN++2BtBWCir/1LwEYw+2mIlOt2GiEUh1EBBw4/wIukGKKNt3xrCZqRA80yLLkV6tF61Lq3cA==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/core-downloads-tracker": "^6.1.0",
-        "@mui/system": "^6.1.0",
-        "@mui/types": "^7.2.16",
-        "@mui/utils": "^6.1.0",
+        "@mui/core-downloads-tracker": "^6.1.1",
+        "@mui/system": "^6.1.1",
+        "@mui/types": "^7.2.17",
+        "@mui/utils": "^6.1.1",
         "@popperjs/core": "^2.11.8",
         "@types/react-transition-group": "^4.4.11",
         "clsx": "^2.1.1",
@@ -721,7 +721,7 @@
       "peerDependencies": {
         "@emotion/react": "^11.5.0",
         "@emotion/styled": "^11.3.0",
-        "@mui/material-pigment-css": "^6.1.0",
+        "@mui/material-pigment-css": "^6.1.1",
         "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
         "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
         "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -742,12 +742,12 @@
       }
     },
     "node_modules/@mui/private-theming": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.0.tgz",
-      "integrity": "sha512-+L5qccs4gwsR0r1dgjqhN24QEQRkqIbfOdxILyMbMkuI50x6wNyt9XrV+J3WtjtZTMGJCrUa5VmZBE6OEPGPWA==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.1.tgz",
+      "integrity": "sha512-JlrjIdhyZUtewtdAuUsvi3ZnO0YS49IW4Mfz19ZWTlQ0sDGga6LNPVwHClWr2/zJK2we2BQx9/i8M32rgKuzrg==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/utils": "^6.1.0",
+        "@mui/utils": "^6.1.1",
         "prop-types": "^15.8.1"
       },
       "engines": {
@@ -768,9 +768,9 @@
       }
     },
     "node_modules/@mui/styled-engine": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.0.tgz",
-      "integrity": "sha512-MZ+vtaCkjamrT41+b0Er9OMenjAtP/32+L6fARL9/+BZKuV2QbR3q3TmavT2x0NhDu35IM03s4yKqj32Ziqnyg==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.1.tgz",
+      "integrity": "sha512-HJyIoMpFb11fnHuRtUILOXgq6vj4LhIlE8maG4SwP/W+E5sa7HFexhnB3vOMT7bKys4UKNxhobC8jwWxYilGsA==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
         "@emotion/cache": "^11.13.1",
@@ -800,15 +800,15 @@
       }
     },
     "node_modules/@mui/system": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.0.tgz",
-      "integrity": "sha512-NumkGDqT6EdXfcoFLYQ+M4XlTW5hH3+aK48xAbRqKPXJfxl36CBt4DLduw/Voa5dcayGus9T6jm1AwU2hoJ5hQ==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.1.tgz",
+      "integrity": "sha512-PaYsCz2tUOcpu3T0okDEsSuP/yCDIj9JZ4Tox1JovRSKIjltHpXPsXZSGr3RiWdtM1MTQMFMCZzu0+CKbyy+Kw==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/private-theming": "^6.1.0",
-        "@mui/styled-engine": "^6.1.0",
-        "@mui/types": "^7.2.16",
-        "@mui/utils": "^6.1.0",
+        "@mui/private-theming": "^6.1.1",
+        "@mui/styled-engine": "^6.1.1",
+        "@mui/types": "^7.2.17",
+        "@mui/utils": "^6.1.1",
         "clsx": "^2.1.1",
         "csstype": "^3.1.3",
         "prop-types": "^15.8.1"
@@ -839,9 +839,9 @@
       }
     },
     "node_modules/@mui/types": {
-      "version": "7.2.16",
-      "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.16.tgz",
-      "integrity": "sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==",
+      "version": "7.2.17",
+      "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.17.tgz",
+      "integrity": "sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==",
       "peerDependencies": {
         "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
       },
@@ -852,12 +852,12 @@
       }
     },
     "node_modules/@mui/utils": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.0.tgz",
-      "integrity": "sha512-oT8ZzMISRUhTVpdbYzY0CgrCBb3t/YEdcaM13tUnuTjZ15pdA6g5lx15ZJUdgYXV6PbJdw7tDQgMEr4uXK5TXQ==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.1.tgz",
+      "integrity": "sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==",
       "dependencies": {
         "@babel/runtime": "^7.25.6",
-        "@mui/types": "^7.2.16",
+        "@mui/types": "^7.2.17",
         "@types/prop-types": "^15.7.12",
         "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
@@ -1223,9 +1223,9 @@
       }
     },
     "node_modules/@types/estree": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
-      "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
       "dev": true
     },
     "node_modules/@types/hoist-non-react-statics": {
@@ -2080,9 +2080,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001660",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
-      "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
+      "version": "1.0.30001662",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz",
+      "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==",
       "dev": true,
       "funding": [
         {
@@ -2409,9 +2409,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.24",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.24.tgz",
-      "integrity": "sha512-0x0wLCmpdKFCi9ulhvYZebgcPmHTkFVUfU2wzDykadkslKwT4oAmDTHEKLnlrDsMGZe4B+ksn8quZfZjYsBetA==",
+      "version": "1.5.25",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz",
+      "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {

+ 4 - 4
frontend/taipy/src/DataNodeViewer.tsx

@@ -786,7 +786,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                 sx={FieldNoMaxWidth}
                                                 value={label || ""}
                                                 onChange={onLabelChange}
-                                                InputProps={{
+                                                slotProps={{input:{
                                                     endAdornment: (
                                                         <InputAdornment position="end">
                                                             <Tooltip title="Apply">
@@ -809,7 +809,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                             </Tooltip>
                                                         </InputAdornment>
                                                     ),
-                                                }}
+                                                }}}
                                                 disabled={!valid}
                                             />
                                         ) : (
@@ -1067,7 +1067,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                                 ? "number"
                                                                 : undefined
                                                         }
-                                                        InputProps={{
+                                                        slotProps={{input: {
                                                             endAdornment: (
                                                                 <InputAdornment position="end">
                                                                     <Tooltip title="Apply">
@@ -1090,7 +1090,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                                     </Tooltip>
                                                                 </InputAdornment>
                                                             ),
-                                                        }}
+                                                        }}}
                                                         disabled={!valid}
                                                     />
                                                 )}

+ 4 - 4
frontend/taipy/src/PropertiesEditor.tsx

@@ -206,7 +206,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                                   data-name="key"
                                                   data-id={property.id}
                                                   onChange={updatePropertyField}
-                                                  inputProps={{ onKeyDown }}
+                                                  slotProps={{ input: { onKeyDown } }}
                                               />
                                           </Grid>
                                           <Grid size={5}>
@@ -219,7 +219,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                                   data-name="value"
                                                   data-id={property.id}
                                                   onChange={updatePropertyField}
-                                                  inputProps={{ onKeyDown, "data-enter": true }}
+                                                  slotProps={{ htmlInput: { onKeyDown, "data-enter": true } }}
                                               />
                                           </Grid>
                                           <Grid
@@ -309,7 +309,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                     variant="outlined"
                                     sx={FieldNoMaxWidth}
                                     disabled={!isDefined}
-                                    inputProps={{ onKeyDown }}
+                                    slotProps={{ htmlInput: { onKeyDown } }}
                                 />
                             </Grid>
                             <Grid size={5}>
@@ -321,7 +321,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                     variant="outlined"
                                     sx={FieldNoMaxWidth}
                                     disabled={!isDefined}
-                                    inputProps={{ onKeyDown, "data-enter": true }}
+                                    slotProps={{ htmlInput: { onKeyDown, "data-enter": true }}}
                                 />
                             </Grid>
                             <Grid size={2} container alignContent="center" alignItems="center" justifyContent="center">

+ 6 - 6
frontend/taipy/src/ScenarioViewer.tsx

@@ -629,7 +629,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                         expandIcon={expandable ? <ArrowForwardIosSharp sx={AccordionIconSx} /> : null}
                         sx={AccordionSummarySx}
                     >
-                        <Stack direction="row" justifyContent="space-between" width="100%" alignItems="center">
+                        <Stack direction="row" justifyContent="space-between" width="100%" alignItems="baseline">
                             <Stack direction="row" spacing={1}>
                                 <Typography>{scLabel}</Typography>
                                 {scPrimary ? (
@@ -712,7 +712,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                             sx={FieldNoMaxWidth}
                                             value={label || ""}
                                             onChange={onLabelChange}
-                                            InputProps={{
+                                            slotProps={{input: {
                                                 onKeyDown: onLabelKeyDown,
                                                 endAdornment: (
                                                     <InputAdornment position="end">
@@ -736,7 +736,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                                         </Tooltip>
                                                     </InputAdornment>
                                                 ),
-                                            }}
+                                            }}}
                                             disabled={!valid}
                                         />
                                     ) : (
@@ -752,7 +752,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                 </Grid>
                                 {showTags ? (
                                     <Grid
-                                    size={12}
+                                        size={12}
                                         container
                                         justifyContent="space-between"
                                         data-focus="tags"
@@ -787,7 +787,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                                         label="Tags"
                                                         sx={tagsAutocompleteSx}
                                                         fullWidth
-                                                        InputProps={{
+                                                        slotProps={{input: {
                                                             ...params.InputProps,
                                                             onKeyDown: onTagsKeyDown,
                                                             endAdornment: (
@@ -812,7 +812,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                                                     </Tooltip>
                                                                 </>
                                                             ),
-                                                        }}
+                                                        }}}
                                                     />
                                                 )}
                                                 disabled={!valid}

+ 1 - 1
taipy/core/job/_job_manager.py

@@ -94,7 +94,7 @@ class _JobManager(_Manager[Job], _VersionMixin):
         if isinstance(job, str):
             job = cls._get(job)
 
-        if not job.is_finished():
+        if job and not job.is_finished():
             reason_collector._add_reason(job.id, JobIsNotFinished(job.id))
 
         return reason_collector

+ 5 - 4
taipy/gui/_gui_section.py

@@ -19,7 +19,7 @@ from ._default_config import default_config
 
 
 class _GuiSection(UniqueSection):
-    name = "gui"
+    name = "gui" # type: ignore[reportAssignmentType]
 
     def __init__(self, property_list: t.Optional[t.List] = None, **properties):
         self._property_list = property_list
@@ -37,13 +37,14 @@ class _GuiSection(UniqueSection):
         return as_dict
 
     @classmethod
-    def _from_dict(cls, as_dict: t.Dict[str, t.Any], *_):
-        return _GuiSection(property_list=list(default_config), **as_dict)
+    def _from_dict(cls, config_as_dict: t.Dict[str, t.Any], id, config):
+        return _GuiSection(property_list=list(default_config), **config_as_dict)
 
     def _update(self, config_as_dict: t.Dict[str, t.Any], default_section=None):
+        as_dict = None
         if self._property_list:
             as_dict = {k: v for k, v in config_as_dict.items() if k in self._property_list}
-        self._properties.update(as_dict)
+        self._properties.update(as_dict or config_as_dict)
 
     @staticmethod
     def _configure(**properties) -> "_GuiSection":

+ 1 - 1
taipy/gui/_page.py

@@ -25,7 +25,7 @@ class _Page(object):
     def __init__(self) -> None:
         self._rendered_jsx: t.Optional[str] = None
         self._renderer: t.Optional[Page] = None
-        self._style: t.Optional[str] = None
+        self._style: t.Optional[t.Union[str, t.Dict[str, t.Any]]] = None
         self._route: t.Optional[str] = None
         self._head: t.Optional[list] = None
 

+ 1 - 1
taipy/gui/_renderers/factory.py

@@ -685,7 +685,7 @@ class _Factory:
         builder = _Factory.__CONTROL_BUILDERS.get(name)
         built = None
         _Factory.__COUNTER += 1
-        with gui._get_autorization():
+        with gui._get_authorization():
             if builder is None:
                 lib, element_name, element = _Factory.__get_library_element(name)
                 if lib:

+ 23 - 23
taipy/gui/config.py

@@ -162,14 +162,16 @@ class _Config(object):
         self.get_time_zone()
 
     def _get_config(self, name: ConfigParameter, default_value: t.Any) -> t.Any:  # pragma: no cover
-        if name in self.config and self.config[name] is not None:
-            if default_value is not None and not isinstance(self.config[name], type(default_value)):
+        if name in self.config and self.config.get(name) is not None:
+            if default_value is not None and not isinstance(self.config.get(name), type(default_value)):
                 try:
-                    return type(default_value)(self.config[name])
+                    return type(default_value)(self.config.get(name))
                 except Exception as e:
-                    _warn(f'app_config "{name}" value "{self.config[name]}" is not of type {type(default_value)}', e)
+                    _warn(
+                        f'app_config "{name}" value "{self.config.get(name)}" is not of type {type(default_value)}', e
+                    )
                     return default_value
-            return self.config[name]
+            return self.config.get(name)
         return default_value
 
     def get_time_zone(self) -> t.Optional[str]:
@@ -234,12 +236,12 @@ class _Config(object):
                     config[key] = _default_stylekit if value else {}
                     continue
                 try:
-                    if isinstance(value, dict) and isinstance(config[key], dict):
-                        config[key].update(value)
+                    if isinstance(value, dict) and isinstance(config.get(key), dict):
+                        t.cast(dict, config.get(key)).update(value)
                     elif key == "port" and str(value).strip() == "auto":
                         config["port"] = "auto"
                     else:
-                        config[key] = value if config[key] is None else type(config[key])(value)
+                        config[key] = value if config.get(key) is None else type(config.get(key))(value)  # type: ignore[reportCallIssue]
                 except Exception as e:
                     _warn(
                         f"Invalid keyword arguments value in Gui.run {key} - {value}. Unable to parse value to the correct type",  # noqa: E501
@@ -282,29 +284,29 @@ class _Config(object):
         app_config = self.config
         logger = _TaipyLogger._get_logger()
         # Special config for notebook runtime
-        if _is_in_notebook() or app_config["run_in_thread"] and not app_config["single_client"]:
+        if _is_in_notebook() or app_config.get("run_in_thread") and not app_config.get("single_client"):
             app_config["single_client"] = True
             self.__log_outside_reloader(logger, "Running in 'single_client' mode in notebook environment")
 
-        if app_config["run_server"] and app_config["ngrok_token"] and app_config["use_reloader"]:
+        if app_config.get("run_server") and app_config.get("ngrok_token") and app_config.get("use_reloader"):
             app_config["use_reloader"] = False
             self.__log_outside_reloader(
                 logger, "'use_reloader' parameter will not be used when 'ngrok_token' parameter is available"
             )
 
-        if app_config["use_reloader"] and _is_in_notebook():
+        if app_config.get("use_reloader") and _is_in_notebook():
             app_config["use_reloader"] = False
             self.__log_outside_reloader(logger, "'use_reloader' parameter is not available in notebook environment")
 
-        if app_config["use_reloader"] and not app_config["debug"]:
+        if app_config.get("use_reloader") and not app_config.get("debug"):
             app_config["debug"] = True
             self.__log_outside_reloader(logger, "Application is running in 'debug' mode")
 
-        if app_config["debug"] and not app_config["allow_unsafe_werkzeug"]:
+        if app_config.get("debug") and not app_config.get("allow_unsafe_werkzeug"):
             app_config["allow_unsafe_werkzeug"] = True
             self.__log_outside_reloader(logger, "'allow_unsafe_werkzeug' has been set to True")
 
-        if app_config["debug"] and app_config["async_mode"] != "threading":
+        if app_config.get("debug") and app_config.get("async_mode") != "threading":
             app_config["async_mode"] = "threading"
             self.__log_outside_reloader(
                 logger,
@@ -318,17 +320,15 @@ class _Config(object):
     def _resolve_stylekit(self):
         app_config = self.config
         # support legacy margin variable
-        stylekit_config = app_config["stylekit"]
+        stylekit_config = app_config.get("stylekit")
 
-        if isinstance(app_config["stylekit"], dict) and "root_margin" in app_config["stylekit"]:
+        if isinstance(stylekit_config, dict) and "root_margin" in stylekit_config:
             from ._default_config import _default_stylekit, default_config
 
-            stylekit_config = app_config["stylekit"]
-            if (
-                stylekit_config["root_margin"] == _default_stylekit["root_margin"]
-                and app_config["margin"] != default_config["margin"]
-            ):
-                app_config["stylekit"]["root_margin"] = str(app_config["margin"])
+            if stylekit_config.get("root_margin") == _default_stylekit.get("root_margin") and app_config.get(
+                "margin"
+            ) != default_config.get("margin"):
+                stylekit_config["root_margin"] = str(app_config.get("margin"))
             app_config["margin"] = None
 
     def _resolve_url_prefix(self):
@@ -343,4 +343,4 @@ class _Config(object):
 
     def _resolve_notebook_proxy(self):
         app_config = self.config
-        app_config["notebook_proxy"] = app_config["notebook_proxy"] if _is_in_notebook() else False
+        app_config["notebook_proxy"] = app_config.get("notebook_proxy", False) if _is_in_notebook() else False

+ 42 - 40
taipy/gui/gui.py

@@ -470,7 +470,7 @@ class Gui:
 
             if callable(provider_fn):
                 try:
-                    return provider_fn(content)
+                    return provider_fn(t.cast(t.Any, content))
                 except Exception as e:
                     _warn(f"Error in content provider for type {str(type(content))}", e)
         return (
@@ -639,7 +639,7 @@ class Gui:
             self.__set_client_id_in_context(expected_client_id)
             g.ws_client_id = expected_client_id
             with self._set_locals_context(message.get("module_context") or None):
-                with self._get_autorization():
+                with self._get_authorization():
                     payload = message.get("payload", {})
                     if msg_type == _WsType.UPDATE.value:
                         self.__front_end_update(
@@ -966,7 +966,7 @@ class Gui:
     def __upload_files(self):
         self.__set_client_id_in_context()
         on_upload_action = request.form.get("on_action", None)
-        var_name = request.form.get("var_name", None)
+        var_name = t.cast(str, request.form.get("var_name", None))
         if not var_name and not on_upload_action:
             _warn("upload files: No var name")
             return ("upload files: No var name", 400)
@@ -1057,7 +1057,7 @@ class Gui:
             else:
                 if isinstance(newvalue, (_TaipyContent, _TaipyContentImage)):
                     ret_value = self.__get_content_accessor().get_info(
-                        front_var, newvalue.get(), isinstance(newvalue, _TaipyContentImage)
+                        t.cast(str, front_var), newvalue.get(), isinstance(newvalue, _TaipyContentImage)
                     )
                     if isinstance(ret_value, tuple):
                         newvalue = f"/{Gui.__CONTENT_ROOT}/{ret_value[0]}"
@@ -1160,8 +1160,8 @@ class Gui:
                 page_path = Gui.__root_page_name
             # Get Module Context
             if mc := self._get_page_context(page_path):
-                page_renderer = self._get_page(page_path)._renderer
-                self._bind_custom_page_variables(page_renderer, self._get_client_id())
+                page_renderer = t.cast(_Page, self._get_page(page_path))._renderer
+                self._bind_custom_page_variables(t.cast(t.Any, page_renderer), self._get_client_id())
                 # get metadata if there is one
                 metadata: t.Dict[str, t.Any] = {}
                 if hasattr(page_renderer, "_metadata"):
@@ -1245,12 +1245,12 @@ class Gui:
 
     def __handle_ws_get_routes(self):
         routes = (
-            [[self._config.root_page._route, self._config.root_page._renderer.page_type]]
+            [[self._config.root_page._route, t.cast(t.Any, self._config.root_page._renderer).page_type]]
             if self._config.root_page
             else []
         )
         routes += [
-            [page._route, page._renderer.page_type]
+            [page._route, t.cast(t.Any, page._renderer).page_type]
             for page in self._config.pages
             if page._route != Gui.__root_page_name
         ]
@@ -1269,7 +1269,7 @@ class Gui:
                 self._server._ws.emit(
                     "message",
                     payload,
-                    to=self.__get_ws_receiver(send_back_only),
+                    to=t.cast(str, self.__get_ws_receiver(send_back_only)),
                 )
                 time.sleep(0.001)
             except Exception as e:  # pragma: no cover
@@ -1280,7 +1280,7 @@ class Gui:
     def __broadcast_ws(self, payload: dict, client_id: t.Optional[str] = None):
         try:
             to = list(self.__get_sids(client_id)) if client_id else []
-            self._server._ws.emit("message", payload, to=to if to else None, include_self=True)
+            self._server._ws.emit("message", payload, to=t.cast(str, to) if to else None, include_self=True)
             time.sleep(0.001)
         except Exception as e:  # pragma: no cover
             _warn(f"Exception raised in WebSocket communication in '{self.__frame.f_code.co_name}'", e)
@@ -1291,7 +1291,7 @@ class Gui:
                 self._server._ws.emit(
                     "message",
                     {"type": _WsType.ACKNOWLEDGEMENT.value, "id": ack_id},
-                    to=self.__get_ws_receiver(True),
+                    to=t.cast(str, self.__get_ws_receiver(True)),
                 )
                 time.sleep(0.001)
             except Exception as e:  # pragma: no cover
@@ -1493,7 +1493,7 @@ class Gui:
 
     def __call_function_with_args(self, **kwargs):
         action_function = kwargs.get("action_function")
-        id = kwargs.get("id")
+        id = t.cast(str, kwargs.get("id"))
         payload = kwargs.get("payload")
 
         if callable(action_function):
@@ -1501,7 +1501,7 @@ class Gui:
                 argcount = action_function.__code__.co_argcount
                 if argcount > 0 and inspect.ismethod(action_function):
                     argcount -= 1
-                args = [None for _ in range(argcount)]
+                args = t.cast(list, [None for _ in range(argcount)])
                 if argcount > 0:
                     args[0] = self.__get_state()
                 if argcount > 1:
@@ -1746,7 +1746,7 @@ class Gui:
                         attributes.get("date_format"),
                         attributes.get("number_format"),
                     )
-                    _enhance_columns(attributes, hashes, col_dict, "table(cols)")
+                    _enhance_columns(attributes, hashes, t.cast(dict, col_dict), "table(cols)")
 
                     return json.dumps(col_dict, cls=_TaipyJsonEncoder)
             except Exception as e:  # pragma: no cover
@@ -1846,7 +1846,7 @@ class Gui:
 
     def _get_locals_context(self) -> str:
         current_context = self.__locals_context.get_context()
-        return current_context if current_context is not None else self.__default_module_name
+        return current_context if current_context is not None else t.cast(str, self.__default_module_name)
 
     def _set_locals_context(self, context: t.Optional[str]) -> t.ContextManager[None]:
         return self.__locals_context.set_locals_context(context)
@@ -2409,7 +2409,7 @@ class Gui:
             The Flask instance used.
         """
         if hasattr(self, "_server"):
-            return self._server.get_flask()
+            return t.cast(Flask, self._server.get_flask())
         raise RuntimeError("get_flask_app() cannot be invoked before run() has been called.")
 
     def _set_frame(self, frame: t.Optional[FrameType]):
@@ -2486,21 +2486,21 @@ class Gui:
                 self,
                 path_mapping=self._path_mapping,
                 flask=self._flask,
-                async_mode=app_config["async_mode"],
-                allow_upgrades=not app_config["notebook_proxy"],
+                async_mode=app_config.get("async_mode"),
+                allow_upgrades=not app_config.get("notebook_proxy"),
                 server_config=app_config.get("server_config"),
             )
 
         # Stop and reinitialize the server if it is still running as a thread
-        if (_is_in_notebook() or app_config["run_in_thread"]) and hasattr(self._server, "_thread"):
+        if (_is_in_notebook() or app_config.get("run_in_thread")) and hasattr(self._server, "_thread"):
             self.stop()
             self._flask_blueprint = []
             self._server = _Server(
                 self,
                 path_mapping=self._path_mapping,
                 flask=self._flask,
-                async_mode=app_config["async_mode"],
-                allow_upgrades=not app_config["notebook_proxy"],
+                async_mode=app_config.get("async_mode"),
+                allow_upgrades=not app_config.get("notebook_proxy"),
                 server_config=app_config.get("server_config"),
             )
             self._bindings()._new_scopes()
@@ -2509,16 +2509,16 @@ class Gui:
         app_config = self._config.config
         if hasattr(self, "_ngrok"):
             # Keep the ngrok instance if token has not changed
-            if app_config["ngrok_token"] == self._ngrok[1]:
+            if app_config.get("ngrok_token") == self._ngrok[1]:
                 _TaipyLogger._get_logger().info(f" * NGROK Public Url: {self._ngrok[0].public_url}")
                 return
             # Close the old tunnel so new tunnel can open for new token
-            ngrok.disconnect(self._ngrok[0].public_url)
-        if app_config["run_server"] and (token := app_config["ngrok_token"]):  # pragma: no cover
+            ngrok.disconnect(self._ngrok[0].public_url)  # type: ignore[reportPossiblyUnboundVariable]
+        if app_config.get("run_server") and (token := app_config.get("ngrok_token")):  # pragma: no cover
             if not util.find_spec("pyngrok"):
                 raise RuntimeError("Cannot use ngrok as pyngrok package is not installed.")
-            ngrok.set_auth_token(token)
-            self._ngrok = (ngrok.connect(app_config["port"], "http"), token)
+            ngrok.set_auth_token(token)  # type: ignore[reportPossiblyUnboundVariable]
+            self._ngrok = (ngrok.connect(app_config.get("port"), "http"), token)  # type: ignore[reportPossiblyUnboundVariable]
             _TaipyLogger._get_logger().info(f" * NGROK Public Url: {self._ngrok[0].public_url}")
 
     def __bind_default_function(self):
@@ -2611,7 +2611,7 @@ class Gui:
 
         # Register Flask Blueprint if available
         for bp in self._flask_blueprint:
-            self._server.get_flask().register_blueprint(bp)
+            t.cast(Flask, self._server.get_flask()).register_blueprint(bp)
 
     def _get_accessor(self):
         if self.__accessors is None:
@@ -2711,7 +2711,7 @@ class Gui:
 
         locals_bind = _filter_locals(self.__frame.f_locals)
 
-        self.__locals_context.set_default(locals_bind, self.__default_module_name)
+        self.__locals_context.set_default(locals_bind, t.cast(str, self.__default_module_name))
 
         self.__var_dir.set_default(self.__frame)
 
@@ -2761,25 +2761,27 @@ class Gui:
         self.__register_blueprint()
 
         # Register data accessor communication data format (JSON, Apache Arrow)
-        self._get_accessor().set_data_format(_DataFormat.APACHE_ARROW if app_config["use_arrow"] else _DataFormat.JSON)
+        self._get_accessor().set_data_format(
+            _DataFormat.APACHE_ARROW if app_config.get("use_arrow") else _DataFormat.JSON
+        )
 
         # Use multi user or not
-        self._bindings()._set_single_client(bool(app_config["single_client"]))
+        self._bindings()._set_single_client(bool(app_config.get("single_client")))
 
         # Start Flask Server
         if not run_server:
             return self.get_flask_app()
 
         return self._server.run(
-            host=app_config["host"],
-            port=app_config["port"],
-            debug=app_config["debug"],
-            use_reloader=app_config["use_reloader"],
-            flask_log=app_config["flask_log"],
-            run_in_thread=app_config["run_in_thread"],
-            allow_unsafe_werkzeug=app_config["allow_unsafe_werkzeug"],
-            notebook_proxy=app_config["notebook_proxy"],
-            port_auto_ranges=app_config["port_auto_ranges"],
+            host=app_config.get("host"),
+            port=app_config.get("port"),
+            debug=app_config.get("debug"),
+            use_reloader=app_config.get("use_reloader"),
+            flask_log=app_config.get("flask_log"),
+            run_in_thread=app_config.get("run_in_thread"),
+            allow_unsafe_werkzeug=app_config.get("allow_unsafe_werkzeug"),
+            notebook_proxy=app_config.get("notebook_proxy"),
+            port_auto_ranges=app_config.get("port_auto_ranges"),
         )
 
     def reload(self):  # pragma: no cover
@@ -2807,7 +2809,7 @@ class Gui:
             self._server.stop_thread()
             _TaipyLogger._get_logger().info("Gui server has been stopped.")
 
-    def _get_autorization(self, client_id: t.Optional[str] = None, system: t.Optional[bool] = False):
+    def _get_authorization(self, client_id: t.Optional[str] = None, system: t.Optional[bool] = False):
         return contextlib.nullcontext()
 
     def set_favicon(self, favicon_path: t.Union[str, Path], state: t.Optional[State] = None):

+ 2 - 2
taipy/gui/partial.py

@@ -53,7 +53,7 @@ class Partial(_Page):
         else:
             self._route = route
 
-    def update_content(self, state: State, content: str | "Page"):
+    def update_content(self, state: State, content: t.Union[str, "Page"]):
         """Update partial content.
 
         Arguments:
@@ -65,7 +65,7 @@ class Partial(_Page):
         else:
             _warn("'Partial.update_content()' must be called in the context of a callback.")
 
-    def __copy(self, content: str | "Page") -> Partial:
+    def __copy(self, content:  t.Union[str, "Page"]) -> Partial:
         new_partial = Partial(self._route)
         from .page import Page
 

+ 5 - 5
taipy/gui/server.py

@@ -247,14 +247,14 @@ class _Server:
         return self._flask
 
     def test_client(self):
-        return self._flask.test_client()
+        return t.cast(Flask, self._flask).test_client()
 
     def _run_notebook(self):
         self._is_running = True
         self._ws.run(self._flask, host=self._host, port=self._port, debug=False, use_reloader=False)
 
     def _get_async_mode(self) -> str:
-        return self._ws.async_mode
+        return self._ws.async_mode  # type: ignore[reportAttributeAccessIssue]
 
     def _apply_patch(self):
         if self._get_async_mode() == "gevent" and util.find_spec("gevent"):
@@ -264,7 +264,7 @@ class _Server:
             if not monkey.is_module_patched("time"):
                 monkey.patch_time()
         if self._get_async_mode() == "eventlet" and util.find_spec("eventlet"):
-            from eventlet import monkey_patch, patcher
+            from eventlet import monkey_patch, patcher  # type: ignore[reportMissingImport]
 
             if not patcher.is_monkey_patched("time"):
                 monkey_patch(time=True)
@@ -349,8 +349,8 @@ class _Server:
             self._is_running = False
             with contextlib.suppress(Exception):
                 if self._get_async_mode() == "gevent":
-                    if self._ws.wsgi_server is not None:
-                        self._ws.wsgi_server.stop()
+                    if self._ws.wsgi_server is not None:  # type: ignore[reportAttributeAccessIssue]
+                        self._ws.wsgi_server.stop()  # type: ignore[reportAttributeAccessIssue]
                     else:
                         self._thread.kill()
                 else:

+ 13 - 13
taipy/gui/utils/_adapter.py

@@ -137,7 +137,7 @@ class _Adapter:
                 return (
                     add(type(result)(tpl_res), result[len(tpl_res) :])
                     if isinstance(result, (tuple, list)) and isinstance(tpl_res, (tuple, list))
-                    else tpl_res
+                    else tpl_res # type: ignore[reportReturnType]
                 )
         except Exception as e:
             _warn(f"Cannot run adapter for {var_name}", e)
@@ -168,9 +168,9 @@ class _Adapter:
             if isinstance(value, (list, tuple)) and len(value):
                 return self.__get_id(value[0], False)
             elif hasattr(value, "id"):
-                return self.__get_id(value.id, False)
+                return self.__get_id(t.cast(t.Any, value).id, False)
             elif hasattr(value, "__getitem__") and "id" in value:
-                return self.__get_id(value.get("id"), False)
+                return self.__get_id(t.cast(dict, value).get("id"), False)
         if value is not None and type(value).__name__ not in self.__warning_by_type:
             _warn(f"LoV id must be a string, using a string representation of {type(value)}.")
             self.__warning_by_type.add(type(value).__name__)
@@ -183,9 +183,9 @@ class _Adapter:
             if isinstance(value, (list, tuple)) and len(value) > 1:
                 return self.__get_label(value[1], False)
             elif hasattr(value, "label"):
-                return self.__get_label(value.label, False)
+                return self.__get_label(t.cast(t.Any, value).label, False)
             elif hasattr(value, "__getitem__") and "label" in value:
-                return self.__get_label(value["label"], False)
+                return self.__get_label(t.cast(dict, value).get("label"), False)
         return None
 
     def __get_children(self, value: t.Any) -> t.Optional[t.List[t.Any]]:
@@ -193,18 +193,18 @@ class _Adapter:
             return value[2] if isinstance(value[2], list) else None if value[2] is None else [value[2]]
         elif hasattr(value, "children"):
             return (
-                value.children
-                if isinstance(value.children, list)
+                t.cast(t.Any, value).children
+                if isinstance(t.cast(t.Any, value).children, list)
                 else None
-                if value.children is None
-                else [value.children]
+                if t.cast(t.Any, value).children is None
+                else [t.cast(t.Any, value).children]
             )
         elif hasattr(value, "__getitem__") and "children" in value:
             return (
-                value["children"]
-                if isinstance(value["children"], list)
+                t.cast(dict, value).get("children")
+                if isinstance(t.cast(dict, value).get("children"), list)
                 else None
-                if value["children"] is None
-                else [value["children"]]
+                if t.cast(dict, value).get("children") is None
+                else [t.cast(dict, value).get("children")]
             )
         return None

+ 1 - 1
taipy/gui/utils/_bindings.py

@@ -40,7 +40,7 @@ class _Bindings:
     def __get_property(self, name):
         def __setter(ud: _Bindings, value: t.Any):
             if isinstance(value, _MapDict):
-                value._update_var = None
+                value._update_var = None  # type: ignore[assignment]
             elif isinstance(value, dict):
                 value = _MapDict(value, None)
             ud.__gui._update_var(name, value)

+ 1 - 1
taipy/gui/utils/_evaluator.py

@@ -242,7 +242,7 @@ class _Evaluator:
             ctx.update(self.__global_ctx)
             # entries in var_val are not always seen (NameError) when passed as locals
             ctx.update(var_val)
-            with gui._get_autorization():
+            with gui._get_authorization():
                 expr_evaluated = eval(not_encoded_expr if is_edge_case else expr_string, ctx)
         except Exception as e:
             _warn(f"Cannot evaluate expression '{not_encoded_expr if is_edge_case else expr_string}'", e)

+ 4 - 4
taipy/gui/utils/_map_dict.py

@@ -22,16 +22,16 @@ class _MapDict(object):
 
     __local_vars = ("_dict", "_update_var")
 
-    def __init__(self, dict_import: dict, app_update_var=None):
+    def __init__(self, dict_import: dict, app_update_var: t.Optional[t.Callable]=None):
         self._dict = dict_import
         # Bind app update var function
-        self._update_var = app_update_var
+        self._update_var = t.cast(t.Callable, app_update_var)
 
     def __len__(self):
         return self._dict.__len__()
 
     def __length_hint__(self):
-        return self._dict.__length_hint__()
+        return self._dict.__length_hint__() # type: ignore[reportAttributeAccessIssue]
 
     def __getitem__(self, key):
         value = self._dict.__getitem__(key)
@@ -52,7 +52,7 @@ class _MapDict(object):
         self._dict.__delitem__(key)
 
     def __missing__(self, key):
-        return self._dict.__missing__(key)
+        return self._dict.__missing__(key) # type: ignore[reportAttributeAccessIssue]
 
     def __iter__(self):
         return self._dict.__iter__()

+ 2 - 2
taipy/gui/utils/_runtime_manager.py

@@ -22,8 +22,8 @@ class _RuntimeManager(object, metaclass=_Singleton):
         self.__port_gui: t.Dict[int, "Gui"] = {}
 
     def add_gui(self, gui: "Gui", port: int):
-        if port in self.__port_gui:
-            self.__port_gui[port].stop()
+        if gui_port := self.__port_gui.get(port):
+            gui_port.stop()
         self.__port_gui[port] = gui
 
     def get_used_port(self):

+ 4 - 4
taipy/gui/utils/_variable_directory.py

@@ -36,7 +36,7 @@ class _VariableDirectory:
         module_name = _get_module_name_from_frame(frame)
         if module_name not in self._imported_var_dir:
             imported_var_list = _get_imported_var(frame)
-            self._imported_var_dir[module_name] = imported_var_list
+            self._imported_var_dir[t.cast(str, module_name)] = imported_var_list
 
     def pre_process_module_import_all(self) -> None:
         for imported_dir in self._imported_var_dir.values():
@@ -54,7 +54,7 @@ class _VariableDirectory:
 
     def process_imported_var(self) -> None:
         self.pre_process_module_import_all()
-        default_imported_dir = self._imported_var_dir[self._default_module]
+        default_imported_dir = self._imported_var_dir[t.cast(str, self._default_module)]
         with self._locals_context.set_locals_context(self._default_module):
             for name, asname, module in default_imported_dir:
                 if name == "*" and asname == "*":
@@ -85,7 +85,7 @@ class _VariableDirectory:
 
     def add_var(self, name: str, module: t.Optional[str], var_name: t.Optional[str] = None) -> str:
         if module is None:
-            module = self._default_module
+            module = t.cast(str, self._default_module)
         if gv := self.get_var(name, module):
             return gv
         var_encode = _variable_encode(name, module) if module != self._default_module else name
@@ -95,7 +95,7 @@ class _VariableDirectory:
         if var_encode != var_name:
             var_name_decode, module_decode = _variable_decode(var_name)
             if module_decode is None:
-                module_decode = self._default_module
+                module_decode = t.cast(str, self._default_module)
             self.__add_var_head(var_name_decode, module_decode, var_encode)
         if name not in self._var_dir:
             self._var_dir[name] = {module: var_name}

+ 4 - 1
taipy/gui/utils/chart_config_builder.py

@@ -207,7 +207,10 @@ def _build_chart_config(gui: "Gui", attributes: t.Dict[str, t.Any], col_types: t
         decimators.append(None)
 
     # set default columns if not defined
-    icols = [[c2 for c2 in [__get_col_from_indexed(c1, i) for c1 in col_dict.keys()] if c2] for i in range(len(traces))]
+    icols = [
+        [c2 for c2 in [__get_col_from_indexed(c1, i) for c1 in t.cast(dict, col_dict).keys()] if c2]
+        for i in range(len(traces))
+    ]
 
     for i, tr in enumerate(traces):
         if i < len(axis):

+ 1 - 1
taipy/gui/utils/datatype.py

@@ -22,4 +22,4 @@ def _get_data_type(value):
             return "int"
         elif pd.api.types.is_float_dtype(value):
             return "float"
-    return re.match(r"^<class '(.*\.)?(.*?)(\d\d)?'>", str(type(value))).group(2)
+    return re.match(r"^<class '(.*\.)?(.*?)(\d\d)?'>", str(type(value))).group(2) # type: ignore[reportOptionalMemberAccess]

+ 1 - 1
taipy/gui/utils/isnotebook.py

@@ -17,7 +17,7 @@ def _is_in_notebook():  # pragma: no cover
         if not util.find_spec("IPython"):
             return False
 
-        from IPython import get_ipython
+        from IPython import get_ipython  # type: ignore[reportPrivateImportUsage]
 
         ipython = get_ipython()
 

+ 1 - 1
taipy/gui_core/__init__.py

@@ -14,5 +14,5 @@
 This package provides classes that can be used in GUI controls dedicated to scenario management.
 """
 
-from ._adapters import CustomScenarioFilter, DataNodeFilter, DataNodeScenarioFilter, ScenarioFilter
 from ._init import *
+from .filters import CustomScenarioFilter, DataNodeFilter, DataNodeScenarioFilter, ScenarioFilter

+ 26 - 87
taipy/gui_core/_adapters.py

@@ -43,11 +43,13 @@ from taipy.gui._warnings import _warn
 from taipy.gui.gui import _DoNotUpdate
 from taipy.gui.utils import _is_boolean, _is_true, _TaipyBase
 
+from .filters import DataNodeFilter, ScenarioFilter, _Filter
+
 
 # prevent gui from trying to push scenario instances to the front-end
 class _GuiCoreDoNotUpdate(_DoNotUpdate):
     def __repr__(self):
-        return self.get_label() if hasattr(self, "get_label") else super().__repr__()
+        return self.get_label() if hasattr(self, "get_label") else super().__repr__()  # type: ignore[reportAttributeAccessIssue]
 
 
 class _EntityType(Enum):
@@ -353,18 +355,18 @@ def _get_entity_property(col: str, *types: t.Type):
         # we compare only strings
         if isinstance(entity, types):
             if isinstance(entity, Cycle):
-                lcol = "creation_date"
-                lfn = None
+                the_col = "creation_date"
+                the_fn = None
             else:
-                lcol = col
-                lfn = col_fn
+                the_col = col
+                the_fn = col_fn
             try:
-                val = attrgetter(lfn or lcol)(entity)
-                if lfn:
+                val = attrgetter(the_fn or the_col)(entity)
+                if the_fn:
                     val = val()
             except AttributeError as e:
                 if _is_debugging():
-                    _warn("Attribute", e)
+                    _warn(f"sort_key({entity.id}):", e)
                 val = ""
         else:
             val = ""
@@ -373,76 +375,17 @@ def _get_entity_property(col: str, *types: t.Type):
     return sort_key
 
 
-@dataclass
-class _Filter(_DoNotUpdate):
-    label: str
-    property_type: t.Optional[t.Type]
-
-    def get_property(self):
-        return self.label
-
-    def get_type(self):
-        if self.property_type is bool:
-            return "boolean"
-        elif self.property_type is int or self.property_type is float:
-            return "number"
-        elif self.property_type is datetime or self.property_type is date:
-            return "date"
-        elif self.property_type is str:
-            return "str"
-        return "any"
-
-
-@dataclass
-class ScenarioFilter(_Filter):
-    property_id: str
-
-    def get_property(self):
-        return self.property_id
-
-
-@dataclass
-class DataNodeScenarioFilter(_Filter):
-    datanode_config_id: str
-    property_id: str
-
-    def get_property(self):
-        return f"{self.datanode_config_id}.{self.property_id}"
-
-
-_CUSTOM_PREFIX = "fn:"
-
-
-@dataclass
-class CustomScenarioFilter(_Filter):
-    filter_function: t.Callable[[Scenario], t.Any]
-
-    def __post_init__(self):
-        if self.filter_function.__name__ == "<lambda>":
-            raise TypeError("ScenarioCustomFilter does not support lambda functions.")
-        mod = self.filter_function.__module__
-        self.module = mod if isinstance(mod, str) else mod.__name__
-
-    def get_property(self):
-        return f"{_CUSTOM_PREFIX}{self.module}:{self.filter_function.__name__}"
-
-    @staticmethod
-    def _get_custom(col: str) -> t.Optional[t.List[str]]:
-        return col[len(_CUSTOM_PREFIX) :].split(":") if col.startswith(_CUSTOM_PREFIX) else None
-
-
-@dataclass
-class DataNodeFilter(_Filter):
-    property_id: str
-
-    def get_property(self):
-        return self.property_id
+@dataclass(frozen=True)
+class _GuiCorePropDesc:
+    filter: _Filter
+    extended: bool = False
+    for_sort: bool = False
 
 
 class _GuiCoreProperties(ABC):
     @staticmethod
     @abstractmethod
-    def get_default_list() -> t.List[_Filter]:
+    def get_default_list() -> t.List[_GuiCorePropDesc]:
         raise NotImplementedError
 
     @staticmethod
@@ -454,7 +397,7 @@ class _GuiCoreProperties(ABC):
         return {}
 
     def get(self):
-        data = super().get()
+        data = super().get()  # type: ignore[reportAttributeAccessIssue]
         if _is_boolean(data):
             if _is_true(data):
                 data = self.get_default_list()
@@ -465,18 +408,21 @@ class _GuiCoreProperties(ABC):
         if isinstance(data, _Filter):
             data = (data,)
         if isinstance(data, (list, tuple)):
-            flist: t.List[_Filter] = []  # type: ignore[annotation-unchecked]
+            f_list: t.List[_Filter] = []  # type: ignore[annotation-unchecked]
             for f in data:
                 if isinstance(f, str):
                     f = f.strip()
                     if f == "*":
-                        flist.extend(p.filter for p in self.get_default_list())
+                        f_list.extend(p.filter for p in self.get_default_list())
                     elif f:
-                        flist.append(
-                            next((p.filter for p in self.get_default_list() if p.get_property() == f), _Filter(f))
+                        f_list.append(
+                            next(
+                                (p.filter for p in self.get_default_list() if p.filter.get_property() == f),
+                                _Filter(f, None),
+                            )
                         )
                 elif isinstance(f, _Filter):
-                    flist.append(f)
+                    f_list.append(f)
             return json.dumps(
                 [
                     (
@@ -487,19 +433,12 @@ class _GuiCoreProperties(ABC):
                     )
                     if self.full_desc()
                     else (attr.label, attr.get_property())
-                    for attr in flist
+                    for attr in f_list
                 ]
             )
         return None
 
 
-@dataclass(frozen=True)
-class _GuiCorePropDesc:
-    filter: _Filter
-    extended: bool = False
-    for_sort: bool = False
-
-
 class _GuiCoreScenarioProperties(_GuiCoreProperties):
     _SC_PROPS: t.List[_GuiCorePropDesc] = [
         _GuiCorePropDesc(ScenarioFilter("Config id", str, "config_id"), for_sort=True),

+ 71 - 64
taipy/gui_core/_context.py

@@ -67,13 +67,13 @@ from taipy.gui.gui import _DoNotUpdate
 from taipy.gui.utils._map_dict import _MapDict
 
 from ._adapters import (
-    CustomScenarioFilter,
     _EntityType,
     _get_entity_property,
     _GuiCoreDatanodeAdapter,
     _GuiCoreScenarioProperties,
     _invoke_action,
 )
+from .filters import CustomScenarioFilter
 
 
 class _GuiCoreContext(CoreEventConsumerBase):
@@ -113,20 +113,20 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def process_event(self, event: Event):
         self.__lazy_start()
-        if event.entity_type == EventEntityType.SCENARIO:
-            with self.gui._get_autorization(system=True):
+        if event.entity_type is EventEntityType.SCENARIO:
+            with self.gui._get_authorization(system=True):
                 self.scenario_refresh(
                     event.entity_id
-                    if event.operation == EventOperation.DELETION or is_readable(t.cast(ScenarioId, event.entity_id))
+                    if event.operation is EventOperation.DELETION or is_readable(t.cast(ScenarioId, event.entity_id))
                     else None
                 )
-        elif event.entity_type == EventEntityType.SEQUENCE and event.entity_id:
+        elif event.entity_type is EventEntityType.SEQUENCE and event.entity_id:
             sequence = None
             try:
-                with self.gui._get_autorization(system=True):
+                with self.gui._get_authorization(system=True):
                     sequence = (
                         core_get(event.entity_id)
-                        if event.operation != EventOperation.DELETION
+                        if event.operation is not EventOperation.DELETION
                         and is_readable(t.cast(SequenceId, event.entity_id))
                         else None
                     )
@@ -134,13 +134,15 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         self.broadcast_core_changed({"scenario": list(sequence.parent_ids)})  # type: ignore
             except Exception as e:
                 _warn(f"Access to sequence {event.entity_id} failed", e)
-        elif event.entity_type == EventEntityType.JOB:
+        elif event.entity_type is EventEntityType.JOB:
             with self.lock:
                 self.jobs_list = None
-            self.broadcast_core_changed({"jobs": event.entity_id})
-        elif event.entity_type == EventEntityType.SUBMISSION:
+            # no broadcast because the submission status will do the job
+            if event.operation is EventOperation.DELETION:
+                self.broadcast_core_changed({"jobs": True})
+        elif event.entity_type is EventEntityType.SUBMISSION:
             self.submission_status_callback(event.entity_id, event)
-        elif event.entity_type == EventEntityType.DATA_NODE:
+        elif event.entity_type is EventEntityType.DATA_NODE:
             with self.lock:
                 self.data_nodes_by_owner = None
             self.broadcast_core_changed(
@@ -178,9 +180,9 @@ class _GuiCoreContext(CoreEventConsumerBase):
             client_id = submission.properties.get("client_id")
             if client_id:
                 running_tasks = {}
-                with self.gui._get_autorization(client_id):
+                with self.gui._get_authorization(client_id):
                     for job in submission.jobs:
-                        job = job if isinstance(job, Job) else core_get(job)
+                        job = job if isinstance(job, Job) else t.cast(Job, core_get(job))
                         running_tasks[job.task.id] = (
                             SubmissionStatus.RUNNING.value
                             if job.is_running()
@@ -190,7 +192,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         )
                     payload.update(tasks=running_tasks)
 
-                    if last_status != new_status:
+                    if last_status is not new_status:
                         # callback
                         submission_name = submission.properties.get("on_submission")
                         if submission_name:
@@ -308,7 +310,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def get_filtered_scenario_list(
         self,
-        entities: t.List[t.Union[t.List, Scenario]],
+        entities: t.List[t.Union[t.List, Scenario, None]],
         filters: t.Optional[t.List[t.Dict[str, t.Any]]],
     ):
         if not filters:
@@ -340,13 +342,15 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 e
                 for e in filtered_list
                 if not isinstance(e, Scenario)
-                or _invoke_action(e, col, col_type, is_datanode_prop, action, val, col_fn)
+                or _invoke_action(e, t.cast(str, col), col_type, is_datanode_prop, action, val, col_fn)
             ]
             # level 2 filtering
             filtered_list = [
                 e
                 if isinstance(e, Scenario)
-                else self.filter_entities(e, col, col_type, is_datanode_prop, action, val, col_fn)
+                else self.filter_entities(
+                    t.cast(list, e), t.cast(str, col), col_type, is_datanode_prop, action, val, col_fn
+                )
                 for e in filtered_list
             ]
         # remove empty cycles
@@ -414,17 +418,17 @@ class _GuiCoreContext(CoreEventConsumerBase):
             or not isinstance(args[start_idx + 2], dict)
         ):
             return
-        error_var = payload.get("error_id")
+        error_var = t.cast(str, payload.get("error_id"))
         update = args[start_idx]
         delete = args[start_idx + 1]
-        data = args[start_idx + 2]
+        data = t.cast(dict, args[start_idx + 2])
         with_dialog = True if len(args) < start_idx + 4 else bool(args[start_idx + 3])
         scenario = None
         user_scenario = None
 
         name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
         if update:
-            scenario_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+            scenario_id = t.cast(ScenarioId, data.get(_GuiCoreContext.__PROP_ENTITY_ID))
             if delete:
                 if not (reason := is_deletable(scenario_id)):
                     state.assign(error_var, f"Scenario. {scenario_id} is not deletable: {_get_reason(reason)}.")
@@ -454,11 +458,11 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 scenario_config = None
                 date = None
             scenario_id = None
+            gui = state.get_gui()
             try:
-                gui = state.get_gui()
                 on_creation = args[0] if isinstance(args[0], str) else None
                 on_creation_function = gui._get_user_function(on_creation) if on_creation else None
-                if callable(on_creation_function):
+                if callable(on_creation_function) and on_creation:
                     try:
                         res = gui._call_function_with_state(
                             on_creation_function,
@@ -503,9 +507,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
                             + f"({len(Config.scenarios) - 1}) found.",
                         )
                         return
-
-                scenario = create_scenario(scenario_config, date, name)
-                scenario_id = scenario.id
+                scenario = create_scenario(scenario_config, date, name) if scenario_config else None
+                scenario_id = scenario.id if scenario else None
             except Exception as e:
                 state.assign(error_var, f"Error creating Scenario. {e}")
             finally:
@@ -547,17 +550,17 @@ class _GuiCoreContext(CoreEventConsumerBase):
         if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
             return
         error_var = payload.get("error_id")
-        data = args[0]
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        data = t.cast(dict, args[0])
+        entity_id = t.cast(str, data.get(_GuiCoreContext.__PROP_ENTITY_ID))
         sequence = data.get("sequence")
         if not self.__check_readable_editable(state, entity_id, "Scenario", error_var):
             return
-        scenario: Scenario = core_get(entity_id)
+        scenario = t.cast(Scenario, core_get(entity_id))
         if scenario:
             try:
                 if not sequence:
                     if isinstance(sequence, str) and (name := data.get(_GuiCoreContext.__PROP_ENTITY_NAME)):
-                        scenario.add_sequence(name, data.get("task_ids"))
+                        scenario.add_sequence(name, t.cast(list, data.get("task_ids")))
                     else:
                         primary = data.get(_GuiCoreContext.__PROP_SCENARIO_PRIMARY)
                         if primary is True:
@@ -572,11 +575,11 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     if data.get("del", False):
                         scenario.remove_sequence(sequence)
                     else:
-                        name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
+                        name = t.cast(str, data.get(_GuiCoreContext.__PROP_ENTITY_NAME))
                         if sequence != name:
                             scenario.rename_sequence(sequence, name)
                         if seqEntity := scenario.sequences.get(name):
-                            seqEntity.tasks = data.get("task_ids")
+                            seqEntity.tasks = t.cast(list, data.get("task_ids"))
                             self.__edit_properties(seqEntity, data)
                         else:
                             _GuiCoreContext.__assign_var(
@@ -595,13 +598,13 @@ class _GuiCoreContext(CoreEventConsumerBase):
         args = payload.get("args")
         if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
             return
-        data = args[0]
+        data = t.cast(dict, args[0])
         error_var = payload.get("error_id")
         try:
-            scenario_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-            entity = core_get(scenario_id)
-            if sequence := data.get("sequence"):
-                entity = entity.sequences.get(sequence)
+            scenario_id = t.cast(str, data.get(_GuiCoreContext.__PROP_ENTITY_ID))
+            entity = t.cast(Scenario, core_get(scenario_id))
+            if sequence := t.cast(str, data.get("sequence")):
+                entity = t.cast(Sequence, entity.sequences.get(sequence))
 
             if not (reason := is_submittable(entity)):
                 _GuiCoreContext.__assign_var(
@@ -631,7 +634,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def get_filtered_datanode_list(
         self,
-        entities: t.List[t.Union[t.List, DataNode]],
+        entities: t.List[t.Union[t.List, DataNode, None]],
         filters: t.Optional[t.List[t.Dict[str, t.Any]]],
     ):
         if not filters or not entities:
@@ -660,13 +663,16 @@ class _GuiCoreContext(CoreEventConsumerBase):
             filtered_list = [
                 e
                 for e in filtered_list
-                if not isinstance(e, DataNode) or _invoke_action(e, col, col_type, False, action, val, col_fn)
+                if not isinstance(e, DataNode)
+                or _invoke_action(e, t.cast(str, col), col_type, False, action, val, col_fn)
             ]
             # level 3 filtering
             filtered_list = [
-                e if isinstance(e, DataNode) else self.filter_entities(d, col, col_type, False, action, val, col_fn)
+                e
+                if isinstance(e, DataNode)
+                else self.filter_entities(d, t.cast(str, col), col_type, False, action, val, col_fn)
                 for e in filtered_list
-                for d in e[2]
+                for d in t.cast(list, t.cast(list, e)[2])
             ]
         # remove empty cycles
         return [e for e in filtered_list if isinstance(e, DataNode) or (isinstance(e, (tuple, list)) and len(e[2]))]
@@ -724,8 +730,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         base_list = []
         else:
             base_list = datanodes
-        adapted_list = self.get_sorted_datanode_list(base_list, sorts)
-        return self.get_filtered_datanode_list(adapted_list, filters)
+        adapted_list = self.get_sorted_datanode_list(t.cast(list, base_list), sorts)
+        return self.get_filtered_datanode_list(t.cast(list, adapted_list), filters)
 
     def data_node_adapter(
         self,
@@ -737,8 +743,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
         if isinstance(data, tuple):
             raise NotImplementedError
         if isinstance(data, list):
-            if data[2] and isinstance(data[2][0], (Cycle, Scenario, Sequence, DataNode)):
-                data[2] = self.get_sorted_datanode_list(data[2], sorts, False)
+            if data[2] and isinstance(t.cast(list, data[2])[0], (Cycle, Scenario, Sequence, DataNode)):
+                data[2] = self.get_sorted_datanode_list(t.cast(list, data[2]), sorts, False)
             return data
         try:
             if hasattr(data, "id") and is_readable(data.id) and core_get(data.id) is not None:
@@ -770,7 +776,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                             data.id,
                             data.get_simple_label(),
                             self.get_sorted_datanode_list(
-                                self.data_nodes_by_owner.get(data.id, []) + list(data.sequences.values()),
+                                t.cast(list, self.data_nodes_by_owner.get(data.id, []) + list(data.sequences.values())),
                                 sorts,
                                 False,
                             ),
@@ -828,7 +834,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
         args = payload.get("args")
         if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
             return
-        data = args[0]
+        data = t.cast(dict, args[0])
         job_ids = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
         job_action = data.get(_GuiCoreContext.__ACTION)
         if job_action and isinstance(job_ids, list):
@@ -879,7 +885,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         [] if job.stacktrace is None else job.stacktrace,
                     )
         except Exception as e:
-            _warn(f"Access to job ({job.id if hasattr(job, 'id') else 'No_id'}) failed", e)
+            _warn(f"Access to job ({job_id}) failed", e)
         return None
 
     def edit_data_node(self, state: State, id: str, payload: t.Dict[str, str]):
@@ -888,11 +894,11 @@ class _GuiCoreContext(CoreEventConsumerBase):
         if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
             return
         error_var = payload.get("error_id")
-        data = args[0]
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        data = t.cast(dict, args[0])
+        entity_id = t.cast(str, data.get(_GuiCoreContext.__PROP_ENTITY_ID))
         if not self.__check_readable_editable(state, entity_id, "DataNode", error_var):
             return
-        entity: DataNode = core_get(entity_id)
+        entity = t.cast(DataNode, core_get(entity_id))
         if isinstance(entity, DataNode):
             try:
                 self.__edit_properties(entity, data)
@@ -905,13 +911,13 @@ class _GuiCoreContext(CoreEventConsumerBase):
         args = payload.get("args")
         if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
             return
-        data = args[0]
+        data = t.cast(dict, args[0])
         error_var = payload.get("error_id")
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        entity_id = t.cast(str, data.get(_GuiCoreContext.__PROP_ENTITY_ID))
         if not self.__check_readable_editable(state, entity_id, "Data node", error_var):
             return
         lock = data.get("lock", True)
-        entity: DataNode = core_get(entity_id)
+        entity = t.cast(DataNode, core_get(entity_id))
         if isinstance(entity, DataNode):
             try:
                 if lock:
@@ -937,13 +943,13 @@ class _GuiCoreContext(CoreEventConsumerBase):
             props = data.get("properties")
             if isinstance(props, (list, tuple)):
                 for prop in props:
-                    key = prop.get("key")
+                    key = t.cast(dict, prop).get("key")
                     if key and key not in _GuiCoreContext.__ENTITY_PROPS:
-                        ent.properties[key] = prop.get("value")
+                        ent.properties[key] = t.cast(dict, prop).get("value")
             deleted_props = data.get("deleted_properties")
             if isinstance(deleted_props, (list, tuple)):
                 for prop in deleted_props:
-                    key = prop.get("key")
+                    key = t.cast(dict, prop).get("key")
                     if key and key not in _GuiCoreContext.__ENTITY_PROPS:
                         ent.properties.pop(key, None)
 
@@ -1006,23 +1012,24 @@ class _GuiCoreContext(CoreEventConsumerBase):
         args = payload.get("args")
         if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
             return
-        data = args[0]
+        data = t.cast(dict, args[0])
         error_var = payload.get("error_id")
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        entity_id = t.cast(str, data.get(_GuiCoreContext.__PROP_ENTITY_ID))
         if not self.__check_readable_editable(state, entity_id, "Data node", error_var):
             return
-        entity: DataNode = core_get(entity_id)
+        entity = t.cast(DataNode, core_get(entity_id))
         if isinstance(entity, DataNode):
             try:
+                val = t.cast(str, data.get("value"))
                 entity.write(
-                    parser.parse(data.get("value"))
+                    parser.parse(val)
                     if data.get("type") == "date"
-                    else int(data.get("value"))
+                    else int(val)
                     if data.get("type") == "int"
-                    else float(data.get("value"))
+                    else float(val)
                     if data.get("type") == "float"
                     else data.get("value"),
-                    comment=data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT),
+                    comment=t.cast(dict, data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT)),
                 )
                 entity.unlock_edit(self.gui._get_client_id())
                 _GuiCoreContext.__assign_var(state, error_var, "")
@@ -1184,7 +1191,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
             try:
                 entity = (
                     core_get(args[0])
-                    if (reason := is_readable(args[0]))
+                    if (reason := is_readable(t.cast(ScenarioId, args[0])))
                     else f"{args[0]} is not readable: {_get_reason(reason)}"
                 )
                 self.gui._call_function_with_state(

+ 99 - 0
taipy/gui_core/filters.py

@@ -0,0 +1,99 @@
+# 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.
+
+import typing as t
+from dataclasses import dataclass
+from datetime import date, datetime
+
+from taipy.core import Scenario
+from taipy.gui.gui import _DoNotUpdate
+
+
+@dataclass
+class _Filter(_DoNotUpdate):
+    label: str
+    property_type: t.Optional[t.Type]
+
+    def get_property(self):
+        return self.label
+
+    def get_type(self):
+        if self.property_type is bool:
+            return "boolean"
+        elif self.property_type is int or self.property_type is float:
+            return "number"
+        elif self.property_type is datetime or self.property_type is date:
+            return "date"
+        elif self.property_type is str:
+            return "str"
+        return "any"
+
+
+@dataclass
+class ScenarioFilter(_Filter):
+    """
+    used to describe a filter on a scenario property
+    """
+
+    property_id: str
+
+    def get_property(self):
+        return self.property_id
+
+
+@dataclass
+class DataNodeScenarioFilter(_Filter):
+    """
+    used to describe a filter on a scenario datanode's property
+    """
+
+    datanode_config_id: str
+    property_id: str
+
+    def get_property(self):
+        return f"{self.datanode_config_id}.{self.property_id}"
+
+
+_CUSTOM_PREFIX = "fn:"
+
+
+@dataclass
+class CustomScenarioFilter(_Filter):
+    """
+    used to describe a custom scenario filter ie based on a user defined function
+    """
+
+    filter_function: t.Callable[[Scenario], t.Any]
+
+    def __post_init__(self):
+        if self.filter_function.__name__ == "<lambda>":
+            raise TypeError("CustomScenarioFilter does not support lambda functions.")
+        mod = self.filter_function.__module__
+        self.module = mod if isinstance(mod, str) else mod.__name__
+
+    def get_property(self):
+        return f"{_CUSTOM_PREFIX}{self.module}:{self.filter_function.__name__}"
+
+    @staticmethod
+    def _get_custom(col: str) -> t.Optional[t.List[str]]:
+        return col[len(_CUSTOM_PREFIX) :].split(":") if col.startswith(_CUSTOM_PREFIX) else None
+
+
+@dataclass
+class DataNodeFilter(_Filter):
+    """
+    used to describe a filter on a datanode property
+    """
+
+    property_id: str
+
+    def get_property(self):
+        return self.property_id

+ 1 - 1
tests/gui_core/test_context_is_readable.py

@@ -178,7 +178,7 @@ class TestGuiCoreContext_is_readable:
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
             mockget.reset_mock()
             mockGui = Mock(Gui)
-            mockGui._get_autorization = lambda s: contextlib.nullcontext()
+            mockGui._get_authorization = lambda s: contextlib.nullcontext()
             gui_core_context = _GuiCoreContext(mockGui)
 
             def sub_cb():