diff --git a/.github/workflows/gds-integration-tests.yml b/.github/workflows/gds-integration-tests.yml index fb51388e..e2b8d17c 100644 --- a/.github/workflows/gds-integration-tests.yml +++ b/.github/workflows/gds-integration-tests.yml @@ -39,5 +39,5 @@ jobs: env: AURA_API_CLIENT_ID: 4V1HYCYEeoU4dSxThKnBeLvE2U4hSphx AURA_API_CLIENT_SECRET: ${{ secrets.AURA_API_CLIENT_SECRET }} - AURA_API_TENANT_ID: 3f8df5e7-4800-4d4f-ad1d-2d044dfd587c + AURA_API_PROJECT_ID: 3f8df5e7-4800-4d4f-ad1d-2d044dfd587c run: uv run pytest tests/ --include-neo4j-and-gds diff --git a/python-wrapper/pyproject.toml b/python-wrapper/pyproject.toml index fd065a48..2f550e39 100644 --- a/python-wrapper/pyproject.toml +++ b/python-wrapper/pyproject.toml @@ -116,8 +116,10 @@ filterwarnings = [ "error", "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # snowflake vendors an older `requests` whose dependency check rejects the chardet - # version pulled in transitively by the notebook group. Harmless; ignore it. - "ignore:.*doesn't match a supported version:snowflake.connector.vendored.requests.exceptions.RequestsDependencyWarning" + # version (pulled in transitively via `encutils`). Harmless; ignore it. + # Matched by message only: naming the category here would make pytest import + # `snowflake.connector` to resolve it, and that import is exactly what fails. + "ignore:.*doesn't match a supported version" ] [tool.ruff] diff --git a/python-wrapper/tests/conftest.py b/python-wrapper/tests/conftest.py index 543b276a..70f70bb7 100644 --- a/python-wrapper/tests/conftest.py +++ b/python-wrapper/tests/conftest.py @@ -1,11 +1,7 @@ -import os -import random -from typing import Any, Generator - import pytest -def pytest_addoption(parser: Any) -> None: +def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption( "--include-neo4j-and-gds", action="store_true", @@ -18,7 +14,7 @@ def pytest_addoption(parser: Any) -> None: ) -def pytest_collection_modifyitems(config: Any, items: Any) -> None: +def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None: if not config.getoption("--include-neo4j-and-gds"): skip = pytest.mark.skip(reason="skipping since requiring Neo4j instance with GDS running") for item in items: @@ -29,96 +25,3 @@ def pytest_collection_modifyitems(config: Any, items: Any) -> None: for item in items: if "requires_snowflake" in item.keywords: item.add_marker(skip) - - -@pytest.fixture(scope="package") -def aura_db_instance() -> Generator[Any, None, None]: - if os.environ.get("NEO4J_URI", ""): - print(f"Skipping Aura DB setup since NEO4J_URI is set to {os.environ['NEO4J_URI']}") - yield None - return - - if os.environ.get("AURA_API_CLIENT_ID", None) is None: - yield None - return - - from tests.gds_helper import aura_api, create_auradb_instance, wait_for_instance - - api = aura_api() - instance_details = create_auradb_instance(api) - - try: - dbms_connection_info = wait_for_instance(api, instance_details) - - # setting as environment variables to run notebooks with this connection - os.environ["NEO4J_URI"] = dbms_connection_info.get_uri() - assert isinstance(dbms_connection_info.username, str) - os.environ["NEO4J_USERNAME"] = dbms_connection_info.username - assert isinstance(dbms_connection_info.password, str) - os.environ["NEO4J_PASSWORD"] = dbms_connection_info.password - old_instance = os.environ.get("AURA_INSTANCEID", "") - if dbms_connection_info.aura_instance_id: - os.environ["AURA_INSTANCEID"] = dbms_connection_info.aura_instance_id - - yield dbms_connection_info - - # Clear Neo4j_URI after test (rerun should create a new instance) - os.environ["AURA_INSTANCEID"] = old_instance - assert dbms_connection_info.aura_instance_id is not None - finally: - api.delete_instance(instance_details.id) - - -@pytest.fixture(scope="package") -def gds(aura_db_instance: Any) -> Generator[Any, None, None]: - from graphdatascience.session import SessionMemory - - from tests.gds_helper import connect_to_local_gds_session, connect_to_plugin_gds, gds_sessions - - if aura_db_instance: - sessions = gds_sessions() - - gds = sessions.get_or_create( - f"neo4j-viz-ci-{os.environ.get('GITHUB_RUN_ID', random.randint(0, 10**6))}", - memory=SessionMemory.m_2GB, - db_connection=aura_db_instance, - ) - - yield gds - gds.delete() - else: - neo4j_uri = os.environ["NEO4J_URI"] - neo4j_auth = (os.environ.get("NEO4J_USERNAME", "neo4j"), os.environ.get("NEO4J_PASSWORD", "password")) - - session_uri = os.environ.get("GDS_SESSION_URI") - if session_uri: - gds = connect_to_local_gds_session(session_uri, neo4j_uri, neo4j_auth) - else: - gds = connect_to_plugin_gds(neo4j_uri, neo4j_auth) # type: ignore - yield gds - gds.close() - - -@pytest.fixture(scope="package") -def neo4j_driver(aura_db_instance: Any) -> Generator[Any, None, None]: - import neo4j - - if aura_db_instance: - driver = neo4j.GraphDatabase.driver( - aura_db_instance.uri, auth=(aura_db_instance.username, aura_db_instance.password) - ) - else: - NEO4J_URI = os.environ.get("NEO4J_URI", "neo4j://localhost:7687") - driver = neo4j.GraphDatabase.driver(NEO4J_URI) - - try: - driver.verify_connectivity() - yield driver - finally: - driver.close() - - -@pytest.fixture(scope="package") -def neo4j_session(neo4j_driver: Any) -> Generator[Any, None, None]: - with neo4j_driver.session() as session: - yield session diff --git a/python-wrapper/tests/neo4j_and_gds/__init__.py b/python-wrapper/tests/neo4j_and_gds/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python-wrapper/tests/neo4j_and_gds/conftest.py b/python-wrapper/tests/neo4j_and_gds/conftest.py new file mode 100644 index 00000000..ffc454d2 --- /dev/null +++ b/python-wrapper/tests/neo4j_and_gds/conftest.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +import os +import random +from typing import Generator + +import pytest + +pytest.importorskip("graphdatascience") + +import neo4j +from graphdatascience import GraphDataScience +from graphdatascience.session import AuraGraphDataScience, DbmsConnectionInfo, SessionMemory + +from tests.neo4j_and_gds.gds_helper import ( + aura_api, + connect_to_local_gds_session, + connect_to_plugin_gds, + create_auradb_instance, + gds_sessions, + wait_for_instance, +) + + +@pytest.fixture(scope="package") +def aura_db_instance() -> Generator[DbmsConnectionInfo | None, None, None]: + if os.environ.get("NEO4J_URI", ""): + print(f"Skipping Aura DB setup since NEO4J_URI is set to {os.environ['NEO4J_URI']}") + yield None + return + + if os.environ.get("AURA_API_CLIENT_ID", None) is None: + yield None + return + + api = aura_api() + instance_details = create_auradb_instance(api) + + try: + dbms_connection_info = wait_for_instance(api, instance_details) + + # setting as environment variables to run notebooks with this connection + os.environ["NEO4J_URI"] = dbms_connection_info.get_uri() + assert isinstance(dbms_connection_info.username, str) + os.environ["NEO4J_USERNAME"] = dbms_connection_info.username + assert isinstance(dbms_connection_info.password, str) + os.environ["NEO4J_PASSWORD"] = dbms_connection_info.password + old_instance = os.environ.get("AURA_INSTANCEID", "") + if dbms_connection_info.aura_instance_id: + os.environ["AURA_INSTANCEID"] = dbms_connection_info.aura_instance_id + + yield dbms_connection_info + + # Clear Neo4j_URI after test (rerun should create a new instance) + os.environ["AURA_INSTANCEID"] = old_instance + assert dbms_connection_info.aura_instance_id is not None + finally: + api.delete_instance(instance_details.id) + + +@pytest.fixture(scope="package") +def gds(aura_db_instance: DbmsConnectionInfo | None) -> Generator[GraphDataScience | AuraGraphDataScience, None, None]: + if aura_db_instance: + sessions = gds_sessions() + + gds = sessions.get_or_create( + f"neo4j-viz-ci-{os.environ.get('GITHUB_RUN_ID', random.randint(0, 10**6))}", + memory=SessionMemory.m_2GB, + db_connection=aura_db_instance, + ) + + yield gds + gds.delete() + else: + neo4j_uri = os.environ["NEO4J_URI"] + neo4j_auth = (os.environ.get("NEO4J_USERNAME", "neo4j"), os.environ.get("NEO4J_PASSWORD", "password")) + + session_uri = os.environ.get("GDS_SESSION_URI") + if session_uri: + gds = connect_to_local_gds_session(session_uri, neo4j_uri, neo4j_auth) + else: + gds = connect_to_plugin_gds(neo4j_uri, neo4j_auth) # type: ignore + yield gds + gds.close() + + +@pytest.fixture(scope="package") +def neo4j_driver(aura_db_instance: DbmsConnectionInfo | None) -> Generator[neo4j.Driver, None, None]: + if aura_db_instance: + assert aura_db_instance.uri is not None + assert aura_db_instance.username is not None + assert aura_db_instance.password is not None + driver = neo4j.GraphDatabase.driver( + aura_db_instance.uri, auth=(aura_db_instance.username, aura_db_instance.password) + ) + else: + NEO4J_URI = os.environ.get("NEO4J_URI", "neo4j://localhost:7687") + driver = neo4j.GraphDatabase.driver(NEO4J_URI) + + try: + driver.verify_connectivity() + yield driver + finally: + driver.close() + + +@pytest.fixture(scope="package") +def neo4j_session(neo4j_driver: neo4j.Driver) -> Generator[neo4j.Session, None, None]: + with neo4j_driver.session() as session: + yield session diff --git a/python-wrapper/tests/gds_helper.py b/python-wrapper/tests/neo4j_and_gds/gds_helper.py similarity index 100% rename from python-wrapper/tests/gds_helper.py rename to python-wrapper/tests/neo4j_and_gds/gds_helper.py diff --git a/python-wrapper/tests/test_gds.py b/python-wrapper/tests/neo4j_and_gds/test_gds.py similarity index 87% rename from python-wrapper/tests/test_gds.py rename to python-wrapper/tests/neo4j_and_gds/test_gds.py index ef9c4d8a..46d9994d 100644 --- a/python-wrapper/tests/test_gds.py +++ b/python-wrapper/tests/neo4j_and_gds/test_gds.py @@ -1,14 +1,19 @@ import re -from typing import Any, Generator +from contextlib import AbstractContextManager +from typing import Generator import pandas as pd import pytest +from graphdatascience import GraphDataScience +from graphdatascience.graph.v2 import GraphV2 +from graphdatascience.session import AuraGraphDataScience from neo4j_viz import Node +from neo4j_viz.gds import from_gds @pytest.fixture(scope="class") -def db_setup(gds: Any) -> Generator[None, None, None]: +def db_setup(gds: GraphDataScience | AuraGraphDataScience) -> Generator[None, None, None]: gds.run_cypher( "CREATE " " (a:_CI_A {name:'Alice', height:20, id:42, _id: 1337, caption: 'hello'})" @@ -20,10 +25,7 @@ def db_setup(gds: Any) -> Generator[None, None, None]: gds.run_cypher("MATCH (n:_CI_A|_CI_B) DETACH DELETE n") -def project_graph(gds: Any) -> Any: - from graphdatascience import GraphDataScience - from graphdatascience.session import AuraGraphDataScience - +def project_graph(gds: GraphDataScience | AuraGraphDataScience) -> AbstractContextManager[GraphV2]: if isinstance(gds, GraphDataScience): return gds.v2.graph.project("g2", "*", "*") elif isinstance(gds, AuraGraphDataScience): @@ -33,9 +35,7 @@ def project_graph(gds: Any) -> Any: @pytest.mark.filterwarnings("ignore::DeprecationWarning") @pytest.mark.requires_neo4j_and_gds -def test_from_gds_integration_all_db_properties(gds: Any, db_setup: None) -> None: - from neo4j_viz.gds import from_gds - +def test_from_gds_integration_all_db_properties(gds: GraphDataScience | AuraGraphDataScience, db_setup: None) -> None: with project_graph(gds) as G: VG = from_gds(gds, G, db_node_properties=["name"]) @@ -44,9 +44,7 @@ def test_from_gds_integration_all_db_properties(gds: Any, db_setup: None) -> Non @pytest.mark.requires_neo4j_and_gds -def test_from_gds_integration_all_properties(gds: Any) -> None: - from neo4j_viz.gds import from_gds - +def test_from_gds_integration_all_properties(gds: GraphDataScience | AuraGraphDataScience) -> None: nodes = pd.DataFrame( { "nodeId": [0, 1, 2], @@ -114,9 +112,7 @@ def test_from_gds_integration_all_properties(gds: Any) -> None: @pytest.mark.requires_neo4j_and_gds -def test_from_gds_sample(gds: Any) -> None: - from neo4j_viz.gds import from_gds - +def test_from_gds_sample(gds: GraphDataScience | AuraGraphDataScience) -> None: with gds.v2.graph.generate("hello", node_count=11_000, average_degree=1) as G: with pytest.warns( UserWarning, @@ -136,9 +132,7 @@ def test_from_gds_sample(gds: Any) -> None: @pytest.mark.requires_neo4j_and_gds -def test_from_gds_hetero(gds: Any) -> None: - from neo4j_viz.gds import from_gds - +def test_from_gds_hetero(gds: GraphDataScience | AuraGraphDataScience) -> None: A_nodes = pd.DataFrame( { "nodeId": [0, 1], diff --git a/python-wrapper/tests/test_neo4j.py b/python-wrapper/tests/neo4j_and_gds/test_neo4j.py similarity index 100% rename from python-wrapper/tests/test_neo4j.py rename to python-wrapper/tests/neo4j_and_gds/test_neo4j.py diff --git a/python-wrapper/tests/neo4j_and_gds/test_notebooks.py b/python-wrapper/tests/neo4j_and_gds/test_notebooks.py new file mode 100644 index 00000000..e9d61c30 --- /dev/null +++ b/python-wrapper/tests/neo4j_and_gds/test_notebooks.py @@ -0,0 +1,17 @@ +import os + +import pytest +from dotenv import load_dotenv +from graphdatascience import GraphDataScience +from graphdatascience.session import AuraGraphDataScience + +from tests.notebook_runner import run_notebooks + + +@pytest.mark.requires_neo4j_and_gds +def test_neo4j(gds: GraphDataScience | AuraGraphDataScience) -> None: + # The `gds` fixture provisions the Aura DB / GDS session and sets the NEO4J_* env vars + # that the notebooks read to connect. + load_dotenv(os.environ.get("ENV_FILE")) + + run_notebooks(["neo4j-example.ipynb", "gds-example.ipynb"]) diff --git a/python-wrapper/tests/notebook_runner.py b/python-wrapper/tests/notebook_runner.py new file mode 100644 index 00000000..d38bd3cc --- /dev/null +++ b/python-wrapper/tests/notebook_runner.py @@ -0,0 +1,112 @@ +import pathlib +import signal +import sys +from datetime import datetime +from typing import Any, NamedTuple + +import nbformat +from nbclient.exceptions import CellExecutionError +from nbconvert.preprocessors.execute import ExecutePreprocessor + +TEARDOWN_CELL_TAG = "teardown" + + +class IndexedCell(NamedTuple): + cell: Any + index: int # type: ignore + + +class TeardownExecutePreprocessor(ExecutePreprocessor): + def __init__(self, **kw: Any): + super().__init__(**kw) # type: ignore + + def init_notebook(self, tear_down_cells: list[IndexedCell]) -> None: + self.tear_down_cells = tear_down_cells + self._skip_rest = False + + # run the cell of a notebook + def preprocess_cell(self, cell: Any, resources: Any, index: int) -> None: + if index == 0: + + def handle_signal(sig, frame): # type: ignore + print("Received SIGNAL, running tear down cells") + self.teardown(resources) + sys.exit(1) + + signal.signal(signal.SIGINT, handle_signal) + signal.signal(signal.SIGTERM, handle_signal) + + try: + if not self._skip_rest: + super().preprocess_cell(cell, resources, index) # type: ignore + except CellExecutionError as e: + if self.tear_down_cells: + print(f"Running tear down cells due to error in notebook execution: {e}") + self.teardown(resources) + raise e + + def teardown(self, resources: Any) -> None: + for td_cell, td_idx in self.tear_down_cells: + try: + super().preprocess_cell(td_cell, resources, td_idx) # type: ignore + except CellExecutionError as td_e: + print(f"Error running tear down cell {td_idx}: {td_e}") + + +class TearDownCollector(ExecutePreprocessor): + def __init__(self, **kw: Any): + super().__init__(**kw) # type: ignore + + def init_notebook(self) -> None: + self._tear_down_cells: list[IndexedCell] = [] + + def preprocess_cell(self, cell: Any, resources: Any, index: int) -> None: + if TEARDOWN_CELL_TAG in cell["metadata"].get("tags", []): + self._tear_down_cells.append(IndexedCell(cell, index)) + + def tear_down_cells(self) -> list[IndexedCell]: + return self._tear_down_cells + + +def run_notebooks(notebook_names: list[str]) -> None: + current_dir = pathlib.Path(__file__).parent.resolve() + examples_path = current_dir.parent.parent / "examples" + + notebook_files = [ + f for f in examples_path.iterdir() if f.is_file() and f.suffix == ".ipynb" and f.name in notebook_names + ] + + if not notebook_files: + raise RuntimeError(f"No matching notebooks found in {examples_path}") + + ep = TeardownExecutePreprocessor(kernel_name="python3") + td_collector = TearDownCollector(kernel_name="python3") + exceptions: list[RuntimeError] = [] + + for notebook_filename in notebook_files: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"{now}: Executing notebook {notebook_filename}", flush=True) + + with open(notebook_filename) as f: + nb = nbformat.read(f, as_version=4) # type: ignore + + # Collect tear down cells + td_collector.init_notebook() + td_collector.preprocess(nb) + + ep.init_notebook(tear_down_cells=td_collector.tear_down_cells()) + + # run the notebook + try: + ep.preprocess(nb) + print(f"Finished executing notebook {notebook_filename}") + except CellExecutionError as e: + exceptions.append(RuntimeError(f"Error executing notebook {notebook_filename}", e)) + continue + + if exceptions: + for nb_ex in exceptions: + print(nb_ex) + raise RuntimeError(f"{len(exceptions)} Errors occurred while executing notebooks") + else: + print("Finished executing notebooks") diff --git a/python-wrapper/tests/snowflake/__init__.py b/python-wrapper/tests/snowflake/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python-wrapper/tests/test_snowflake.py b/python-wrapper/tests/snowflake/conftest.py similarity index 53% rename from python-wrapper/tests/test_snowflake.py rename to python-wrapper/tests/snowflake/conftest.py index c786b921..3a47bbf5 100644 --- a/python-wrapper/tests/test_snowflake.py +++ b/python-wrapper/tests/snowflake/conftest.py @@ -1,10 +1,10 @@ import pytest + +pytest.importorskip("snowflake.snowpark") + from snowflake.snowpark import Session from snowflake.snowpark.types import LongType, StructField, StructType -from neo4j_viz.node import Node -from neo4j_viz.snowflake import from_snowflake - @pytest.fixture def session() -> Session: @@ -43,30 +43,3 @@ def session_with_minimal_graph(session: Session) -> Session: rel_df.write.save_as_table("RELS") return session - - -def test_from_snowflake(session_with_minimal_graph: Session) -> None: - VG = from_snowflake( - session_with_minimal_graph, - { - "nodeTables": ["NODES"], - "relationshipTables": { - "RELS": { - "sourceTable": "NODES", - "targetTable": "NODES", - }, - }, - }, - ) - - assert VG.nodes == [ - Node(id=0, caption="NODES", color="#ffdf81", properties={"SNOWFLAKEID": 6}), - Node(id=1, caption="NODES", color="#ffdf81", properties={"SNOWFLAKEID": 7}), - ] - - assert len(VG.relationships) == 1 - - assert VG.relationships[0].source == 0 - assert VG.relationships[0].target == 1 - assert VG.relationships[0].caption == "RELS" - assert VG.relationships[0].properties == {} diff --git a/python-wrapper/tests/snowflake/test_notebooks.py b/python-wrapper/tests/snowflake/test_notebooks.py new file mode 100644 index 00000000..7e45ccba --- /dev/null +++ b/python-wrapper/tests/snowflake/test_notebooks.py @@ -0,0 +1,8 @@ +import pytest + +from tests.notebook_runner import run_notebooks + + +@pytest.mark.requires_snowflake +def test_snowflake() -> None: + run_notebooks(["snowflake-example.ipynb"]) diff --git a/python-wrapper/tests/snowflake/test_snowflake.py b/python-wrapper/tests/snowflake/test_snowflake.py new file mode 100644 index 00000000..d179bb66 --- /dev/null +++ b/python-wrapper/tests/snowflake/test_snowflake.py @@ -0,0 +1,31 @@ +from snowflake.snowpark import Session + +from neo4j_viz.node import Node +from neo4j_viz.snowflake import from_snowflake + + +def test_from_snowflake(session_with_minimal_graph: Session) -> None: + VG = from_snowflake( + session_with_minimal_graph, + { + "nodeTables": ["NODES"], + "relationshipTables": { + "RELS": { + "sourceTable": "NODES", + "targetTable": "NODES", + }, + }, + }, + ) + + assert VG.nodes == [ + Node(id=0, caption="NODES", color="#ffdf81", properties={"SNOWFLAKEID": 6}), + Node(id=1, caption="NODES", color="#ffdf81", properties={"SNOWFLAKEID": 7}), + ] + + assert len(VG.relationships) == 1 + + assert VG.relationships[0].source == 0 + assert VG.relationships[0].target == 1 + assert VG.relationships[0].caption == "RELS" + assert VG.relationships[0].properties == {} diff --git a/python-wrapper/tests/test_notebooks.py b/python-wrapper/tests/test_notebooks.py index 939a01e1..efa97672 100644 --- a/python-wrapper/tests/test_notebooks.py +++ b/python-wrapper/tests/test_notebooks.py @@ -1,146 +1,5 @@ -import os -import pathlib -import signal -import sys -from datetime import datetime -from typing import Any, Callable, NamedTuple - -import nbformat -import pytest -from dotenv import load_dotenv -from nbclient.exceptions import CellExecutionError -from nbconvert.preprocessors.execute import ExecutePreprocessor - -TEARDOWN_CELL_TAG = "teardown" - - -class IndexedCell(NamedTuple): - cell: Any - index: int # type: ignore - - -class TeardownExecutePreprocessor(ExecutePreprocessor): - def __init__(self, **kw: Any): - super().__init__(**kw) # type: ignore - - def init_notebook(self, tear_down_cells: list[IndexedCell]) -> None: - self.tear_down_cells = tear_down_cells - self._skip_rest = False - - # run the cell of a notebook - def preprocess_cell(self, cell: Any, resources: Any, index: int) -> None: - if index == 0: - - def handle_signal(sig, frame): # type: ignore - print("Received SIGNAL, running tear down cells") - self.teardown(resources) - sys.exit(1) - - signal.signal(signal.SIGINT, handle_signal) - signal.signal(signal.SIGTERM, handle_signal) - - try: - if not self._skip_rest: - super().preprocess_cell(cell, resources, index) # type: ignore - except CellExecutionError as e: - if self.tear_down_cells: - print(f"Running tear down cells due to error in notebook execution: {e}") - self.teardown(resources) - raise e - - def teardown(self, resources: Any) -> None: - for td_cell, td_idx in self.tear_down_cells: - try: - super().preprocess_cell(td_cell, resources, td_idx) # type: ignore - except CellExecutionError as td_e: - print(f"Error running tear down cell {td_idx}: {td_e}") - - -class TearDownCollector(ExecutePreprocessor): - def __init__(self, **kw: Any): - super().__init__(**kw) # type: ignore - - def init_notebook(self) -> None: - self._tear_down_cells: list[IndexedCell] = [] - - def preprocess_cell(self, cell: Any, resources: Any, index: int) -> None: - if TEARDOWN_CELL_TAG in cell["metadata"].get("tags", []): - self._tear_down_cells.append(IndexedCell(cell, index)) - - def tear_down_cells(self) -> list[IndexedCell]: - return self._tear_down_cells - - -def run_notebooks(filter_func: Callable[[str], bool]) -> None: - current_dir = pathlib.Path(__file__).parent.resolve() - examples_path = current_dir.parent.parent / "examples" - - notebook_files = [ - f for f in examples_path.iterdir() if f.is_file() and f.suffix == ".ipynb" and filter_func(f.name) - ] - - if not notebook_files: - raise RuntimeError(f"No matching notebooks found in {examples_path}") - - ep = TeardownExecutePreprocessor(kernel_name="python3") - td_collector = TearDownCollector(kernel_name="python3") - exceptions: list[RuntimeError] = [] - - for notebook_filename in notebook_files: - now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - print(f"{now}: Executing notebook {notebook_filename}", flush=True) - - with open(notebook_filename) as f: - nb = nbformat.read(f, as_version=4) # type: ignore - - # Collect tear down cells - td_collector.init_notebook() - td_collector.preprocess(nb) - - ep.init_notebook(tear_down_cells=td_collector.tear_down_cells()) - - # run the notebook - try: - ep.preprocess(nb) - print(f"Finished executing notebook {notebook_filename}") - except CellExecutionError as e: - exceptions.append(RuntimeError(f"Error executing notebook {notebook_filename}", e)) - continue - - if exceptions: - for nb_ex in exceptions: - print(nb_ex) - raise RuntimeError(f"{len(exceptions)} Errors occurred while executing notebooks") - else: - print("Finished executing notebooks") - - -@pytest.mark.requires_neo4j_and_gds -def test_neo4j(gds: Any) -> None: - neo4j_notebooks = ["neo4j-example.ipynb", "gds-example.ipynb"] - - load_dotenv(os.environ.get("ENV_FILE")) - - def filter_func(notebook: str) -> bool: - return notebook in neo4j_notebooks - - run_notebooks(filter_func) - - -@pytest.mark.requires_snowflake -def test_snowflake() -> None: - snowflake_notebooks = ["snowflake-example.ipynb"] - - def filter_func(notebook: str) -> bool: - return notebook in snowflake_notebooks - - run_notebooks(filter_func) +from tests.notebook_runner import run_notebooks def test_simple() -> None: - simple_notebooks = ["getting-started.ipynb"] - - def filter_func(notebook: str) -> bool: - return notebook in simple_notebooks - - run_notebooks(filter_func) + run_notebooks(["getting-started.ipynb"]) diff --git a/python-wrapper/uv.lock b/python-wrapper/uv.lock index 32ee284a..0621c272 100644 --- a/python-wrapper/uv.lock +++ b/python-wrapper/uv.lock @@ -2473,15 +2473,15 @@ wheels = [ [[package]] name = "nbsphinx-link" -version = "1.3.1" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nbsphinx" }, { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/d3/aabce2e96a7080b15a19342fa0839573463b9c3a8c57ee8e657a7ce9c176/nbsphinx-link-1.3.1.tar.gz", hash = "sha256:8b5a3f5279ba9e9f81c01309ff5436dfcc14c23f188ec8a04658f871a3860420", size = 16624, upload-time = "2024-09-18T12:20:26.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/1a/e38d8c65034ba12fce5d2579fd42ce8a8be3a766c03c49b24cd525e9a99c/nbsphinx_link-1.4.1.tar.gz", hash = "sha256:566d5640c165627dc3ae8442a9424ae89cc4af9794261737d8b16bb6717597b8", size = 17657, upload-time = "2026-05-29T19:24:13.051Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/8f/cd4bb6849fef05dafc02e38b3c9b9e985d266c3ca7cb485c3160ddeb96f2/nbsphinx_link-1.3.1-py3-none-any.whl", hash = "sha256:2188fc42294a38ba253eedd184dea8750ab035fd94fe63e855ff01f911631769", size = 5230, upload-time = "2024-09-18T12:20:45.982Z" }, + { url = "https://files.pythonhosted.org/packages/03/ad/4f4aa68f8e9566187e9e3ff1677fc2a02364baa3007deb38a1704010b32e/nbsphinx_link-1.4.1-py3-none-any.whl", hash = "sha256:9f5554b844b0f2c4098f5886e331ffea4999e13d13b846235c1bcb4efc9d9b54", size = 5799, upload-time = "2026-05-29T19:24:11.895Z" }, ] [[package]] @@ -2588,14 +2588,14 @@ dev = [ { name = "pytest", specifier = "==9.0.3" }, { name = "pytest-mock", specifier = "==3.15.1" }, { name = "python-dotenv" }, - { name = "ruff", specifier = "==0.15.12" }, - { name = "selenium", specifier = "==4.43.0" }, - { name = "streamlit", specifier = "==1.57.0" }, + { name = "ruff", specifier = "==0.15.16" }, + { name = "selenium", specifier = "==4.44.0" }, + { name = "streamlit", specifier = "==1.58.0" }, ] docs = [ { name = "enum-tools", extras = ["sphinx"] }, { name = "nbsphinx", specifier = "==0.9.8" }, - { name = "nbsphinx-link", specifier = "==1.3.1" }, + { name = "nbsphinx-link", specifier = "==1.4.1" }, { name = "sphinx", specifier = "==8.1.3" }, ] notebook = [ @@ -2608,7 +2608,7 @@ notebook = [ { name = "pykernel", specifier = ">=0.1.6" }, { name = "python-dotenv" }, { name = "requests" }, - { name = "snowflake-snowpark-python", specifier = "==1.50.0" }, + { name = "snowflake-snowpark-python", specifier = "==1.51.1" }, ] [[package]] @@ -3970,27 +3970,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, - { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, - { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, - { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, - { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +version = "0.15.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, + { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, + { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, ] [[package]] @@ -4007,7 +4007,7 @@ wheels = [ [[package]] name = "selenium" -version = "4.43.0" +version = "4.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -4017,9 +4017,9 @@ dependencies = [ { name = "urllib3", extra = ["socks"] }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/6a/fe950b498a3c570ab538ad1c2b60f18863eecf077a865eea4459f3fa78a9/selenium-4.43.0.tar.gz", hash = "sha256:bada5c08a989f812728a4b5bea884d8e91894e939a441cc3a025201ce718581e", size = 967747, upload-time = "2026-04-10T06:47:03.149Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/4a/6d0a4f4a07e2a91511a51398203ee82bf6ce644a448aaa35c59b44aa9531/selenium-4.44.0.tar.gz", hash = "sha256:b03a831fcfcab9d912b4682f60718c48a04560d6c62f7496c16b7498c9a4427e", size = 993133, upload-time = "2026-05-12T22:48:19.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/c7/0c55fbb0275fc368676ea50514ce7d7839d799a8b3ff8425f380186c7626/selenium-4.43.0-py3-none-any.whl", hash = "sha256:4f97639055dcfa9eadf8ccf549ba7b0e49c655d4e2bde19b9a44e916b754e769", size = 9573091, upload-time = "2026-04-10T06:47:01.134Z" }, + { url = "https://files.pythonhosted.org/packages/1f/bc/885047e975e996cb317db31c4551caa915aafc6befea990f082c7233adc2/selenium-4.44.0-py3-none-any.whl", hash = "sha256:d01ea3e5ecad8149460a765f7cf5177194c21dcc0173093fc05427c289b1bf24", size = 9654291, upload-time = "2026-05-12T22:48:16.836Z" }, ] [[package]] @@ -4125,7 +4125,7 @@ wheels = [ [[package]] name = "snowflake-snowpark-python" -version = "1.50.0" +version = "1.51.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cloudpickle" }, @@ -4138,9 +4138,9 @@ dependencies = [ { name = "tzlocal" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/f9/c7e4d0f4dceb6cf1b88ed984948af5471ecc19514a7e519da7276226429f/snowflake_snowpark_python-1.50.0.tar.gz", hash = "sha256:8af823326c2681333bf59ad3d6152b07098b7926165667a7fdcebd5adb53642f", size = 1760444, upload-time = "2026-04-23T20:33:56.522Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/56/7e746286afe2c92247f7ee00f0408c3f0c281c1ba53e3fd09ce48afdb7be/snowflake_snowpark_python-1.51.1.tar.gz", hash = "sha256:734bf5e89a6193472aa369192b61e082ae36304bbf07b2050228abb887ac2649", size = 1768391, upload-time = "2026-05-28T18:10:44.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/11/0fbeb214832e5f9a6c8123e8de3e94610cab9c9a94eb1f011580f717f682/snowflake_snowpark_python-1.50.0-py3-none-any.whl", hash = "sha256:1a0140dba9a4d44910a052110494069fc9142577ef5db67d58abe6996f420a11", size = 1851309, upload-time = "2026-04-23T20:33:54.654Z" }, + { url = "https://files.pythonhosted.org/packages/97/24/e0c396986054d9a6e44fece81a2f4a3cea0e651658b03485c12ee2689fd5/snowflake_snowpark_python-1.51.1-py3-none-any.whl", hash = "sha256:e4435d5201b5864287bcdfd4380e6517436505e3a8cb30cc69126c33fcba2209", size = 1858422, upload-time = "2026-05-28T18:10:42.422Z" }, ] [[package]] @@ -4397,7 +4397,7 @@ wheels = [ [[package]] name = "streamlit" -version = "1.57.0" +version = "1.58.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "altair" }, @@ -4426,9 +4426,9 @@ dependencies = [ { name = "watchdog", marker = "sys_platform != 'darwin'" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/f8/b2daf7a5f8ae15527daf94406e771bb6075e958a01c3dde9eba79dc3c9a3/streamlit-1.57.0.tar.gz", hash = "sha256:0b028d305c1a1a757071b2c9504966787602842fc8af6e873795ca58d2b4d12f", size = 8678859, upload-time = "2026-04-28T22:13:32.238Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/74/20dac6d6200d6ec0e1c230fb8eeb6a1a423645eacb76e8d802adfc246456/streamlit-1.58.0.tar.gz", hash = "sha256:78a22e7085b053af7ce544442bf4b670771e68c509ba1bdaa056ba0708f49c3d", size = 8721149, upload-time = "2026-05-28T18:02:44.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/1a/3ca2293d8552bacea3e67e9600d2d1df7df4a325059769ad83d91c279595/streamlit-1.57.0-py3-none-any.whl", hash = "sha256:0d1d41972aeade5637dbb0e7f0eefa5312272f85304923d240a1b1f0475249c8", size = 9194216, upload-time = "2026-04-28T22:13:29.624Z" }, + { url = "https://files.pythonhosted.org/packages/df/84/14c36a92fb24f8e1cea452f53b0744b5da69d52cdd2fe22e71e6fbf765d5/streamlit-1.58.0-py3-none-any.whl", hash = "sha256:4ca8a7afc5bd16a5f176ccf4be1e34e8121cad0240becd127fb58a103ea3178d", size = 9219185, upload-time = "2026-05-28T18:02:41.993Z" }, ] [[package]]