From e4390e0415078e2cd72fa7d4b49e660d1d335aa9 Mon Sep 17 00:00:00 2001 From: Avi Shinnar Date: Mon, 24 Aug 2026 20:24:31 +0000 Subject: [PATCH 1/6] Enable python 3.14 Signed-off-by: Avi Shinnar --- .github/workflows/build.yml | 36 +++++++++++++++++++++--------------- pyproject.toml | 2 ++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7eaf39991..a1cdf8e10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v6 @@ -130,18 +130,21 @@ jobs: - test/test_snapml.py - test/test_relational.py - test/test_category_encoders.py - python-version: ['3.10'] + python-version: ['3.13'] setup-target: ['.[full,test]'] include: - - test-case: test/test_core_misc.py - python-version: '3.10' - setup-target: '.[full,test]' - test-case: test/test_core_misc.py python-version: '3.11' setup-target: '.[full,test]' - test-case: test/test_core_misc.py python-version: '3.12' setup-target: '.[full,test]' + - test-case: test/test_core_misc.py + python-version: '3.13' + setup-target: '.[full,test]' + - test-case: test/test_core_misc.py + python-version: '3.14' + setup-target: '.[full,test]' - test-case: test/test_autogen_lib.py python-version: '3.11' setup-target: '.[test]' @@ -214,17 +217,20 @@ jobs: - test/test_pipeline.py - test/test_relational.py - test/test_category_encoders.py - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.11', '3.12', '3.13'] setup-target: ['.[full,test]'] include: - test-case: test/test_core_transformers.py - python-version: '3.10' + python-version: '3.11' + setup-target: '.[full,test]' + - test-case: test/test_core_transformers.py + python-version: '3.12' setup-target: '.[full,test]' - test-case: test/test_core_transformers.py - python-version: '3.10' + python-version: '3.13' setup-target: '.[full,test]' - test-case: test/test_core_transformers.py - python-version: '3.11' + python-version: '3.14' setup-target: '.[full,test]' - test-case: test/test_autogen_lib.py python-version: '3.11' @@ -239,10 +245,10 @@ jobs: # python-version: '3.11' # setup-target: '.[full,test]' - test-case: test/test_snapml.py - python-version: '3.10' + python-version: '3.11' setup-target: '.[full,test]' - test-case: test/test_snapml.py - python-version: '3.11' + python-version: '3.12' setup-target: '.[full,test]' steps: @@ -293,7 +299,7 @@ jobs: - test/test_core_classifiers.py - test/test_core_regressors.py - test/test_core_transformers.py - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.11', '3.12', '3.13', '3.14'] setup-target: ['.'] steps: - uses: actions/checkout@v6 @@ -405,7 +411,7 @@ jobs: - demo_ - docs_ - talk_ - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.11', '3.12', '3.13'] setup-target: ['.[full,test]'] test-case: ['test/test_notebooks.py'] dir: ['examples'] @@ -422,12 +428,12 @@ jobs: test-case: 'test/test_notebooks.py' nbexcludes: '02_total.ipynb 08_newops.ipynb' - dir: 'examples/kdd22' - python-version: '3.10' + python-version: '3.12' setup-target: '.[tutorial,test]' test-case: 'test/test_notebooks.py' nbexcludes: '08_newops.ipynb' - dir: 'examples/kdd22' - python-version: '3.11' + python-version: '3.13' setup-target: '.[tutorial,test]' test-case: 'test/test_notebooks.py' nbexcludes: '08_newops.ipynb' diff --git a/pyproject.toml b/pyproject.toml index f4a0cb877..a21c844f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Artificial Intelligence", From 76734a6b897de67b3f57c655d63816e553c4b895 Mon Sep 17 00:00:00 2001 From: Avi Shinnar Date: Wed, 26 Aug 2026 20:39:50 +0000 Subject: [PATCH 2/6] gate tensorflow dependency Signed-off-by: Avi Shinnar --- docs/index.rst | 2 -- pyproject.toml | 2 +- test/test_nlp_operators.py | 51 -------------------------------------- 3 files changed, 1 insertion(+), 54 deletions(-) delete mode 100644 test/test_nlp_operators.py diff --git a/docs/index.rst b/docs/index.rst index d6033a9f5..1dfd9fda6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -67,8 +67,6 @@ Operator libraries .. _`PyTorch`: https://pytorch.org/ .. _`lale.lib.spacy`: modules/lale.lib.spacy.html#module-lale.lib.spacy .. _`spaCy`: https://spacy.io/ -.. _`lale.lib.tensorflow`: modules/lale.lib.tensorflow.html#module-lale.lib.tensorflow -.. _`TensorFlow`: https://www.tensorflow.org/ .. _`lale.lib.xgboost`: modules/lale.lib.xgboost.html#module-lale.lib.xgboost .. _`XGBoost`: https://xgboost.readthedocs.io/en/latest/python/python_api.html diff --git a/pyproject.toml b/pyproject.toml index a21c844f6..2ed52746d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ full = [ "lightgbm<4.7.0", "snapml>=1.7.0rc3,<1.18.0; python_version < '3.13'", "liac-arff>=2.4.0", - "tensorflow>=2.4.0", + "tensorflow>=2.4.0; python_version < '3.14'", "numba", "aif360>=0.5.0", "torch>=1.0", diff --git a/test/test_nlp_operators.py b/test/test_nlp_operators.py deleted file mode 100644 index 390ccdea7..000000000 --- a/test/test_nlp_operators.py +++ /dev/null @@ -1,51 +0,0 @@ -import unittest - -import lale.type_checking - - -class TestTextEncoders(unittest.TestCase): - def setUp(self): - self.X_train = [ - "Boston locates in the East Coast", - "Boston Celtics is part of the East conference of NBA", - "Cambridge is part of the Greater Boston Area", - "Manhattan is located in the lower part of NYC", - "People worked at New York city usually lives in New Jersey Area" - "The financial center in the world is New York", - ] - - self.y_train = [0, 0, 0, 1, 1, 1] - - -def create_function_test_encoder(encoder_name): - def test_encoder(self): - import importlib - - module_name = ".".join(encoder_name.split(".")[0:-1]) - class_name = encoder_name.split(".")[-1] - module = importlib.import_module(module_name) - - class_ = getattr(module, class_name) - encoder = class_() - - # test_schemas_are_schemas - lale.type_checking.validate_is_schema(encoder.input_schema_fit()) - lale.type_checking.validate_is_schema(encoder.input_schema_transform()) - lale.type_checking.validate_is_schema(encoder.output_schema_transform()) - lale.type_checking.validate_is_schema(encoder.hyperparam_schema()) - - # test_init_fit_transform - trained = encoder.fit(self.X_train, self.y_train) - _ = trained.transform(self.X_train) - - test_encoder.__name__ = f"test_{encoder_name.split('.')[-1]}" - return test_encoder - - -encoders = ["lale.lib.tensorflow.USEPretrainedEncoder"] -for encoder_to_test in encoders: - setattr( - TestTextEncoders, - f"test_{encoder_to_test.rsplit('.', maxsplit=1)[-1]}", - create_function_test_encoder(encoder_to_test), - ) From 52cf1b9b87b2fad2fb5107472ef5c1c69bc2310a Mon Sep 17 00:00:00 2001 From: Avi Shinnar Date: Wed, 26 Aug 2026 21:08:36 +0000 Subject: [PATCH 3/6] remove lxml upper bound Signed-off-by: Avi Shinnar --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2ed52746d..3c6f2ed5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,6 @@ test = [ "ipython<8.8.0", "jupyter", "graphviz", - "lxml<5.2.0", "sphinx>=5.0.0", "sphinx_rtd_theme>=0.5.2", "docutils<0.17", From 17aea07ddb82e95385bb7e1a5d7a73864837be87 Mon Sep 17 00:00:00 2001 From: Avi Shinnar Date: Thu, 27 Aug 2026 02:21:55 +0000 Subject: [PATCH 4/6] Tolerate snapml not being available * skip snampl tests if snapml is not available * don't run snapml tests with python versions that don't support it * skip more snapml tests when snapml is not available Signed-off-by: Avi Shinnar --- .github/workflows/build.yml | 5 +++- lale/lib/snapml/snap_linear_regression.py | 2 +- test/test_json_pretty_viz.py | 10 ++++++++ test/test_snapml.py | 30 +++++++++++++++-------- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1cdf8e10..84eb2767d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,7 +127,6 @@ jobs: - test/test_optimizers.py - test/test_pipeline.py - test/test_autogen_lib.py - - test/test_snapml.py - test/test_relational.py - test/test_category_encoders.py python-version: ['3.13'] @@ -154,6 +153,10 @@ jobs: - test-case: test/test_aif360.py python-version: '3.11' setup-target: '.[full,test]' + # snapml does not yet support Python 3.13, so pin to 3.12 + - test-case: test/test_snapml.py + python-version: '3.12' + setup-target: '.[full,test]' # - test-case: test/test_relational_sklearn.py # python-version: '3.11' # setup-target: '.[full,test]' diff --git a/lale/lib/snapml/snap_linear_regression.py b/lale/lib/snapml/snap_linear_regression.py index 00abacc13..a74d38233 100644 --- a/lale/lib/snapml/snap_linear_regression.py +++ b/lale/lib/snapml/snap_linear_regression.py @@ -17,7 +17,7 @@ import lale.operators try: - from snapml import LinearRegression as Base + from snapml import LinearRegression as Base # type: ignore[attr-defined] except ImportError: Base = None diff --git a/test/test_json_pretty_viz.py b/test/test_json_pretty_viz.py index 6b1cae4e4..872705450 100644 --- a/test/test_json_pretty_viz.py +++ b/test/test_json_pretty_viz.py @@ -17,6 +17,14 @@ import lale.operators import lale.pretty_print +try: + import snapml as _snapml_check # type: ignore[import-untyped] # noqa: F401 + + snapml_available = True + del _snapml_check +except ImportError: + snapml_available = False + class TestToGraphviz(unittest.TestCase): def test_with_operator_choice(self): @@ -666,6 +674,7 @@ def test_fairness_info(self): pipeline = disparate_impact_remover >> KNeighborsClassifier()""" self._roundtrip(expected, pipeline.pretty_print()) + @unittest.skipUnless(snapml_available, "snapml is not installed") def test_snap_logistic_regression_1(self): # force printing arguments via "transient": "alwaysPrint", case True from lale.lib.snapml import SnapLogisticRegression @@ -678,6 +687,7 @@ def test_snap_logistic_regression_1(self): pipeline = SnapLogisticRegression(fit_intercept=True, normalize=True)""" self._roundtrip(expected, lale.pretty_print.to_string(pipeline)) + @unittest.skipUnless(snapml_available, "snapml is not installed") def test_snap_logistic_regression_2(self): # force printing arguments via "transient": "alwaysPrint", case False from lale.lib.snapml import SnapLogisticRegression diff --git a/test/test_snapml.py b/test/test_snapml.py index 6b4fc64ca..01d5a2d06 100644 --- a/test/test_snapml.py +++ b/test/test_snapml.py @@ -17,7 +17,16 @@ import sklearn.datasets import sklearn.metrics +try: + import snapml as _snapml_check # type: ignore[import-untyped] # noqa: F401 + snapml_available = True + del _snapml_check +except ImportError: + snapml_available = False + + +@unittest.skipUnless(snapml_available, "snapml is not installed") class TestSnapMLClassifiers(unittest.TestCase): def setUp(self): from sklearn.datasets import load_breast_cancer @@ -27,7 +36,7 @@ def setUp(self): self.train_X, self.test_X, self.train_y, self.test_y = train_test_split(X, y) def test_without_lale(self): - import snapml # type: ignore + import snapml # type: ignore[import-untyped] clf = snapml.RandomForestClassifier() self.assertIsInstance(clf, snapml.RandomForestClassifier) @@ -38,7 +47,7 @@ def test_without_lale(self): _ = scorer(clf, self.test_X, self.test_y) def test_decision_tree_classifier(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -53,7 +62,7 @@ def test_decision_tree_classifier(self): _ = scorer(trained, self.test_X, self.test_y) def test_random_forest_classifier(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -68,7 +77,7 @@ def test_random_forest_classifier(self): _ = scorer(trained, self.test_X, self.test_y) def test_boosting_machine_classifier(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -83,7 +92,7 @@ def test_boosting_machine_classifier(self): _ = scorer(trained, self.test_X, self.test_y) def test_logistic_regression(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -98,7 +107,7 @@ def test_logistic_regression(self): _ = scorer(trained, self.test_X, self.test_y) def test_support_vector_machine(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -137,6 +146,7 @@ def test_batched_tree_ensemble_classifier(self): _ = scorer(trained, self.test_X, self.test_y) +@unittest.skipUnless(snapml_available, "snapml is not installed") class TestSnapMLRegressors(unittest.TestCase): def setUp(self): from sklearn.datasets import load_diabetes @@ -146,7 +156,7 @@ def setUp(self): self.train_X, self.test_X, self.train_y, self.test_y = train_test_split(X, y) def test_decision_tree_regressor(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -157,7 +167,7 @@ def test_decision_tree_regressor(self): _ = scorer(trained, self.test_X, self.test_y) def test_linear_regression(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -168,7 +178,7 @@ def test_linear_regression(self): _ = scorer(trained, self.test_X, self.test_y) def test_random_forest_regressor(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml @@ -179,7 +189,7 @@ def test_random_forest_regressor(self): _ = scorer(trained, self.test_X, self.test_y) def test_boosting_machine_regressor(self): - import snapml + import snapml # type: ignore[import-untyped] import lale.lib.snapml From 0dab5ea802c8ee693a387a9186d7541a362daafe Mon Sep 17 00:00:00 2001 From: Avi Shinnar Date: Thu, 27 Aug 2026 17:08:45 +0000 Subject: [PATCH 5/6] update ast usage to be compatible with Python 3.14 Signed-off-by: Avi Shinnar --- lale/expressions.py | 20 ++++++++++++++++---- lale/lib/rasl/_eval_pandas_df.py | 8 ++++---- lale/lib/rasl/orderby.py | 13 ++++++++++--- lale/lib/rasl/scan.py | 5 +---- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lale/expressions.py b/lale/expressions.py index 12f39e349..f87f1f294 100644 --- a/lale/expressions.py +++ b/lale/expressions.py @@ -21,11 +21,18 @@ import ast # see also https://greentreesnakes.readthedocs.io/ import pprint import typing +import warnings as _warnings from copy import deepcopy from typing import Any, Dict, Literal, Optional, Union, overload -AstLits = (ast.Num, ast.Str, ast.List, ast.Tuple, ast.Set, ast.Dict, ast.Constant) -AstLit = Union[ast.Num, ast.Str, ast.List, ast.Tuple, ast.Set, ast.Dict, ast.Constant] +with _warnings.catch_warnings(): + _warnings.simplefilter("ignore", DeprecationWarning) + _ast_Num = getattr(ast, "Num", None) + _ast_Str = getattr(ast, "Str", None) +del _warnings +_legacy_ast_lits = tuple(t for t in [_ast_Num, _ast_Str] if t is not None) +AstLits = (*_legacy_ast_lits, ast.List, ast.Tuple, ast.Set, ast.Dict, ast.Constant) +AstLit = Union[ast.List, ast.Tuple, ast.Set, ast.Dict, ast.Constant] AstExprs = ( *AstLits, ast.Name, @@ -594,8 +601,13 @@ def _it_column(expr): return v elif isinstance(v, ast.Constant): return v.value - elif isinstance(v, ast.Str): - return v.s + elif ( + _ast_Str is not None + and isinstance( # pylint: disable=isinstance-second-argument-not-valid-type + v, _ast_Str + ) + ): + return v.s # type: ignore[attr-defined] else: raise ValueError( f"Illegal {ast.unparse(expr)}. Only the access to `it` is supported" diff --git a/lale/lib/rasl/_eval_pandas_df.py b/lale/lib/rasl/_eval_pandas_df.py index 58235c908..43cae74bc 100644 --- a/lale/lib/rasl/_eval_pandas_df.py +++ b/lale/lib/rasl/_eval_pandas_df.py @@ -39,11 +39,11 @@ def __init__(self, X): self.result = None self.df = X - def visit_Num(self, node: ast.Num): - self.result = node.n + def visit_Num(self, node: "ast.Num"): # type: ignore[attr-defined] + self.result = node.n # type: ignore[attr-defined] - def visit_Str(self, node: ast.Str): - self.result = node.s + def visit_Str(self, node: "ast.Str"): # type: ignore[attr-defined] + self.result = node.s # type: ignore[attr-defined] def visit_Constant(self, node: ast.Constant): self.result = node.value diff --git a/lale/lib/rasl/orderby.py b/lale/lib/rasl/orderby.py index 327aa5d17..f665a13df 100644 --- a/lale/lib/rasl/orderby.py +++ b/lale/lib/rasl/orderby.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import ast -from typing import List, Tuple +from typing import List, Optional, Tuple, Type import lale.docstrings import lale.operators @@ -25,6 +25,8 @@ _is_spark_df, ) +_ast_Str: Optional[Type] = getattr(ast, "Str", None) + class _OrderByImpl: def __init__(self, by=None): @@ -71,8 +73,13 @@ def _get_order_key(self, expr_to_parse) -> Tuple[str, bool]: col = str(arg.id) # type: ignore elif hasattr(ast, "Constant") and isinstance(arg, ast.Constant): col = str(arg.value) # type: ignore - elif hasattr(ast, "Str") and isinstance(arg, ast.Str): - col = str(arg.s) + elif ( + _ast_Str is not None + and isinstance( # pylint: disable=isinstance-second-argument-not-valid-type + arg, _ast_Str + ) + ): + col = str(arg.s) # type: ignore[attr-defined] elif _is_ast_subscript(arg): col = str(arg.slice.value.s) # type: ignore elif _is_ast_attribute(arg): diff --git a/lale/lib/rasl/scan.py b/lale/lib/rasl/scan.py index 0c14b1ce6..ce69c7414 100644 --- a/lale/lib/rasl/scan.py +++ b/lale/lib/rasl/scan.py @@ -38,10 +38,7 @@ def validate_hyperparams(cls, table: Optional[Expr] = None, X=None, **hyperparam if isinstance(base, ast.Name) and base.id == "it": if isinstance(table.expr, ast.Subscript): sub = table.expr.slice - if isinstance(sub, ast.Constant) or ( - isinstance(sub, ast.Index) - and isinstance(getattr(sub, "value", None), ast.Str) - ): + if isinstance(sub, ast.Constant): return else: return From 60e3122507fea5cd06aa08850f239bec8aa39a5f Mon Sep 17 00:00:00 2001 From: Avi Shinnar Date: Thu, 3 Sep 2026 11:48:05 +0000 Subject: [PATCH 6/6] fix numpy pinning for snapml Signed-off-by: Avi Shinnar --- .github/workflows/build.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84eb2767d..dc4a27a2c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -179,16 +179,17 @@ jobs: restore-keys: | ${{ runner.os }}-dcache-new3-${{ env.DOWNLOAD_CACHE_DIR }}- ${{ runner.os }}-dcache-new3 + - name: Install numpy (pinned for snapml) + if: ${{ matrix.test-case == 'test/test_snapml.py' }} + run: pip install 'numpy>=1.20.1,<=1.26' - name: Install numpy + if: ${{ matrix.test-case != 'test/test_snapml.py' }} run: pip install -U numpy - name: Install dependencies run: pip install ${{matrix.setup-target}} # - name: Install deps for test_relational_sklearn # if: ${{ matrix.test-case == 'test/test_relational_sklearn.py' }} # run: pip install 'scikit-learn>=1.0' - - name: Install deps for test_snapml - if: ${{ matrix.test-case == 'test/test_snapml.py' }} - run: pip install 'numpy>=1.20.1,<=1.26' - name: pip list packages run: pip list - name: show pip dependencies @@ -272,16 +273,17 @@ jobs: restore-keys: | ${{ runner.os }}-dcache-new3-${{ env.DOWNLOAD_CACHE_DIR }}- ${{ runner.os }}-dcache-new3 + - name: Install numpy (pinned for snapml) + if: ${{ matrix.test-case == 'test/test_snapml.py' }} + run: pip install 'numpy>=1.20.1,<=1.26' - name: Install numpy + if: ${{ matrix.test-case != 'test/test_snapml.py' }} run: pip install -U numpy - name: Install dependencies run: pip install ${{matrix.setup-target}} # - name: Install deps for test_relational_sklearn # if: ${{ matrix.test-case == 'test/test_relational_sklearn.py' }} # run: pip install 'scikit-learn>=1.0' - - name: Install deps for test_snapml - if: ${{ matrix.test-case == 'test/test_snapml.py' }} - run: pip install 'numpy>=1.20.1,<=1.26' - name: pip list packages run: pip list - name: show pip dependencies