diff --git a/airbyte/__init__.py b/airbyte/__init__.py index 3a739ae8c..b4f6acea6 100644 --- a/airbyte/__init__.py +++ b/airbyte/__init__.py @@ -16,6 +16,7 @@ documents, exceptions, # noqa: ICN001 # No 'exc' alias for top-level module experimental, + records, results, secrets, sources, diff --git a/airbyte/_processors/sql/__init__.py b/airbyte/_processors/sql/__init__.py index 980745fe1..cf1d02f27 100644 --- a/airbyte/_processors/sql/__init__.py +++ b/airbyte/_processors/sql/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations +from airbyte._processors.sql import snowflakecortex from airbyte._processors.sql.snowflakecortex import ( SnowflakeCortexSqlProcessor, SnowflakeCortexTypeConverter, diff --git a/airbyte/caches/__init__.py b/airbyte/caches/__init__.py index 14e714bdd..ed6ab6833 100644 --- a/airbyte/caches/__init__.py +++ b/airbyte/caches/__init__.py @@ -3,7 +3,7 @@ from __future__ import annotations -from airbyte.caches import bigquery, duckdb, motherduck, postgres, snowflake, util +from airbyte.caches import base, bigquery, duckdb, motherduck, postgres, snowflake, util from airbyte.caches.base import CacheBase from airbyte.caches.bigquery import BigQueryCache from airbyte.caches.duckdb import DuckDBCache diff --git a/airbyte/sources/declarative.py b/airbyte/sources/declarative.py new file mode 100644 index 000000000..d96af8a1a --- /dev/null +++ b/airbyte/sources/declarative.py @@ -0,0 +1,104 @@ +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. +"""Support for declarative yaml source testing.""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import TYPE_CHECKING, cast + +from airbyte_cdk.entrypoint import AirbyteEntrypoint +from airbyte_cdk.sources.declarative.manifest_declarative_source import ManifestDeclarativeSource + +from airbyte._executor import Executor +from airbyte.exceptions import PyAirbyteInternalError +from airbyte.sources.base import Source + + +if TYPE_CHECKING: + from collections.abc import Iterator + + +class DeclarativeExecutor(Executor): + """An executor for declarative sources.""" + + def __init__( + self, + manifest: str | dict | Path, + ) -> None: + """Initialize a declarative executor. + + - If `manifest` is a path, it will be read as a json file. + - If `manifest` is a string, it will be parsed as an HTTP path. + - If `manifest` is a dict, it will be used as is. + """ + self._manifest_dict: dict + if isinstance(manifest, Path): + self._manifest_dict = cast(dict, json.loads(manifest.read_text())) + + elif isinstance(manifest, str): + # TODO: Implement HTTP path parsing + raise NotImplementedError("HTTP path parsing is not yet implemented.") + + elif isinstance(manifest, dict): + self._manifest_dict = manifest + + if not isinstance(self._manifest_dict, dict): + raise PyAirbyteInternalError(message="Manifest must be a dict.") + + self.declarative_source = ManifestDeclarativeSource(source_config=self._manifest_dict) + self.reported_version: str | None = None # TODO: Consider adding version detection + + def execute(self, args: list[str]) -> Iterator[str]: + """Execute the declarative source.""" + source_entrypoint = AirbyteEntrypoint(self.declarative_source) + parsed_args = source_entrypoint.parse_args(args) + yield from source_entrypoint.run(parsed_args) + + def ensure_installation(self, *, auto_fix: bool = True) -> None: + """No-op. The declarative source is included with PyAirbyte.""" + _ = auto_fix + pass + + def install(self) -> None: + """No-op. The declarative source is included with PyAirbyte.""" + pass + + def uninstall(self) -> None: + """No-op. The declarative source is included with PyAirbyte.""" + pass + + +class DeclarativeSource(Source): + """A declarative source using Airbyte's Yaml low-code/no-code framework.""" + + def __init__( + self, + manifest: str | dict | Path, + ) -> None: + """Initialize a declarative source. + + Sample usages: + ```python + manifest_path = "path/to/manifest.yaml" + + source_a = DeclarativeSource(manifest=Path(manifest_path)) + source_b = DeclarativeSource(manifest=Path(manifest_path).read_text()) + source_c = DeclarativeSource(manifest=yaml.load(Path(manifest_path).read_text())) + ``` + + Args: + manifest: The manifest for the declarative source. This can be a path to a yaml file, a + yaml string, or a dict. + """ + # TODO: Conform manifest to a dict or str (TBD) + self.manifest = manifest + + # Initialize the source using the base class implementation + super().__init__( + name="Declarative", # TODO: Get name from manifest + config={ # TODO: Put 'real' config here + "manifest": manifest, + }, + executor=DeclarativeExecutor(manifest), + ) diff --git a/airbyte/sources/registry.py b/airbyte/sources/registry.py index a14861637..8df8cd60e 100644 --- a/airbyte/sources/registry.py +++ b/airbyte/sources/registry.py @@ -3,8 +3,10 @@ import json import os +import warnings from copy import copy from dataclasses import dataclass +from enum import Enum from pathlib import Path import requests @@ -19,6 +21,89 @@ _REGISTRY_ENV_VAR = "AIRBYTE_LOCAL_REGISTRY" _REGISTRY_URL = "https://connectors.airbyte.com/files/registries/v0/oss_registry.json" +_LOWCODE_LABEL = "cdk:low-code" + +_LOWCODE_CONNECTORS_NEEDING_PYTHON = [ + "source-alpha-vantage", + "source-amplitude", + "source-apify-dataset", + "source-avni", + "source-bamboo-hr", + "source-braintree", + "source-braze", + "source-chargebee", + "source-close-com", + "source-commercetools", + "source-facebook-pages", + "source-fastbill", + "source-freshdesk", + "source-gitlab", + "source-gnews", + "source-greenhouse", + "source-instatus", + "source-intercom", + "source-iterable", + "source-jira", + "source-klaviyo", + "source-mailchimp", + "source-mixpanel", + "source-monday", + "source-my-hours", + "source-notion", + "source-okta", + "source-outreach", + "source-partnerstack", + "source-paypal-transaction", + "source-pinterest", + "source-pipedrive", + "source-pocket", + "source-posthog", + "source-prestashop", + "source-public-apis", + "source-qualaroo", + "source-quickbooks", + "source-railz", + "source-recharge", + "source-retently", + "source-rss", + "source-slack", + "source-surveymonkey", + "source-the-guardian-api", + "source-trello", + "source-typeform", + "source-xero", + "source-younium", + "source-zendesk-chat", + "source-zendesk-sunshine", + "source-zendesk-support", + "source-zendesk-talk", + "source-zenloop", + "source-zoom", +] +_LOWCODE_CONNECTORS_FAILING_VALIDATION = [ + "source-amazon-ads", +] +_LOWCODE_CONNECTORS_404 = [ + "source-unleash", +] +_LOWCODE_CONNECTORS_EXCLUDED: list[str] = [ + *_LOWCODE_CONNECTORS_FAILING_VALIDATION, + *_LOWCODE_CONNECTORS_404, + *_LOWCODE_CONNECTORS_NEEDING_PYTHON, +] + + +class InstallType(str, Enum): + YAML = "yaml" + PYTHON = "python" + DOCKER = "docker" + JAVA = "java" + + +class Language(str, Enum): + PYTHON = InstallType.PYTHON.value + JAVA = InstallType.JAVA.value + @dataclass class ConnectorMetadata: @@ -33,6 +118,12 @@ class ConnectorMetadata: pypi_package_name: str | None """The name of the PyPI package for the connector, if it exists.""" + language: Language | None + """The language of the connector.""" + + install_types: set[InstallType] + """The supported install types for the connector.""" + def _get_registry_url() -> str: if _REGISTRY_ENV_VAR in os.environ: @@ -43,14 +134,36 @@ def _get_registry_url() -> str: def _registry_entry_to_connector_metadata(entry: dict) -> ConnectorMetadata: name = entry["dockerRepository"].replace("airbyte/", "") + language: Language | None = None + if "language" in entry and entry["language"] is not None: + try: + language = Language(entry["language"]) + except Exception: + warnings.warn( + message=f"Invalid language for connector {name}: {entry['language']}", + stacklevel=2, + ) remote_registries: dict = entry.get("remoteRegistries", {}) pypi_registry: dict = remote_registries.get("pypi", {}) pypi_package_name: str = pypi_registry.get("packageName", None) pypi_enabled: bool = pypi_registry.get("enabled", False) + install_types: set[InstallType] = { + x + for x in [ + InstallType.DOCKER if entry.get("dockerImageTag") else None, + InstallType.PYTHON if pypi_enabled else None, + InstallType.JAVA if language == Language.JAVA else None, + InstallType.YAML if _LOWCODE_LABEL in entry.get("tags", []) else None, + ] + if x + } + return ConnectorMetadata( name=name, - latest_available_version=entry["dockerImageTag"], + latest_available_version=entry.get("dockerImageTag", None), pypi_package_name=pypi_package_name if pypi_enabled else None, + language=language, + install_types=install_types, ) @@ -114,11 +227,45 @@ def get_connector_metadata(name: str) -> ConnectorMetadata: return cache[name] -def get_available_connectors() -> list[str]: +def get_available_connectors(install_type: InstallType | str = InstallType.PYTHON) -> list[str]: """Return a list of all available connectors. Connectors will be returned in alphabetical order, with the standard prefix "source-". """ - return sorted( - conn.name for conn in _get_registry_cache().values() if conn.pypi_package_name is not None + if not isinstance(install_type, InstallType): + install_type = InstallType(install_type) + + if install_type == InstallType.PYTHON: + return sorted( + conn.name + for conn in _get_registry_cache().values() + if conn.pypi_package_name is not None + ) + + if install_type == InstallType.JAVA: + warnings.warn( + message="Java connectors are not yet supported.", + stacklevel=2, + ) + return sorted( + conn.name for conn in _get_registry_cache().values() if conn.language == Language.JAVA + ) + + if install_type == InstallType.DOCKER: + return sorted(conn.name for conn in _get_registry_cache().values()) + + if install_type == InstallType.YAML: + return sorted( + conn.name + for conn in _get_registry_cache().values() + if InstallType.YAML in conn.install_types + and conn.name not in _LOWCODE_CONNECTORS_EXCLUDED + ) + + # pragma: no cover # Should never be reached. + raise exc.PyAirbyteInputError( + message="Invalid install type.", + context={ + "install_type": install_type, + }, ) diff --git a/airbyte/sources/util.py b/airbyte/sources/util.py index 5b8b42aaa..92b9bb84e 100644 --- a/airbyte/sources/util.py +++ b/airbyte/sources/util.py @@ -7,13 +7,18 @@ import sys import tempfile import warnings +from json import JSONDecodeError from pathlib import Path -from typing import Any +from typing import Any, cast + +import requests +import yaml from airbyte import exceptions as exc from airbyte._executor import DockerExecutor, PathExecutor, VenvExecutor from airbyte._util.telemetry import EventState, log_install_state from airbyte.sources.base import Source +from airbyte.sources.declarative import DeclarativeExecutor from airbyte.sources.registry import ConnectorMetadata, get_connector_metadata @@ -45,7 +50,7 @@ def get_connector( # This non-public function includes the `docker_image` parameter, which is not exposed in the # public API. See the `experimental` module for more info. -def _get_source( # noqa: PLR0912, PLR0913 # Too many branches +def _get_source( # noqa: PLR0912, PLR0913, PLR0915 # Too complex name: str, config: dict[str, Any] | None = None, *, @@ -53,7 +58,8 @@ def _get_source( # noqa: PLR0912, PLR0913 # Too many branches version: str | None = None, pip_url: str | None = None, local_executable: Path | str | None = None, - docker_image: str | bool = False, + docker_image: bool | str = False, + source_manifest: bool | dict | Path | str = False, install_if_missing: bool = True, install_root: Path | None = None, ) -> Source: @@ -78,21 +84,36 @@ def _get_source( # noqa: PLR0912, PLR0913 # Too many branches to use the default image for the connector, or you can specify a custom image name. If `version` is specified and your image name does not already contain a tag (e.g. `my-image:latest`), the version will be appended as a tag (e.g. `my-image:0.1.0`). + source_manifest: If set, the connector will be executed based on a declarative Yaml + source definition. This input can be `True` to auto-download the yaml spec, `dict` + to accept a Python dictionary as the manifest, `Path` to pull a manifest from + the local file system, or `str` to pull the definition from a web URL. install_if_missing: Whether to install the connector if it is not available locally. This parameter is ignored when local_executable is set. install_root: (Optional.) The root directory where the virtual environment will be created. If not provided, the current working directory will be used. """ - if sum([bool(local_executable), bool(docker_image), bool(pip_url)]) > 1: + if ( + sum( + [ + bool(local_executable), + bool(docker_image), + bool(pip_url), + bool(source_manifest), + ] + ) + > 1 + ): raise exc.PyAirbyteInputError( message=( "You can only specify one of the settings: 'local_executable', 'docker_image', " - "or 'pip_url'." + "'pip_url', or 'source_manifest'." ), context={ "local_executable": local_executable, "docker_image": docker_image, "pip_url": pip_url, + "source_manifest": source_manifest, }, ) @@ -172,6 +193,49 @@ def _get_source( # noqa: PLR0912, PLR0913 # Too many branches ), ) + if source_manifest: + if source_manifest is True: + http_url = ( + "https://raw.githubusercontent.com/airbytehq/airbyte/master/airbyte-integrations" + f"/connectors/{name}/{name.replace('-', '_')}/manifest.yaml" + ) + print("Installing connector from YAML manifest:", http_url) + # Download the file + response = requests.get(http_url) + response.raise_for_status() # Raise an exception if the download failed + + if "class_name:" in response.text: + raise exc.AirbyteConnectorInstallationError( + message=( + "The provided manifest requires additional code files (`class_name` key " + "detected). This feature is not compatible with the declarative YAML " + "executor. To use this executor, please try again with the Python " + "executor." + ), + connector_name=name, + context={ + "manifest_url": http_url, + }, + ) + + try: + source_manifest = cast(dict, yaml.safe_load(response.text)) + except JSONDecodeError as ex: + raise exc.AirbyteConnectorInstallationError( + connector_name=name, + context={ + "manifest_url": http_url, + }, + ) from ex + + return Source( + name=name, + config=config, + streams=streams, + executor=DeclarativeExecutor( + manifest=source_manifest, + ), + ) # else: we are installing a connector in a virtual environment: metadata: ConnectorMetadata | None = None diff --git a/examples/run_declarative_manifest_source.py b/examples/run_declarative_manifest_source.py new file mode 100644 index 000000000..80bc511e8 --- /dev/null +++ b/examples/run_declarative_manifest_source.py @@ -0,0 +1,181 @@ +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. +"""A test of PyAirbyte calling a declarative manifest. + +Usage (from PyAirbyte root directory): +> poetry run python examples/run_declarative_manifest_source.py + +""" + +from __future__ import annotations + +from typing import cast + +import yaml + +from airbyte.experimental import get_source + + +# Copy-pasted from the Builder "Yaml" view: +SOURCE_MANIFEST_TEXT = """ +version: 0.85.0 + +type: DeclarativeSource + +check: + type: CheckStream + stream_names: + - characters + +definitions: + streams: + characters: + type: DeclarativeStream + name: characters + primary_key: + - id + retriever: + type: SimpleRetriever + requester: + $ref: '#/definitions/base_requester' + path: character/ + http_method: GET + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - type: HttpResponseFilter + action: SUCCESS + error_message_contains: There is nothing here + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - results + paginator: + type: DefaultPaginator + page_token_option: + type: RequestOption + inject_into: request_parameter + field_name: page + pagination_strategy: + type: PageIncrement + start_from_page: 1 + schema_loader: + type: InlineSchemaLoader + schema: + $ref: '#/schemas/characters' + base_requester: + type: HttpRequester + url_base: https://rickandmortyapi.com/api + +streams: + - $ref: '#/definitions/streams/characters' + +spec: + type: Spec + connection_specification: + type: object + $schema: http://json-schema.org/draft-07/schema# + required: [] + properties: {} + additionalProperties: true + +metadata: + autoImportSchema: + characters: true + +schemas: + characters: + type: object + $schema: http://json-schema.org/schema# + properties: + type: + type: + - string + - 'null' + created: + type: + - string + - 'null' + episode: + type: + - array + - 'null' + items: + type: + - string + - 'null' + gender: + type: + - string + - 'null' + id: + type: number + image: + type: + - string + - 'null' + location: + type: + - object + - 'null' + properties: + name: + type: + - string + - 'null' + url: + type: + - string + - 'null' + name: + type: + - string + - 'null' + origin: + type: + - object + - 'null' + properties: + name: + type: + - string + - 'null' + url: + type: + - string + - 'null' + species: + type: + - string + - 'null' + status: + type: + - string + - 'null' + url: + type: + - string + - 'null' + required: + - id + additionalProperties: true +""" + +source_manifest_dict = cast(dict, yaml.safe_load(SOURCE_MANIFEST_TEXT)) + +print("Installing declarative source...") +source = get_source( + "source-rick-and-morty", + config={}, + source_manifest=source_manifest_dict, +) +source.check() +source.select_all_streams() + +result = source.read() + +for name, records in result.streams.items(): + print(f"Stream {name}: {len(records)} records") diff --git a/examples/run_downloadable_yaml_source.py b/examples/run_downloadable_yaml_source.py new file mode 100644 index 000000000..3285761fc --- /dev/null +++ b/examples/run_downloadable_yaml_source.py @@ -0,0 +1,53 @@ +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. +"""A test of PyAirbyte calling a declarative manifest. + +Usage (from PyAirbyte root directory): +> poetry run python examples/run_downloadable_yaml_source.py + +""" + +from __future__ import annotations + +import airbyte as ab +from airbyte.experimental import get_source + + +yaml_connectors: list[str] = ab.get_available_connectors(install_type="yaml") + +print(f"Downloadable yaml sources ({len(yaml_connectors)}): \n- " + "\n- ".join(yaml_connectors)) + +failed_installs: dict[str, list[str]] = {} + +for yaml_connector in yaml_connectors: + try: + _ = get_source(yaml_connector, source_manifest=True) + except Exception as ex: + exception_type = type(ex).__name__ + if exception_type in failed_installs: + failed_installs[exception_type].append(yaml_connector) + else: + failed_installs[exception_type] = [yaml_connector] + +# Print any connector failures, grouped by the error message +for error, connectors_failed in failed_installs.items(): + print( + f"\nInstallation Errors ({len(failed_installs)}): {error}\n- " + + "\n- ".join(connectors_failed) + + "\n" + ) + +print("Running declarative source...") +source = get_source( + "source-pokeapi", + config={ + "pokemon_name": "ditto", + }, + source_manifest=True, +) +source.check() +source.select_all_streams() + +result = source.read() + +for name, records in result.streams.items(): + print(f"Stream {name}: {len(records)} records") diff --git a/poetry.lock b/poetry.lock index f0aa7ab52..d47070b41 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,21 +32,22 @@ dev = ["pylint (==3.1.0)"] [[package]] name = "airbyte-cdk" -version = "0.81.8" +version = "1.2.1" description = "A framework for writing Airbyte Connectors." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "airbyte_cdk-0.81.8-py3-none-any.whl", hash = "sha256:1f826715e99b190b0581f0ce5192bd7e5eae69133e77a242a339a4227df02642"}, - {file = "airbyte_cdk-0.81.8.tar.gz", hash = "sha256:8854a899c9a4fabd2143b86befece8fd62130ffd74049b8c6fb8ac67c7c1da54"}, + {file = "airbyte_cdk-1.2.1-py3-none-any.whl", hash = "sha256:ca60ae569cdb8360daac2f428efb52591a34fb959ab48498a6996788ade8af24"}, + {file = "airbyte_cdk-1.2.1.tar.gz", hash = "sha256:da958afe1a08701a5db47786f865aa889003f77d4863e60384a363845ee78042"}, ] [package.dependencies] -airbyte-protocol-models = "*" +airbyte-protocol-models = ">=0.9.0,<1.0" backoff = "*" cachetools = "*" +cryptography = ">=42.0.5,<43.0.0" Deprecated = ">=1.2,<1.3" -dpath = ">=2.0.1,<2.1.0" +dpath = ">=2.1.6,<3.0.0" genson = "1.2.2" isodate = ">=0.6.1,<0.7.0" Jinja2 = ">=3.1.2,<3.2.0" @@ -55,8 +56,10 @@ jsonschema = ">=3.2.0,<3.3.0" langchain_core = "0.1.42" pendulum = "<3.0.0" pydantic = ">=1.10.8,<2.0.0" +pyjwt = ">=2.8.0,<3.0.0" pyrate-limiter = ">=3.1.0,<3.2.0" python-dateutil = "*" +pytz = "2024.1" PyYAML = ">=6.0.1,<7.0.0" requests = "*" requests_cache = "*" @@ -83,22 +86,19 @@ pydantic = ">=1.9.2,<2.0.0" [[package]] name = "airbyte-source-faker" -version = "6.0.1" +version = "6.1.2" description = "Source implementation for fake but realistic looking data." optional = false -python-versions = "*" +python-versions = "<3.12,>=3.9" files = [ - {file = "airbyte-source-faker-6.0.1.tar.gz", hash = "sha256:8173a48551fbfe0eb6e9c331fec650fa490f283736aef0d58e2f14e55f8cf90a"}, - {file = "airbyte_source_faker-6.0.1-py3-none-any.whl", hash = "sha256:622cd123589218cffe69755727addfe85873d7563002cf8d5f949586604e0d9f"}, + {file = "airbyte_source_faker-6.1.2-py3-none-any.whl", hash = "sha256:7447c5ce9448b40e679cb5cbea93df36ef22e05f8f934605f8d68f02c11c8d40"}, + {file = "airbyte_source_faker-6.1.2.tar.gz", hash = "sha256:a6fd5c7f37890d95cbb5567595069ca0f6b3cc41e46169933f1f35d3250d2b47"}, ] [package.dependencies] -airbyte-cdk = ">=0.2,<1.0" +airbyte-cdk = ">=0.73.0,<2.0" mimesis = "6.1.1" -[package.extras] -tests = ["pytest (>=6.2,<7.0)", "pytest-mock (>=3.6.1,<3.7.0)", "requests-mock (>=1.9.3,<1.10.0)"] - [[package]] name = "airbyte-source-pokeapi" version = "0.2.0" @@ -206,13 +206,13 @@ ujson = ["ujson (>=5.7.0)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -391,63 +391,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.1" +version = "7.5.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, - {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, - {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, - {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, - {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, - {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, - {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, - {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, - {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, - {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, - {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, - {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, - {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, - {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, + {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, + {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, + {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, + {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, + {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, + {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, + {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, + {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, + {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, + {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, + {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, + {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, ] [package.extras] @@ -541,34 +541,35 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] name = "docker" -version = "7.0.0" +version = "7.1.0" description = "A Python library for the Docker Engine API." optional = false python-versions = ">=3.8" files = [ - {file = "docker-7.0.0-py3-none-any.whl", hash = "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b"}, - {file = "docker-7.0.0.tar.gz", hash = "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3"}, + {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, + {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, ] [package.dependencies] -packaging = ">=14.0" pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} requests = ">=2.26.0" urllib3 = ">=1.26.0" [package.extras] +dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] +docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] ssh = ["paramiko (>=2.4.3)"] websockets = ["websocket-client (>=1.3.0)"] [[package]] name = "dpath" -version = "2.0.8" +version = "2.1.6" description = "Filesystem-like pathing and searching for dictionaries" optional = false python-versions = ">=3.7" files = [ - {file = "dpath-2.0.8-py3-none-any.whl", hash = "sha256:f92f595214dd93a00558d75d4b858beee519f4cffca87f02616ad6cd013f3436"}, - {file = "dpath-2.0.8.tar.gz", hash = "sha256:a3440157ebe80d0a3ad794f1b61c571bef125214800ffdb9afc9424e8250fe9b"}, + {file = "dpath-2.1.6-py3-none-any.whl", hash = "sha256:31407395b177ab63ef72e2f6ae268c15e938f2990a8ecf6510f5686c02b6db73"}, + {file = "dpath-2.1.6.tar.gz", hash = "sha256:f1e07c72e8605c6a9e80b64bc8f42714de08a789c7de417e49c3f87a19692e47"}, ] [[package]] @@ -956,18 +957,18 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] [[package]] name = "googleapis-common-protos" -version = "1.63.0" +version = "1.63.1" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e"}, - {file = "googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632"}, + {file = "googleapis-common-protos-1.63.1.tar.gz", hash = "sha256:c6442f7a0a6b2a80369457d79e6672bb7dcbaab88e0848302497e3ec80780a6a"}, + {file = "googleapis_common_protos-1.63.1-py2.py3-none-any.whl", hash = "sha256:0e1c2cdfcbc354b76e4a211a35ea35d6926a835cba1377073c4861db904a1877"}, ] [package.dependencies] grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<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,<5.0.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<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,<6.0.0.dev0" [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] @@ -1278,13 +1279,13 @@ extended-testing = ["jinja2 (>=3,<4)"] [[package]] name = "langsmith" -version = "0.1.60" +version = "0.1.67" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langsmith-0.1.60-py3-none-any.whl", hash = "sha256:3c3520ea473de0a984237b3e9d638fdf23ef3acc5aec89a42e693225e72d6120"}, - {file = "langsmith-0.1.60.tar.gz", hash = "sha256:6a145b5454437f9e0f81525f23c4dcdbb8c07b1c91553b8f697456c418d6a599"}, + {file = "langsmith-0.1.67-py3-none-any.whl", hash = "sha256:7eb2e1c1b375925ff47700ed8071e10c15e942e9d1d634b4a449a9060364071a"}, + {file = "langsmith-0.1.67.tar.gz", hash = "sha256:149558669a2ac4f21471cd964e61072687bba23b7c1ccb51f190a8f59b595b39"}, ] [package.dependencies] @@ -1680,17 +1681,20 @@ xml = ["lxml (>=4.8.0)"] [[package]] name = "pandas-stubs" -version = "2.2.2.240514" +version = "2.2.2.240603" description = "Type annotations for pandas" optional = false python-versions = ">=3.9" files = [ - {file = "pandas_stubs-2.2.2.240514-py3-none-any.whl", hash = "sha256:5d6f64d45a98bc94152a0f76fa648e598cd2b9ba72302fd34602479f0c391a53"}, - {file = "pandas_stubs-2.2.2.240514.tar.gz", hash = "sha256:85b20da44a62c80eb8389bcf4cbfe31cce1cafa8cca4bf1fc75ec45892e72ce8"}, + {file = "pandas_stubs-2.2.2.240603-py3-none-any.whl", hash = "sha256:e08ce7f602a4da2bff5a67475ba881c39f2a4d4f7fccc1cba57c6f35a379c6c0"}, + {file = "pandas_stubs-2.2.2.240603.tar.gz", hash = "sha256:2dcc86e8fa6ea41535a4561c1f08b3942ba5267b464eff2e99caeee66f9e4cd1"}, ] [package.dependencies] -numpy = {version = ">=1.26.0", markers = "python_version < \"3.13\""} +numpy = [ + {version = ">=1.23.5", markers = "python_version >= \"3.9\" and python_version < \"3.12\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\" and python_version < \"3.13\""}, +] types-pytz = ">=2022.1.1" [[package]] @@ -2242,13 +2246,13 @@ cli = ["click (>=5.0)"] [[package]] name = "python-ulid" -version = "2.5.0" +version = "2.6.0" description = "Universally unique lexicographically sortable identifier" optional = false python-versions = ">=3.9" files = [ - {file = "python_ulid-2.5.0-py3-none-any.whl", hash = "sha256:f5abfc3bbab8476abaaf8cec534f2285383586a1e2f8118831eba4d962b4fa55"}, - {file = "python_ulid-2.5.0.tar.gz", hash = "sha256:d000bc05bf3e7f4a56507f72b02bcbb0cb0170da102cf17af39c59ea946050dc"}, + {file = "python_ulid-2.6.0-py3-none-any.whl", hash = "sha256:b47cc7a427b82f7526af96385d7702685df808e9b4922523dd5988a3ba98a89d"}, + {file = "python_ulid-2.6.0.tar.gz", hash = "sha256:904e19093dd6578a5ce01a8274e3e228d556d47be3bda328da2d3601c5240c4f"}, ] [package.extras] @@ -2575,28 +2579,28 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.4.4" +version = "0.4.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"}, - {file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"}, - {file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"}, - {file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"}, - {file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"}, - {file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"}, + {file = "ruff-0.4.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e089371c67892a73b6bb1525608e89a2aca1b77b5440acf7a71dda5dac958f9e"}, + {file = "ruff-0.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:10f973d521d910e5f9c72ab27e409e839089f955be8a4c8826601a6323a89753"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c3d110970001dfa494bcd95478e62286c751126dfb15c3c46e7915fc49694f"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa9773c6c00f4958f73b317bc0fd125295110c3776089f6ef318f4b775f0abe4"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07fc80bbb61e42b3b23b10fda6a2a0f5a067f810180a3760c5ef1b456c21b9db"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fa4dafe3fe66d90e2e2b63fa1591dd6e3f090ca2128daa0be33db894e6c18648"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7c0083febdec17571455903b184a10026603a1de078428ba155e7ce9358c5f6"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad1b20e66a44057c326168437d680a2166c177c939346b19c0d6b08a62a37589"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf5d818553add7511c38b05532d94a407f499d1a76ebb0cad0374e32bc67202"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50e9651578b629baec3d1513b2534de0ac7ed7753e1382272b8d609997e27e83"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8874a9df7766cb956b218a0a239e0a5d23d9e843e4da1e113ae1d27ee420877a"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b9de9a6e49f7d529decd09381c0860c3f82fa0b0ea00ea78409b785d2308a567"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:13a1768b0691619822ae6d446132dbdfd568b700ecd3652b20d4e8bc1e498f78"}, + {file = "ruff-0.4.7-py3-none-win32.whl", hash = "sha256:769e5a51df61e07e887b81e6f039e7ed3573316ab7dd9f635c5afaa310e4030e"}, + {file = "ruff-0.4.7-py3-none-win_amd64.whl", hash = "sha256:9e3ab684ad403a9ed1226894c32c3ab9c2e0718440f6f50c7c5829932bc9e054"}, + {file = "ruff-0.4.7-py3-none-win_arm64.whl", hash = "sha256:10f2204b9a613988e3484194c2c9e96a22079206b22b787605c255f130db5ed7"}, + {file = "ruff-0.4.7.tar.gz", hash = "sha256:2331d2b051dc77a289a653fcc6a42cce357087c5975738157cd966590b18b5e1"}, ] [[package]] @@ -2918,13 +2922,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.1" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, + {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, ] [[package]] @@ -3089,4 +3093,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "82693b2dd7ad744ac885b722e61eb22ca61bba293b0e06b9d808339a46dabbaa" +content-hash = "09d2f91cad4deb7bb2c1b4cc87375b69d7792a4fa324de8d04d67256d47f7f47" diff --git a/pyproject.toml b/pyproject.toml index cb20ff4ef..9f3cb17ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ enable = true [tool.poetry.dependencies] python = ">=3.9,<4.0" -airbyte-cdk = "^0.81.6" +airbyte-cdk = "^1.2.1" duckdb = "^1.0.0" duckdb-engine = "^0.13.0" google-auth = ">=2.27.0,<3.0" @@ -58,7 +58,7 @@ ruff = "^0.4.1" types-jsonschema = "^4.20.0.0" types-requests = "2.31.0.4" freezegun = "^1.4.0" -airbyte-source-faker = "^6.0.0" +airbyte-source-faker = { version = "^6.1.2", python = "<3.12" } tomli = "^2.0" responses = "^0.25.0" airbyte-source-pokeapi = "^0.2.0" diff --git a/tests/conftest.py b/tests/conftest.py index 0e176b24f..845166edd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,7 @@ import warnings from pathlib import Path +import airbyte import docker import psycopg2 as psycopg import pytest @@ -225,21 +226,31 @@ def new_postgres_cache(): postgres.remove() -@pytest.fixture(autouse=True) +@pytest.fixture(autouse=False) def source_test_registry(monkeypatch): """ - Set environment variables for the test source. - - These are applied to this test file only. + Mock the registry to return our custom registry containing the 'source-test' connector. This means the normal registry is not usable. Expect AirbyteConnectorNotRegisteredError for other connectors. """ - env_vars = { - "AIRBYTE_LOCAL_REGISTRY": LOCAL_TEST_REGISTRY_URL, - } - for key, value in env_vars.items(): - monkeypatch.setenv(key, value) + + # Define the mock function + def mock_get_registry_cache(): + return LOCAL_TEST_REGISTRY_URL + + # Replace _get_registry_url() with the mock function + monkeypatch.setattr( + airbyte.sources.registry, "_get_registry_url", mock_get_registry_cache + ) + + # reset the registry cache + airbyte.sources.registry.__cache = None + + yield + + # reset the registry cache (clean up) + airbyte.sources.registry.__cache = None @pytest.fixture(autouse=True) diff --git a/tests/integration_tests/test_lowcode_connectors.py b/tests/integration_tests/test_lowcode_connectors.py new file mode 100644 index 000000000..c20208236 --- /dev/null +++ b/tests/integration_tests/test_lowcode_connectors.py @@ -0,0 +1,78 @@ +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. +from __future__ import annotations + +from pathlib import Path + +import airbyte +import jsonschema +import pytest +from airbyte import exceptions as exc +from airbyte.experimental import get_source +from airbyte.sources.registry import ( + _LOWCODE_CONNECTORS_404, + _LOWCODE_CONNECTORS_FAILING_VALIDATION, + _LOWCODE_CONNECTORS_NEEDING_PYTHON, +) + +UNIT_TEST_DB_PATH: Path = Path(".cache") / "unit_tests" / "test_db.duckdb" + + +@pytest.mark.parametrize( + "connector_name", + airbyte.get_available_connectors(install_type="yaml"), +) +def test_nocode_connectors_setup(connector_name: str) -> None: + """Test that all connectors can be initialized. + + If a specific connector fails to initialize, it should be added to the + hardcoded failure list (tested below). + """ + try: + _ = get_source( + name=connector_name, + source_manifest=True, + ) + except Exception as ex: + raise AssertionError( + f"Expected '{connector_name}' init success but got '{type(ex).__name__}': {ex}" + ) + + +@pytest.mark.parametrize( + "failure_group, exception_type", + [ + (_LOWCODE_CONNECTORS_FAILING_VALIDATION, jsonschema.exceptions.ValidationError), + (_LOWCODE_CONNECTORS_NEEDING_PYTHON, exc.AirbyteConnectorInstallationError), + (_LOWCODE_CONNECTORS_404, Exception), + ], +) +def test_expected_hardcoded_failures( + failure_group, + exception_type: str, +) -> None: + """Test that hardcoded failure groups are failing as expected. + + If a connector starts passing, this is probably good news, and it should be removed from the + hardcoded failure list. + """ + for connector_name in failure_group: + try: + _ = get_source( + name=connector_name, + source_manifest=True, + ) + except Exception as ex: + if isinstance(ex, exception_type): + pass + else: + raise AssertionError( + f"Expected connector {connector_name} to fail with" + f" '{exception_type}' but got '{type(ex).__name__}'. " + ) + else: + raise AssertionError( + f"Expected connector {connector_name} to fail with" + f" '{exception_type}' but got no exception. " + "This probably means you need to remove this connector from the" + " hardcoded failure list." + ) diff --git a/tests/integration_tests/test_source_test_fixture.py b/tests/integration_tests/test_source_test_fixture.py index bbb568490..fbabc839b 100644 --- a/tests/integration_tests/test_source_test_fixture.py +++ b/tests/integration_tests/test_source_test_fixture.py @@ -27,6 +27,11 @@ from sqlalchemy import column, text +@pytest.fixture(scope="function", autouse=True) +def autouse_source_test_registry(source_test_registry): + return + + def pop_internal_columns_from_dataset( dataset: datasets.DatasetBase | list[dict[str, Any]], /, @@ -93,11 +98,6 @@ def autouse_source_test_installation(source_test_installation): return -@pytest.fixture(scope="function", autouse=True) -def autouse_source_test_registry(source_test_registry): - return - - @pytest.fixture def source_test(source_test_env) -> ab.Source: return ab.get_source("source-test", config={"apiKey": "test"}) @@ -218,6 +218,8 @@ def test_version_enforcement( name="source-test", latest_available_version=latest_available_version, pypi_package_name="airbyte-source-test", + language=registry.Language.PYTHON, + install_types={registry.InstallType.PYTHON, registry.InstallType.DOCKER}, ) # We need to initialize the cache before we can patch it. diff --git a/tests/unit_tests/test_anonymous_usage_stats.py b/tests/unit_tests/test_anonymous_usage_stats.py index e29b10d8b..cb8558438 100644 --- a/tests/unit_tests/test_anonymous_usage_stats.py +++ b/tests/unit_tests/test_anonymous_usage_stats.py @@ -2,20 +2,23 @@ from __future__ import annotations import json -from pathlib import Path import re +from pathlib import Path from unittest.mock import MagicMock -import responses - import airbyte as ab import pytest - +import responses from airbyte._util import telemetry +@pytest.fixture(scope="function", autouse=True) +def autouse_source_test_registry(source_test_registry): + return + + @responses.activate -def test_telemetry_track(monkeypatch): +def test_telemetry_track(monkeypatch, source_test_registry): """Check that track is called and the correct data is sent.""" monkeypatch.delenv("DO_NOT_TRACK", raising=False) @@ -69,7 +72,11 @@ def test_telemetry_track(monkeypatch): @pytest.mark.parametrize("do_not_track", ["1", "true", "t"]) @responses.activate -def test_do_not_track(monkeypatch, do_not_track): +def test_do_not_track( + monkeypatch, + do_not_track, + source_test_registry, +): """Check that track is called and the correct data is sent.""" monkeypatch.setenv("DO_NOT_TRACK", do_not_track) diff --git a/tests/unit_tests/test_lowcode_connectors.py b/tests/unit_tests/test_lowcode_connectors.py new file mode 100644 index 000000000..0147b7216 --- /dev/null +++ b/tests/unit_tests/test_lowcode_connectors.py @@ -0,0 +1,31 @@ +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. +from __future__ import annotations + +from pathlib import Path + +import pytest +from airbyte._util.meta import is_windows +from airbyte.experimental import get_source + +UNIT_TEST_DB_PATH: Path = Path(".cache") / "unit_tests" / "test_db.duckdb" + + +@pytest.mark.parametrize( + "connector_name, config", + [ + ("source-pokeapi", {"pokemon_name": "ditto"}), + ], +) +@pytest.mark.xfail(condition=is_windows(), reason="Test expected to fail on Windows.") +def test_nocode_execution(connector_name: str, config: dict) -> None: + source = get_source( + name=connector_name, + config=config, + source_manifest=True, + ) + source.check() + source.select_all_streams() + source.read() + for name, records in source.read().streams.items(): + assert name + assert len(records) > 0