From a954a7bff911f39706144196d32fdcd4666fc9e3 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 10:52:30 -0700 Subject: [PATCH 01/66] cherry-pick-me: update gitignore to not commit junit results --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index f3c3af230..3936ce877 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Build and test results +build +test-results + # Node artifacts node_modules From a610fa9fb62a32dc48906edc49ffb5a7eea46a08 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 10:56:16 -0700 Subject: [PATCH 02/66] add docker-based tests --- .../test/standard_tests/connector_base.py | 74 +++++++++++++++++++ .../test/standard_tests/models/scenario.py | 22 +++++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/connector_base.py b/airbyte_cdk/test/standard_tests/connector_base.py index 394028247..0eb2a1374 100644 --- a/airbyte_cdk/test/standard_tests/connector_base.py +++ b/airbyte_cdk/test/standard_tests/connector_base.py @@ -7,11 +7,13 @@ import importlib import inspect import os +import shutil import sys from collections.abc import Callable from pathlib import Path from typing import cast +import pytest import yaml from boltons.typeutils import classproperty @@ -19,6 +21,7 @@ AirbyteMessage, Type, ) +from airbyte_cdk.models.connector_metadata import MetadataFile from airbyte_cdk.test import entrypoint_wrapper from airbyte_cdk.test.standard_tests._job_runner import IConnector, run_test_job from airbyte_cdk.test.standard_tests.models import ( @@ -28,6 +31,7 @@ ACCEPTANCE_TEST_CONFIG, find_connector_root, ) +from airbyte_cdk.utils.docker import build_connector_image, run_docker_command class ConnectorTestSuiteBase(abc.ABC): @@ -179,3 +183,73 @@ def get_scenarios( test.configured_catalog_path = connector_root / test.configured_catalog_path return test_scenarios + + @pytest.mark.skipif( + shutil.which("docker") is None, + reason="docker CLI not found in PATH, skipping docker image tests", + ) + def test_docker_image_build_and_spec( + self, + ) -> None: + """Run `docker_image` acceptance tests.""" + tag = "dev-latest" + connector_dir = self.get_connector_root_dir() + metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") + build_connector_image( + connector_name=connector_dir.name, + connector_directory=connector_dir, + metadata=metadata, + tag=tag, + no_verify=False, + ) + run_docker_command( + [ + "docker", + "run", + "--rm", + f"{metadata.data.dockerRepository}:{tag}", + "spec", + ], + ) + + @pytest.mark.skipif( + shutil.which("docker") is None, + reason="docker CLI not found in PATH, skipping docker image tests", + ) + def test_docker_image_build_and_check( + self, + scenario: ConnectorTestScenario, + ) -> None: + """Run `docker_image` acceptance tests. + + This test builds the connector image and runs the `check` command inside the container. + + Note: + - It is expected for docker image caches to be reused between test runs. + - In the rare case that image caches need to be cleared, please clear + the local docker image cache using `docker image prune -a` command. + """ + tag = "dev-latest" + connector_dir = self.get_connector_root_dir() + metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") + build_connector_image( + connector_name=connector_dir.name, + connector_directory=connector_dir, + metadata=metadata, + tag=tag, + no_verify=False, + ) + container_config_path = "/secrets/config.json" + with scenario.with_temp_config_file() as temp_config_file: + run_docker_command( + [ + "docker", + "run", + "--rm", + "-v", + f"{temp_config_file}:{container_config_path}", + f"{metadata.data.dockerRepository}:{tag}", + "check", + f"--config={container_config_path}", + ], + ) diff --git a/airbyte_cdk/test/standard_tests/models/scenario.py b/airbyte_cdk/test/standard_tests/models/scenario.py index 0ace85d33..aaefc17a4 100644 --- a/airbyte_cdk/test/standard_tests/models/scenario.py +++ b/airbyte_cdk/test/standard_tests/models/scenario.py @@ -9,12 +9,18 @@ from __future__ import annotations +import json +import tempfile +from contextlib import contextmanager, suppress from pathlib import Path -from typing import Any, Literal, cast +from typing import TYPE_CHECKING, Any, Literal, cast import yaml from pydantic import BaseModel +if TYPE_CHECKING: + from collections.abc import Generator + class ConnectorTestScenario(BaseModel): """Acceptance test scenario, as a Pydantic model. @@ -83,3 +89,17 @@ def __str__(self) -> str: return f"'{self.config_path.name}' Test Scenario" return f"'{hash(self)}' Test Scenario" + + @contextmanager + def with_temp_config_file(self) -> Generator[Path, None, None]: + """Yield a temporary JSON file path containing the config dict and delete it on exit.""" + config = self.get_config_dict(empty_if_missing=True) + _, path_str = tempfile.mkstemp(prefix="config-", suffix=".json", text=True) + path = Path(path_str) + try: + path.write_text(json.dumps(config)) + yield path + finally: + # attempt cleanup, ignore errors + with suppress(OSError): + path.unlink() From c35e613b0a119d84aba6e254ea51d1799b892a78 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 10:57:54 -0700 Subject: [PATCH 03/66] improve readability of calls --- airbyte_cdk/test/standard_tests/connector_base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/connector_base.py b/airbyte_cdk/test/standard_tests/connector_base.py index 0eb2a1374..5a9809f16 100644 --- a/airbyte_cdk/test/standard_tests/connector_base.py +++ b/airbyte_cdk/test/standard_tests/connector_base.py @@ -202,7 +202,7 @@ def test_docker_image_build_and_spec( tag=tag, no_verify=False, ) - run_docker_command( + _ = run_docker_command( [ "docker", "run", @@ -210,6 +210,8 @@ def test_docker_image_build_and_spec( f"{metadata.data.dockerRepository}:{tag}", "spec", ], + check=True, # Raise an error if the command fails + capture_output=False, ) @pytest.mark.skipif( @@ -241,7 +243,7 @@ def test_docker_image_build_and_check( ) container_config_path = "/secrets/config.json" with scenario.with_temp_config_file() as temp_config_file: - run_docker_command( + _ = run_docker_command( [ "docker", "run", @@ -252,4 +254,6 @@ def test_docker_image_build_and_check( "check", f"--config={container_config_path}", ], + check=True, # Raise an error if the command fails + capture_output=False, ) From db4c18cfc77c6db2159eae70a24dfc66207ec6fe Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 12:44:51 -0700 Subject: [PATCH 04/66] add metadata.yaml for source-pokeapi example --- .../metadata.yaml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 unit_tests/resources/source_pokeapi_w_components_py/metadata.yaml diff --git a/unit_tests/resources/source_pokeapi_w_components_py/metadata.yaml b/unit_tests/resources/source_pokeapi_w_components_py/metadata.yaml new file mode 100644 index 000000000..b16ba5784 --- /dev/null +++ b/unit_tests/resources/source_pokeapi_w_components_py/metadata.yaml @@ -0,0 +1,48 @@ +# Copied from the source-pokeapi connector metadata file. +data: + allowedHosts: + hosts: + - "*" + registryOverrides: + oss: + enabled: true + cloud: + enabled: true + remoteRegistries: + pypi: + enabled: false + packageName: airbyte-source-pokeapi + connectorBuildOptions: + # Please update to the latest version of the connector base image. + # https://hub.docker.com/r/airbyte/python-connector-base + # Please use the full address with sha256 hash to guarantee build reproducibility. + baseImage: docker.io/airbyte/source-declarative-manifest:6.51.0@sha256:890b109f243b8b9406f23ea7522de41025f7b3e87f6fc9710bc1e521213a276f + connectorSubtype: api + connectorType: source + definitionId: 6371b14b-bc68-4236-bfbd-468e8df8e968 + dockerImageTag: 0.3.24 + dockerRepository: airbyte/source-pokeapi + githubIssueLabel: source-pokeapi + icon: pokeapi.svg + license: MIT + name: PokeAPI + releaseDate: "2020-05-14" + releaseStage: alpha + supportLevel: community + documentationUrl: https://docs.airbyte.com/integrations/sources/pokeapi + tags: + - cdk:low-code + - language:manifest-only + connectorTestSuitesOptions: + - suite: liveTests + testConnections: + - name: pokeapi_config_dev_null + id: 5a290dcf-b2cf-4768-ac2d-a1aaca90c186 + - suite: acceptanceTests + testSecrets: + - name: SECRET_SOURCE-POKEAPI__CREDS + fileName: config.json + secretStore: + type: GSM + alias: airbyte-connector-testing-secret-store +metadataSpecVersion: "1.0" From defe48c82cec32857ab013d298a8c752f56032ce Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 13:02:30 -0700 Subject: [PATCH 05/66] fix: create dir if not exists --- airbyte_cdk/utils/docker.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index db6355eac..4336376e8 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -164,12 +164,13 @@ def build_connector_image( ConnectorImageBuildError: If the image build or tag operation fails. """ connector_kebab_name = connector_name + connector_dockerfile_dir = connector_directory / "build" / "docker" if dockerfile_override: dockerfile_path = dockerfile_override else: - dockerfile_path = connector_directory / "build" / "docker" / "Dockerfile" - dockerignore_path = connector_directory / "build" / "docker" / "Dockerfile.dockerignore" + dockerfile_path = connector_dockerfile_dir / "Dockerfile" + dockerignore_path = connector_dockerfile_dir / "Dockerfile.dockerignore" try: dockerfile_text, dockerignore_text = get_dockerfile_templates( metadata=metadata, @@ -192,6 +193,8 @@ def build_connector_image( ), ) from e + # ensure the directory exists + connector_dockerfile_dir.mkdir(parents=True, exist_ok=True) dockerfile_path.write_text(dockerfile_text) dockerignore_path.write_text(dockerignore_text) From f30386beb203151169957783fd5fea9c327bf64a Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 13:06:58 -0700 Subject: [PATCH 06/66] fix forced exit on success in util method --- airbyte_cdk/utils/docker.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index 4336376e8..47e996736 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -258,13 +258,12 @@ def build_connector_image( if not no_verify: if verify_connector_image(base_tag): click.echo(f"Build completed successfully: {base_tag}") - sys.exit(0) - else: - click.echo(f"Built image failed verification: {base_tag}", err=True) - sys.exit(1) + return + + click.echo(f"Built image failed verification: {base_tag}", err=True) + sys.exit(1) else: click.echo(f"Build completed successfully (without verification): {base_tag}") - sys.exit(0) def _download_dockerfile_defs( From 69d41bfa2552b00c4406e482502239d662679b37 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 18:11:11 -0700 Subject: [PATCH 07/66] port fix from other PR --- airbyte_cdk/test/standard_tests/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/util.py b/airbyte_cdk/test/standard_tests/util.py index 58ae19d85..f391d99ca 100644 --- a/airbyte_cdk/test/standard_tests/util.py +++ b/airbyte_cdk/test/standard_tests/util.py @@ -67,7 +67,7 @@ def create_connector_test_suite( ) subclass_overrides: dict[str, Any] = { - "get_connector_root_dir": lambda: connector_directory, + "get_connector_root_dir": classmethod(lambda cls: connector_directory), } TestSuiteAuto = type( From 1241e400c8e040b5704875cb08e5820ddb6d3e1c Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 6 Jun 2025 23:02:55 -0700 Subject: [PATCH 08/66] working image tests --- airbyte_cdk/cli/airbyte_cdk/_connector.py | 40 ++++++++-- airbyte_cdk/cli/airbyte_cdk/_image.py | 76 +++++++++++++++++++ .../test/standard_tests/connector_base.py | 49 +++++++----- .../test/standard_tests/pytest_hooks.py | 19 ++++- airbyte_cdk/utils/docker.py | 12 +-- 5 files changed, 164 insertions(+), 32 deletions(-) diff --git a/airbyte_cdk/cli/airbyte_cdk/_connector.py b/airbyte_cdk/cli/airbyte_cdk/_connector.py index cebf36251..0d3240ab4 100644 --- a/airbyte_cdk/cli/airbyte_cdk/_connector.py +++ b/airbyte_cdk/cli/airbyte_cdk/_connector.py @@ -101,7 +101,7 @@ def connector_cli_group() -> None: pass -@connector_cli_group.command() +@connector_cli_group.command("test") @click.argument( "connector", required=False, @@ -114,10 +114,18 @@ def connector_cli_group() -> None: default=False, help="Only collect tests, do not run them.", ) -def test( +@click.option( + "--pytest-arg", + "pytest_args", # ← map --pytest-arg into pytest_args + type=str, + multiple=True, + help="Additional argument(s) to pass to pytest. Can be specified multiple times.", +) +def connector_test( connector: str | Path | None = None, *, collect_only: bool = False, + pytest_args: list[str] | None = None, ) -> None: """Run connector tests. @@ -130,19 +138,36 @@ def test( directory. If the current working directory is not a connector directory (e.g. starting with 'source-') and no connector name or path is provided, the process will fail. """ + click.echo("Connector test command executed.") + connector_name, connector_directory = resolve_connector_name_and_directory(connector) + + pytest_args = pytest_args or [] + if collect_only: + pytest_args.append("--collect-only") + + run_connector_tests( + connector_name=connector_name, + connector_directory=connector_directory, + extra_pytest_args=pytest_args, + ) + + +def run_connector_tests( + connector_name: str, + connector_directory: Path, + extra_pytest_args: list[str], +) -> None: if pytest is None: raise ImportError( "pytest is not installed. Please install pytest to run the connector tests." ) - click.echo("Connector test command executed.") - connector_name, connector_directory = resolve_connector_name_and_directory(connector) connector_test_suite = create_connector_test_suite( connector_name=connector_name if not connector_directory else None, connector_directory=connector_directory, ) - pytest_args: list[str] = [] + pytest_args: list[str] = ["-p", "airbyte_cdk.test.standard_tests.pytest_hooks"] if connector_directory: pytest_args.append(f"--rootdir={connector_directory}") os.chdir(str(connector_directory)) @@ -158,8 +183,8 @@ def test( test_file_path.parent.mkdir(parents=True, exist_ok=True) test_file_path.write_text(file_text) - if collect_only: - pytest_args.append("--collect-only") + if extra_pytest_args: + pytest_args.extend(extra_pytest_args) pytest_args.append(str(test_file_path)) @@ -170,7 +195,6 @@ def test( click.echo(f"Running tests from connector directory: {connector_directory}...") click.echo(f"Test file: {test_file_path}") - click.echo(f"Collect only: {collect_only}") click.echo(f"Pytest args: {pytest_args}") click.echo("Invoking Pytest...") exit_code = pytest.main( diff --git a/airbyte_cdk/cli/airbyte_cdk/_image.py b/airbyte_cdk/cli/airbyte_cdk/_image.py index cd0ca5cc9..4261402a5 100644 --- a/airbyte_cdk/cli/airbyte_cdk/_image.py +++ b/airbyte_cdk/cli/airbyte_cdk/_image.py @@ -10,6 +10,7 @@ import rich_click as click +from airbyte_cdk.cli.airbyte_cdk._connector import run_connector_tests from airbyte_cdk.models.connector_metadata import MetadataFile from airbyte_cdk.utils.connector_paths import resolve_connector_name_and_directory from airbyte_cdk.utils.docker import ( @@ -88,6 +89,81 @@ def build( sys.exit(1) +@image_cli_group.command("test") +@click.argument( + "connector", + required=False, + type=str, + metavar="[CONNECTOR]", +) +@click.option( + "--image", + help="Image to test, instead of building a new one.", +) +def image_test( # "image test" command + connector: str | None = None, + *, + image: str | None = None, +) -> None: + """Test a connector Docker image. + + [CONNECTOR] can be a connector name (e.g. 'source-pokeapi'), a path to a connector directory, or omitted to use the current working directory. + If a string containing '/' is provided, it is treated as a path. Otherwise, it is treated as a connector name. + + If an image is provided, it will be used for testing instead of building a new one. + + Note: You should run `airbyte-cdk secrets fetch` before running this command to ensure + that the secrets are available for the connector tests. + """ + if not verify_docker_installation(): + click.echo( + "Docker is not installed or not running. Please install Docker and try again.", err=True + ) + sys.exit(1) + + connector_name, connector_directory = resolve_connector_name_and_directory(connector) + + # Select only tests with the 'image_tests' mark + pytest_args = ["-m", "image_tests"] + if not image: + metadata_file_path: Path = connector_directory / "metadata.yaml" + try: + metadata = MetadataFile.from_file(metadata_file_path) + except (FileNotFoundError, ValueError) as e: + click.echo( + f"Error loading metadata file '{metadata_file_path}': {e!s}", + err=True, + ) + sys.exit(1) + + tag = "dev-latest" + image = f"{metadata.data.dockerRepository}:{tag}" + click.echo(f"Building Image for Connector: {image}") + try: + image = build_connector_image( + connector_directory=connector_directory, + connector_name=connector_name, + metadata=metadata, + tag=tag, + no_verify=True, + ) + except ConnectorImageBuildError as e: + click.echo( + f"Error building connector image: {e!s}", + err=True, + ) + sys.exit(1) + + pytest_args.extend(["--connector-image", image]) + + click.echo(f"Testing Connector Image: {image}") + run_connector_tests( + connector_name=connector_name, + connector_directory=connector_directory, + extra_pytest_args=pytest_args, + ) + + __all__ = [ "image_cli_group", ] diff --git a/airbyte_cdk/test/standard_tests/connector_base.py b/airbyte_cdk/test/standard_tests/connector_base.py index 5a9809f16..d2f5241a6 100644 --- a/airbyte_cdk/test/standard_tests/connector_base.py +++ b/airbyte_cdk/test/standard_tests/connector_base.py @@ -188,26 +188,32 @@ def get_scenarios( shutil.which("docker") is None, reason="docker CLI not found in PATH, skipping docker image tests", ) + @pytest.mark.image_tests def test_docker_image_build_and_spec( self, + connector_image_override: str | None, ) -> None: """Run `docker_image` acceptance tests.""" - tag = "dev-latest" connector_dir = self.get_connector_root_dir() metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") - build_connector_image( - connector_name=connector_dir.name, - connector_directory=connector_dir, - metadata=metadata, - tag=tag, - no_verify=False, - ) + + connector_image: str | None = connector_image_override + if not connector_image: + tag = "dev-latest" + connector_image = build_connector_image( + connector_name=connector_dir.name, + connector_directory=connector_dir, + metadata=metadata, + tag=tag, + no_verify=False, + ) + _ = run_docker_command( [ "docker", "run", "--rm", - f"{metadata.data.dockerRepository}:{tag}", + connector_image, "spec", ], check=True, # Raise an error if the command fails @@ -218,9 +224,11 @@ def test_docker_image_build_and_spec( shutil.which("docker") is None, reason="docker CLI not found in PATH, skipping docker image tests", ) + @pytest.mark.image_tests def test_docker_image_build_and_check( self, scenario: ConnectorTestScenario, + connector_image_override: str | None, ) -> None: """Run `docker_image` acceptance tests. @@ -231,16 +239,23 @@ def test_docker_image_build_and_check( - In the rare case that image caches need to be cleared, please clear the local docker image cache using `docker image prune -a` command. """ + if scenario.expect_exception: + pytest.skip("Skipping test_docker_image_build_and_check (expected to fail).") + tag = "dev-latest" connector_dir = self.get_connector_root_dir() metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") - build_connector_image( - connector_name=connector_dir.name, - connector_directory=connector_dir, - metadata=metadata, - tag=tag, - no_verify=False, - ) + connector_image: str | None = connector_image_override + if not connector_image: + tag = "dev-latest" + connector_image = build_connector_image( + connector_name=connector_dir.name, + connector_directory=connector_dir, + metadata=metadata, + tag=tag, + no_verify=False, + ) + container_config_path = "/secrets/config.json" with scenario.with_temp_config_file() as temp_config_file: _ = run_docker_command( @@ -250,7 +265,7 @@ def test_docker_image_build_and_check( "--rm", "-v", f"{temp_config_file}:{container_config_path}", - f"{metadata.data.dockerRepository}:{tag}", + connector_image, "check", f"--config={container_config_path}", ], diff --git a/airbyte_cdk/test/standard_tests/pytest_hooks.py b/airbyte_cdk/test/standard_tests/pytest_hooks.py index b6197a0c3..f408606da 100644 --- a/airbyte_cdk/test/standard_tests/pytest_hooks.py +++ b/airbyte_cdk/test/standard_tests/pytest_hooks.py @@ -16,9 +16,24 @@ import pytest +def pytest_addoption(parser: pytest.Parser) -> None: + """Add --connector-image to pytest's CLI.""" + parser.addoption( + "--connector-image", + action="store", + default=None, + help="Use this pre-built connector Docker image instead of building one.", + ) + + +@pytest.fixture +def connector_image_override(request: pytest.FixtureRequest) -> str | None: + """Return the value of --connector-image, or None if not set.""" + return request.config.getoption("--connector-image") + + def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: - """ - A helper for pytest_generate_tests hook. + """A helper for pytest_generate_tests hook. If a test method (in a class subclassed from our base class) declares an argument 'scenario', this function retrieves the diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index 47e996736..82b0cdcdf 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -137,12 +137,13 @@ def _tag_image( def build_connector_image( connector_name: str, connector_directory: Path, + *, metadata: MetadataFile, tag: str, primary_arch: ArchEnum = ArchEnum.ARM64, # Assume MacBook M series by default no_verify: bool = False, dockerfile_override: Path | None = None, -) -> None: +) -> str: """Build a connector Docker image. This command builds a Docker image for a connector, using either @@ -257,13 +258,14 @@ def build_connector_image( ) if not no_verify: if verify_connector_image(base_tag): - click.echo(f"Build completed successfully: {base_tag}") - return + click.echo(f"Build and verification completed successfully: {base_tag}") + return base_tag click.echo(f"Built image failed verification: {base_tag}", err=True) sys.exit(1) - else: - click.echo(f"Build completed successfully (without verification): {base_tag}") + + click.echo(f"Build completed successfully: {base_tag}") + return base_tag def _download_dockerfile_defs( From cd0ea9e462d3ab72d981f6a164711ce8c64f0f64 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Sat, 7 Jun 2025 00:12:07 -0700 Subject: [PATCH 09/66] add chmod statement --- airbyte_cdk/test/standard_tests/models/scenario.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/airbyte_cdk/test/standard_tests/models/scenario.py b/airbyte_cdk/test/standard_tests/models/scenario.py index aaefc17a4..74fb36bfc 100644 --- a/airbyte_cdk/test/standard_tests/models/scenario.py +++ b/airbyte_cdk/test/standard_tests/models/scenario.py @@ -98,6 +98,8 @@ def with_temp_config_file(self) -> Generator[Path, None, None]: path = Path(path_str) try: path.write_text(json.dumps(config)) + # Allow the file to be read by other processes + path.chmod(path.stat().st_mode | 0o444) yield path finally: # attempt cleanup, ignore errors From 2e6abc1428d8447389f6da6627657ea0046823c4 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Sat, 7 Jun 2025 00:29:24 -0700 Subject: [PATCH 10/66] refactor into docker base test suite for java connectors --- .../test/standard_tests/connector_base.py | 184 ++---------------- .../test/standard_tests/docker_base.py | 177 +++++++++++++++++ airbyte_cdk/test/standard_tests/util.py | 7 +- 3 files changed, 193 insertions(+), 175 deletions(-) create mode 100644 airbyte_cdk/test/standard_tests/docker_base.py diff --git a/airbyte_cdk/test/standard_tests/connector_base.py b/airbyte_cdk/test/standard_tests/connector_base.py index d2f5241a6..dc822fb91 100644 --- a/airbyte_cdk/test/standard_tests/connector_base.py +++ b/airbyte_cdk/test/standard_tests/connector_base.py @@ -3,39 +3,31 @@ from __future__ import annotations -import abc import importlib -import inspect import os -import shutil -import sys -from collections.abc import Callable from pathlib import Path -from typing import cast +from typing import TYPE_CHECKING, cast -import pytest -import yaml from boltons.typeutils import classproperty from airbyte_cdk.models import ( AirbyteMessage, Type, ) -from airbyte_cdk.models.connector_metadata import MetadataFile -from airbyte_cdk.test import entrypoint_wrapper from airbyte_cdk.test.standard_tests._job_runner import IConnector, run_test_job -from airbyte_cdk.test.standard_tests.models import ( - ConnectorTestScenario, -) -from airbyte_cdk.utils.connector_paths import ( - ACCEPTANCE_TEST_CONFIG, - find_connector_root, -) -from airbyte_cdk.utils.docker import build_connector_image, run_docker_command +from airbyte_cdk.test.standard_tests.docker_base import DockerConnectorTestSuite + +if TYPE_CHECKING: + from collections.abc import Callable + + from airbyte_cdk.test import entrypoint_wrapper + from airbyte_cdk.test.standard_tests.models import ( + ConnectorTestScenario, + ) -class ConnectorTestSuiteBase(abc.ABC): - """Base class for connector test suites.""" +class ConnectorTestSuiteBase(DockerConnectorTestSuite): + """Base class for Python connector test suites.""" connector: type[IConnector] | Callable[[], IConnector] | None # type: ignore [reportRedeclaration] """The connector class or a factory function that returns an scenario of IConnector.""" @@ -83,13 +75,6 @@ def connector(cls) -> type[IConnector] | Callable[[], IConnector] | None: ) from e return cast(type[IConnector], getattr(module, matching_class_name)) - @classmethod - def get_test_class_dir(cls) -> Path: - """Get the file path that contains the class.""" - module = sys.modules[cls.__module__] - # Get the directory containing the test file - return Path(inspect.getfile(module)).parent - @classmethod def create_connector( cls, @@ -127,148 +112,3 @@ def test_check( assert len(conn_status_messages) == 1, ( f"Expected exactly one CONNECTION_STATUS message. Got: {result._messages}" ) - - @classmethod - def get_connector_root_dir(cls) -> Path: - """Get the root directory of the connector.""" - return find_connector_root([cls.get_test_class_dir(), Path.cwd()]) - - @classproperty - def acceptance_test_config_path(cls) -> Path: - """Get the path to the acceptance test config file.""" - result = cls.get_connector_root_dir() / ACCEPTANCE_TEST_CONFIG - if result.exists(): - return result - - raise FileNotFoundError(f"Acceptance test config file not found at: {str(result)}") - - @classmethod - def get_scenarios( - cls, - ) -> list[ConnectorTestScenario]: - """Get acceptance tests for a given category. - - This has to be a separate function because pytest does not allow - parametrization of fixtures with arguments from the test class itself. - """ - categories = ["connection", "spec"] - all_tests_config = yaml.safe_load(cls.acceptance_test_config_path.read_text()) - if "acceptance_tests" not in all_tests_config: - raise ValueError( - f"Acceptance tests config not found in {cls.acceptance_test_config_path}." - f" Found only: {str(all_tests_config)}." - ) - - test_scenarios: list[ConnectorTestScenario] = [] - for category in categories: - if ( - category not in all_tests_config["acceptance_tests"] - or "tests" not in all_tests_config["acceptance_tests"][category] - ): - continue - - test_scenarios.extend( - [ - ConnectorTestScenario.model_validate(test) - for test in all_tests_config["acceptance_tests"][category]["tests"] - if "config_path" in test and "iam_role" not in test["config_path"] - ] - ) - - connector_root = cls.get_connector_root_dir().absolute() - for test in test_scenarios: - if test.config_path: - test.config_path = connector_root / test.config_path - if test.configured_catalog_path: - test.configured_catalog_path = connector_root / test.configured_catalog_path - - return test_scenarios - - @pytest.mark.skipif( - shutil.which("docker") is None, - reason="docker CLI not found in PATH, skipping docker image tests", - ) - @pytest.mark.image_tests - def test_docker_image_build_and_spec( - self, - connector_image_override: str | None, - ) -> None: - """Run `docker_image` acceptance tests.""" - connector_dir = self.get_connector_root_dir() - metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") - - connector_image: str | None = connector_image_override - if not connector_image: - tag = "dev-latest" - connector_image = build_connector_image( - connector_name=connector_dir.name, - connector_directory=connector_dir, - metadata=metadata, - tag=tag, - no_verify=False, - ) - - _ = run_docker_command( - [ - "docker", - "run", - "--rm", - connector_image, - "spec", - ], - check=True, # Raise an error if the command fails - capture_output=False, - ) - - @pytest.mark.skipif( - shutil.which("docker") is None, - reason="docker CLI not found in PATH, skipping docker image tests", - ) - @pytest.mark.image_tests - def test_docker_image_build_and_check( - self, - scenario: ConnectorTestScenario, - connector_image_override: str | None, - ) -> None: - """Run `docker_image` acceptance tests. - - This test builds the connector image and runs the `check` command inside the container. - - Note: - - It is expected for docker image caches to be reused between test runs. - - In the rare case that image caches need to be cleared, please clear - the local docker image cache using `docker image prune -a` command. - """ - if scenario.expect_exception: - pytest.skip("Skipping test_docker_image_build_and_check (expected to fail).") - - tag = "dev-latest" - connector_dir = self.get_connector_root_dir() - metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") - connector_image: str | None = connector_image_override - if not connector_image: - tag = "dev-latest" - connector_image = build_connector_image( - connector_name=connector_dir.name, - connector_directory=connector_dir, - metadata=metadata, - tag=tag, - no_verify=False, - ) - - container_config_path = "/secrets/config.json" - with scenario.with_temp_config_file() as temp_config_file: - _ = run_docker_command( - [ - "docker", - "run", - "--rm", - "-v", - f"{temp_config_file}:{container_config_path}", - connector_image, - "check", - f"--config={container_config_path}", - ], - check=True, # Raise an error if the command fails - capture_output=False, - ) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py new file mode 100644 index 000000000..90248ef14 --- /dev/null +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -0,0 +1,177 @@ +# Copyright (c) 2024 Airbyte, Inc., all rights reserved. +"""Base class for connector test suites.""" + +from __future__ import annotations + +import inspect +import shutil +import sys +from pathlib import Path + +import pytest +import yaml +from boltons.typeutils import classproperty + +from airbyte_cdk.models.connector_metadata import MetadataFile +from airbyte_cdk.test.standard_tests.models import ( + ConnectorTestScenario, +) +from airbyte_cdk.utils.connector_paths import ( + ACCEPTANCE_TEST_CONFIG, + find_connector_root, +) +from airbyte_cdk.utils.docker import build_connector_image, run_docker_command + + +class DockerConnectorTestSuite: + """Base class for connector test suites.""" + + @classmethod + def get_test_class_dir(cls) -> Path: + """Get the file path that contains the class.""" + module = sys.modules[cls.__module__] + # Get the directory containing the test file + return Path(inspect.getfile(module)).parent + + @classmethod + def get_connector_root_dir(cls) -> Path: + """Get the root directory of the connector.""" + return find_connector_root([cls.get_test_class_dir(), Path.cwd()]) + + @classproperty + def acceptance_test_config_path(cls) -> Path: + """Get the path to the acceptance test config file.""" + result = cls.get_connector_root_dir() / ACCEPTANCE_TEST_CONFIG + if result.exists(): + return result + + raise FileNotFoundError(f"Acceptance test config file not found at: {str(result)}") + + @classmethod + def get_scenarios( + cls, + ) -> list[ConnectorTestScenario]: + """Get acceptance tests for a given category. + + This has to be a separate function because pytest does not allow + parametrization of fixtures with arguments from the test class itself. + """ + categories = ["connection", "spec"] + all_tests_config = yaml.safe_load(cls.acceptance_test_config_path.read_text()) + if "acceptance_tests" not in all_tests_config: + raise ValueError( + f"Acceptance tests config not found in {cls.acceptance_test_config_path}." + f" Found only: {str(all_tests_config)}." + ) + + test_scenarios: list[ConnectorTestScenario] = [] + for category in categories: + if ( + category not in all_tests_config["acceptance_tests"] + or "tests" not in all_tests_config["acceptance_tests"][category] + ): + continue + + test_scenarios.extend([ + ConnectorTestScenario.model_validate(test) + for test in all_tests_config["acceptance_tests"][category]["tests"] + if "config_path" in test and "iam_role" not in test["config_path"] + ]) + + connector_root = cls.get_connector_root_dir().absolute() + for test in test_scenarios: + if test.config_path: + test.config_path = connector_root / test.config_path + if test.configured_catalog_path: + test.configured_catalog_path = connector_root / test.configured_catalog_path + + return test_scenarios + + @pytest.mark.skipif( + shutil.which("docker") is None, + reason="docker CLI not found in PATH, skipping docker image tests", + ) + @pytest.mark.image_tests + def test_docker_image_build_and_spec( + self, + connector_image_override: str | None, + ) -> None: + """Run `docker_image` acceptance tests.""" + connector_dir = self.get_connector_root_dir() + metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") + + connector_image: str | None = connector_image_override + if not connector_image: + tag = "dev-latest" + connector_image = build_connector_image( + connector_name=connector_dir.name, + connector_directory=connector_dir, + metadata=metadata, + tag=tag, + no_verify=False, + ) + + _ = run_docker_command( + [ + "docker", + "run", + "--rm", + connector_image, + "spec", + ], + check=True, # Raise an error if the command fails + capture_output=False, + ) + + @pytest.mark.skipif( + shutil.which("docker") is None, + reason="docker CLI not found in PATH, skipping docker image tests", + ) + @pytest.mark.image_tests + def test_docker_image_build_and_check( + self, + scenario: ConnectorTestScenario, + connector_image_override: str | None, + ) -> None: + """Run `docker_image` acceptance tests. + + This test builds the connector image and runs the `check` command inside the container. + + Note: + - It is expected for docker image caches to be reused between test runs. + - In the rare case that image caches need to be cleared, please clear + the local docker image cache using `docker image prune -a` command. + """ + if scenario.expect_exception: + pytest.skip("Skipping test_docker_image_build_and_check (expected to fail).") + + tag = "dev-latest" + connector_dir = self.get_connector_root_dir() + metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") + connector_image: str | None = connector_image_override + if not connector_image: + tag = "dev-latest" + connector_image = build_connector_image( + connector_name=connector_dir.name, + connector_directory=connector_dir, + metadata=metadata, + tag=tag, + no_verify=False, + ) + + container_config_path = "/secrets/config.json" + with scenario.with_temp_config_file() as temp_config_file: + _ = run_docker_command( + [ + "docker", + "run", + "--rm", + "-v", + f"{temp_config_file}:{container_config_path}", + connector_image, + "check", + f"--config={container_config_path}", + ], + check=True, # Raise an error if the command fails + capture_output=False, + ) diff --git a/airbyte_cdk/test/standard_tests/util.py b/airbyte_cdk/test/standard_tests/util.py index f391d99ca..cb65c5260 100644 --- a/airbyte_cdk/test/standard_tests/util.py +++ b/airbyte_cdk/test/standard_tests/util.py @@ -10,7 +10,7 @@ from airbyte_cdk.test.standard_tests.declarative_sources import ( DeclarativeSourceTestSuite, ) -from airbyte_cdk.test.standard_tests.destination_base import DestinationTestSuiteBase +from airbyte_cdk.test.standard_tests.docker_base import DockerConnectorTestSuite from airbyte_cdk.test.standard_tests.source_base import SourceTestSuiteBase from airbyte_cdk.utils.connector_paths import ( METADATA_YAML, @@ -18,11 +18,12 @@ ) TEST_CLASS_MAPPING: dict[ - Literal["python", "manifest-only", "declarative"], type[ConnectorTestSuiteBase] + Literal["python", "manifest-only", "java"], + type[DockerConnectorTestSuite], ] = { "python": SourceTestSuiteBase, "manifest-only": DeclarativeSourceTestSuite, - # "declarative": DeclarativeSourceTestSuite, + "java": DockerConnectorTestSuite, } From ea22210cd5d1aaaaadd268699c996ffec0f554cb Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Sat, 7 Jun 2025 00:33:58 -0700 Subject: [PATCH 11/66] fix import --- airbyte_cdk/test/standard_tests/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/__init__.py b/airbyte_cdk/test/standard_tests/__init__.py index c6aeaaf1c..b77c9cc13 100644 --- a/airbyte_cdk/test/standard_tests/__init__.py +++ b/airbyte_cdk/test/standard_tests/__init__.py @@ -27,14 +27,12 @@ class TestSuiteSourcePokeAPI(standard_tests.DeclarativeSourceTestSuite): ''' -from airbyte_cdk.test.standard_tests.connector_base import ( - ConnectorTestScenario, - ConnectorTestSuiteBase, -) +from airbyte_cdk.test.standard_tests.connector_base import ConnectorTestSuiteBase from airbyte_cdk.test.standard_tests.declarative_sources import ( DeclarativeSourceTestSuite, ) from airbyte_cdk.test.standard_tests.destination_base import DestinationTestSuiteBase +from airbyte_cdk.test.standard_tests.models.scenario import ConnectorTestScenario from airbyte_cdk.test.standard_tests.source_base import SourceTestSuiteBase __all__ = [ From 7e06bdd04df4e374d1f61e3b4a38dcbe87bacafc Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Sat, 7 Jun 2025 00:34:20 -0700 Subject: [PATCH 12/66] move import to type checking --- airbyte_cdk/test/standard_tests/source_base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/source_base.py b/airbyte_cdk/test/standard_tests/source_base.py index a256fa04c..efb5ca126 100644 --- a/airbyte_cdk/test/standard_tests/source_base.py +++ b/airbyte_cdk/test/standard_tests/source_base.py @@ -2,6 +2,7 @@ """Base class for source test suites.""" from dataclasses import asdict +from typing import TYPE_CHECKING from airbyte_cdk.models import ( AirbyteMessage, @@ -12,7 +13,6 @@ SyncMode, Type, ) -from airbyte_cdk.test import entrypoint_wrapper from airbyte_cdk.test.standard_tests._job_runner import run_test_job from airbyte_cdk.test.standard_tests.connector_base import ( ConnectorTestSuiteBase, @@ -21,6 +21,9 @@ ConnectorTestScenario, ) +if TYPE_CHECKING: + from airbyte_cdk.test import entrypoint_wrapper + class SourceTestSuiteBase(ConnectorTestSuiteBase): """Base class for source test suites. From af99d50d7be4cbc1677b6b3468edd32e3ad43005 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Sat, 7 Jun 2025 00:44:17 -0700 Subject: [PATCH 13/66] allow 'exception' status type --- airbyte_cdk/test/standard_tests/models/scenario.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/models/scenario.py b/airbyte_cdk/test/standard_tests/models/scenario.py index 74fb36bfc..48129bbfd 100644 --- a/airbyte_cdk/test/standard_tests/models/scenario.py +++ b/airbyte_cdk/test/standard_tests/models/scenario.py @@ -47,7 +47,7 @@ class AcceptanceTestFileTypes(BaseModel): timeout_seconds: int | None = None expect_records: AcceptanceTestExpectRecords | None = None file_types: AcceptanceTestFileTypes | None = None - status: Literal["succeed", "failed"] | None = None + status: Literal["succeed", "failed", "exception"] | None = None def get_config_dict( self, @@ -76,7 +76,7 @@ def get_config_dict( @property def expect_exception(self) -> bool: - return self.status and self.status == "failed" or False + return (self.status and self.status in {"failed", "exception"}) or False @property def instance_name(self) -> str: From 35546a8ff07c90e10560392d949bbaae2b34a5fe Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Sat, 7 Jun 2025 00:46:03 -0700 Subject: [PATCH 14/66] java images don't like --config=... --- airbyte_cdk/test/standard_tests/docker_base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index 90248ef14..ea839be61 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -170,7 +170,8 @@ def test_docker_image_build_and_check( f"{temp_config_file}:{container_config_path}", connector_image, "check", - f"--config={container_config_path}", + "--config", + container_config_path, ], check=True, # Raise an error if the command fails capture_output=False, From dc788fac8c340a06dc27a58329afad368d10f626 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Sat, 7 Jun 2025 01:09:27 -0700 Subject: [PATCH 15/66] tolerate java destinations with no acceptance test config --- .../test/standard_tests/docker_base.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index ea839be61..4ef58009b 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -6,6 +6,7 @@ import inspect import shutil import sys +import warnings from pathlib import Path import pytest @@ -57,11 +58,23 @@ def get_scenarios( parametrization of fixtures with arguments from the test class itself. """ categories = ["connection", "spec"] - all_tests_config = yaml.safe_load(cls.acceptance_test_config_path.read_text()) + try: + acceptance_test_config_path = cls.acceptance_test_config_path + except FileNotFoundError as e: + # Destinations sometimes do not have an acceptance tests file. + warnings.warn( + f"Acceptance test config file not found: {e!s}. " + "No scenarios will be loaded.", + category=UserWarning, + stacklevel=0, + ) + return [] + + all_tests_config = yaml.safe_load(acceptance_test_config_path.read_text()) if "acceptance_tests" not in all_tests_config: raise ValueError( - f"Acceptance tests config not found in {cls.acceptance_test_config_path}." - f" Found only: {str(all_tests_config)}." + f"Acceptance tests config not found in {acceptance_test_config_path}. " + f"Found only: {all_tests_config!s}." ) test_scenarios: list[ConnectorTestScenario] = [] From b5c01743dffd4a762b38182f8c8418eec77bab22 Mon Sep 17 00:00:00 2001 From: "Aaron (\"AJ\") Steers" Date: Wed, 11 Jun 2025 13:50:01 -0700 Subject: [PATCH 16/66] Update airbyte_cdk/test/standard_tests/models/scenario.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../test/standard_tests/models/scenario.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/models/scenario.py b/airbyte_cdk/test/standard_tests/models/scenario.py index 48129bbfd..e19adfd5d 100644 --- a/airbyte_cdk/test/standard_tests/models/scenario.py +++ b/airbyte_cdk/test/standard_tests/models/scenario.py @@ -94,14 +94,13 @@ def __str__(self) -> str: def with_temp_config_file(self) -> Generator[Path, None, None]: """Yield a temporary JSON file path containing the config dict and delete it on exit.""" config = self.get_config_dict(empty_if_missing=True) - _, path_str = tempfile.mkstemp(prefix="config-", suffix=".json", text=True) - path = Path(path_str) - try: - path.write_text(json.dumps(config)) + with tempfile.NamedTemporaryFile(prefix="config-", suffix=".json", mode="w", delete=False) as temp_file: + temp_file.write(json.dumps(config)) + temp_file.flush() # Allow the file to be read by other processes - path.chmod(path.stat().st_mode | 0o444) - yield path - finally: - # attempt cleanup, ignore errors - with suppress(OSError): - path.unlink() + temp_path = Path(temp_file.name) + temp_path.chmod(temp_path.stat().st_mode | 0o444) + yield temp_path + # attempt cleanup, ignore errors + with suppress(OSError): + temp_path.unlink() From 37fcbd879141f9e9cbaeaa45b74de2cbe6a57996 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 13:57:43 -0700 Subject: [PATCH 17/66] add comment --- airbyte_cdk/test/standard_tests/models/scenario.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/models/scenario.py b/airbyte_cdk/test/standard_tests/models/scenario.py index e19adfd5d..ac5a8821f 100644 --- a/airbyte_cdk/test/standard_tests/models/scenario.py +++ b/airbyte_cdk/test/standard_tests/models/scenario.py @@ -94,13 +94,19 @@ def __str__(self) -> str: def with_temp_config_file(self) -> Generator[Path, None, None]: """Yield a temporary JSON file path containing the config dict and delete it on exit.""" config = self.get_config_dict(empty_if_missing=True) - with tempfile.NamedTemporaryFile(prefix="config-", suffix=".json", mode="w", delete=False) as temp_file: + with tempfile.NamedTemporaryFile( + prefix="config-", + suffix=".json", + mode="w", + delete=False, # Don't fail if cannot delete the file on exit + ) as temp_file: temp_file.write(json.dumps(config)) temp_file.flush() # Allow the file to be read by other processes temp_path = Path(temp_file.name) temp_path.chmod(temp_path.stat().st_mode | 0o444) yield temp_path - # attempt cleanup, ignore errors - with suppress(OSError): - temp_path.unlink() + + # attempt cleanup, ignore errors + with suppress(OSError): + temp_path.unlink() From c4205fffb88b0f25a60a2a17fa8c53c7119ff8a7 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 15:58:50 -0700 Subject: [PATCH 18/66] update workflow to test connectors with cdk standard test framework --- .github/workflows/connector-tests.yml | 80 +++++---------------------- 1 file changed, 15 insertions(+), 65 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index b02fddfad..68a31a8b0 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -90,6 +90,8 @@ jobs: # add `--use-local-cdk` support for manifest connectors. - connector: source-amplitude cdk_extra: n/a + - connector: source-intercom + cdk_extra: n/a - connector: source-pokeapi cdk_extra: n/a @@ -129,6 +131,15 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.11" + + - name: Set up `uv` + uses: astral-sh/setup-uv@v6.1.0 + + - name: Set up Poetry + uses: Gr1N/setup-poetry@v9 + with: + poetry-version: "2.0.1" + # Create initial pending status for test report - name: Create Pending Test Report Status if: steps.no_changes.outputs.status != 'cancelled' @@ -148,52 +159,16 @@ jobs: - name: Test Connector if: steps.no_changes.outputs.status != 'cancelled' timeout-minutes: 90 + working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} env: GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" run: | - cd airbyte - make tools.airbyte-ci.install - airbyte-ci \ - --ci-report-bucket-name=airbyte-ci-reports-multi \ - connectors \ - --name ${{matrix.connector}} \ - --use-local-cdk \ - test \ - --fail-fast \ - --skip-step qa_checks \ - --skip-step connector_live_tests - - - name: Evaluate Test - id: evaluate_output - if: always() && steps.no_changes.outputs.status != 'cancelled' - run: | - # save job output json file as ci step output - json_output_file=$(find airbyte/airbyte-ci/connectors/pipelines/pipeline_reports -name 'output.json' -print -quit) - job_output=$(cat ${json_output_file}) - success=$(echo ${job_output} | jq -r '.success') - failed_step=$(echo ${job_output} | jq -r '.failed_steps | select(length > 0) | .[0] // "None"') - run_duration=$(echo ${job_output} | jq -r '.run_duration') - html_report_url=$(echo ${job_output} | jq -r '.html_report_url') - echo "## Job Output for ${{matrix.connector}}" >> $GITHUB_STEP_SUMMARY - echo "- [HTML Report](${html_report_url})" >> $GITHUB_STEP_SUMMARY - echo "- Success: ${success}" >> $GITHUB_STEP_SUMMARY - echo "- Test Duration: $(printf "%.0f" ${run_duration})s" >> $GITHUB_STEP_SUMMARY - if [ "${success}" != "true" ]; then - echo "- Failed Step: ${failed_step}" >> $GITHUB_STEP_SUMMARY - fi - echo -e "\n[Download Job Output](${{steps.upload_job_output.outputs.artifact-url}})" >> $GITHUB_STEP_SUMMARY - if [ "${success}" != "true" ]; then - echo "::error::Test failed for connector '${{ matrix.connector }}' on step '${failed_step}'. " - exit 1 - fi - echo "See the execution report for details: ${html_report_url}" - echo "success=${success}" >> $GITHUB_OUTPUT - echo "html_report_url=${html_report_url}" >> $GITHUB_OUTPUT + airbyte-cdk connector test ${{matrix.connector}} # Update the test report status with results - name: Update Test Report Status - if: always() && steps.no_changes.outputs.status != 'cancelled' && steps.evaluate_output.outcome == 'success' + if: always() && steps.no_changes.outputs.status != 'cancelled' env: GH_TOKEN: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }} run: | @@ -203,32 +178,7 @@ jobs: -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ repos/${{ github.repository }}/statuses/$HEAD_SHA \ - -f state="${{ steps.evaluate_output.outputs.success == 'true' && 'success' || 'failure' }}" \ + -f state="${{ job.status }}" \ -f target_url="${{ steps.evaluate_output.outputs.html_report_url }}" \ -f description="Click Details to view the test report" \ -f context="${{ matrix.connector }} Test Report" - - # Create failure status if report generation failed - - name: Create Report Generation Failed Status - if: always() && steps.no_changes.outputs.status != 'cancelled' && steps.evaluate_output.outcome != 'success' - env: - GH_TOKEN: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }} - run: | - HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}" - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/${{ github.repository }}/statuses/$HEAD_SHA \ - -f state="failure" \ - -f description="Failed to run connector tests." \ - -f context="${{ matrix.connector }} Test Report" - - # Upload the job output to the artifacts - - name: Upload Job Output - id: upload_job_output - if: always() && steps.no_changes.outputs.status != 'cancelled' - uses: actions/upload-artifact@v4 - with: - name: ${{matrix.connector}}-job-output - path: airbyte/airbyte-ci/connectors/pipelines/pipeline_reports From f011acf72b0bc79a7ab79bb575493578ea26daa8 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 16:06:52 -0700 Subject: [PATCH 19/66] dispose of file outside of with block --- airbyte_cdk/test/standard_tests/models/scenario.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/models/scenario.py b/airbyte_cdk/test/standard_tests/models/scenario.py index ac5a8821f..3ccd4eadd 100644 --- a/airbyte_cdk/test/standard_tests/models/scenario.py +++ b/airbyte_cdk/test/standard_tests/models/scenario.py @@ -107,6 +107,6 @@ def with_temp_config_file(self) -> Generator[Path, None, None]: temp_path.chmod(temp_path.stat().st_mode | 0o444) yield temp_path - # attempt cleanup, ignore errors - with suppress(OSError): - temp_path.unlink() + # attempt cleanup, ignore errors + with suppress(OSError): + temp_path.unlink() From c5c1f066c632a91f22c4e26b713d91bd4e322495 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 16:07:21 -0700 Subject: [PATCH 20/66] add explicit encoding --- airbyte_cdk/test/standard_tests/models/scenario.py | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte_cdk/test/standard_tests/models/scenario.py b/airbyte_cdk/test/standard_tests/models/scenario.py index 3ccd4eadd..ab0c472d4 100644 --- a/airbyte_cdk/test/standard_tests/models/scenario.py +++ b/airbyte_cdk/test/standard_tests/models/scenario.py @@ -99,6 +99,7 @@ def with_temp_config_file(self) -> Generator[Path, None, None]: suffix=".json", mode="w", delete=False, # Don't fail if cannot delete the file on exit + encoding="utf-8", ) as temp_file: temp_file.write(json.dumps(config)) temp_file.flush() From d982c2455d48f106bf9f9969a203043258bf0976 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 16:10:18 -0700 Subject: [PATCH 21/66] pre-install airbyte-cdk --- .github/workflows/connector-tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 68a31a8b0..2677ad6a0 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -140,6 +140,12 @@ jobs: with: poetry-version: "2.0.1" + - name: Install Airbyte CDK + if: steps.no_changes.outputs.status != 'cancelled' + run: | + cd airbyte-python-cdk + poetry install --all-extras + # Create initial pending status for test report - name: Create Pending Test Report Status if: steps.no_changes.outputs.status != 'cancelled' @@ -164,7 +170,7 @@ jobs: GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" run: | - airbyte-cdk connector test ${{matrix.connector}} + poetry run airbyte-cdk connector test ${{matrix.connector}} # Update the test report status with results - name: Update Test Report Status From cb9c0371bbaa3279010ecda011cd53a7e7a24013 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 22:43:19 -0700 Subject: [PATCH 22/66] fix imports --- airbyte_cdk/test/standard_tests/__init__.py | 2 +- airbyte_cdk/test/standard_tests/docker_base.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/__init__.py b/airbyte_cdk/test/standard_tests/__init__.py index b77c9cc13..50c233228 100644 --- a/airbyte_cdk/test/standard_tests/__init__.py +++ b/airbyte_cdk/test/standard_tests/__init__.py @@ -27,12 +27,12 @@ class TestSuiteSourcePokeAPI(standard_tests.DeclarativeSourceTestSuite): ''' +from airbyte_cdk.test.models.scenario import ConnectorTestScenario from airbyte_cdk.test.standard_tests.connector_base import ConnectorTestSuiteBase from airbyte_cdk.test.standard_tests.declarative_sources import ( DeclarativeSourceTestSuite, ) from airbyte_cdk.test.standard_tests.destination_base import DestinationTestSuiteBase -from airbyte_cdk.test.standard_tests.models.scenario import ConnectorTestScenario from airbyte_cdk.test.standard_tests.source_base import SourceTestSuiteBase __all__ = [ diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index 5c04c8439..06d6af57f 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -14,9 +14,7 @@ from boltons.typeutils import classproperty from airbyte_cdk.models.connector_metadata import MetadataFile -from airbyte_cdk.test.standard_tests.models import ( - ConnectorTestScenario, -) +from airbyte_cdk.test.models import ConnectorTestScenario from airbyte_cdk.utils.connector_paths import ( ACCEPTANCE_TEST_CONFIG, find_connector_root, From 2e587c400be6232a5f01fe4c3d16b516a00b9b08 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 22:44:47 -0700 Subject: [PATCH 23/66] fix if condition --- airbyte_cdk/test/standard_tests/docker_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index 06d6af57f..fd0007a9d 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -158,7 +158,7 @@ def test_docker_image_build_and_check( - In the rare case that image caches need to be cleared, please clear the local docker image cache using `docker image prune -a` command. """ - if scenario.expect_exception: + if scenario.expected_outcome.expect_exception(): pytest.skip("Skipping test_docker_image_build_and_check (expected to fail).") tag = "dev-latest" From 701efe32837efdc81106e0a8b64a0187eda4258a Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 22:46:47 -0700 Subject: [PATCH 24/66] fixes --- .../test/standard_tests/docker_base.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index fd0007a9d..a699b2d7f 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -113,15 +113,15 @@ def test_docker_image_build_and_spec( connector_image_override: str | None, ) -> None: """Run `docker_image` acceptance tests.""" - connector_dir = self.get_connector_root_dir() - metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") + connector_root = self.get_connector_root_dir() + metadata = MetadataFile.from_file(connector_root / "metadata.yaml") connector_image: str | None = connector_image_override if not connector_image: tag = "dev-latest" connector_image = build_connector_image( - connector_name=connector_dir.name, - connector_directory=connector_dir, + connector_name=connector_root.name, + connector_directory=connector_root, metadata=metadata, tag=tag, no_verify=False, @@ -162,21 +162,23 @@ def test_docker_image_build_and_check( pytest.skip("Skipping test_docker_image_build_and_check (expected to fail).") tag = "dev-latest" - connector_dir = self.get_connector_root_dir() - metadata = MetadataFile.from_file(connector_dir / "metadata.yaml") + connector_root = self.get_connector_root_dir() + metadata = MetadataFile.from_file(connector_root / "metadata.yaml") connector_image: str | None = connector_image_override if not connector_image: tag = "dev-latest" connector_image = build_connector_image( - connector_name=connector_dir.name, - connector_directory=connector_dir, + connector_name=connector_root.name, + connector_directory=connector_root, metadata=metadata, tag=tag, no_verify=False, ) container_config_path = "/secrets/config.json" - with scenario.with_temp_config_file() as temp_config_file: + with scenario.with_temp_config_file( + connector_root=connector_root, + ) as temp_config_file: _ = run_docker_command( [ "docker", From 4bde1dd04f4c5ea9069f14474f76b7c5d785097f Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Wed, 11 Jun 2025 22:52:54 -0700 Subject: [PATCH 25/66] fix github action --- .github/workflows/connector-tests.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 2677ad6a0..3af804437 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -162,15 +162,24 @@ jobs: -f description="Running connector tests..." \ -f context="${{ matrix.connector }} Test Report" - - name: Test Connector + - name: Fetch Connector Secrets if: steps.no_changes.outputs.status != 'cancelled' timeout-minutes: 90 - working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} + working-directory: airbyte-python-cdk + env: + GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} + POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" + run: | + poetry run airbyte-cdk secrets fetch ${{ matrix.connector }} + + - name: Test Connector + if: steps.no_changes.outputs.status != 'cancelled' + working-directory: airbyte-python-cdk env: GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" run: | - poetry run airbyte-cdk connector test ${{matrix.connector}} + poetry run airbyte-cdk connector test ${{ matrix.connector }} # Update the test report status with results - name: Update Test Report Status From 1a8ee4c9377f7b53edf63e9a3cf9330c45034441 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 10:52:20 -0700 Subject: [PATCH 26/66] improve docker output handling --- airbyte_cdk/utils/docker.py | 55 ++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index 82b0cdcdf..355457471 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -7,8 +7,10 @@ import os import subprocess import sys +from contextlib import ExitStack from dataclasses import dataclass from enum import Enum +from io import TextIOWrapper from pathlib import Path import click @@ -90,6 +92,7 @@ def _build_image( run_docker_command( docker_args, check=True, + capture_stderr=True, ) except subprocess.CalledProcessError as e: raise ConnectorImageBuildError( @@ -126,6 +129,7 @@ def _tag_image( run_docker_command( docker_args, check=True, + capture_stderr=True, ) except subprocess.CalledProcessError as e: raise ConnectorImageBuildError( @@ -368,7 +372,8 @@ def run_docker_command( cmd: list[str], *, check: bool = True, - capture_output: bool = False, + capture_stdout: bool | Path = False, + capture_stderr: bool | Path = False, ) -> subprocess.CompletedProcess[str]: """Run a Docker command as a subprocess. @@ -376,23 +381,46 @@ def run_docker_command( cmd: The command to run as a list of strings. check: If True, raises an exception if the command fails. If False, the caller is responsible for checking the return code. - capture_output: If True, captures stdout and stderr and returns to the caller. - If False, the output is printed to the console. + capture_stdout: How to process stdout. + capture_stderr: If True, captures stderr in memory and returns to the caller. + If a Path is provided, the output is written to the specified file. + + For stdout and stderr process: + - If False (the default), stdout is not captured. + - If True, output is captured in memory and returned within the `CompletedProcess` object. + - If a Path is provided, the output is written to the specified file. (Recommended for large syncs.) Raises: subprocess.CalledProcessError: If the command fails and check is True. """ print(f"Running command: {' '.join(cmd)}") - process = subprocess.run( - cmd, - text=True, - check=check, - # If capture_output=True, stderr and stdout are captured and returned to caller: - capture_output=capture_output, - env={**os.environ, "DOCKER_BUILDKIT": "1"}, - ) - return process + with ExitStack() as stack: + # Shared context manager to handle file closing, if needed. + stderr: TextIOWrapper | int | None + stdout: TextIOWrapper | int | None + + # If capture_stderr or capture_stdout is a Path, we open the file in write mode. + # If it's a boolean, we set it to either subprocess.PIPE or None. + if isinstance(capture_stderr, Path): + stderr = stack.enter_context(capture_stderr.open("w", encoding="utf-8")) + elif isinstance(capture_stderr, bool): + stderr = subprocess.PIPE if capture_stderr is True else None + + if isinstance(capture_stdout, Path): + stdout = stack.enter_context(capture_stdout.open("w", encoding="utf-8")) + elif isinstance(capture_stdout, bool): + stdout = subprocess.PIPE if capture_stdout is True else None + + completed_process: subprocess.CompletedProcess[str] = subprocess.run( + cmd, + env={**os.environ, "DOCKER_BUILDKIT": "1"}, + text=True, + check=check, + stderr=stderr, + stdout=stdout, + ) + return completed_process def verify_docker_installation() -> bool: @@ -423,7 +451,8 @@ def verify_connector_image( result = run_docker_command( cmd, check=True, - capture_output=True, + capture_stderr=True, + capture_stdout=True, ) # check that the output is valid JSON if result.stdout: From 0f413adb5bec150ff4afe1ad7e88e5b0e9f8fbd1 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 13:28:12 -0700 Subject: [PATCH 27/66] cherry-pick-me: add missing serializer classes --- airbyte_cdk/models/airbyte_protocol_serializers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/airbyte_cdk/models/airbyte_protocol_serializers.py b/airbyte_cdk/models/airbyte_protocol_serializers.py index 129556acc..52cab83d2 100644 --- a/airbyte_cdk/models/airbyte_protocol_serializers.py +++ b/airbyte_cdk/models/airbyte_protocol_serializers.py @@ -4,9 +4,11 @@ from serpyco_rs import CustomType, Serializer from .airbyte_protocol import ( # type: ignore[attr-defined] # all classes are imported to airbyte_protocol via * + AirbyteCatalog, AirbyteMessage, AirbyteStateBlob, AirbyteStateMessage, + AirbyteStream, AirbyteStreamState, ConfiguredAirbyteCatalog, ConfiguredAirbyteStream, @@ -30,6 +32,8 @@ def custom_type_resolver(t: type) -> CustomType[AirbyteStateBlob, Dict[str, Any] return AirbyteStateBlobType() if t is AirbyteStateBlob else None +AirbyteCatalogSerializer = Serializer(AirbyteCatalog, omit_none=True) +AirbyteStreamSerializer = Serializer(AirbyteStream, omit_none=True) AirbyteStreamStateSerializer = Serializer( AirbyteStreamState, omit_none=True, custom_type_resolver=custom_type_resolver ) From fc99ddcc87be55e35f8661b927eb29d6dd14d07e Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 13:28:39 -0700 Subject: [PATCH 28/66] cherry-pick-me: add 'suggestedStreams' declaration for metadata dataclass --- airbyte_cdk/models/connector_metadata.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/airbyte_cdk/models/connector_metadata.py b/airbyte_cdk/models/connector_metadata.py index e76f94951..7efbd8285 100644 --- a/airbyte_cdk/models/connector_metadata.py +++ b/airbyte_cdk/models/connector_metadata.py @@ -47,6 +47,11 @@ class ConnectorMetadata(BaseModel): description="List of tags for the connector", ) + suggestedStreams: list[str] | None = Field( + None, + description="List of suggested streams for the connector", + ) + @property def language(self) -> ConnectorLanguage: """Get the connector language.""" From 6cd27c73d37bbe91abd618f56d89bcb3ab07e14b Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 14:23:20 -0700 Subject: [PATCH 29/66] feat: working container 'read' tests --- airbyte_cdk/test/models/scenario.py | 21 +- .../test/standard_tests/docker_base.py | 206 +++++++++++++++++- .../test/standard_tests/pytest_hooks.py | 104 ++++++++- 3 files changed, 305 insertions(+), 26 deletions(-) diff --git a/airbyte_cdk/test/models/scenario.py b/airbyte_cdk/test/models/scenario.py index 28e809991..92813101e 100644 --- a/airbyte_cdk/test/models/scenario.py +++ b/airbyte_cdk/test/models/scenario.py @@ -47,7 +47,7 @@ class AcceptanceTestFileTypes(BaseModel): config_path: Path | None = None config_dict: dict[str, Any] | None = None - id: str | None = None + _id: str | None = None # Used to override the default ID generation configured_catalog_path: Path | None = None timeout_seconds: int | None = None @@ -99,16 +99,21 @@ def expected_outcome(self) -> ExpectedOutcome: return ExpectedOutcome.from_status_str(self.status) @property - def instance_name(self) -> str: - return self.config_path.stem if self.config_path else "Unnamed Scenario" + def id(self) -> str: + """Return a unique identifier for the test scenario. + + This is used by PyTest to identify the test scenario. + """ + if self._id: + return self._id - def __str__(self) -> str: - if self.id: - return f"'{self.id}' Test Scenario" if self.config_path: - return f"'{self.config_path.name}' Test Scenario" + return self.config_path.stem - return f"'{hash(self)}' Test Scenario" + return str(hash(self)) + + def __str__(self) -> str: + return f"'{self.id}' Test Scenario" @contextmanager def with_temp_config_file( diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index a699b2d7f..db2727044 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -6,15 +6,30 @@ import inspect import shutil import sys +import tempfile import warnings +from dataclasses import asdict from pathlib import Path +from subprocess import CompletedProcess, SubprocessError +import orjson import pytest import yaml from boltons.typeutils import classproperty +from airbyte_cdk.models import ( + AirbyteCatalog, + ConfiguredAirbyteCatalog, + ConfiguredAirbyteStream, + DestinationSyncMode, +) +from airbyte_cdk.models.airbyte_protocol_serializers import ( + AirbyteCatalogSerializer, + AirbyteStreamSerializer, +) from airbyte_cdk.models.connector_metadata import MetadataFile from airbyte_cdk.test.models import ConnectorTestScenario +from airbyte_cdk.test.utils.reading import catalog from airbyte_cdk.utils.connector_paths import ( ACCEPTANCE_TEST_CONFIG, find_connector_root, @@ -127,17 +142,23 @@ def test_docker_image_build_and_spec( no_verify=False, ) - _ = run_docker_command( - [ - "docker", - "run", - "--rm", - connector_image, - "spec", - ], - check=True, # Raise an error if the command fails - capture_output=False, - ) + try: + result: CompletedProcess[str] = run_docker_command( + [ + "docker", + "run", + "--rm", + connector_image, + "spec", + ], + check=True, # Raise an error if the command fails + capture_stderr=True, + capture_stdout=True, + ) + except SubprocessError as ex: + raise AssertionError( + f"Failed to run `spec` command in docker image {connector_image!r}. Error: {ex!s}" + ) from None @pytest.mark.skipif( shutil.which("docker") is None, @@ -192,5 +213,166 @@ def test_docker_image_build_and_check( container_config_path, ], check=True, # Raise an error if the command fails - capture_output=False, + capture_stderr=True, + capture_stdout=True, + ) + + @pytest.mark.skipif( + shutil.which("docker") is None, + reason="docker CLI not found in PATH, skipping docker image tests", + ) + @pytest.mark.image_tests + def test_docker_image_build_and_read( + self, + scenario: ConnectorTestScenario, + connector_image_override: str | None, + read_from_streams: Literal["all", "none", "default"] | list[str], + read_scenarios: Literal["all", "none", "default"] | list[str], + ) -> None: + """Read from the connector's Docker image. + + This test builds the connector image and runs the `read` command inside the container. + + Note: + - It is expected for docker image caches to be reused between test runs. + - In the rare case that image caches need to be cleared, please clear + the local docker image cache using `docker image prune -a` command. + - If the --connector-image arg is provided, it will be used instead of building the image. + """ + if scenario.expected_outcome.expect_exception(): + pytest.skip("Skipping (expected to fail).") + + if read_from_streams == "none": + pytest.skip("Skipping read test (`--read-from-streams=false`).") + + if read_scenarios == "none": + pytest.skip("Skipping (`--read-scenarios=none`).") + + default_scenario_ids = ["config", "valid_config", "default"] + if read_scenarios == "all": + pass + elif read_scenarios == "default": + if scenario.id not in default_scenario_ids: + pytest.skip( + f"Skipping read test for scenario '{scenario.id}' " + f"(not in default scenarios list '{default_scenario_ids}')." + ) + elif scenario.id not in read_scenarios: + # pytest.skip( + raise ValueError( + f"Skipping read test for scenario '{scenario.id}' " + f"(not in --read-scenarios={read_scenarios})." + ) + + tag = "dev-latest" + connector_root = self.get_connector_root_dir() + connector_name = connector_root.name + metadata = MetadataFile.from_file(connector_root / "metadata.yaml") + connector_image: str | None = connector_image_override + if not connector_image: + tag = "dev-latest" + connector_image = build_connector_image( + connector_name=connector_name, + connector_directory=connector_root, + metadata=metadata, + tag=tag, + no_verify=False, + ) + + container_config_path = "/secrets/config.json" + container_catalog_path = "/secrets/catalog.json" + + discovered_catalog_path = Path( + tempfile.mktemp(prefix=f"{connector_name}-discovered-catalog-", suffix=".json") + ) + configured_catalog_path = Path( + tempfile.mktemp(prefix=f"{connector_name}-configured-catalog-", suffix=".json") + ) + with scenario.with_temp_config_file( + connector_root=connector_root, + ) as temp_config_file: + discover_result = run_docker_command( + [ + "docker", + "run", + "--rm", + "-v", + f"{temp_config_file}:{container_config_path}", + connector_image, + "discover", + "--config", + container_config_path, + ], + check=True, # Raise an error if the command fails + capture_stderr=True, + capture_stdout=True, + ) + try: + discovered_catalog: AirbyteCatalog = AirbyteCatalogSerializer.load( + orjson.loads(discover_result.stdout)["catalog"], + ) + except Exception as ex: + raise AssertionError( + f"Failed to load discovered catalog from {discover_result.stdout}. " + f"Error: {ex!s}" + ) from None + if not discovered_catalog.streams: + raise ValueError( + f"Discovered catalog for connector '{connector_name}' is empty. " + "Please check the connector's discover implementation." + ) + + streams_list = [stream.name for stream in discovered_catalog.streams] + if read_from_streams == "default" and metadata.data.suggestedStreams: + # set `streams_list` to be the intersection of discovered and suggested streams. + streams_list = list(set(streams_list) & set(metadata.data.suggestedStreams)) + + if isinstance(read_from_streams, list): + # If `read_from_streams` is a list, we filter the discovered streams. + streams_list = list(set(streams_list) & set(read_from_streams)) + + configured_catalog: ConfiguredAirbyteCatalog = ConfiguredAirbyteCatalog( + streams=[ + ConfiguredAirbyteStream( + stream=stream, + sync_mode=stream.supported_sync_modes[0], + destination_sync_mode=DestinationSyncMode.append, + ) + for stream in discovered_catalog.streams + if stream.name in streams_list + ] + ) + configured_catalog_path.write_text( + orjson.dumps(asdict(configured_catalog)).decode("utf-8") + ) + read_result: CompletedProcess[str] = run_docker_command( + [ + "docker", + "run", + "--rm", + "-v", + f"{temp_config_file}:{container_config_path}", + "-v", + f"{configured_catalog_path}:{container_catalog_path}", + connector_image, + "read", + "--config", + container_config_path, + "--catalog", + container_catalog_path, + ], + check=False, + capture_stderr=True, + capture_stdout=True, ) + if read_result.returncode != 0: + raise AssertionError( + f"Failed to run `read` command in docker image {connector_image!r}. " + "\n-----------------" + f"EXIT CODE: {read_result.returncode}\n" + "STDERR:\n" + f"{read_result.stderr}\n" + f"STDOUT:\n" + f"{read_result.stdout}\n" + "\n-----------------" + ) from None diff --git a/airbyte_cdk/test/standard_tests/pytest_hooks.py b/airbyte_cdk/test/standard_tests/pytest_hooks.py index f408606da..187a1c787 100644 --- a/airbyte_cdk/test/standard_tests/pytest_hooks.py +++ b/airbyte_cdk/test/standard_tests/pytest_hooks.py @@ -13,9 +13,95 @@ ``` """ +from typing import Literal + import pytest +@pytest.fixture +def connector_image_override(request: pytest.FixtureRequest) -> str | None: + """Return the value of --connector-image, or None if not set.""" + return request.config.getoption("--connector-image") + + +@pytest.fixture +def read_from_streams( + request: pytest.FixtureRequest, +) -> Literal["all", "none", "default"] | list[str]: + """Specify if the test should read from streams. + + The input can be one of the following: + - [Omitted] - Default to False, meaning no streams will be read. + - `--read-from-streams`: Read from all suggested streams. + - `--read-from-streams=true`: Read from all suggested streams. + - `--read-from-streams=suggested`: Read from all suggested streams. + - `--read-from-streams=default`: Read from all suggested streams. + - `--read-from-streams=all`: Read from all streams. + - `--read-from-streams=stream1,stream2`: Read from the specified streams only. + - `--read-from-streams=false`: Do not read from any streams. + - `--read-from-streams=none`: Do not read from any streams. + """ + input_val: str | bool | None = request.config.getoption( + "--read-from-streams", + default="suggested", # type: ignore + ) # type: ignore + + if isinstance(input_val, str): + if input_val.lower() == "false": + return False + if input_val.lower() in ["true", "suggested", "default"]: + # Default to 'default' (suggested) streams if the input is 'true', 'suggested', or + # 'default'. + # This is the default behavior if the option is not set. + return "default" + if input_val.lower() == "all": + # This will sometimes fail if the account doesn't have permissions + # to premium or restricted stream data. + return "all" + + # If the input is a comma-separated list, split it into a list. + # This will return a one-element list if the input is a single stream name. + return input_val.split(",") + + # Else, probably a bool; return it as is. + return input_val or False + + +@pytest.fixture +def read_scenarios( + request: pytest.FixtureRequest, +) -> list[str] | Literal["all", "default"]: + """Return the value of `--read-scenarios`. + + This argument is ignored if `--read-from-streams` is False or not set. + + The input can be one of the following: + - [Omitted] - Default to 'config.json', meaning the default scenario will be read. + - `--read-scenarios=all`: Read all scenarios. + - `--read-scenarios=none`: Read no scenarios. (Overrides `--read-from-streams`, if set.) + - `--read-scenarios=scenario1,scenario2`: Read the specified scenarios only. + + """ + input_val = request.config.getoption("--read-scenarios", default="default") + + if input_val.lower() == "default": + # Default config scenario is always 'config.json'. + return "default" + + if input_val.lower() == "none": + # Default config scenario is always 'config.json'. + return [] + + return ( + [ + scenario_name.strip().lower().removesuffix(".json") + for scenario_name in input_val.split(",") + ] + if input_val + else [] + ) + + def pytest_addoption(parser: pytest.Parser) -> None: """Add --connector-image to pytest's CLI.""" parser.addoption( @@ -24,12 +110,18 @@ def pytest_addoption(parser: pytest.Parser) -> None: default=None, help="Use this pre-built connector Docker image instead of building one.", ) - - -@pytest.fixture -def connector_image_override(request: pytest.FixtureRequest) -> str | None: - """Return the value of --connector-image, or None if not set.""" - return request.config.getoption("--connector-image") + parser.addoption( + "--read-from-streams", + action="store", + default=None, + help=read_from_streams.__doc__, + ) + parser.addoption( + "--read-scenarios", + action="store", + default="default", + help=read_scenarios.__doc__, + ) def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: From 31ad95907c6d916d3149668f04f10ad82a0c9220 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 14:28:23 -0700 Subject: [PATCH 30/66] fix mypy --- .../test/standard_tests/docker_base.py | 1 + .../test/standard_tests/pytest_hooks.py | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index db2727044..0fcdf1ee6 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -11,6 +11,7 @@ from dataclasses import asdict from pathlib import Path from subprocess import CompletedProcess, SubprocessError +from typing import Literal import orjson import pytest diff --git a/airbyte_cdk/test/standard_tests/pytest_hooks.py b/airbyte_cdk/test/standard_tests/pytest_hooks.py index 187a1c787..b267bd7a3 100644 --- a/airbyte_cdk/test/standard_tests/pytest_hooks.py +++ b/airbyte_cdk/test/standard_tests/pytest_hooks.py @@ -13,7 +13,7 @@ ``` """ -from typing import Literal +from typing import Literal, cast import pytest @@ -21,7 +21,7 @@ @pytest.fixture def connector_image_override(request: pytest.FixtureRequest) -> str | None: """Return the value of --connector-image, or None if not set.""" - return request.config.getoption("--connector-image") + return cast(str | None, request.config.getoption("--connector-image")) @pytest.fixture @@ -41,14 +41,14 @@ def read_from_streams( - `--read-from-streams=false`: Do not read from any streams. - `--read-from-streams=none`: Do not read from any streams. """ - input_val: str | bool | None = request.config.getoption( + input_val: str = request.config.getoption( "--read-from-streams", - default="suggested", # type: ignore + default="default", # type: ignore ) # type: ignore if isinstance(input_val, str): if input_val.lower() == "false": - return False + return "none" if input_val.lower() in ["true", "suggested", "default"]: # Default to 'default' (suggested) streams if the input is 'true', 'suggested', or # 'default'. @@ -64,7 +64,7 @@ def read_from_streams( return input_val.split(",") # Else, probably a bool; return it as is. - return input_val or False + return input_val or "none" @pytest.fixture @@ -82,7 +82,13 @@ def read_scenarios( - `--read-scenarios=scenario1,scenario2`: Read the specified scenarios only. """ - input_val = request.config.getoption("--read-scenarios", default="default") + input_val = cast( + str, + request.config.getoption( + "--read-scenarios", + default="default", # type: ignore + ), + ) if input_val.lower() == "default": # Default config scenario is always 'config.json'. From 975e3d1297701bb2fc51c145813957097d6eb84d Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 16:59:42 -0700 Subject: [PATCH 31/66] use buildx to make this portable for ubuntu runners --- airbyte_cdk/utils/docker.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index 355457471..5b5c5d406 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -4,7 +4,7 @@ import json import logging -import os +import platform import subprocess import sys from contextlib import ExitStack @@ -58,11 +58,13 @@ def _build_image( """Build a Docker image for the specified architecture. Returns the tag of the built image. + We use buildx to ensure we can build multi-platform images. Raises: ConnectorImageBuildError if the build fails. """ docker_args: list[str] = [ "docker", + "buildx", "build", "--platform", f"linux/{arch.value}", @@ -144,7 +146,6 @@ def build_connector_image( *, metadata: MetadataFile, tag: str, - primary_arch: ArchEnum = ArchEnum.ARM64, # Assume MacBook M series by default no_verify: bool = False, dockerfile_override: Path | None = None, ) -> str: @@ -159,15 +160,19 @@ def build_connector_image( connector_directory: The directory containing the connector code. metadata: The metadata of the connector. tag: The tag to apply to the built image. - primary_arch: The primary architecture for the build (default: arm64). This - architecture will be used for the same-named tag. Both AMD64 and ARM64 - images will be built, with the suffixes '-amd64' and '-arm64'. no_verify: If True, skip verification of the built image. Raises: ValueError: If the connector build options are not defined in metadata.yaml. ConnectorImageBuildError: If the image build or tag operation fails. """ + # Detect primary architecture based on the machine type. + primary_arch: ArchEnum = ( + ArchEnum.ARM64 + if platform.machine().lower().startswith(("arm", "aarch")) + else ArchEnum.AMD64 + ) + connector_kebab_name = connector_name connector_dockerfile_dir = connector_directory / "build" / "docker" @@ -414,7 +419,6 @@ def run_docker_command( completed_process: subprocess.CompletedProcess[str] = subprocess.run( cmd, - env={**os.environ, "DOCKER_BUILDKIT": "1"}, text=True, check=check, stderr=stderr, From 1778aa63acf38df0147951fb36c4f36e8b61650d Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 17:49:50 -0700 Subject: [PATCH 32/66] fix security concern for temp files --- .../test/standard_tests/docker_base.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index 0fcdf1ee6..d51eca9d1 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -283,15 +283,13 @@ def test_docker_image_build_and_read( container_config_path = "/secrets/config.json" container_catalog_path = "/secrets/catalog.json" - discovered_catalog_path = Path( - tempfile.mktemp(prefix=f"{connector_name}-discovered-catalog-", suffix=".json") - ) - configured_catalog_path = Path( - tempfile.mktemp(prefix=f"{connector_name}-configured-catalog-", suffix=".json") - ) - with scenario.with_temp_config_file( - connector_root=connector_root, - ) as temp_config_file: + with ( + scenario.with_temp_config_file( + connector_root=connector_root, + ) as temp_config_file, + tempfile.TemporaryDirectory(delete=False) as temp_dir_str, + ): + temp_dir = Path(temp_dir_str) discover_result = run_docker_command( [ "docker", @@ -326,7 +324,7 @@ def test_docker_image_build_and_read( streams_list = [stream.name for stream in discovered_catalog.streams] if read_from_streams == "default" and metadata.data.suggestedStreams: # set `streams_list` to be the intersection of discovered and suggested streams. - streams_list = list(set(streams_list) & set(metadata.data.suggestedStreams)) + streams_list = list(set(streams_list) & set(metadata.data.suggestedStreams.streams)) if isinstance(read_from_streams, list): # If `read_from_streams` is a list, we filter the discovered streams. @@ -343,6 +341,7 @@ def test_docker_image_build_and_read( if stream.name in streams_list ] ) + configured_catalog_path = temp_dir / "catalog.json" configured_catalog_path.write_text( orjson.dumps(asdict(configured_catalog)).decode("utf-8") ) From 978ed9254a716ec8da9754612aae7aac52fbfa40 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 17:50:16 -0700 Subject: [PATCH 33/66] fix suggested streams --- airbyte_cdk/models/connector_metadata.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/airbyte_cdk/models/connector_metadata.py b/airbyte_cdk/models/connector_metadata.py index 7efbd8285..d0fd51099 100644 --- a/airbyte_cdk/models/connector_metadata.py +++ b/airbyte_cdk/models/connector_metadata.py @@ -34,6 +34,14 @@ class ConnectorBuildOptions(BaseModel): ) +class SuggestedStreams(BaseModel): + """Suggested streams from metadata.yaml.""" + + streams: list[str] = Field( + default=[], + description="List of suggested streams for the connector", + ) + class ConnectorMetadata(BaseModel): """Connector metadata from metadata.yaml.""" @@ -47,9 +55,9 @@ class ConnectorMetadata(BaseModel): description="List of tags for the connector", ) - suggestedStreams: list[str] | None = Field( - None, - description="List of suggested streams for the connector", + suggestedStreams: SuggestedStreams | None = Field( + default=None, + description="Suggested streams for the connector", ) @property From 5a235becbffdab50901d6f561e9be1b8037081de Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 17:51:31 -0700 Subject: [PATCH 34/66] fix format --- airbyte_cdk/models/connector_metadata.py | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte_cdk/models/connector_metadata.py b/airbyte_cdk/models/connector_metadata.py index d0fd51099..7f945aadc 100644 --- a/airbyte_cdk/models/connector_metadata.py +++ b/airbyte_cdk/models/connector_metadata.py @@ -42,6 +42,7 @@ class SuggestedStreams(BaseModel): description="List of suggested streams for the connector", ) + class ConnectorMetadata(BaseModel): """Connector metadata from metadata.yaml.""" From 311e31447663afd5da10ecf93f737c17fa8ba6ec Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 18:17:23 -0700 Subject: [PATCH 35/66] clearer temp dir name --- airbyte_cdk/test/standard_tests/docker_base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index d51eca9d1..f9355b532 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -287,7 +287,10 @@ def test_docker_image_build_and_read( scenario.with_temp_config_file( connector_root=connector_root, ) as temp_config_file, - tempfile.TemporaryDirectory(delete=False) as temp_dir_str, + tempfile.TemporaryDirectory( + prefix=f"{connector_name}-test", + delete=False, + ) as temp_dir_str, ): temp_dir = Path(temp_dir_str) discover_result = run_docker_command( From 7b163f802f73e20dda23ff329c6e09ab2debc0d3 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 18:28:51 -0700 Subject: [PATCH 36/66] ignore arm when running on amd --- airbyte_cdk/utils/docker.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index 5b5c5d406..231e8dab6 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -228,7 +228,6 @@ def build_connector_image( } base_tag = f"{metadata.data.dockerRepository}:{tag}" - arch_images: list[str] = [] if metadata.data.language == ConnectorLanguage.JAVA: # This assumes that the repo root ('airbyte') is three levels above the @@ -245,12 +244,18 @@ def build_connector_image( check=True, ) - for arch in [ArchEnum.AMD64, ArchEnum.ARM64]: + # Always build for AMD64, and optionally for ARM64 if needed locally. + architectures = [ArchEnum.AMD64] + if primary_arch == ArchEnum.ARM64: + architectures += [ArchEnum.ARM64] + + built_images: list[str] = [] + for arch in architectures: docker_tag = f"{base_tag}-{arch.value}" docker_tag_parts = docker_tag.split("/") if len(docker_tag_parts) > 2: docker_tag = "/".join(docker_tag_parts[-1:]) - arch_images.append( + built_images.append( _build_image( context_dir=connector_directory, dockerfile=dockerfile_path, From a77999fca69256bd71cc4fdab13e007f0360a7f5 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 18:32:34 -0700 Subject: [PATCH 37/66] fix missing 'delete' on python 3.11 --- airbyte_cdk/test/standard_tests/docker_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index f9355b532..f8f894896 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -289,7 +289,7 @@ def test_docker_image_build_and_read( ) as temp_config_file, tempfile.TemporaryDirectory( prefix=f"{connector_name}-test", - delete=False, + ignore_cleanup_errors=True, ) as temp_dir_str, ): temp_dir = Path(temp_dir_str) From a1eceaacdc34a6763ff72df85d564dd60e287e57 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 18:44:56 -0700 Subject: [PATCH 38/66] fix warning --- airbyte_cdk/test/standard_tests/docker_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index f8f894896..f5157b8fb 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -79,7 +79,7 @@ def get_scenarios( warnings.warn( f"Acceptance test config file not found: {e!s}. No scenarios will be loaded.", category=UserWarning, - stacklevel=0, + stacklevel=1, ) return [] From 08a1c22ced87bfaf924183de2ab0f43cf432b55e Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Thu, 12 Jun 2025 18:47:12 -0700 Subject: [PATCH 39/66] fix sync mode --- airbyte_cdk/test/standard_tests/docker_base.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index f5157b8fb..c1ebad7ce 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -23,6 +23,7 @@ ConfiguredAirbyteCatalog, ConfiguredAirbyteStream, DestinationSyncMode, + SyncMode, ) from airbyte_cdk.models.airbyte_protocol_serializers import ( AirbyteCatalogSerializer, @@ -337,7 +338,11 @@ def test_docker_image_build_and_read( streams=[ ConfiguredAirbyteStream( stream=stream, - sync_mode=stream.supported_sync_modes[0], + sync_mode=( + stream.supported_sync_modes[0] + if stream.supported_sync_modes + else SyncMode.full_refresh + ), destination_sync_mode=DestinationSyncMode.append, ) for stream in discovered_catalog.streams From b13f37bc7a0e3d98364de0660e8cf9cb7da47912 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 09:35:09 -0700 Subject: [PATCH 40/66] cherry-pick-me: add safe entrypoint methods, add file handling for the EntrypointOutput class --- airbyte_cdk/test/entrypoint_wrapper.py | 167 ++++++++++++++++++++++--- 1 file changed, 151 insertions(+), 16 deletions(-) diff --git a/airbyte_cdk/test/entrypoint_wrapper.py b/airbyte_cdk/test/entrypoint_wrapper.py index b46e7f86a..0f0824e07 100644 --- a/airbyte_cdk/test/entrypoint_wrapper.py +++ b/airbyte_cdk/test/entrypoint_wrapper.py @@ -19,9 +19,11 @@ import re import tempfile import traceback +from collections import deque +from collections.abc import Generator, Mapping from io import StringIO from pathlib import Path -from typing import Any, List, Mapping, Optional, Union +from typing import Any, List, Literal, Optional, Union, final, overload import orjson from pydantic import ValidationError as V2ValidationError @@ -43,18 +45,47 @@ TraceType, Type, ) +from airbyte_cdk.models.airbyte_protocol import AirbyteMessage, AirbyteStreamState from airbyte_cdk.sources import Source from airbyte_cdk.test.models.scenario import ExpectedOutcome class EntrypointOutput: - def __init__(self, messages: List[str], uncaught_exception: Optional[BaseException] = None): - try: - self._messages = [self._parse_message(message) for message in messages] - except V2ValidationError as exception: - raise ValueError("All messages are expected to be AirbyteMessage") from exception + """A class to encapsulate the output of an Airbyte connector's execution. + + This class can be initialized with a list of messages or a file containing messages. + It provides methods to access different types of messages produced during the execution + of an Airbyte connector, including both successful messages and error messages. + + When working with records and state messages, it provides both a list and an iterator + implementation. Lists are easier to work with, but generators are better suited to handle + large volumes of messages without overflowing the available memory. + """ + + def __init__( + self, + messages: list[str] | None = None, + uncaught_exception: Optional[BaseException] = None, + *, + message_file: Path | None = None, + ) -> None: + if messages is None and message_file is None: + raise ValueError("Either messages or message_file must be provided") + if messages is not None and message_file is not None: + raise ValueError("Only one of messages or message_file can be provided") + + self._messages: list[AirbyteMessage] | None = [] + self._message_file: Path | None = message_file + if messages: + try: + self._messages = [self._parse_message(message) for message in messages] + except V2ValidationError as exception: + raise ValueError("All messages are expected to be AirbyteMessage") from exception if uncaught_exception: + if self._messages is None: + self._messages = [] + self._messages.append( assemble_uncaught_exception( type(uncaught_exception), uncaught_exception @@ -72,13 +103,40 @@ def _parse_message(message: str) -> AirbyteMessage: ) @property - def records_and_state_messages(self) -> List[AirbyteMessage]: - return self._get_message_by_types([Type.RECORD, Type.STATE]) + def records_and_state_messages( + self, + ) -> list[AirbyteMessage]: + return self._get_message_by_types( + message_types=[Type.RECORD, Type.STATE], + safe_iterator=False, + ) + + def records_and_state_messages_iterator( + self, + ) -> Generator[AirbyteMessage, None, None]: + """Returns a generator that yields record and state messages one by one. + + Use this instead of `records_and_state_messages` when the volume of messages could be large + enough to overload available memory. + """ + return self._get_message_by_types( + message_types=[Type.RECORD, Type.STATE], + safe_iterator=True, + ) @property def records(self) -> List[AirbyteMessage]: return self._get_message_by_types([Type.RECORD]) + @property + def records_iterator(self) -> Generator[AirbyteMessage, None, None]: + """Returns a generator that yields record messages one by one. + + Use this instead of `records` when the volume of records could be large + enough to overload available memory. + """ + return self._get_message_by_types([Type.RECORD], safe_iterator=True) + @property def state_messages(self) -> List[AirbyteMessage]: return self._get_message_by_types([Type.STATE]) @@ -92,11 +150,21 @@ def connection_status_messages(self) -> List[AirbyteMessage]: return self._get_message_by_types([Type.CONNECTION_STATUS]) @property - def most_recent_state(self) -> Any: - state_messages = self._get_message_by_types([Type.STATE]) - if not state_messages: - raise ValueError("Can't provide most recent state as there are no state messages") - return state_messages[-1].state.stream # type: ignore[union-attr] # state has `stream` + def most_recent_state(self) -> AirbyteStreamState | None: + state_message_iterator = self._get_message_by_types( + [Type.STATE], + safe_iterator=True, + ) + # Use a deque with maxlen=1 to efficiently get the last state message + double_ended_queue = deque(state_message_iterator, maxlen=1) + try: + final_state_message: AirbyteMessage = double_ended_queue.pop() + except IndexError: + raise ValueError( + "Can't provide most recent state as there are no state messages." + ) from None + + return final_state_message.state.stream # type: ignore[union-attr] # state has `stream` @property def logs(self) -> List[AirbyteMessage]: @@ -131,13 +199,80 @@ def get_stream_statuses(self, stream_name: str) -> List[AirbyteStreamStatus]: ) return list(status_messages) - def _get_message_by_types(self, message_types: List[Type]) -> List[AirbyteMessage]: - return [message for message in self._messages if message.type in message_types] + def _read_all_messages(self) -> Generator[AirbyteMessage, None, None]: + """Creates a generator which yields messages one by one. + + This will iterate over all messages in the output file (if provided) or the messages + provided during initialization. File results are provided first, followed by any + messages that were passed in directly. + """ + if self._message_file: + try: + with open(self._message_file, "r", encoding="utf-8") as file: + for line in file: + if not line.strip(): + # Skip empty lines + continue + + yield self._parse_message(line.strip()) + except FileNotFoundError: + raise ValueError(f"Message file {self._message_file} not found") + + if self._messages is not None: + yield from self._messages + + # Overloads to provide proper type hints for different usages of `_get_message_by_types`. + + @overload + def _get_message_by_types( + self, + message_types: list[Type], + ) -> list[AirbyteMessage]: ... + + @overload + def _get_message_by_types( + self, + message_types: list[Type], + *, + safe_iterator: Literal[False], + ) -> list[AirbyteMessage]: ... + + @overload + def _get_message_by_types( + self, + message_types: list[Type], + *, + safe_iterator: Literal[True], + ) -> Generator[AirbyteMessage, None, None]: ... + + def _get_message_by_types( + self, + message_types: list[Type], + *, + safe_iterator: bool = True, + ) -> list[AirbyteMessage] | Generator[AirbyteMessage, None, None]: + """Get messages of specific types. + + If `safe_iterator` is True, returns a generator that yields messages one by one. + If `safe_iterator` is False, returns a list of messages. + + Use `safe_iterator=True` when the volume of messages could overload the available + memory. + """ + message_generator = self._read_all_messages() + + if safe_iterator: + return (message for message in message_generator if message.type in message_types) + + return [message for message in message_generator if message.type in message_types] def _get_trace_message_by_trace_type(self, trace_type: TraceType) -> List[AirbyteMessage]: return [ message - for message in self._get_message_by_types([Type.TRACE]) + for message in self._get_message_by_types( + [Type.TRACE], + safe_iterator=True, + ) if message.trace.type == trace_type # type: ignore[union-attr] # trace has `type` ] From 8f9948220bace9e03f4bdffe93d4aed92bc55877 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:00:03 -0700 Subject: [PATCH 41/66] try matrix conditions rewrite --- .github/workflows/connector-tests.yml | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 3af804437..73908d128 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -72,22 +72,12 @@ jobs: cdk_extra: n/a - connector: source-shopify cdk_extra: n/a - # Chargebee is being flaky: - # - connector: source-chargebee - # cdk_extra: n/a - # This one is behind in CDK updates and can't be used as tests until it is updated: - # - connector: destination-pinecone - # cdk_extra: vector-db-based - connector: source-google-drive cdk_extra: file-based - connector: destination-motherduck cdk_extra: sql - # ZenDesk currently failing (as of 2024-12-02) - # TODO: Re-enable once fixed - # - connector: source-zendesk-support - # cdk_extra: n/a - # TODO: These are manifest connectors and won't work as expected until we - # add `--use-local-cdk` support for manifest connectors. + - connector: source-zendesk-support + cdk_extra: n/a - connector: source-amplitude cdk_extra: n/a - connector: source-intercom @@ -95,7 +85,10 @@ jobs: - connector: source-pokeapi cdk_extra: n/a - name: "Check: '${{matrix.connector}}' (skip=${{needs.cdk_changes.outputs['src'] == 'false' || needs.cdk_changes.outputs[matrix.cdk_extra] == 'false'}})" + name: "Check: '${{matrix.connector}}'" + if: > + needs.cdk_changes.outputs['src'] == 'true' && + (matrix.cdk_extra != 'n/a' || needs.cdk_changes.outputs[matrix.cdk_extra] == 'true') permissions: checks: write contents: write # Required for creating commit statuses @@ -135,6 +128,10 @@ jobs: - name: Set up `uv` uses: astral-sh/setup-uv@v6.1.0 + - name: Set up `poe` + run: | + uv tool install poethepoet + - name: Set up Poetry uses: Gr1N/setup-poetry@v9 with: @@ -172,6 +169,12 @@ jobs: run: | poetry run airbyte-cdk secrets fetch ${{ matrix.connector }} + - name: Bump to Dev CDK + if: steps.no_changes.outputs.status != 'cancelled' + working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} + run: | + poe use-cdk-dev + - name: Test Connector if: steps.no_changes.outputs.status != 'cancelled' working-directory: airbyte-python-cdk From c4c8216a3769166d318497f745b68c713a5b9d9b Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:03:52 -0700 Subject: [PATCH 42/66] remove matrix condition from if --- .github/workflows/connector-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 73908d128..1f85a56ca 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -86,9 +86,7 @@ jobs: cdk_extra: n/a name: "Check: '${{matrix.connector}}'" - if: > - needs.cdk_changes.outputs['src'] == 'true' && - (matrix.cdk_extra != 'n/a' || needs.cdk_changes.outputs[matrix.cdk_extra] == 'true') + if: needs.cdk_changes.outputs['src'] == 'true' permissions: checks: write contents: write # Required for creating commit statuses From bbb090a32dda5453b686ebec372f336af8523ef1 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:06:15 -0700 Subject: [PATCH 43/66] bring back skipped indicator --- .github/workflows/connector-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 1f85a56ca..300732824 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -85,7 +85,7 @@ jobs: - connector: source-pokeapi cdk_extra: n/a - name: "Check: '${{matrix.connector}}'" + name: "Check: '${{matrix.connector}}'${{ needs.cdk_changes.outputs['src'] == 'false' && '(skipped)' || '' }}" if: needs.cdk_changes.outputs['src'] == 'true' permissions: checks: write From c948f53903f2ac85cb5964239216f8dd2a6b43ff Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:08:17 -0700 Subject: [PATCH 44/66] retry conditional name --- .github/workflows/connector-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 300732824..491651aed 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -85,7 +85,7 @@ jobs: - connector: source-pokeapi cdk_extra: n/a - name: "Check: '${{matrix.connector}}'${{ needs.cdk_changes.outputs['src'] == 'false' && '(skipped)' || '' }}" + name: "Check: '${{matrix.connector}}'${{ needs.cdk_changes.outputs[matrix.cdk_extra] == 'false' && '(skipped)' || '' }}" if: needs.cdk_changes.outputs['src'] == 'true' permissions: checks: write From 061ce29c7457bc2db416c58ba14d97f350770d10 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:09:39 -0700 Subject: [PATCH 45/66] BLUF - bottom line up front --- .github/workflows/connector-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 491651aed..7c25b7a25 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -85,7 +85,7 @@ jobs: - connector: source-pokeapi cdk_extra: n/a - name: "Check: '${{matrix.connector}}'${{ needs.cdk_changes.outputs[matrix.cdk_extra] == 'false' && '(skipped)' || '' }}" + name: "${{ needs.cdk_changes.outputs[matrix.cdk_extra] == 'false' && 'Skipped Check' || 'Check' }}: '${{matrix.connector}}" if: needs.cdk_changes.outputs['src'] == 'true' permissions: checks: write From fcd72870c2ff0adc6674e5c82ff98c2b42b13060 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:11:59 -0700 Subject: [PATCH 46/66] try dev from active cdk branch ref --- .github/workflows/connector-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 7c25b7a25..52e3ee090 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -171,7 +171,7 @@ jobs: if: steps.no_changes.outputs.status != 'cancelled' working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} run: | - poe use-cdk-dev + poe use-cdk-branch-active - name: Test Connector if: steps.no_changes.outputs.status != 'cancelled' From 434af51d2abb363ce6398fe1e9c5759bd00f300c Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:31:01 -0700 Subject: [PATCH 47/66] add language-specific testing --- .github/workflows/connector-tests.yml | 50 ++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 52e3ee090..c192bf60b 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -118,6 +118,7 @@ jobs: repository: airbytehq/airbyte ref: master path: airbyte + - name: Set up Python uses: actions/setup-python@v5 with: @@ -135,6 +136,13 @@ jobs: with: poetry-version: "2.0.1" + - name: Get Connector Language + working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} + run: | + # Get the language of the connector from the metadata file + CONNECTOR_LANGUAGE=$(poe -qq get-language) + echo "language=$CONNECTOR_LANGUAGE" | tee -a $GITHUB_ENV + - name: Install Airbyte CDK if: steps.no_changes.outputs.status != 'cancelled' run: | @@ -167,14 +175,39 @@ jobs: run: | poetry run airbyte-cdk secrets fetch ${{ matrix.connector }} - - name: Bump to Dev CDK - if: steps.no_changes.outputs.status != 'cancelled' + - name: Bump to Dev Branch CDK [Python Connectors] + if: env.CONNECTOR_LANGUAGE == 'python' working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} run: | poe use-cdk-branch-active + poe install - - name: Test Connector - if: steps.no_changes.outputs.status != 'cancelled' + - name: Run Unit Tests [Python Connectors] + if: env.CONNECTOR_LANGUAGE == 'python' + working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} + run: | + poe test-unit-tests + + - name: Run FAST Standard Tests [Python Connectors] + if: env.CONNECTOR_LANGUAGE == 'python' + working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} + env: + GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} + POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" + run: | + poetry run airbyte-cdk connector test + + - name: Test Connector using Dev CDK [Python Connectors] + if: env.CONNECTOR_LANGUAGE == 'python' + working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} + env: + GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} + POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" + run: | + poetry run airbyte-cdk connector test + + - name: Test Connector [Manifest-Only Connectors] + if: env.CONNECTOR_LANGUAGE == 'manifest-only' working-directory: airbyte-python-cdk env: GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} @@ -182,6 +215,15 @@ jobs: run: | poetry run airbyte-cdk connector test ${{ matrix.connector }} + - name: Container Build and Test [All Connectors] + if: steps.no_changes.outputs.status != 'cancelled' + working-directory: airbyte-python-cdk + env: + GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} + POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" + run: | + poetry run airbyte-cdk image test ${{ matrix.connector }} + # Update the test report status with results - name: Update Test Report Status if: always() && steps.no_changes.outputs.status != 'cancelled' From 504d2867612302c30be58fa08f8b906897d8ba64 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:41:08 -0700 Subject: [PATCH 48/66] drive-by-fix: CI should always print secret masks unless we ask not to --- airbyte_cdk/cli/airbyte_cdk/_secrets.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/airbyte_cdk/cli/airbyte_cdk/_secrets.py b/airbyte_cdk/cli/airbyte_cdk/_secrets.py index 2b28addd2..1fb689c8e 100644 --- a/airbyte_cdk/cli/airbyte_cdk/_secrets.py +++ b/airbyte_cdk/cli/airbyte_cdk/_secrets.py @@ -99,12 +99,12 @@ def secrets_cli_group() -> None: help="Print GitHub CI mask for secrets.", type=bool, is_flag=True, - default=False, + default=None, ) def fetch( connector: str | Path | None = None, gcp_project_id: str = GCP_PROJECT_ID, - print_ci_secrets_masks: bool = False, + print_ci_secrets_masks: bool | None = None, ) -> None: """Fetch secrets for a connector from Google Secret Manager. @@ -181,22 +181,23 @@ def fetch( if secret_count == 0: raise exceptions[0] - if not print_ci_secrets_masks: - return - - if not os.environ.get("CI", None): + if print_ci_secrets_masks and "CI" not in os.environ: click.echo( "The `--print-ci-secrets-masks` option is only available in CI environments. " "The `CI` env var is either not set or not set to a truthy value. " "Skipping printing secret masks.", err=True, ) - return - - # Else print the CI mask - _print_ci_secrets_masks( - secrets_dir=secrets_dir, - ) + print_ci_secrets_masks = False + elif print_ci_secrets_masks is None: + # If not explicitly set, we check if we are in a CI environment + # and set to True if so. + print_ci_secrets_masks = os.environ.get("CI", "") != "" + + if print_ci_secrets_masks: + _print_ci_secrets_masks( + secrets_dir=secrets_dir, + ) @secrets_cli_group.command("list") From ca0bf50752730dd9d3b37a9ce27b392f0f06a121 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:41:50 -0700 Subject: [PATCH 49/66] fix if condition --- .github/workflows/connector-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index c192bf60b..2d2580444 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -137,6 +137,7 @@ jobs: poetry-version: "2.0.1" - name: Get Connector Language + if: steps.no_changes.outputs.status != 'cancelled' working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} run: | # Get the language of the connector from the metadata file From 79cc4629c80ab65bfecaf149c860c794886053bd Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:44:38 -0700 Subject: [PATCH 50/66] fix env var name --- .github/workflows/connector-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 2d2580444..f9570be91 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -142,9 +142,9 @@ jobs: run: | # Get the language of the connector from the metadata file CONNECTOR_LANGUAGE=$(poe -qq get-language) - echo "language=$CONNECTOR_LANGUAGE" | tee -a $GITHUB_ENV + echo "CONNECTOR_LANGUAGE=$CONNECTOR_LANGUAGE" | tee -a $GITHUB_ENV - - name: Install Airbyte CDK + - name: Install CDK with Poetry if: steps.no_changes.outputs.status != 'cancelled' run: | cd airbyte-python-cdk From ba21cf4bda412150182f6697ff62cdf6e8efc255 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 10:50:33 -0700 Subject: [PATCH 51/66] use more explicit install from poetry --- .github/workflows/connector-tests.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index f9570be91..d8c7fa968 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -181,7 +181,7 @@ jobs: working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} run: | poe use-cdk-branch-active - poe install + poetry install --all-extras - name: Run Unit Tests [Python Connectors] if: env.CONNECTOR_LANGUAGE == 'python' @@ -198,23 +198,23 @@ jobs: run: | poetry run airbyte-cdk connector test - - name: Test Connector using Dev CDK [Python Connectors] - if: env.CONNECTOR_LANGUAGE == 'python' - working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} + - name: Run FAST Standard Tests [Manifest-Only Connectors] + if: env.CONNECTOR_LANGUAGE == 'manifest-only' + working-directory: airbyte-python-cdk env: GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" run: | - poetry run airbyte-cdk connector test + poetry run airbyte-cdk connector test ${{ matrix.connector }} - - name: Test Connector [Manifest-Only Connectors] - if: env.CONNECTOR_LANGUAGE == 'manifest-only' - working-directory: airbyte-python-cdk + - name: Test Connector using Dev CDK [Python Connectors] + if: env.CONNECTOR_LANGUAGE == 'python' + working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} env: GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" run: | - poetry run airbyte-cdk connector test ${{ matrix.connector }} + poetry run airbyte-cdk connector test - name: Container Build and Test [All Connectors] if: steps.no_changes.outputs.status != 'cancelled' From 1d9fc99f47ddba2d8cf35ad8193cc45c0b915512 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:03:51 -0700 Subject: [PATCH 52/66] improve skip logic, use explicit branch ref --- .github/workflows/connector-tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index d8c7fa968..b910a64f5 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -120,18 +120,22 @@ jobs: path: airbyte - name: Set up Python + if: steps.no_changes.outputs.status != 'cancelled' uses: actions/setup-python@v5 with: python-version: "3.11" - name: Set up `uv` + if: steps.no_changes.outputs.status != 'cancelled' uses: astral-sh/setup-uv@v6.1.0 - name: Set up `poe` + if: steps.no_changes.outputs.status != 'cancelled' run: | uv tool install poethepoet - name: Set up Poetry + if: steps.no_changes.outputs.status != 'cancelled' uses: Gr1N/setup-poetry@v9 with: poetry-version: "2.0.1" @@ -180,7 +184,8 @@ jobs: if: env.CONNECTOR_LANGUAGE == 'python' working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} run: | - poe use-cdk-branch-active + echo "Using CDK ref ${{ github.event.pull_request.head.sha || github.sha }}" + poe use-cdk-branch ${{ github.event.pull_request.head.sha || github.sha }} poetry install --all-extras - name: Run Unit Tests [Python Connectors] From 4514b8864c5b3da9c47398f2604f0a3894d0304d Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:14:10 -0700 Subject: [PATCH 53/66] make public: get_message_iterator() and get_message_by_types() --- airbyte_cdk/test/entrypoint_wrapper.py | 42 +++++++++---------- .../test/standard_tests/connector_base.py | 8 ++-- .../test/standard_tests/source_base.py | 7 +--- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/airbyte_cdk/test/entrypoint_wrapper.py b/airbyte_cdk/test/entrypoint_wrapper.py index 0f0824e07..68a423197 100644 --- a/airbyte_cdk/test/entrypoint_wrapper.py +++ b/airbyte_cdk/test/entrypoint_wrapper.py @@ -74,7 +74,7 @@ def __init__( if messages is not None and message_file is not None: raise ValueError("Only one of messages or message_file can be provided") - self._messages: list[AirbyteMessage] | None = [] + self._messages: list[AirbyteMessage] | None = None self._message_file: Path | None = message_file if messages: try: @@ -106,7 +106,7 @@ def _parse_message(message: str) -> AirbyteMessage: def records_and_state_messages( self, ) -> list[AirbyteMessage]: - return self._get_message_by_types( + return self.get_message_by_types( message_types=[Type.RECORD, Type.STATE], safe_iterator=False, ) @@ -119,14 +119,14 @@ def records_and_state_messages_iterator( Use this instead of `records_and_state_messages` when the volume of messages could be large enough to overload available memory. """ - return self._get_message_by_types( + return self.get_message_by_types( message_types=[Type.RECORD, Type.STATE], safe_iterator=True, ) @property def records(self) -> List[AirbyteMessage]: - return self._get_message_by_types([Type.RECORD]) + return self.get_message_by_types([Type.RECORD]) @property def records_iterator(self) -> Generator[AirbyteMessage, None, None]: @@ -135,23 +135,23 @@ def records_iterator(self) -> Generator[AirbyteMessage, None, None]: Use this instead of `records` when the volume of records could be large enough to overload available memory. """ - return self._get_message_by_types([Type.RECORD], safe_iterator=True) + return self.get_message_by_types([Type.RECORD], safe_iterator=True) @property def state_messages(self) -> List[AirbyteMessage]: - return self._get_message_by_types([Type.STATE]) + return self.get_message_by_types([Type.STATE]) @property def spec_messages(self) -> List[AirbyteMessage]: - return self._get_message_by_types([Type.SPEC]) + return self.get_message_by_types([Type.SPEC]) @property def connection_status_messages(self) -> List[AirbyteMessage]: - return self._get_message_by_types([Type.CONNECTION_STATUS]) + return self.get_message_by_types([Type.CONNECTION_STATUS]) @property def most_recent_state(self) -> AirbyteStreamState | None: - state_message_iterator = self._get_message_by_types( + state_message_iterator = self.get_message_by_types( [Type.STATE], safe_iterator=True, ) @@ -168,11 +168,11 @@ def most_recent_state(self) -> AirbyteStreamState | None: @property def logs(self) -> List[AirbyteMessage]: - return self._get_message_by_types([Type.LOG]) + return self.get_message_by_types([Type.LOG]) @property def trace_messages(self) -> List[AirbyteMessage]: - return self._get_message_by_types([Type.TRACE]) + return self.get_message_by_types([Type.TRACE]) @property def analytics_messages(self) -> List[AirbyteMessage]: @@ -184,7 +184,7 @@ def errors(self) -> List[AirbyteMessage]: @property def catalog(self) -> AirbyteMessage: - catalog = self._get_message_by_types([Type.CATALOG]) + catalog = self.get_message_by_types([Type.CATALOG]) if len(catalog) != 1: raise ValueError(f"Expected exactly one catalog but got {len(catalog)}") return catalog[0] @@ -199,7 +199,7 @@ def get_stream_statuses(self, stream_name: str) -> List[AirbyteStreamStatus]: ) return list(status_messages) - def _read_all_messages(self) -> Generator[AirbyteMessage, None, None]: + def get_message_iterator(self) -> Generator[AirbyteMessage, None, None]: """Creates a generator which yields messages one by one. This will iterate over all messages in the output file (if provided) or the messages @@ -221,16 +221,16 @@ def _read_all_messages(self) -> Generator[AirbyteMessage, None, None]: if self._messages is not None: yield from self._messages - # Overloads to provide proper type hints for different usages of `_get_message_by_types`. + # Overloads to provide proper type hints for different usages of `get_message_by_types`. @overload - def _get_message_by_types( + def get_message_by_types( self, message_types: list[Type], ) -> list[AirbyteMessage]: ... @overload - def _get_message_by_types( + def get_message_by_types( self, message_types: list[Type], *, @@ -238,18 +238,18 @@ def _get_message_by_types( ) -> list[AirbyteMessage]: ... @overload - def _get_message_by_types( + def get_message_by_types( self, message_types: list[Type], *, safe_iterator: Literal[True], ) -> Generator[AirbyteMessage, None, None]: ... - def _get_message_by_types( + def get_message_by_types( self, message_types: list[Type], *, - safe_iterator: bool = True, + safe_iterator: bool = False, ) -> list[AirbyteMessage] | Generator[AirbyteMessage, None, None]: """Get messages of specific types. @@ -259,7 +259,7 @@ def _get_message_by_types( Use `safe_iterator=True` when the volume of messages could overload the available memory. """ - message_generator = self._read_all_messages() + message_generator = self.get_message_iterator() if safe_iterator: return (message for message in message_generator if message.type in message_types) @@ -269,7 +269,7 @@ def _get_message_by_types( def _get_trace_message_by_trace_type(self, trace_type: TraceType) -> List[AirbyteMessage]: return [ message - for message in self._get_message_by_types( + for message in self.get_message_by_types( [Type.TRACE], safe_iterator=True, ) diff --git a/airbyte_cdk/test/standard_tests/connector_base.py b/airbyte_cdk/test/standard_tests/connector_base.py index 30f4261a5..588b7d0bd 100644 --- a/airbyte_cdk/test/standard_tests/connector_base.py +++ b/airbyte_cdk/test/standard_tests/connector_base.py @@ -112,9 +112,7 @@ def test_check( test_scenario=scenario, connector_root=self.get_connector_root_dir(), ) - conn_status_messages: list[AirbyteMessage] = [ - msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS - ] # noqa: SLF001 # Non-public API - assert len(conn_status_messages) == 1, ( - f"Expected exactly one CONNECTION_STATUS message. Got: {result._messages}" + assert len(result.connection_status_messages) == 1, ( + f"Expected exactly one CONNECTION_STATUS message. " + "Got: {result.connection_status_messages!s}" ) diff --git a/airbyte_cdk/test/standard_tests/source_base.py b/airbyte_cdk/test/standard_tests/source_base.py index f3cc1a50c..2b1aa2829 100644 --- a/airbyte_cdk/test/standard_tests/source_base.py +++ b/airbyte_cdk/test/standard_tests/source_base.py @@ -48,13 +48,10 @@ def test_check( test_scenario=scenario, connector_root=self.get_connector_root_dir(), ) - conn_status_messages: list[AirbyteMessage] = [ - msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS - ] # noqa: SLF001 # Non-public API - num_status_messages = len(conn_status_messages) + num_status_messages = len(result.connection_status_messages) assert num_status_messages == 1, ( f"Expected exactly one CONNECTION_STATUS message. Got {num_status_messages}: \n" - + "\n".join([str(m) for m in result._messages]) + + "\n".join([str(m) for m in result.get_message_iterator()]) ) def test_discover( From d5db571fc09aa36b4e7f8d2c785b271f73f792c6 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:23:05 -0700 Subject: [PATCH 54/66] improve catalog handling --- airbyte_cdk/test/standard_tests/docker_base.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index c1ebad7ce..d87cc7552 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -30,6 +30,7 @@ AirbyteStreamSerializer, ) from airbyte_cdk.models.connector_metadata import MetadataFile +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput from airbyte_cdk.test.models import ConnectorTestScenario from airbyte_cdk.test.utils.reading import catalog from airbyte_cdk.utils.connector_paths import ( @@ -310,10 +311,11 @@ def test_docker_image_build_and_read( capture_stderr=True, capture_stdout=True, ) + parsed_output = EntrypointOutput(messages=discover_result.stdout.splitlines()) try: - discovered_catalog: AirbyteCatalog = AirbyteCatalogSerializer.load( - orjson.loads(discover_result.stdout)["catalog"], - ) + catalog_message = parsed_output.catalog # Get catalog message + assert catalog_message.catalog is not None, "Catalog message missing catalog." + discovered_catalog: AirbyteCatalog = parsed_output.catalog.catalog except Exception as ex: raise AssertionError( f"Failed to load discovered catalog from {discover_result.stdout}. " From b16828c81d5827d01aed8280f375be7a5129b273 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:25:22 -0700 Subject: [PATCH 55/66] fix imports --- airbyte_cdk/test/entrypoint_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte_cdk/test/entrypoint_wrapper.py b/airbyte_cdk/test/entrypoint_wrapper.py index 68a423197..eec9e11e9 100644 --- a/airbyte_cdk/test/entrypoint_wrapper.py +++ b/airbyte_cdk/test/entrypoint_wrapper.py @@ -38,6 +38,7 @@ AirbyteMessageSerializer, AirbyteStateMessage, AirbyteStateMessageSerializer, + AirbyteStreamState, AirbyteStreamStatus, ConfiguredAirbyteCatalog, ConfiguredAirbyteCatalogSerializer, @@ -45,7 +46,6 @@ TraceType, Type, ) -from airbyte_cdk.models.airbyte_protocol import AirbyteMessage, AirbyteStreamState from airbyte_cdk.sources import Source from airbyte_cdk.test.models.scenario import ExpectedOutcome From c495fc262d83adb8fce1311d8720aa2bcfec2e52 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:28:26 -0700 Subject: [PATCH 56/66] remove manual post of excess checks --- .github/workflows/connector-tests.yml | 33 --------------------------- 1 file changed, 33 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index b910a64f5..4e359554a 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -154,22 +154,6 @@ jobs: cd airbyte-python-cdk poetry install --all-extras - # Create initial pending status for test report - - name: Create Pending Test Report Status - if: steps.no_changes.outputs.status != 'cancelled' - env: - GH_TOKEN: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }} - run: | - HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}" - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/${{ github.repository }}/statuses/$HEAD_SHA \ - -f state="pending" \ - -f description="Running connector tests..." \ - -f context="${{ matrix.connector }} Test Report" - - name: Fetch Connector Secrets if: steps.no_changes.outputs.status != 'cancelled' timeout-minutes: 90 @@ -229,20 +213,3 @@ jobs: POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" run: | poetry run airbyte-cdk image test ${{ matrix.connector }} - - # Update the test report status with results - - name: Update Test Report Status - if: always() && steps.no_changes.outputs.status != 'cancelled' - env: - GH_TOKEN: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }} - run: | - HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}" - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/${{ github.repository }}/statuses/$HEAD_SHA \ - -f state="${{ job.status }}" \ - -f target_url="${{ steps.evaluate_output.outputs.html_report_url }}" \ - -f description="Click Details to view the test report" \ - -f context="${{ matrix.connector }} Test Report" From c9ea6d370565f40eb8d1ac5dd0a93e17bf68fa4e Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:32:48 -0700 Subject: [PATCH 57/66] add explicit typing for messages --- airbyte_cdk/test/entrypoint_wrapper.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/airbyte_cdk/test/entrypoint_wrapper.py b/airbyte_cdk/test/entrypoint_wrapper.py index eec9e11e9..3c0bcdfed 100644 --- a/airbyte_cdk/test/entrypoint_wrapper.py +++ b/airbyte_cdk/test/entrypoint_wrapper.py @@ -319,7 +319,7 @@ def _run_command( parsed_args = AirbyteEntrypoint.parse_args(args) source_entrypoint = AirbyteEntrypoint(source) - messages = [] + messages: list[str] = [] uncaught_exception = None try: for message in source_entrypoint.run(parsed_args): @@ -334,8 +334,10 @@ def _run_command( captured_logs = log_capture_buffer.getvalue().split("\n")[:-1] parent_logger.removeHandler(stream_handler) - - return EntrypointOutput(messages + captured_logs, uncaught_exception=uncaught_exception) + return EntrypointOutput( + messages=messages + captured_logs, + uncaught_exception=uncaught_exception, + ) def discover( From d7b6bae79436e757489726ff731ff35138545e5d Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:39:17 -0700 Subject: [PATCH 58/66] remove stray quote --- .github/workflows/connector-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index 4e359554a..aa4e523ae 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -85,7 +85,7 @@ jobs: - connector: source-pokeapi cdk_extra: n/a - name: "${{ needs.cdk_changes.outputs[matrix.cdk_extra] == 'false' && 'Skipped Check' || 'Check' }}: '${{matrix.connector}}" + name: "${{ needs.cdk_changes.outputs[matrix.cdk_extra] == 'false' && 'Skipped Check' || 'Check' }}: ${{matrix.connector}}" if: needs.cdk_changes.outputs['src'] == 'true' permissions: checks: write From 488dba7be9138c46692e08b87e9d064c970ccc0e Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:46:19 -0700 Subject: [PATCH 59/66] chore: add setuptools explicitly --- poetry.lock | 354 +++++++++++++++++-------------------------------- pyproject.toml | 1 + 2 files changed, 119 insertions(+), 236 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6bb7ae666..92fda9c50 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -7,7 +7,7 @@ description = "Happy Eyeballs for asyncio" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, @@ -20,7 +20,7 @@ description = "Async http client/server framework (asyncio)" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8"}, {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5"}, @@ -111,7 +111,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -120,7 +120,7 @@ description = "aiosignal: a list of registered asynchronous callbacks" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, @@ -136,7 +136,6 @@ description = "Declares the Airbyte Protocol using Python Dataclasses. Dataclass optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "airbyte_protocol_models_dataclasses-0.15.0-py3-none-any.whl", hash = "sha256:0fe8d7c2863c348b350efcf5f1af5872dc9071060408285e4708d97a9be5e2fb"}, {file = "airbyte_protocol_models_dataclasses-0.15.0.tar.gz", hash = "sha256:a5bad4ee7ae0a04f1436967b7afd3306d28e1cd2e5acedf0cce588f0c80ed001"}, @@ -149,7 +148,6 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -162,7 +160,6 @@ description = "Unicode to ASCII transliteration" optional = false python-versions = ">=3.3" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4"}, {file = "anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730"}, @@ -175,7 +172,6 @@ description = "High level compatibility layer for multiple asynchronous event lo optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, @@ -189,7 +185,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -199,7 +195,7 @@ description = "Timeout context manager for asyncio programs" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"vector-db-based\" and python_version < \"3.11\"" +markers = "extra == \"vector-db-based\" and python_version == \"3.10\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, @@ -212,7 +208,6 @@ description = "reference implementation of PEP 3156" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"}, {file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"}, @@ -227,7 +222,6 @@ description = "PEP 224 implementation" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "attributes-doc-0.4.0.tar.gz", hash = "sha256:b1576c94a714e9fc2c65c47cf10d0c8e1a5f7c4f5ae7f69006be108d95cbfbfb"}, {file = "attributes_doc-0.4.0-py2.py3-none-any.whl", hash = "sha256:4c3007d9e58f3a6cb4b9c614c4d4ce2d92161581f28e594ddd8241cc3a113bdd"}, @@ -240,19 +234,18 @@ description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "avro" @@ -261,7 +254,7 @@ description = "Avro is a serialization and RPC framework." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "avro-1.12.0-py2.py3-none-any.whl", hash = "sha256:9a255c72e1837341dd4f6ff57b2b6f68c0f0cecdef62dd04962e10fd33bec05b"}, {file = "avro-1.12.0.tar.gz", hash = "sha256:cad9c53b23ceed699c7af6bddced42e2c572fd6b408c257a7d4fc4e8cf2e2d6b"}, @@ -278,7 +271,6 @@ description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -291,7 +283,7 @@ description = "Screen-scraping library" optional = true python-versions = ">=3.6.0" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, @@ -314,7 +306,6 @@ description = "When they're not builtins, they're boltons." optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "boltons-25.0.0-py3-none-any.whl", hash = "sha256:dc9fb38bf28985715497d1b54d00b62ea866eca3938938ea9043e254a3a6ca62"}, {file = "boltons-25.0.0.tar.gz", hash = "sha256:e110fbdc30b7b9868cb604e3f71d4722dd8f4dcb4a5ddd06028ba8f1ab0b5ace"}, @@ -327,7 +318,6 @@ description = "Bash style brace expander." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6"}, {file = "bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6"}, @@ -340,7 +330,6 @@ description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, @@ -353,7 +342,6 @@ description = "Composable complex class support for attrs and dataclasses." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0"}, {file = "cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85"}, @@ -368,8 +356,8 @@ typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_ver bson = ["pymongo (>=4.4.0)"] cbor2 = ["cbor2 (>=5.4.6)"] msgpack = ["msgpack (>=1.0.5)"] -msgspec = ["msgspec (>=0.18.5)"] -orjson = ["orjson (>=3.9.2)"] +msgspec = ["msgspec (>=0.18.5) ; implementation_name == \"cpython\""] +orjson = ["orjson (>=3.9.2) ; implementation_name == \"cpython\""] pyyaml = ["pyyaml (>=6.0)"] tomlkit = ["tomlkit (>=0.11.8)"] ujson = ["ujson (>=5.7.0)"] @@ -381,7 +369,6 @@ description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, @@ -394,7 +381,7 @@ description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and platform_python_implementation != \"PyPy\"" +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -475,7 +462,7 @@ description = "Universal encoding detector for Python 3" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, @@ -488,7 +475,6 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -591,7 +577,6 @@ description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -607,7 +592,7 @@ description = "" optional = true python-versions = ">=3.7,<4.0" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "cohere-4.21-py3-none-any.whl", hash = "sha256:5eb81db62e78b3156e734421cc3e657054f9d9f1d68b9f38cf48fe3a8ae40dbc"}, {file = "cohere-4.21.tar.gz", hash = "sha256:f611438f409dfc5d5a0a153a585349f5a80b169c7102b5994d9999ecf8440866"}, @@ -628,7 +613,7 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main", "dev"] -markers = "(platform_system == \"Windows\" or sys_platform == \"win32\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -641,7 +626,7 @@ description = "Python library for calculating contours of 2D quadrilateral grids optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"}, {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"}, @@ -716,7 +701,6 @@ description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, @@ -786,7 +770,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cramjam" @@ -795,7 +779,7 @@ description = "Thin Python bindings to de/compression algorithms in Rust" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "cramjam-2.9.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8e82464d1e00fbbb12958999b8471ba5e9f3d9711954505a0a7b378762332e6f"}, {file = "cramjam-2.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d2df8a6511cc08ef1fccd2e0c65e2ebc9f57574ec8376052a76851af5398810"}, @@ -899,7 +883,6 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7"}, {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1"}, @@ -942,10 +925,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] +pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -958,7 +941,7 @@ description = "Composable style cycles" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -975,7 +958,7 @@ description = "Easily serialize dataclasses to and from JSON." optional = true python-versions = "<4.0,>=3.7" groups = ["main"] -markers = "(extra == \"vector-db-based\" or extra == \"file-based\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "extra == \"vector-db-based\" or extra == \"file-based\"" files = [ {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, @@ -992,7 +975,6 @@ description = "A command line utility to check for unused, missing and transitiv optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "deptry-0.23.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1f2a6817a37d76e8f6b667381b7caf6ea3e6d6c18b5be24d36c625f387c79852"}, {file = "deptry-0.23.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:9601b64cc0aed42687fdd5c912d5f1e90d7f7333fb589b14e35bfdfebae866f3"}, @@ -1026,7 +1008,6 @@ description = "Filesystem-like pathing and searching for dictionaries" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576"}, {file = "dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e"}, @@ -1039,7 +1020,6 @@ description = "Dynamic version generation" optional = false python-versions = ">=3.5" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041"}, {file = "dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4"}, @@ -1055,7 +1035,7 @@ description = "Emoji for Python" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b"}, {file = "emoji-2.14.1.tar.gz", hash = "sha256:f8c50043d79a2c1410ebfae833ae1868d5941a67a6cd4d18377e2eb0bd79346b"}, @@ -1071,7 +1051,7 @@ description = "An implementation of lxml.xmlfile for the standard library" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, @@ -1084,7 +1064,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -1100,7 +1080,7 @@ description = "Fast read/write of AVRO files" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"vector-db-based\" or extra == \"file-based\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "extra == \"vector-db-based\" or extra == \"file-based\"" files = [ {file = "fastavro-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:0e08964b2e9a455d831f2557402a683d4c4d45206f2ab9ade7c69d3dc14e0e58"}, {file = "fastavro-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:401a70b1e5c7161420c6019e0c8afa88f7c8a373468591f5ec37639a903c2509"}, @@ -1142,7 +1122,7 @@ description = "Infer file type and MIME type of any file/buffer. No external dep optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, @@ -1155,7 +1135,6 @@ description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -1173,7 +1152,7 @@ description = "Tools to manipulate font files" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "fonttools-4.55.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b332ea7b7f5f3d99f9bc5a28a23c3824ae72711abf7c4e1d62fa21699fdebe7"}, {file = "fonttools-4.55.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d8f925909256e62152e7c3e192655dbca3ab8c3cdef7d7b436732727e80feb6"}, @@ -1228,18 +1207,18 @@ files = [ ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] -type1 = ["xattr"] +type1 = ["xattr ; sys_platform == \"darwin\""] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] name = "freezegun" @@ -1248,7 +1227,6 @@ description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -1264,7 +1242,7 @@ description = "A list-like structure which implements collections.abc.MutableSeq optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -1367,7 +1345,6 @@ description = "GenSON is a powerful, user-friendly JSON Schema generator." optional = false python-versions = "*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, @@ -1380,7 +1357,6 @@ description = "Google API client core library" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, @@ -1390,12 +1366,12 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, ] proto-plus = ">=1.22.3,<2.0.0" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" @@ -1403,7 +1379,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -1414,7 +1390,6 @@ description = "Google Authentication Library" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "google_auth-2.39.0-py2.py3-none-any.whl", hash = "sha256:0150b6711e97fb9f52fe599f55648950cc4540015565d8fbb31be2ad6e1548a2"}, {file = "google_auth-2.39.0.tar.gz", hash = "sha256:73222d43cdc35a3aeacbfdcaf73142a97839f10de930550d89ebfe1d0a00cde7"}, @@ -1428,11 +1403,11 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +pyjwt = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0)"] -testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] urllib3 = ["packaging", "urllib3"] [[package]] @@ -1442,7 +1417,6 @@ description = "Google Cloud Secret Manager API client library" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "google_cloud_secret_manager-2.23.3-py3-none-any.whl", hash = "sha256:fe06ebb2f71eb739ecc6c14ea9e8dafcb9bbc6123b78b2f8986ece6733d23a1a"}, {file = "google_cloud_secret_manager-2.23.3.tar.gz", hash = "sha256:598c4c0a9d10d49d500eb4aea3255dff250aa2f92c028f5c97e3b367f768c808"}, @@ -1462,7 +1436,6 @@ description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, @@ -1482,7 +1455,7 @@ description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"vector-db-based\" or extra == \"sql\") and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "(extra == \"vector-db-based\" or extra == \"sql\") and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -1570,7 +1543,6 @@ description = "IAM API client library" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351"}, {file = "grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20"}, @@ -1588,7 +1560,6 @@ description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, @@ -1653,7 +1624,6 @@ description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, @@ -1671,7 +1641,6 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1684,7 +1653,6 @@ description = "A minimal low-level HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1707,7 +1675,6 @@ description = "The next generation HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1720,7 +1687,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -1733,7 +1700,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1749,7 +1715,7 @@ description = "Read metadata from Python packages" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, @@ -1761,7 +1727,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" @@ -1770,7 +1736,6 @@ description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -1783,7 +1748,6 @@ description = "An ISO 8601 date/time/duration parser and formatter" optional = false python-versions = "*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, @@ -1799,7 +1763,6 @@ description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1818,7 +1781,6 @@ description = "Lightweight pipelining with Python functions" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, @@ -1831,7 +1793,6 @@ description = "Apply JSON-Patches (RFC 6902)" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1847,7 +1808,6 @@ description = "Identify specific nodes in a JSON document (RFC 6901)" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1860,7 +1820,6 @@ description = "An implementation of JSON Reference for Python" optional = false python-versions = "*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "jsonref-0.2-py3-none-any.whl", hash = "sha256:b1e82fa0b62e2c2796a13e5401fe51790b248f6d9bf9d7212a3e31a3501b291f"}, {file = "jsonref-0.2.tar.gz", hash = "sha256:f3c45b121cf6257eafabdc3a8008763aed1cd7da06dbabc59a9e4d2a5e4e6697"}, @@ -1873,7 +1832,6 @@ description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, @@ -1894,7 +1852,7 @@ description = "A fast implementation of the Cassowary constraint solver" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, @@ -1985,7 +1943,7 @@ description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.8.1" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "langchain-0.1.16-py3-none-any.whl", hash = "sha256:bc074cc5e51fad79b9ead1572fc3161918d0f614a6c8f0460543d505ad249ac7"}, {file = "langchain-0.1.16.tar.gz", hash = "sha256:b6bce78f8c071baa898884accfff15c3d81da2f0dd86c20e2f4c80b41463f49f"}, @@ -2014,11 +1972,11 @@ cli = ["typer (>=0.9.0,<0.10.0)"] cohere = ["cohere (>=4,<6)"] docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] embeddings = ["sentence-transformers (>=2,<3)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<6)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.0.2,<0.1)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<6)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.0.2,<0.1)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0) ; python_full_version >= \"3.8.1\" and python_version < \"3.12\"", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0) ; python_full_version >= \"3.8.1\" and python_full_version != \"3.9.7\" and python_version < \"4.0\"", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] javascript = ["esprima (>=4.0.1,<5.0.0)"] llms = ["clarifai (>=9.1.0)", "cohere (>=4,<6)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] -openai = ["openai (<2)", "tiktoken (>=0.3.2,<0.6.0)"] -qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] +openai = ["openai (<2)", "tiktoken (>=0.3.2,<0.6.0) ; python_version >= \"3.9\""] +qdrant = ["qdrant-client (>=1.3.1,<2.0.0) ; python_full_version >= \"3.8.1\" and python_version < \"3.12\""] text-helpers = ["chardet (>=5.1.0,<6.0.0)"] [[package]] @@ -2028,7 +1986,7 @@ description = "Community contributed LangChain integrations." optional = true python-versions = "<4.0,>=3.8.1" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "langchain_community-0.0.32-py3-none-any.whl", hash = "sha256:406977009999952d0705de3806de2b4867e9bb8eda8ca154a59c7a8ed58da38d"}, {file = "langchain_community-0.0.32.tar.gz", hash = "sha256:1510217d646c8380f54e9850351f6d2a0b0dd73c501b666c6f4b40baa8160b29"}, @@ -2047,7 +2005,7 @@ tenacity = ">=8.1.0,<9.0.0" [package.extras] cli = ["typer (>=0.9.0,<0.10.0)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cloudpickle (>=2.0.0)", "cohere (>=4,<5)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "friendli-client (>=1.2.4,<2.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hdbcli (>=2.19.21,<3.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "httpx (>=0.24.1,<0.25.0)", "httpx-sse (>=0.4.0,<0.5.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "nvidia-riva-client (>=2.14.0,<3.0.0)", "oci (>=2.119.1,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "premai (>=0.3.25,<0.4.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pyjwt (>=2.8.0,<3.0.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "tidb-vector (>=0.0.3,<1.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "tree-sitter (>=0.20.2,<0.21.0)", "tree-sitter-languages (>=1.8.0,<2.0.0)", "upstash-redis (>=0.15.0,<0.16.0)", "vdms (>=0.0.20,<0.0.21)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cloudpickle (>=2.0.0)", "cohere (>=4,<5)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "friendli-client (>=1.2.4,<2.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hdbcli (>=2.19.21,<3.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "httpx (>=0.24.1,<0.25.0)", "httpx-sse (>=0.4.0,<0.5.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "nvidia-riva-client (>=2.14.0,<3.0.0)", "oci (>=2.119.1,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "premai (>=0.3.25,<0.4.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pyjwt (>=2.8.0,<3.0.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0) ; python_full_version >= \"3.8.1\" and python_version < \"3.12\"", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0) ; python_full_version >= \"3.8.1\" and python_full_version != \"3.9.7\" and python_version < \"4.0\"", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "tidb-vector (>=0.0.3,<1.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "tree-sitter (>=0.20.2,<0.21.0)", "tree-sitter-languages (>=1.8.0,<2.0.0)", "upstash-redis (>=0.15.0,<0.16.0)", "vdms (>=0.0.20,<0.0.21)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] [[package]] name = "langchain-core" @@ -2056,7 +2014,6 @@ description = "Building applications with LLMs through composability" optional = true python-versions = "<4.0,>=3.8.1" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "langchain_core-0.1.42-py3-none-any.whl", hash = "sha256:c5653ffa08a44f740295c157a24c0def4a753333f6a2c41f76bf431cd00be8b5"}, {file = "langchain_core-0.1.42.tar.gz", hash = "sha256:40751bf60ea5d8e2b2efe65290db434717ee3834870c002e40e2811f09d814e6"}, @@ -2080,7 +2037,7 @@ description = "LangChain text splitting utilities" optional = true python-versions = "<4.0,>=3.8.1" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "langchain_text_splitters-0.0.2-py3-none-any.whl", hash = "sha256:13887f32705862c1e1454213cb7834a63aae57c26fcd80346703a1d09c46168d"}, {file = "langchain_text_splitters-0.0.2.tar.gz", hash = "sha256:ac8927dc0ba08eba702f6961c9ed7df7cead8de19a9f7101ab2b5ea34201b3c1"}, @@ -2099,7 +2056,7 @@ description = "Language detection library ported from Google's language-detectio optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "langdetect-1.0.9-py2-none-any.whl", hash = "sha256:7cbc0746252f19e76f77c0b1690aadf01963be835ef0cd4b56dddf2a8f1dfc2a"}, {file = "langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0"}, @@ -2115,7 +2072,6 @@ description = "Client library to connect to the LangSmith LLM Tracing and Evalua optional = true python-versions = "<4.0,>=3.8.1" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15"}, {file = "langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a"}, @@ -2141,7 +2097,6 @@ description = "Links recognition library with FULL unicode support." optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, @@ -2163,7 +2118,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li optional = true python-versions = ">=3.6" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, @@ -2319,7 +2274,7 @@ description = "Python implementation of John Gruber's Markdown." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, @@ -2336,7 +2291,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -2364,7 +2318,6 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -2436,7 +2389,7 @@ description = "A lightweight library for converting complex datatypes to and fro optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"vector-db-based\" or extra == \"file-based\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "extra == \"vector-db-based\" or extra == \"file-based\"" files = [ {file = "marshmallow-3.25.1-py3-none-any.whl", hash = "sha256:ec5d00d873ce473b7f2ffcb7104286a376c354cab0c2fa12f5573dab03e87210"}, {file = "marshmallow-3.25.1.tar.gz", hash = "sha256:f4debda3bb11153d81ac34b0d582bf23053055ee11e791b54b4b35493468040a"}, @@ -2457,7 +2410,7 @@ description = "Python plotting package" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6"}, {file = "matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e"}, @@ -2516,7 +2469,6 @@ description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -2529,7 +2481,6 @@ description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, @@ -2550,7 +2501,6 @@ description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -2563,7 +2513,6 @@ description = "A memory profiler for Python applications" optional = false python-versions = ">=3.7.0" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "memray-1.15.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9b623c0c651d611dd068236566a8a202250e3d59307c3a3f241acc47835e73eb"}, {file = "memray-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74765f92887b7eed152e3b9f14c147c43bf0247417b18c7ea0dec173cd01633c"}, @@ -2617,10 +2566,10 @@ textual = ">=0.41.0" [package.extras] benchmark = ["asv"] -dev = ["Cython", "IPython", "asv", "black", "bump2version", "check-manifest", "flake8", "furo", "greenlet", "ipython", "isort", "mypy", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools", "sphinx", "sphinx-argparse", "textual (>=0.43,!=0.65.2,!=0.66)", "towncrier"] +dev = ["Cython", "IPython", "asv", "black", "bump2version", "check-manifest", "flake8", "furo", "greenlet ; python_version < \"3.14\"", "ipython", "isort", "mypy", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools ; python_version >= \"3.12\"", "sphinx", "sphinx-argparse", "textual (>=0.43,!=0.65.2,!=0.66)", "towncrier"] docs = ["IPython", "bump2version", "furo", "sphinx", "sphinx-argparse", "towncrier"] lint = ["black", "check-manifest", "flake8", "isort", "mypy"] -test = ["Cython", "greenlet", "ipython", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools", "textual (>=0.43,!=0.65.2,!=0.66)"] +test = ["Cython", "greenlet ; python_version < \"3.14\"", "ipython", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools ; python_version >= \"3.12\"", "textual (>=0.43,!=0.65.2,!=0.66)"] [[package]] name = "multidict" @@ -2629,7 +2578,7 @@ description = "multidict implementation" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -2735,7 +2684,6 @@ description = "Optional static typing for Python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, @@ -2800,7 +2748,7 @@ files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -markers = {main = "(extra == \"vector-db-based\" or extra == \"file-based\") and (python_version <= \"3.11\" or python_version >= \"3.12\")", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} +markers = {main = "extra == \"vector-db-based\" or extra == \"file-based\""} [[package]] name = "nltk" @@ -2809,7 +2757,6 @@ description = "Natural Language Toolkit" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"}, {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"}, @@ -2836,7 +2783,6 @@ description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2883,7 +2829,7 @@ description = "Python client library for the OpenAI API" optional = true python-versions = ">=3.7.1" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "openai-0.27.9-py3-none-any.whl", hash = "sha256:6a3cf8e276d1a6262b50562fbc0cba7967cfebb78ed827d375986b48fdad6475"}, {file = "openai-0.27.9.tar.gz", hash = "sha256:b687761c82f5ebb6f61efc791b2083d2d068277b94802d4d1369efe39851813d"}, @@ -2916,7 +2862,7 @@ description = "A Python library to read/write Excel 2010 xlsx/xlsm files" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, @@ -2932,7 +2878,6 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, @@ -3022,7 +2967,6 @@ description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, @@ -3035,7 +2979,6 @@ description = "Powerful data structures for data analysis, time series, and stat optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, @@ -3070,9 +3013,9 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -3110,7 +3053,7 @@ description = "Type annotations for pandas" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "pandas_stubs-2.2.3.241126-py3-none-any.whl", hash = "sha256:74aa79c167af374fe97068acc90776c0ebec5266a6e5c69fe11e9c2cf51f2267"}, {file = "pandas_stubs-2.2.3.241126.tar.gz", hash = "sha256:cf819383c6d9ae7d4dabf34cd47e1e45525bb2f312e6ad2939c2c204cb708acd"}, @@ -3127,7 +3070,6 @@ description = "Bring colors to your terminal." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, @@ -3140,7 +3082,7 @@ description = "A wrapper around the pdftoppm and pdftocairo command line tools t optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "pdf2image-1.16.3-py3-none-any.whl", hash = "sha256:b6154164af3677211c22cbb38b2bd778b43aca02758e962fe1e231f6d3b0e380"}, {file = "pdf2image-1.16.3.tar.gz", hash = "sha256:74208810c2cef4d9e347769b8e62a52303982ddb4f2dfd744c7ab4b940ae287e"}, @@ -3156,7 +3098,7 @@ description = "PDF parser and analyzer" optional = true python-versions = ">=3.6" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "pdfminer.six-20221105-py3-none-any.whl", hash = "sha256:1eaddd712d5b2732f8ac8486824533514f8ba12a0787b3d5fe1e686cd826532d"}, {file = "pdfminer.six-20221105.tar.gz", hash = "sha256:8448ab7b939d18b64820478ecac5394f482d7a79f5f7eaa7703c6c959c175e1d"}, @@ -3178,7 +3120,6 @@ description = "API Documentation for Python Projects" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pdoc-15.0.1-py3-none-any.whl", hash = "sha256:fd437ab8eb55f9b942226af7865a3801e2fb731665199b74fd9a44737dbe20f9"}, {file = "pdoc-15.0.1.tar.gz", hash = "sha256:3b08382c9d312243ee6c2a1813d0ff517a6ab84d596fa2c6c6b5255b17c3d666"}, @@ -3196,7 +3137,7 @@ description = "Python Imaging Library (Fork)" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"vector-db-based\" or extra == \"file-based\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "extra == \"vector-db-based\" or extra == \"file-based\"" files = [ {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, @@ -3276,7 +3217,7 @@ docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -3286,7 +3227,6 @@ description = "A small Python package for determining appropriate platform-speci optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -3304,7 +3244,7 @@ description = "An open-source, interactive data visualization library for Python optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -3321,7 +3261,6 @@ description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -3338,7 +3277,6 @@ description = "A task runner that works well with poetry." optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "poethepoet-0.24.4-py3-none-any.whl", hash = "sha256:fb4ea35d7f40fe2081ea917d2e4102e2310fda2cde78974050ca83896e229075"}, {file = "poethepoet-0.24.4.tar.gz", hash = "sha256:ff4220843a87c888cbcb5312c8905214701d0af60ac7271795baa8369b428fef"}, @@ -3358,7 +3296,7 @@ description = "Accelerated property cache" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, @@ -3451,7 +3389,6 @@ description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, @@ -3470,7 +3407,6 @@ description = "" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7"}, {file = "protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d"}, @@ -3492,7 +3428,6 @@ description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, @@ -3524,7 +3459,7 @@ description = "Python library for Apache Arrow" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fc28912a2dc924dddc2087679cc8b7263accc71b9ff025a1362b004711661a69"}, {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fca15aabbe9b8355800d923cc2e82c8ef514af321e18b437c3d782aa884eaeec"}, @@ -3580,7 +3515,6 @@ description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -3593,7 +3527,6 @@ description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, @@ -3609,7 +3542,6 @@ description = "Python style guide checker" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -3622,7 +3554,7 @@ description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and platform_python_implementation != \"PyPy\"" +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -3635,7 +3567,6 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, @@ -3648,7 +3579,7 @@ typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -3657,7 +3588,6 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -3771,7 +3701,6 @@ description = "passive checker of Python programs" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -3784,7 +3713,6 @@ description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -3800,7 +3728,6 @@ description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, @@ -3819,7 +3746,6 @@ description = "A development tool to measure, monitor and analyze the memory beh optional = false python-versions = ">=3.6" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "Pympler-1.1-py3-none-any.whl", hash = "sha256:5b223d6027d0619584116a0cbc28e8d2e378f7a79c1e5e024f9ff3b673c58506"}, {file = "pympler-1.1.tar.gz", hash = "sha256:1eaa867cb8992c218430f1708fdaccda53df064144d1c5656b1e6f1ee6000424"}, @@ -3835,7 +3761,7 @@ description = "pyparsing module - Classes and methods to define and execute pars optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, @@ -3851,7 +3777,6 @@ description = "pyproject-flake8 (`pflake8`), a monkey patching wrapper to connec optional = false python-versions = ">=3.8.1" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyproject_flake8-6.1.0-py3-none-any.whl", hash = "sha256:86ea5559263c098e1aa4f866776aa2cf45362fd91a576b9fd8fbbbb55db12c4e"}, {file = "pyproject_flake8-6.1.0.tar.gz", hash = "sha256:6da8e5a264395e0148bc11844c6fb50546f1fac83ac9210f7328664135f9e70f"}, @@ -3868,7 +3793,6 @@ description = "Python Rate-Limiter using Leaky-Bucket Algorithm" optional = false python-versions = ">=3.8,<4.0" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyrate_limiter-3.1.1-py3-none-any.whl", hash = "sha256:c51906f1d51d56dc992ff6c26e8300e32151bc6cfa3e6559792e31971dfd4e2b"}, {file = "pyrate_limiter-3.1.1.tar.gz", hash = "sha256:2f57eda712687e6eccddf6afe8f8a15b409b97ed675fe64a626058f12863b7b7"}, @@ -3885,7 +3809,6 @@ description = "Persistent/Functional/Immutable data structures" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"}, {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"}, @@ -3928,7 +3851,7 @@ description = "Python-tesseract is a python wrapper for Google's Tesseract-OCR" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "pytesseract-0.3.10-py3-none-any.whl", hash = "sha256:8f22cc98f765bf13517ead0c70effedb46c153540d25783e04014f28b55a5fc6"}, {file = "pytesseract-0.3.10.tar.gz", hash = "sha256:f1c3a8b0f07fd01a1085d451f5b8315be6eec1d5577a6796d46dc7a62bd4120f"}, @@ -3945,7 +3868,6 @@ description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -3969,7 +3891,6 @@ description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, @@ -3989,7 +3910,6 @@ description = "pytest-httpserver is a httpserver for pytest" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_httpserver-1.1.1-py3-none-any.whl", hash = "sha256:aadc744bfac773a2ea93d05c2ef51fa23c087e3cc5dace3ea9d45cdd4bfe1fe8"}, {file = "pytest_httpserver-1.1.1.tar.gz", hash = "sha256:e5c46c62c0aa65e5d4331228cb2cb7db846c36e429c3e74ca806f284806bf7c6"}, @@ -4005,7 +3925,6 @@ description = "A simple plugin to use with pytest" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_memray-1.7.0-py3-none-any.whl", hash = "sha256:b896718c1adf6d0cd339dfaaaa5620f035c9919e1199a79b3453804a1254306f"}, {file = "pytest_memray-1.7.0.tar.gz", hash = "sha256:c18fa907d2210b42f4096c093e2d3416dfc002dcaa450ef3f9ba819bc3dd8f5f"}, @@ -4027,7 +3946,6 @@ description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -4046,7 +3964,7 @@ description = "Python binding for Rust's library for reading excel and odf file optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "python_calamine-0.2.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f292a03591b1cab1537424851b74baa33b0a55affc315248a7592ba3de1c3e83"}, {file = "python_calamine-0.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6cfbd23d1147f53fd70fddfb38af2a98896ecad069c9a4120e77358a6fc43b39"}, @@ -4157,7 +4075,6 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -4173,7 +4090,7 @@ description = "Create, read, and update Microsoft Word .docx files." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe"}, {file = "python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd"}, @@ -4190,7 +4107,7 @@ description = "ISO 639 language codes, names, and other associated information" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "python_iso639-2024.10.22-py3-none-any.whl", hash = "sha256:02d3ce2e01c6896b30b9cbbd3e1c8ee0d7221250b5d63ea9803e0d2a81fd1047"}, {file = "python_iso639-2024.10.22.tar.gz", hash = "sha256:750f21b6a0bc6baa24253a3d8aae92b582bf93aa40988361cd96852c2c6d9a52"}, @@ -4206,7 +4123,7 @@ description = "File type identification using libmagic" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, @@ -4219,7 +4136,7 @@ description = "Generate and manipulate Open XML PowerPoint (.pptx) files" optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "python-pptx-0.6.21.tar.gz", hash = "sha256:7798a2aaf89563565b3c7120c0acfe9aff775db0db3580544e3bf4840c2e378f"}, ] @@ -4236,7 +4153,7 @@ description = "Python library for the snappy compression library from Google" optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "python_snappy-0.7.3-py3-none-any.whl", hash = "sha256:074c0636cfcd97e7251330f428064050ac81a52c62ed884fc2ddebbb60ed7f50"}, {file = "python_snappy-0.7.3.tar.gz", hash = "sha256:40216c1badfb2d38ac781ecb162a1d0ec40f8ee9747e610bcfefdfa79486cee3"}, @@ -4252,7 +4169,6 @@ description = "Universally unique lexicographically sortable identifier" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, @@ -4268,7 +4184,6 @@ description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -4281,7 +4196,7 @@ description = "Python for Window Extensions" optional = false python-versions = "*" groups = ["dev"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and platform_system == \"Windows\"" +markers = "platform_system == \"Windows\"" files = [ {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, @@ -4310,7 +4225,6 @@ description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -4374,7 +4288,6 @@ description = "rapid fuzzy string matching" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rapidfuzz-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33"}, {file = "rapidfuzz-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19"}, @@ -4476,7 +4389,6 @@ description = "Alternative regular expression module, to replace re." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -4581,7 +4493,6 @@ description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -4604,7 +4515,6 @@ description = "A persistent cache for python requests" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603"}, {file = "requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1"}, @@ -4636,7 +4546,6 @@ description = "Mock out responses from the requests package" optional = false python-versions = ">=3.5" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, @@ -4655,7 +4564,6 @@ description = "A utility belt for advanced users of python-requests" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -4671,7 +4579,6 @@ description = "This is a small Python module for parsing Pip requirement files." optional = false python-versions = "<4.0,>=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requirements_parser-0.11.0-py3-none-any.whl", hash = "sha256:50379eb50311834386c2568263ae5225d7b9d0867fb55cf4ecc93959de2c2684"}, {file = "requirements_parser-0.11.0.tar.gz", hash = "sha256:35f36dc969d14830bf459803da84f314dc3d17c802592e9e970f63d0359e5920"}, @@ -4688,7 +4595,6 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar optional = false python-versions = ">=3.8.0" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, @@ -4709,7 +4615,6 @@ description = "Format click help output nicely with rich" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rich_click-1.8.8-py3-none-any.whl", hash = "sha256:205aabd5a98e64ab2c105dee9e368be27480ba004c7dfa2accd0ed44f9f1550e"}, {file = "rich_click-1.8.8.tar.gz", hash = "sha256:547c618dea916620af05d4a6456da797fbde904c97901f44d2f32f89d85d6c84"}, @@ -4731,7 +4636,6 @@ description = "Pure-Python RSA implementation" optional = false python-versions = "<4,>=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, @@ -4747,7 +4651,6 @@ description = "An extremely fast Python linter and code formatter, written in Ru optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, @@ -4776,7 +4679,7 @@ description = "A set of python modules for machine learning and data mining" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e"}, {file = "scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36"}, @@ -4832,7 +4735,7 @@ description = "Fundamental algorithms for scientific computing in Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "scipy-1.15.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1"}, {file = "scipy-1.15.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff"}, @@ -4882,7 +4785,7 @@ numpy = ">=1.23.5,<2.5" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "serpyco-rs" @@ -4891,7 +4794,6 @@ description = "" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "serpyco_rs-1.13.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e722b3053e627d8a304e462bce20cae1670a2c4b0ef875b84d0de0081bec4029"}, {file = "serpyco_rs-1.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f10e89c752ff78d720a42e026b0a9ada70717ad6306a9356f794280167d62bf"}, @@ -4942,25 +4844,24 @@ typing-extensions = "*" [[package]] name = "setuptools" -version = "78.1.1" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" -groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +groups = ["main", "dev"] files = [ - {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, - {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -4969,7 +4870,6 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -4982,7 +4882,6 @@ description = "Sniff out which async library your code is running under" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -4995,7 +4894,7 @@ description = "A modern CSS selector implementation for Beautiful Soup." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, @@ -5008,7 +4907,7 @@ description = "Database Abstraction Library" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"vector-db-based\" or extra == \"sql\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "extra == \"vector-db-based\" or extra == \"sql\"" files = [ {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, @@ -5105,7 +5004,7 @@ description = "Pretty-print tabular data" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -5121,7 +5020,6 @@ description = "Retry code until it succeeds" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, @@ -5138,7 +5036,6 @@ description = "Modern Text User Interface framework" optional = false python-versions = "<4.0.0,>=3.8.1" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f"}, {file = "textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399"}, @@ -5151,7 +5048,7 @@ rich = ">=13.3.3" typing-extensions = ">=4.4.0,<5.0.0" [package.extras] -syntax = ["tree-sitter (>=0.23.0)", "tree-sitter-bash (>=0.23.0)", "tree-sitter-css (>=0.23.0)", "tree-sitter-go (>=0.23.0)", "tree-sitter-html (>=0.23.0)", "tree-sitter-java (>=0.23.0)", "tree-sitter-javascript (>=0.23.0)", "tree-sitter-json (>=0.24.0)", "tree-sitter-markdown (>=0.3.0)", "tree-sitter-python (>=0.23.0)", "tree-sitter-regex (>=0.24.0)", "tree-sitter-rust (>=0.23.0)", "tree-sitter-sql (>=0.3.0)", "tree-sitter-toml (>=0.6.0)", "tree-sitter-xml (>=0.7.0)", "tree-sitter-yaml (>=0.6.0)"] +syntax = ["tree-sitter (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-bash (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-css (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-go (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-html (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-java (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-javascript (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-json (>=0.24.0) ; python_version >= \"3.9\"", "tree-sitter-markdown (>=0.3.0) ; python_version >= \"3.9\"", "tree-sitter-python (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-regex (>=0.24.0) ; python_version >= \"3.9\"", "tree-sitter-rust (>=0.23.0) ; python_version >= \"3.9\"", "tree-sitter-sql (>=0.3.0) ; python_version >= \"3.9\"", "tree-sitter-toml (>=0.6.0) ; python_version >= \"3.9\"", "tree-sitter-xml (>=0.7.0) ; python_version >= \"3.9\"", "tree-sitter-yaml (>=0.6.0) ; python_version >= \"3.9\""] [[package]] name = "threadpoolctl" @@ -5160,7 +5057,7 @@ description = "threadpoolctl" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, @@ -5173,7 +5070,7 @@ description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "tiktoken-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b07e33283463089c81ef1467180e3e00ab00d46c2c4bbcef0acab5f771d6695e"}, {file = "tiktoken-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9269348cb650726f44dd3bbb3f9110ac19a8dcc8f54949ad3ef652ca22a38e21"}, @@ -5256,7 +5153,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version < \"3.11\"", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} +markers = {main = "python_version == \"3.10\""} [[package]] name = "tqdm" @@ -5265,7 +5162,6 @@ description = "Fast, Extensible Progress Meter" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -5288,7 +5184,6 @@ description = "Typing stubs for cachetools" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "types-cachetools-5.5.0.20240820.tar.gz", hash = "sha256:b888ab5c1a48116f7799cd5004b18474cd82b5463acb5ffb2db2fc9c7b053bc0"}, {file = "types_cachetools-5.5.0.20240820-py3-none-any.whl", hash = "sha256:efb2ed8bf27a4b9d3ed70d33849f536362603a90b8090a328acf0cd42fda82e2"}, @@ -5301,7 +5196,6 @@ description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, @@ -5314,7 +5208,7 @@ description = "Typing stubs for pytz" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "types_pytz-2024.2.0.20241221-py3-none-any.whl", hash = "sha256:8fc03195329c43637ed4f593663df721fef919b60a969066e22606edf0b53ad5"}, {file = "types_pytz-2024.2.0.20241221.tar.gz", hash = "sha256:06d7cde9613e9f7504766a0554a270c369434b50e00975b3a4a0f6eed0f2c1a9"}, @@ -5327,7 +5221,6 @@ description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6"}, {file = "types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c"}, @@ -5340,7 +5233,6 @@ description = "Typing stubs for requests" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, @@ -5356,7 +5248,6 @@ description = "Typing stubs for setuptools" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "types_setuptools-75.8.2.20250305-py3-none-any.whl", hash = "sha256:ba80953fd1f5f49e552285c024f75b5223096a38a5138a54d18ddd3fa8f6a2d4"}, {file = "types_setuptools-75.8.2.20250305.tar.gz", hash = "sha256:a987269b49488f21961a1d99aa8d281b611625883def6392a93855b31544e405"}, @@ -5372,7 +5263,6 @@ description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, @@ -5385,7 +5275,7 @@ description = "Runtime inspection utilities for typing module." optional = true python-versions = "*" groups = ["main"] -markers = "(extra == \"vector-db-based\" or extra == \"file-based\") and (python_version <= \"3.11\" or python_version >= \"3.12\")" +markers = "extra == \"vector-db-based\" or extra == \"file-based\"" files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, @@ -5402,7 +5292,6 @@ description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, @@ -5415,7 +5304,6 @@ description = "Micro subset of unicode data files for linkify-it-py projects." optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, @@ -5431,7 +5319,7 @@ description = "A library that prepares raw documents for downstream ML tasks." optional = true python-versions = ">=3.7.0" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "unstructured-0.10.27-py3-none-any.whl", hash = "sha256:3a8a8e44302388ddc39c184059e8b4458f1cdc58032540b9af7d85f6c3eca3be"}, {file = "unstructured-0.10.27.tar.gz", hash = "sha256:f567b5c4385993a9ab48db5563dd7b413aac4f2002bb22e6250496ea8f440f5e"}, @@ -5513,7 +5401,7 @@ description = "Python-tesseract is a python wrapper for Google's Tesseract-OCR" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "unstructured.pytesseract-0.3.13-py3-none-any.whl", hash = "sha256:8001bc860470d56185176eb3ceb4623e888eba058ca3b30af79003784bc40e19"}, {file = "unstructured.pytesseract-0.3.13.tar.gz", hash = "sha256:ff2e6391496e457dbf4b4e327f4a4577cce18921ea6570dc74bd64381b10e963"}, @@ -5530,7 +5418,6 @@ description = "URL normalization for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, @@ -5546,14 +5433,13 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.9" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -5565,7 +5451,6 @@ description = "Wildcard/glob file name matcher." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a"}, {file = "wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a"}, @@ -5581,7 +5466,6 @@ description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -5600,7 +5484,6 @@ description = "Modern datetime library for Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "whenever-0.6.16-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:901783ba877b5d73ce5b1bc1697c6097a9ac14c43064788b24ec7dc75a85a90a"}, {file = "whenever-0.6.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d374cd750ea68adb4ad69d52aef3838eda38ae63183c6135b122772ac053c66"}, @@ -5685,7 +5568,7 @@ description = "A Python module for creating Excel XLSX files." optional = true python-versions = ">=3.6" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"file-based\"" +markers = "extra == \"file-based\"" files = [ {file = "XlsxWriter-3.2.0-py3-none-any.whl", hash = "sha256:ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e"}, {file = "XlsxWriter-3.2.0.tar.gz", hash = "sha256:9977d0c661a72866a61f9f7a809e25ebbb0fb7036baa3b9fe74afcfca6b3cb8c"}, @@ -5698,7 +5581,6 @@ description = "Makes working with XML feel like you are working with JSON" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, @@ -5711,7 +5593,7 @@ description = "Yet another URL library" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, @@ -5809,18 +5691,18 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and extra == \"vector-db-based\"" +markers = "extra == \"vector-db-based\"" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] @@ -5832,4 +5714,4 @@ vector-db-based = ["cohere", "langchain", "openai", "tiktoken"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "3462b02e03e9f7edd9dec23acde60ad4f7128ace08ce1787556aa0b4bcd25a86" +content-hash = "5180a321559042764244c1b0f8a3fb4b2032a14f386bcd643c19841a2c602aa2" diff --git a/pyproject.toml b/pyproject.toml index 14c5c7959..5059f7fee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ sqlalchemy = {version = "^2.0,!=2.0.36", optional = true } xmltodict = ">=0.13,<0.15" anyascii = "^0.3.2" whenever = "^0.6.16" +setuptools = "^80.9.0" # Some connectors depend on this as an undeclared transitive dependency (e.g. source-shopify) [tool.poetry.group.dev.dependencies] freezegun = "*" From 46ef98d25c94ad1b4c09357844fa1fbe1247917a Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 13:54:18 -0700 Subject: [PATCH 60/66] try build arg fix --- airbyte_cdk/utils/docker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index 231e8dab6..a2120709b 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -78,9 +78,10 @@ def _build_image( if build_args: for key, value in build_args.items(): if value is not None: - docker_args.append(f"--build-arg={key}={value}") + docker_args.extend(["--build-arg", f"{key}={value}"]) else: - docker_args.append(f"--build-arg={key}") + docker_args.extend(["--build-arg", key]) + docker_args.extend( [ "-t", From d319dbcb0242df70a3361c47661660dfbe04d36b Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 14:00:13 -0700 Subject: [PATCH 61/66] add guard statements for connector_name and connector_directory --- airbyte_cdk/utils/docker.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index a2120709b..88050d99f 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -173,6 +173,12 @@ def build_connector_image( if platform.machine().lower().startswith(("arm", "aarch")) else ArchEnum.AMD64 ) + if not connector_name: + raise ValueError("Connector name must be provided.") + if not connector_directory: + raise ValueError("Connector directory must be provided.") + if not connector_directory.exists(): + raise ValueError(f"Connector directory does not exist: {connector_directory}") connector_kebab_name = connector_name connector_dockerfile_dir = connector_directory / "build" / "docker" From cabe7c140eba57a034981ee1f18321f4123d54dc Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 14:04:10 -0700 Subject: [PATCH 62/66] add deptry exception for setuptools --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5059f7fee..bc48e6ac6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -225,6 +225,7 @@ DEP001 = [ # DEP002: Project should not contain unused dependencies. # https://deptry.com/rules-violations/#missing-dependencies-dep002 DEP002 = [ + "setuptools", # Incorrectly used without declaring in some connectors (e.g. source-shopify) "cryptography", # Constrained as transitive dependency due to a bug in newer versions "google-cloud-secret-manager", # Deptry can't detect that `google.cloud.secretmanager_v1` uses this package From beb96e4807f5fedbeb8409478fd27eaa114a4cd2 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 14:11:56 -0700 Subject: [PATCH 63/66] fix bug related to inability to scrape connector name from connector dir --- airbyte_cdk/test/standard_tests/docker_base.py | 8 ++++---- airbyte_cdk/utils/connector_paths.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/docker_base.py b/airbyte_cdk/test/standard_tests/docker_base.py index d87cc7552..c3ee1f060 100644 --- a/airbyte_cdk/test/standard_tests/docker_base.py +++ b/airbyte_cdk/test/standard_tests/docker_base.py @@ -131,14 +131,14 @@ def test_docker_image_build_and_spec( connector_image_override: str | None, ) -> None: """Run `docker_image` acceptance tests.""" - connector_root = self.get_connector_root_dir() + connector_root = self.get_connector_root_dir().absolute() metadata = MetadataFile.from_file(connector_root / "metadata.yaml") connector_image: str | None = connector_image_override if not connector_image: tag = "dev-latest" connector_image = build_connector_image( - connector_name=connector_root.name, + connector_name=connector_root.absolute().name, connector_directory=connector_root, metadata=metadata, tag=tag, @@ -192,7 +192,7 @@ def test_docker_image_build_and_check( if not connector_image: tag = "dev-latest" connector_image = build_connector_image( - connector_name=connector_root.name, + connector_name=connector_root.absolute().name, connector_directory=connector_root, metadata=metadata, tag=tag, @@ -269,7 +269,7 @@ def test_docker_image_build_and_read( tag = "dev-latest" connector_root = self.get_connector_root_dir() - connector_name = connector_root.name + connector_name = connector_root.absolute().name metadata = MetadataFile.from_file(connector_root / "metadata.yaml") connector_image: str | None = connector_image_override if not connector_image: diff --git a/airbyte_cdk/utils/connector_paths.py b/airbyte_cdk/utils/connector_paths.py index c05a195e3..86ce48e61 100644 --- a/airbyte_cdk/utils/connector_paths.py +++ b/airbyte_cdk/utils/connector_paths.py @@ -181,11 +181,11 @@ def find_connector_root(from_paths: list[Path]) -> Path: # Check if the manifest file exists in the current directory for parent in [path, *path.parents]: if (parent / METADATA_YAML).exists(): - return parent + return parent.absolute() if (parent / MANIFEST_YAML).exists(): - return parent + return parent.absolute() if (parent / ACCEPTANCE_TEST_CONFIG).exists(): - return parent + return parent.absolute() if parent.name == "airbyte_cdk": break From 1c9dc2e6ec08f5349cd8047d4ac9666205230ce0 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 15:16:48 -0700 Subject: [PATCH 64/66] fix: false-positive failure for reads expected to fail --- airbyte_cdk/test/standard_tests/source_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte_cdk/test/standard_tests/source_base.py b/airbyte_cdk/test/standard_tests/source_base.py index 2b1aa2829..cd65e6022 100644 --- a/airbyte_cdk/test/standard_tests/source_base.py +++ b/airbyte_cdk/test/standard_tests/source_base.py @@ -130,8 +130,8 @@ def test_basic_read( catalog=configured_catalog, ) - if not result.records: - raise AssertionError("Expected records but got none.") # noqa: TRY003 + if scenario.expected_outcome.expect_success() and not result.records: + raise AssertionError("Expected records but got none.") def test_fail_read_with_bad_catalog( self, From 5d21ad94a59d7143e673e34a7a644408ce96207a Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 15:17:10 -0700 Subject: [PATCH 65/66] remove duplicate workflow step --- .github/workflows/connector-tests.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/connector-tests.yml b/.github/workflows/connector-tests.yml index aa4e523ae..d8f31af09 100644 --- a/.github/workflows/connector-tests.yml +++ b/.github/workflows/connector-tests.yml @@ -76,8 +76,6 @@ jobs: cdk_extra: file-based - connector: destination-motherduck cdk_extra: sql - - connector: source-zendesk-support - cdk_extra: n/a - connector: source-amplitude cdk_extra: n/a - connector: source-intercom @@ -85,6 +83,10 @@ jobs: - connector: source-pokeapi cdk_extra: n/a + # CDK Tests cannot build the Connector object (constructor args not optional). + # - connector: source-zendesk-support + # cdk_extra: n/a + name: "${{ needs.cdk_changes.outputs[matrix.cdk_extra] == 'false' && 'Skipped Check' || 'Check' }}: ${{matrix.connector}}" if: needs.cdk_changes.outputs['src'] == 'true' permissions: @@ -196,15 +198,6 @@ jobs: run: | poetry run airbyte-cdk connector test ${{ matrix.connector }} - - name: Test Connector using Dev CDK [Python Connectors] - if: env.CONNECTOR_LANGUAGE == 'python' - working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }} - env: - GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }} - POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0" - run: | - poetry run airbyte-cdk connector test - - name: Container Build and Test [All Connectors] if: steps.no_changes.outputs.status != 'cancelled' working-directory: airbyte-python-cdk From 9fcc198b91bb489dde2dd175c687bab8f2a8faed Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Fri, 13 Jun 2025 15:21:05 -0700 Subject: [PATCH 66/66] fix: resolve false-positive failures when discover doesn't trigger expected failure (due to static schemas) --- airbyte_cdk/test/standard_tests/source_base.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/airbyte_cdk/test/standard_tests/source_base.py b/airbyte_cdk/test/standard_tests/source_base.py index cd65e6022..f995f4682 100644 --- a/airbyte_cdk/test/standard_tests/source_base.py +++ b/airbyte_cdk/test/standard_tests/source_base.py @@ -4,6 +4,8 @@ from dataclasses import asdict from typing import TYPE_CHECKING +import pytest + from airbyte_cdk.models import ( AirbyteMessage, AirbyteStream, @@ -59,6 +61,13 @@ def test_discover( scenario: ConnectorTestScenario, ) -> None: """Standard test for `discover`.""" + if scenario.expected_outcome.expect_exception(): + # If the scenario expects an exception, we can't ensure it specifically would fail + # in discover, because some discover implementations do not need to make a connection. + # We skip this test in that case. + pytest.skip("Skipping discover test for scenario that expects an exception.") + return + run_test_job( self.create_connector(scenario), "discover",