From 861b1c31ef93c2c3c5b15cc7fbd6a5b36348882e Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 2 Jul 2025 09:51:29 -0400 Subject: [PATCH 01/10] Add filepath to extract-meta errors --- dash/extract-meta.js | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/dash/extract-meta.js b/dash/extract-meta.js index 3427ef81d3..997616b031 100755 --- a/dash/extract-meta.js +++ b/dash/extract-meta.js @@ -47,6 +47,7 @@ function getTsConfigCompilerOptions() { let failedBuild = false; const excludedDocProps = ['setProps', 'id', 'className', 'style']; +const errorFiles = []; const isOptional = prop => (prop.getFlags() & ts.SymbolFlags.Optional) !== 0; @@ -91,14 +92,18 @@ function logError(error, filePath) { if (error instanceof Error) { process.stderr.write(error.stack + '\n'); } + if (filePath && !errorFiles.includes(filePath)) { + errorFiles.push(filePath); + } } -function isReservedPropName(propName) { +function isReservedPropName(propName, filepath) { reservedPatterns.forEach(reservedPattern => { if (reservedPattern.test(propName)) { - process.stderr.write( - `\nERROR: "${propName}" matches reserved word ` + - `pattern: ${reservedPattern.toString()}\n` + logError( + `\nERROR:${filepath}: "${propName}" matches reserved word ` + + `pattern: ${reservedPattern.toString()}\n`, + filepath ); failedBuild = true; } @@ -140,7 +145,7 @@ function parseJSX(filepath) { const src = fs.readFileSync(filepath); const doc = reactDocs.parse(src); Object.keys(doc.props).forEach(propName => - isReservedPropName(propName) + isReservedPropName(propName, filepath) ); docstringWarning(doc); return doc; @@ -152,6 +157,7 @@ function parseJSX(filepath) { function gatherComponents(sources, components = {}) { const names = []; const filepaths = []; + let currentFilepath = ""; // For debugging purposes. const gather = filepath => { if (ignorePattern && ignorePattern.test(filepath)) { @@ -166,8 +172,9 @@ function gatherComponents(sources, components = {}) { filepaths.push(filepath); names.push(name); } catch (err) { - process.stderr.write( - `ERROR: Invalid component file ${filepath}: ${err}` + logError( + `ERROR: Invalid component file ${filepath}: ${err}`, + filepath, ); } } @@ -594,7 +601,7 @@ function gatherComponents(sources, components = {}) { properties.forEach(prop => { const name = prop.getName(); - if (isReservedPropName(name)) { + if (isReservedPropName(name, currentFilepath)) { return; } const propType = checker.getTypeOfSymbolAtLocation( @@ -660,6 +667,7 @@ function gatherComponents(sources, components = {}) { }; zipArrays(filepaths, names).forEach(([filepath, name]) => { + currentFilepath = filepath; const source = program.getSourceFile(filepath); const moduleSymbol = checker.getSymbolAtLocation(source); const exports = checker.getExportsOfModule(moduleSymbol); @@ -791,5 +799,9 @@ if (!failedBuild) { process.stdout.write(JSON.stringify(metadata, null, 2)); } else { logError('extract-meta failed'); + logError('Check these files for errors:') + errorFiles.forEach((errorFile) => { + logError(`Error in: ${errorFile}`) + }) process.exit(1); } From ed05ef88fa13bfb889183d49fb08f70b5271de5b Mon Sep 17 00:00:00 2001 From: philippe Date: Thu, 3 Jul 2025 08:29:32 -0400 Subject: [PATCH 02/10] add base prop to extract meta error --- dash/extract-meta.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dash/extract-meta.js b/dash/extract-meta.js index 997616b031..57fc92419d 100755 --- a/dash/extract-meta.js +++ b/dash/extract-meta.js @@ -97,11 +97,11 @@ function logError(error, filePath) { } } -function isReservedPropName(propName, filepath) { +function isReservedPropName(propName, filepath, baseProp) { reservedPatterns.forEach(reservedPattern => { if (reservedPattern.test(propName)) { logError( - `\nERROR:${filepath}: "${propName}" matches reserved word ` + + `\nERROR:${filepath}: "${baseProp ? baseProp + "." : ""}${propName}" matches reserved word ` + `pattern: ${reservedPattern.toString()}\n`, filepath ); @@ -157,7 +157,7 @@ function parseJSX(filepath) { function gatherComponents(sources, components = {}) { const names = []; const filepaths = []; - let currentFilepath = ""; // For debugging purposes. + let currentFilepath = "", currentBasePropName = ""; // For debugging purposes. const gather = filepath => { if (ignorePattern && ignorePattern.test(filepath)) { @@ -601,7 +601,11 @@ function gatherComponents(sources, components = {}) { properties.forEach(prop => { const name = prop.getName(); - if (isReservedPropName(name, currentFilepath)) { + + if (parentType === null) { + currentBasePropName = name; + } + if (isReservedPropName(name, currentFilepath, currentBasePropName)) { return; } const propType = checker.getTypeOfSymbolAtLocation( From 6a04b6ba1d2d07bc562e70a466ff4a1c08085b0a Mon Sep 17 00:00:00 2001 From: philippe Date: Thu, 3 Jul 2025 09:17:51 -0400 Subject: [PATCH 03/10] remove setuptools pkg_resources usage --- dash/_hooks.py | 2 +- dash/dash.py | 2 +- dash/development/component_generator.py | 4 ++-- requirements/install.txt | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dash/_hooks.py b/dash/_hooks.py index 058f0f7e12..7a0c5c8f83 100644 --- a/dash/_hooks.py +++ b/dash/_hooks.py @@ -244,7 +244,7 @@ def get_hooks(cls, hook: str): return cls.hooks.get_hooks(hook) @classmethod - def register_setuptools(cls): + def register_plugins(cls): if cls._registered: # Only have to register once. return diff --git a/dash/dash.py b/dash/dash.py index cf6d0f43f1..1dddf541a2 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -642,7 +642,7 @@ def _setup_hooks(self): from ._hooks import HooksManager self._hooks = HooksManager - self._hooks.register_setuptools() + self._hooks.register_plugins() for setup in self._hooks.get_hooks("setup"): setup(self) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 276dbfb0f5..4d18353bd4 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -8,7 +8,7 @@ import argparse import shutil import functools -import pkg_resources +from importlib import resources import yaml from ._r_components_generation import write_class_file @@ -57,7 +57,7 @@ def generate_components( is_windows = sys.platform == "win32" - extract_path = pkg_resources.resource_filename("dash", "extract-meta.js") + extract_path = os.path.join(str(resources.files("dash")), "extract-meta.js") reserved_patterns = "|".join(f"^{p}$" for p in reserved_words) diff --git a/requirements/install.txt b/requirements/install.txt index df0e1299e3..d5cbde53ca 100644 --- a/requirements/install.txt +++ b/requirements/install.txt @@ -6,4 +6,3 @@ typing_extensions>=4.1.1 requests retrying nest-asyncio -setuptools From e6ed22e8ac0c0828f41d6628bccc9e0370211956 Mon Sep 17 00:00:00 2001 From: philippe Date: Mon, 7 Jul 2025 11:25:16 -0400 Subject: [PATCH 04/10] fix build for 3.8 --- dash/development/component_generator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 4d18353bd4..a08295d3cf 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -57,7 +57,9 @@ def generate_components( is_windows = sys.platform == "win32" - extract_path = os.path.join(str(resources.files("dash")), "extract-meta.js") + # Python 3.8 compatible approach using importlib.resources.path() + with resources.path("dash", "extract-meta.js") as resource_path: + extract_path = str(resource_path) reserved_patterns = "|".join(f"^{p}$" for p in reserved_words) From 376537ba3be190276a6e0b8e4b6e6c4cb6525bdc Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 8 Jul 2025 09:26:14 -0400 Subject: [PATCH 05/10] Exclude typescript prototype types from extraction --- dash/extract-meta.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dash/extract-meta.js b/dash/extract-meta.js index 57fc92419d..347ca45f8a 100755 --- a/dash/extract-meta.js +++ b/dash/extract-meta.js @@ -602,6 +602,12 @@ function gatherComponents(sources, components = {}) { properties.forEach(prop => { const name = prop.getName(); + // Skip symbol properties (e.g., __@iterator@3570, __@asyncIterator@3571, etc.) + // These come from TypeScript's getApparentProperties() including inherited symbols + if (name.startsWith('__@') && /@\d+$/.test(name)) { + return; + } + if (parentType === null) { currentBasePropName = name; } From 3ce1a250ce44f019e7f0b616d0fd87e39c49d1ae Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 8 Jul 2025 10:08:29 -0400 Subject: [PATCH 06/10] build From 84af3ecdfa7dd965538a4a23227edab827a1e0e4 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 8 Jul 2025 11:00:24 -0400 Subject: [PATCH 07/10] fix async/bg tests cleanup --- .../managers/diskcache_manager.py | 9 ++++++-- tests/async_tests/utils.py | 21 ++++++++++++++++++- tests/background_callback/utils.py | 21 ++++++++++++++++++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/dash/background_callback/managers/diskcache_manager.py b/dash/background_callback/managers/diskcache_manager.py index 094485ad71..ae261a1ced 100644 --- a/dash/background_callback/managers/diskcache_manager.py +++ b/dash/background_callback/managers/diskcache_manager.py @@ -85,9 +85,14 @@ def terminate_job(self, job): pass try: - process.wait(1) + process.wait(2) # Increased timeout except (psutil.TimeoutExpired, psutil.NoSuchProcess): - pass + # Force kill if still running + try: + process.kill() + process.wait(1) + except (psutil.TimeoutExpired, psutil.NoSuchProcess): + pass def terminate_unhealthy_job(self, job): import psutil # pylint: disable=import-outside-toplevel,import-error diff --git a/tests/async_tests/utils.py b/tests/async_tests/utils.py index b7074b0735..22ca222197 100644 --- a/tests/async_tests/utils.py +++ b/tests/async_tests/utils.py @@ -147,7 +147,26 @@ def setup_background_callback_app(manager_name, app_name): for job in manager.running_jobs: manager.terminate_job(job) - shutil.rmtree(cache_directory, ignore_errors=True) + # Wait for processes to actually terminate + import time + + for _ in range(10): # Wait up to 5 seconds + if not manager.running_jobs: + break + time.sleep(0.5) + + # Force cleanup with retry logic + import os + + for _ in range(5): + try: + shutil.rmtree(cache_directory, ignore_errors=False) + break + except OSError: + time.sleep(0.5) + else: + # Final attempt with ignore_errors=True + shutil.rmtree(cache_directory, ignore_errors=True) os.environ.pop("LONG_CALLBACK_MANAGER") os.environ.pop("DISKCACHE_DIR") from dash import page_registry diff --git a/tests/background_callback/utils.py b/tests/background_callback/utils.py index c6386f2680..23790a13a2 100644 --- a/tests/background_callback/utils.py +++ b/tests/background_callback/utils.py @@ -150,7 +150,26 @@ def setup_background_callback_app(manager_name, app_name): for job in manager.running_jobs: manager.terminate_job(job) - shutil.rmtree(cache_directory, ignore_errors=True) + # Wait for processes to actually terminate + import time + + for _ in range(10): # Wait up to 5 seconds + if not manager.running_jobs: + break + time.sleep(0.5) + + # Force cleanup with retry logic + import os + + for _ in range(5): + try: + shutil.rmtree(cache_directory, ignore_errors=False) + break + except OSError: + time.sleep(0.5) + else: + # Final attempt with ignore_errors=True + shutil.rmtree(cache_directory, ignore_errors=True) os.environ.pop("LONG_CALLBACK_MANAGER") os.environ.pop("DISKCACHE_DIR") from dash import page_registry From 7102c7d800b6a17730987f52c146e1c6500a7c04 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 8 Jul 2025 11:48:25 -0400 Subject: [PATCH 08/10] fix async/bg tests cleanup --- tests/async_tests/utils.py | 4 ---- tests/background_callback/utils.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/tests/async_tests/utils.py b/tests/async_tests/utils.py index 22ca222197..259b91a98d 100644 --- a/tests/async_tests/utils.py +++ b/tests/async_tests/utils.py @@ -148,16 +148,12 @@ def setup_background_callback_app(manager_name, app_name): manager.terminate_job(job) # Wait for processes to actually terminate - import time - for _ in range(10): # Wait up to 5 seconds if not manager.running_jobs: break time.sleep(0.5) # Force cleanup with retry logic - import os - for _ in range(5): try: shutil.rmtree(cache_directory, ignore_errors=False) diff --git a/tests/background_callback/utils.py b/tests/background_callback/utils.py index 23790a13a2..c18071cd87 100644 --- a/tests/background_callback/utils.py +++ b/tests/background_callback/utils.py @@ -151,16 +151,12 @@ def setup_background_callback_app(manager_name, app_name): manager.terminate_job(job) # Wait for processes to actually terminate - import time - for _ in range(10): # Wait up to 5 seconds if not manager.running_jobs: break time.sleep(0.5) # Force cleanup with retry logic - import os - for _ in range(5): try: shutil.rmtree(cache_directory, ignore_errors=False) From 79f6babf99d53d31e02d30a567598f9a1af103b7 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 8 Jul 2025 13:31:22 -0400 Subject: [PATCH 09/10] build From cf103fcea310e2902b7f77c5c5a51231cb1df4a6 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 8 Jul 2025 15:42:08 -0400 Subject: [PATCH 10/10] fix context in diskcache manager --- dash/background_callback/managers/diskcache_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dash/background_callback/managers/diskcache_manager.py b/dash/background_callback/managers/diskcache_manager.py index ae261a1ced..9c64ae5412 100644 --- a/dash/background_callback/managers/diskcache_manager.py +++ b/dash/background_callback/managers/diskcache_manager.py @@ -1,5 +1,5 @@ import traceback -from contextvars import copy_context +from contextvars import Context import asyncio from functools import partial @@ -227,7 +227,8 @@ def _set_progress(progress_value): def _set_props(_id, props): cache.set(f"{result_key}-set_props", {_id: props}) - ctx = copy_context() + # Create a minimal context to avoid copying ThreadPoolExecutor from parent + ctx = Context() def run(): c = AttributeDict(**context)