diff --git a/Makefile b/Makefile index 36f6f31..ab59696 100644 --- a/Makefile +++ b/Makefile @@ -67,9 +67,10 @@ check-test-coverage: @pyenv exec poetry run pytest -vv --cov=$(CODE_DIR) --cov-report=term-missing generate-docs: - @pyenv exec poetry run python -m pdoc --force --html ${CODE_DIR} -o docs - @mv docs/dynamicio/* docs - @rm -rf docs/dynamicio + @pyenv exec poetry run mkdocs build --strict + +serve-docs: + @pyenv exec poetry run mkdocs serve build-locally: @pyenv exec poetry build diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..650eae6 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,51 @@ +# API Reference + +Auto-generated API documentation for the `dynamicio` package, rendered by +[`mkdocstrings`](https://mkdocstrings.github.io/) directly from Python +docstrings. + +## `dynamicio.core` + +::: dynamicio.core + +## `dynamicio.config` + +::: dynamicio.config.io_config + +## `dynamicio.mixins` + +### S3 + +::: dynamicio.mixins.with_s3 + +### Postgres + +::: dynamicio.mixins.with_postgres + +### Athena + +::: dynamicio.mixins.with_athena + +### Kafka + +::: dynamicio.mixins.with_kafka + +### Local filesystem + +::: dynamicio.mixins.with_local + +### Utilities + +::: dynamicio.mixins.utils + +## Validation + +::: dynamicio.validations + +## Errors + +::: dynamicio.errors + +## CLI + +::: dynamicio.cli diff --git a/docs/cli.html b/docs/cli.html deleted file mode 100644 index 850cf46..0000000 --- a/docs/cli.html +++ /dev/null @@ -1,332 +0,0 @@ - - - - - - -dynamicio.cli API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.cli

-
-
-

Implements the dynamicio Command Line Interface (CLI).

-
- -Expand source code - -
"""Implements the dynamicio Command Line Interface (CLI)."""
-import argparse
-import glob
-import os
-import pprint
-from typing import Mapping, MutableMapping, Optional, Sequence
-
-import pandas as pd  # type: ignore
-import yaml
-
-from dynamicio.errors import InvalidDatasetTypeError
-
-
-def parse_args(args: Optional[Sequence] = None) -> argparse.Namespace:
-    """Arguments parser for dynamicio cli.py.
-
-    Args:
-        args: List of args to be parsed. Defaults to None, in which case
-            sys.argv[1:] is used.
-
-    Returns:
-        An instance of ArgumentParser populated with the provided args.
-    """
-    parser = argparse.ArgumentParser(prog="dynamicio", description="Generate dataset schemas")
-    group = parser.add_mutually_exclusive_group(required=True)
-    group.add_argument(
-        "-b",
-        "--batch",
-        action="store_true",
-        help="flag, used to generate multiple schemas provided a datasets directory.",
-    )
-    group.add_argument(
-        "-s",
-        "--single",
-        action="store_true",
-        help="flag, used to generate a schema provided a single dataset.",
-    )
-    parser.add_argument("-p", "--path", required=True, help="the path to the dataset/datasets-directory.", type=str)
-    parser.add_argument("-o", "--output", required=True, help="the path to the schemas output directory.", type=str)
-    return parser.parse_args(args)
-
-
-def generate_schema_for(dataset: str) -> Mapping:
-    """Generate a schema for a dataset.
-
-    Args:
-        dataset: The path to the dataset for which we want to generate a schema
-
-    Returns:
-        A dictionary containing the schema for the dataset, or None if the dataset is not valid.
-
-    Raises:
-        InvalidDatasetTypeError: If the dataset type is not supported by dynamicio.
-    """
-    dataset_name, file_type = os.path.splitext(os.path.basename(dataset))
-
-    if file_type == ".parquet":
-        df = pd.read_parquet(dataset)
-    elif file_type == ".csv":
-        df = pd.read_csv(dataset)
-    elif file_type == ".json":
-        df = pd.read_json(dataset)
-    elif file_type == ".h5":
-        df = pd.read_hdf(dataset)
-    else:
-        raise InvalidDatasetTypeError(dataset)
-
-    print(f"Generating schema for: {dataset}")
-    json_schema: MutableMapping = {"name": dataset_name, "columns": {}}
-    for column, d_type in zip(list(df.columns), list(df.dtypes)):
-        json_schema["columns"][column] = {"type": "", "validations": {}, "metrics": []}
-        json_schema["columns"][column]["type"] = d_type.name
-
-    return json_schema
-
-
-def main(args: argparse.Namespace):
-    """Main function for dynamicio cli.py.
-
-    Args:
-        args: Parsed args.
-    """
-    if args.batch:
-        dataset_files = glob.glob(os.path.join(args.path, "*.*"))
-        for dataset in dataset_files:
-            try:
-                json_schema = generate_schema_for(dataset)
-            except InvalidDatasetTypeError as exception:
-                print(f"Skipping {exception.message}! You may want to remove this file from the datasets directory")
-            else:
-                with open(os.path.join(args.output, f"{json_schema['name']}.yaml"), "w") as file:  # pylint: disable=unspecified-encoding]
-                    file.write("---\n")
-                    yaml.safe_dump(json_schema, file)
-
-    if args.single:
-        json_schema = generate_schema_for(str(args.path))
-        with open(os.path.join(args.output, f"{json_schema['name']}.yaml"), "w") as file:  # pylint: disable=unspecified-encoding]
-            file.write("---\n")
-            yaml.safe_dump(json_schema, file)
-        pprint.pprint(json_schema)
-
-
-def run():
-    """Entry point for the dynamicio cli.py."""
-    args = parse_args()
-    main(args)
-
-
-
-
-
-
-
-

Functions

-
-
-def generate_schema_for(dataset: str) ‑> Mapping -
-
-

Generate a schema for a dataset.

-

Args

-
-
dataset
-
The path to the dataset for which we want to generate a schema
-
-

Returns

-

A dictionary containing the schema for the dataset, or None if the dataset is not valid.

-

Raises

-
-
InvalidDatasetTypeError
-
If the dataset type is not supported by dynamicio.
-
-
- -Expand source code - -
def generate_schema_for(dataset: str) -> Mapping:
-    """Generate a schema for a dataset.
-
-    Args:
-        dataset: The path to the dataset for which we want to generate a schema
-
-    Returns:
-        A dictionary containing the schema for the dataset, or None if the dataset is not valid.
-
-    Raises:
-        InvalidDatasetTypeError: If the dataset type is not supported by dynamicio.
-    """
-    dataset_name, file_type = os.path.splitext(os.path.basename(dataset))
-
-    if file_type == ".parquet":
-        df = pd.read_parquet(dataset)
-    elif file_type == ".csv":
-        df = pd.read_csv(dataset)
-    elif file_type == ".json":
-        df = pd.read_json(dataset)
-    elif file_type == ".h5":
-        df = pd.read_hdf(dataset)
-    else:
-        raise InvalidDatasetTypeError(dataset)
-
-    print(f"Generating schema for: {dataset}")
-    json_schema: MutableMapping = {"name": dataset_name, "columns": {}}
-    for column, d_type in zip(list(df.columns), list(df.dtypes)):
-        json_schema["columns"][column] = {"type": "", "validations": {}, "metrics": []}
-        json_schema["columns"][column]["type"] = d_type.name
-
-    return json_schema
-
-
-
-def main(args: argparse.Namespace) -
-
-

Main function for dynamicio cli.py.

-

Args

-
-
args
-
Parsed args.
-
-
- -Expand source code - -
def main(args: argparse.Namespace):
-    """Main function for dynamicio cli.py.
-
-    Args:
-        args: Parsed args.
-    """
-    if args.batch:
-        dataset_files = glob.glob(os.path.join(args.path, "*.*"))
-        for dataset in dataset_files:
-            try:
-                json_schema = generate_schema_for(dataset)
-            except InvalidDatasetTypeError as exception:
-                print(f"Skipping {exception.message}! You may want to remove this file from the datasets directory")
-            else:
-                with open(os.path.join(args.output, f"{json_schema['name']}.yaml"), "w") as file:  # pylint: disable=unspecified-encoding]
-                    file.write("---\n")
-                    yaml.safe_dump(json_schema, file)
-
-    if args.single:
-        json_schema = generate_schema_for(str(args.path))
-        with open(os.path.join(args.output, f"{json_schema['name']}.yaml"), "w") as file:  # pylint: disable=unspecified-encoding]
-            file.write("---\n")
-            yaml.safe_dump(json_schema, file)
-        pprint.pprint(json_schema)
-
-
-
-def parse_args(args: Optional[Sequence] = None) ‑> argparse.Namespace -
-
-

Arguments parser for dynamicio cli.py.

-

Args

-
-
args
-
List of args to be parsed. Defaults to None, in which case -sys.argv[1:] is used.
-
-

Returns

-

An instance of ArgumentParser populated with the provided args.

-
- -Expand source code - -
def parse_args(args: Optional[Sequence] = None) -> argparse.Namespace:
-    """Arguments parser for dynamicio cli.py.
-
-    Args:
-        args: List of args to be parsed. Defaults to None, in which case
-            sys.argv[1:] is used.
-
-    Returns:
-        An instance of ArgumentParser populated with the provided args.
-    """
-    parser = argparse.ArgumentParser(prog="dynamicio", description="Generate dataset schemas")
-    group = parser.add_mutually_exclusive_group(required=True)
-    group.add_argument(
-        "-b",
-        "--batch",
-        action="store_true",
-        help="flag, used to generate multiple schemas provided a datasets directory.",
-    )
-    group.add_argument(
-        "-s",
-        "--single",
-        action="store_true",
-        help="flag, used to generate a schema provided a single dataset.",
-    )
-    parser.add_argument("-p", "--path", required=True, help="the path to the dataset/datasets-directory.", type=str)
-    parser.add_argument("-o", "--output", required=True, help="the path to the schemas output directory.", type=str)
-    return parser.parse_args(args)
-
-
-
-def run() -
-
-

Entry point for the dynamicio cli.py.

-
- -Expand source code - -
def run():
-    """Entry point for the dynamicio cli.py."""
-    args = parse_args()
-    main(args)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/config/index.html b/docs/config/index.html deleted file mode 100644 index 51aaa51..0000000 --- a/docs/config/index.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - -dynamicio.config API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config

-
-
-

Dynamicio config file handling routines.

-
- -Expand source code - -
"""Dynamicio config file handling routines."""
-
-from dynamicio.config import pydantic
-from dynamicio.config.io_config import IOConfig
-
-
-
-

Sub-modules

-
-
dynamicio.config.io_config
-
-

Implements the IOConfig class, generating objects used as a configuration parameter for the instantiation …

-
-
dynamicio.config.pydantic
-
-

Pydantic config models.

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/config/io_config.html b/docs/config/io_config.html deleted file mode 100644 index cde95ad..0000000 --- a/docs/config/io_config.html +++ /dev/null @@ -1,1016 +0,0 @@ - - - - - - -dynamicio.config.io_config API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.io_config

-
-
-

Implements the IOConfig class, generating objects used as a configuration parameter for the instantiation ofsrc.utils.dynamicio.dataio.DynamicDataIO objects.

-

The IOConfig object, essentially parses a yaml file that contains a set of input sources that will be processed by a -task, converting filtering and converting them into dictionaries.

-

For example, suppose an input.yaml file, containing:

-
READ_FROM_S3_CSV:
-  LOCAL:
-    type: "local"
-    local:
-      file_path: "[[ TEST_RESOURCES ]]/data/input/some_csv_to_read.csv"
-      file_type: "csv"
-  CLOUD:
-    type: "s3"
-    s3:
-      bucket: "[[ MOCK_BUCKET ]]"
-      file_path: "[[ MOCK_KEY ]]"
-      file_type: "csv"
-
-

would be loaded with:

-
input_sources_config = IOConfig(
-        "path_to/input.yaml",
-        env_identifier="CLOUD",
-        dynamic_vars=config_module
-    )
-
-

and:

-
input_sources_config.config
-
-

would return:

-
    {
-        "READ_FROM_S3_CSV": {
-            "LOCAL": {
-                "type": "local",
-                "local": {
-                    "file_path": f"{test_global_vars.TEST_RESOURCES}/data/input/some_csv_to_read.csv",
-                    "file_type": "csv",
-                },
-            },
-            "CLOUD": {
-                "type": "s3",
-                "s3": {
-                    "bucket": "mock-bucket",
-                    "file_path": "mock-key",
-                    "file_type": "csv"
-                }
-            },
-        }
-    }
-
-
- -Expand source code - -
"""Implements the `IOConfig` class, generating objects used as a configuration parameter for the instantiation of`src.utils.dynamicio.dataio.DynamicDataIO` objects.
-
-The `IOConfig` object, essentially parses a yaml file that contains a set of input sources that will be processed by a
-task, converting filtering and converting them into dictionaries.
-
-For example, suppose an `input.yaml` file, containing:
-
-    READ_FROM_S3_CSV:
-      LOCAL:
-        type: "local"
-        local:
-          file_path: "[[ TEST_RESOURCES ]]/data/input/some_csv_to_read.csv"
-          file_type: "csv"
-      CLOUD:
-        type: "s3"
-        s3:
-          bucket: "[[ MOCK_BUCKET ]]"
-          file_path: "[[ MOCK_KEY ]]"
-          file_type: "csv"
-
-would be loaded with:
-
-    input_sources_config = IOConfig(
-            "path_to/input.yaml",
-            env_identifier="CLOUD",
-            dynamic_vars=config_module
-        )
-
-and:
-
-    input_sources_config.config
-
-would return:
-
-        {
-            "READ_FROM_S3_CSV": {
-                "LOCAL": {
-                    "type": "local",
-                    "local": {
-                        "file_path": f"{test_global_vars.TEST_RESOURCES}/data/input/some_csv_to_read.csv",
-                        "file_type": "csv",
-                    },
-                },
-                "CLOUD": {
-                    "type": "s3",
-                    "s3": {
-                        "bucket": "mock-bucket",
-                        "file_path": "mock-key",
-                        "file_type": "csv"
-                    }
-                },
-            }
-        }
-"""
-__all__ = ["IOConfig", "SafeDynamicResourceLoader", "SafeDynamicSchemaLoader"]
-
-import re
-from types import ModuleType
-from typing import Any, List, MutableMapping
-
-import pydantic
-import yaml
-from magic_logger import logger
-
-from dynamicio.config.pydantic import BindingsYaml, IOEnvironment
-
-
-class SafeDynamicResourceLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) -> str:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-        return value
-
-
-class SafeDynamicSchemaLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) -> Any:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-            try:
-                value = float(value)
-                return value
-            except ValueError:
-                pass
-
-        return value
-
-
-class IOConfig:
-    """Generates an object that returns a sub-dictionary of the elements of that yaml file.
-
-    The file serves as a config for setting up DynamicDataIO objects. Requires a resources yaml file,
-    an ENVIRONMENT value {CLOUD or LOCAL} and a vars module.
-
-    Example:
-        input_sources_config = IOConfig(
-            "path_to/input.yaml",
-            env_identifier="CLOUD",
-            dynamic_vars=config_module
-        )
-    """
-
-    YAML_TAG = "tag:yaml.org,2002:str"
-    SafeDynamicResourceLoader.add_constructor(YAML_TAG, SafeDynamicResourceLoader.dyn_str_constructor)
-    SafeDynamicSchemaLoader.add_constructor(YAML_TAG, SafeDynamicSchemaLoader.dyn_value_constructor)
-
-    path_to_source_yaml: str
-    env_identifier: str
-    config: BindingsYaml
-
-    def __init__(self, path_to_source_yaml: str, env_identifier: str, dynamic_vars: ModuleType):
-        """Class constructor.
-
-        Args:
-            path_to_source_yaml: Absolute file path to yaml file containing source definitions
-            env_identifier: "LOCAL" or "CLOUD".
-            dynamic_vars: module containing values for dynamic values that the source yaml
-                may reference.
-        """
-        self.path_to_source_yaml = path_to_source_yaml
-        self.env_identifier = env_identifier
-        self.dynamic_vars = dynamic_vars
-        self.config = self._parse_sources_config()
-
-    def _parse_sources_config(self) -> BindingsYaml:
-        """Parses the yaml input and return a dictionary.
-
-        Returns:
-            A dictionary with the list of all file paths pointing to various input sources as those
-            are defined in their respective data/*.yaml files.
-        """
-        used_file_inputs = [self.path_to_source_yaml]
-        with open(self.path_to_source_yaml, "r") as stream:  # pylint: disable=unspecified-encoding]
-            logger.debug(f"Parsing {self.path_to_source_yaml}...")
-            data = yaml.load(stream, SafeDynamicResourceLoader.with_module(self.dynamic_vars))
-
-        # Load any file_path's found in schema definitions
-        for io_binding in data.values():
-            if isinstance(io_binding, MutableMapping) and io_binding.get("schema", {}).get("file_path"):
-                file_path = io_binding["schema"]["file_path"]
-                used_file_inputs.append(file_path)
-                # schema has `file_path`` in it
-                with open(file_path, "r", encoding="utf8") as stream:
-                    io_binding["schema"] = yaml.load(stream, SafeDynamicSchemaLoader.with_module(self.dynamic_vars))
-
-        try:
-            config = BindingsYaml(bindings=data)
-            config.update_config_refs()
-        except pydantic.ValidationError:
-            logger.exception(f"Error loading {data=!r}, {used_file_inputs=!r}")
-            raise
-        return config
-
-    @property
-    def sources(self) -> List[str]:
-        """Class property for easy access to a list of sources.
-
-        Returns:
-            All top level names of the available resources for the used resources yaml config.
-        """
-        return list(self.config.bindings.keys())
-
-    def get(self, source_key: str) -> IOEnvironment:
-        """A getter.
-
-        Args:
-            source_key: The name of the resource for which we want to create a config.
-
-        Returns:
-            A dictionary with the necessary fields for loading the data from a source.
-
-        Example:
-
-            Given:
-
-                VOYAGE_DATA:
-                  LOCAL:
-                    type: "local"
-                    local:
-                      file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-                      file_type: "parquet"
-                  CLOUD:
-                    type: "kafka"
-                    KAFKA:
-                      KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-                      KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-            If you do:
-
-                input_sources_config = IOConfig(
-                    "path_to/input.yaml",
-                    env_identifier="CLOUD",
-                    dynamic_vars=globals
-                )
-                voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-            then `voyage_data_cloud_mapping` is:
-
-                "KAFKA": {
-                    "KAFKA_SERVER": "mock-kafka-server",
-                    "KAFKA_TOPIC": "mock-kafka-topic"
-                }
-        """
-        return self.config.bindings[source_key].get_binding_for_environment(self.env_identifier)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class IOConfig -(path_to_source_yaml: str, env_identifier: str, dynamic_vars: module) -
-
-

Generates an object that returns a sub-dictionary of the elements of that yaml file.

-

The file serves as a config for setting up DynamicDataIO objects. Requires a resources yaml file, -an ENVIRONMENT value {CLOUD or LOCAL} and a vars module.

-

Example

-

input_sources_config = IOConfig( -"path_to/input.yaml", -env_identifier="CLOUD", -dynamic_vars=config_module -)

-

Class constructor.

-

Args

-
-
path_to_source_yaml
-
Absolute file path to yaml file containing source definitions
-
env_identifier
-
"LOCAL" or "CLOUD".
-
dynamic_vars
-
module containing values for dynamic values that the source yaml -may reference.
-
-
- -Expand source code - -
class IOConfig:
-    """Generates an object that returns a sub-dictionary of the elements of that yaml file.
-
-    The file serves as a config for setting up DynamicDataIO objects. Requires a resources yaml file,
-    an ENVIRONMENT value {CLOUD or LOCAL} and a vars module.
-
-    Example:
-        input_sources_config = IOConfig(
-            "path_to/input.yaml",
-            env_identifier="CLOUD",
-            dynamic_vars=config_module
-        )
-    """
-
-    YAML_TAG = "tag:yaml.org,2002:str"
-    SafeDynamicResourceLoader.add_constructor(YAML_TAG, SafeDynamicResourceLoader.dyn_str_constructor)
-    SafeDynamicSchemaLoader.add_constructor(YAML_TAG, SafeDynamicSchemaLoader.dyn_value_constructor)
-
-    path_to_source_yaml: str
-    env_identifier: str
-    config: BindingsYaml
-
-    def __init__(self, path_to_source_yaml: str, env_identifier: str, dynamic_vars: ModuleType):
-        """Class constructor.
-
-        Args:
-            path_to_source_yaml: Absolute file path to yaml file containing source definitions
-            env_identifier: "LOCAL" or "CLOUD".
-            dynamic_vars: module containing values for dynamic values that the source yaml
-                may reference.
-        """
-        self.path_to_source_yaml = path_to_source_yaml
-        self.env_identifier = env_identifier
-        self.dynamic_vars = dynamic_vars
-        self.config = self._parse_sources_config()
-
-    def _parse_sources_config(self) -> BindingsYaml:
-        """Parses the yaml input and return a dictionary.
-
-        Returns:
-            A dictionary with the list of all file paths pointing to various input sources as those
-            are defined in their respective data/*.yaml files.
-        """
-        used_file_inputs = [self.path_to_source_yaml]
-        with open(self.path_to_source_yaml, "r") as stream:  # pylint: disable=unspecified-encoding]
-            logger.debug(f"Parsing {self.path_to_source_yaml}...")
-            data = yaml.load(stream, SafeDynamicResourceLoader.with_module(self.dynamic_vars))
-
-        # Load any file_path's found in schema definitions
-        for io_binding in data.values():
-            if isinstance(io_binding, MutableMapping) and io_binding.get("schema", {}).get("file_path"):
-                file_path = io_binding["schema"]["file_path"]
-                used_file_inputs.append(file_path)
-                # schema has `file_path`` in it
-                with open(file_path, "r", encoding="utf8") as stream:
-                    io_binding["schema"] = yaml.load(stream, SafeDynamicSchemaLoader.with_module(self.dynamic_vars))
-
-        try:
-            config = BindingsYaml(bindings=data)
-            config.update_config_refs()
-        except pydantic.ValidationError:
-            logger.exception(f"Error loading {data=!r}, {used_file_inputs=!r}")
-            raise
-        return config
-
-    @property
-    def sources(self) -> List[str]:
-        """Class property for easy access to a list of sources.
-
-        Returns:
-            All top level names of the available resources for the used resources yaml config.
-        """
-        return list(self.config.bindings.keys())
-
-    def get(self, source_key: str) -> IOEnvironment:
-        """A getter.
-
-        Args:
-            source_key: The name of the resource for which we want to create a config.
-
-        Returns:
-            A dictionary with the necessary fields for loading the data from a source.
-
-        Example:
-
-            Given:
-
-                VOYAGE_DATA:
-                  LOCAL:
-                    type: "local"
-                    local:
-                      file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-                      file_type: "parquet"
-                  CLOUD:
-                    type: "kafka"
-                    KAFKA:
-                      KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-                      KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-            If you do:
-
-                input_sources_config = IOConfig(
-                    "path_to/input.yaml",
-                    env_identifier="CLOUD",
-                    dynamic_vars=globals
-                )
-                voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-            then `voyage_data_cloud_mapping` is:
-
-                "KAFKA": {
-                    "KAFKA_SERVER": "mock-kafka-server",
-                    "KAFKA_TOPIC": "mock-kafka-topic"
-                }
-        """
-        return self.config.bindings[source_key].get_binding_for_environment(self.env_identifier)
-
-

Class variables

-
-
var YAML_TAG
-
-
-
-
var configBindingsYaml
-
-
-
-
var env_identifier : str
-
-
-
-
var path_to_source_yaml : str
-
-
-
-
-

Instance variables

-
-
var sources : List[str]
-
-

Class property for easy access to a list of sources.

-

Returns

-

All top level names of the available resources for the used resources yaml config.

-
- -Expand source code - -
@property
-def sources(self) -> List[str]:
-    """Class property for easy access to a list of sources.
-
-    Returns:
-        All top level names of the available resources for the used resources yaml config.
-    """
-    return list(self.config.bindings.keys())
-
-
-
-

Methods

-
-
-def get(self, source_key: str) ‑> IOEnvironment -
-
-

A getter.

-

Args

-
-
source_key
-
The name of the resource for which we want to create a config.
-
-

Returns

-

A dictionary with the necessary fields for loading the data from a source.

-

Example

-

Given:

-
VOYAGE_DATA:
-  LOCAL:
-    type: "local"
-    local:
-      file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-      file_type: "parquet"
-  CLOUD:
-    type: "kafka"
-    KAFKA:
-      KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-      KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-

If you do:

-
input_sources_config = IOConfig(
-    "path_to/input.yaml",
-    env_identifier="CLOUD",
-    dynamic_vars=globals
-)
-voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-

then voyage_data_cloud_mapping is:

-
"KAFKA": {
-    "KAFKA_SERVER": "mock-kafka-server",
-    "KAFKA_TOPIC": "mock-kafka-topic"
-}
-
-
- -Expand source code - -
def get(self, source_key: str) -> IOEnvironment:
-    """A getter.
-
-    Args:
-        source_key: The name of the resource for which we want to create a config.
-
-    Returns:
-        A dictionary with the necessary fields for loading the data from a source.
-
-    Example:
-
-        Given:
-
-            VOYAGE_DATA:
-              LOCAL:
-                type: "local"
-                local:
-                  file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-                  file_type: "parquet"
-              CLOUD:
-                type: "kafka"
-                KAFKA:
-                  KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-                  KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-        If you do:
-
-            input_sources_config = IOConfig(
-                "path_to/input.yaml",
-                env_identifier="CLOUD",
-                dynamic_vars=globals
-            )
-            voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-        then `voyage_data_cloud_mapping` is:
-
-            "KAFKA": {
-                "KAFKA_SERVER": "mock-kafka-server",
-                "KAFKA_TOPIC": "mock-kafka-topic"
-            }
-    """
-    return self.config.bindings[source_key].get_binding_for_environment(self.env_identifier)
-
-
-
-
-
-class SafeDynamicResourceLoader -(stream) -
-
-

Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].

-

Dynamic variables defined in a provided module object.

-

Initialize the scanner.

-
- -Expand source code - -
class SafeDynamicResourceLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) -> str:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-        return value
-
-

Ancestors

-
    -
  • yaml.loader.SafeLoader
  • -
  • yaml.reader.Reader
  • -
  • yaml.scanner.Scanner
  • -
  • yaml.parser.Parser
  • -
  • yaml.composer.Composer
  • -
  • yaml.constructor.SafeConstructor
  • -
  • yaml.constructor.BaseConstructor
  • -
  • yaml.resolver.Resolver
  • -
  • yaml.resolver.BaseResolver
  • -
-

Class variables

-
-
var dynamic_data_matcher
-
-
-
-
var module
-
-
-
-
var yaml_constructors
-
-
-
-
-

Static methods

-
-
-def with_module(module: module) -
-
-

Creates a dynamic subclass of SafeDynamicLoader with the data_module attribute set to module.

-

Args

-
-
module
-
A global vars module with all the dynamic values defined in it.
-
-

Returns

-

type

-
- -Expand source code - -
@classmethod
-def with_module(cls, module: ModuleType):
-    """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-    Args:
-        module: A global vars module with all the dynamic values defined in it.
-
-    Returns:
-        type
-    """
-    return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-
-
-

Methods

-
-
-def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) ‑> str -
-
-

Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.

-

Args

-
-
node
-
Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention -are replaced with the respective attributes in te provided module.
-
-

Returns

-

Constructed str or numerical.

-
- -Expand source code - -
def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) -> str:
-    """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-    Args:
-        node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-            are replaced with the respective attributes in te provided module.
-
-    Returns:
-        Constructed `str` or numerical.
-    """
-    value = node.value
-
-    while result := self.dynamic_data_matcher.match(value):
-        ref = result.group(3)
-        replacement = getattr(self.module, ref)
-
-        value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-    return value
-
-
-
-
-
-class SafeDynamicSchemaLoader -(stream) -
-
-

Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].

-

Dynamic variables defined in a provided module object.

-

Initialize the scanner.

-
- -Expand source code - -
class SafeDynamicSchemaLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) -> Any:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-            try:
-                value = float(value)
-                return value
-            except ValueError:
-                pass
-
-        return value
-
-

Ancestors

-
    -
  • yaml.loader.SafeLoader
  • -
  • yaml.reader.Reader
  • -
  • yaml.scanner.Scanner
  • -
  • yaml.parser.Parser
  • -
  • yaml.composer.Composer
  • -
  • yaml.constructor.SafeConstructor
  • -
  • yaml.constructor.BaseConstructor
  • -
  • yaml.resolver.Resolver
  • -
  • yaml.resolver.BaseResolver
  • -
-

Class variables

-
-
var dynamic_data_matcher
-
-
-
-
var module
-
-
-
-
var yaml_constructors
-
-
-
-
-

Static methods

-
-
-def with_module(module: module) -
-
-

Creates a dynamic subclass of SafeDynamicLoader with the data_module attribute set to module.

-

Args

-
-
module
-
A global vars module with all the dynamic values defined in it.
-
-

Returns

-

type

-
- -Expand source code - -
@classmethod
-def with_module(cls, module: ModuleType):
-    """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-    Args:
-        module: A global vars module with all the dynamic values defined in it.
-
-    Returns:
-        type
-    """
-    return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-
-
-

Methods

-
-
-def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) ‑> Any -
-
-

Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.

-

Args

-
-
node
-
Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention -are replaced with the respective attributes in te provided module.
-
-

Returns

-

Constructed str or numerical.

-
- -Expand source code - -
def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) -> Any:
-    """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-    Args:
-        node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-            are replaced with the respective attributes in te provided module.
-
-    Returns:
-        Constructed `str` or numerical.
-    """
-    value = node.value
-
-    while result := self.dynamic_data_matcher.match(value):
-        ref = result.group(3)
-        replacement = getattr(self.module, ref)
-
-        value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-        try:
-            value = float(value)
-            return value
-        except ValueError:
-            pass
-
-    return value
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/config/pydantic/config.html b/docs/config/pydantic/config.html deleted file mode 100644 index 72252a0..0000000 --- a/docs/config/pydantic/config.html +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - -dynamicio.config.pydantic.config API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic.config

-
-
-

Pydantic schema for YAML files

-
- -Expand source code - -
# pylint: disable=no-member, no-self-argument, unused-argument
-"""Pydantic schema for YAML files"""
-
-from typing import Mapping, MutableMapping
-
-import pydantic
-
-import dynamicio.config.pydantic.io_resources as env_spec
-
-
-class BindingsYaml(pydantic.BaseModel):
-    """Class controlling structure of the top-level IOConfig yaml file.
-
-    The top-level config is a dictionary of <binding_name> -> <env_name>
-    """
-
-    bindings: Mapping[str, env_spec.IOBinding]
-
-    @pydantic.validator("bindings", pre=True)
-    def _validate_bindings(cls, value: Mapping):
-        if not isinstance(value, Mapping):
-            raise ValueError(f"Bindings must be a mapping. (got {value!r} instead).")
-        # Tell each binding its name
-        for (name, sub_config) in value.items():
-            if not isinstance(sub_config, MutableMapping):
-                raise ValueError(f"Each element for the name binding must be a dict. (got {sub_config!r} instead)")
-            sub_config["__binding_name__"] = name
-        return value
-
-    def update_config_refs(self) -> "BindingsYaml":
-        """Updates dynamic parts of the config:
-        - Configure _parent for all `IOEnvironment`s
-        - Replace all IOSchemaRef with actual schema objects
-        """
-        for binding in self.bindings.values():
-            for io_env in binding.environments.values():
-                io_env.set_parent(binding)
-        return self
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class BindingsYaml -(**data: Any) -
-
-

Class controlling structure of the top-level IOConfig yaml file.

-

The top-level config is a dictionary of ->

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class BindingsYaml(pydantic.BaseModel):
-    """Class controlling structure of the top-level IOConfig yaml file.
-
-    The top-level config is a dictionary of <binding_name> -> <env_name>
-    """
-
-    bindings: Mapping[str, env_spec.IOBinding]
-
-    @pydantic.validator("bindings", pre=True)
-    def _validate_bindings(cls, value: Mapping):
-        if not isinstance(value, Mapping):
-            raise ValueError(f"Bindings must be a mapping. (got {value!r} instead).")
-        # Tell each binding its name
-        for (name, sub_config) in value.items():
-            if not isinstance(sub_config, MutableMapping):
-                raise ValueError(f"Each element for the name binding must be a dict. (got {sub_config!r} instead)")
-            sub_config["__binding_name__"] = name
-        return value
-
-    def update_config_refs(self) -> "BindingsYaml":
-        """Updates dynamic parts of the config:
-        - Configure _parent for all `IOEnvironment`s
-        - Replace all IOSchemaRef with actual schema objects
-        """
-        for binding in self.bindings.values():
-            for io_env in binding.environments.values():
-                io_env.set_parent(binding)
-        return self
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var bindings : Mapping[str, IOBinding]
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-

Methods

-
-
-def update_config_refs(self) ‑> BindingsYaml -
-
-

Updates dynamic parts of the config: -- Configure _parent for all IOEnvironments -- Replace all IOSchemaRef with actual schema objects

-
- -Expand source code - -
def update_config_refs(self) -> "BindingsYaml":
-    """Updates dynamic parts of the config:
-    - Configure _parent for all `IOEnvironment`s
-    - Replace all IOSchemaRef with actual schema objects
-    """
-    for binding in self.bindings.values():
-        for io_env in binding.environments.values():
-            io_env.set_parent(binding)
-    return self
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/config/pydantic/index.html b/docs/config/pydantic/index.html deleted file mode 100644 index 3552ee8..0000000 --- a/docs/config/pydantic/index.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - -dynamicio.config.pydantic API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic

-
-
-

Pydantic config models.

-
- -Expand source code - -
"""Pydantic config models."""
-
-from dynamicio.config.pydantic.config import BindingsYaml
-from dynamicio.config.pydantic.io_resources import (
-    IOEnvironment,
-    KafkaDataEnvironment,
-    LocalBatchDataEnvironment,
-    LocalDataEnvironment,
-    PostgresDataEnvironment,
-    S3DataEnvironment,
-    S3PathPrefixEnvironment,
-)
-from dynamicio.config.pydantic.table_schema import DataframeSchema, SchemaColumn
-
-
-
-

Sub-modules

-
-
dynamicio.config.pydantic.config
-
-

Pydantic schema for YAML files

-
-
dynamicio.config.pydantic.io_resources
-
-

This module contains pylint models for physical data sources (places the bytes are being read from).

-
-
dynamicio.config.pydantic.table_schema
-
-

This module defines Config schema for data source (pandas dataframe)

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/config/pydantic/io_resources.html b/docs/config/pydantic/io_resources.html deleted file mode 100644 index 5dab44d..0000000 --- a/docs/config/pydantic/io_resources.html +++ /dev/null @@ -1,1613 +0,0 @@ - - - - - - -dynamicio.config.pydantic.io_resources API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic.io_resources

-
-
-

This module contains pylint models for physical data sources (places the bytes are being read from).

-
- -Expand source code - -
# pylint: disable=no-member, no-self-argument, unused-argument
-
-"""This module contains pylint models for physical data sources (places the bytes are being read from)."""
-
-import enum
-import posixpath
-from typing import Mapping, Optional, Union
-
-import pydantic
-from pydantic import BaseModel, model_validator
-
-import dynamicio.config.pydantic.table_schema as table_spec
-
-
-@enum.unique
-class DataBackendType(str, enum.Enum):
-    """Input file types."""
-
-    # pylint: disable=invalid-name
-    local = "local"
-    local_batch = "local_batch"
-    s3 = "s3"  # is there a difference between 's3' and 's3_file' ?
-    s3_file = "s3_file"
-    s3_path_prefix = "s3_path_prefix"
-    postgres = "postgres"
-    athena = "athena"
-    kafka = "kafka"
-
-
-@enum.unique
-class FileType(str, enum.Enum):
-    """List of supported file formats."""
-
-    # pylint: disable=invalid-name
-    parquet = "parquet"
-    csv = "csv"
-    json = "json"
-    hdf = "hdf"
-
-
-class IOBinding(BaseModel):
-    """A binding for a single i/o object."""
-
-    name: str = pydantic.Field(alias="__binding_name__")
-    environments: Mapping[
-        str,
-        Union["IOEnvironment", "LocalDataEnvironment", "LocalBatchDataEnvironment", "S3DataEnvironment", "S3PathPrefixEnvironment", "KafkaDataEnvironment", "PostgresDataEnvironment"],
-    ]
-    dynamicio_schema: Union[table_spec.DataframeSchema, None] = pydantic.Field(default=None, alias="schema")
-
-    def get_binding_for_environment(self, environment: str) -> "IOEnvironment":
-        """Fetch the IOEnvironment spec for the name provided."""
-        return self.environments[environment]
-
-    @pydantic.validator("environments", pre=True, always=True)
-    def pick_correct_env_cls(cls, info):
-        """This pre-validator picks an appropriate IOEnvironment subclass for the `data_backend_type`."""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"Environments input should be a dict. Got {info!r} instead.")
-        config_cls_overrides = {
-            DataBackendType.local: LocalDataEnvironment,
-            DataBackendType.local_batch: LocalBatchDataEnvironment,
-            DataBackendType.s3: S3DataEnvironment,
-            DataBackendType.s3_file: S3DataEnvironment,
-            DataBackendType.s3_path_prefix: S3PathPrefixEnvironment,
-            DataBackendType.kafka: KafkaDataEnvironment,
-            DataBackendType.postgres: PostgresDataEnvironment,
-        }
-        out_dict = {}
-        for (env_name, env_data) in info.items():
-            base_obj: IOEnvironment = IOEnvironment(**env_data)
-            override_cls = config_cls_overrides.get(base_obj.data_backend_type)
-            if override_cls:
-                use_obj = override_cls(**env_data)
-            else:
-                use_obj = base_obj
-            out_dict[env_name] = use_obj
-        return out_dict
-
-    @pydantic.root_validator(pre=True)
-    def _preprocess_raw_config(cls, values):
-        if not isinstance(values, Mapping):
-            raise ValueError(f"IOBinding must be a dict at the top level. (got {values!r} instead)")
-        remapped_value = {"environments": {}}
-        for (key, value) in values.items():
-            if key in ("__binding_name__", "schema"):
-                # Passthrough params
-                remapped_value[key] = value
-            else:
-                # Assuming an environment config
-                remapped_value["environments"][key] = value
-        return remapped_value
-
-
-class IOEnvironment(BaseModel):
-    """A section specifiing an data source backed by a particular data backend."""
-
-    _parent: Optional[IOBinding] = None  # noqa: F821
-    options: Mapping = pydantic.Field(default_factory=dict)
-    data_backend_type: DataBackendType = pydantic.Field(alias="type", const=None)
-
-    class Config:
-        """Additional pydantic configuration for the model."""
-
-        underscore_attrs_are_private = True
-
-    @property
-    def dynamicio_schema(self) -> Union[table_spec.DataframeSchema, None]:
-        """Returns tabular data structure definition for the data source (if available)."""
-        if not self._parent:
-            raise Exception("Parent field is not set.")
-        return self._parent.dynamicio_schema
-
-    def set_parent(self, parent: IOBinding):  # noqa: F821
-        """Helper method to set parent config object."""
-        assert self._parent is None
-        self._parent = parent
-
-
-class LocalDataSubSection(BaseModel):
-    """Config section for local data provider."""
-
-    file_path: str
-    file_type: FileType
-
-
-class LocalDataEnvironment(IOEnvironment):
-    """The data is provided by local storage."""
-
-    local: LocalDataSubSection
-
-
-class LocalBatchDataSubSection(BaseModel):
-    """Config section for local batch data (multiple input files)."""
-
-    path_prefix: Optional[str] = None
-    dynamic_file_path: Optional[str] = None
-    file_type: FileType
-
-    @model_validator(mode="before")
-    def check_path_fields(cls, values):
-        """Check that only one of path_prefix or dynamic_file_path is provided."""
-        if not values.get("path_prefix") and not values.get("dynamic_file_path"):
-            raise ValueError("Either path_prefix or dynamic_file_path must be provided")
-        if values.get("path_prefix") and values.get("dynamic_file_path"):
-            raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-        return values
-
-
-class LocalBatchDataEnvironment(IOEnvironment):
-    """Parent section for local batch (multiple files) config."""
-
-    local: LocalBatchDataSubSection
-
-
-class S3DataSubSection(BaseModel):
-    """Config section for S3 data source."""
-
-    file_path: str
-    file_type: FileType
-    bucket: str
-
-
-class S3DataEnvironment(IOEnvironment):
-    """Parent section for s3 data source config."""
-
-    s3: S3DataSubSection
-
-
-class S3PathPrefixSubSection(BaseModel):
-    """Config section for s3 prefix data source (multiple s3 objects)."""
-
-    path_prefix: str
-    file_type: FileType
-    bucket: str
-
-    @pydantic.root_validator(pre=True)
-    def support_legacy_config_path_prefix(cls, values):
-        """This validator implements support for legacy config format where the bucket & path_prefix path could've been passed as a single param in 'bucket' field.
-
-        E.g.
-            bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"
-        """
-        bucket = values.get("bucket")
-        path_prefix = values.get("path_prefix")
-        if (bucket and isinstance(bucket, str) and posixpath.sep in bucket) and (not path_prefix):
-            (new_bucket, new_path_prefix) = bucket.split(posixpath.sep, 1)
-            values.update(
-                {
-                    "bucket": new_bucket,
-                    "path_prefix": new_path_prefix,
-                }
-            )
-        return values
-
-
-class S3PathPrefixEnvironment(IOEnvironment):
-    """Parent section for the multi-object s3 data source."""
-
-    s3: S3PathPrefixSubSection
-
-
-class KafkaDataSubSection(BaseModel):
-    """Kafka configuration section."""
-
-    kafka_server: str
-    kafka_topic: str
-
-
-class KafkaDataEnvironment(IOEnvironment):
-    """Parent section for kafka data source config."""
-
-    kafka: KafkaDataSubSection
-
-
-class PostgresDataSubSection(BaseModel):
-    """Postgres data source configuration."""
-
-    db_host: str
-    db_port: str
-    db_name: str
-    db_user: str
-    db_password: str
-
-
-class PostgresDataEnvironment(IOEnvironment):
-    """Parent section for postgres data source."""
-
-    postgres: PostgresDataSubSection
-
-
-IOBinding.model_rebuild()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class DataBackendType -(value, names=None, *, module=None, qualname=None, type=None, start=1) -
-
-

Input file types.

-
- -Expand source code - -
class DataBackendType(str, enum.Enum):
-    """Input file types."""
-
-    # pylint: disable=invalid-name
-    local = "local"
-    local_batch = "local_batch"
-    s3 = "s3"  # is there a difference between 's3' and 's3_file' ?
-    s3_file = "s3_file"
-    s3_path_prefix = "s3_path_prefix"
-    postgres = "postgres"
-    athena = "athena"
-    kafka = "kafka"
-
-

Ancestors

-
    -
  • builtins.str
  • -
  • enum.Enum
  • -
-

Class variables

-
-
var athena
-
-
-
-
var kafka
-
-
-
-
var local
-
-
-
-
var local_batch
-
-
-
-
var postgres
-
-
-
-
var s3
-
-
-
-
var s3_file
-
-
-
-
var s3_path_prefix
-
-
-
-
-
-
-class FileType -(value, names=None, *, module=None, qualname=None, type=None, start=1) -
-
-

List of supported file formats.

-
- -Expand source code - -
class FileType(str, enum.Enum):
-    """List of supported file formats."""
-
-    # pylint: disable=invalid-name
-    parquet = "parquet"
-    csv = "csv"
-    json = "json"
-    hdf = "hdf"
-
-

Ancestors

-
    -
  • builtins.str
  • -
  • enum.Enum
  • -
-

Class variables

-
-
var csv
-
-
-
-
var hdf
-
-
-
-
var json
-
-
-
-
var parquet
-
-
-
-
-
-
-class IOBinding -(**data: Any) -
-
-

A binding for a single i/o object.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class IOBinding(BaseModel):
-    """A binding for a single i/o object."""
-
-    name: str = pydantic.Field(alias="__binding_name__")
-    environments: Mapping[
-        str,
-        Union["IOEnvironment", "LocalDataEnvironment", "LocalBatchDataEnvironment", "S3DataEnvironment", "S3PathPrefixEnvironment", "KafkaDataEnvironment", "PostgresDataEnvironment"],
-    ]
-    dynamicio_schema: Union[table_spec.DataframeSchema, None] = pydantic.Field(default=None, alias="schema")
-
-    def get_binding_for_environment(self, environment: str) -> "IOEnvironment":
-        """Fetch the IOEnvironment spec for the name provided."""
-        return self.environments[environment]
-
-    @pydantic.validator("environments", pre=True, always=True)
-    def pick_correct_env_cls(cls, info):
-        """This pre-validator picks an appropriate IOEnvironment subclass for the `data_backend_type`."""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"Environments input should be a dict. Got {info!r} instead.")
-        config_cls_overrides = {
-            DataBackendType.local: LocalDataEnvironment,
-            DataBackendType.local_batch: LocalBatchDataEnvironment,
-            DataBackendType.s3: S3DataEnvironment,
-            DataBackendType.s3_file: S3DataEnvironment,
-            DataBackendType.s3_path_prefix: S3PathPrefixEnvironment,
-            DataBackendType.kafka: KafkaDataEnvironment,
-            DataBackendType.postgres: PostgresDataEnvironment,
-        }
-        out_dict = {}
-        for (env_name, env_data) in info.items():
-            base_obj: IOEnvironment = IOEnvironment(**env_data)
-            override_cls = config_cls_overrides.get(base_obj.data_backend_type)
-            if override_cls:
-                use_obj = override_cls(**env_data)
-            else:
-                use_obj = base_obj
-            out_dict[env_name] = use_obj
-        return out_dict
-
-    @pydantic.root_validator(pre=True)
-    def _preprocess_raw_config(cls, values):
-        if not isinstance(values, Mapping):
-            raise ValueError(f"IOBinding must be a dict at the top level. (got {values!r} instead)")
-        remapped_value = {"environments": {}}
-        for (key, value) in values.items():
-            if key in ("__binding_name__", "schema"):
-                # Passthrough params
-                remapped_value[key] = value
-            else:
-                # Assuming an environment config
-                remapped_value["environments"][key] = value
-        return remapped_value
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var dynamicio_schema : Optional[DataframeSchema]
-
-
-
-
var environments : Mapping[str, Union[IOEnvironmentLocalDataEnvironmentLocalBatchDataEnvironmentS3DataEnvironmentS3PathPrefixEnvironmentKafkaDataEnvironmentPostgresDataEnvironment]]
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var name : str
-
-
-
-
-

Static methods

-
-
-def pick_correct_env_cls(info) -
-
-

This pre-validator picks an appropriate IOEnvironment subclass for the data_backend_type.

-
- -Expand source code - -
@pydantic.validator("environments", pre=True, always=True)
-def pick_correct_env_cls(cls, info):
-    """This pre-validator picks an appropriate IOEnvironment subclass for the `data_backend_type`."""
-    if not isinstance(info, Mapping):
-        raise ValueError(f"Environments input should be a dict. Got {info!r} instead.")
-    config_cls_overrides = {
-        DataBackendType.local: LocalDataEnvironment,
-        DataBackendType.local_batch: LocalBatchDataEnvironment,
-        DataBackendType.s3: S3DataEnvironment,
-        DataBackendType.s3_file: S3DataEnvironment,
-        DataBackendType.s3_path_prefix: S3PathPrefixEnvironment,
-        DataBackendType.kafka: KafkaDataEnvironment,
-        DataBackendType.postgres: PostgresDataEnvironment,
-    }
-    out_dict = {}
-    for (env_name, env_data) in info.items():
-        base_obj: IOEnvironment = IOEnvironment(**env_data)
-        override_cls = config_cls_overrides.get(base_obj.data_backend_type)
-        if override_cls:
-            use_obj = override_cls(**env_data)
-        else:
-            use_obj = base_obj
-        out_dict[env_name] = use_obj
-    return out_dict
-
-
-
-

Methods

-
-
-def get_binding_for_environment(self, environment: str) ‑> IOEnvironment -
-
-

Fetch the IOEnvironment spec for the name provided.

-
- -Expand source code - -
def get_binding_for_environment(self, environment: str) -> "IOEnvironment":
-    """Fetch the IOEnvironment spec for the name provided."""
-    return self.environments[environment]
-
-
-
-
-
-class IOEnvironment -(**data: Any) -
-
-

A section specifiing an data source backed by a particular data backend.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class IOEnvironment(BaseModel):
-    """A section specifiing an data source backed by a particular data backend."""
-
-    _parent: Optional[IOBinding] = None  # noqa: F821
-    options: Mapping = pydantic.Field(default_factory=dict)
-    data_backend_type: DataBackendType = pydantic.Field(alias="type", const=None)
-
-    class Config:
-        """Additional pydantic configuration for the model."""
-
-        underscore_attrs_are_private = True
-
-    @property
-    def dynamicio_schema(self) -> Union[table_spec.DataframeSchema, None]:
-        """Returns tabular data structure definition for the data source (if available)."""
-        if not self._parent:
-            raise Exception("Parent field is not set.")
-        return self._parent.dynamicio_schema
-
-    def set_parent(self, parent: IOBinding):  # noqa: F821
-        """Helper method to set parent config object."""
-        assert self._parent is None
-        self._parent = parent
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Subclasses

- -

Class variables

-
-
var Config
-
-

Additional pydantic configuration for the model.

-
-
var data_backend_typeDataBackendType
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var options : Mapping[~KT, +VT_co]
-
-
-
-
-

Instance variables

-
-
var dynamicio_schema : Optional[DataframeSchema]
-
-

Returns tabular data structure definition for the data source (if available).

-
- -Expand source code - -
@property
-def dynamicio_schema(self) -> Union[table_spec.DataframeSchema, None]:
-    """Returns tabular data structure definition for the data source (if available)."""
-    if not self._parent:
-        raise Exception("Parent field is not set.")
-    return self._parent.dynamicio_schema
-
-
-
-

Methods

-
-
-def model_post_init(self: BaseModel, __context: Any) ‑> None -
-
-

This function is meant to behave like a BaseModel method to initialise private attributes.

-

It takes context as an argument since that's what pydantic-core passes when calling it.

-

Args

-
-
self
-
The BaseModel instance.
-
__context
-
The context.
-
-
- -Expand source code - -
def init_private_attributes(self: BaseModel, __context: Any) -> None:
-    """This function is meant to behave like a BaseModel method to initialise private attributes.
-
-    It takes context as an argument since that's what pydantic-core passes when calling it.
-
-    Args:
-        self: The BaseModel instance.
-        __context: The context.
-    """
-    if getattr(self, '__pydantic_private__', None) is None:
-        pydantic_private = {}
-        for name, private_attr in self.__private_attributes__.items():
-            default = private_attr.get_default()
-            if default is not PydanticUndefined:
-                pydantic_private[name] = default
-        object_setattr(self, '__pydantic_private__', pydantic_private)
-
-
-
-def set_parent(self, parent: IOBinding) -
-
-

Helper method to set parent config object.

-
- -Expand source code - -
def set_parent(self, parent: IOBinding):  # noqa: F821
-    """Helper method to set parent config object."""
-    assert self._parent is None
-    self._parent = parent
-
-
-
-
-
-class KafkaDataEnvironment -(**data: Any) -
-
-

Parent section for kafka data source config.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class KafkaDataEnvironment(IOEnvironment):
-    """Parent section for kafka data source config."""
-
-    kafka: KafkaDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var kafkaKafkaDataSubSection
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-

Inherited members

- -
-
-class KafkaDataSubSection -(**data: Any) -
-
-

Kafka configuration section.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class KafkaDataSubSection(BaseModel):
-    """Kafka configuration section."""
-
-    kafka_server: str
-    kafka_topic: str
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var kafka_server : str
-
-
-
-
var kafka_topic : str
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-
-
-class LocalBatchDataEnvironment -(**data: Any) -
-
-

Parent section for local batch (multiple files) config.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalBatchDataEnvironment(IOEnvironment):
-    """Parent section for local batch (multiple files) config."""
-
-    local: LocalBatchDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var localLocalBatchDataSubSection
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-

Inherited members

- -
-
-class LocalBatchDataSubSection -(**data: Any) -
-
-

Config section for local batch data (multiple input files).

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalBatchDataSubSection(BaseModel):
-    """Config section for local batch data (multiple input files)."""
-
-    path_prefix: Optional[str] = None
-    dynamic_file_path: Optional[str] = None
-    file_type: FileType
-
-    @model_validator(mode="before")
-    def check_path_fields(cls, values):
-        """Check that only one of path_prefix or dynamic_file_path is provided."""
-        if not values.get("path_prefix") and not values.get("dynamic_file_path"):
-            raise ValueError("Either path_prefix or dynamic_file_path must be provided")
-        if values.get("path_prefix") and values.get("dynamic_file_path"):
-            raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-        return values
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var dynamic_file_path : Optional[str]
-
-
-
-
var file_typeFileType
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var path_prefix : Optional[str]
-
-
-
-
-

Static methods

-
-
-def check_path_fields(values) -
-
-

Check that only one of path_prefix or dynamic_file_path is provided.

-
- -Expand source code - -
@model_validator(mode="before")
-def check_path_fields(cls, values):
-    """Check that only one of path_prefix or dynamic_file_path is provided."""
-    if not values.get("path_prefix") and not values.get("dynamic_file_path"):
-        raise ValueError("Either path_prefix or dynamic_file_path must be provided")
-    if values.get("path_prefix") and values.get("dynamic_file_path"):
-        raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-    return values
-
-
-
-
-
-class LocalDataEnvironment -(**data: Any) -
-
-

The data is provided by local storage.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalDataEnvironment(IOEnvironment):
-    """The data is provided by local storage."""
-
-    local: LocalDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var localLocalDataSubSection
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-

Inherited members

- -
-
-class LocalDataSubSection -(**data: Any) -
-
-

Config section for local data provider.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalDataSubSection(BaseModel):
-    """Config section for local data provider."""
-
-    file_path: str
-    file_type: FileType
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var file_path : str
-
-
-
-
var file_typeFileType
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-
-
-class PostgresDataEnvironment -(**data: Any) -
-
-

Parent section for postgres data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class PostgresDataEnvironment(IOEnvironment):
-    """Parent section for postgres data source."""
-
-    postgres: PostgresDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var postgresPostgresDataSubSection
-
-
-
-
-

Inherited members

- -
-
-class PostgresDataSubSection -(**data: Any) -
-
-

Postgres data source configuration.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class PostgresDataSubSection(BaseModel):
-    """Postgres data source configuration."""
-
-    db_host: str
-    db_port: str
-    db_name: str
-    db_user: str
-    db_password: str
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var db_host : str
-
-
-
-
var db_name : str
-
-
-
-
var db_password : str
-
-
-
-
var db_port : str
-
-
-
-
var db_user : str
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-
-
-class S3DataEnvironment -(**data: Any) -
-
-

Parent section for s3 data source config.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3DataEnvironment(IOEnvironment):
-    """Parent section for s3 data source config."""
-
-    s3: S3DataSubSection
-
-

Ancestors

- -

Class variables

-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var s3S3DataSubSection
-
-
-
-
-

Inherited members

- -
-
-class S3DataSubSection -(**data: Any) -
-
-

Config section for S3 data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3DataSubSection(BaseModel):
-    """Config section for S3 data source."""
-
-    file_path: str
-    file_type: FileType
-    bucket: str
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var bucket : str
-
-
-
-
var file_path : str
-
-
-
-
var file_typeFileType
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
-
-
-class S3PathPrefixEnvironment -(**data: Any) -
-
-

Parent section for the multi-object s3 data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3PathPrefixEnvironment(IOEnvironment):
-    """Parent section for the multi-object s3 data source."""
-
-    s3: S3PathPrefixSubSection
-
-

Ancestors

- -

Class variables

-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var s3S3PathPrefixSubSection
-
-
-
-
-

Inherited members

- -
-
-class S3PathPrefixSubSection -(**data: Any) -
-
-

Config section for s3 prefix data source (multiple s3 objects).

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3PathPrefixSubSection(BaseModel):
-    """Config section for s3 prefix data source (multiple s3 objects)."""
-
-    path_prefix: str
-    file_type: FileType
-    bucket: str
-
-    @pydantic.root_validator(pre=True)
-    def support_legacy_config_path_prefix(cls, values):
-        """This validator implements support for legacy config format where the bucket & path_prefix path could've been passed as a single param in 'bucket' field.
-
-        E.g.
-            bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"
-        """
-        bucket = values.get("bucket")
-        path_prefix = values.get("path_prefix")
-        if (bucket and isinstance(bucket, str) and posixpath.sep in bucket) and (not path_prefix):
-            (new_bucket, new_path_prefix) = bucket.split(posixpath.sep, 1)
-            values.update(
-                {
-                    "bucket": new_bucket,
-                    "path_prefix": new_path_prefix,
-                }
-            )
-        return values
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var bucket : str
-
-
-
-
var file_typeFileType
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var path_prefix : str
-
-
-
-
-

Static methods

-
-
-def support_legacy_config_path_prefix(values) -
-
-

This validator implements support for legacy config format where the bucket & path_prefix path could've been passed as a single param in 'bucket' field.

-

E.g. -bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"

-
- -Expand source code - -
@pydantic.root_validator(pre=True)
-def support_legacy_config_path_prefix(cls, values):
-    """This validator implements support for legacy config format where the bucket & path_prefix path could've been passed as a single param in 'bucket' field.
-
-    E.g.
-        bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"
-    """
-    bucket = values.get("bucket")
-    path_prefix = values.get("path_prefix")
-    if (bucket and isinstance(bucket, str) and posixpath.sep in bucket) and (not path_prefix):
-        (new_bucket, new_path_prefix) = bucket.split(posixpath.sep, 1)
-        values.update(
-            {
-                "bucket": new_bucket,
-                "path_prefix": new_path_prefix,
-            }
-        )
-    return values
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/config/pydantic/table_schema.html b/docs/config/pydantic/table_schema.html deleted file mode 100644 index d4b093e..0000000 --- a/docs/config/pydantic/table_schema.html +++ /dev/null @@ -1,622 +0,0 @@ - - - - - - -dynamicio.config.pydantic.table_schema API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic.table_schema

-
-
-

This module defines Config schema for data source (pandas dataframe)

-
- -Expand source code - -
# pylint: disable=no-member, no-self-argument, unused-argument
-
-"""This module defines Config schema for data source (pandas dataframe)"""
-
-import enum
-from typing import Mapping, Sequence
-
-import pydantic
-from pandas.core.dtypes.common import pandas_dtype
-
-
-@enum.unique
-class MetricsName(str, enum.Enum):
-    """The list of valid metrics names."""
-
-    # pylint: disable=invalid-name
-    min = "Min"
-    max = "Max"
-    mean = "Mean"
-    stddev = "Std"
-    variance = "Variance"
-    counts = "Counts"
-    counts_per_label = "CountsPerLabel"
-    unique_counts = "UniqueCounts"
-
-
-class ColumnValidationBase(pydantic.BaseModel):
-    """A single column validator."""
-
-    name: str
-    apply: bool
-    options: Mapping[str, object]
-
-
-class SchemaColumn(pydantic.BaseModel):
-    """Definition os a single data source column."""
-
-    name: str
-    data_type: str = pydantic.Field(alias="type")
-    validations: Sequence[ColumnValidationBase] = pydantic.Field(default_factory=list)
-    metrics: Sequence[MetricsName] = ()
-
-    @pydantic.validator("data_type")
-    def is_valid_pandas_type(cls, info):
-        """Checks that the data_type is understood by pandas."""
-        try:
-            pandas_dtype(info)
-        except TypeError:
-            raise ValueError(f"Unexpected data type {info}") from None
-        return info
-
-    @pydantic.validator("validations", pre=True)
-    def remap_validations(cls, info):
-        """Remap the yaml structure of {validation_type: <params>} to a list with validation_type as a key"""
-        if not isinstance(info, dict):
-            raise ValueError(f"{info!r} should be a dict")
-        out = []
-        for (key, params) in info.items():
-            new_el = params.copy()
-            new_el.update({"name": key})
-            out.append(new_el)
-        return out
-
-    @pydantic.validator("metrics", pre=True, always=True)
-    def validate_metrics(cls, info):
-        """Remap any false-ish `metrics` value to an empty list."""
-        if info:
-            out = info
-        else:
-            out = []
-        return out
-
-
-class DataframeSchema(pydantic.BaseModel):
-    """Pydantic model describing the tabular data provided by the data source."""
-
-    name: str
-    columns: Mapping[str, SchemaColumn]
-
-    @pydantic.validator("columns", pre=True)
-    def supply_column_names(cls, info):
-        """Tell each column its name (the key it is listed under)"""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"{info!r} shoudl be a dict.")
-
-        return {str(col_name): {**{"name": str(col_name)}, **col_data} for (col_name, col_data) in info.items()}
-
-    @property
-    def validations(self) -> Mapping[str, Sequence[ColumnValidationBase]]:
-        """A short-hand property to access the validators for each column."""
-        return {col_name: col.validations for (col_name, col) in self.columns.items()}
-
-    @property
-    def metrics(self) -> Mapping[str, Sequence[MetricsName]]:
-        """A short-hand property to access the metrics for each column."""
-        return {col_name: col.metrics for (col_name, col) in self.columns.items()}
-
-    @property
-    def column_names(self) -> Sequence[str]:
-        """Property providing the list of all column names."""
-        return tuple(self.columns.keys())
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class ColumnValidationBase -(**data: Any) -
-
-

A single column validator.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class ColumnValidationBase(pydantic.BaseModel):
-    """A single column validator."""
-
-    name: str
-    apply: bool
-    options: Mapping[str, object]
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var apply : bool
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var name : str
-
-
-
-
var options : Mapping[str, object]
-
-
-
-
-
-
-class DataframeSchema -(**data: Any) -
-
-

Pydantic model describing the tabular data provided by the data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class DataframeSchema(pydantic.BaseModel):
-    """Pydantic model describing the tabular data provided by the data source."""
-
-    name: str
-    columns: Mapping[str, SchemaColumn]
-
-    @pydantic.validator("columns", pre=True)
-    def supply_column_names(cls, info):
-        """Tell each column its name (the key it is listed under)"""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"{info!r} shoudl be a dict.")
-
-        return {str(col_name): {**{"name": str(col_name)}, **col_data} for (col_name, col_data) in info.items()}
-
-    @property
-    def validations(self) -> Mapping[str, Sequence[ColumnValidationBase]]:
-        """A short-hand property to access the validators for each column."""
-        return {col_name: col.validations for (col_name, col) in self.columns.items()}
-
-    @property
-    def metrics(self) -> Mapping[str, Sequence[MetricsName]]:
-        """A short-hand property to access the metrics for each column."""
-        return {col_name: col.metrics for (col_name, col) in self.columns.items()}
-
-    @property
-    def column_names(self) -> Sequence[str]:
-        """Property providing the list of all column names."""
-        return tuple(self.columns.keys())
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var columns : Mapping[str, SchemaColumn]
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var name : str
-
-
-
-
-

Static methods

-
-
-def supply_column_names(info) -
-
-

Tell each column its name (the key it is listed under)

-
- -Expand source code - -
@pydantic.validator("columns", pre=True)
-def supply_column_names(cls, info):
-    """Tell each column its name (the key it is listed under)"""
-    if not isinstance(info, Mapping):
-        raise ValueError(f"{info!r} shoudl be a dict.")
-
-    return {str(col_name): {**{"name": str(col_name)}, **col_data} for (col_name, col_data) in info.items()}
-
-
-
-

Instance variables

-
-
var column_names : Sequence[str]
-
-

Property providing the list of all column names.

-
- -Expand source code - -
@property
-def column_names(self) -> Sequence[str]:
-    """Property providing the list of all column names."""
-    return tuple(self.columns.keys())
-
-
-
var metrics : Mapping[str, Sequence[MetricsName]]
-
-

A short-hand property to access the metrics for each column.

-
- -Expand source code - -
@property
-def metrics(self) -> Mapping[str, Sequence[MetricsName]]:
-    """A short-hand property to access the metrics for each column."""
-    return {col_name: col.metrics for (col_name, col) in self.columns.items()}
-
-
-
var validations : Mapping[str, Sequence[ColumnValidationBase]]
-
-

A short-hand property to access the validators for each column.

-
- -Expand source code - -
@property
-def validations(self) -> Mapping[str, Sequence[ColumnValidationBase]]:
-    """A short-hand property to access the validators for each column."""
-    return {col_name: col.validations for (col_name, col) in self.columns.items()}
-
-
-
-
-
-class MetricsName -(value, names=None, *, module=None, qualname=None, type=None, start=1) -
-
-

The list of valid metrics names.

-
- -Expand source code - -
class MetricsName(str, enum.Enum):
-    """The list of valid metrics names."""
-
-    # pylint: disable=invalid-name
-    min = "Min"
-    max = "Max"
-    mean = "Mean"
-    stddev = "Std"
-    variance = "Variance"
-    counts = "Counts"
-    counts_per_label = "CountsPerLabel"
-    unique_counts = "UniqueCounts"
-
-

Ancestors

-
    -
  • builtins.str
  • -
  • enum.Enum
  • -
-

Class variables

-
-
var counts
-
-
-
-
var counts_per_label
-
-
-
-
var max
-
-
-
-
var mean
-
-
-
-
var min
-
-
-
-
var stddev
-
-
-
-
var unique_counts
-
-
-
-
var variance
-
-
-
-
-
-
-class SchemaColumn -(**data: Any) -
-
-

Definition os a single data source column.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class SchemaColumn(pydantic.BaseModel):
-    """Definition os a single data source column."""
-
-    name: str
-    data_type: str = pydantic.Field(alias="type")
-    validations: Sequence[ColumnValidationBase] = pydantic.Field(default_factory=list)
-    metrics: Sequence[MetricsName] = ()
-
-    @pydantic.validator("data_type")
-    def is_valid_pandas_type(cls, info):
-        """Checks that the data_type is understood by pandas."""
-        try:
-            pandas_dtype(info)
-        except TypeError:
-            raise ValueError(f"Unexpected data type {info}") from None
-        return info
-
-    @pydantic.validator("validations", pre=True)
-    def remap_validations(cls, info):
-        """Remap the yaml structure of {validation_type: <params>} to a list with validation_type as a key"""
-        if not isinstance(info, dict):
-            raise ValueError(f"{info!r} should be a dict")
-        out = []
-        for (key, params) in info.items():
-            new_el = params.copy()
-            new_el.update({"name": key})
-            out.append(new_el)
-        return out
-
-    @pydantic.validator("metrics", pre=True, always=True)
-    def validate_metrics(cls, info):
-        """Remap any false-ish `metrics` value to an empty list."""
-        if info:
-            out = info
-        else:
-            out = []
-        return out
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var data_type : str
-
-
-
-
var metrics : Sequence[MetricsName]
-
-
-
-
var model_computed_fields
-
-
-
-
var model_config
-
-
-
-
var model_fields
-
-
-
-
var name : str
-
-
-
-
var validations : Sequence[ColumnValidationBase]
-
-
-
-
-

Static methods

-
-
-def is_valid_pandas_type(info) -
-
-

Checks that the data_type is understood by pandas.

-
- -Expand source code - -
@pydantic.validator("data_type")
-def is_valid_pandas_type(cls, info):
-    """Checks that the data_type is understood by pandas."""
-    try:
-        pandas_dtype(info)
-    except TypeError:
-        raise ValueError(f"Unexpected data type {info}") from None
-    return info
-
-
-
-def remap_validations(info) -
-
-

Remap the yaml structure of {validation_type: } to a list with validation_type as a key

-
- -Expand source code - -
@pydantic.validator("validations", pre=True)
-def remap_validations(cls, info):
-    """Remap the yaml structure of {validation_type: <params>} to a list with validation_type as a key"""
-    if not isinstance(info, dict):
-        raise ValueError(f"{info!r} should be a dict")
-    out = []
-    for (key, params) in info.items():
-        new_el = params.copy()
-        new_el.update({"name": key})
-        out.append(new_el)
-    return out
-
-
-
-def validate_metrics(info) -
-
-

Remap any false-ish metrics value to an empty list.

-
- -Expand source code - -
@pydantic.validator("metrics", pre=True, always=True)
-def validate_metrics(cls, info):
-    """Remap any false-ish `metrics` value to an empty list."""
-    if info:
-        out = info
-    else:
-        out = []
-    return out
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/core.html b/docs/core.html deleted file mode 100644 index 8e1a280..0000000 --- a/docs/core.html +++ /dev/null @@ -1,963 +0,0 @@ - - - - - - -dynamicio.core API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.core

-
-
-

Implements the DynamicDataIO class which provides functionality for data: loading; sinking, and; schema validation.

-
- -Expand source code - -
"""Implements the DynamicDataIO class which provides functionality for data: loading; sinking, and; schema validation."""
-# pylint: disable=no-member
-__all__ = ["DynamicDataIO", "SCHEMA_FROM_FILE"]
-
-import asyncio
-import inspect
-import re
-from concurrent.futures import ThreadPoolExecutor
-from typing import Any, List, Mapping, MutableMapping, Optional, Tuple
-
-import pandas as pd  # type: ignore
-import pydantic
-from magic_logger import logger
-
-from dynamicio import validations
-from dynamicio.config.pydantic import DataframeSchema, IOEnvironment, SchemaColumn
-from dynamicio.errors import CASTING_WARNING_MSG, ColumnsDataTypeError, NOTICE_MSG, SchemaNotFoundError, SchemaValidationError
-from dynamicio.metrics import get_metric
-
-SCHEMA_FROM_FILE = {"schema": object()}
-
-pool = ThreadPoolExecutor()
-
-
-class DynamicDataIO:
-    """Given a `src.utils.dynamicio.config.IOConfig` object, it generates an object with access to a series of methods for cloud I/O operations and data validations.
-
-    Example:
-       >>> input_sources_config = IOConfig(
-       >>>     "path_to/input.yaml",
-       >>>     os.getenv("ENVIRONMENT",default="LOCAL")
-       >>> )
-       >>>
-       >>> class IO(WithS3File, WithLocal, DynamicDataIO):
-       >>>     schema = S
-       >>>
-       >>> my_dataset_local_mapping = input_config.get(source_key="MY_DATASET")
-       >>> my_dataset_io = IO(my_dataset_local_mapping)
-       >>> my_dataset_df = my_dataset_io.read()
-    """
-
-    schema: DataframeSchema
-    sources_config: IOEnvironment
-
-    def __init__(
-        self,
-        source_config: IOEnvironment,
-        apply_schema_validations: bool = False,
-        log_schema_metrics: bool = False,
-        show_casting_warnings: bool = False,
-        **options: MutableMapping[str, Any],
-    ):
-        """Class constructor.
-
-        Args:
-            source_config: Configuration to use when reading/writing data from/to a source
-            apply_schema_validations: Applies schema validations on either read() or write()
-            log_schema_metrics: Logs schema metrics on either read() or write()
-            show_casting_warnings: Logs casting warnings on either read() or write() if set to True
-            options: Any additional kwargs that may be used throughout the lifecycle of the object
-        """
-        if type(self) is DynamicDataIO:  # pylint: disable=unidiomatic-typecheck
-            raise TypeError("Abstract class DynamicDataIO cannot be used to instantiate an object...")
-
-        self.sources_config = source_config
-        self.name = self._transform_class_name_to_dataset_name(self.__class__.__name__)
-        self.apply_schema_validations = apply_schema_validations
-        self.log_schema_metrics = log_schema_metrics
-        self.show_casting_warnings = show_casting_warnings
-        self.options = self._get_options(options, source_config.options)
-        source_name = self.sources_config.data_backend_type.value
-        if self.schema is SCHEMA_FROM_FILE:
-            active_schema = self.sources_config.dynamicio_schema
-        else:
-            active_schema = self._schema_from_obj(self)
-
-        if not active_schema:
-            raise SchemaNotFoundError()
-
-        assert isinstance(active_schema, DataframeSchema)
-        self.schema = active_schema
-        self.name = self.schema.name.upper()
-        self.schema_validations = self.schema.validations
-        self.schema_metrics = self.schema.metrics
-
-        assert hasattr(self, f"_read_from_{source_name}") or hasattr(
-            self, f"_write_to_{source_name}"
-        ), f"No method '_read_from_{source_name}' or '_write_to_{source_name}'. Have you registered a mixin for {source_name}?"
-
-    @staticmethod
-    def _schema_from_obj(target) -> DataframeSchema:
-        """Construct `DataframeSchema` from an object.
-
-        The object:
-            - MUST have `schema` attribute that is a dictionary specifying columns and datatypes
-            - CAN have `schema_validations` and `schema_metrics` attributes
-        """
-        col_info = {}
-        for (col_name, dtype) in target.schema.items():
-            col_validations = {}
-            col_metrics = []
-            try:
-                col_validations = target.schema_validations[col_name]
-            except (KeyError, AttributeError):
-                pass
-            try:
-                col_metrics = target.schema_metrics[col_name]
-            except (KeyError, AttributeError):
-                pass
-            col_info[col_name] = {
-                "type": dtype,
-                "validations": col_validations,
-                "metrics": col_metrics,
-            }
-        try:
-            out = DataframeSchema(name=target.name, columns=col_info)
-        except pydantic.ValidationError:
-            logger.exception(f"Error parsing {target.name=!r} {col_info=!r}")
-            raise
-        return out
-
-    def __init_subclass__(cls):
-        """Ensure that all subclasses have a `schema` attribute and a `validate` method.
-
-        Raises:
-            AssertionError: If either of the attributes is not implemented
-        """
-        if not inspect.getmodule(cls).__name__.startswith("dynamicio"):
-            assert "schema" in cls.__dict__
-
-            if cls.schema is None or (cls.schema is not SCHEMA_FROM_FILE and len(cls.schema) == 0):
-                raise ValueError(f"schema for class {cls} cannot be None or empty...")
-
-    async def async_read(self):
-        """Allows the use of asyncio to concurrently read files in memory.
-
-        Returns:
-            A pandas dataframe or an iterable.
-        """
-        loop = asyncio.get_running_loop()
-        return await loop.run_in_executor(pool, self.read)
-
-    def read(self) -> pd.DataFrame:
-        """Reads data source and returns a schema validated dataframe (by means of _apply_schema).
-
-        Returns:
-            A pandas dataframe or an iterable.
-        """
-        source_name = self.sources_config.data_backend_type.value
-        df = getattr(self, f"_read_from_{source_name}")()
-
-        df = self._apply_schema(df)
-        if self.apply_schema_validations:
-            self.validate_from_schema(df)
-        if self.log_schema_metrics:
-            self.log_metrics_from_schema(df)
-
-        return df
-
-    async def async_write(self, df: pd.DataFrame):
-        """Allows the use of asyncio to concurrently write files out.
-
-        Args:
-            df: The data to be written
-        """
-        loop = asyncio.get_running_loop()
-        return await loop.run_in_executor(pool, self.write, df)
-
-    def write(self, df: pd.DataFrame):
-        """Sink data to a given source based on the sources_config.
-
-        Args:
-            df: The data to be written
-        """
-        source_name = self.sources_config.data_backend_type.value
-        if set(df.columns) != self.schema.column_names:  # pylint: disable=E1101
-            columns = [column for column in df.columns.to_list() if column in self.schema.column_names]
-            df = df[columns]
-
-        if self.apply_schema_validations:
-            self.validate_from_schema(df)
-        if self.log_schema_metrics:
-            self.log_metrics_from_schema(df)
-
-        getattr(self, f"_write_to_{source_name}")(self._apply_schema(df))
-
-    def validate_from_schema(self, df: pd.DataFrame) -> "DynamicDataIO":
-        """Validates a dataframe based on the validations present in its schema definition.
-
-        All validations are checked and if any of them fails, a `SchemaValidationError` is raised.
-
-        Args:
-            df: A dataframe to be validated.
-
-        Returns:
-             self (to allow for method chaining).
-
-        Raises:
-            SchemaValidationError: if any of the validations failed. The `message` attribute of
-                the exception object is a `List[str]`, where each element is the name of a
-                validation that failed.
-        """
-        failed_validations = {}
-        for column in self.schema_validations.keys():
-            col_validations = self.schema_validations[column]
-            for validation in col_validations:
-                if validation.apply:
-                    validator = validations.ALL_VALIDATORS[validation.name]
-                    validation_result = validator(self.name, df, column, **validation.options)
-                    if not validation_result.valid:
-                        failed_validations[validation.name] = validation_result.message
-
-        if len(failed_validations) > 0:
-            raise SchemaValidationError(failed_validations)
-
-        return self
-
-    def log_metrics_from_schema(self, df: pd.DataFrame) -> "DynamicDataIO":
-        """Calculates and logs metrics based on the metrics present in its schema definition.
-
-        Args:
-            df: A dataframe for which metrics are generated and logged
-
-        Returns:
-             self (to allow for method chaining).
-        """
-        for column in self.schema_metrics.keys():
-            for metric in self.schema_metrics[column]:
-                get_metric(metric)(self.name, df, column)()  # type: ignore
-
-        return self
-
-    def _apply_schema(self, df: pd.DataFrame) -> pd.DataFrame:
-        """Called by the `self.read()` and the `self._write_to_local()` methods.
-
-        Contrasts a dataframe's read from a given source against the class's schema dictionary,
-        checking that columns are the same (by means of _has_columns and _has_valid_dtypes). Then,
-        check if the columns are fine, it further validates if the types of columns conform to the
-        expected schema. Finally, if schema types are different, then it attempts to apply schema;
-        if possible then the schema validation is successful.
-
-        Args:
-            df: A pandas dataframe.
-
-        Returns:
-            A schema validated dataframe.
-        """
-        if not self._has_valid_dtypes(df):
-            raise ColumnsDataTypeError()
-        return df
-
-    @staticmethod
-    def _transform_class_name_to_dataset_name(string_to_transform: str) -> str:
-        """Called by the init function to fetch dataset names from class name.
-
-        Used to create dataset name from class name, turns camel case into upper snake case.
-        For example: 'ThisNameABC' -> 'THIS_NAME_ABC'.
-        """
-        words = re.findall(r"\d[A-Z]+|[A-Z]?[a-z\d]+|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)|\d+|[A-Z]{2,}|[A-Z]", string_to_transform)
-        return "_".join(map(str.lower, words)).upper()
-
-    def _has_valid_dtypes(self, df: pd.DataFrame) -> bool:
-        """Checks if `df` has the expected dtypes defined in `schema`.
-
-        Schema is a dictionary object where keys are column names and values are dtypes in string format as returned by e.g.
-        `df[column].dtype.name`.
-
-        This function issues `error` level logs describing the first column that caused the check to fail.
-
-        It is assumed that `df` only has the columns defined in `schema`.
-
-        Args:
-            df:
-
-        Returns:
-            bool - `True` if `df` has the given dtypes, `False` otherwise
-        """
-        dtypes = df.dtypes
-
-        cols_allowed, cols_not_allowed = self._split_columns_to_validate_between_allowed_and_not(columns_to_validate=self.options.get("columns", self.schema.column_names))
-        if cols_not_allowed:
-            logger.exception(f"The columns passed: {cols_not_allowed} do not belong to the schema")
-            return False
-
-        for col_info in cols_allowed:
-            column_name = col_info.name
-            expected_dtype = col_info.data_type
-            found_dtype = dtypes[column_name].name
-            if found_dtype != expected_dtype:
-                if self.show_casting_warnings:
-                    logger.info(f"Expected: '{expected_dtype}' dtype for {self.name}['{column_name}]', found '{found_dtype}'")
-                try:
-                    if len(set(type(v) for v in df[column_name].values)) > 1:  # pylint: disable=consider-using-set-comprehension
-                        logger.warning(CASTING_WARNING_MSG.format(column_name, expected_dtype, found_dtype))  # pylint: disable=logging-format-interpolation
-                        logger.info(NOTICE_MSG.format(column_name))  # pylint: disable=logging-format-interpolation
-                    df[column_name] = df[column_name].astype(self.schema.columns[column_name].data_type)
-                except (ValueError, TypeError):
-                    logger.exception(f"ValueError: Tried casting column {self.name}['{column_name}'] to '{expected_dtype}' from '{found_dtype}', but failed")
-                    return False
-        return True
-
-    def _split_columns_to_validate_between_allowed_and_not(self, columns_to_validate: List[str]) -> Tuple[List[SchemaColumn], List[str]]:
-        not_allowed_cols, allowed_cols = [], []
-        for col in columns_to_validate:
-            col_from_schema = self.schema.columns.get(col)
-            if col_from_schema:
-                allowed_cols.append(col_from_schema)
-            else:
-                not_allowed_cols.append(col)
-        return allowed_cols, not_allowed_cols
-
-    @staticmethod
-    def _get_options(options_from_code: MutableMapping[str, Any], options_from_resource_definition: Optional[Mapping[str, Any]]) -> MutableMapping[str, Any]:
-        """Retrieves options either from code or from a resource-definition.
-
-        Options are merged if they are provided by both sources, while in the case of conflicts, the options from the code
-        take precedence.
-
-        Args:
-            options_from_code (Optional[Mapping])
-            options_from_resource_definition (Optional[Mapping])
-
-        Returns:
-            [Optional[Mapping]]: options that are going to be used
-        """
-        if options_from_resource_definition:
-            return {**options_from_resource_definition, **options_from_code}
-        return options_from_code
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class DynamicDataIO -(source_config: IOEnvironment, apply_schema_validations: bool = False, log_schema_metrics: bool = False, show_casting_warnings: bool = False, **options: MutableMapping[str, Any]) -
-
-

Given a src.utils.dynamicio.config.IOConfig object, it generates an object with access to a series of methods for cloud I/O operations and data validations.

-

Example

-
>>> input_sources_config = IOConfig(
->>>     "path_to/input.yaml",
->>>     os.getenv("ENVIRONMENT",default="LOCAL")
->>> )
->>>
->>> class IO(WithS3File, WithLocal, DynamicDataIO):
->>>     schema = S
->>>
->>> my_dataset_local_mapping = input_config.get(source_key="MY_DATASET")
->>> my_dataset_io = IO(my_dataset_local_mapping)
->>> my_dataset_df = my_dataset_io.read()
-
-

Class constructor.

-

Args

-
-
source_config
-
Configuration to use when reading/writing data from/to a source
-
apply_schema_validations
-
Applies schema validations on either read() or write()
-
log_schema_metrics
-
Logs schema metrics on either read() or write()
-
show_casting_warnings
-
Logs casting warnings on either read() or write() if set to True
-
options
-
Any additional kwargs that may be used throughout the lifecycle of the object
-
-
- -Expand source code - -
class DynamicDataIO:
-    """Given a `src.utils.dynamicio.config.IOConfig` object, it generates an object with access to a series of methods for cloud I/O operations and data validations.
-
-    Example:
-       >>> input_sources_config = IOConfig(
-       >>>     "path_to/input.yaml",
-       >>>     os.getenv("ENVIRONMENT",default="LOCAL")
-       >>> )
-       >>>
-       >>> class IO(WithS3File, WithLocal, DynamicDataIO):
-       >>>     schema = S
-       >>>
-       >>> my_dataset_local_mapping = input_config.get(source_key="MY_DATASET")
-       >>> my_dataset_io = IO(my_dataset_local_mapping)
-       >>> my_dataset_df = my_dataset_io.read()
-    """
-
-    schema: DataframeSchema
-    sources_config: IOEnvironment
-
-    def __init__(
-        self,
-        source_config: IOEnvironment,
-        apply_schema_validations: bool = False,
-        log_schema_metrics: bool = False,
-        show_casting_warnings: bool = False,
-        **options: MutableMapping[str, Any],
-    ):
-        """Class constructor.
-
-        Args:
-            source_config: Configuration to use when reading/writing data from/to a source
-            apply_schema_validations: Applies schema validations on either read() or write()
-            log_schema_metrics: Logs schema metrics on either read() or write()
-            show_casting_warnings: Logs casting warnings on either read() or write() if set to True
-            options: Any additional kwargs that may be used throughout the lifecycle of the object
-        """
-        if type(self) is DynamicDataIO:  # pylint: disable=unidiomatic-typecheck
-            raise TypeError("Abstract class DynamicDataIO cannot be used to instantiate an object...")
-
-        self.sources_config = source_config
-        self.name = self._transform_class_name_to_dataset_name(self.__class__.__name__)
-        self.apply_schema_validations = apply_schema_validations
-        self.log_schema_metrics = log_schema_metrics
-        self.show_casting_warnings = show_casting_warnings
-        self.options = self._get_options(options, source_config.options)
-        source_name = self.sources_config.data_backend_type.value
-        if self.schema is SCHEMA_FROM_FILE:
-            active_schema = self.sources_config.dynamicio_schema
-        else:
-            active_schema = self._schema_from_obj(self)
-
-        if not active_schema:
-            raise SchemaNotFoundError()
-
-        assert isinstance(active_schema, DataframeSchema)
-        self.schema = active_schema
-        self.name = self.schema.name.upper()
-        self.schema_validations = self.schema.validations
-        self.schema_metrics = self.schema.metrics
-
-        assert hasattr(self, f"_read_from_{source_name}") or hasattr(
-            self, f"_write_to_{source_name}"
-        ), f"No method '_read_from_{source_name}' or '_write_to_{source_name}'. Have you registered a mixin for {source_name}?"
-
-    @staticmethod
-    def _schema_from_obj(target) -> DataframeSchema:
-        """Construct `DataframeSchema` from an object.
-
-        The object:
-            - MUST have `schema` attribute that is a dictionary specifying columns and datatypes
-            - CAN have `schema_validations` and `schema_metrics` attributes
-        """
-        col_info = {}
-        for (col_name, dtype) in target.schema.items():
-            col_validations = {}
-            col_metrics = []
-            try:
-                col_validations = target.schema_validations[col_name]
-            except (KeyError, AttributeError):
-                pass
-            try:
-                col_metrics = target.schema_metrics[col_name]
-            except (KeyError, AttributeError):
-                pass
-            col_info[col_name] = {
-                "type": dtype,
-                "validations": col_validations,
-                "metrics": col_metrics,
-            }
-        try:
-            out = DataframeSchema(name=target.name, columns=col_info)
-        except pydantic.ValidationError:
-            logger.exception(f"Error parsing {target.name=!r} {col_info=!r}")
-            raise
-        return out
-
-    def __init_subclass__(cls):
-        """Ensure that all subclasses have a `schema` attribute and a `validate` method.
-
-        Raises:
-            AssertionError: If either of the attributes is not implemented
-        """
-        if not inspect.getmodule(cls).__name__.startswith("dynamicio"):
-            assert "schema" in cls.__dict__
-
-            if cls.schema is None or (cls.schema is not SCHEMA_FROM_FILE and len(cls.schema) == 0):
-                raise ValueError(f"schema for class {cls} cannot be None or empty...")
-
-    async def async_read(self):
-        """Allows the use of asyncio to concurrently read files in memory.
-
-        Returns:
-            A pandas dataframe or an iterable.
-        """
-        loop = asyncio.get_running_loop()
-        return await loop.run_in_executor(pool, self.read)
-
-    def read(self) -> pd.DataFrame:
-        """Reads data source and returns a schema validated dataframe (by means of _apply_schema).
-
-        Returns:
-            A pandas dataframe or an iterable.
-        """
-        source_name = self.sources_config.data_backend_type.value
-        df = getattr(self, f"_read_from_{source_name}")()
-
-        df = self._apply_schema(df)
-        if self.apply_schema_validations:
-            self.validate_from_schema(df)
-        if self.log_schema_metrics:
-            self.log_metrics_from_schema(df)
-
-        return df
-
-    async def async_write(self, df: pd.DataFrame):
-        """Allows the use of asyncio to concurrently write files out.
-
-        Args:
-            df: The data to be written
-        """
-        loop = asyncio.get_running_loop()
-        return await loop.run_in_executor(pool, self.write, df)
-
-    def write(self, df: pd.DataFrame):
-        """Sink data to a given source based on the sources_config.
-
-        Args:
-            df: The data to be written
-        """
-        source_name = self.sources_config.data_backend_type.value
-        if set(df.columns) != self.schema.column_names:  # pylint: disable=E1101
-            columns = [column for column in df.columns.to_list() if column in self.schema.column_names]
-            df = df[columns]
-
-        if self.apply_schema_validations:
-            self.validate_from_schema(df)
-        if self.log_schema_metrics:
-            self.log_metrics_from_schema(df)
-
-        getattr(self, f"_write_to_{source_name}")(self._apply_schema(df))
-
-    def validate_from_schema(self, df: pd.DataFrame) -> "DynamicDataIO":
-        """Validates a dataframe based on the validations present in its schema definition.
-
-        All validations are checked and if any of them fails, a `SchemaValidationError` is raised.
-
-        Args:
-            df: A dataframe to be validated.
-
-        Returns:
-             self (to allow for method chaining).
-
-        Raises:
-            SchemaValidationError: if any of the validations failed. The `message` attribute of
-                the exception object is a `List[str]`, where each element is the name of a
-                validation that failed.
-        """
-        failed_validations = {}
-        for column in self.schema_validations.keys():
-            col_validations = self.schema_validations[column]
-            for validation in col_validations:
-                if validation.apply:
-                    validator = validations.ALL_VALIDATORS[validation.name]
-                    validation_result = validator(self.name, df, column, **validation.options)
-                    if not validation_result.valid:
-                        failed_validations[validation.name] = validation_result.message
-
-        if len(failed_validations) > 0:
-            raise SchemaValidationError(failed_validations)
-
-        return self
-
-    def log_metrics_from_schema(self, df: pd.DataFrame) -> "DynamicDataIO":
-        """Calculates and logs metrics based on the metrics present in its schema definition.
-
-        Args:
-            df: A dataframe for which metrics are generated and logged
-
-        Returns:
-             self (to allow for method chaining).
-        """
-        for column in self.schema_metrics.keys():
-            for metric in self.schema_metrics[column]:
-                get_metric(metric)(self.name, df, column)()  # type: ignore
-
-        return self
-
-    def _apply_schema(self, df: pd.DataFrame) -> pd.DataFrame:
-        """Called by the `self.read()` and the `self._write_to_local()` methods.
-
-        Contrasts a dataframe's read from a given source against the class's schema dictionary,
-        checking that columns are the same (by means of _has_columns and _has_valid_dtypes). Then,
-        check if the columns are fine, it further validates if the types of columns conform to the
-        expected schema. Finally, if schema types are different, then it attempts to apply schema;
-        if possible then the schema validation is successful.
-
-        Args:
-            df: A pandas dataframe.
-
-        Returns:
-            A schema validated dataframe.
-        """
-        if not self._has_valid_dtypes(df):
-            raise ColumnsDataTypeError()
-        return df
-
-    @staticmethod
-    def _transform_class_name_to_dataset_name(string_to_transform: str) -> str:
-        """Called by the init function to fetch dataset names from class name.
-
-        Used to create dataset name from class name, turns camel case into upper snake case.
-        For example: 'ThisNameABC' -> 'THIS_NAME_ABC'.
-        """
-        words = re.findall(r"\d[A-Z]+|[A-Z]?[a-z\d]+|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)|\d+|[A-Z]{2,}|[A-Z]", string_to_transform)
-        return "_".join(map(str.lower, words)).upper()
-
-    def _has_valid_dtypes(self, df: pd.DataFrame) -> bool:
-        """Checks if `df` has the expected dtypes defined in `schema`.
-
-        Schema is a dictionary object where keys are column names and values are dtypes in string format as returned by e.g.
-        `df[column].dtype.name`.
-
-        This function issues `error` level logs describing the first column that caused the check to fail.
-
-        It is assumed that `df` only has the columns defined in `schema`.
-
-        Args:
-            df:
-
-        Returns:
-            bool - `True` if `df` has the given dtypes, `False` otherwise
-        """
-        dtypes = df.dtypes
-
-        cols_allowed, cols_not_allowed = self._split_columns_to_validate_between_allowed_and_not(columns_to_validate=self.options.get("columns", self.schema.column_names))
-        if cols_not_allowed:
-            logger.exception(f"The columns passed: {cols_not_allowed} do not belong to the schema")
-            return False
-
-        for col_info in cols_allowed:
-            column_name = col_info.name
-            expected_dtype = col_info.data_type
-            found_dtype = dtypes[column_name].name
-            if found_dtype != expected_dtype:
-                if self.show_casting_warnings:
-                    logger.info(f"Expected: '{expected_dtype}' dtype for {self.name}['{column_name}]', found '{found_dtype}'")
-                try:
-                    if len(set(type(v) for v in df[column_name].values)) > 1:  # pylint: disable=consider-using-set-comprehension
-                        logger.warning(CASTING_WARNING_MSG.format(column_name, expected_dtype, found_dtype))  # pylint: disable=logging-format-interpolation
-                        logger.info(NOTICE_MSG.format(column_name))  # pylint: disable=logging-format-interpolation
-                    df[column_name] = df[column_name].astype(self.schema.columns[column_name].data_type)
-                except (ValueError, TypeError):
-                    logger.exception(f"ValueError: Tried casting column {self.name}['{column_name}'] to '{expected_dtype}' from '{found_dtype}', but failed")
-                    return False
-        return True
-
-    def _split_columns_to_validate_between_allowed_and_not(self, columns_to_validate: List[str]) -> Tuple[List[SchemaColumn], List[str]]:
-        not_allowed_cols, allowed_cols = [], []
-        for col in columns_to_validate:
-            col_from_schema = self.schema.columns.get(col)
-            if col_from_schema:
-                allowed_cols.append(col_from_schema)
-            else:
-                not_allowed_cols.append(col)
-        return allowed_cols, not_allowed_cols
-
-    @staticmethod
-    def _get_options(options_from_code: MutableMapping[str, Any], options_from_resource_definition: Optional[Mapping[str, Any]]) -> MutableMapping[str, Any]:
-        """Retrieves options either from code or from a resource-definition.
-
-        Options are merged if they are provided by both sources, while in the case of conflicts, the options from the code
-        take precedence.
-
-        Args:
-            options_from_code (Optional[Mapping])
-            options_from_resource_definition (Optional[Mapping])
-
-        Returns:
-            [Optional[Mapping]]: options that are going to be used
-        """
-        if options_from_resource_definition:
-            return {**options_from_resource_definition, **options_from_code}
-        return options_from_code
-
-

Subclasses

- -

Class variables

-
-
var schemaDataframeSchema
-
-
-
-
var sources_configIOEnvironment
-
-
-
-
-

Methods

-
-
-async def async_read(self) -
-
-

Allows the use of asyncio to concurrently read files in memory.

-

Returns

-

A pandas dataframe or an iterable.

-
- -Expand source code - -
async def async_read(self):
-    """Allows the use of asyncio to concurrently read files in memory.
-
-    Returns:
-        A pandas dataframe or an iterable.
-    """
-    loop = asyncio.get_running_loop()
-    return await loop.run_in_executor(pool, self.read)
-
-
-
-async def async_write(self, df: pandas.core.frame.DataFrame) -
-
-

Allows the use of asyncio to concurrently write files out.

-

Args

-
-
df
-
The data to be written
-
-
- -Expand source code - -
async def async_write(self, df: pd.DataFrame):
-    """Allows the use of asyncio to concurrently write files out.
-
-    Args:
-        df: The data to be written
-    """
-    loop = asyncio.get_running_loop()
-    return await loop.run_in_executor(pool, self.write, df)
-
-
-
-def log_metrics_from_schema(self, df: pandas.core.frame.DataFrame) ‑> DynamicDataIO -
-
-

Calculates and logs metrics based on the metrics present in its schema definition.

-

Args

-
-
df
-
A dataframe for which metrics are generated and logged
-
-

Returns

-

self (to allow for method chaining).

-
- -Expand source code - -
def log_metrics_from_schema(self, df: pd.DataFrame) -> "DynamicDataIO":
-    """Calculates and logs metrics based on the metrics present in its schema definition.
-
-    Args:
-        df: A dataframe for which metrics are generated and logged
-
-    Returns:
-         self (to allow for method chaining).
-    """
-    for column in self.schema_metrics.keys():
-        for metric in self.schema_metrics[column]:
-            get_metric(metric)(self.name, df, column)()  # type: ignore
-
-    return self
-
-
-
-def read(self) ‑> pandas.core.frame.DataFrame -
-
-

Reads data source and returns a schema validated dataframe (by means of _apply_schema).

-

Returns

-

A pandas dataframe or an iterable.

-
- -Expand source code - -
def read(self) -> pd.DataFrame:
-    """Reads data source and returns a schema validated dataframe (by means of _apply_schema).
-
-    Returns:
-        A pandas dataframe or an iterable.
-    """
-    source_name = self.sources_config.data_backend_type.value
-    df = getattr(self, f"_read_from_{source_name}")()
-
-    df = self._apply_schema(df)
-    if self.apply_schema_validations:
-        self.validate_from_schema(df)
-    if self.log_schema_metrics:
-        self.log_metrics_from_schema(df)
-
-    return df
-
-
-
-def validate_from_schema(self, df: pandas.core.frame.DataFrame) ‑> DynamicDataIO -
-
-

Validates a dataframe based on the validations present in its schema definition.

-

All validations are checked and if any of them fails, a SchemaValidationError is raised.

-

Args

-
-
df
-
A dataframe to be validated.
-
-

Returns

-

self (to allow for method chaining).

-

Raises

-
-
SchemaValidationError
-
if any of the validations failed. The message attribute of -the exception object is a List[str], where each element is the name of a -validation that failed.
-
-
- -Expand source code - -
def validate_from_schema(self, df: pd.DataFrame) -> "DynamicDataIO":
-    """Validates a dataframe based on the validations present in its schema definition.
-
-    All validations are checked and if any of them fails, a `SchemaValidationError` is raised.
-
-    Args:
-        df: A dataframe to be validated.
-
-    Returns:
-         self (to allow for method chaining).
-
-    Raises:
-        SchemaValidationError: if any of the validations failed. The `message` attribute of
-            the exception object is a `List[str]`, where each element is the name of a
-            validation that failed.
-    """
-    failed_validations = {}
-    for column in self.schema_validations.keys():
-        col_validations = self.schema_validations[column]
-        for validation in col_validations:
-            if validation.apply:
-                validator = validations.ALL_VALIDATORS[validation.name]
-                validation_result = validator(self.name, df, column, **validation.options)
-                if not validation_result.valid:
-                    failed_validations[validation.name] = validation_result.message
-
-    if len(failed_validations) > 0:
-        raise SchemaValidationError(failed_validations)
-
-    return self
-
-
-
-def write(self, df: pandas.core.frame.DataFrame) -
-
-

Sink data to a given source based on the sources_config.

-

Args

-
-
df
-
The data to be written
-
-
- -Expand source code - -
def write(self, df: pd.DataFrame):
-    """Sink data to a given source based on the sources_config.
-
-    Args:
-        df: The data to be written
-    """
-    source_name = self.sources_config.data_backend_type.value
-    if set(df.columns) != self.schema.column_names:  # pylint: disable=E1101
-        columns = [column for column in df.columns.to_list() if column in self.schema.column_names]
-        df = df[columns]
-
-    if self.apply_schema_validations:
-        self.validate_from_schema(df)
-    if self.log_schema_metrics:
-        self.log_metrics_from_schema(df)
-
-    getattr(self, f"_write_to_{source_name}")(self._apply_schema(df))
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/config/index.html b/docs/dynamicio/config/index.html deleted file mode 100644 index 51aaa51..0000000 --- a/docs/dynamicio/config/index.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - -dynamicio.config API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config

-
-
-

Dynamicio config file handling routines.

-
- -Expand source code - -
"""Dynamicio config file handling routines."""
-
-from dynamicio.config import pydantic
-from dynamicio.config.io_config import IOConfig
-
-
-
-

Sub-modules

-
-
dynamicio.config.io_config
-
-

Implements the IOConfig class, generating objects used as a configuration parameter for the instantiation …

-
-
dynamicio.config.pydantic
-
-

Pydantic config models.

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/config/io_config.html b/docs/dynamicio/config/io_config.html deleted file mode 100644 index cde95ad..0000000 --- a/docs/dynamicio/config/io_config.html +++ /dev/null @@ -1,1016 +0,0 @@ - - - - - - -dynamicio.config.io_config API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.io_config

-
-
-

Implements the IOConfig class, generating objects used as a configuration parameter for the instantiation ofsrc.utils.dynamicio.dataio.DynamicDataIO objects.

-

The IOConfig object, essentially parses a yaml file that contains a set of input sources that will be processed by a -task, converting filtering and converting them into dictionaries.

-

For example, suppose an input.yaml file, containing:

-
READ_FROM_S3_CSV:
-  LOCAL:
-    type: "local"
-    local:
-      file_path: "[[ TEST_RESOURCES ]]/data/input/some_csv_to_read.csv"
-      file_type: "csv"
-  CLOUD:
-    type: "s3"
-    s3:
-      bucket: "[[ MOCK_BUCKET ]]"
-      file_path: "[[ MOCK_KEY ]]"
-      file_type: "csv"
-
-

would be loaded with:

-
input_sources_config = IOConfig(
-        "path_to/input.yaml",
-        env_identifier="CLOUD",
-        dynamic_vars=config_module
-    )
-
-

and:

-
input_sources_config.config
-
-

would return:

-
    {
-        "READ_FROM_S3_CSV": {
-            "LOCAL": {
-                "type": "local",
-                "local": {
-                    "file_path": f"{test_global_vars.TEST_RESOURCES}/data/input/some_csv_to_read.csv",
-                    "file_type": "csv",
-                },
-            },
-            "CLOUD": {
-                "type": "s3",
-                "s3": {
-                    "bucket": "mock-bucket",
-                    "file_path": "mock-key",
-                    "file_type": "csv"
-                }
-            },
-        }
-    }
-
-
- -Expand source code - -
"""Implements the `IOConfig` class, generating objects used as a configuration parameter for the instantiation of`src.utils.dynamicio.dataio.DynamicDataIO` objects.
-
-The `IOConfig` object, essentially parses a yaml file that contains a set of input sources that will be processed by a
-task, converting filtering and converting them into dictionaries.
-
-For example, suppose an `input.yaml` file, containing:
-
-    READ_FROM_S3_CSV:
-      LOCAL:
-        type: "local"
-        local:
-          file_path: "[[ TEST_RESOURCES ]]/data/input/some_csv_to_read.csv"
-          file_type: "csv"
-      CLOUD:
-        type: "s3"
-        s3:
-          bucket: "[[ MOCK_BUCKET ]]"
-          file_path: "[[ MOCK_KEY ]]"
-          file_type: "csv"
-
-would be loaded with:
-
-    input_sources_config = IOConfig(
-            "path_to/input.yaml",
-            env_identifier="CLOUD",
-            dynamic_vars=config_module
-        )
-
-and:
-
-    input_sources_config.config
-
-would return:
-
-        {
-            "READ_FROM_S3_CSV": {
-                "LOCAL": {
-                    "type": "local",
-                    "local": {
-                        "file_path": f"{test_global_vars.TEST_RESOURCES}/data/input/some_csv_to_read.csv",
-                        "file_type": "csv",
-                    },
-                },
-                "CLOUD": {
-                    "type": "s3",
-                    "s3": {
-                        "bucket": "mock-bucket",
-                        "file_path": "mock-key",
-                        "file_type": "csv"
-                    }
-                },
-            }
-        }
-"""
-__all__ = ["IOConfig", "SafeDynamicResourceLoader", "SafeDynamicSchemaLoader"]
-
-import re
-from types import ModuleType
-from typing import Any, List, MutableMapping
-
-import pydantic
-import yaml
-from magic_logger import logger
-
-from dynamicio.config.pydantic import BindingsYaml, IOEnvironment
-
-
-class SafeDynamicResourceLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) -> str:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-        return value
-
-
-class SafeDynamicSchemaLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) -> Any:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-            try:
-                value = float(value)
-                return value
-            except ValueError:
-                pass
-
-        return value
-
-
-class IOConfig:
-    """Generates an object that returns a sub-dictionary of the elements of that yaml file.
-
-    The file serves as a config for setting up DynamicDataIO objects. Requires a resources yaml file,
-    an ENVIRONMENT value {CLOUD or LOCAL} and a vars module.
-
-    Example:
-        input_sources_config = IOConfig(
-            "path_to/input.yaml",
-            env_identifier="CLOUD",
-            dynamic_vars=config_module
-        )
-    """
-
-    YAML_TAG = "tag:yaml.org,2002:str"
-    SafeDynamicResourceLoader.add_constructor(YAML_TAG, SafeDynamicResourceLoader.dyn_str_constructor)
-    SafeDynamicSchemaLoader.add_constructor(YAML_TAG, SafeDynamicSchemaLoader.dyn_value_constructor)
-
-    path_to_source_yaml: str
-    env_identifier: str
-    config: BindingsYaml
-
-    def __init__(self, path_to_source_yaml: str, env_identifier: str, dynamic_vars: ModuleType):
-        """Class constructor.
-
-        Args:
-            path_to_source_yaml: Absolute file path to yaml file containing source definitions
-            env_identifier: "LOCAL" or "CLOUD".
-            dynamic_vars: module containing values for dynamic values that the source yaml
-                may reference.
-        """
-        self.path_to_source_yaml = path_to_source_yaml
-        self.env_identifier = env_identifier
-        self.dynamic_vars = dynamic_vars
-        self.config = self._parse_sources_config()
-
-    def _parse_sources_config(self) -> BindingsYaml:
-        """Parses the yaml input and return a dictionary.
-
-        Returns:
-            A dictionary with the list of all file paths pointing to various input sources as those
-            are defined in their respective data/*.yaml files.
-        """
-        used_file_inputs = [self.path_to_source_yaml]
-        with open(self.path_to_source_yaml, "r") as stream:  # pylint: disable=unspecified-encoding]
-            logger.debug(f"Parsing {self.path_to_source_yaml}...")
-            data = yaml.load(stream, SafeDynamicResourceLoader.with_module(self.dynamic_vars))
-
-        # Load any file_path's found in schema definitions
-        for io_binding in data.values():
-            if isinstance(io_binding, MutableMapping) and io_binding.get("schema", {}).get("file_path"):
-                file_path = io_binding["schema"]["file_path"]
-                used_file_inputs.append(file_path)
-                # schema has `file_path`` in it
-                with open(file_path, "r", encoding="utf8") as stream:
-                    io_binding["schema"] = yaml.load(stream, SafeDynamicSchemaLoader.with_module(self.dynamic_vars))
-
-        try:
-            config = BindingsYaml(bindings=data)
-            config.update_config_refs()
-        except pydantic.ValidationError:
-            logger.exception(f"Error loading {data=!r}, {used_file_inputs=!r}")
-            raise
-        return config
-
-    @property
-    def sources(self) -> List[str]:
-        """Class property for easy access to a list of sources.
-
-        Returns:
-            All top level names of the available resources for the used resources yaml config.
-        """
-        return list(self.config.bindings.keys())
-
-    def get(self, source_key: str) -> IOEnvironment:
-        """A getter.
-
-        Args:
-            source_key: The name of the resource for which we want to create a config.
-
-        Returns:
-            A dictionary with the necessary fields for loading the data from a source.
-
-        Example:
-
-            Given:
-
-                VOYAGE_DATA:
-                  LOCAL:
-                    type: "local"
-                    local:
-                      file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-                      file_type: "parquet"
-                  CLOUD:
-                    type: "kafka"
-                    KAFKA:
-                      KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-                      KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-            If you do:
-
-                input_sources_config = IOConfig(
-                    "path_to/input.yaml",
-                    env_identifier="CLOUD",
-                    dynamic_vars=globals
-                )
-                voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-            then `voyage_data_cloud_mapping` is:
-
-                "KAFKA": {
-                    "KAFKA_SERVER": "mock-kafka-server",
-                    "KAFKA_TOPIC": "mock-kafka-topic"
-                }
-        """
-        return self.config.bindings[source_key].get_binding_for_environment(self.env_identifier)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class IOConfig -(path_to_source_yaml: str, env_identifier: str, dynamic_vars: module) -
-
-

Generates an object that returns a sub-dictionary of the elements of that yaml file.

-

The file serves as a config for setting up DynamicDataIO objects. Requires a resources yaml file, -an ENVIRONMENT value {CLOUD or LOCAL} and a vars module.

-

Example

-

input_sources_config = IOConfig( -"path_to/input.yaml", -env_identifier="CLOUD", -dynamic_vars=config_module -)

-

Class constructor.

-

Args

-
-
path_to_source_yaml
-
Absolute file path to yaml file containing source definitions
-
env_identifier
-
"LOCAL" or "CLOUD".
-
dynamic_vars
-
module containing values for dynamic values that the source yaml -may reference.
-
-
- -Expand source code - -
class IOConfig:
-    """Generates an object that returns a sub-dictionary of the elements of that yaml file.
-
-    The file serves as a config for setting up DynamicDataIO objects. Requires a resources yaml file,
-    an ENVIRONMENT value {CLOUD or LOCAL} and a vars module.
-
-    Example:
-        input_sources_config = IOConfig(
-            "path_to/input.yaml",
-            env_identifier="CLOUD",
-            dynamic_vars=config_module
-        )
-    """
-
-    YAML_TAG = "tag:yaml.org,2002:str"
-    SafeDynamicResourceLoader.add_constructor(YAML_TAG, SafeDynamicResourceLoader.dyn_str_constructor)
-    SafeDynamicSchemaLoader.add_constructor(YAML_TAG, SafeDynamicSchemaLoader.dyn_value_constructor)
-
-    path_to_source_yaml: str
-    env_identifier: str
-    config: BindingsYaml
-
-    def __init__(self, path_to_source_yaml: str, env_identifier: str, dynamic_vars: ModuleType):
-        """Class constructor.
-
-        Args:
-            path_to_source_yaml: Absolute file path to yaml file containing source definitions
-            env_identifier: "LOCAL" or "CLOUD".
-            dynamic_vars: module containing values for dynamic values that the source yaml
-                may reference.
-        """
-        self.path_to_source_yaml = path_to_source_yaml
-        self.env_identifier = env_identifier
-        self.dynamic_vars = dynamic_vars
-        self.config = self._parse_sources_config()
-
-    def _parse_sources_config(self) -> BindingsYaml:
-        """Parses the yaml input and return a dictionary.
-
-        Returns:
-            A dictionary with the list of all file paths pointing to various input sources as those
-            are defined in their respective data/*.yaml files.
-        """
-        used_file_inputs = [self.path_to_source_yaml]
-        with open(self.path_to_source_yaml, "r") as stream:  # pylint: disable=unspecified-encoding]
-            logger.debug(f"Parsing {self.path_to_source_yaml}...")
-            data = yaml.load(stream, SafeDynamicResourceLoader.with_module(self.dynamic_vars))
-
-        # Load any file_path's found in schema definitions
-        for io_binding in data.values():
-            if isinstance(io_binding, MutableMapping) and io_binding.get("schema", {}).get("file_path"):
-                file_path = io_binding["schema"]["file_path"]
-                used_file_inputs.append(file_path)
-                # schema has `file_path`` in it
-                with open(file_path, "r", encoding="utf8") as stream:
-                    io_binding["schema"] = yaml.load(stream, SafeDynamicSchemaLoader.with_module(self.dynamic_vars))
-
-        try:
-            config = BindingsYaml(bindings=data)
-            config.update_config_refs()
-        except pydantic.ValidationError:
-            logger.exception(f"Error loading {data=!r}, {used_file_inputs=!r}")
-            raise
-        return config
-
-    @property
-    def sources(self) -> List[str]:
-        """Class property for easy access to a list of sources.
-
-        Returns:
-            All top level names of the available resources for the used resources yaml config.
-        """
-        return list(self.config.bindings.keys())
-
-    def get(self, source_key: str) -> IOEnvironment:
-        """A getter.
-
-        Args:
-            source_key: The name of the resource for which we want to create a config.
-
-        Returns:
-            A dictionary with the necessary fields for loading the data from a source.
-
-        Example:
-
-            Given:
-
-                VOYAGE_DATA:
-                  LOCAL:
-                    type: "local"
-                    local:
-                      file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-                      file_type: "parquet"
-                  CLOUD:
-                    type: "kafka"
-                    KAFKA:
-                      KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-                      KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-            If you do:
-
-                input_sources_config = IOConfig(
-                    "path_to/input.yaml",
-                    env_identifier="CLOUD",
-                    dynamic_vars=globals
-                )
-                voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-            then `voyage_data_cloud_mapping` is:
-
-                "KAFKA": {
-                    "KAFKA_SERVER": "mock-kafka-server",
-                    "KAFKA_TOPIC": "mock-kafka-topic"
-                }
-        """
-        return self.config.bindings[source_key].get_binding_for_environment(self.env_identifier)
-
-

Class variables

-
-
var YAML_TAG
-
-
-
-
var configBindingsYaml
-
-
-
-
var env_identifier : str
-
-
-
-
var path_to_source_yaml : str
-
-
-
-
-

Instance variables

-
-
var sources : List[str]
-
-

Class property for easy access to a list of sources.

-

Returns

-

All top level names of the available resources for the used resources yaml config.

-
- -Expand source code - -
@property
-def sources(self) -> List[str]:
-    """Class property for easy access to a list of sources.
-
-    Returns:
-        All top level names of the available resources for the used resources yaml config.
-    """
-    return list(self.config.bindings.keys())
-
-
-
-

Methods

-
-
-def get(self, source_key: str) ‑> IOEnvironment -
-
-

A getter.

-

Args

-
-
source_key
-
The name of the resource for which we want to create a config.
-
-

Returns

-

A dictionary with the necessary fields for loading the data from a source.

-

Example

-

Given:

-
VOYAGE_DATA:
-  LOCAL:
-    type: "local"
-    local:
-      file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-      file_type: "parquet"
-  CLOUD:
-    type: "kafka"
-    KAFKA:
-      KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-      KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-

If you do:

-
input_sources_config = IOConfig(
-    "path_to/input.yaml",
-    env_identifier="CLOUD",
-    dynamic_vars=globals
-)
-voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-

then voyage_data_cloud_mapping is:

-
"KAFKA": {
-    "KAFKA_SERVER": "mock-kafka-server",
-    "KAFKA_TOPIC": "mock-kafka-topic"
-}
-
-
- -Expand source code - -
def get(self, source_key: str) -> IOEnvironment:
-    """A getter.
-
-    Args:
-        source_key: The name of the resource for which we want to create a config.
-
-    Returns:
-        A dictionary with the necessary fields for loading the data from a source.
-
-    Example:
-
-        Given:
-
-            VOYAGE_DATA:
-              LOCAL:
-                type: "local"
-                local:
-                  file_path: "[[ TEST_RESOURCES ]]/data/processed/voyage_data.parquet"
-                  file_type: "parquet"
-              CLOUD:
-                type: "kafka"
-                KAFKA:
-                  KAFKA_SERVER: "[[ KAFKA_SERVER ]]"
-                  KAFKA_TOPIC: "[[ KAFKA_TOPIC ]]"
-
-        If you do:
-
-            input_sources_config = IOConfig(
-                "path_to/input.yaml",
-                env_identifier="CLOUD",
-                dynamic_vars=globals
-            )
-            voyage_data_cloud_mapping = input_config.get(source_key="VOYAGE_DATA")
-
-        then `voyage_data_cloud_mapping` is:
-
-            "KAFKA": {
-                "KAFKA_SERVER": "mock-kafka-server",
-                "KAFKA_TOPIC": "mock-kafka-topic"
-            }
-    """
-    return self.config.bindings[source_key].get_binding_for_environment(self.env_identifier)
-
-
-
-
-
-class SafeDynamicResourceLoader -(stream) -
-
-

Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].

-

Dynamic variables defined in a provided module object.

-

Initialize the scanner.

-
- -Expand source code - -
class SafeDynamicResourceLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) -> str:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-        return value
-
-

Ancestors

-
    -
  • yaml.loader.SafeLoader
  • -
  • yaml.reader.Reader
  • -
  • yaml.scanner.Scanner
  • -
  • yaml.parser.Parser
  • -
  • yaml.composer.Composer
  • -
  • yaml.constructor.SafeConstructor
  • -
  • yaml.constructor.BaseConstructor
  • -
  • yaml.resolver.Resolver
  • -
  • yaml.resolver.BaseResolver
  • -
-

Class variables

-
-
var dynamic_data_matcher
-
-
-
-
var module
-
-
-
-
var yaml_constructors
-
-
-
-
-

Static methods

-
-
-def with_module(module: module) -
-
-

Creates a dynamic subclass of SafeDynamicLoader with the data_module attribute set to module.

-

Args

-
-
module
-
A global vars module with all the dynamic values defined in it.
-
-

Returns

-

type

-
- -Expand source code - -
@classmethod
-def with_module(cls, module: ModuleType):
-    """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-    Args:
-        module: A global vars module with all the dynamic values defined in it.
-
-    Returns:
-        type
-    """
-    return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-
-
-

Methods

-
-
-def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) ‑> str -
-
-

Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.

-

Args

-
-
node
-
Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention -are replaced with the respective attributes in te provided module.
-
-

Returns

-

Constructed str or numerical.

-
- -Expand source code - -
def dyn_str_constructor(self, node: yaml.nodes.ScalarNode) -> str:
-    """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-    Args:
-        node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-            are replaced with the respective attributes in te provided module.
-
-    Returns:
-        Constructed `str` or numerical.
-    """
-    value = node.value
-
-    while result := self.dynamic_data_matcher.match(value):
-        ref = result.group(3)
-        replacement = getattr(self.module, ref)
-
-        value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-    return value
-
-
-
-
-
-class SafeDynamicSchemaLoader -(stream) -
-
-

Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].

-

Dynamic variables defined in a provided module object.

-

Initialize the scanner.

-
- -Expand source code - -
class SafeDynamicSchemaLoader(yaml.SafeLoader):
-    """Implements a dynamic yaml loader that parses yaml files and replaces strings that map to [[ DYNAMIC_VAR ]].
-
-    Dynamic variables defined in a provided module object.
-    """
-
-    module = None
-    dynamic_data_matcher = re.compile(r"(.*)(\[\[\s*(\S+)\s*]])(.*)")
-
-    @classmethod
-    def with_module(cls, module: ModuleType):
-        """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-        Args:
-            module: A global vars module with all the dynamic values defined in it.
-
-        Returns:
-            type
-        """
-        return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-    def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) -> Any:
-        """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-        Args:
-            node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-                are replaced with the respective attributes in te provided module.
-
-        Returns:
-            Constructed `str` or numerical.
-        """
-        value = node.value
-
-        while result := self.dynamic_data_matcher.match(value):
-            ref = result.group(3)
-            replacement = getattr(self.module, ref)
-
-            value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-            try:
-                value = float(value)
-                return value
-            except ValueError:
-                pass
-
-        return value
-
-

Ancestors

-
    -
  • yaml.loader.SafeLoader
  • -
  • yaml.reader.Reader
  • -
  • yaml.scanner.Scanner
  • -
  • yaml.parser.Parser
  • -
  • yaml.composer.Composer
  • -
  • yaml.constructor.SafeConstructor
  • -
  • yaml.constructor.BaseConstructor
  • -
  • yaml.resolver.Resolver
  • -
  • yaml.resolver.BaseResolver
  • -
-

Class variables

-
-
var dynamic_data_matcher
-
-
-
-
var module
-
-
-
-
var yaml_constructors
-
-
-
-
-

Static methods

-
-
-def with_module(module: module) -
-
-

Creates a dynamic subclass of SafeDynamicLoader with the data_module attribute set to module.

-

Args

-
-
module
-
A global vars module with all the dynamic values defined in it.
-
-

Returns

-

type

-
- -Expand source code - -
@classmethod
-def with_module(cls, module: ModuleType):
-    """Creates a dynamic subclass of SafeDynamicLoader with the `data_module` attribute set to `module`.
-
-    Args:
-        module: A global vars module with all the dynamic values defined in it.
-
-    Returns:
-        type
-    """
-    return type(f"{cls.__name__}_{module.__name__}", (cls,), {"module": module})
-
-
-
-

Methods

-
-
-def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) ‑> Any -
-
-

Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.

-

Args

-
-
node
-
Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention -are replaced with the respective attributes in te provided module.
-
-

Returns

-

Constructed str or numerical.

-
- -Expand source code - -
def dyn_value_constructor(self, node: yaml.nodes.ScalarNode) -> Any:
-    """Responsible for the switching of one or more "[[ DYNAMIC_VAR ]]" strings with the respective attributes value in a given module.
-
-    Args:
-        node: Parsed item whose dynamic values that map to the "[[ DYNAMIC_VAR ]]" convention
-            are replaced with the respective attributes in te provided module.
-
-    Returns:
-        Constructed `str` or numerical.
-    """
-    value = node.value
-
-    while result := self.dynamic_data_matcher.match(value):
-        ref = result.group(3)
-        replacement = getattr(self.module, ref)
-
-        value = self.dynamic_data_matcher.sub(f"\\g<1>{replacement}\\g<4>", value)
-
-        try:
-            value = float(value)
-            return value
-        except ValueError:
-            pass
-
-    return value
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/config/pydantic/config.html b/docs/dynamicio/config/pydantic/config.html deleted file mode 100644 index 33e7d0f..0000000 --- a/docs/dynamicio/config/pydantic/config.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - -dynamicio.config.pydantic.config API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic.config

-
-
-

Pydantic schema for YAML files

-
- -Expand source code - -
# pylint: disable=no-member, no-self-argument, unused-argument
-"""Pydantic schema for YAML files"""
-
-from typing import Mapping, MutableMapping
-
-import pydantic
-
-import dynamicio.config.pydantic.io_resources as env_spec
-
-
-class BindingsYaml(pydantic.BaseModel):
-    """Class controlling structure of the top-level IOConfig yaml file.
-
-    The top-level config is a dictionary of <binding_name> -> <env_name>
-    """
-
-    bindings: Mapping[str, env_spec.IOBinding]
-
-    @pydantic.validator("bindings", pre=True)
-    def _validate_bindings(cls, value: Mapping):
-        if not isinstance(value, Mapping):
-            raise ValueError(f"Bindings must be a mapping. (got {value!r} instead).")
-        # Tell each binding its name
-        for (name, sub_config) in value.items():
-            if not isinstance(sub_config, MutableMapping):
-                raise ValueError(f"Each element for the name binding must be a dict. (got {sub_config!r} instead)")
-            sub_config["__binding_name__"] = name
-        return value
-
-    def update_config_refs(self) -> "BindingsYaml":
-        """Updates dynamic parts of the config:
-        - Configure _parent for all `IOEnvironment`s
-        - Replace all IOSchemaRef with actual schema objects
-        """
-        for binding in self.bindings.values():
-            for io_env in binding.environments.values():
-                io_env.set_parent(binding)
-        return self
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class BindingsYaml -(**data: Any) -
-
-

Class controlling structure of the top-level IOConfig yaml file.

-

The top-level config is a dictionary of ->

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class BindingsYaml(pydantic.BaseModel):
-    """Class controlling structure of the top-level IOConfig yaml file.
-
-    The top-level config is a dictionary of <binding_name> -> <env_name>
-    """
-
-    bindings: Mapping[str, env_spec.IOBinding]
-
-    @pydantic.validator("bindings", pre=True)
-    def _validate_bindings(cls, value: Mapping):
-        if not isinstance(value, Mapping):
-            raise ValueError(f"Bindings must be a mapping. (got {value!r} instead).")
-        # Tell each binding its name
-        for (name, sub_config) in value.items():
-            if not isinstance(sub_config, MutableMapping):
-                raise ValueError(f"Each element for the name binding must be a dict. (got {sub_config!r} instead)")
-            sub_config["__binding_name__"] = name
-        return value
-
-    def update_config_refs(self) -> "BindingsYaml":
-        """Updates dynamic parts of the config:
-        - Configure _parent for all `IOEnvironment`s
-        - Replace all IOSchemaRef with actual schema objects
-        """
-        for binding in self.bindings.values():
-            for io_env in binding.environments.values():
-                io_env.set_parent(binding)
-        return self
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var bindings : Mapping[str, IOBinding]
-
-
-
-
var model_config
-
-
-
-
-

Methods

-
-
-def update_config_refs(self) ‑> BindingsYaml -
-
-

Updates dynamic parts of the config: -- Configure _parent for all IOEnvironments -- Replace all IOSchemaRef with actual schema objects

-
- -Expand source code - -
def update_config_refs(self) -> "BindingsYaml":
-    """Updates dynamic parts of the config:
-    - Configure _parent for all `IOEnvironment`s
-    - Replace all IOSchemaRef with actual schema objects
-    """
-    for binding in self.bindings.values():
-        for io_env in binding.environments.values():
-            io_env.set_parent(binding)
-    return self
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/config/pydantic/index.html b/docs/dynamicio/config/pydantic/index.html deleted file mode 100644 index 3552ee8..0000000 --- a/docs/dynamicio/config/pydantic/index.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - -dynamicio.config.pydantic API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic

-
-
-

Pydantic config models.

-
- -Expand source code - -
"""Pydantic config models."""
-
-from dynamicio.config.pydantic.config import BindingsYaml
-from dynamicio.config.pydantic.io_resources import (
-    IOEnvironment,
-    KafkaDataEnvironment,
-    LocalBatchDataEnvironment,
-    LocalDataEnvironment,
-    PostgresDataEnvironment,
-    S3DataEnvironment,
-    S3PathPrefixEnvironment,
-)
-from dynamicio.config.pydantic.table_schema import DataframeSchema, SchemaColumn
-
-
-
-

Sub-modules

-
-
dynamicio.config.pydantic.config
-
-

Pydantic schema for YAML files

-
-
dynamicio.config.pydantic.io_resources
-
-

This module contains pylint models for physical data sources (places the bytes are being read from).

-
-
dynamicio.config.pydantic.table_schema
-
-

This module defines Config schema for data source (pandas dataframe)

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/config/pydantic/io_resources.html b/docs/dynamicio/config/pydantic/io_resources.html deleted file mode 100644 index 37e67e8..0000000 --- a/docs/dynamicio/config/pydantic/io_resources.html +++ /dev/null @@ -1,1614 +0,0 @@ - - - - - - -dynamicio.config.pydantic.io_resources API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic.io_resources

-
-
-

This module contains pylint models for physical data sources (places the bytes are being read from).

-
- -Expand source code - -
# pylint: disable=no-member, no-self-argument, unused-argument
-
-"""This module contains pylint models for physical data sources (places the bytes are being read from)."""
-
-import enum
-import posixpath
-from typing import Mapping, Optional, Union
-
-import pydantic
-from pydantic import BaseModel
-
-import dynamicio.config.pydantic.table_schema as table_spec
-
-
-@enum.unique
-class DataBackendType(str, enum.Enum):
-    """Input file types."""
-
-    # pylint: disable=invalid-name
-    local = "local"
-    local_batch = "local_batch"
-    s3 = "s3"  # is there a difference between 's3' and 's3_file' ?
-    s3_file = "s3_file"
-    s3_path_prefix = "s3_path_prefix"
-    postgres = "postgres"
-    athena = "athena"
-    kafka = "kafka"
-
-
-@enum.unique
-class FileType(str, enum.Enum):
-    """List of supported file formats."""
-
-    # pylint: disable=invalid-name
-    parquet = "parquet"
-    csv = "csv"
-    json = "json"
-    hdf = "hdf"
-
-
-class IOBinding(BaseModel):
-    """A binding for a single i/o object."""
-
-    name: str = pydantic.Field(alias="__binding_name__")
-    environments: Mapping[
-        str,
-        Union["IOEnvironment", "LocalDataEnvironment", "LocalBatchDataEnvironment", "S3DataEnvironment", "S3PathPrefixEnvironment", "KafkaDataEnvironment", "PostgresDataEnvironment"],
-    ]
-    dynamicio_schema: Union[table_spec.DataframeSchema, None] = pydantic.Field(default=None, alias="schema")
-
-    def get_binding_for_environment(self, environment: str) -> "IOEnvironment":
-        """Fetch the IOEnvironment spec for the name provided."""
-        return self.environments[environment]
-
-    @pydantic.validator("environments", pre=True, always=True)
-    def pick_correct_env_cls(cls, info):
-        """This pre-validator picks an appropriate IOEnvironment subclass for the `data_backend_type`."""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"Environments input should be a dict. Got {info!r} instead.")
-        config_cls_overrides = {
-            DataBackendType.local: LocalDataEnvironment,
-            DataBackendType.local_batch: LocalBatchDataEnvironment,
-            DataBackendType.s3: S3DataEnvironment,
-            DataBackendType.s3_file: S3DataEnvironment,
-            DataBackendType.s3_path_prefix: S3PathPrefixEnvironment,
-            DataBackendType.kafka: KafkaDataEnvironment,
-            DataBackendType.postgres: PostgresDataEnvironment,
-        }
-        out_dict = {}
-        for (env_name, env_data) in info.items():
-            base_obj: IOEnvironment = IOEnvironment(**env_data)
-            override_cls = config_cls_overrides.get(base_obj.data_backend_type)
-            if override_cls:
-                use_obj = override_cls(**env_data)
-            else:
-                use_obj = base_obj
-            out_dict[env_name] = use_obj
-        return out_dict
-
-    @pydantic.root_validator(pre=True)
-    def _preprocess_raw_config(cls, values):
-        if not isinstance(values, Mapping):
-            raise ValueError(f"IOBinding must be a dict at the top level. (got {values!r} instead)")
-        remapped_value = {"environments": {}}
-        for (key, value) in values.items():
-            if key in ("__binding_name__", "schema"):
-                # Passthrough params
-                remapped_value[key] = value
-            else:
-                # Assuming an environment config
-                remapped_value["environments"][key] = value
-        return remapped_value
-
-
-class IOEnvironment(BaseModel):
-    """A section specifying an data source backed by a particular data backend."""
-
-    _parent: Optional[IOBinding] = None  # noqa: F821
-    options: Mapping = pydantic.Field(default_factory=dict)
-    data_backend_type: DataBackendType = pydantic.Field(alias="type", const=None)
-
-    class Config:
-        """Additional pydantic configuration for the model."""
-
-        underscore_attrs_are_private = True
-
-    @property
-    def dynamicio_schema(self) -> Union[table_spec.DataframeSchema, None]:
-        """Returns tabular data structure definition for the data source (if available)."""
-        if not self._parent:
-            raise Exception("Parent field is not set.")
-        return self._parent.dynamicio_schema
-
-    def set_parent(self, parent: IOBinding):  # noqa: F821
-        """Helper method to set parent config object."""
-        assert self._parent is None
-        self._parent = parent
-
-
-class LocalDataSubSection(BaseModel):
-    """Config section for local data provider."""
-
-    file_path: str
-    file_type: FileType
-
-
-class LocalDataEnvironment(IOEnvironment):
-    """The data is provided by local storage."""
-
-    local: LocalDataSubSection
-
-
-class LocalBatchDataSubSection(BaseModel):
-    """Config section for local batch data (multiple input files)."""
-
-    path_prefix: Optional[str] = None
-    dynamic_file_path: Optional[str] = None
-    file_type: FileType
-
-    @pydantic.root_validator(pre=True)
-    def check_path_fields(cls, values):
-        """Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements."""
-        path_prefix = values.get("path_prefix")
-        dynamic_file_path = values.get("dynamic_file_path")
-
-        # Check for mutual exclusivity
-        if path_prefix and dynamic_file_path:
-            raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-
-        # Check for path_prefix format
-        if path_prefix and not path_prefix.endswith("/"):
-            raise ValueError("path_prefix must end with '/'")
-
-        # Check for dynamic_file_path format
-        if dynamic_file_path:
-            allowed_extensions = [".parquet", ".h5", ".json", ".csv"]
-            if not any(dynamic_file_path.endswith(ext) for ext in allowed_extensions):
-                raise ValueError("`dynamic_file_path` must end with one of: .parquet, .h5, .json, .csv")
-
-        return values
-
-
-class LocalBatchDataEnvironment(IOEnvironment):
-    """Parent section for local batch (multiple files) config."""
-
-    local: LocalBatchDataSubSection
-
-
-class S3DataSubSection(BaseModel):
-    """Config section for S3 data source."""
-
-    file_path: str
-    file_type: FileType
-    bucket: str
-
-
-class S3DataEnvironment(IOEnvironment):
-    """Parent section for s3 data source config."""
-
-    s3: S3DataSubSection
-
-
-class S3PathPrefixSubSection(BaseModel):
-    """Config section for s3 prefix data source (multiple s3 objects)."""
-
-    path_prefix: Optional[str] = None
-    dynamic_file_path: Optional[str] = None
-    file_type: FileType
-    bucket: str
-
-    @pydantic.root_validator(pre=True)
-    def check_path_fields(cls, values):
-        """Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements."""
-        path_prefix = values.get("path_prefix")
-        dynamic_file_path = values.get("dynamic_file_path")
-
-        # Check for mutual exclusivity
-        if path_prefix and dynamic_file_path:
-            raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-
-        # Check for at least one being provided
-        if not path_prefix and not dynamic_file_path:
-            raise ValueError("Either path_prefix or dynamic_file_path must be provided")
-
-        # Check for path_prefix format
-        if path_prefix and not path_prefix.endswith("/"):
-            raise ValueError("path_prefix must end with '/'")
-
-        # Check for dynamic_file_path format
-        if dynamic_file_path:
-            allowed_extensions = [".parquet", ".h5", ".json", ".csv"]
-            if not any(dynamic_file_path.endswith(ext) for ext in allowed_extensions):
-                raise ValueError("`dynamic_file_path` must end with one of: .parquet, .h5, .json, .csv")
-
-        return values
-
-    @pydantic.root_validator(pre=True)
-    def support_legacy_config_path_prefix(cls, values):
-        """This validator implements support for legacy config format.
-
-        I addresses a case where the bucket & path_prefix path could've been passed as a single param in 'bucket' field.
-        E.g.
-            bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"
-        """
-        bucket = values.get("bucket")
-        path_prefix = values.get("path_prefix")
-        if (bucket and isinstance(bucket, str) and posixpath.sep in bucket) and (not path_prefix):
-            (new_bucket, new_path_prefix) = bucket.split(posixpath.sep, 1)
-            values.update(
-                {
-                    "bucket": new_bucket,
-                    "path_prefix": new_path_prefix,
-                }
-            )
-        return values
-
-
-class S3PathPrefixEnvironment(IOEnvironment):
-    """Parent section for the multi-object s3 data source."""
-
-    s3: S3PathPrefixSubSection
-
-
-class KafkaDataSubSection(BaseModel):
-    """Kafka configuration section."""
-
-    kafka_server: str
-    kafka_topic: str
-
-
-class KafkaDataEnvironment(IOEnvironment):
-    """Parent section for kafka data source config."""
-
-    kafka: KafkaDataSubSection
-
-
-class PostgresDataSubSection(BaseModel):
-    """Postgres data source configuration."""
-
-    db_host: str
-    db_port: str
-    db_name: str
-    db_user: str
-    db_password: str
-
-
-class PostgresDataEnvironment(IOEnvironment):
-    """Parent section for postgres data source."""
-
-    postgres: PostgresDataSubSection
-
-
-IOBinding.update_forward_refs()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class DataBackendType -(*args, **kwds) -
-
-

Input file types.

-
- -Expand source code - -
@enum.unique
-class DataBackendType(str, enum.Enum):
-    """Input file types."""
-
-    # pylint: disable=invalid-name
-    local = "local"
-    local_batch = "local_batch"
-    s3 = "s3"  # is there a difference between 's3' and 's3_file' ?
-    s3_file = "s3_file"
-    s3_path_prefix = "s3_path_prefix"
-    postgres = "postgres"
-    athena = "athena"
-    kafka = "kafka"
-
-

Ancestors

-
    -
  • builtins.str
  • -
  • enum.Enum
  • -
-

Class variables

-
-
var athena
-
-
-
-
var kafka
-
-
-
-
var local
-
-
-
-
var local_batch
-
-
-
-
var postgres
-
-
-
-
var s3
-
-
-
-
var s3_file
-
-
-
-
var s3_path_prefix
-
-
-
-
-
-
-class FileType -(*args, **kwds) -
-
-

List of supported file formats.

-
- -Expand source code - -
@enum.unique
-class FileType(str, enum.Enum):
-    """List of supported file formats."""
-
-    # pylint: disable=invalid-name
-    parquet = "parquet"
-    csv = "csv"
-    json = "json"
-    hdf = "hdf"
-
-

Ancestors

-
    -
  • builtins.str
  • -
  • enum.Enum
  • -
-

Class variables

-
-
var csv
-
-
-
-
var hdf
-
-
-
-
var json
-
-
-
-
var parquet
-
-
-
-
-
-
-class IOBinding -(**data: Any) -
-
-

A binding for a single i/o object.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class IOBinding(BaseModel):
-    """A binding for a single i/o object."""
-
-    name: str = pydantic.Field(alias="__binding_name__")
-    environments: Mapping[
-        str,
-        Union["IOEnvironment", "LocalDataEnvironment", "LocalBatchDataEnvironment", "S3DataEnvironment", "S3PathPrefixEnvironment", "KafkaDataEnvironment", "PostgresDataEnvironment"],
-    ]
-    dynamicio_schema: Union[table_spec.DataframeSchema, None] = pydantic.Field(default=None, alias="schema")
-
-    def get_binding_for_environment(self, environment: str) -> "IOEnvironment":
-        """Fetch the IOEnvironment spec for the name provided."""
-        return self.environments[environment]
-
-    @pydantic.validator("environments", pre=True, always=True)
-    def pick_correct_env_cls(cls, info):
-        """This pre-validator picks an appropriate IOEnvironment subclass for the `data_backend_type`."""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"Environments input should be a dict. Got {info!r} instead.")
-        config_cls_overrides = {
-            DataBackendType.local: LocalDataEnvironment,
-            DataBackendType.local_batch: LocalBatchDataEnvironment,
-            DataBackendType.s3: S3DataEnvironment,
-            DataBackendType.s3_file: S3DataEnvironment,
-            DataBackendType.s3_path_prefix: S3PathPrefixEnvironment,
-            DataBackendType.kafka: KafkaDataEnvironment,
-            DataBackendType.postgres: PostgresDataEnvironment,
-        }
-        out_dict = {}
-        for (env_name, env_data) in info.items():
-            base_obj: IOEnvironment = IOEnvironment(**env_data)
-            override_cls = config_cls_overrides.get(base_obj.data_backend_type)
-            if override_cls:
-                use_obj = override_cls(**env_data)
-            else:
-                use_obj = base_obj
-            out_dict[env_name] = use_obj
-        return out_dict
-
-    @pydantic.root_validator(pre=True)
-    def _preprocess_raw_config(cls, values):
-        if not isinstance(values, Mapping):
-            raise ValueError(f"IOBinding must be a dict at the top level. (got {values!r} instead)")
-        remapped_value = {"environments": {}}
-        for (key, value) in values.items():
-            if key in ("__binding_name__", "schema"):
-                # Passthrough params
-                remapped_value[key] = value
-            else:
-                # Assuming an environment config
-                remapped_value["environments"][key] = value
-        return remapped_value
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var dynamicio_schema : Optional[DataframeSchema]
-
-
-
-
var environments : Mapping[str, Union[IOEnvironmentLocalDataEnvironmentLocalBatchDataEnvironmentS3DataEnvironmentS3PathPrefixEnvironmentKafkaDataEnvironmentPostgresDataEnvironment]]
-
-
-
-
var model_config
-
-
-
-
var name : str
-
-
-
-
-

Static methods

-
-
-def pick_correct_env_cls(info) -
-
-

This pre-validator picks an appropriate IOEnvironment subclass for the data_backend_type.

-
- -Expand source code - -
@pydantic.validator("environments", pre=True, always=True)
-def pick_correct_env_cls(cls, info):
-    """This pre-validator picks an appropriate IOEnvironment subclass for the `data_backend_type`."""
-    if not isinstance(info, Mapping):
-        raise ValueError(f"Environments input should be a dict. Got {info!r} instead.")
-    config_cls_overrides = {
-        DataBackendType.local: LocalDataEnvironment,
-        DataBackendType.local_batch: LocalBatchDataEnvironment,
-        DataBackendType.s3: S3DataEnvironment,
-        DataBackendType.s3_file: S3DataEnvironment,
-        DataBackendType.s3_path_prefix: S3PathPrefixEnvironment,
-        DataBackendType.kafka: KafkaDataEnvironment,
-        DataBackendType.postgres: PostgresDataEnvironment,
-    }
-    out_dict = {}
-    for (env_name, env_data) in info.items():
-        base_obj: IOEnvironment = IOEnvironment(**env_data)
-        override_cls = config_cls_overrides.get(base_obj.data_backend_type)
-        if override_cls:
-            use_obj = override_cls(**env_data)
-        else:
-            use_obj = base_obj
-        out_dict[env_name] = use_obj
-    return out_dict
-
-
-
-

Methods

-
-
-def get_binding_for_environment(self, environment: str) ‑> IOEnvironment -
-
-

Fetch the IOEnvironment spec for the name provided.

-
- -Expand source code - -
def get_binding_for_environment(self, environment: str) -> "IOEnvironment":
-    """Fetch the IOEnvironment spec for the name provided."""
-    return self.environments[environment]
-
-
-
-
-
-class IOEnvironment -(**data: Any) -
-
-

A section specifying an data source backed by a particular data backend.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class IOEnvironment(BaseModel):
-    """A section specifying an data source backed by a particular data backend."""
-
-    _parent: Optional[IOBinding] = None  # noqa: F821
-    options: Mapping = pydantic.Field(default_factory=dict)
-    data_backend_type: DataBackendType = pydantic.Field(alias="type", const=None)
-
-    class Config:
-        """Additional pydantic configuration for the model."""
-
-        underscore_attrs_are_private = True
-
-    @property
-    def dynamicio_schema(self) -> Union[table_spec.DataframeSchema, None]:
-        """Returns tabular data structure definition for the data source (if available)."""
-        if not self._parent:
-            raise Exception("Parent field is not set.")
-        return self._parent.dynamicio_schema
-
-    def set_parent(self, parent: IOBinding):  # noqa: F821
-        """Helper method to set parent config object."""
-        assert self._parent is None
-        self._parent = parent
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Subclasses

- -

Class variables

-
-
var Config
-
-

Additional pydantic configuration for the model.

-
-
var data_backend_typeDataBackendType
-
-
-
-
var model_config
-
-
-
-
var options : Mapping
-
-
-
-
-

Instance variables

-
-
var dynamicio_schema : Optional[DataframeSchema]
-
-

Returns tabular data structure definition for the data source (if available).

-
- -Expand source code - -
@property
-def dynamicio_schema(self) -> Union[table_spec.DataframeSchema, None]:
-    """Returns tabular data structure definition for the data source (if available)."""
-    if not self._parent:
-        raise Exception("Parent field is not set.")
-    return self._parent.dynamicio_schema
-
-
-
-

Methods

-
-
-def model_post_init(self: BaseModel, context: Any, /) ‑> None -
-
-

This function is meant to behave like a BaseModel method to initialise private attributes.

-

It takes context as an argument since that's what pydantic-core passes when calling it.

-

Args

-
-
self
-
The BaseModel instance.
-
context
-
The context.
-
-
- -Expand source code - -
def init_private_attributes(self: BaseModel, context: Any, /) -> None:
-    """This function is meant to behave like a BaseModel method to initialise private attributes.
-
-    It takes context as an argument since that's what pydantic-core passes when calling it.
-
-    Args:
-        self: The BaseModel instance.
-        context: The context.
-    """
-    if getattr(self, '__pydantic_private__', None) is None:
-        pydantic_private = {}
-        for name, private_attr in self.__private_attributes__.items():
-            default = private_attr.get_default()
-            if default is not PydanticUndefined:
-                pydantic_private[name] = default
-        object_setattr(self, '__pydantic_private__', pydantic_private)
-
-
-
-def set_parent(self, parent: IOBinding) -
-
-

Helper method to set parent config object.

-
- -Expand source code - -
def set_parent(self, parent: IOBinding):  # noqa: F821
-    """Helper method to set parent config object."""
-    assert self._parent is None
-    self._parent = parent
-
-
-
-
-
-class KafkaDataEnvironment -(**data: Any) -
-
-

Parent section for kafka data source config.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class KafkaDataEnvironment(IOEnvironment):
-    """Parent section for kafka data source config."""
-
-    kafka: KafkaDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var kafkaKafkaDataSubSection
-
-
-
-
var model_config
-
-
-
-
-

Inherited members

- -
-
-class KafkaDataSubSection -(**data: Any) -
-
-

Kafka configuration section.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class KafkaDataSubSection(BaseModel):
-    """Kafka configuration section."""
-
-    kafka_server: str
-    kafka_topic: str
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var kafka_server : str
-
-
-
-
var kafka_topic : str
-
-
-
-
var model_config
-
-
-
-
-
-
-class LocalBatchDataEnvironment -(**data: Any) -
-
-

Parent section for local batch (multiple files) config.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalBatchDataEnvironment(IOEnvironment):
-    """Parent section for local batch (multiple files) config."""
-
-    local: LocalBatchDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var localLocalBatchDataSubSection
-
-
-
-
var model_config
-
-
-
-
-

Inherited members

- -
-
-class LocalBatchDataSubSection -(**data: Any) -
-
-

Config section for local batch data (multiple input files).

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalBatchDataSubSection(BaseModel):
-    """Config section for local batch data (multiple input files)."""
-
-    path_prefix: Optional[str] = None
-    dynamic_file_path: Optional[str] = None
-    file_type: FileType
-
-    @pydantic.root_validator(pre=True)
-    def check_path_fields(cls, values):
-        """Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements."""
-        path_prefix = values.get("path_prefix")
-        dynamic_file_path = values.get("dynamic_file_path")
-
-        # Check for mutual exclusivity
-        if path_prefix and dynamic_file_path:
-            raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-
-        # Check for path_prefix format
-        if path_prefix and not path_prefix.endswith("/"):
-            raise ValueError("path_prefix must end with '/'")
-
-        # Check for dynamic_file_path format
-        if dynamic_file_path:
-            allowed_extensions = [".parquet", ".h5", ".json", ".csv"]
-            if not any(dynamic_file_path.endswith(ext) for ext in allowed_extensions):
-                raise ValueError("`dynamic_file_path` must end with one of: .parquet, .h5, .json, .csv")
-
-        return values
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var dynamic_file_path : Optional[str]
-
-
-
-
var file_typeFileType
-
-
-
-
var model_config
-
-
-
-
var path_prefix : Optional[str]
-
-
-
-
-

Static methods

-
-
-def check_path_fields(values) -
-
-

Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements.

-
- -Expand source code - -
@pydantic.root_validator(pre=True)
-def check_path_fields(cls, values):
-    """Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements."""
-    path_prefix = values.get("path_prefix")
-    dynamic_file_path = values.get("dynamic_file_path")
-
-    # Check for mutual exclusivity
-    if path_prefix and dynamic_file_path:
-        raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-
-    # Check for path_prefix format
-    if path_prefix and not path_prefix.endswith("/"):
-        raise ValueError("path_prefix must end with '/'")
-
-    # Check for dynamic_file_path format
-    if dynamic_file_path:
-        allowed_extensions = [".parquet", ".h5", ".json", ".csv"]
-        if not any(dynamic_file_path.endswith(ext) for ext in allowed_extensions):
-            raise ValueError("`dynamic_file_path` must end with one of: .parquet, .h5, .json, .csv")
-
-    return values
-
-
-
-
-
-class LocalDataEnvironment -(**data: Any) -
-
-

The data is provided by local storage.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalDataEnvironment(IOEnvironment):
-    """The data is provided by local storage."""
-
-    local: LocalDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var localLocalDataSubSection
-
-
-
-
var model_config
-
-
-
-
-

Inherited members

- -
-
-class LocalDataSubSection -(**data: Any) -
-
-

Config section for local data provider.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class LocalDataSubSection(BaseModel):
-    """Config section for local data provider."""
-
-    file_path: str
-    file_type: FileType
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var file_path : str
-
-
-
-
var file_typeFileType
-
-
-
-
var model_config
-
-
-
-
-
-
-class PostgresDataEnvironment -(**data: Any) -
-
-

Parent section for postgres data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class PostgresDataEnvironment(IOEnvironment):
-    """Parent section for postgres data source."""
-
-    postgres: PostgresDataSubSection
-
-

Ancestors

- -

Class variables

-
-
var model_config
-
-
-
-
var postgresPostgresDataSubSection
-
-
-
-
-

Inherited members

- -
-
-class PostgresDataSubSection -(**data: Any) -
-
-

Postgres data source configuration.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class PostgresDataSubSection(BaseModel):
-    """Postgres data source configuration."""
-
-    db_host: str
-    db_port: str
-    db_name: str
-    db_user: str
-    db_password: str
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var db_host : str
-
-
-
-
var db_name : str
-
-
-
-
var db_password : str
-
-
-
-
var db_port : str
-
-
-
-
var db_user : str
-
-
-
-
var model_config
-
-
-
-
-
-
-class S3DataEnvironment -(**data: Any) -
-
-

Parent section for s3 data source config.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3DataEnvironment(IOEnvironment):
-    """Parent section for s3 data source config."""
-
-    s3: S3DataSubSection
-
-

Ancestors

- -

Class variables

-
-
var model_config
-
-
-
-
var s3S3DataSubSection
-
-
-
-
-

Inherited members

- -
-
-class S3DataSubSection -(**data: Any) -
-
-

Config section for S3 data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3DataSubSection(BaseModel):
-    """Config section for S3 data source."""
-
-    file_path: str
-    file_type: FileType
-    bucket: str
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var bucket : str
-
-
-
-
var file_path : str
-
-
-
-
var file_typeFileType
-
-
-
-
var model_config
-
-
-
-
-
-
-class S3PathPrefixEnvironment -(**data: Any) -
-
-

Parent section for the multi-object s3 data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3PathPrefixEnvironment(IOEnvironment):
-    """Parent section for the multi-object s3 data source."""
-
-    s3: S3PathPrefixSubSection
-
-

Ancestors

- -

Class variables

-
-
var model_config
-
-
-
-
var s3S3PathPrefixSubSection
-
-
-
-
-

Inherited members

- -
-
-class S3PathPrefixSubSection -(**data: Any) -
-
-

Config section for s3 prefix data source (multiple s3 objects).

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class S3PathPrefixSubSection(BaseModel):
-    """Config section for s3 prefix data source (multiple s3 objects)."""
-
-    path_prefix: Optional[str] = None
-    dynamic_file_path: Optional[str] = None
-    file_type: FileType
-    bucket: str
-
-    @pydantic.root_validator(pre=True)
-    def check_path_fields(cls, values):
-        """Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements."""
-        path_prefix = values.get("path_prefix")
-        dynamic_file_path = values.get("dynamic_file_path")
-
-        # Check for mutual exclusivity
-        if path_prefix and dynamic_file_path:
-            raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-
-        # Check for at least one being provided
-        if not path_prefix and not dynamic_file_path:
-            raise ValueError("Either path_prefix or dynamic_file_path must be provided")
-
-        # Check for path_prefix format
-        if path_prefix and not path_prefix.endswith("/"):
-            raise ValueError("path_prefix must end with '/'")
-
-        # Check for dynamic_file_path format
-        if dynamic_file_path:
-            allowed_extensions = [".parquet", ".h5", ".json", ".csv"]
-            if not any(dynamic_file_path.endswith(ext) for ext in allowed_extensions):
-                raise ValueError("`dynamic_file_path` must end with one of: .parquet, .h5, .json, .csv")
-
-        return values
-
-    @pydantic.root_validator(pre=True)
-    def support_legacy_config_path_prefix(cls, values):
-        """This validator implements support for legacy config format.
-
-        I addresses a case where the bucket & path_prefix path could've been passed as a single param in 'bucket' field.
-        E.g.
-            bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"
-        """
-        bucket = values.get("bucket")
-        path_prefix = values.get("path_prefix")
-        if (bucket and isinstance(bucket, str) and posixpath.sep in bucket) and (not path_prefix):
-            (new_bucket, new_path_prefix) = bucket.split(posixpath.sep, 1)
-            values.update(
-                {
-                    "bucket": new_bucket,
-                    "path_prefix": new_path_prefix,
-                }
-            )
-        return values
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var bucket : str
-
-
-
-
var dynamic_file_path : Optional[str]
-
-
-
-
var file_typeFileType
-
-
-
-
var model_config
-
-
-
-
var path_prefix : Optional[str]
-
-
-
-
-

Static methods

-
-
-def check_path_fields(values) -
-
-

Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements.

-
- -Expand source code - -
@pydantic.root_validator(pre=True)
-def check_path_fields(cls, values):
-    """Check that only one of path_prefix or dynamic_file_path is provided & they meet format requirements."""
-    path_prefix = values.get("path_prefix")
-    dynamic_file_path = values.get("dynamic_file_path")
-
-    # Check for mutual exclusivity
-    if path_prefix and dynamic_file_path:
-        raise ValueError("Only one of path_prefix or dynamic_file_path should be provided")
-
-    # Check for at least one being provided
-    if not path_prefix and not dynamic_file_path:
-        raise ValueError("Either path_prefix or dynamic_file_path must be provided")
-
-    # Check for path_prefix format
-    if path_prefix and not path_prefix.endswith("/"):
-        raise ValueError("path_prefix must end with '/'")
-
-    # Check for dynamic_file_path format
-    if dynamic_file_path:
-        allowed_extensions = [".parquet", ".h5", ".json", ".csv"]
-        if not any(dynamic_file_path.endswith(ext) for ext in allowed_extensions):
-            raise ValueError("`dynamic_file_path` must end with one of: .parquet, .h5, .json, .csv")
-
-    return values
-
-
-
-def support_legacy_config_path_prefix(values) -
-
-

This validator implements support for legacy config format.

-

I addresses a case where the bucket & path_prefix path could've been passed as a single param in 'bucket' field. -E.g. -bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"

-
- -Expand source code - -
@pydantic.root_validator(pre=True)
-def support_legacy_config_path_prefix(cls, values):
-    """This validator implements support for legacy config format.
-
-    I addresses a case where the bucket & path_prefix path could've been passed as a single param in 'bucket' field.
-    E.g.
-        bucket: "[[ MOCK_BUCKET ]]/data/input/{file_name_to_replace}.hdf"
-    """
-    bucket = values.get("bucket")
-    path_prefix = values.get("path_prefix")
-    if (bucket and isinstance(bucket, str) and posixpath.sep in bucket) and (not path_prefix):
-        (new_bucket, new_path_prefix) = bucket.split(posixpath.sep, 1)
-        values.update(
-            {
-                "bucket": new_bucket,
-                "path_prefix": new_path_prefix,
-            }
-        )
-    return values
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/config/pydantic/table_schema.html b/docs/dynamicio/config/pydantic/table_schema.html deleted file mode 100644 index fc3a32c..0000000 --- a/docs/dynamicio/config/pydantic/table_schema.html +++ /dev/null @@ -1,593 +0,0 @@ - - - - - - -dynamicio.config.pydantic.table_schema API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.config.pydantic.table_schema

-
-
-

This module defines Config schema for data source (pandas dataframe)

-
- -Expand source code - -
# pylint: disable=no-member, no-self-argument, unused-argument
-
-"""This module defines Config schema for data source (pandas dataframe)"""
-
-import enum
-from typing import Mapping, Sequence
-
-import pydantic
-from pandas.core.dtypes.common import pandas_dtype
-
-
-@enum.unique
-class MetricsName(str, enum.Enum):
-    """The list of valid metrics names."""
-
-    # pylint: disable=invalid-name
-    min = "Min"
-    max = "Max"
-    mean = "Mean"
-    stddev = "Std"
-    variance = "Variance"
-    counts = "Counts"
-    counts_per_label = "CountsPerLabel"
-    unique_counts = "UniqueCounts"
-
-
-class ColumnValidationBase(pydantic.BaseModel):
-    """A single column validator."""
-
-    name: str
-    apply: bool
-    options: Mapping[str, object]
-
-
-class SchemaColumn(pydantic.BaseModel):
-    """Definition os a single data source column."""
-
-    name: str
-    data_type: str = pydantic.Field(alias="type")
-    validations: Sequence[ColumnValidationBase] = pydantic.Field(default_factory=list)
-    metrics: Sequence[MetricsName] = ()
-
-    @pydantic.validator("data_type")
-    def is_valid_pandas_type(cls, info):
-        """Checks that the data_type is understood by pandas."""
-        try:
-            pandas_dtype(info)
-        except TypeError:
-            raise ValueError(f"Unexpected data type {info}") from None
-        return info
-
-    @pydantic.validator("validations", pre=True)
-    def remap_validations(cls, info):
-        """Remap the yaml structure of {validation_type: <params>} to a list with validation_type as a key"""
-        if not isinstance(info, dict):
-            raise ValueError(f"{info!r} should be a dict")
-        out = []
-        for (key, params) in info.items():
-            new_el = params.copy()
-            new_el.update({"name": key})
-            out.append(new_el)
-        return out
-
-    @pydantic.validator("metrics", pre=True, always=True)
-    def validate_metrics(cls, info):
-        """Remap any false-ish `metrics` value to an empty list."""
-        if info:
-            out = info
-        else:
-            out = []
-        return out
-
-
-class DataframeSchema(pydantic.BaseModel):
-    """Pydantic model describing the tabular data provided by the data source."""
-
-    name: str
-    columns: Mapping[str, SchemaColumn]
-
-    @pydantic.validator("columns", pre=True)
-    def supply_column_names(cls, info):
-        """Tell each column its name (the key it is listed under)"""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"{info!r} shoudl be a dict.")
-
-        return {str(col_name): {**{"name": str(col_name)}, **col_data} for (col_name, col_data) in info.items()}
-
-    @property
-    def validations(self) -> Mapping[str, Sequence[ColumnValidationBase]]:
-        """A short-hand property to access the validators for each column."""
-        return {col_name: col.validations for (col_name, col) in self.columns.items()}
-
-    @property
-    def metrics(self) -> Mapping[str, Sequence[MetricsName]]:
-        """A short-hand property to access the metrics for each column."""
-        return {col_name: col.metrics for (col_name, col) in self.columns.items()}
-
-    @property
-    def column_names(self) -> Sequence[str]:
-        """Property providing the list of all column names."""
-        return tuple(self.columns.keys())
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class ColumnValidationBase -(**data: Any) -
-
-

A single column validator.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class ColumnValidationBase(pydantic.BaseModel):
-    """A single column validator."""
-
-    name: str
-    apply: bool
-    options: Mapping[str, object]
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var apply : bool
-
-
-
-
var model_config
-
-
-
-
var name : str
-
-
-
-
var options : Mapping[str, object]
-
-
-
-
-
-
-class DataframeSchema -(**data: Any) -
-
-

Pydantic model describing the tabular data provided by the data source.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class DataframeSchema(pydantic.BaseModel):
-    """Pydantic model describing the tabular data provided by the data source."""
-
-    name: str
-    columns: Mapping[str, SchemaColumn]
-
-    @pydantic.validator("columns", pre=True)
-    def supply_column_names(cls, info):
-        """Tell each column its name (the key it is listed under)"""
-        if not isinstance(info, Mapping):
-            raise ValueError(f"{info!r} shoudl be a dict.")
-
-        return {str(col_name): {**{"name": str(col_name)}, **col_data} for (col_name, col_data) in info.items()}
-
-    @property
-    def validations(self) -> Mapping[str, Sequence[ColumnValidationBase]]:
-        """A short-hand property to access the validators for each column."""
-        return {col_name: col.validations for (col_name, col) in self.columns.items()}
-
-    @property
-    def metrics(self) -> Mapping[str, Sequence[MetricsName]]:
-        """A short-hand property to access the metrics for each column."""
-        return {col_name: col.metrics for (col_name, col) in self.columns.items()}
-
-    @property
-    def column_names(self) -> Sequence[str]:
-        """Property providing the list of all column names."""
-        return tuple(self.columns.keys())
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var columns : Mapping[str, SchemaColumn]
-
-
-
-
var model_config
-
-
-
-
var name : str
-
-
-
-
-

Static methods

-
-
-def supply_column_names(info) -
-
-

Tell each column its name (the key it is listed under)

-
- -Expand source code - -
@pydantic.validator("columns", pre=True)
-def supply_column_names(cls, info):
-    """Tell each column its name (the key it is listed under)"""
-    if not isinstance(info, Mapping):
-        raise ValueError(f"{info!r} shoudl be a dict.")
-
-    return {str(col_name): {**{"name": str(col_name)}, **col_data} for (col_name, col_data) in info.items()}
-
-
-
-

Instance variables

-
-
var column_names : Sequence[str]
-
-

Property providing the list of all column names.

-
- -Expand source code - -
@property
-def column_names(self) -> Sequence[str]:
-    """Property providing the list of all column names."""
-    return tuple(self.columns.keys())
-
-
-
var metrics : Mapping[str, Sequence[MetricsName]]
-
-

A short-hand property to access the metrics for each column.

-
- -Expand source code - -
@property
-def metrics(self) -> Mapping[str, Sequence[MetricsName]]:
-    """A short-hand property to access the metrics for each column."""
-    return {col_name: col.metrics for (col_name, col) in self.columns.items()}
-
-
-
var validations : Mapping[str, Sequence[ColumnValidationBase]]
-
-

A short-hand property to access the validators for each column.

-
- -Expand source code - -
@property
-def validations(self) -> Mapping[str, Sequence[ColumnValidationBase]]:
-    """A short-hand property to access the validators for each column."""
-    return {col_name: col.validations for (col_name, col) in self.columns.items()}
-
-
-
-
-
-class MetricsName -(*args, **kwds) -
-
-

The list of valid metrics names.

-
- -Expand source code - -
@enum.unique
-class MetricsName(str, enum.Enum):
-    """The list of valid metrics names."""
-
-    # pylint: disable=invalid-name
-    min = "Min"
-    max = "Max"
-    mean = "Mean"
-    stddev = "Std"
-    variance = "Variance"
-    counts = "Counts"
-    counts_per_label = "CountsPerLabel"
-    unique_counts = "UniqueCounts"
-
-

Ancestors

-
    -
  • builtins.str
  • -
  • enum.Enum
  • -
-

Class variables

-
-
var counts
-
-
-
-
var counts_per_label
-
-
-
-
var max
-
-
-
-
var mean
-
-
-
-
var min
-
-
-
-
var stddev
-
-
-
-
var unique_counts
-
-
-
-
var variance
-
-
-
-
-
-
-class SchemaColumn -(**data: Any) -
-
-

Definition os a single data source column.

-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
- -Expand source code - -
class SchemaColumn(pydantic.BaseModel):
-    """Definition os a single data source column."""
-
-    name: str
-    data_type: str = pydantic.Field(alias="type")
-    validations: Sequence[ColumnValidationBase] = pydantic.Field(default_factory=list)
-    metrics: Sequence[MetricsName] = ()
-
-    @pydantic.validator("data_type")
-    def is_valid_pandas_type(cls, info):
-        """Checks that the data_type is understood by pandas."""
-        try:
-            pandas_dtype(info)
-        except TypeError:
-            raise ValueError(f"Unexpected data type {info}") from None
-        return info
-
-    @pydantic.validator("validations", pre=True)
-    def remap_validations(cls, info):
-        """Remap the yaml structure of {validation_type: <params>} to a list with validation_type as a key"""
-        if not isinstance(info, dict):
-            raise ValueError(f"{info!r} should be a dict")
-        out = []
-        for (key, params) in info.items():
-            new_el = params.copy()
-            new_el.update({"name": key})
-            out.append(new_el)
-        return out
-
-    @pydantic.validator("metrics", pre=True, always=True)
-    def validate_metrics(cls, info):
-        """Remap any false-ish `metrics` value to an empty list."""
-        if info:
-            out = info
-        else:
-            out = []
-        return out
-
-

Ancestors

-
    -
  • pydantic.main.BaseModel
  • -
-

Class variables

-
-
var data_type : str
-
-
-
-
var metrics : Sequence[MetricsName]
-
-
-
-
var model_config
-
-
-
-
var name : str
-
-
-
-
var validations : Sequence[ColumnValidationBase]
-
-
-
-
-

Static methods

-
-
-def is_valid_pandas_type(info) -
-
-

Checks that the data_type is understood by pandas.

-
- -Expand source code - -
@pydantic.validator("data_type")
-def is_valid_pandas_type(cls, info):
-    """Checks that the data_type is understood by pandas."""
-    try:
-        pandas_dtype(info)
-    except TypeError:
-        raise ValueError(f"Unexpected data type {info}") from None
-    return info
-
-
-
-def remap_validations(info) -
-
-

Remap the yaml structure of {validation_type: } to a list with validation_type as a key

-
- -Expand source code - -
@pydantic.validator("validations", pre=True)
-def remap_validations(cls, info):
-    """Remap the yaml structure of {validation_type: <params>} to a list with validation_type as a key"""
-    if not isinstance(info, dict):
-        raise ValueError(f"{info!r} should be a dict")
-    out = []
-    for (key, params) in info.items():
-        new_el = params.copy()
-        new_el.update({"name": key})
-        out.append(new_el)
-    return out
-
-
-
-def validate_metrics(info) -
-
-

Remap any false-ish metrics value to an empty list.

-
- -Expand source code - -
@pydantic.validator("metrics", pre=True, always=True)
-def validate_metrics(cls, info):
-    """Remap any false-ish `metrics` value to an empty list."""
-    if info:
-        out = info
-    else:
-        out = []
-    return out
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/mixins/index.html b/docs/dynamicio/mixins/index.html deleted file mode 100644 index bc538f6..0000000 --- a/docs/dynamicio/mixins/index.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - -dynamicio.mixins API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins

-
-
-

Default dynamicio mixins module

-
- -Expand source code - -
"""Default dynamicio mixins module"""
-
-from dynamicio.mixins.with_kafka import (
-    WithKafka,
-)
-from dynamicio.mixins.with_local import (
-    WithLocal,
-    WithLocalBatch,
-)
-from dynamicio.mixins.with_postgres import (
-    WithPostgres,
-)
-from dynamicio.mixins.with_s3 import (
-    WithS3File,
-    WithS3PathPrefix,
-)
-
-
-
-

Sub-modules

-
-
dynamicio.mixins.utils
-
-

Mixin utility functions.

-
-
dynamicio.mixins.with_kafka
-
-

This module provides mixins that are providing Kafka I/O support.

-
-
dynamicio.mixins.with_local
-
-

This module provides mixins that are providing Local FS I/O support.

-
-
dynamicio.mixins.with_postgres
-
-

This module provides mixins that are providing Postgres I/O support.

-
-
dynamicio.mixins.with_s3
-
-

This module provides mixins that are providing S3 I/O support.

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/mixins/utils.html b/docs/dynamicio/mixins/utils.html deleted file mode 100644 index 355c254..0000000 --- a/docs/dynamicio/mixins/utils.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - -dynamicio.mixins.utils API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.utils

-
-
-

Mixin utility functions.

-
- -Expand source code - -
"""Mixin utility functions."""
-# pylint: disable=no-member, protected-access, too-few-public-methods
-
-import inspect
-import string
-from contextlib import contextmanager
-from enum import Enum
-from functools import wraps
-from types import FunctionType, MethodType
-from typing import Any, Collection, Iterable, Mapping, MutableMapping, Optional, Union
-
-from magic_logger import logger
-
-
-def allow_options(options: Union[Iterable, FunctionType, MethodType]):
-    """Validate **options for a decorated reader function.
-
-    Args:
-        options: A set of valid options for a reader (e.g. `pandas.read_parquet` or `pandas.read_csv`)
-
-    Returns:
-        read_with_valid_options: The input function called with modified options.
-    """
-
-    def _filter_out_irrelevant_options(kwargs: Mapping, valid_options: Iterable):
-        filtered_options = {}
-        invalid_options = {}
-        for key_arg in kwargs.keys():
-            if key_arg in valid_options:
-                filtered_options[key_arg] = kwargs[key_arg]
-            else:
-                invalid_options[key_arg] = kwargs[key_arg]
-        if len(invalid_options) > 0:
-            logger.warning(
-                f"Options {invalid_options} were not used because they were not supported by the read or write method configured for this source. "
-                "Check if you expected any of those to have been used by the operation!"
-            )
-        return filtered_options
-
-    def read_with_valid_options(func):
-        @wraps(func)
-        def _(*args, **kwargs):
-            if callable(options):
-                return func(*args, **_filter_out_irrelevant_options(kwargs, args_of(options)))
-            return func(*args, **_filter_out_irrelevant_options(kwargs, options))
-
-        return _
-
-    return read_with_valid_options
-
-
-def args_of(func):
-    """Retrieve allowed options for a given function.
-
-    Args:
-        func: A function like, e.g., pd.read_csv
-
-    Returns:
-        A set of allowed options
-    """
-    return set(inspect.signature(func).parameters.keys())
-
-
-def get_string_template_field_names(s: str) -> Collection[str]:  # pylint: disable=C0103
-    """Given a string `s`, it parses the string to identify any template fields and returns the names of those fields.
-
-     If `s` is not a string template, the returned `Collection` is empty.
-
-    Args:
-        s:
-
-    Returns:
-        Collection[str]
-
-    Example:
-
-        >>> get_string_template_field_names("abc{def}{efg}")
-        ["def", "efg"]
-        >>> get_string_template_field_names("{0}-{1}")
-        ["0", "1"]
-        >>> get_string_template_field_names("hello world")
-        []
-    """
-    # string.Formatter.parse returns a 4-tuple of:
-    # `literal_text`, `field_name`, `form_at_spec`, `conversion`
-    # More info here https://docs.python.org/3.8/library/string.html#string.Formatter.parse
-    field_names = [group[1] for group in string.Formatter().parse(s) if group[1] is not None]
-
-    return field_names
-
-
-def resolve_template(path: str, options: MutableMapping[str, Any]) -> str:  # pylint: disable=C0103
-    """Given a string `path`, it attempts to replace all templates fields with values provided in `options`.
-
-    If `path` is not a string template, `path` is returned.
-
-    Args:
-        path: A string which is either a template, e.g. /path/to/file/{replace_me}.h5 or just a path /path/to/file/dont_replace_me.h5
-        options: A dynamic name for the "replace_me" field in the templated string. e.g. {"replace_me": "name_of_file"}
-
-    Returns:
-        str: Returns a static path replaced with the value in the options mapping.
-
-    Raises:
-        ValueError: if any template fields in s are not named using valid Python identifiers
-        ValueError: if a given template field cannot be resolved in `options`
-    """
-    fields = get_string_template_field_names(path)
-
-    if len(fields) == 0:
-        return path
-
-    if not all(field.isidentifier() for field in fields):
-        raise ValueError(f"Expected valid Python identifiers, found {fields}")
-
-    if not all(field in options for field in fields):
-        raise ValueError(f"Expected values for all fields in {fields}, found {list(options.keys())}")
-
-    path = path.format(**{field: options[field] for field in fields})
-    for field in fields:
-        options.pop(field)
-
-    return path
-
-
-@contextmanager
-def pickle_protocol(protocol: Optional[int]):
-    """Downgrade to the provided pickle protocol within the context manager.
-
-    Args:
-        protocol: The number of the protocol HIGHEST_PROTOCOL to downgrade to. Defaults to 4, which covers python 3.4 and higher.
-    """
-    import pickle  # pylint: disable=import-outside-toplevel
-
-    previous = pickle.HIGHEST_PROTOCOL
-    try:
-        pickle.HIGHEST_PROTOCOL = 4
-        if protocol:
-            pickle.HIGHEST_PROTOCOL = protocol
-        yield
-    finally:
-        pickle.HIGHEST_PROTOCOL = previous
-
-
-def get_file_type_value(file_type: Union[str, Enum]) -> str:
-    """Get the value of the file type."""
-    return file_type.value if isinstance(file_type, Enum) else file_type
-
-
-
-
-
-
-
-

Functions

-
-
-def allow_options(options: Union[Iterable, function, method]) -
-
-

Validate **options for a decorated reader function.

-

Args

-
-
options
-
A set of valid options for a reader (e.g. pandas.read_parquet or pandas.read_csv)
-
-

Returns

-
-
read_with_valid_options
-
The input function called with modified options.
-
-
- -Expand source code - -
def allow_options(options: Union[Iterable, FunctionType, MethodType]):
-    """Validate **options for a decorated reader function.
-
-    Args:
-        options: A set of valid options for a reader (e.g. `pandas.read_parquet` or `pandas.read_csv`)
-
-    Returns:
-        read_with_valid_options: The input function called with modified options.
-    """
-
-    def _filter_out_irrelevant_options(kwargs: Mapping, valid_options: Iterable):
-        filtered_options = {}
-        invalid_options = {}
-        for key_arg in kwargs.keys():
-            if key_arg in valid_options:
-                filtered_options[key_arg] = kwargs[key_arg]
-            else:
-                invalid_options[key_arg] = kwargs[key_arg]
-        if len(invalid_options) > 0:
-            logger.warning(
-                f"Options {invalid_options} were not used because they were not supported by the read or write method configured for this source. "
-                "Check if you expected any of those to have been used by the operation!"
-            )
-        return filtered_options
-
-    def read_with_valid_options(func):
-        @wraps(func)
-        def _(*args, **kwargs):
-            if callable(options):
-                return func(*args, **_filter_out_irrelevant_options(kwargs, args_of(options)))
-            return func(*args, **_filter_out_irrelevant_options(kwargs, options))
-
-        return _
-
-    return read_with_valid_options
-
-
-
-def args_of(func) -
-
-

Retrieve allowed options for a given function.

-

Args

-
-
func
-
A function like, e.g., pd.read_csv
-
-

Returns

-

A set of allowed options

-
- -Expand source code - -
def args_of(func):
-    """Retrieve allowed options for a given function.
-
-    Args:
-        func: A function like, e.g., pd.read_csv
-
-    Returns:
-        A set of allowed options
-    """
-    return set(inspect.signature(func).parameters.keys())
-
-
-
-def get_file_type_value(file_type: Union[str, enum.Enum]) ‑> str -
-
-

Get the value of the file type.

-
- -Expand source code - -
def get_file_type_value(file_type: Union[str, Enum]) -> str:
-    """Get the value of the file type."""
-    return file_type.value if isinstance(file_type, Enum) else file_type
-
-
-
-def get_string_template_field_names(s: str) ‑> Collection[str] -
-
-

Given a string s, it parses the string to identify any template fields and returns the names of those fields.

-

If s is not a string template, the returned Collection is empty.

-

Args

-

s:

-

Returns

-

Collection[str]

-

Example

-
>>> get_string_template_field_names("abc{def}{efg}")
-["def", "efg"]
->>> get_string_template_field_names("{0}-{1}")
-["0", "1"]
->>> get_string_template_field_names("hello world")
-[]
-
-
- -Expand source code - -
def get_string_template_field_names(s: str) -> Collection[str]:  # pylint: disable=C0103
-    """Given a string `s`, it parses the string to identify any template fields and returns the names of those fields.
-
-     If `s` is not a string template, the returned `Collection` is empty.
-
-    Args:
-        s:
-
-    Returns:
-        Collection[str]
-
-    Example:
-
-        >>> get_string_template_field_names("abc{def}{efg}")
-        ["def", "efg"]
-        >>> get_string_template_field_names("{0}-{1}")
-        ["0", "1"]
-        >>> get_string_template_field_names("hello world")
-        []
-    """
-    # string.Formatter.parse returns a 4-tuple of:
-    # `literal_text`, `field_name`, `form_at_spec`, `conversion`
-    # More info here https://docs.python.org/3.8/library/string.html#string.Formatter.parse
-    field_names = [group[1] for group in string.Formatter().parse(s) if group[1] is not None]
-
-    return field_names
-
-
-
-def pickle_protocol(protocol: Optional[int]) -
-
-

Downgrade to the provided pickle protocol within the context manager.

-

Args

-
-
protocol
-
The number of the protocol HIGHEST_PROTOCOL to downgrade to. Defaults to 4, which covers python 3.4 and higher.
-
-
- -Expand source code - -
@contextmanager
-def pickle_protocol(protocol: Optional[int]):
-    """Downgrade to the provided pickle protocol within the context manager.
-
-    Args:
-        protocol: The number of the protocol HIGHEST_PROTOCOL to downgrade to. Defaults to 4, which covers python 3.4 and higher.
-    """
-    import pickle  # pylint: disable=import-outside-toplevel
-
-    previous = pickle.HIGHEST_PROTOCOL
-    try:
-        pickle.HIGHEST_PROTOCOL = 4
-        if protocol:
-            pickle.HIGHEST_PROTOCOL = protocol
-        yield
-    finally:
-        pickle.HIGHEST_PROTOCOL = previous
-
-
-
-def resolve_template(path: str, options: MutableMapping[str, Any]) ‑> str -
-
-

Given a string path, it attempts to replace all templates fields with values provided in options.

-

If path is not a string template, path is returned.

-

Args

-
-
path
-
A string which is either a template, e.g. /path/to/file/{replace_me}.h5 or just a path /path/to/file/dont_replace_me.h5
-
options
-
A dynamic name for the "replace_me" field in the templated string. e.g. {"replace_me": "name_of_file"}
-
-

Returns

-
-
str
-
Returns a static path replaced with the value in the options mapping.
-
-

Raises

-
-
ValueError
-
if any template fields in s are not named using valid Python identifiers
-
ValueError
-
if a given template field cannot be resolved in options
-
-
- -Expand source code - -
def resolve_template(path: str, options: MutableMapping[str, Any]) -> str:  # pylint: disable=C0103
-    """Given a string `path`, it attempts to replace all templates fields with values provided in `options`.
-
-    If `path` is not a string template, `path` is returned.
-
-    Args:
-        path: A string which is either a template, e.g. /path/to/file/{replace_me}.h5 or just a path /path/to/file/dont_replace_me.h5
-        options: A dynamic name for the "replace_me" field in the templated string. e.g. {"replace_me": "name_of_file"}
-
-    Returns:
-        str: Returns a static path replaced with the value in the options mapping.
-
-    Raises:
-        ValueError: if any template fields in s are not named using valid Python identifiers
-        ValueError: if a given template field cannot be resolved in `options`
-    """
-    fields = get_string_template_field_names(path)
-
-    if len(fields) == 0:
-        return path
-
-    if not all(field.isidentifier() for field in fields):
-        raise ValueError(f"Expected valid Python identifiers, found {fields}")
-
-    if not all(field in options for field in fields):
-        raise ValueError(f"Expected values for all fields in {fields}, found {list(options.keys())}")
-
-    path = path.format(**{field: options[field] for field in fields})
-    for field in fields:
-        options.pop(field)
-
-    return path
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/mixins/with_kafka.html b/docs/dynamicio/mixins/with_kafka.html deleted file mode 100644 index 4444e0b..0000000 --- a/docs/dynamicio/mixins/with_kafka.html +++ /dev/null @@ -1,1129 +0,0 @@ - - - - - - -dynamicio.mixins.with_kafka API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_kafka

-
-
-

This module provides mixins that are providing Kafka I/O support.

-
- -Expand source code - -
"""This module provides mixins that are providing Kafka I/O support."""
-# pylint: disable=no-member, protected-access, too-few-public-methods
-
-from typing import Any, Callable, Mapping, MutableMapping, Optional
-
-import pandas as pd  # type: ignore
-import simplejson
-from confluent_kafka import Producer
-from magic_logger import logger
-
-from dynamicio.config.pydantic import DataframeSchema, KafkaDataEnvironment
-from dynamicio.mixins import utils
-
-
-class WithKafka:
-    """Handles I/O operations for Kafka.
-
-    Args:
-        - options:
-            - Standard: Keyword-arguments passed to the KafkaProducer constructor (see `KafkaProducer.DEFAULT_CONFIG.keys()`).
-             - Additional Options:
-
-                - `key_generator: Callable[[Any, Mapping], T]`: defines the keying policy to be used for sending keyed-messages to Kafka. It is a `Callable` that takes a
-                `tuple(idx, row)` and returns a string that will serve as the message's key, invoked prior to serialising the key. It defaults to the dataframe's index
-                (which may not be composed of unique values or string type keys). It goes hand in hand with the default `key-serialiser`, which assumes that the keys
-                are strings and encode's them as such.
-
-                - `key_serializer: Callable[T, bytes]`: Custom key serialiser; if not provided, a default key-serializer will be used, applied on a string-key (unless key is None).
-
-                N.B. Providing a custom key-generator that generates a non-string key is best provided alongside a custom key-serializer best suited to handle the custom key-type.
-
-                - `document_transformer: Callable[[Mapping[Any, Any]`: Manipulates the messages/rows sent to Kafka as values. It is  a `Callable` taking a `Mapping` as its only
-                argument and return a `Mapping`, then this callable will be invoked prior to serializing each document. This can be used, for example, to add metadata to each
-                document that will be written to the target  Kafka topic.
-
-                - `value_serializer: Callable[Mapping, bytes]`: Custom value serialiser; if not provided, a default value-serializer will be used applied on a Mapping..
-
-    Example:
-        >>> # Given
-        >>> keyed_test_df = pd.DataFrame.from_records(
-        >>>     [
-        >>>         ["key-01", "cm_1", "id_1", 1000, "ABC"],
-        >>>         ["key-02", "cm_2", "id_2", 1000, "ABC"],
-        >>>         ["key-03", "cm_3", "id_3", 1000, "ABC"],
-        >>>     ],
-        >>>     columns=["key", "id", "foo", "bar", "baz"],
-        >>> ).set_index("key")
-        >>>
-        >>> kafka_cloud_config = IOConfig(
-        >>>     path_to_source_yaml=(os.path.join(constants.TEST_RESOURCES, "processed.yaml")),
-        >>>     env_identifier="CLOUD",
-        >>>     dynamic_vars=constants,
-        >>> ).get(source_key="WRITE_TO_KAFKA_JSON")
-        >>>
-        >>> write_kafka_io = WriteKafkaIO(kafka_cloud_config, key_generator=lambda key, _: key, document_transformer=lambda doc: doc["new_field"]="new_value")
-        >>>
-        >>> # When
-        >>> with patch.object(mixins, "KafkaProducer") as mock__kafka_producer:
-        >>>     mock__kafka_producer.DEFAULT_CONFIG = KafkaProducer.DEFAULT_CONFIG
-        >>>     mock_producer = MockKafkaProducer()
-        >>>     mock__kafka_producer.return_value = mock_producer
-        >>>     write_kafka_io.write(keyed_test_df)
-        >>>
-        >>> # Then
-        >>> assert mock_producer.my_stream == [
-        >>>     {"key": "key-01", "value": {"bar": 1000, "baz": "ABC", "foo": "id_1", "id": "cm_1", "new_field": "new_value"}},
-        >>>     {"key": "key-02", "value": {"bar": 1000, "baz": "ABC", "foo": "id_2", "id": "cm_2", "new_field": "new_value"}},
-        >>>     {"key": "key-03", "value": {"bar": 1000, "baz": "ABC", "foo": "id_3", "id": "cm_3", "new_field": "new_value"}},
-        >>> ]
-    """
-
-    sources_config: KafkaDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-    __kafka_config: Optional[Mapping] = None
-    __producer: Optional[Producer] = None
-    __key_generator: Optional[Callable[[Any, Mapping[Any, Any]], Optional[str]]] = None
-    __document_transformer: Optional[Callable[[Mapping[Any, Any]], Mapping[Any, Any]]] = None
-    __key_serializer: Optional[Callable[[Optional[str]], Optional[bytes]]] = None
-    __value_serializer: Optional[Callable[[Mapping[Any, Any]], bytes]] = None
-
-    # N.B.: Please refer to https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md and update this config in case of a major release change.
-    VALID_CONFIG_KEYS = {
-        "acks",
-        "allow.auto.create.topics",
-        "api.version.fallback.ms",
-        "api.version.request.timeout.ms",
-        "api.version.request",
-        "auto.commit.enable",
-        "auto.commit.interval.ms",
-        "auto.offset.reset",
-        "background_event_cb",
-        "batch.size",
-        "batch.num.messages",
-        "bootstrap.servers",
-        "broker.address.family",
-        "broker.address.ttl",
-        "broker.version.fallback",
-        "builtin.features",
-        "check.crcs",
-        "client.dns.lookup",
-        "client.id",
-        "client.rack",
-        "closesocket_cb",
-        "compression.codec",
-        "compression.level",
-        "compression.type",
-        "connect_cb",
-        "connections.max.idle.ms",
-        "consume_cb",
-        "consume.callback.max.messages",
-        "coordinator.query.interval.ms",
-        "debug",
-        "default_topic_conf",
-        "delivery.report.only.error",
-        "dr_cb",
-        "dr_msg_cb",
-        "enable.auto.commit",
-        "enable.auto.offset.store",
-        "enable.gapless.guarantee",
-        "enable.idempotence",
-        "enable.partition.eof",
-        "enable.random.seed",
-        "enable.sasl.oauthbearer.unsecure.jwt",
-        "enable.ssl.certificate.verification",
-        "enabled_events",
-        "error_cb",
-        "fetch.error.backoff.ms",
-        "fetch.max.bytes",
-        "fetch.message.max.bytes",
-        "fetch.min.bytes",
-        "fetch.queue.backoff.ms",
-        "fetch.wait.max.ms",
-        "group.id",
-        "group.instance.id",
-        "group.protocol.type",
-        "group.protocol",
-        "group.remote.assignor",
-        "heartbeat.interval.ms",
-        "interceptors",
-        "internal.termination.signal",
-        "isolation.level",
-        "linger.ms",
-        "log_cb",
-        "log_level",
-        "log.connection.close",
-        "log.queue",
-        "log.thread.name",
-        "max.block.ms",
-        "max.in.flight.requests.per.connection",
-        "max.in.flight",
-        "max.partition.fetch.bytes",
-        "max.poll.interval.ms",
-        "max.request.size",
-        "message.copy.max.bytes",
-        "message.max.bytes",
-        "message.send.max.retries",
-        "metadata.broker.list",
-        "metadata.max.age.ms",
-        "msg_order_cmp",
-        "oauth_cb",
-        "oauthbearer_token_refresh_cb",
-        "offset_commit_cb",
-        "offset.store.method",
-        "offset.store.path",
-        "offset.store.sync.interval.ms",
-        "on_delivery",
-        "opaque",
-        "open_cb",
-        "partition.assignment.strategy",
-        "partitioner_cb",
-        "partitioner",
-        "plugin.library.paths",
-        "produce.offset.report",
-        "queue.buffering.backpressure.threshold",
-        "queue.buffering.max.kbytes",
-        "queue.buffering.max.messages",
-        "queue.buffering.max.ms",
-        "queued.max.messages.kbytes",
-        "queued.min.messages",
-        "rebalance_cb",
-        "receive.buffer.bytes",
-        "receive.message.max.bytes",
-        "reconnect.backoff.jitter.ms",
-        "reconnect.backoff.max.ms",
-        "reconnect.backoff.ms",
-        "request.timeout.ms",
-        "resolve_cb",
-        "retry.backoff.ms",
-        "sasl.kerberos.keytab",
-        "sasl.kerberos.kinit.cmd",
-        "sasl.kerberos.min.time.before.relogin",
-        "sasl.kerberos.principal",
-        "sasl.kerberos.service.name",
-        "sasl.mechanisms",
-        "sasl.oauthbearer.client.id",
-        "sasl.oauthbearer.client.secret",
-        "sasl.oauthbearer.config",
-        "sasl.oauthbearer.extensions",
-        "sasl.oauthbearer.method",
-        "sasl.oauthbearer.scope",
-        "sasl.oauthbearer.token.endpoint.url",
-        "sasl.password",
-        "sasl.username",
-        "security.protocol",
-        "send.buffer.bytes",
-        "session.timeout.ms",
-        "socket_cb",
-        "socket.blocking.max.ms",
-        "socket.connection.setup.timeout.ms",
-        "socket.keepalive.enable",
-        "socket.max.fails",
-        "socket.nagle.disable",
-        "socket.timeout.ms",
-        "ssl_engine_callback_data",
-        "ssl.ca.certificate.stores",
-        "ssl.ca.location",
-        "ssl.certificate.location",
-        "ssl.certificate.verify_cb",
-        "ssl.cipher.suites",
-        "ssl.crl.location",
-        "ssl.curves.list",
-        "ssl.endpoint.identification.algorithm",
-        "ssl.engine.id",
-        "ssl.engine.location",
-        "ssl.key.location",
-        "ssl.key.password",
-        "ssl.keystore.location",
-        "ssl.keystore.password",
-        "ssl.providers",
-        "ssl.sigalgs.list",
-        "statistics.interval.ms",
-        "sticky.partitioning.linger.ms",
-        "throttle_cb",
-        "topic.blacklist",
-        "topic.metadata.propagation.max.ms",
-        "topic.metadata.refresh.fast.cnt",
-        "topic.metadata.refresh.fast.interval.ms",
-        "topic.metadata.refresh.interval.ms",
-        "topic.metadata.refresh.sparse",
-        "transaction.timeout.ms",
-        "transactional.id",
-    }
-
-    def _write_to_kafka(self, df: pd.DataFrame) -> None:
-        """Given a dataframe where each row is a message to be sent to a Kafka Topic, iterate through all rows and send them to a Kafka topic.
-
-         The topic is defined in `self.sources_config["kafka"]` and using a kafka producer, which is flushed at the
-         end of this process.
-
-        Args:
-            df: A dataframe where each row is a message to be sent to a Kafka Topic.
-        """
-        self.populate_cls_attributes()
-
-        if self.__producer is None:
-            self.__producer = self._get_producer(self.sources_config.kafka.kafka_server, **self.options)
-
-        self._send_messages(df=df, topic=self.sources_config.kafka.kafka_topic)
-
-    def populate_cls_attributes(self):
-        """Pop dynamicio options (key_generator, document_transformer, key_serializer, value_serializer) from kafka config options."""
-        if self.__key_generator is None:
-            self.__key_generator = lambda idx, __: idx  # default key generator uses the dataframe's index
-            if self.options.get("key_generator") is not None:
-                self.__key_generator = self.options.pop("key_generator")
-        if self.__document_transformer is None:
-            self.__document_transformer = lambda value: value
-            if self.options.get("document_transformer") is not None:
-                self.__document_transformer = self.options.pop("document_transformer")
-        if self.__key_serializer is None:
-            self.__key_serializer = self._default_key_serializer
-            if self.options.get("key_serializer") is not None:
-                self.__key_serializer = self.options.pop("key_serializer")
-        if self.__value_serializer is None:
-            self.__value_serializer = self._default_value_serializer
-            if self.options.get("value_serializer") is not None:
-                self.__value_serializer = self.options.pop("value_serializer")
-
-    @utils.allow_options(VALID_CONFIG_KEYS)
-    def _get_producer(self, server: str, **options: MutableMapping[str, Any]) -> Producer:
-        """Generate and return a Kafka Producer.
-
-        Default options are used to generate the producer. Specifically:
-            - `bootstrap.servers`: Passed on through the source_config
-            - `compression.type`: Uses snappy compression
-
-        More options can be added to the producer by passing them as keyword arguments, through valid options.
-
-        These can also override the default options.
-
-        Args:
-            server: The host name.
-            **options: Keyword arguments to pass to the KafkaProducer.
-
-        Returns:
-            A Kafka producer instance.
-        """
-        self.__kafka_config = {
-            "bootstrap.servers": server,
-            "compression.type": "snappy",
-            **options,
-        }
-        return Producer(**self.__kafka_config)
-
-    def _send_messages(self, df: pd.DataFrame, topic: str) -> None:
-        logger.debug(f"Sending {len(df)} messages to Kafka topic: {topic}.")
-        messages = df.reset_index(drop=True).to_dict("records")
-
-        for idx, message in zip(df.index.values, messages):
-            key = self.__key_generator(idx, message)
-            transformed_message = self.__document_transformer(message)
-            serialized_key = self.__key_serializer(key)
-            serialized_value = self.__value_serializer(transformed_message)
-
-            self.__producer.produce(topic=topic, key=serialized_key, value=serialized_value, on_delivery=self._on_delivery)
-
-        self.__producer.flush()
-
-    @staticmethod
-    def _on_delivery(err, msg):
-        """Callback for message delivery."""
-        if err is not None:
-            raise Exception(f"Message delivery failed: {err}, for message: {msg}")
-
-    @staticmethod
-    def _default_key_serializer(key: Optional[Any]) -> Optional[bytes]:
-        if key is not None:
-            return str(key).encode("utf-8")
-        return None
-
-    @staticmethod
-    def _default_value_serializer(value: Mapping) -> bytes:
-        return simplejson.dumps(value, ignore_nan=True).encode("utf-8")
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Producer -(*args, **kwargs) -
-
-

Asynchronous Kafka Producer

-

.. py:function:: Producer(config)

-

:param dict config: Configuration properties. At a minimum bootstrap.servers should be set

-

Create a new Producer instance using the provided configuration dict.

-

.. py:function:: len(self)

-

Producer implements len that can be used as len(producer) to obtain number of messages waiting. -:returns: Number of messages and Kafka protocol requests waiting to be delivered to broker. -:rtype: int

-

Subclasses

-
    -
  • confluent_kafka.serializing_producer.SerializingProducer
  • -
-

Methods

-
-
-def abort_transaction(...) -
-
-

.. py:function:: abort_transaction([timeout])

-

Aborts the current transaction. -This function should also be used to recover from non-fatal -abortable transaction errors when KafkaError.txn_requires_abort() -is True.

-

Any outstanding messages will be purged and fail with -_PURGE_INFLIGHT or _PURGE_QUEUE.

-

Note: This function will block until all outstanding messages -are purged and the transaction abort request has been -successfully handled by the transaction coordinator, or until -the timeout expires, which ever comes first. On timeout the -application may call the function again.

-

Note: Will automatically call purge() and flush() -to ensure -all queued and in-flight messages are purged before attempting -to abort the transaction.

-

:param float timeout: The maximum amount of time to block -waiting for transaction to abort in seconds.

-

:raises: KafkaError: Use exc.args[0].retriable() to check if the -operation may be retried. -Treat any other error as a fatal error.

-
-
-def begin_transaction(...) -
-
-

.. py:function:: begin_transaction()

-

Begin a new transaction.

-

init_transactions() must have been called successfully (once) -before this function is called.

-

Any messages produced or offsets sent to a transaction, after -the successful return of this function will be part of the -transaction and committed or aborted atomically.

-

Complete the transaction by calling commit_transaction() or -Abort the transaction by calling abort_transaction().

-

:raises: KafkaError: Use exc.args[0].retriable() to check if the -operation may be retried, else treat the -error as a fatal error.

-
-
-def commit_transaction(...) -
-
-

.. py:function:: commit_transaction([timeout])

-

Commmit the current transaction. -Any outstanding messages will be flushed (delivered) before -actually committing the transaction.

-

If any of the outstanding messages fail permanently the current -transaction will enter the abortable error state and this -function will return an abortable error, in this case the -application must call abort_transaction() before attempting -a new transaction with begin_transaction().

-

Note: This function will block until all outstanding messages -are delivered and the transaction commit request has been -successfully handled by the transaction coordinator, or until -the timeout expires, which ever comes first. On timeout the -application may call the function again.

-

Note: Will automatically call flush() to ensure all queued -messages are delivered before attempting to commit the -transaction. Delivery reports and other callbacks may thus be -triggered from this method.

-

:param float timeout: The amount of time to block in seconds.

-

:raises: KafkaError: Use exc.args[0].retriable() to check if the -operation may be retried, or -exc.args[0].txn_requires_abort() if the current -transaction has failed and must be aborted by calling -abort_transaction() and then start a new transaction -with begin_transaction(). -Treat any other error as a fatal error.

-
-
-def flush(...) -
-
-

.. py:function:: flush([timeout])

-

Wait for all messages in the Producer queue to be delivered. -This is a convenience method that calls :py:func:poll() until :py:func:len() is zero or the optional timeout elapses.

-

:param: float timeout: Maximum time to block (requires librdkafka >= v0.9.4). (Seconds) -:returns: Number of messages still in queue.

-
-

Note: See :py:func:poll() for a description on what callbacks may be triggered.

-
-
-
-def init_transactions(...) -
-
-

.. py:function: init_transactions([timeout])

-

Initializes transactions for the producer instance.

-

This function ensures any transactions initiated by previous -instances of the producer with the same transactional.id are -completed. -If the previous instance failed with a transaction in progress -the previous transaction will be aborted. -This function needs to be called before any other transactional -or produce functions are called when the transactional.id is -configured.

-

If the last transaction had begun completion (following -transaction commit) but not yet finished, this function will -await the previous transaction's completion.

-

When any previous transactions have been fenced this function -will acquire the internal producer id and epoch, used in all -future transactional messages issued by this producer instance.

-

Upon successful return from this function the application has to -perform at least one of the following operations within -transaction.timeout.ms to avoid timing out the transaction -on the broker: -* produce() (et.al) -* send_offsets_to_transaction() -* commit_transaction() -* abort_transaction()

-

:param float timeout: Maximum time to block in seconds.

-

:raises: KafkaError: Use exc.args[0].retriable() to check if the -operation may be retried, else treat the -error as a fatal error.

-
-
-def list_topics(...) -
-
-

.. py:function:: list_topics([topic=None], [timeout=-1])

-

Request metadata from the cluster. -This method provides the same information as -listTopics(), describeTopics() and describeCluster() in -the Java Admin client.

-

:param str topic: If specified, only request information about this topic, else return results for all topics in cluster. Warning: If auto.create.topics.enable is set to true on the broker and an unknown topic is specified, it will be created. -:param float timeout: The maximum response time before timing out, or -1 for infinite timeout. -:rtype: ClusterMetadata -:raises: KafkaException

-
-
-def poll(...) -
-
-

.. py:function:: poll([timeout])

-

Polls the producer for events and calls the corresponding callbacks (if registered).

-

Callbacks:

-
    -
  • on_delivery callbacks from :py:func:produce()
  • -
  • -
-

:param float timeout: Maximum time to block waiting for events. (Seconds) -:returns: Number of events processed (callbacks served) -:rtype: int

-
-
-def produce(...) -
-
-

.. py:function:: produce(topic, [value], [key], [partition], [on_delivery], [timestamp], [headers])

-

Produce message to topic. -This is an asynchronous operation, an application may use the callback (alias on_delivery) argument to pass a function (or lambda) that will be called from :py:func:poll() when the message has been successfully delivered or permanently fails delivery.

-

Currently message headers are not supported on the message returned to the callback. The msg.headers() will return None even if the original message had headers set.

-

:param str topic: Topic to produce message to -:param str|bytes value: Message payload -:param str|bytes key: Message key -:param int partition: Partition to produce to, else uses the configured built-in partitioner. -:param func on_delivery(err,msg): Delivery report callback to call (from :py:func:poll() or :py:func:flush()) on successful or failed delivery -:param int timestamp: Message timestamp (CreateTime) in milliseconds since epoch UTC (requires librdkafka >= v0.9.4, api.version.request=true, and broker >= 0.10.0.0). Default value is current time.

-

:param dict|list headers: Message headers to set on the message. The header key must be a string while the value must be binary, unicode or None. Accepts a list of (key,value) or a dict. (Requires librdkafka >= v0.11.4 and broker version >= 0.11.0.0) -:rtype: None -:raises BufferError: if the internal producer message queue is full (queue.buffering.max.messages exceeded) -:raises KafkaException: for other errors, see exception code -:raises NotImplementedError: if timestamp is specified without underlying library support.

-
-
-def purge(...) -
-
-

.. py:function:: purge([in_queue=True], [in_flight=True], [blocking=True])

-

Purge messages currently handled by the producer instance. -The application will need to call poll() or flush() afterwards to serve the delivery report callbacks of the purged messages.

-

:param: bool in_queue: Purge messages from internal queues. By default, true. -:param: bool in_flight: Purge messages in flight to or from the broker. By default, true. -:param: bool blocking: If set to False, will not wait on background thread queue purging to finish. By default, true.

-
-
-def send_offsets_to_transaction(...) -
-
-

.. py:function:: send_offsets_to_transaction(positions, group_metadata, [timeout])

-

Sends a list of topic partition offsets to the consumer group -coordinator for group_metadata and marks the offsets as part -of the current transaction. -These offsets will be considered committed only if the -transaction is committed successfully.

-

The offsets should be the next message your application will -consume, i.e., the last processed message's offset + 1 for each -partition. -Either track the offsets manually during processing or use -consumer.position() (on the consumer) to get the current offsets -for the partitions assigned to the consumer.

-

Use this method at the end of a consume-transform-produce loop -prior to committing the transaction with commit_transaction().

-

Note: The consumer must disable auto commits -(set enable.auto.commit to false on the consumer).

-

Note: Logical and invalid offsets (e.g., OFFSET_INVALID) in -offsets will be ignored. If there are no valid offsets in -offsets the function will return successfully and no action -will be taken.

-

:param list(TopicPartition) offsets: current consumer/processing -position(offsets) for the -list of partitions. -:param object group_metadata: consumer group metadata retrieved -from the input consumer's -get_consumer_group_metadata(). -:param float timeout: Amount of time to block in seconds.

-

:raises: KafkaError: Use exc.args[0].retriable() to check if the -operation may be retried, or -exc.args[0].txn_requires_abort() if the current -transaction has failed and must be aborted by calling -abort_transaction() and then start a new transaction -with begin_transaction(). -Treat any other error as a fatal error.

-
-
-def set_sasl_credentials(...) -
-
-

.. py:function:: set_sasl_credentials(username, password)

-

Sets the SASL credentials used for this client. -These credentials will overwrite the old ones, and will be used the next time the client needs to authenticate. -This method will not disconnect existing broker connections that have been established with the old credentials. -This method is applicable only to SASL PLAIN and SCRAM mechanisms.

-
-
-
-
-class WithKafka -
-
-

Handles I/O operations for Kafka.

-

Args

-
    -
  • options:
      -
    • Standard: Keyword-arguments passed to the KafkaProducer constructor (see KafkaProducer.DEFAULT_CONFIG.keys()).
    • -
    • -

      Additional Options:

      -
        -
      • -

        key_generator: Callable[[Any, Mapping], T]: defines the keying policy to be used for sending keyed-messages to Kafka. It is a Callable that takes a -tuple(idx, row) and returns a string that will serve as the message's key, invoked prior to serialising the key. It defaults to the dataframe's index -(which may not be composed of unique values or string type keys). It goes hand in hand with the default key-serialiser, which assumes that the keys -are strings and encode's them as such.

        -
      • -
      • -

        key_serializer: Callable[T, bytes]: Custom key serialiser; if not provided, a default key-serializer will be used, applied on a string-key (unless key is None).

        -
      • -
      -

      N.B. Providing a custom key-generator that generates a non-string key is best provided alongside a custom key-serializer best suited to handle the custom key-type.

      -
        -
      • -

        document_transformer: Callable[[Mapping[Any, Any]: Manipulates the messages/rows sent to Kafka as values. It is -a Callable taking a Mapping as its only -argument and return a Mapping, then this callable will be invoked prior to serializing each document. This can be used, for example, to add metadata to each -document that will be written to the target -Kafka topic.

        -
      • -
      • -

        value_serializer: Callable[Mapping, bytes]: Custom value serialiser; if not provided, a default value-serializer will be used applied on a Mapping..

        -
      • -
      -
    • -
    -
  • -
-

Example

-
>>> # Given
->>> keyed_test_df = pd.DataFrame.from_records(
->>>     [
->>>         ["key-01", "cm_1", "id_1", 1000, "ABC"],
->>>         ["key-02", "cm_2", "id_2", 1000, "ABC"],
->>>         ["key-03", "cm_3", "id_3", 1000, "ABC"],
->>>     ],
->>>     columns=["key", "id", "foo", "bar", "baz"],
->>> ).set_index("key")
->>>
->>> kafka_cloud_config = IOConfig(
->>>     path_to_source_yaml=(os.path.join(constants.TEST_RESOURCES, "processed.yaml")),
->>>     env_identifier="CLOUD",
->>>     dynamic_vars=constants,
->>> ).get(source_key="WRITE_TO_KAFKA_JSON")
->>>
->>> write_kafka_io = WriteKafkaIO(kafka_cloud_config, key_generator=lambda key, _: key, document_transformer=lambda doc: doc["new_field"]="new_value")
->>>
->>> # When
->>> with patch.object(mixins, "KafkaProducer") as mock__kafka_producer:
->>>     mock__kafka_producer.DEFAULT_CONFIG = KafkaProducer.DEFAULT_CONFIG
->>>     mock_producer = MockKafkaProducer()
->>>     mock__kafka_producer.return_value = mock_producer
->>>     write_kafka_io.write(keyed_test_df)
->>>
->>> # Then
->>> assert mock_producer.my_stream == [
->>>     {"key": "key-01", "value": {"bar": 1000, "baz": "ABC", "foo": "id_1", "id": "cm_1", "new_field": "new_value"}},
->>>     {"key": "key-02", "value": {"bar": 1000, "baz": "ABC", "foo": "id_2", "id": "cm_2", "new_field": "new_value"}},
->>>     {"key": "key-03", "value": {"bar": 1000, "baz": "ABC", "foo": "id_3", "id": "cm_3", "new_field": "new_value"}},
->>> ]
-
-
- -Expand source code - -
class WithKafka:
-    """Handles I/O operations for Kafka.
-
-    Args:
-        - options:
-            - Standard: Keyword-arguments passed to the KafkaProducer constructor (see `KafkaProducer.DEFAULT_CONFIG.keys()`).
-             - Additional Options:
-
-                - `key_generator: Callable[[Any, Mapping], T]`: defines the keying policy to be used for sending keyed-messages to Kafka. It is a `Callable` that takes a
-                `tuple(idx, row)` and returns a string that will serve as the message's key, invoked prior to serialising the key. It defaults to the dataframe's index
-                (which may not be composed of unique values or string type keys). It goes hand in hand with the default `key-serialiser`, which assumes that the keys
-                are strings and encode's them as such.
-
-                - `key_serializer: Callable[T, bytes]`: Custom key serialiser; if not provided, a default key-serializer will be used, applied on a string-key (unless key is None).
-
-                N.B. Providing a custom key-generator that generates a non-string key is best provided alongside a custom key-serializer best suited to handle the custom key-type.
-
-                - `document_transformer: Callable[[Mapping[Any, Any]`: Manipulates the messages/rows sent to Kafka as values. It is  a `Callable` taking a `Mapping` as its only
-                argument and return a `Mapping`, then this callable will be invoked prior to serializing each document. This can be used, for example, to add metadata to each
-                document that will be written to the target  Kafka topic.
-
-                - `value_serializer: Callable[Mapping, bytes]`: Custom value serialiser; if not provided, a default value-serializer will be used applied on a Mapping..
-
-    Example:
-        >>> # Given
-        >>> keyed_test_df = pd.DataFrame.from_records(
-        >>>     [
-        >>>         ["key-01", "cm_1", "id_1", 1000, "ABC"],
-        >>>         ["key-02", "cm_2", "id_2", 1000, "ABC"],
-        >>>         ["key-03", "cm_3", "id_3", 1000, "ABC"],
-        >>>     ],
-        >>>     columns=["key", "id", "foo", "bar", "baz"],
-        >>> ).set_index("key")
-        >>>
-        >>> kafka_cloud_config = IOConfig(
-        >>>     path_to_source_yaml=(os.path.join(constants.TEST_RESOURCES, "processed.yaml")),
-        >>>     env_identifier="CLOUD",
-        >>>     dynamic_vars=constants,
-        >>> ).get(source_key="WRITE_TO_KAFKA_JSON")
-        >>>
-        >>> write_kafka_io = WriteKafkaIO(kafka_cloud_config, key_generator=lambda key, _: key, document_transformer=lambda doc: doc["new_field"]="new_value")
-        >>>
-        >>> # When
-        >>> with patch.object(mixins, "KafkaProducer") as mock__kafka_producer:
-        >>>     mock__kafka_producer.DEFAULT_CONFIG = KafkaProducer.DEFAULT_CONFIG
-        >>>     mock_producer = MockKafkaProducer()
-        >>>     mock__kafka_producer.return_value = mock_producer
-        >>>     write_kafka_io.write(keyed_test_df)
-        >>>
-        >>> # Then
-        >>> assert mock_producer.my_stream == [
-        >>>     {"key": "key-01", "value": {"bar": 1000, "baz": "ABC", "foo": "id_1", "id": "cm_1", "new_field": "new_value"}},
-        >>>     {"key": "key-02", "value": {"bar": 1000, "baz": "ABC", "foo": "id_2", "id": "cm_2", "new_field": "new_value"}},
-        >>>     {"key": "key-03", "value": {"bar": 1000, "baz": "ABC", "foo": "id_3", "id": "cm_3", "new_field": "new_value"}},
-        >>> ]
-    """
-
-    sources_config: KafkaDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-    __kafka_config: Optional[Mapping] = None
-    __producer: Optional[Producer] = None
-    __key_generator: Optional[Callable[[Any, Mapping[Any, Any]], Optional[str]]] = None
-    __document_transformer: Optional[Callable[[Mapping[Any, Any]], Mapping[Any, Any]]] = None
-    __key_serializer: Optional[Callable[[Optional[str]], Optional[bytes]]] = None
-    __value_serializer: Optional[Callable[[Mapping[Any, Any]], bytes]] = None
-
-    # N.B.: Please refer to https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md and update this config in case of a major release change.
-    VALID_CONFIG_KEYS = {
-        "acks",
-        "allow.auto.create.topics",
-        "api.version.fallback.ms",
-        "api.version.request.timeout.ms",
-        "api.version.request",
-        "auto.commit.enable",
-        "auto.commit.interval.ms",
-        "auto.offset.reset",
-        "background_event_cb",
-        "batch.size",
-        "batch.num.messages",
-        "bootstrap.servers",
-        "broker.address.family",
-        "broker.address.ttl",
-        "broker.version.fallback",
-        "builtin.features",
-        "check.crcs",
-        "client.dns.lookup",
-        "client.id",
-        "client.rack",
-        "closesocket_cb",
-        "compression.codec",
-        "compression.level",
-        "compression.type",
-        "connect_cb",
-        "connections.max.idle.ms",
-        "consume_cb",
-        "consume.callback.max.messages",
-        "coordinator.query.interval.ms",
-        "debug",
-        "default_topic_conf",
-        "delivery.report.only.error",
-        "dr_cb",
-        "dr_msg_cb",
-        "enable.auto.commit",
-        "enable.auto.offset.store",
-        "enable.gapless.guarantee",
-        "enable.idempotence",
-        "enable.partition.eof",
-        "enable.random.seed",
-        "enable.sasl.oauthbearer.unsecure.jwt",
-        "enable.ssl.certificate.verification",
-        "enabled_events",
-        "error_cb",
-        "fetch.error.backoff.ms",
-        "fetch.max.bytes",
-        "fetch.message.max.bytes",
-        "fetch.min.bytes",
-        "fetch.queue.backoff.ms",
-        "fetch.wait.max.ms",
-        "group.id",
-        "group.instance.id",
-        "group.protocol.type",
-        "group.protocol",
-        "group.remote.assignor",
-        "heartbeat.interval.ms",
-        "interceptors",
-        "internal.termination.signal",
-        "isolation.level",
-        "linger.ms",
-        "log_cb",
-        "log_level",
-        "log.connection.close",
-        "log.queue",
-        "log.thread.name",
-        "max.block.ms",
-        "max.in.flight.requests.per.connection",
-        "max.in.flight",
-        "max.partition.fetch.bytes",
-        "max.poll.interval.ms",
-        "max.request.size",
-        "message.copy.max.bytes",
-        "message.max.bytes",
-        "message.send.max.retries",
-        "metadata.broker.list",
-        "metadata.max.age.ms",
-        "msg_order_cmp",
-        "oauth_cb",
-        "oauthbearer_token_refresh_cb",
-        "offset_commit_cb",
-        "offset.store.method",
-        "offset.store.path",
-        "offset.store.sync.interval.ms",
-        "on_delivery",
-        "opaque",
-        "open_cb",
-        "partition.assignment.strategy",
-        "partitioner_cb",
-        "partitioner",
-        "plugin.library.paths",
-        "produce.offset.report",
-        "queue.buffering.backpressure.threshold",
-        "queue.buffering.max.kbytes",
-        "queue.buffering.max.messages",
-        "queue.buffering.max.ms",
-        "queued.max.messages.kbytes",
-        "queued.min.messages",
-        "rebalance_cb",
-        "receive.buffer.bytes",
-        "receive.message.max.bytes",
-        "reconnect.backoff.jitter.ms",
-        "reconnect.backoff.max.ms",
-        "reconnect.backoff.ms",
-        "request.timeout.ms",
-        "resolve_cb",
-        "retry.backoff.ms",
-        "sasl.kerberos.keytab",
-        "sasl.kerberos.kinit.cmd",
-        "sasl.kerberos.min.time.before.relogin",
-        "sasl.kerberos.principal",
-        "sasl.kerberos.service.name",
-        "sasl.mechanisms",
-        "sasl.oauthbearer.client.id",
-        "sasl.oauthbearer.client.secret",
-        "sasl.oauthbearer.config",
-        "sasl.oauthbearer.extensions",
-        "sasl.oauthbearer.method",
-        "sasl.oauthbearer.scope",
-        "sasl.oauthbearer.token.endpoint.url",
-        "sasl.password",
-        "sasl.username",
-        "security.protocol",
-        "send.buffer.bytes",
-        "session.timeout.ms",
-        "socket_cb",
-        "socket.blocking.max.ms",
-        "socket.connection.setup.timeout.ms",
-        "socket.keepalive.enable",
-        "socket.max.fails",
-        "socket.nagle.disable",
-        "socket.timeout.ms",
-        "ssl_engine_callback_data",
-        "ssl.ca.certificate.stores",
-        "ssl.ca.location",
-        "ssl.certificate.location",
-        "ssl.certificate.verify_cb",
-        "ssl.cipher.suites",
-        "ssl.crl.location",
-        "ssl.curves.list",
-        "ssl.endpoint.identification.algorithm",
-        "ssl.engine.id",
-        "ssl.engine.location",
-        "ssl.key.location",
-        "ssl.key.password",
-        "ssl.keystore.location",
-        "ssl.keystore.password",
-        "ssl.providers",
-        "ssl.sigalgs.list",
-        "statistics.interval.ms",
-        "sticky.partitioning.linger.ms",
-        "throttle_cb",
-        "topic.blacklist",
-        "topic.metadata.propagation.max.ms",
-        "topic.metadata.refresh.fast.cnt",
-        "topic.metadata.refresh.fast.interval.ms",
-        "topic.metadata.refresh.interval.ms",
-        "topic.metadata.refresh.sparse",
-        "transaction.timeout.ms",
-        "transactional.id",
-    }
-
-    def _write_to_kafka(self, df: pd.DataFrame) -> None:
-        """Given a dataframe where each row is a message to be sent to a Kafka Topic, iterate through all rows and send them to a Kafka topic.
-
-         The topic is defined in `self.sources_config["kafka"]` and using a kafka producer, which is flushed at the
-         end of this process.
-
-        Args:
-            df: A dataframe where each row is a message to be sent to a Kafka Topic.
-        """
-        self.populate_cls_attributes()
-
-        if self.__producer is None:
-            self.__producer = self._get_producer(self.sources_config.kafka.kafka_server, **self.options)
-
-        self._send_messages(df=df, topic=self.sources_config.kafka.kafka_topic)
-
-    def populate_cls_attributes(self):
-        """Pop dynamicio options (key_generator, document_transformer, key_serializer, value_serializer) from kafka config options."""
-        if self.__key_generator is None:
-            self.__key_generator = lambda idx, __: idx  # default key generator uses the dataframe's index
-            if self.options.get("key_generator") is not None:
-                self.__key_generator = self.options.pop("key_generator")
-        if self.__document_transformer is None:
-            self.__document_transformer = lambda value: value
-            if self.options.get("document_transformer") is not None:
-                self.__document_transformer = self.options.pop("document_transformer")
-        if self.__key_serializer is None:
-            self.__key_serializer = self._default_key_serializer
-            if self.options.get("key_serializer") is not None:
-                self.__key_serializer = self.options.pop("key_serializer")
-        if self.__value_serializer is None:
-            self.__value_serializer = self._default_value_serializer
-            if self.options.get("value_serializer") is not None:
-                self.__value_serializer = self.options.pop("value_serializer")
-
-    @utils.allow_options(VALID_CONFIG_KEYS)
-    def _get_producer(self, server: str, **options: MutableMapping[str, Any]) -> Producer:
-        """Generate and return a Kafka Producer.
-
-        Default options are used to generate the producer. Specifically:
-            - `bootstrap.servers`: Passed on through the source_config
-            - `compression.type`: Uses snappy compression
-
-        More options can be added to the producer by passing them as keyword arguments, through valid options.
-
-        These can also override the default options.
-
-        Args:
-            server: The host name.
-            **options: Keyword arguments to pass to the KafkaProducer.
-
-        Returns:
-            A Kafka producer instance.
-        """
-        self.__kafka_config = {
-            "bootstrap.servers": server,
-            "compression.type": "snappy",
-            **options,
-        }
-        return Producer(**self.__kafka_config)
-
-    def _send_messages(self, df: pd.DataFrame, topic: str) -> None:
-        logger.debug(f"Sending {len(df)} messages to Kafka topic: {topic}.")
-        messages = df.reset_index(drop=True).to_dict("records")
-
-        for idx, message in zip(df.index.values, messages):
-            key = self.__key_generator(idx, message)
-            transformed_message = self.__document_transformer(message)
-            serialized_key = self.__key_serializer(key)
-            serialized_value = self.__value_serializer(transformed_message)
-
-            self.__producer.produce(topic=topic, key=serialized_key, value=serialized_value, on_delivery=self._on_delivery)
-
-        self.__producer.flush()
-
-    @staticmethod
-    def _on_delivery(err, msg):
-        """Callback for message delivery."""
-        if err is not None:
-            raise Exception(f"Message delivery failed: {err}, for message: {msg}")
-
-    @staticmethod
-    def _default_key_serializer(key: Optional[Any]) -> Optional[bytes]:
-        if key is not None:
-            return str(key).encode("utf-8")
-        return None
-
-    @staticmethod
-    def _default_value_serializer(value: Mapping) -> bytes:
-        return simplejson.dumps(value, ignore_nan=True).encode("utf-8")
-
-

Subclasses

- -

Class variables

-
-
var VALID_CONFIG_KEYS
-
-
-
-
var options : MutableMapping[str, Any]
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configKafkaDataEnvironment
-
-
-
-
-

Methods

-
-
-def populate_cls_attributes(self) -
-
-

Pop dynamicio options (key_generator, document_transformer, key_serializer, value_serializer) from kafka config options.

-
- -Expand source code - -
def populate_cls_attributes(self):
-    """Pop dynamicio options (key_generator, document_transformer, key_serializer, value_serializer) from kafka config options."""
-    if self.__key_generator is None:
-        self.__key_generator = lambda idx, __: idx  # default key generator uses the dataframe's index
-        if self.options.get("key_generator") is not None:
-            self.__key_generator = self.options.pop("key_generator")
-    if self.__document_transformer is None:
-        self.__document_transformer = lambda value: value
-        if self.options.get("document_transformer") is not None:
-            self.__document_transformer = self.options.pop("document_transformer")
-    if self.__key_serializer is None:
-        self.__key_serializer = self._default_key_serializer
-        if self.options.get("key_serializer") is not None:
-            self.__key_serializer = self.options.pop("key_serializer")
-    if self.__value_serializer is None:
-        self.__value_serializer = self._default_value_serializer
-        if self.options.get("value_serializer") is not None:
-            self.__value_serializer = self.options.pop("value_serializer")
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/mixins/with_local.html b/docs/dynamicio/mixins/with_local.html deleted file mode 100644 index 16e267d..0000000 --- a/docs/dynamicio/mixins/with_local.html +++ /dev/null @@ -1,715 +0,0 @@ - - - - - - -dynamicio.mixins.with_local API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_local

-
-
-

This module provides mixins that are providing Local FS I/O support.

-
- -Expand source code - -
# pylint: disable=no-member, protected-access, too-few-public-methods
-
-"""This module provides mixins that are providing Local FS I/O support."""
-
-import glob
-import os
-from threading import Lock
-from typing import Any, MutableMapping
-
-import pandas as pd  # type: ignore
-from fastparquet import ParquetFile, write  # type: ignore
-from pyarrow.parquet import read_table, write_table  # type: ignore # pylint: disable=no-name-in-module
-
-from dynamicio.config.pydantic import DataframeSchema, LocalBatchDataEnvironment, LocalDataEnvironment
-from dynamicio.mixins import utils
-from dynamicio.mixins.utils import get_file_type_value
-
-hdf_lock = Lock()
-
-
-class WithLocal:
-    """Handles local I/O operations."""
-
-    schema: DataframeSchema
-    sources_config: LocalDataEnvironment
-    options: MutableMapping[str, Any]
-
-    def _read_from_local(self) -> pd.DataFrame:
-        """Read a local file as a `DataFrame`.
-
-        The configuration object is expected to have two keys:
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using
-        "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = get_file_type_value(local_config.file_type)
-
-        return getattr(self, f"_read_{file_type}_file")(file_path, self.schema, **self.options)
-
-    def _write_to_local(self, df: pd.DataFrame):
-        """Write a dataframe locally based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using
-        "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out.
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = get_file_type_value(local_config.file_type)
-        getattr(self, f"_write_{file_type}_file")(df, file_path, **self.options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_hdf)
-    def _read_hdf_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a HDF file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be read sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            file_path: The path to the hdf file to be read.
-            options: The pandas `read_hdf` options.
-
-        Returns:
-            DataFrame: The dataframe read from the hdf file.
-        """
-        with hdf_lock:
-            df = pd.read_hdf(file_path, **options)
-
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    @utils.allow_options(pd.read_csv)
-    def _read_csv_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a CSV file as a DataFrame using `pd.read_csv`.
-
-        All `options` are passed directly to `pd.read_csv`.
-
-        Args:
-            file_path: The path to the csv file to be read.
-            options: The pandas `read_csv` options.
-
-        Returns:
-            DataFrame: The dataframe read from the csv file.
-        """
-        options["usecols"] = list(schema.column_names)
-        return pd.read_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_json)
-    def _read_json_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a json file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Args:
-            file_path:
-            options:
-
-        Returns:
-            DataFrame
-        """
-        df = pd.read_json(file_path, **options)
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    def _read_parquet_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a Parquet file as a DataFrame using `pd.read_parquet`.
-
-        All `options` are passed directly to `pd.read_parquet`.
-
-        Args:
-            file_path: The path to the parquet file to be read.
-            options: The pandas `read_parquet` options.
-
-        Returns:
-            DataFrame: The dataframe read from the parquet file.
-        """
-        options["columns"] = options.get("columns", list(schema.column_names))
-
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__read_with_fastparquet(file_path, **options)
-        return WithLocal.__read_with_pyarrow(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(read_table)])
-    def __read_with_pyarrow(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(ParquetFile)])
-    def __read_with_fastparquet(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_hdf), *["protocol"]])
-    def _write_hdf_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe to hdf using `df.to_hdf`.
-
-        All `options` are passed directly to `df.to_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be written sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: The pandas `to_hdf` options.
-
-                - The pandas `to_hdf` options, &;
-                - protocol: The pickle protocol to use for writing the hdf file out; a value <=5.
-        """
-        with utils.pickle_protocol(protocol=options.pop("protocol", None)), hdf_lock:
-            df.to_hdf(file_path, key="df", mode="w", **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_csv)
-    def _write_csv_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a CSV file using `df.to_csv`.
-
-        All `options` are passed directly to `df.to_csv`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a csv file.
-        """
-        df.to_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_json)
-    def _write_json_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a json file using `df.to_json`.
-
-        All `options` are passed directly to `df.to_json`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a json file.
-        """
-        df.to_json(file_path, **options)
-
-    @staticmethod
-    def _write_parquet_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a parquet file using `df.to_parquet`.
-
-        All `options` are passed directly to `df.to_parquet`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a parquet file.
-        """
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__write_with_fastparquet(df, file_path, **options)
-        return WithLocal.__write_with_pyarrow(df, file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write_table)])
-    def __write_with_pyarrow(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write)])
-    def __write_with_fastparquet(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-
-class WithLocalBatch(WithLocal):
-    """Responsible for batch reading local files.
-
-    Examples:
-        >>> READ_FROM_BATCH_LOCAL_PARQUET:
-        >>>   LOCAL:
-        >>>     type: "local_batch"
-        >>>     local:
-        >>>       path_prefix: "[[ TEST_RESOURCES ]]/data/input/batch/parquet/"
-        >>>       file_type: "parquet"
-        >>>
-        >>> READ_FROM_BATCH_LOCAL_TEMPLATED_PARQUET:
-        >>>   LOCAL:
-        >>>     type: "local_batch"
-        >>>     local:
-        >>>       path_prefix: "[[ TEST_RESOURCES ]]/data/input/{templated}/parquet/"
-        >>>       file_type: "parquet"
-        >>>
-        >>> READ_DYNAMIC_FROM_BATCH_LOCAL_PARQUET:
-        >>>   LOCAL:
-        >>>     type: "local_batch"
-        >>>     local:
-        >>>       dynamic_file_path: "[[ TEST_RESOURCES ]]/data/input/batch/parquet/dynamic/**/part_{runner_id}.parquet"
-        >>>       file_type: "parquet"
-    """
-
-    sources_config: LocalBatchDataEnvironment  # type: ignore
-
-    def _read_from_local_batch(self) -> pd.DataFrame:
-        """Reads a set of files for a specified file type, concatenates them and returns a dataframe.
-
-        Returns:
-            A concatenated dataframe composed of all files read through local_batch.
-        """
-        local_batch_config = self.sources_config.local
-
-        file_type = get_file_type_value(local_batch_config.file_type)
-        filtering_file_type = "h5" if file_type == "hdf" else file_type
-
-        # Determine if the path is dynamic or static
-        if local_batch_config.dynamic_file_path:
-            # Dynamic path: use it directly
-            file_path = utils.resolve_template(local_batch_config.dynamic_file_path, self.options)
-            files = sorted(glob.glob(file_path, recursive=True))
-        elif local_batch_config.path_prefix:
-            # Static path: append the file type
-            file_path = utils.resolve_template(local_batch_config.path_prefix, self.options)
-            files = sorted(glob.glob(os.path.join(file_path, f"*.{filtering_file_type}"), recursive=True))
-        else:
-            raise ValueError("Either path_prefix or dynamic_path must be provided in local_batch_config")
-
-        dfs_to_concatenate = []
-        for file in files:
-            file_to_load = os.path.join(file_path, file)
-            dfs_to_concatenate.append(getattr(self, f"_read_{file_type}_file")(file_to_load, self.schema, **self.options))  # type: ignore
-
-        return pd.concat(dfs_to_concatenate).reset_index(drop=True)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class WithLocal -
-
-

Handles local I/O operations.

-
- -Expand source code - -
class WithLocal:
-    """Handles local I/O operations."""
-
-    schema: DataframeSchema
-    sources_config: LocalDataEnvironment
-    options: MutableMapping[str, Any]
-
-    def _read_from_local(self) -> pd.DataFrame:
-        """Read a local file as a `DataFrame`.
-
-        The configuration object is expected to have two keys:
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using
-        "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = get_file_type_value(local_config.file_type)
-
-        return getattr(self, f"_read_{file_type}_file")(file_path, self.schema, **self.options)
-
-    def _write_to_local(self, df: pd.DataFrame):
-        """Write a dataframe locally based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using
-        "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out.
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = get_file_type_value(local_config.file_type)
-        getattr(self, f"_write_{file_type}_file")(df, file_path, **self.options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_hdf)
-    def _read_hdf_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a HDF file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be read sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            file_path: The path to the hdf file to be read.
-            options: The pandas `read_hdf` options.
-
-        Returns:
-            DataFrame: The dataframe read from the hdf file.
-        """
-        with hdf_lock:
-            df = pd.read_hdf(file_path, **options)
-
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    @utils.allow_options(pd.read_csv)
-    def _read_csv_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a CSV file as a DataFrame using `pd.read_csv`.
-
-        All `options` are passed directly to `pd.read_csv`.
-
-        Args:
-            file_path: The path to the csv file to be read.
-            options: The pandas `read_csv` options.
-
-        Returns:
-            DataFrame: The dataframe read from the csv file.
-        """
-        options["usecols"] = list(schema.column_names)
-        return pd.read_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_json)
-    def _read_json_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a json file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Args:
-            file_path:
-            options:
-
-        Returns:
-            DataFrame
-        """
-        df = pd.read_json(file_path, **options)
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    def _read_parquet_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a Parquet file as a DataFrame using `pd.read_parquet`.
-
-        All `options` are passed directly to `pd.read_parquet`.
-
-        Args:
-            file_path: The path to the parquet file to be read.
-            options: The pandas `read_parquet` options.
-
-        Returns:
-            DataFrame: The dataframe read from the parquet file.
-        """
-        options["columns"] = options.get("columns", list(schema.column_names))
-
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__read_with_fastparquet(file_path, **options)
-        return WithLocal.__read_with_pyarrow(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(read_table)])
-    def __read_with_pyarrow(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(ParquetFile)])
-    def __read_with_fastparquet(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_hdf), *["protocol"]])
-    def _write_hdf_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe to hdf using `df.to_hdf`.
-
-        All `options` are passed directly to `df.to_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be written sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: The pandas `to_hdf` options.
-
-                - The pandas `to_hdf` options, &;
-                - protocol: The pickle protocol to use for writing the hdf file out; a value <=5.
-        """
-        with utils.pickle_protocol(protocol=options.pop("protocol", None)), hdf_lock:
-            df.to_hdf(file_path, key="df", mode="w", **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_csv)
-    def _write_csv_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a CSV file using `df.to_csv`.
-
-        All `options` are passed directly to `df.to_csv`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a csv file.
-        """
-        df.to_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_json)
-    def _write_json_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a json file using `df.to_json`.
-
-        All `options` are passed directly to `df.to_json`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a json file.
-        """
-        df.to_json(file_path, **options)
-
-    @staticmethod
-    def _write_parquet_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a parquet file using `df.to_parquet`.
-
-        All `options` are passed directly to `df.to_parquet`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a parquet file.
-        """
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__write_with_fastparquet(df, file_path, **options)
-        return WithLocal.__write_with_pyarrow(df, file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write_table)])
-    def __write_with_pyarrow(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write)])
-    def __write_with_fastparquet(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-

Subclasses

- -

Class variables

-
-
var options : MutableMapping[str, Any]
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configLocalDataEnvironment
-
-
-
-
-
-
-class WithLocalBatch -
-
-

Responsible for batch reading local files.

-

Examples

-
>>> READ_FROM_BATCH_LOCAL_PARQUET:
->>>   LOCAL:
->>>     type: "local_batch"
->>>     local:
->>>       path_prefix: "[[ TEST_RESOURCES ]]/data/input/batch/parquet/"
->>>       file_type: "parquet"
->>>
->>> READ_FROM_BATCH_LOCAL_TEMPLATED_PARQUET:
->>>   LOCAL:
->>>     type: "local_batch"
->>>     local:
->>>       path_prefix: "[[ TEST_RESOURCES ]]/data/input/{templated}/parquet/"
->>>       file_type: "parquet"
->>>
->>> READ_DYNAMIC_FROM_BATCH_LOCAL_PARQUET:
->>>   LOCAL:
->>>     type: "local_batch"
->>>     local:
->>>       dynamic_file_path: "[[ TEST_RESOURCES ]]/data/input/batch/parquet/dynamic/**/part_{runner_id}.parquet"
->>>       file_type: "parquet"
-
-
- -Expand source code - -
class WithLocalBatch(WithLocal):
-    """Responsible for batch reading local files.
-
-    Examples:
-        >>> READ_FROM_BATCH_LOCAL_PARQUET:
-        >>>   LOCAL:
-        >>>     type: "local_batch"
-        >>>     local:
-        >>>       path_prefix: "[[ TEST_RESOURCES ]]/data/input/batch/parquet/"
-        >>>       file_type: "parquet"
-        >>>
-        >>> READ_FROM_BATCH_LOCAL_TEMPLATED_PARQUET:
-        >>>   LOCAL:
-        >>>     type: "local_batch"
-        >>>     local:
-        >>>       path_prefix: "[[ TEST_RESOURCES ]]/data/input/{templated}/parquet/"
-        >>>       file_type: "parquet"
-        >>>
-        >>> READ_DYNAMIC_FROM_BATCH_LOCAL_PARQUET:
-        >>>   LOCAL:
-        >>>     type: "local_batch"
-        >>>     local:
-        >>>       dynamic_file_path: "[[ TEST_RESOURCES ]]/data/input/batch/parquet/dynamic/**/part_{runner_id}.parquet"
-        >>>       file_type: "parquet"
-    """
-
-    sources_config: LocalBatchDataEnvironment  # type: ignore
-
-    def _read_from_local_batch(self) -> pd.DataFrame:
-        """Reads a set of files for a specified file type, concatenates them and returns a dataframe.
-
-        Returns:
-            A concatenated dataframe composed of all files read through local_batch.
-        """
-        local_batch_config = self.sources_config.local
-
-        file_type = get_file_type_value(local_batch_config.file_type)
-        filtering_file_type = "h5" if file_type == "hdf" else file_type
-
-        # Determine if the path is dynamic or static
-        if local_batch_config.dynamic_file_path:
-            # Dynamic path: use it directly
-            file_path = utils.resolve_template(local_batch_config.dynamic_file_path, self.options)
-            files = sorted(glob.glob(file_path, recursive=True))
-        elif local_batch_config.path_prefix:
-            # Static path: append the file type
-            file_path = utils.resolve_template(local_batch_config.path_prefix, self.options)
-            files = sorted(glob.glob(os.path.join(file_path, f"*.{filtering_file_type}"), recursive=True))
-        else:
-            raise ValueError("Either path_prefix or dynamic_path must be provided in local_batch_config")
-
-        dfs_to_concatenate = []
-        for file in files:
-            file_to_load = os.path.join(file_path, file)
-            dfs_to_concatenate.append(getattr(self, f"_read_{file_type}_file")(file_to_load, self.schema, **self.options))  # type: ignore
-
-        return pd.concat(dfs_to_concatenate).reset_index(drop=True)
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var sources_configLocalBatchDataEnvironment
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/mixins/with_postgres.html b/docs/dynamicio/mixins/with_postgres.html deleted file mode 100644 index dd56ce3..0000000 --- a/docs/dynamicio/mixins/with_postgres.html +++ /dev/null @@ -1,489 +0,0 @@ - - - - - - -dynamicio.mixins.with_postgres API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_postgres

-
-
-

This module provides mixins that are providing Postgres I/O support.

-
- -Expand source code - -
# pylint: disable=no-member, protected-access, too-few-public-methods
-
-"""This module provides mixins that are providing Postgres I/O support."""
-
-import csv
-import tempfile
-from contextlib import contextmanager
-from typing import Any, Dict, Generator, MutableMapping, Union
-
-import pandas as pd  # type: ignore
-from magic_logger import logger
-from sqlalchemy import BigInteger, Boolean, Column, create_engine, Date, DateTime, Float, Integer, String  # type: ignore
-from sqlalchemy.ext.declarative import declarative_base  # type: ignore
-from sqlalchemy.orm import Query  # type: ignore
-from sqlalchemy.orm.decl_api import DeclarativeMeta  # type: ignore
-from sqlalchemy.orm.session import Session as SqlAlchemySession  # type: ignore
-from sqlalchemy.orm.session import sessionmaker  # type: ignore
-
-from dynamicio.config.pydantic import DataframeSchema, PostgresDataEnvironment
-from dynamicio.mixins import utils
-
-Session = sessionmaker(autoflush=True)
-
-Base = declarative_base()
-_type_lookup = {
-    "bool": Boolean,
-    "boolean": Boolean,
-    "object": String(64),
-    "int64": Integer,
-    "float64": Float,
-    "int": Integer,
-    "date": Date,
-    "datetime64[ns]": DateTime,
-    "bigint": BigInteger,
-}
-
-
-@contextmanager
-def session_for(connection_string: str) -> Generator[SqlAlchemySession, None, None]:
-    """Connect to a database using `connection_string` and returns an active session to that connection.
-
-    Args:
-        connection_string:
-
-    Yields:
-        Active session
-    """
-    engine = create_engine(connection_string)
-    session = Session(bind=engine)
-
-    try:
-        yield session
-    finally:
-        session.close()  # pylint: disable=no-member
-
-
-class WithPostgres:
-    """Handles I/O operations for Postgres.
-
-    Args:
-       - options:
-           - `truncate_and_append: bool`: If set to `True`, truncates the table and then appends the new rows. Otherwise, it drops the table and recreates it with the new rows.
-    """
-
-    sources_config: PostgresDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-
-    def _read_from_postgres(self) -> pd.DataFrame:
-        """Read data from postgres as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `db_user`
-            - `db_password`
-            - `db_host`
-            - `db_port`
-            - `db_name`
-
-        Returns:
-            DataFrame
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        sql_query = self.options.pop("sql_query", None)
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        query = Query(self._get_table_columns(model))
-        if sql_query:
-            query = sql_query
-
-        logger.info(f"[postgres] Started downloading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            return self._read_database(session, query, **self.options)
-
-    @staticmethod
-    def _generate_model_from_schema(schema: DataframeSchema) -> DeclarativeMeta:
-        json_cls_schema: Dict[str, Any] = {"tablename": schema.name, "columns": []}
-
-        for col in schema.columns.values():
-            sql_type = _type_lookup.get(col.data_type)
-            if sql_type:
-                json_cls_schema["columns"].append({"name": col.name, "type": sql_type})
-
-        class_name = "".join(word.capitalize() or "_" for word in schema.name.split("_")) + "Model"
-
-        class_dict = {"clsname": class_name, "__tablename__": schema.name, "__table_args__": {"extend_existing": True}}
-        class_dict.update({column["name"]: Column(column["type"], primary_key=True) if idx == 0 else Column(column["type"]) for idx, column in enumerate(json_cls_schema["columns"])})
-
-        generated_model = type(class_name, (Base,), class_dict)
-        return generated_model
-
-    @staticmethod
-    def _get_table_columns(model):
-        tables_colums = []
-        if model:
-            for col in list(model.__table__.columns):
-                tables_colums.append(getattr(model, col.name))
-        return tables_colums
-
-    @staticmethod
-    @utils.allow_options(pd.read_sql)
-    def _read_database(session: SqlAlchemySession, query: Union[str, Query], **options: Any) -> pd.DataFrame:
-        """Run `query` against active `session` and returns the result as a `DataFrame`.
-
-        Args:
-            session: Active session
-            query: If a `Query` object is given, it should be unbound. If a `str` is given, the
-                value is used as-is.
-
-        Returns:
-            DataFrame
-        """
-        if isinstance(query, Query):
-            query = query.with_session(session).statement
-        return pd.read_sql(sql=query, con=session.get_bind(), **options)
-
-    def _write_to_postgres(self, df: pd.DataFrame):
-        """Write a dataframe to postgres based on the {file_type} of the config_io configuration.
-
-        Args:
-            df: The dataframe to be written
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        is_truncate_and_append = self.options.get("truncate_and_append", False)
-
-        logger.info(f"[postgres] Started uploading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            self._write_to_database(session, model.__tablename__, df, is_truncate_and_append)  # type: ignore
-
-    @staticmethod
-    def _write_to_database(session: SqlAlchemySession, table_name: str, df: pd.DataFrame, is_truncate_and_append: bool):
-        """Write a dataframe to any database provided a session with a data model and a table name.
-
-        Args:
-            session: Generated from a data model and a table name
-            table_name: The name of the table to read from a DB
-            df: The dataframe to be written out
-            is_truncate_and_append: Supply to truncate the table and append new rows to it; otherwise, delete and replace
-        """
-        if is_truncate_and_append:
-            session.execute(f"TRUNCATE TABLE {table_name};")
-
-            # Below is a speedup hack in place of `df.to_csv` with the multipart option. As of today, even with
-            # `method="multi"`, uploading to Postgres is painfully slow. Hence, we're resorting to dumping the file as
-            # csv and using Postgres's CSV import function.
-            # https://stackoverflow.com/questions/2987433/how-to-import-csv-file-data-into-a-postgresql-table
-            with tempfile.NamedTemporaryFile(mode="r+") as temp_file:
-                df.to_csv(temp_file, index=False, header=False, sep="\t", doublequote=False, escapechar="\\", quoting=csv.QUOTE_NONE)
-                temp_file.flush()
-                temp_file.seek(0)
-
-                cur = session.connection().connection.cursor()
-                cur.copy_from(temp_file, table_name, columns=df.columns, null="")
-        else:
-            df.to_sql(name=table_name, con=session.get_bind(), if_exists="replace", index=False)
-
-        session.commit()
-
-
-
-
-
-
-
-

Functions

-
-
-def session_for(connection_string: str) ‑> Generator[sqlalchemy.orm.session.Session, None, None] -
-
-

Connect to a database using connection_string and returns an active session to that connection.

-

Args

-

connection_string:

-

Yields

-

Active session

-
- -Expand source code - -
@contextmanager
-def session_for(connection_string: str) -> Generator[SqlAlchemySession, None, None]:
-    """Connect to a database using `connection_string` and returns an active session to that connection.
-
-    Args:
-        connection_string:
-
-    Yields:
-        Active session
-    """
-    engine = create_engine(connection_string)
-    session = Session(bind=engine)
-
-    try:
-        yield session
-    finally:
-        session.close()  # pylint: disable=no-member
-
-
-
-
-
-

Classes

-
-
-class WithPostgres -
-
-

Handles I/O operations for Postgres.

-

Args

-
    -
  • options:
      -
    • truncate_and_append: bool: If set to True, truncates the table and then appends the new rows. Otherwise, it drops the table and recreates it with the new rows.
    • -
    -
  • -
-
- -Expand source code - -
class WithPostgres:
-    """Handles I/O operations for Postgres.
-
-    Args:
-       - options:
-           - `truncate_and_append: bool`: If set to `True`, truncates the table and then appends the new rows. Otherwise, it drops the table and recreates it with the new rows.
-    """
-
-    sources_config: PostgresDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-
-    def _read_from_postgres(self) -> pd.DataFrame:
-        """Read data from postgres as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `db_user`
-            - `db_password`
-            - `db_host`
-            - `db_port`
-            - `db_name`
-
-        Returns:
-            DataFrame
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        sql_query = self.options.pop("sql_query", None)
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        query = Query(self._get_table_columns(model))
-        if sql_query:
-            query = sql_query
-
-        logger.info(f"[postgres] Started downloading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            return self._read_database(session, query, **self.options)
-
-    @staticmethod
-    def _generate_model_from_schema(schema: DataframeSchema) -> DeclarativeMeta:
-        json_cls_schema: Dict[str, Any] = {"tablename": schema.name, "columns": []}
-
-        for col in schema.columns.values():
-            sql_type = _type_lookup.get(col.data_type)
-            if sql_type:
-                json_cls_schema["columns"].append({"name": col.name, "type": sql_type})
-
-        class_name = "".join(word.capitalize() or "_" for word in schema.name.split("_")) + "Model"
-
-        class_dict = {"clsname": class_name, "__tablename__": schema.name, "__table_args__": {"extend_existing": True}}
-        class_dict.update({column["name"]: Column(column["type"], primary_key=True) if idx == 0 else Column(column["type"]) for idx, column in enumerate(json_cls_schema["columns"])})
-
-        generated_model = type(class_name, (Base,), class_dict)
-        return generated_model
-
-    @staticmethod
-    def _get_table_columns(model):
-        tables_colums = []
-        if model:
-            for col in list(model.__table__.columns):
-                tables_colums.append(getattr(model, col.name))
-        return tables_colums
-
-    @staticmethod
-    @utils.allow_options(pd.read_sql)
-    def _read_database(session: SqlAlchemySession, query: Union[str, Query], **options: Any) -> pd.DataFrame:
-        """Run `query` against active `session` and returns the result as a `DataFrame`.
-
-        Args:
-            session: Active session
-            query: If a `Query` object is given, it should be unbound. If a `str` is given, the
-                value is used as-is.
-
-        Returns:
-            DataFrame
-        """
-        if isinstance(query, Query):
-            query = query.with_session(session).statement
-        return pd.read_sql(sql=query, con=session.get_bind(), **options)
-
-    def _write_to_postgres(self, df: pd.DataFrame):
-        """Write a dataframe to postgres based on the {file_type} of the config_io configuration.
-
-        Args:
-            df: The dataframe to be written
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        is_truncate_and_append = self.options.get("truncate_and_append", False)
-
-        logger.info(f"[postgres] Started uploading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            self._write_to_database(session, model.__tablename__, df, is_truncate_and_append)  # type: ignore
-
-    @staticmethod
-    def _write_to_database(session: SqlAlchemySession, table_name: str, df: pd.DataFrame, is_truncate_and_append: bool):
-        """Write a dataframe to any database provided a session with a data model and a table name.
-
-        Args:
-            session: Generated from a data model and a table name
-            table_name: The name of the table to read from a DB
-            df: The dataframe to be written out
-            is_truncate_and_append: Supply to truncate the table and append new rows to it; otherwise, delete and replace
-        """
-        if is_truncate_and_append:
-            session.execute(f"TRUNCATE TABLE {table_name};")
-
-            # Below is a speedup hack in place of `df.to_csv` with the multipart option. As of today, even with
-            # `method="multi"`, uploading to Postgres is painfully slow. Hence, we're resorting to dumping the file as
-            # csv and using Postgres's CSV import function.
-            # https://stackoverflow.com/questions/2987433/how-to-import-csv-file-data-into-a-postgresql-table
-            with tempfile.NamedTemporaryFile(mode="r+") as temp_file:
-                df.to_csv(temp_file, index=False, header=False, sep="\t", doublequote=False, escapechar="\\", quoting=csv.QUOTE_NONE)
-                temp_file.flush()
-                temp_file.seek(0)
-
-                cur = session.connection().connection.cursor()
-                cur.copy_from(temp_file, table_name, columns=df.columns, null="")
-        else:
-            df.to_sql(name=table_name, con=session.get_bind(), if_exists="replace", index=False)
-
-        session.commit()
-
-

Subclasses

- -

Class variables

-
-
var options : MutableMapping[str, Any]
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configPostgresDataEnvironment
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/dynamicio/mixins/with_s3.html b/docs/dynamicio/mixins/with_s3.html deleted file mode 100644 index dffe4f6..0000000 --- a/docs/dynamicio/mixins/with_s3.html +++ /dev/null @@ -1,1223 +0,0 @@ - - - - - - -dynamicio.mixins.with_s3 API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_s3

-
-
-

This module provides mixins that are providing S3 I/O support.

-
- -Expand source code - -
# pylint: disable=no-member, protected-access, too-few-public-methods
-
-"""This module provides mixins that are providing S3 I/O support."""
-
-import dataclasses
-import io
-import os
-import tempfile
-import urllib.parse
-import uuid
-from contextlib import contextmanager
-from typing import IO, Generator, List, Optional, Union  # noqa: I101
-
-import boto3  # type: ignore
-import pandas as pd
-import s3transfer.futures  # type: ignore
-import tables  # type: ignore
-from awscli.clidriver import create_clidriver  # type: ignore
-from magic_logger import logger
-from pandas import DataFrame, Series
-
-from dynamicio.config.pydantic import DataframeSchema, S3DataEnvironment, S3PathPrefixEnvironment
-from dynamicio.mixins import utils, with_local
-from dynamicio.mixins.utils import get_file_type_value
-
-
-class InMemStore(pd.io.pytables.HDFStore):
-    """A subclass of pandas HDFStore that does not manage the pytables File object."""
-
-    _in_mem_table = None
-
-    def __init__(self, path: str, table: tables.File, mode: str = "r"):
-        """Create a new HDFStore object."""
-        self._in_mem_table = table
-        super().__init__(path=path, mode=mode)
-
-    def open(self, *_args, **_kwargs):
-        """Open the in-memory table."""
-        pd.io.pytables._tables()
-        self._handle = self._in_mem_table
-
-    def close(self, *_args, **_kwargs):
-        """Close the in-memory table."""
-
-    @property
-    def is_open(self):
-        """Check if the in-memory table is open."""
-        return self._handle is not None
-
-
-class HdfIO:
-    """Class providing stream support for HDF tables."""
-
-    @contextmanager
-    def create_file(self, label: str, mode: str, data: Optional[bytes] = None) -> Generator[tables.File, None, None]:
-        """Create an in-memory pytables table."""
-        extra_kw = {}
-        if data:
-            extra_kw["driver_core_image"] = data
-        file_handle = tables.File(f"{label}_{uuid.uuid4()}.h5", mode, title=label, root_uep="/", filters=None, driver="H5FD_CORE", driver_core_backing_store=0, **extra_kw)
-        try:
-            yield file_handle
-        finally:
-            file_handle.close()
-
-    def load(self, fobj: IO[bytes], label: str = "unknown_file.h5") -> Union[DataFrame, Series]:
-        """Load the dataframe from an file-like object."""
-        with self.create_file(label, mode="r", data=fobj.read()) as file_handle:
-            return pd.read_hdf(InMemStore(label, file_handle))
-
-    def save(self, df: DataFrame, fobj: IO[bytes], label: str = "unknown_file.h5", options: Optional[dict] = None):
-        """Load the dataframe to a file-like object."""
-        if not options:
-            options = {}
-        with self.create_file(label, mode="w", data=fobj.read()) as file_handle:
-            store = InMemStore(path=label, table=file_handle, mode="w")
-            store.put(key="df", value=df, **options)
-            fobj.write(file_handle.get_file_image())
-
-
-def awscli_runner(*cmd: str):
-    """Runs the awscli command provided.
-
-    Args:
-        *cmd: A list of args used in the command.
-
-    Raises:
-        A runtime error exception is raised if download fails.
-
-    Example:
-
-        >>> awscli_runner("s3", "sync", "s3://mock-bucket/mock-key", ".")
-    """
-    # Run
-    exit_code = create_clidriver().main(cmd)
-
-    if exit_code > 0:
-        raise RuntimeError(f"AWS CLI exited with code {exit_code}")
-
-
-@dataclasses.dataclass
-class S3TransferHandle:
-    """A dataclass used to track an ongoing data download from the s3."""
-
-    s3_object: object  # boto3.resource('s3').ObjectSummary
-    fobj: IO[bytes]  # file-like object the data is being downloaded to
-    done_future: s3transfer.futures.BaseTransferFuture
-
-
-class WithS3PathPrefix(with_local.WithLocal):
-    """Handles I/O operations for AWS S3; implements read operations only.
-
-    This mixin assumes that the directories it reads from will only contain a single file-type.
-    """
-
-    sources_config: S3PathPrefixEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_resource = boto3.resource("s3")
-    boto3_client = boto3.client("s3")
-
-    def _write_to_s3_path_prefix(self, df: pd.DataFrame):
-        """Write a DataFrame to an S3 path prefix.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `path_prefix`
-            - `file_type`
-
-        Args:
-            df (pd.DataFrame): the DataFrame to be written to S3
-
-        Raises:
-            ValueError: In case `path_prefix` is missing from config
-            ValueError: In case the `partition_cols` arg is missing while trying to write a parquet file
-        """
-        s3_config = self.sources_config.s3
-        file_type = get_file_type_value(s3_config.file_type)
-        if file_type != "parquet":
-            raise ValueError(f"File type not supported: {file_type}, only parquet files can be written to an S3 key")
-        if "partition_cols" not in self.options:
-            raise ValueError("`partition_cols` is required as an option to write partitioned parquet files to S3")
-
-        bucket = s3_config.bucket
-        path_prefix = s3_config.path_prefix
-        full_path_prefix = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            self._write_parquet_file(df, temp_dir, **self.options)
-            awscli_runner(
-                "s3",
-                "sync",
-                temp_dir,
-                full_path_prefix,
-                "--acl",
-                "bucket-owner-full-control",
-                "--only-show-errors",
-                "--exact-timestamps",
-            )
-
-    def _read_from_s3_path_prefix(self) -> pd.DataFrame:
-        """Read files from an S3 bucket based on a path_prefix/dynamic_file_path and return a concatenated DataFrame.
-
-        This function supports two types of file paths from the S3 configuration:
-        1. `path_prefix`: Used to specify a static path prefix in the S3 bucket for downloading files.
-        2. `dynamic_file_path`: Used for more dynamic path specifications, allowing pattern matching for selective file
-        downloads.
-
-        The `dynamic_file_path` supports pattern matching and variables (e.g., `part_{runner_id}.parquet`). This
-        approach  enables the downloading of files that specifically match the given pattern, optimizing I/O for
-        scenarios involving large datasets or multiple runners.
-
-        The method dynamically invokes appropriate file reading functions based on the `file_type` specified in the
-        configuration, supporting formats such as 'parquet', 'csv', 'hdf', and 'json'.
-
-        The function also includes an option to minimize disk space usage (`no_disk_space`). This is particularly
-        useful when needing to read a subset of columns from large files, thereby reducing the overall disk footprint.
-
-        Parameters:
-        - None
-
-        Returns:
-        - DataFrame: A pandas DataFrame concatenated from the read files.
-
-        Raises:
-        - ValueError: If the `file_type` specified in the configuration is not supported.
-
-        Configuration Keys:
-        - `bucket` (str): Name of the S3 bucket.
-        - `path_prefix` (str, optional): Static path prefix in the S3 bucket for file downloads.
-        - `dynamic_file_path` (str, optional): Dynamic file path with pattern matching for selective downloading of files.
-        - `file_type` (str): Type of the file to read ('parquet', 'csv', 'hdf', 'json').
-
-        Notes:
-        - Only one of `path_prefix` and `dynamic_file_path` can be provided.
-        - The function intelligently handles the download of files by synchronizing only those that match the specified
-        pattern in `dynamic_file_path`.
-        - e.g. a `runner_id` or any other variable used in `dynamic_file_path` for pattern matching should be specified
-        in the `options` of the configuration.
-        """
-        s3_config = self.sources_config.s3
-        file_type = get_file_type_value(s3_config.file_type)
-        if file_type not in {"parquet", "csv", "hdf", "json"}:
-            raise ValueError(f"File type not supported: {file_type}")
-
-        bucket = s3_config.bucket
-        dynamic_file_path = s3_config.dynamic_file_path
-        path_prefix = s3_config.path_prefix
-
-        if dynamic_file_path:
-            full_path = utils.resolve_template(f"s3://{bucket}/{dynamic_file_path}", self.options)
-        else:
-            full_path = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        # The `no_disk_space` option should be used only when reading a subset of columns from S3
-        if self.options.pop("no_disk_space", False) and path_prefix:
-            if file_type == "parquet":
-                return self._read_parquet_file(full_path, self.schema, **self.options)
-            if file_type == "hdf":
-                dfs: List[DataFrame] = []
-                for fobj in self._iter_s3_files(full_path, file_ext=".h5", max_memory_use=1024**3):  # 1 gib
-                    dfs.append(HdfIO().load(fobj))
-                df = pd.concat(dfs, ignore_index=True)
-                columns = [column for column in df.columns.to_list() if column in self.schema.columns.keys()]
-                return df[columns]
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            # aws-cli is shown to be up to 6 times faster when downloading the complete dataset from S3 than using the boto3
-            # client or pandas directly. This is because aws-cli uses the parallel downloader, which is much faster than the
-            # boto3 client.
-            if dynamic_file_path:
-                prefix, suffix = full_path.rsplit("/**/", 1)
-                awscli_runner(
-                    "s3",
-                    "sync",
-                    prefix,
-                    temp_dir,
-                    "--exclude",
-                    "*",
-                    "--include",
-                    f"**/{suffix}",
-                    "--acl",
-                    "bucket-owner-full-control",
-                    "--only-show-errors",
-                    "--exact-timestamps",
-                )
-            else:
-                awscli_runner(
-                    "s3",
-                    "sync",
-                    full_path,
-                    temp_dir,
-                    "--acl",
-                    "bucket-owner-full-control",
-                    "--only-show-errors",
-                    "--exact-timestamps",
-                )
-
-            dfs: List[DataFrame] = []
-            for file in os.listdir(temp_dir):
-                df = getattr(self, f"_read_{file_type}_file")(os.path.join(temp_dir, file), self.schema, **self.options)  # type: ignore
-                if len(df) > 0:
-                    dfs.append(df)
-
-            return pd.concat(dfs, ignore_index=True)
-
-    def _iter_s3_files(self, s3_prefix: str, file_ext: Optional[str] = None, max_memory_use: int = -1) -> Generator[IO[bytes], None, None]:  # pylint: disable=too-many-locals
-        """Download sways of S3 objects.
-
-        Args:
-            s3_prefix: s3 url to fetch objects with
-            file_ext: extension of s3 objects to allow through
-            max_memory_use: The approximate number of bytes to allocate on each yield of Generator
-        """
-        parsed_url = urllib.parse.urlparse(s3_prefix)
-        assert parsed_url.scheme == "s3", f"{s3_prefix!r} should be an s3 url"
-        bucket_name = parsed_url.netloc
-        file_prefix = f"{parsed_url.path.strip('/')}/"
-        s3_objects_to_fetch = []
-        # Collect objects to be loaded
-        for s3_object in self.boto3_resource.Bucket(bucket_name).objects.filter(Prefix=file_prefix):
-            good_object = (not file_ext) or (s3_object.key.endswith(file_ext))
-            if good_object:
-                s3_objects_to_fetch.append(s3_object)
-
-        if max_memory_use < 0:
-            # Unlimited memory use - fetch ALL
-            max_memory_use = sum(s3_obj.size for s3_obj in s3_objects_to_fetch) * 2
-        transfer_config = boto3.s3.transfer.TransferConfig(max_concurrency=20)
-        while s3_objects_to_fetch:
-            mem_use_left = max_memory_use
-            handles = []
-            with boto3.s3.transfer.create_transfer_manager(self.boto3_client, transfer_config) as transfer_manager:
-                while mem_use_left > 0 and s3_objects_to_fetch:
-                    s3_object = s3_objects_to_fetch.pop()
-                    fobj = io.BytesIO()
-                    future = transfer_manager.download(bucket_name, s3_object.key, fobj)
-                    handles.append(S3TransferHandle(s3_object, fobj, future))
-                    mem_use_left -= s3_object.size
-                # Leaving the `transfer_manager` context implicitly waits for all downloads to complete
-            # Rewind and yield all fobjs
-            for handle in handles:
-                handle.fobj.seek(0)
-                yield handle.fobj
-
-
-class WithS3File(with_local.WithLocal):
-    """Handles I/O operations for AWS S3.
-
-    All files are persisted to disk first using boto3 as this has proven to be faster than reading them into memory.
-    Note that reading things into memory is available for csv, json and parquet types only. Unfortunately, until support
-    for generic buffer is added to read_hdf, we need to download and persists the file to disk first anyway.
-
-    Options:
-        no_disk_space: If `True`, then s3fs + fsspec will be used to read data directly into memory.
-    """
-
-    sources_config: S3DataEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_client = boto3.client("s3")
-
-    @contextmanager
-    def _s3_named_file_reader(self, s3_bucket: str, s3_key: str) -> Generator:
-        """Contextmanager to abstract reading different file types in S3.
-
-        This implementation saves the downloaded data to a temporary file.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        with tempfile.NamedTemporaryFile("wb") as target_file:
-            # Download the file from S3
-            self.boto3_client.download_fileobj(s3_bucket, s3_key, target_file)
-            # Yield local file path to body of `with` statement
-            target_file.flush()
-            yield target_file
-
-    @contextmanager
-    def _s3_reader(self, s3_bucket: str, s3_key: str) -> Generator[io.BytesIO, None, None]:
-        """Contextmanager to abstract reading different file types in S3.
-
-         This implementation only retains data in-memory, avoiding creating any temp files.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        fobj = io.BytesIO()
-        # Download the file from S3
-        self.boto3_client.download_fileobj(s3_bucket, s3_key, fobj)
-        # Yield the buffer
-        fobj.seek(0)
-        yield fobj
-
-    @contextmanager
-    def _s3_writer(self, s3_bucket: str, s3_key: str) -> Generator[IO[bytes], None, None]:
-        """Contextmanager to abstract loading different file types to S3.
-
-        Args:
-            s3_bucket: The S3 bucket to upload the file to.
-            s3_key: The file-path where the target file should be uploaded to.
-
-        Returns:
-            The local file path where to actually write the file, to be read and uploaded by boto3.client.
-        """
-        fobj = io.BytesIO()
-        yield fobj
-        fobj.seek(0)
-        self.boto3_client.upload_fileobj(fobj, s3_bucket, s3_key, ExtraArgs={"ACL": "bucket-owner-full-control"})
-
-    def _read_from_s3_file(self) -> pd.DataFrame:
-        """Read a file from an S3 bucket as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        s3_config = self.sources_config.s3
-        file_type = get_file_type_value(s3_config.file_type)
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        bucket = s3_config.bucket
-
-        logger.info(f"[s3] Started downloading: s3://{s3_config.bucket}/{file_path}")
-        if self.options.pop("no_disk_space", None):
-            no_disk_space_rv = None
-            if file_type in ["csv", "json", "parquet"]:
-                no_disk_space_rv = getattr(self, f"_read_{file_type}_file")(f"s3://{s3_config.bucket}/{file_path}", self.schema, **self.options)  # type: ignore
-            elif file_type == "hdf":
-                with self._s3_reader(s3_bucket=bucket, s3_key=file_path) as fobj:  # type: ignore
-                    no_disk_space_rv = HdfIO().load(fobj)  # type: ignore
-            else:
-                raise NotImplementedError(f"Unsupported file type {file_type!r}.")
-            if no_disk_space_rv is not None:
-                return no_disk_space_rv
-        with self._s3_named_file_reader(s3_bucket=bucket, s3_key=file_path) as target_file:  # type: ignore
-            return getattr(self, f"_read_{file_type}_file")(target_file.name, self.schema, **self.options)  # type: ignore
-
-    def _write_to_s3_file(self, df: pd.DataFrame):
-        """Write a dataframe to s3 based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out
-        """
-        s3_config = self.sources_config.s3
-        bucket = s3_config.bucket
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        file_type = get_file_type_value(s3_config.file_type)
-
-        logger.info(f"[s3] Started uploading: s3://{bucket}/{file_path}")
-        if file_type in ["csv", "json", "parquet"]:
-            getattr(self, f"_write_{file_type}_file")(df, f"s3://{bucket}/{file_path}", **self.options)  # type: ignore
-        elif file_type == "hdf":
-            hdf_options = dict(self.options)
-            pickle_protocol = hdf_options.pop("pickle_protocol", None)
-            with self._s3_writer(s3_bucket=s3_config.bucket, s3_key=file_path) as target_file, utils.pickle_protocol(protocol=pickle_protocol):
-                HdfIO().save(df, target_file, hdf_options)  # type: ignore
-        else:
-            raise ValueError(f"File type: {file_type} not supported!")
-        logger.info(f"[s3] Finished uploading: s3://{bucket}/{file_path}")
-
-
-
-
-
-
-
-

Functions

-
-
-def awscli_runner(*cmd: str) -
-
-

Runs the awscli command provided.

-

Args

-
-
*cmd
-
A list of args used in the command.
-
-

Raises

-

A runtime error exception is raised if download fails.

-

Example

-
>>> awscli_runner("s3", "sync", "s3://mock-bucket/mock-key", ".")
-
-
- -Expand source code - -
def awscli_runner(*cmd: str):
-    """Runs the awscli command provided.
-
-    Args:
-        *cmd: A list of args used in the command.
-
-    Raises:
-        A runtime error exception is raised if download fails.
-
-    Example:
-
-        >>> awscli_runner("s3", "sync", "s3://mock-bucket/mock-key", ".")
-    """
-    # Run
-    exit_code = create_clidriver().main(cmd)
-
-    if exit_code > 0:
-        raise RuntimeError(f"AWS CLI exited with code {exit_code}")
-
-
-
-
-
-

Classes

-
-
-class HdfIO -
-
-

Class providing stream support for HDF tables.

-
- -Expand source code - -
class HdfIO:
-    """Class providing stream support for HDF tables."""
-
-    @contextmanager
-    def create_file(self, label: str, mode: str, data: Optional[bytes] = None) -> Generator[tables.File, None, None]:
-        """Create an in-memory pytables table."""
-        extra_kw = {}
-        if data:
-            extra_kw["driver_core_image"] = data
-        file_handle = tables.File(f"{label}_{uuid.uuid4()}.h5", mode, title=label, root_uep="/", filters=None, driver="H5FD_CORE", driver_core_backing_store=0, **extra_kw)
-        try:
-            yield file_handle
-        finally:
-            file_handle.close()
-
-    def load(self, fobj: IO[bytes], label: str = "unknown_file.h5") -> Union[DataFrame, Series]:
-        """Load the dataframe from an file-like object."""
-        with self.create_file(label, mode="r", data=fobj.read()) as file_handle:
-            return pd.read_hdf(InMemStore(label, file_handle))
-
-    def save(self, df: DataFrame, fobj: IO[bytes], label: str = "unknown_file.h5", options: Optional[dict] = None):
-        """Load the dataframe to a file-like object."""
-        if not options:
-            options = {}
-        with self.create_file(label, mode="w", data=fobj.read()) as file_handle:
-            store = InMemStore(path=label, table=file_handle, mode="w")
-            store.put(key="df", value=df, **options)
-            fobj.write(file_handle.get_file_image())
-
-

Methods

-
-
-def create_file(self, label: str, mode: str, data: Optional[bytes] = None) ‑> Generator[tables.file.File, None, None] -
-
-

Create an in-memory pytables table.

-
- -Expand source code - -
@contextmanager
-def create_file(self, label: str, mode: str, data: Optional[bytes] = None) -> Generator[tables.File, None, None]:
-    """Create an in-memory pytables table."""
-    extra_kw = {}
-    if data:
-        extra_kw["driver_core_image"] = data
-    file_handle = tables.File(f"{label}_{uuid.uuid4()}.h5", mode, title=label, root_uep="/", filters=None, driver="H5FD_CORE", driver_core_backing_store=0, **extra_kw)
-    try:
-        yield file_handle
-    finally:
-        file_handle.close()
-
-
-
-def load(self, fobj: IO[bytes], label: str = 'unknown_file.h5') ‑> Union[pandas.core.frame.DataFrame, pandas.core.series.Series] -
-
-

Load the dataframe from an file-like object.

-
- -Expand source code - -
def load(self, fobj: IO[bytes], label: str = "unknown_file.h5") -> Union[DataFrame, Series]:
-    """Load the dataframe from an file-like object."""
-    with self.create_file(label, mode="r", data=fobj.read()) as file_handle:
-        return pd.read_hdf(InMemStore(label, file_handle))
-
-
-
-def save(self, df: pandas.core.frame.DataFrame, fobj: IO[bytes], label: str = 'unknown_file.h5', options: Optional[dict] = None) -
-
-

Load the dataframe to a file-like object.

-
- -Expand source code - -
def save(self, df: DataFrame, fobj: IO[bytes], label: str = "unknown_file.h5", options: Optional[dict] = None):
-    """Load the dataframe to a file-like object."""
-    if not options:
-        options = {}
-    with self.create_file(label, mode="w", data=fobj.read()) as file_handle:
-        store = InMemStore(path=label, table=file_handle, mode="w")
-        store.put(key="df", value=df, **options)
-        fobj.write(file_handle.get_file_image())
-
-
-
-
-
-class InMemStore -(path: str, table: tables.file.File, mode: str = 'r') -
-
-

A subclass of pandas HDFStore that does not manage the pytables File object.

-

Create a new HDFStore object.

-
- -Expand source code - -
class InMemStore(pd.io.pytables.HDFStore):
-    """A subclass of pandas HDFStore that does not manage the pytables File object."""
-
-    _in_mem_table = None
-
-    def __init__(self, path: str, table: tables.File, mode: str = "r"):
-        """Create a new HDFStore object."""
-        self._in_mem_table = table
-        super().__init__(path=path, mode=mode)
-
-    def open(self, *_args, **_kwargs):
-        """Open the in-memory table."""
-        pd.io.pytables._tables()
-        self._handle = self._in_mem_table
-
-    def close(self, *_args, **_kwargs):
-        """Close the in-memory table."""
-
-    @property
-    def is_open(self):
-        """Check if the in-memory table is open."""
-        return self._handle is not None
-
-

Ancestors

-
    -
  • pandas.io.pytables.HDFStore
  • -
-

Instance variables

-
-
var is_open
-
-

Check if the in-memory table is open.

-
- -Expand source code - -
@property
-def is_open(self):
-    """Check if the in-memory table is open."""
-    return self._handle is not None
-
-
-
-

Methods

-
-
-def close(self, *_args, **_kwargs) -
-
-

Close the in-memory table.

-
- -Expand source code - -
def close(self, *_args, **_kwargs):
-    """Close the in-memory table."""
-
-
-
-def open(self, *_args, **_kwargs) -
-
-

Open the in-memory table.

-
- -Expand source code - -
def open(self, *_args, **_kwargs):
-    """Open the in-memory table."""
-    pd.io.pytables._tables()
-    self._handle = self._in_mem_table
-
-
-
-
-
-class S3TransferHandle -(s3_object: object, fobj: IO[bytes], done_future: s3transfer.futures.BaseTransferFuture) -
-
-

A dataclass used to track an ongoing data download from the s3.

-
- -Expand source code - -
@dataclasses.dataclass
-class S3TransferHandle:
-    """A dataclass used to track an ongoing data download from the s3."""
-
-    s3_object: object  # boto3.resource('s3').ObjectSummary
-    fobj: IO[bytes]  # file-like object the data is being downloaded to
-    done_future: s3transfer.futures.BaseTransferFuture
-
-

Class variables

-
-
var done_future : s3transfer.futures.BaseTransferFuture
-
-
-
-
var fobj : IO[bytes]
-
-
-
-
var s3_object : object
-
-
-
-
-
-
-class WithS3File -
-
-

Handles I/O operations for AWS S3.

-

All files are persisted to disk first using boto3 as this has proven to be faster than reading them into memory. -Note that reading things into memory is available for csv, json and parquet types only. Unfortunately, until support -for generic buffer is added to read_hdf, we need to download and persists the file to disk first anyway.

-

Options

-

no_disk_space: If True, then s3fs + fsspec will be used to read data directly into memory.

-
- -Expand source code - -
class WithS3File(with_local.WithLocal):
-    """Handles I/O operations for AWS S3.
-
-    All files are persisted to disk first using boto3 as this has proven to be faster than reading them into memory.
-    Note that reading things into memory is available for csv, json and parquet types only. Unfortunately, until support
-    for generic buffer is added to read_hdf, we need to download and persists the file to disk first anyway.
-
-    Options:
-        no_disk_space: If `True`, then s3fs + fsspec will be used to read data directly into memory.
-    """
-
-    sources_config: S3DataEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_client = boto3.client("s3")
-
-    @contextmanager
-    def _s3_named_file_reader(self, s3_bucket: str, s3_key: str) -> Generator:
-        """Contextmanager to abstract reading different file types in S3.
-
-        This implementation saves the downloaded data to a temporary file.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        with tempfile.NamedTemporaryFile("wb") as target_file:
-            # Download the file from S3
-            self.boto3_client.download_fileobj(s3_bucket, s3_key, target_file)
-            # Yield local file path to body of `with` statement
-            target_file.flush()
-            yield target_file
-
-    @contextmanager
-    def _s3_reader(self, s3_bucket: str, s3_key: str) -> Generator[io.BytesIO, None, None]:
-        """Contextmanager to abstract reading different file types in S3.
-
-         This implementation only retains data in-memory, avoiding creating any temp files.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        fobj = io.BytesIO()
-        # Download the file from S3
-        self.boto3_client.download_fileobj(s3_bucket, s3_key, fobj)
-        # Yield the buffer
-        fobj.seek(0)
-        yield fobj
-
-    @contextmanager
-    def _s3_writer(self, s3_bucket: str, s3_key: str) -> Generator[IO[bytes], None, None]:
-        """Contextmanager to abstract loading different file types to S3.
-
-        Args:
-            s3_bucket: The S3 bucket to upload the file to.
-            s3_key: The file-path where the target file should be uploaded to.
-
-        Returns:
-            The local file path where to actually write the file, to be read and uploaded by boto3.client.
-        """
-        fobj = io.BytesIO()
-        yield fobj
-        fobj.seek(0)
-        self.boto3_client.upload_fileobj(fobj, s3_bucket, s3_key, ExtraArgs={"ACL": "bucket-owner-full-control"})
-
-    def _read_from_s3_file(self) -> pd.DataFrame:
-        """Read a file from an S3 bucket as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        s3_config = self.sources_config.s3
-        file_type = get_file_type_value(s3_config.file_type)
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        bucket = s3_config.bucket
-
-        logger.info(f"[s3] Started downloading: s3://{s3_config.bucket}/{file_path}")
-        if self.options.pop("no_disk_space", None):
-            no_disk_space_rv = None
-            if file_type in ["csv", "json", "parquet"]:
-                no_disk_space_rv = getattr(self, f"_read_{file_type}_file")(f"s3://{s3_config.bucket}/{file_path}", self.schema, **self.options)  # type: ignore
-            elif file_type == "hdf":
-                with self._s3_reader(s3_bucket=bucket, s3_key=file_path) as fobj:  # type: ignore
-                    no_disk_space_rv = HdfIO().load(fobj)  # type: ignore
-            else:
-                raise NotImplementedError(f"Unsupported file type {file_type!r}.")
-            if no_disk_space_rv is not None:
-                return no_disk_space_rv
-        with self._s3_named_file_reader(s3_bucket=bucket, s3_key=file_path) as target_file:  # type: ignore
-            return getattr(self, f"_read_{file_type}_file")(target_file.name, self.schema, **self.options)  # type: ignore
-
-    def _write_to_s3_file(self, df: pd.DataFrame):
-        """Write a dataframe to s3 based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out
-        """
-        s3_config = self.sources_config.s3
-        bucket = s3_config.bucket
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        file_type = get_file_type_value(s3_config.file_type)
-
-        logger.info(f"[s3] Started uploading: s3://{bucket}/{file_path}")
-        if file_type in ["csv", "json", "parquet"]:
-            getattr(self, f"_write_{file_type}_file")(df, f"s3://{bucket}/{file_path}", **self.options)  # type: ignore
-        elif file_type == "hdf":
-            hdf_options = dict(self.options)
-            pickle_protocol = hdf_options.pop("pickle_protocol", None)
-            with self._s3_writer(s3_bucket=s3_config.bucket, s3_key=file_path) as target_file, utils.pickle_protocol(protocol=pickle_protocol):
-                HdfIO().save(df, target_file, hdf_options)  # type: ignore
-        else:
-            raise ValueError(f"File type: {file_type} not supported!")
-        logger.info(f"[s3] Finished uploading: s3://{bucket}/{file_path}")
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var boto3_client
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configS3DataEnvironment
-
-
-
-
-
-
-class WithS3PathPrefix -
-
-

Handles I/O operations for AWS S3; implements read operations only.

-

This mixin assumes that the directories it reads from will only contain a single file-type.

-
- -Expand source code - -
class WithS3PathPrefix(with_local.WithLocal):
-    """Handles I/O operations for AWS S3; implements read operations only.
-
-    This mixin assumes that the directories it reads from will only contain a single file-type.
-    """
-
-    sources_config: S3PathPrefixEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_resource = boto3.resource("s3")
-    boto3_client = boto3.client("s3")
-
-    def _write_to_s3_path_prefix(self, df: pd.DataFrame):
-        """Write a DataFrame to an S3 path prefix.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `path_prefix`
-            - `file_type`
-
-        Args:
-            df (pd.DataFrame): the DataFrame to be written to S3
-
-        Raises:
-            ValueError: In case `path_prefix` is missing from config
-            ValueError: In case the `partition_cols` arg is missing while trying to write a parquet file
-        """
-        s3_config = self.sources_config.s3
-        file_type = get_file_type_value(s3_config.file_type)
-        if file_type != "parquet":
-            raise ValueError(f"File type not supported: {file_type}, only parquet files can be written to an S3 key")
-        if "partition_cols" not in self.options:
-            raise ValueError("`partition_cols` is required as an option to write partitioned parquet files to S3")
-
-        bucket = s3_config.bucket
-        path_prefix = s3_config.path_prefix
-        full_path_prefix = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            self._write_parquet_file(df, temp_dir, **self.options)
-            awscli_runner(
-                "s3",
-                "sync",
-                temp_dir,
-                full_path_prefix,
-                "--acl",
-                "bucket-owner-full-control",
-                "--only-show-errors",
-                "--exact-timestamps",
-            )
-
-    def _read_from_s3_path_prefix(self) -> pd.DataFrame:
-        """Read files from an S3 bucket based on a path_prefix/dynamic_file_path and return a concatenated DataFrame.
-
-        This function supports two types of file paths from the S3 configuration:
-        1. `path_prefix`: Used to specify a static path prefix in the S3 bucket for downloading files.
-        2. `dynamic_file_path`: Used for more dynamic path specifications, allowing pattern matching for selective file
-        downloads.
-
-        The `dynamic_file_path` supports pattern matching and variables (e.g., `part_{runner_id}.parquet`). This
-        approach  enables the downloading of files that specifically match the given pattern, optimizing I/O for
-        scenarios involving large datasets or multiple runners.
-
-        The method dynamically invokes appropriate file reading functions based on the `file_type` specified in the
-        configuration, supporting formats such as 'parquet', 'csv', 'hdf', and 'json'.
-
-        The function also includes an option to minimize disk space usage (`no_disk_space`). This is particularly
-        useful when needing to read a subset of columns from large files, thereby reducing the overall disk footprint.
-
-        Parameters:
-        - None
-
-        Returns:
-        - DataFrame: A pandas DataFrame concatenated from the read files.
-
-        Raises:
-        - ValueError: If the `file_type` specified in the configuration is not supported.
-
-        Configuration Keys:
-        - `bucket` (str): Name of the S3 bucket.
-        - `path_prefix` (str, optional): Static path prefix in the S3 bucket for file downloads.
-        - `dynamic_file_path` (str, optional): Dynamic file path with pattern matching for selective downloading of files.
-        - `file_type` (str): Type of the file to read ('parquet', 'csv', 'hdf', 'json').
-
-        Notes:
-        - Only one of `path_prefix` and `dynamic_file_path` can be provided.
-        - The function intelligently handles the download of files by synchronizing only those that match the specified
-        pattern in `dynamic_file_path`.
-        - e.g. a `runner_id` or any other variable used in `dynamic_file_path` for pattern matching should be specified
-        in the `options` of the configuration.
-        """
-        s3_config = self.sources_config.s3
-        file_type = get_file_type_value(s3_config.file_type)
-        if file_type not in {"parquet", "csv", "hdf", "json"}:
-            raise ValueError(f"File type not supported: {file_type}")
-
-        bucket = s3_config.bucket
-        dynamic_file_path = s3_config.dynamic_file_path
-        path_prefix = s3_config.path_prefix
-
-        if dynamic_file_path:
-            full_path = utils.resolve_template(f"s3://{bucket}/{dynamic_file_path}", self.options)
-        else:
-            full_path = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        # The `no_disk_space` option should be used only when reading a subset of columns from S3
-        if self.options.pop("no_disk_space", False) and path_prefix:
-            if file_type == "parquet":
-                return self._read_parquet_file(full_path, self.schema, **self.options)
-            if file_type == "hdf":
-                dfs: List[DataFrame] = []
-                for fobj in self._iter_s3_files(full_path, file_ext=".h5", max_memory_use=1024**3):  # 1 gib
-                    dfs.append(HdfIO().load(fobj))
-                df = pd.concat(dfs, ignore_index=True)
-                columns = [column for column in df.columns.to_list() if column in self.schema.columns.keys()]
-                return df[columns]
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            # aws-cli is shown to be up to 6 times faster when downloading the complete dataset from S3 than using the boto3
-            # client or pandas directly. This is because aws-cli uses the parallel downloader, which is much faster than the
-            # boto3 client.
-            if dynamic_file_path:
-                prefix, suffix = full_path.rsplit("/**/", 1)
-                awscli_runner(
-                    "s3",
-                    "sync",
-                    prefix,
-                    temp_dir,
-                    "--exclude",
-                    "*",
-                    "--include",
-                    f"**/{suffix}",
-                    "--acl",
-                    "bucket-owner-full-control",
-                    "--only-show-errors",
-                    "--exact-timestamps",
-                )
-            else:
-                awscli_runner(
-                    "s3",
-                    "sync",
-                    full_path,
-                    temp_dir,
-                    "--acl",
-                    "bucket-owner-full-control",
-                    "--only-show-errors",
-                    "--exact-timestamps",
-                )
-
-            dfs: List[DataFrame] = []
-            for file in os.listdir(temp_dir):
-                df = getattr(self, f"_read_{file_type}_file")(os.path.join(temp_dir, file), self.schema, **self.options)  # type: ignore
-                if len(df) > 0:
-                    dfs.append(df)
-
-            return pd.concat(dfs, ignore_index=True)
-
-    def _iter_s3_files(self, s3_prefix: str, file_ext: Optional[str] = None, max_memory_use: int = -1) -> Generator[IO[bytes], None, None]:  # pylint: disable=too-many-locals
-        """Download sways of S3 objects.
-
-        Args:
-            s3_prefix: s3 url to fetch objects with
-            file_ext: extension of s3 objects to allow through
-            max_memory_use: The approximate number of bytes to allocate on each yield of Generator
-        """
-        parsed_url = urllib.parse.urlparse(s3_prefix)
-        assert parsed_url.scheme == "s3", f"{s3_prefix!r} should be an s3 url"
-        bucket_name = parsed_url.netloc
-        file_prefix = f"{parsed_url.path.strip('/')}/"
-        s3_objects_to_fetch = []
-        # Collect objects to be loaded
-        for s3_object in self.boto3_resource.Bucket(bucket_name).objects.filter(Prefix=file_prefix):
-            good_object = (not file_ext) or (s3_object.key.endswith(file_ext))
-            if good_object:
-                s3_objects_to_fetch.append(s3_object)
-
-        if max_memory_use < 0:
-            # Unlimited memory use - fetch ALL
-            max_memory_use = sum(s3_obj.size for s3_obj in s3_objects_to_fetch) * 2
-        transfer_config = boto3.s3.transfer.TransferConfig(max_concurrency=20)
-        while s3_objects_to_fetch:
-            mem_use_left = max_memory_use
-            handles = []
-            with boto3.s3.transfer.create_transfer_manager(self.boto3_client, transfer_config) as transfer_manager:
-                while mem_use_left > 0 and s3_objects_to_fetch:
-                    s3_object = s3_objects_to_fetch.pop()
-                    fobj = io.BytesIO()
-                    future = transfer_manager.download(bucket_name, s3_object.key, fobj)
-                    handles.append(S3TransferHandle(s3_object, fobj, future))
-                    mem_use_left -= s3_object.size
-                # Leaving the `transfer_manager` context implicitly waits for all downloads to complete
-            # Rewind and yield all fobjs
-            for handle in handles:
-                handle.fobj.seek(0)
-                yield handle.fobj
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var boto3_client
-
-
-
-
var boto3_resource
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configS3PathPrefixEnvironment
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/errors.html b/docs/errors.html deleted file mode 100644 index b97a281..0000000 --- a/docs/errors.html +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - -dynamicio.errors API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.errors

-
-
-

Hosts exception implementations for different errors.

-
- -Expand source code - -
"""Hosts exception implementations for different errors."""
-# pylint: disable=missing-module-docstring, missing-class-docstring, missing-function-docstring, super-init-not-called
-__all__ = [
-    "DynamicIOError",
-    "DataSourceError",
-    "ColumnsDataTypeError",
-    "NonUniqueIdColumnError",
-    "NullValueInColumnError",
-    "NotExpectedCategoricalValue",
-    "MissingSchemaDefinition",
-    "SchemaNotFoundError",
-    "SchemaValidationError",
-    "InvalidDatasetTypeError",
-    "CASTING_WARNING_MSG",
-    "NOTICE_MSG",
-]
-
-from typing import Any, Optional
-
-
-class DynamicIOError(Exception):
-    """Base class for DynamicIO errors."""
-
-    ERROR_STR: str = ""
-    ERROR_STR_DETAILED: str = "{0}"
-
-    @property
-    def message(self) -> Optional[Any]:
-        """Easy access for optional message argument.
-
-        Returns:
-            Message or `None` if not set
-        """
-        try:
-            return self.args[0]
-        except IndexError:
-            return None
-
-    def __str__(self):
-        """Enrich and return error message."""
-        message = self.message
-
-        if message is None:
-            return self.ERROR_STR
-
-        return self.ERROR_STR_DETAILED.format(message)
-
-
-class SchemaNotFoundError(DynamicIOError):
-    """Error raised when schema is not specified in the provided source."""
-
-    ERROR_STR = "Schema not specified in the provided source"
-    ERROR_STR_DETAILED = "Schema not specified in the provided source: {0} "
-
-
-class SchemaValidationError(DynamicIOError):
-    """Error raised when schema validation fails."""
-
-
-class MissingSchemaDefinition(DynamicIOError):
-    """Error raised when schema is not specified in the provided source."""
-
-    ERROR_STR = "The resource definition for this class is missing a schema definition"
-    ERROR_STR_DETAILED = "The resource definition for this class is missing a schema definition: {0}"
-
-
-class DataSourceError(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-
-class ColumnsDataTypeError(DynamicIOError):
-    """Error raised when the validated data does not have the expected data types."""
-
-
-class NonUniqueIdColumnError(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-
-class NullValueInColumnError(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-
-class NotExpectedCategoricalValue(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-
-class InvalidDatasetTypeError(DynamicIOError):
-    """Error raised when dataset type is not one of [parquet, json, csv, h5]."""
-
-    ERROR_STR = "The dataset provided is not amongst the supported types (parquet, json, csv, h5) handled by dynamicio."
-    ERROR_STR_DETAILED = "Dataset: {0} provided is not amongst the supported types (parquet, json, csv, h5) handled by dynamicio."
-
-
-# Warning messages
-CASTING_WARNING_MSG = "Applying casting column: '{0}' to: 'type:{1}' from 'type:{2}' though not advised, as `dtypes`>1 for {0}, which may lead to data corruption!"
-NOTICE_MSG = "Keeping the {0} as is, may anyway cause I/O errors or data corruption issues especially when using `pandas.DataFrame.to_parquet` or `pandas.DataFrame.to_json`."
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class ColumnsDataTypeError -(*args, **kwargs) -
-
-

Error raised when the validated data does not have the expected data types.

-
- -Expand source code - -
class ColumnsDataTypeError(DynamicIOError):
-    """Error raised when the validated data does not have the expected data types."""
-
-

Ancestors

- -

Inherited members

- -
-
-class DataSourceError -(*args, **kwargs) -
-
-

Error raised when the data source fails to load.

-
- -Expand source code - -
class DataSourceError(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-

Ancestors

- -

Inherited members

- -
-
-class DynamicIOError -(*args, **kwargs) -
-
-

Base class for DynamicIO errors.

-
- -Expand source code - -
class DynamicIOError(Exception):
-    """Base class for DynamicIO errors."""
-
-    ERROR_STR: str = ""
-    ERROR_STR_DETAILED: str = "{0}"
-
-    @property
-    def message(self) -> Optional[Any]:
-        """Easy access for optional message argument.
-
-        Returns:
-            Message or `None` if not set
-        """
-        try:
-            return self.args[0]
-        except IndexError:
-            return None
-
-    def __str__(self):
-        """Enrich and return error message."""
-        message = self.message
-
-        if message is None:
-            return self.ERROR_STR
-
-        return self.ERROR_STR_DETAILED.format(message)
-
-

Ancestors

-
    -
  • builtins.Exception
  • -
  • builtins.BaseException
  • -
-

Subclasses

- -

Class variables

-
-
var ERROR_STR : str
-
-
-
-
var ERROR_STR_DETAILED : str
-
-
-
-
-

Instance variables

-
-
var message : Optional[Any]
-
-

Easy access for optional message argument.

-

Returns

-

Message or None if not set

-
- -Expand source code - -
@property
-def message(self) -> Optional[Any]:
-    """Easy access for optional message argument.
-
-    Returns:
-        Message or `None` if not set
-    """
-    try:
-        return self.args[0]
-    except IndexError:
-        return None
-
-
-
-
-
-class InvalidDatasetTypeError -(*args, **kwargs) -
-
-

Error raised when dataset type is not one of [parquet, json, csv, h5].

-
- -Expand source code - -
class InvalidDatasetTypeError(DynamicIOError):
-    """Error raised when dataset type is not one of [parquet, json, csv, h5]."""
-
-    ERROR_STR = "The dataset provided is not amongst the supported types (parquet, json, csv, h5) handled by dynamicio."
-    ERROR_STR_DETAILED = "Dataset: {0} provided is not amongst the supported types (parquet, json, csv, h5) handled by dynamicio."
-
-

Ancestors

- -

Class variables

-
-
var ERROR_STR : str
-
-
-
-
var ERROR_STR_DETAILED : str
-
-
-
-
-

Inherited members

- -
-
-class MissingSchemaDefinition -(*args, **kwargs) -
-
-

Error raised when schema is not specified in the provided source.

-
- -Expand source code - -
class MissingSchemaDefinition(DynamicIOError):
-    """Error raised when schema is not specified in the provided source."""
-
-    ERROR_STR = "The resource definition for this class is missing a schema definition"
-    ERROR_STR_DETAILED = "The resource definition for this class is missing a schema definition: {0}"
-
-

Ancestors

- -

Class variables

-
-
var ERROR_STR : str
-
-
-
-
var ERROR_STR_DETAILED : str
-
-
-
-
-

Inherited members

- -
-
-class NonUniqueIdColumnError -(*args, **kwargs) -
-
-

Error raised when the data source fails to load.

-
- -Expand source code - -
class NonUniqueIdColumnError(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-

Ancestors

- -

Inherited members

- -
-
-class NotExpectedCategoricalValue -(*args, **kwargs) -
-
-

Error raised when the data source fails to load.

-
- -Expand source code - -
class NotExpectedCategoricalValue(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-

Ancestors

- -

Inherited members

- -
-
-class NullValueInColumnError -(*args, **kwargs) -
-
-

Error raised when the data source fails to load.

-
- -Expand source code - -
class NullValueInColumnError(DynamicIOError):
-    """Error raised when the data source fails to load."""
-
-

Ancestors

- -

Inherited members

- -
-
-class SchemaNotFoundError -(*args, **kwargs) -
-
-

Error raised when schema is not specified in the provided source.

-
- -Expand source code - -
class SchemaNotFoundError(DynamicIOError):
-    """Error raised when schema is not specified in the provided source."""
-
-    ERROR_STR = "Schema not specified in the provided source"
-    ERROR_STR_DETAILED = "Schema not specified in the provided source: {0} "
-
-

Ancestors

- -

Class variables

-
-
var ERROR_STR : str
-
-
-
-
var ERROR_STR_DETAILED : str
-
-
-
-
-

Inherited members

- -
-
-class SchemaValidationError -(*args, **kwargs) -
-
-

Error raised when schema validation fails.

-
- -Expand source code - -
class SchemaValidationError(DynamicIOError):
-    """Error raised when schema validation fails."""
-
-

Ancestors

- -

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 48d90fa..0000000 --- a/docs/index.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - -dynamicio API documentation - - - - - - - - - - - -
-
-
-

Package dynamicio

-
-
-

A package for wrapping your I/O operations.

-
- -Expand source code - -
"""A package for wrapping your I/O operations."""
-import os
-from contextlib import suppress
-
-import pkg_resources
-from magic_logger import logger
-
-with suppress(Exception):
-    __version__ = pkg_resources.get_distribution("dynamicio").version
-
-from dynamicio.core import DynamicDataIO
-from dynamicio.mixins import WithKafka, WithLocal, WithLocalBatch, WithPostgres, WithS3File, WithS3PathPrefix
-
-os.environ["LC_CTYPE"] = "en_US.UTF"  # Set your locale to a unicode-compatible one
-
-
-class UnifiedIO(WithS3File, WithS3PathPrefix, WithLocalBatch, WithLocal, WithKafka, WithPostgres, DynamicDataIO):  # type: ignore
-    """A unified io composed of dynamicio.mixins."""
-
-
-logging_config = {
-    "version": 1,
-    "disable_existing_loggers": True,
-    "formatters": {
-        "standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"},
-        "generic-metrics": {"format": "%(message)s"},
-    },
-    "handlers": {
-        "default": {
-            "level": "INFO",
-            "formatter": "standard",
-            "class": "logging.StreamHandler",
-            "stream": "ext://sys.stdout",  # Default is stderr
-        },
-        "metrics": {
-            "level": "INFO",
-            "formatter": "generic-metrics",
-            "class": "logging.StreamHandler",
-            "stream": "ext://sys.stdout",  # Default is stderr
-        },
-    },
-    "loggers": {
-        "": {"handlers": ["default"], "level": "INFO", "propagate": False},
-        "dynamicio.metrics": {"handlers": ["metrics"], "level": "INFO", "propagate": False},
-        "awscli": {
-            "handlers": ["default"],
-            "level": "INFO",
-            "propagate": False,
-        },
-    },
-}
-
-if os.environ.get("DYNAMICIO_DO_NOT_CONFIGURE_LOGGING", "") != "1":
-    logger.dict_config(logging_config)
-
-
-
-

Sub-modules

-
-
dynamicio.cli
-
-

Implements the dynamicio Command Line Interface (CLI).

-
-
dynamicio.config
-
-

Dynamicio config file handling routines.

-
-
dynamicio.core
-
-

Implements the DynamicDataIO class which provides functionality for data: loading; sinking, and; schema validation.

-
-
dynamicio.errors
-
-

Hosts exception implementations for different errors.

-
-
dynamicio.metrics
-
-

A module responsible for metrics generation and logging.

-
-
dynamicio.mixins
-
-

Default dynamicio mixins module

-
-
dynamicio.validations
-
-

Implements the Validator class responsible for various generic data validations and metrics generation.

-
-
-
-
-
-
-
-
-

Classes

-
-
-class UnifiedIO -(source_config: IOEnvironment, apply_schema_validations: bool = False, log_schema_metrics: bool = False, show_casting_warnings: bool = False, **options: MutableMapping[str, Any]) -
-
-

A unified io composed of dynamicio.mixins.

-

Class constructor.

-

Args

-
-
source_config
-
Configuration to use when reading/writing data from/to a source
-
apply_schema_validations
-
Applies schema validations on either read() or write()
-
log_schema_metrics
-
Logs schema metrics on either read() or write()
-
show_casting_warnings
-
Logs casting warnings on either read() or write() if set to True
-
options
-
Any additional kwargs that may be used throughout the lifecycle of the object
-
-
- -Expand source code - -
class UnifiedIO(WithS3File, WithS3PathPrefix, WithLocalBatch, WithLocal, WithKafka, WithPostgres, DynamicDataIO):  # type: ignore
-    """A unified io composed of dynamicio.mixins."""
-
-

Ancestors

- -

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/docs/metrics.html b/docs/metrics.html deleted file mode 100644 index 09f2742..0000000 --- a/docs/metrics.html +++ /dev/null @@ -1,930 +0,0 @@ - - - - - - -dynamicio.metrics API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.metrics

-
-
-

A module responsible for metrics generation and logging.

-
- -Expand source code - -
"""A module responsible for metrics generation and logging."""
-# pylint: disable=missing-function-docstring,missing-class-docstring
-import json
-import logging
-import sys
-from datetime import date, timedelta
-from numbers import Number
-from typing import Any, Dict, Mapping, Type, Union
-
-import pandas as pd
-from magic_logger import logger
-from numpy import datetime64, timedelta64
-from pythonjsonlogger import jsonlogger  # type: ignore
-
-logHandler = logging.StreamHandler(sys.stdout)
-formatter = jsonlogger.JsonFormatter()
-logHandler.setFormatter(formatter)
-logger.addHandler(logHandler)
-
-__metrics__: Dict[str, Type["Metric"]] = {}
-
-
-def get_metric(name: str) -> Type["Metric"]:
-    return __metrics__[name]
-
-
-def log_metric(dataset: str, column: str, metric: str, value: float):
-    """Logs a metric in a structured way for a given dataset column.
-
-    Args:
-        dataset: The dataset for which the metric is logged
-        column: Column for which the metric is logged
-        metric: name fo the metric, e.g. "unique_vals"
-        value: The metric's value, e.g. "10000"
-    """
-    logger.info(json.dumps({"message": "METRIC", "dataset": dataset, "column": column, "metric": metric, "value": float(value)}))
-
-
-class Metric:
-    """A base class for implementing metrics classes."""
-
-    def __init__(self, dataset_name: str, df: pd.DataFrame, column: str):  # noqa
-        self.dataset_name = dataset_name
-        self.df = df
-        self.column = column
-
-    def __init_subclass__(cls):  # noqa
-        __metrics__[cls.__name__] = cls
-        assert "calculate_metric" in cls.__dict__
-
-    def __call__(self) -> Any:  # noqa
-        metric_value = self.calculate_metric()
-
-        if isinstance(metric_value, Mapping):
-            for entity in sorted(metric_value.keys()):  # pylint: disable=no-member
-                column = metric_value[entity]  # pylint: disable=unsubscriptable-object
-                log_metric(self.dataset_name, entity, self.metric_name, column)
-        else:
-            log_metric(dataset=self.dataset_name, column=self.column, metric=self.metric_name, value=metric_value)
-        return metric_value
-
-    @property
-    def metric_name(self) -> str:
-        """Retrieves the name of the metric from the class name.
-
-        Returns:
-            The name of the metric, e.g. "Min or Mean".
-        """
-        return self.__class__.__name__
-
-    def calculate_metric(self) -> Any:
-        """Dictates that subclasses need to implement this method.
-
-        Returns:
-            NotImplemented is returned if the method is not implemented, by the subclass
-            inevitably pointing to the parent implementation.
-        """
-        return NotImplemented
-
-
-class Min(Metric):
-    """A metric instance that enables generating and returning the minimum value of a column."""
-
-    def calculate_metric(self) -> Number:
-        """Generate and return the minimum value of a column.
-
-        Returns:
-             The minimum value of a column.
-        """
-        return self.df[self.column].min()
-
-
-class Max(Metric):
-    """A metric instance that enables generating and returning the maximum value of a column."""
-
-    def calculate_metric(self) -> Number:
-        """Generate and return the maximum value of a column.
-
-        Returns:
-            The maximum value of a column.
-        """
-        return self.df[self.column].max()
-
-
-class Mean(Metric):
-    """A metric instance that enables generating and returning the mean value of a column."""
-
-    def calculate_metric(self) -> float:
-        """Generate and return the mean value of a column.
-
-        Returns:
-            The mean value of a column.
-        """
-        return self.df[self.column].mean()
-
-
-class Std(Metric):
-    """A metric instance that enables generating and returning the standard deviation of a column."""
-
-    def calculate_metric(self) -> float:
-        """Generate and return the standard deviation of a column.
-
-        Returns:
-            The standard deviation of a column.
-        """
-        return self.df[self.column].std()
-
-
-class Variance(Metric):
-    """A metric instance that generated and returns the variance of a column."""
-
-    def calculate_metric(self) -> Union[str, bytes, date, timedelta, datetime64, timedelta64, int, float, complex]:
-        """Generate and return the variance of a column.
-
-        Returns:
-            The variance of a column.
-        """
-        return self.df[self.column].var()
-
-
-class Counts(Metric):
-    """A metric instance that enables generating and returning the length of a column."""
-
-    def calculate_metric(self) -> int:
-        """Generate and return the length of a column.
-
-        Returns:
-            The length of a column.
-        """
-        return len(self.df[self.column])
-
-
-class UniqueCounts(Metric):
-    """A metric instance that enables generating and returning the unique values of a column."""
-
-    def calculate_metric(self) -> int:
-        """Generate and return the unique values of a column.
-
-        Returns:
-            The unique values of a column.
-        """
-        return len(self.df[self.column].unique())
-
-
-class CountsPerLabel(Metric):
-    """A metric instance that enables generating and returning the counts per label in a categorical column."""
-
-    def calculate_metric(self) -> Mapping:
-        """Generate and return the counts per label in a categorical column.
-
-        Returns:
-            The counts per label in a categorical column
-        """
-        column_vs_metric_value = self.df[self.column].value_counts().to_dict()
-        label_vs_metric_value_with_column_prefix = {}
-        for key in column_vs_metric_value.keys():
-            new_key = self.column + "-" + key
-            label_vs_metric_value_with_column_prefix[new_key] = column_vs_metric_value[key]
-        return label_vs_metric_value_with_column_prefix
-
-
-
-
-
-
-
-

Functions

-
-
-def get_metric(name: str) ‑> Type[Metric] -
-
-
-
- -Expand source code - -
def get_metric(name: str) -> Type["Metric"]:
-    return __metrics__[name]
-
-
-
-def log_metric(dataset: str, column: str, metric: str, value: float) -
-
-

Logs a metric in a structured way for a given dataset column.

-

Args

-
-
dataset
-
The dataset for which the metric is logged
-
column
-
Column for which the metric is logged
-
metric
-
name fo the metric, e.g. "unique_vals"
-
value
-
The metric's value, e.g. "10000"
-
-
- -Expand source code - -
def log_metric(dataset: str, column: str, metric: str, value: float):
-    """Logs a metric in a structured way for a given dataset column.
-
-    Args:
-        dataset: The dataset for which the metric is logged
-        column: Column for which the metric is logged
-        metric: name fo the metric, e.g. "unique_vals"
-        value: The metric's value, e.g. "10000"
-    """
-    logger.info(json.dumps({"message": "METRIC", "dataset": dataset, "column": column, "metric": metric, "value": float(value)}))
-
-
-
-
-
-

Classes

-
-
-class Counts -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that enables generating and returning the length of a column.

-
- -Expand source code - -
class Counts(Metric):
-    """A metric instance that enables generating and returning the length of a column."""
-
-    def calculate_metric(self) -> int:
-        """Generate and return the length of a column.
-
-        Returns:
-            The length of a column.
-        """
-        return len(self.df[self.column])
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> int -
-
-

Generate and return the length of a column.

-

Returns

-

The length of a column.

-
- -Expand source code - -
def calculate_metric(self) -> int:
-    """Generate and return the length of a column.
-
-    Returns:
-        The length of a column.
-    """
-    return len(self.df[self.column])
-
-
-
-

Inherited members

- -
-
-class CountsPerLabel -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that enables generating and returning the counts per label in a categorical column.

-
- -Expand source code - -
class CountsPerLabel(Metric):
-    """A metric instance that enables generating and returning the counts per label in a categorical column."""
-
-    def calculate_metric(self) -> Mapping:
-        """Generate and return the counts per label in a categorical column.
-
-        Returns:
-            The counts per label in a categorical column
-        """
-        column_vs_metric_value = self.df[self.column].value_counts().to_dict()
-        label_vs_metric_value_with_column_prefix = {}
-        for key in column_vs_metric_value.keys():
-            new_key = self.column + "-" + key
-            label_vs_metric_value_with_column_prefix[new_key] = column_vs_metric_value[key]
-        return label_vs_metric_value_with_column_prefix
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> Mapping -
-
-

Generate and return the counts per label in a categorical column.

-

Returns

-

The counts per label in a categorical column

-
- -Expand source code - -
def calculate_metric(self) -> Mapping:
-    """Generate and return the counts per label in a categorical column.
-
-    Returns:
-        The counts per label in a categorical column
-    """
-    column_vs_metric_value = self.df[self.column].value_counts().to_dict()
-    label_vs_metric_value_with_column_prefix = {}
-    for key in column_vs_metric_value.keys():
-        new_key = self.column + "-" + key
-        label_vs_metric_value_with_column_prefix[new_key] = column_vs_metric_value[key]
-    return label_vs_metric_value_with_column_prefix
-
-
-
-

Inherited members

- -
-
-class Max -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that enables generating and returning the maximum value of a column.

-
- -Expand source code - -
class Max(Metric):
-    """A metric instance that enables generating and returning the maximum value of a column."""
-
-    def calculate_metric(self) -> Number:
-        """Generate and return the maximum value of a column.
-
-        Returns:
-            The maximum value of a column.
-        """
-        return self.df[self.column].max()
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> numbers.Number -
-
-

Generate and return the maximum value of a column.

-

Returns

-

The maximum value of a column.

-
- -Expand source code - -
def calculate_metric(self) -> Number:
-    """Generate and return the maximum value of a column.
-
-    Returns:
-        The maximum value of a column.
-    """
-    return self.df[self.column].max()
-
-
-
-

Inherited members

- -
-
-class Mean -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that enables generating and returning the mean value of a column.

-
- -Expand source code - -
class Mean(Metric):
-    """A metric instance that enables generating and returning the mean value of a column."""
-
-    def calculate_metric(self) -> float:
-        """Generate and return the mean value of a column.
-
-        Returns:
-            The mean value of a column.
-        """
-        return self.df[self.column].mean()
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> float -
-
-

Generate and return the mean value of a column.

-

Returns

-

The mean value of a column.

-
- -Expand source code - -
def calculate_metric(self) -> float:
-    """Generate and return the mean value of a column.
-
-    Returns:
-        The mean value of a column.
-    """
-    return self.df[self.column].mean()
-
-
-
-

Inherited members

- -
-
-class Metric -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A base class for implementing metrics classes.

-
- -Expand source code - -
class Metric:
-    """A base class for implementing metrics classes."""
-
-    def __init__(self, dataset_name: str, df: pd.DataFrame, column: str):  # noqa
-        self.dataset_name = dataset_name
-        self.df = df
-        self.column = column
-
-    def __init_subclass__(cls):  # noqa
-        __metrics__[cls.__name__] = cls
-        assert "calculate_metric" in cls.__dict__
-
-    def __call__(self) -> Any:  # noqa
-        metric_value = self.calculate_metric()
-
-        if isinstance(metric_value, Mapping):
-            for entity in sorted(metric_value.keys()):  # pylint: disable=no-member
-                column = metric_value[entity]  # pylint: disable=unsubscriptable-object
-                log_metric(self.dataset_name, entity, self.metric_name, column)
-        else:
-            log_metric(dataset=self.dataset_name, column=self.column, metric=self.metric_name, value=metric_value)
-        return metric_value
-
-    @property
-    def metric_name(self) -> str:
-        """Retrieves the name of the metric from the class name.
-
-        Returns:
-            The name of the metric, e.g. "Min or Mean".
-        """
-        return self.__class__.__name__
-
-    def calculate_metric(self) -> Any:
-        """Dictates that subclasses need to implement this method.
-
-        Returns:
-            NotImplemented is returned if the method is not implemented, by the subclass
-            inevitably pointing to the parent implementation.
-        """
-        return NotImplemented
-
-

Subclasses

- -

Instance variables

-
-
var metric_name : str
-
-

Retrieves the name of the metric from the class name.

-

Returns

-

The name of the metric, e.g. "Min or Mean".

-
- -Expand source code - -
@property
-def metric_name(self) -> str:
-    """Retrieves the name of the metric from the class name.
-
-    Returns:
-        The name of the metric, e.g. "Min or Mean".
-    """
-    return self.__class__.__name__
-
-
-
-

Methods

-
-
-def calculate_metric(self) ‑> Any -
-
-

Dictates that subclasses need to implement this method.

-

Returns

-

NotImplemented is returned if the method is not implemented, by the subclass -inevitably pointing to the parent implementation.

-
- -Expand source code - -
def calculate_metric(self) -> Any:
-    """Dictates that subclasses need to implement this method.
-
-    Returns:
-        NotImplemented is returned if the method is not implemented, by the subclass
-        inevitably pointing to the parent implementation.
-    """
-    return NotImplemented
-
-
-
-
-
-class Min -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that enables generating and returning the minimum value of a column.

-
- -Expand source code - -
class Min(Metric):
-    """A metric instance that enables generating and returning the minimum value of a column."""
-
-    def calculate_metric(self) -> Number:
-        """Generate and return the minimum value of a column.
-
-        Returns:
-             The minimum value of a column.
-        """
-        return self.df[self.column].min()
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> numbers.Number -
-
-

Generate and return the minimum value of a column.

-

Returns

-

The minimum value of a column.

-
- -Expand source code - -
def calculate_metric(self) -> Number:
-    """Generate and return the minimum value of a column.
-
-    Returns:
-         The minimum value of a column.
-    """
-    return self.df[self.column].min()
-
-
-
-

Inherited members

- -
-
-class Std -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that enables generating and returning the standard deviation of a column.

-
- -Expand source code - -
class Std(Metric):
-    """A metric instance that enables generating and returning the standard deviation of a column."""
-
-    def calculate_metric(self) -> float:
-        """Generate and return the standard deviation of a column.
-
-        Returns:
-            The standard deviation of a column.
-        """
-        return self.df[self.column].std()
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> float -
-
-

Generate and return the standard deviation of a column.

-

Returns

-

The standard deviation of a column.

-
- -Expand source code - -
def calculate_metric(self) -> float:
-    """Generate and return the standard deviation of a column.
-
-    Returns:
-        The standard deviation of a column.
-    """
-    return self.df[self.column].std()
-
-
-
-

Inherited members

- -
-
-class UniqueCounts -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that enables generating and returning the unique values of a column.

-
- -Expand source code - -
class UniqueCounts(Metric):
-    """A metric instance that enables generating and returning the unique values of a column."""
-
-    def calculate_metric(self) -> int:
-        """Generate and return the unique values of a column.
-
-        Returns:
-            The unique values of a column.
-        """
-        return len(self.df[self.column].unique())
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> int -
-
-

Generate and return the unique values of a column.

-

Returns

-

The unique values of a column.

-
- -Expand source code - -
def calculate_metric(self) -> int:
-    """Generate and return the unique values of a column.
-
-    Returns:
-        The unique values of a column.
-    """
-    return len(self.df[self.column].unique())
-
-
-
-

Inherited members

- -
-
-class Variance -(dataset_name: str, df: pandas.core.frame.DataFrame, column: str) -
-
-

A metric instance that generated and returns the variance of a column.

-
- -Expand source code - -
class Variance(Metric):
-    """A metric instance that generated and returns the variance of a column."""
-
-    def calculate_metric(self) -> Union[str, bytes, date, timedelta, datetime64, timedelta64, int, float, complex]:
-        """Generate and return the variance of a column.
-
-        Returns:
-            The variance of a column.
-        """
-        return self.df[self.column].var()
-
-

Ancestors

- -

Methods

-
-
-def calculate_metric(self) ‑> Union[str, bytes, datetime.date, datetime.timedelta, numpy.datetime64, numpy.timedelta64, int, float, complex] -
-
-

Generate and return the variance of a column.

-

Returns

-

The variance of a column.

-
- -Expand source code - -
def calculate_metric(self) -> Union[str, bytes, date, timedelta, datetime64, timedelta64, int, float, complex]:
-    """Generate and return the variance of a column.
-
-    Returns:
-        The variance of a column.
-    """
-    return self.df[self.column].var()
-
-
-
-

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/mixins/index.html b/docs/mixins/index.html deleted file mode 100644 index d5e6eac..0000000 --- a/docs/mixins/index.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - -dynamicio.mixins API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins

-
-
-

Default dynamicio mixins module

-
- -Expand source code - -
"""Default dynamicio mixins module"""
-
-from dynamicio.mixins.with_kafka import (
-    WithKafka,
-)
-from dynamicio.mixins.with_local import (
-    WithLocal,
-    WithLocalBatch,
-)
-from dynamicio.mixins.with_postgres import (
-    WithPostgres,
-)
-from dynamicio.mixins.with_s3 import (
-    WithS3File,
-    WithS3PathPrefix,
-)
-
-
-
-

Sub-modules

-
-
dynamicio.mixins.utils
-
-

Mixin utility functions

-
-
dynamicio.mixins.with_kafka
-
-

This module provides mixins that are providing Kafka I/O support.

-
-
dynamicio.mixins.with_local
-
-

This module provides mixins that are providing Local FS I/O support.

-
-
dynamicio.mixins.with_postgres
-
-

This module provides mixins that are providing Postgres I/O support.

-
-
dynamicio.mixins.with_s3
-
-

This module provides mixins that are providing S3 I/O support.

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/mixins/utils.html b/docs/mixins/utils.html deleted file mode 100644 index 54b3555..0000000 --- a/docs/mixins/utils.html +++ /dev/null @@ -1,442 +0,0 @@ - - - - - - -dynamicio.mixins.utils API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.utils

-
-
-

Mixin utility functions

-
- -Expand source code - -
"""Mixin utility functions"""
-# pylint: disable=no-member, protected-access, too-few-public-methods
-
-import inspect
-import string
-from contextlib import contextmanager
-from functools import wraps
-from types import FunctionType, MethodType
-from typing import Any, Collection, Iterable, Mapping, MutableMapping, Optional, Union
-
-from magic_logger import logger
-
-
-def allow_options(options: Union[Iterable, FunctionType, MethodType]):
-    """Validate **options for a decorated reader function.
-
-    Args:
-        options: A set of valid options for a reader (e.g. `pandas.read_parquet` or `pandas.read_csv`)
-
-    Returns:
-        read_with_valid_options: The input function called with modified options.
-    """
-
-    def _filter_out_irrelevant_options(kwargs: Mapping, valid_options: Iterable):
-        filtered_options = {}
-        invalid_options = {}
-        for key_arg in kwargs.keys():
-            if key_arg in valid_options:
-                filtered_options[key_arg] = kwargs[key_arg]
-            else:
-                invalid_options[key_arg] = kwargs[key_arg]
-        if len(invalid_options) > 0:
-            logger.warning(
-                f"Options {invalid_options} were not used because they were not supported by the read or write method configured for this source. "
-                "Check if you expected any of those to have been used by the operation!"
-            )
-        return filtered_options
-
-    def read_with_valid_options(func):
-        @wraps(func)
-        def _(*args, **kwargs):
-            if callable(options):
-                return func(*args, **_filter_out_irrelevant_options(kwargs, args_of(options)))
-            return func(*args, **_filter_out_irrelevant_options(kwargs, options))
-
-        return _
-
-    return read_with_valid_options
-
-
-def args_of(func):
-    """Retrieve allowed options for a given function.
-
-    Args:
-        func: A function like, e.g., pd.read_csv
-
-    Returns:
-        A set of allowed options
-    """
-    return set(inspect.signature(func).parameters.keys())
-
-
-def get_string_template_field_names(s: str) -> Collection[str]:  # pylint: disable=C0103
-    """Given a string `s`, it parses the string to identify any template fields and returns the names of those fields.
-
-     If `s` is not a string template, the returned `Collection` is empty.
-
-    Args:
-        s:
-
-    Returns:
-        Collection[str]
-
-    Example:
-
-        >>> get_string_template_field_names("abc{def}{efg}")
-        ["def", "efg"]
-        >>> get_string_template_field_names("{0}-{1}")
-        ["0", "1"]
-        >>> get_string_template_field_names("hello world")
-        []
-    """
-    # string.Formatter.parse returns a 4-tuple of:
-    # `literal_text`, `field_name`, `form_at_spec`, `conversion`
-    # More info here https://docs.python.org/3.8/library/string.html#string.Formatter.parse
-    field_names = [group[1] for group in string.Formatter().parse(s) if group[1] is not None]
-
-    return field_names
-
-
-def resolve_template(path: str, options: MutableMapping[str, Any]) -> str:  # pylint: disable=C0103
-    """Given a string `path`, it attempts to replace all templates fields with values provided in `options`.
-
-    If `path` is not a string template, `path` is returned.
-
-    Args:
-        path: A string which is either a template, e.g. /path/to/file/{replace_me}.h5 or just a path /path/to/file/dont_replace_me.h5
-        options: A dynamic name for the "replace_me" field in the templated string. e.g. {"replace_me": "name_of_file"}
-
-    Returns:
-        str: Returns a static path replaced with the value in the options mapping.
-
-    Raises:
-        ValueError: if any template fields in s are not named using valid Python identifiers
-        ValueError: if a given template field cannot be resolved in `options`
-    """
-    fields = get_string_template_field_names(path)
-
-    if len(fields) == 0:
-        return path
-
-    if not all(field.isidentifier() for field in fields):
-        raise ValueError(f"Expected valid Python identifiers, found {fields}")
-
-    if not all(field in options for field in fields):
-        raise ValueError(f"Expected values for all fields in {fields}, found {list(options.keys())}")
-
-    path = path.format(**{field: options[field] for field in fields})
-    for field in fields:
-        options.pop(field)
-
-    return path
-
-
-@contextmanager
-def pickle_protocol(protocol: Optional[int]):
-    """Downgrade to the provided pickle protocol within the context manager.
-
-    Args:
-        protocol: The number of the protocol HIGHEST_PROTOCOL to downgrade to. Defaults to 4, which covers python 3.4 and higher.
-    """
-    import pickle  # pylint: disable=import-outside-toplevel
-
-    previous = pickle.HIGHEST_PROTOCOL
-    try:
-        pickle.HIGHEST_PROTOCOL = 4
-        if protocol:
-            pickle.HIGHEST_PROTOCOL = protocol
-        yield
-    finally:
-        pickle.HIGHEST_PROTOCOL = previous
-
-
-
-
-
-
-
-

Functions

-
-
-def allow_options(options: Union[Iterable[+T_co], function, method]) -
-
-

Validate **options for a decorated reader function.

-

Args

-
-
options
-
A set of valid options for a reader (e.g. pandas.read_parquet or pandas.read_csv)
-
-

Returns

-
-
read_with_valid_options
-
The input function called with modified options.
-
-
- -Expand source code - -
def allow_options(options: Union[Iterable, FunctionType, MethodType]):
-    """Validate **options for a decorated reader function.
-
-    Args:
-        options: A set of valid options for a reader (e.g. `pandas.read_parquet` or `pandas.read_csv`)
-
-    Returns:
-        read_with_valid_options: The input function called with modified options.
-    """
-
-    def _filter_out_irrelevant_options(kwargs: Mapping, valid_options: Iterable):
-        filtered_options = {}
-        invalid_options = {}
-        for key_arg in kwargs.keys():
-            if key_arg in valid_options:
-                filtered_options[key_arg] = kwargs[key_arg]
-            else:
-                invalid_options[key_arg] = kwargs[key_arg]
-        if len(invalid_options) > 0:
-            logger.warning(
-                f"Options {invalid_options} were not used because they were not supported by the read or write method configured for this source. "
-                "Check if you expected any of those to have been used by the operation!"
-            )
-        return filtered_options
-
-    def read_with_valid_options(func):
-        @wraps(func)
-        def _(*args, **kwargs):
-            if callable(options):
-                return func(*args, **_filter_out_irrelevant_options(kwargs, args_of(options)))
-            return func(*args, **_filter_out_irrelevant_options(kwargs, options))
-
-        return _
-
-    return read_with_valid_options
-
-
-
-def args_of(func) -
-
-

Retrieve allowed options for a given function.

-

Args

-
-
func
-
A function like, e.g., pd.read_csv
-
-

Returns

-

A set of allowed options

-
- -Expand source code - -
def args_of(func):
-    """Retrieve allowed options for a given function.
-
-    Args:
-        func: A function like, e.g., pd.read_csv
-
-    Returns:
-        A set of allowed options
-    """
-    return set(inspect.signature(func).parameters.keys())
-
-
-
-def get_string_template_field_names(s: str) ‑> Collection[str] -
-
-

Given a string s, it parses the string to identify any template fields and returns the names of those fields.

-

If s is not a string template, the returned Collection is empty.

-

Args

-

s:

-

Returns

-

Collection[str]

-

Example

-
>>> get_string_template_field_names("abc{def}{efg}")
-["def", "efg"]
->>> get_string_template_field_names("{0}-{1}")
-["0", "1"]
->>> get_string_template_field_names("hello world")
-[]
-
-
- -Expand source code - -
def get_string_template_field_names(s: str) -> Collection[str]:  # pylint: disable=C0103
-    """Given a string `s`, it parses the string to identify any template fields and returns the names of those fields.
-
-     If `s` is not a string template, the returned `Collection` is empty.
-
-    Args:
-        s:
-
-    Returns:
-        Collection[str]
-
-    Example:
-
-        >>> get_string_template_field_names("abc{def}{efg}")
-        ["def", "efg"]
-        >>> get_string_template_field_names("{0}-{1}")
-        ["0", "1"]
-        >>> get_string_template_field_names("hello world")
-        []
-    """
-    # string.Formatter.parse returns a 4-tuple of:
-    # `literal_text`, `field_name`, `form_at_spec`, `conversion`
-    # More info here https://docs.python.org/3.8/library/string.html#string.Formatter.parse
-    field_names = [group[1] for group in string.Formatter().parse(s) if group[1] is not None]
-
-    return field_names
-
-
-
-def pickle_protocol(protocol: Optional[int]) -
-
-

Downgrade to the provided pickle protocol within the context manager.

-

Args

-
-
protocol
-
The number of the protocol HIGHEST_PROTOCOL to downgrade to. Defaults to 4, which covers python 3.4 and higher.
-
-
- -Expand source code - -
@contextmanager
-def pickle_protocol(protocol: Optional[int]):
-    """Downgrade to the provided pickle protocol within the context manager.
-
-    Args:
-        protocol: The number of the protocol HIGHEST_PROTOCOL to downgrade to. Defaults to 4, which covers python 3.4 and higher.
-    """
-    import pickle  # pylint: disable=import-outside-toplevel
-
-    previous = pickle.HIGHEST_PROTOCOL
-    try:
-        pickle.HIGHEST_PROTOCOL = 4
-        if protocol:
-            pickle.HIGHEST_PROTOCOL = protocol
-        yield
-    finally:
-        pickle.HIGHEST_PROTOCOL = previous
-
-
-
-def resolve_template(path: str, options: MutableMapping[str, Any]) ‑> str -
-
-

Given a string path, it attempts to replace all templates fields with values provided in options.

-

If path is not a string template, path is returned.

-

Args

-
-
path
-
A string which is either a template, e.g. /path/to/file/{replace_me}.h5 or just a path /path/to/file/dont_replace_me.h5
-
options
-
A dynamic name for the "replace_me" field in the templated string. e.g. {"replace_me": "name_of_file"}
-
-

Returns

-
-
str
-
Returns a static path replaced with the value in the options mapping.
-
-

Raises

-
-
ValueError
-
if any template fields in s are not named using valid Python identifiers
-
ValueError
-
if a given template field cannot be resolved in options
-
-
- -Expand source code - -
def resolve_template(path: str, options: MutableMapping[str, Any]) -> str:  # pylint: disable=C0103
-    """Given a string `path`, it attempts to replace all templates fields with values provided in `options`.
-
-    If `path` is not a string template, `path` is returned.
-
-    Args:
-        path: A string which is either a template, e.g. /path/to/file/{replace_me}.h5 or just a path /path/to/file/dont_replace_me.h5
-        options: A dynamic name for the "replace_me" field in the templated string. e.g. {"replace_me": "name_of_file"}
-
-    Returns:
-        str: Returns a static path replaced with the value in the options mapping.
-
-    Raises:
-        ValueError: if any template fields in s are not named using valid Python identifiers
-        ValueError: if a given template field cannot be resolved in `options`
-    """
-    fields = get_string_template_field_names(path)
-
-    if len(fields) == 0:
-        return path
-
-    if not all(field.isidentifier() for field in fields):
-        raise ValueError(f"Expected valid Python identifiers, found {fields}")
-
-    if not all(field in options for field in fields):
-        raise ValueError(f"Expected values for all fields in {fields}, found {list(options.keys())}")
-
-    path = path.format(**{field: options[field] for field in fields})
-    for field in fields:
-        options.pop(field)
-
-    return path
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/mixins/with_kafka.html b/docs/mixins/with_kafka.html deleted file mode 100644 index 0161374..0000000 --- a/docs/mixins/with_kafka.html +++ /dev/null @@ -1,478 +0,0 @@ - - - - - - -dynamicio.mixins.with_kafka API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_kafka

-
-
-

This module provides mixins that are providing Kafka I/O support.

-
- -Expand source code - -
# pylint: disable=no-member, protected-access, too-few-public-methods
-
-"""This module provides mixins that are providing Kafka I/O support."""
-
-
-from typing import Any, Callable, Iterable, Mapping, MutableMapping, Optional
-
-import pandas as pd  # type: ignore
-import simplejson
-from kafka import KafkaProducer  # type: ignore
-from magic_logger import logger
-
-
-from dynamicio.config.pydantic import DataframeSchema, KafkaDataEnvironment
-from dynamicio.mixins import utils
-
-
-class WithKafka:
-    """Handles I/O operations for Kafka.
-
-    Args:
-        - options:
-            - Standard: Keyword-arguments passed to the KafkaProducer constructor (see `KafkaProducer.DEFAULT_CONFIG.keys()`).
-             - Additional Options:
-
-                - `key_generator: Callable[[Any, Mapping], T]`: defines the keying policy to be used for sending keyed-messages to Kafka. It is a `Callable` that takes a
-                `tuple(idx, row)` and returns a string that will serve as the message's key, invoked prior to serialising the key. It defaults to the dataframe's index
-                (which may not be composed of unique values or string type keys). It goes hand in hand with the default `key-serialiser`, which assumes that the keys
-                are strings and encode's them as such.
-
-                - `key_serializer: Callable[T, bytes]`: Custom key serialiser; if not provided, a default key-serializer will be used, applied on a string-key (unless key is None).
-
-                N.B. Providing a custom key-generator that generates a non-string key is best provided alongside a custom key-serializer best suited to handle the custom key-type.
-
-                - `document_transformer: Callable[[Mapping[Any, Any]`: Manipulates the messages/rows sent to Kafka as values. It is  a `Callable` taking a `Mapping` as its only
-                argument and return a `Mapping`, then this callable will be invoked prior to serializing each document. This can be used, for example, to add metadata to each
-                document that will be written to the target  Kafka topic.
-
-                - `value_serializer: Callable[Mapping, bytes]`: Custom value serialiser; if not provided, a default value-serializer will be used applied on a Mapping..
-
-    Example:
-        >>> # Given
-        >>> keyed_test_df = pd.DataFrame.from_records(
-        >>>     [
-        >>>         ["key-01", "cm_1", "id_1", 1000, "ABC"],
-        >>>         ["key-02", "cm_2", "id_2", 1000, "ABC"],
-        >>>         ["key-03", "cm_3", "id_3", 1000, "ABC"],
-        >>>     ],
-        >>>     columns=["key", "id", "foo", "bar", "baz"],
-        >>> ).set_index("key")
-        >>>
-        >>> kafka_cloud_config = IOConfig(
-        >>>     path_to_source_yaml=(os.path.join(constants.TEST_RESOURCES, "processed.yaml")),
-        >>>     env_identifier="CLOUD",
-        >>>     dynamic_vars=constants,
-        >>> ).get(source_key="WRITE_TO_KAFKA_JSON")
-        >>>
-        >>> write_kafka_io = WriteKafkaIO(kafka_cloud_config, key_generator=lambda key, _: key, document_transformer=lambda doc: doc["new_field"]="new_value")
-        >>>
-        >>> # When
-        >>> with patch.object(mixins, "KafkaProducer") as mock__kafka_producer:
-        >>>     mock__kafka_producer.DEFAULT_CONFIG = KafkaProducer.DEFAULT_CONFIG
-        >>>     mock_producer = MockKafkaProducer()
-        >>>     mock__kafka_producer.return_value = mock_producer
-        >>>     write_kafka_io.write(keyed_test_df)
-        >>>
-        >>> # Then
-        >>> assert mock_producer.my_stream == [
-        >>>     {"key": "key-01", "value": {"bar": 1000, "baz": "ABC", "foo": "id_1", "id": "cm_1", "new_field": "new_value"}},
-        >>>     {"key": "key-02", "value": {"bar": 1000, "baz": "ABC", "foo": "id_2", "id": "cm_2", "new_field": "new_value"}},
-        >>>     {"key": "key-03", "value": {"bar": 1000, "baz": "ABC", "foo": "id_3", "id": "cm_3", "new_field": "new_value"}},
-        >>> ]
-    """
-
-    sources_config: KafkaDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-    __kafka_config: Optional[Mapping] = None
-    __producer: Optional[KafkaProducer] = None
-    __key_generator: Optional[Callable[[Any, Mapping[Any, Any]], Optional[str]]] = None
-    __document_transformer: Optional[Callable[[Mapping[Any, Any]], Mapping[Any, Any]]] = None
-
-    def _write_to_kafka(self, df: pd.DataFrame) -> None:
-        """Given a dataframe where each row is a message to be sent to a Kafka Topic, iterate through all rows and send them to a Kafka topic.
-
-         The topic is defined in `self.sources_config["kafka"]` and using a kafka producer, which is flushed at the
-         end of this process.
-
-        Args:
-            df: A dataframe where each row is a message to be sent to a Kafka Topic.
-        """
-        if self.__key_generator is None:
-            self.__key_generator = lambda idx, __: idx  # default key generator uses the dataframe's index
-            if self.options.get("key_generator") is not None:
-                self.__key_generator = self.options.pop("key_generator")
-
-        if self.__document_transformer is None:
-            self.__document_transformer = lambda value: value
-            if self.options.get("document_transformer") is not None:
-                self.__document_transformer = self.options.pop("document_transformer")
-
-        if self.__producer is None:
-            self.__producer = self._get_producer(self.sources_config.kafka.kafka_server, **self.options)
-
-        self._send_messages(df=df, topic=self.sources_config.kafka.kafka_topic)
-
-    @utils.allow_options(KafkaProducer.DEFAULT_CONFIG.keys())
-    def _get_producer(self, server: str, **options: MutableMapping[str, Any]) -> KafkaProducer:
-        """Generate and return a Kafka Producer.
-
-        Default options are used to generate the producer. Specifically:
-            - `bootstrap_servers`: Passed on through the source_config
-            - `value_serializer`: Uses a default_value_serializer defined in this mixin
-
-        More options can be added to the producer by passing them as keyword arguments, through valid options.
-
-        These can also override the default options.
-
-        Args:
-            server: The host name.
-            **options: Keyword arguments to pass to the KafkaProducer.
-
-        Returns:
-            A Kafka producer instance.
-        """
-        self.__kafka_config = {
-            **{
-                "bootstrap_servers": server,
-                "compression_type": "snappy",
-                "key_serializer": self._default_key_serializer,
-                "value_serializer": self._default_value_serializer,
-            },
-            **options,
-        }
-        return KafkaProducer(**self.__kafka_config)
-
-    def _send_messages(self, df: pd.DataFrame, topic: str) -> None:
-        logger.info(f"Sending {len(df)} messages to Kafka topic:{topic}.")
-
-        messages = df.reset_index(drop=True).to_dict("records")
-        for idx, message in zip(df.index.values, messages):
-            self.__producer.send(topic, key=self.__key_generator(idx, message), value=self.__document_transformer(message))  # type: ignore
-
-        self.__producer.flush()  # type: ignore
-
-    @staticmethod
-    def _default_key_serializer(key: Optional[str]) -> Optional[bytes]:
-        if key:
-            return key.encode("utf-8")
-        return None
-
-    @staticmethod
-    def _default_value_serializer(value: Mapping) -> bytes:
-        return simplejson.dumps(value, ignore_nan=True).encode("utf-8")
-
-    def _read_from_kafka(self) -> Iterable[Mapping]:  # type: ignore
-        """Read messages from a Kafka Topic and convert them to separate dataframes.
-
-        Returns:
-            Multiple dataframes, one per message read from the Kafka topic of interest.
-        """
-        # TODO: Implement kafka reader
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class WithKafka -
-
-

Handles I/O operations for Kafka.

-

Args

-
    -
  • options:
      -
    • Standard: Keyword-arguments passed to the KafkaProducer constructor (see KafkaProducer.DEFAULT_CONFIG.keys()).
    • -
    • -

      Additional Options:

      -
        -
      • -

        key_generator: Callable[[Any, Mapping], T]: defines the keying policy to be used for sending keyed-messages to Kafka. It is a Callable that takes a -tuple(idx, row) and returns a string that will serve as the message's key, invoked prior to serialising the key. It defaults to the dataframe's index -(which may not be composed of unique values or string type keys). It goes hand in hand with the default key-serialiser, which assumes that the keys -are strings and encode's them as such.

        -
      • -
      • -

        key_serializer: Callable[T, bytes]: Custom key serialiser; if not provided, a default key-serializer will be used, applied on a string-key (unless key is None).

        -
      • -
      -

      N.B. Providing a custom key-generator that generates a non-string key is best provided alongside a custom key-serializer best suited to handle the custom key-type.

      -
        -
      • -

        document_transformer: Callable[[Mapping[Any, Any]: Manipulates the messages/rows sent to Kafka as values. It is -a Callable taking a Mapping as its only -argument and return a Mapping, then this callable will be invoked prior to serializing each document. This can be used, for example, to add metadata to each -document that will be written to the target -Kafka topic.

        -
      • -
      • -

        value_serializer: Callable[Mapping, bytes]: Custom value serialiser; if not provided, a default value-serializer will be used applied on a Mapping..

        -
      • -
      -
    • -
    -
  • -
-

Example

-
>>> # Given
->>> keyed_test_df = pd.DataFrame.from_records(
->>>     [
->>>         ["key-01", "cm_1", "id_1", 1000, "ABC"],
->>>         ["key-02", "cm_2", "id_2", 1000, "ABC"],
->>>         ["key-03", "cm_3", "id_3", 1000, "ABC"],
->>>     ],
->>>     columns=["key", "id", "foo", "bar", "baz"],
->>> ).set_index("key")
->>>
->>> kafka_cloud_config = IOConfig(
->>>     path_to_source_yaml=(os.path.join(constants.TEST_RESOURCES, "processed.yaml")),
->>>     env_identifier="CLOUD",
->>>     dynamic_vars=constants,
->>> ).get(source_key="WRITE_TO_KAFKA_JSON")
->>>
->>> write_kafka_io = WriteKafkaIO(kafka_cloud_config, key_generator=lambda key, _: key, document_transformer=lambda doc: doc["new_field"]="new_value")
->>>
->>> # When
->>> with patch.object(mixins, "KafkaProducer") as mock__kafka_producer:
->>>     mock__kafka_producer.DEFAULT_CONFIG = KafkaProducer.DEFAULT_CONFIG
->>>     mock_producer = MockKafkaProducer()
->>>     mock__kafka_producer.return_value = mock_producer
->>>     write_kafka_io.write(keyed_test_df)
->>>
->>> # Then
->>> assert mock_producer.my_stream == [
->>>     {"key": "key-01", "value": {"bar": 1000, "baz": "ABC", "foo": "id_1", "id": "cm_1", "new_field": "new_value"}},
->>>     {"key": "key-02", "value": {"bar": 1000, "baz": "ABC", "foo": "id_2", "id": "cm_2", "new_field": "new_value"}},
->>>     {"key": "key-03", "value": {"bar": 1000, "baz": "ABC", "foo": "id_3", "id": "cm_3", "new_field": "new_value"}},
->>> ]
-
-
- -Expand source code - -
class WithKafka:
-    """Handles I/O operations for Kafka.
-
-    Args:
-        - options:
-            - Standard: Keyword-arguments passed to the KafkaProducer constructor (see `KafkaProducer.DEFAULT_CONFIG.keys()`).
-             - Additional Options:
-
-                - `key_generator: Callable[[Any, Mapping], T]`: defines the keying policy to be used for sending keyed-messages to Kafka. It is a `Callable` that takes a
-                `tuple(idx, row)` and returns a string that will serve as the message's key, invoked prior to serialising the key. It defaults to the dataframe's index
-                (which may not be composed of unique values or string type keys). It goes hand in hand with the default `key-serialiser`, which assumes that the keys
-                are strings and encode's them as such.
-
-                - `key_serializer: Callable[T, bytes]`: Custom key serialiser; if not provided, a default key-serializer will be used, applied on a string-key (unless key is None).
-
-                N.B. Providing a custom key-generator that generates a non-string key is best provided alongside a custom key-serializer best suited to handle the custom key-type.
-
-                - `document_transformer: Callable[[Mapping[Any, Any]`: Manipulates the messages/rows sent to Kafka as values. It is  a `Callable` taking a `Mapping` as its only
-                argument and return a `Mapping`, then this callable will be invoked prior to serializing each document. This can be used, for example, to add metadata to each
-                document that will be written to the target  Kafka topic.
-
-                - `value_serializer: Callable[Mapping, bytes]`: Custom value serialiser; if not provided, a default value-serializer will be used applied on a Mapping..
-
-    Example:
-        >>> # Given
-        >>> keyed_test_df = pd.DataFrame.from_records(
-        >>>     [
-        >>>         ["key-01", "cm_1", "id_1", 1000, "ABC"],
-        >>>         ["key-02", "cm_2", "id_2", 1000, "ABC"],
-        >>>         ["key-03", "cm_3", "id_3", 1000, "ABC"],
-        >>>     ],
-        >>>     columns=["key", "id", "foo", "bar", "baz"],
-        >>> ).set_index("key")
-        >>>
-        >>> kafka_cloud_config = IOConfig(
-        >>>     path_to_source_yaml=(os.path.join(constants.TEST_RESOURCES, "processed.yaml")),
-        >>>     env_identifier="CLOUD",
-        >>>     dynamic_vars=constants,
-        >>> ).get(source_key="WRITE_TO_KAFKA_JSON")
-        >>>
-        >>> write_kafka_io = WriteKafkaIO(kafka_cloud_config, key_generator=lambda key, _: key, document_transformer=lambda doc: doc["new_field"]="new_value")
-        >>>
-        >>> # When
-        >>> with patch.object(mixins, "KafkaProducer") as mock__kafka_producer:
-        >>>     mock__kafka_producer.DEFAULT_CONFIG = KafkaProducer.DEFAULT_CONFIG
-        >>>     mock_producer = MockKafkaProducer()
-        >>>     mock__kafka_producer.return_value = mock_producer
-        >>>     write_kafka_io.write(keyed_test_df)
-        >>>
-        >>> # Then
-        >>> assert mock_producer.my_stream == [
-        >>>     {"key": "key-01", "value": {"bar": 1000, "baz": "ABC", "foo": "id_1", "id": "cm_1", "new_field": "new_value"}},
-        >>>     {"key": "key-02", "value": {"bar": 1000, "baz": "ABC", "foo": "id_2", "id": "cm_2", "new_field": "new_value"}},
-        >>>     {"key": "key-03", "value": {"bar": 1000, "baz": "ABC", "foo": "id_3", "id": "cm_3", "new_field": "new_value"}},
-        >>> ]
-    """
-
-    sources_config: KafkaDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-    __kafka_config: Optional[Mapping] = None
-    __producer: Optional[KafkaProducer] = None
-    __key_generator: Optional[Callable[[Any, Mapping[Any, Any]], Optional[str]]] = None
-    __document_transformer: Optional[Callable[[Mapping[Any, Any]], Mapping[Any, Any]]] = None
-
-    def _write_to_kafka(self, df: pd.DataFrame) -> None:
-        """Given a dataframe where each row is a message to be sent to a Kafka Topic, iterate through all rows and send them to a Kafka topic.
-
-         The topic is defined in `self.sources_config["kafka"]` and using a kafka producer, which is flushed at the
-         end of this process.
-
-        Args:
-            df: A dataframe where each row is a message to be sent to a Kafka Topic.
-        """
-        if self.__key_generator is None:
-            self.__key_generator = lambda idx, __: idx  # default key generator uses the dataframe's index
-            if self.options.get("key_generator") is not None:
-                self.__key_generator = self.options.pop("key_generator")
-
-        if self.__document_transformer is None:
-            self.__document_transformer = lambda value: value
-            if self.options.get("document_transformer") is not None:
-                self.__document_transformer = self.options.pop("document_transformer")
-
-        if self.__producer is None:
-            self.__producer = self._get_producer(self.sources_config.kafka.kafka_server, **self.options)
-
-        self._send_messages(df=df, topic=self.sources_config.kafka.kafka_topic)
-
-    @utils.allow_options(KafkaProducer.DEFAULT_CONFIG.keys())
-    def _get_producer(self, server: str, **options: MutableMapping[str, Any]) -> KafkaProducer:
-        """Generate and return a Kafka Producer.
-
-        Default options are used to generate the producer. Specifically:
-            - `bootstrap_servers`: Passed on through the source_config
-            - `value_serializer`: Uses a default_value_serializer defined in this mixin
-
-        More options can be added to the producer by passing them as keyword arguments, through valid options.
-
-        These can also override the default options.
-
-        Args:
-            server: The host name.
-            **options: Keyword arguments to pass to the KafkaProducer.
-
-        Returns:
-            A Kafka producer instance.
-        """
-        self.__kafka_config = {
-            **{
-                "bootstrap_servers": server,
-                "compression_type": "snappy",
-                "key_serializer": self._default_key_serializer,
-                "value_serializer": self._default_value_serializer,
-            },
-            **options,
-        }
-        return KafkaProducer(**self.__kafka_config)
-
-    def _send_messages(self, df: pd.DataFrame, topic: str) -> None:
-        logger.info(f"Sending {len(df)} messages to Kafka topic:{topic}.")
-
-        messages = df.reset_index(drop=True).to_dict("records")
-        for idx, message in zip(df.index.values, messages):
-            self.__producer.send(topic, key=self.__key_generator(idx, message), value=self.__document_transformer(message))  # type: ignore
-
-        self.__producer.flush()  # type: ignore
-
-    @staticmethod
-    def _default_key_serializer(key: Optional[str]) -> Optional[bytes]:
-        if key:
-            return key.encode("utf-8")
-        return None
-
-    @staticmethod
-    def _default_value_serializer(value: Mapping) -> bytes:
-        return simplejson.dumps(value, ignore_nan=True).encode("utf-8")
-
-    def _read_from_kafka(self) -> Iterable[Mapping]:  # type: ignore
-        """Read messages from a Kafka Topic and convert them to separate dataframes.
-
-        Returns:
-            Multiple dataframes, one per message read from the Kafka topic of interest.
-        """
-        # TODO: Implement kafka reader
-
-

Subclasses

- -

Class variables

-
-
var options : MutableMapping[str, Any]
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configKafkaDataEnvironment
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/mixins/with_local.html b/docs/mixins/with_local.html deleted file mode 100644 index d4f8d1b..0000000 --- a/docs/mixins/with_local.html +++ /dev/null @@ -1,652 +0,0 @@ - - - - - - -dynamicio.mixins.with_local API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_local

-
-
-

This module provides mixins that are providing Local FS I/O support.

-
- -Expand source code - -
# pylint: disable=no-member, protected-access, too-few-public-methods
-
-"""This module provides mixins that are providing Local FS I/O support."""
-
-import glob
-import os
-from threading import Lock
-from typing import Any, MutableMapping
-
-import pandas as pd  # type: ignore
-from fastparquet import ParquetFile, write  # type: ignore
-from pyarrow.parquet import read_table, write_table  # type: ignore # pylint: disable=no-name-in-module
-
-from dynamicio.config.pydantic import DataframeSchema, LocalBatchDataEnvironment, LocalDataEnvironment
-from dynamicio.mixins import utils
-
-hdf_lock = Lock()
-
-
-class WithLocal:
-    """Handles local I/O operations."""
-
-    schema: DataframeSchema
-    sources_config: LocalDataEnvironment
-    options: MutableMapping[str, Any]
-
-    def _read_from_local(self) -> pd.DataFrame:
-        """Read a local file as a `DataFrame`.
-
-        The configuration object is expected to have two keys:
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using
-        "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = local_config.file_type
-
-        return getattr(self, f"_read_{file_type}_file")(file_path, self.schema, **self.options)
-
-    def _write_to_local(self, df: pd.DataFrame):
-        """Write a dataframe locally based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using
-        "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out.
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = local_config.file_type
-
-        getattr(self, f"_write_{file_type}_file")(df, file_path, **self.options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_hdf)
-    def _read_hdf_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a HDF file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be read sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            file_path: The path to the hdf file to be read.
-            options: The pandas `read_hdf` options.
-
-        Returns:
-            DataFrame: The dataframe read from the hdf file.
-        """
-        with hdf_lock:
-            df = pd.read_hdf(file_path, **options)
-
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    @utils.allow_options(pd.read_csv)
-    def _read_csv_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a CSV file as a DataFrame using `pd.read_csv`.
-
-        All `options` are passed directly to `pd.read_csv`.
-
-        Args:
-            file_path: The path to the csv file to be read.
-            options: The pandas `read_csv` options.
-
-        Returns:
-            DataFrame: The dataframe read from the csv file.
-        """
-        options["usecols"] = list(schema.column_names)
-        return pd.read_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_json)
-    def _read_json_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a json file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Args:
-            file_path:
-            options:
-
-        Returns:
-            DataFrame
-        """
-        df = pd.read_json(file_path, **options)
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    def _read_parquet_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a Parquet file as a DataFrame using `pd.read_parquet`.
-
-        All `options` are passed directly to `pd.read_parquet`.
-
-        Args:
-            file_path: The path to the parquet file to be read.
-            options: The pandas `read_parquet` options.
-
-        Returns:
-            DataFrame: The dataframe read from the parquet file.
-        """
-        options["columns"] = options.get("columns", list(schema.column_names))
-
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__read_with_fastparquet(file_path, **options)
-        return WithLocal.__read_with_pyarrow(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(read_table)])
-    def __read_with_pyarrow(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(ParquetFile)])
-    def __read_with_fastparquet(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_hdf), *["protocol"]])
-    def _write_hdf_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe to hdf using `df.to_hdf`.
-
-        All `options` are passed directly to `df.to_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be written sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: The pandas `to_hdf` options.
-
-                - The pandas `to_hdf` options, &;
-                - protocol: The pickle protocol to use for writing the hdf file out; a value <=5.
-        """
-        with utils.pickle_protocol(protocol=options.pop("protocol", None)), hdf_lock:
-            df.to_hdf(file_path, key="df", mode="w", **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_csv)
-    def _write_csv_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a CSV file using `df.to_csv`.
-
-        All `options` are passed directly to `df.to_csv`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a csv file.
-        """
-        df.to_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_json)
-    def _write_json_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a json file using `df.to_json`.
-
-        All `options` are passed directly to `df.to_json`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a json file.
-        """
-        df.to_json(file_path, **options)
-
-    @staticmethod
-    def _write_parquet_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a parquet file using `df.to_parquet`.
-
-        All `options` are passed directly to `df.to_parquet`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a parquet file.
-        """
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__write_with_fastparquet(df, file_path, **options)
-        return WithLocal.__write_with_pyarrow(df, file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write_table)])
-    def __write_with_pyarrow(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write)])
-    def __write_with_fastparquet(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-
-class WithLocalBatch(WithLocal):
-    """Responsible for batch reading local files."""
-
-    sources_config: LocalBatchDataEnvironment  # type: ignore
-
-    def _read_from_local_batch(self) -> pd.DataFrame:
-        """Reads a set of files for a specified file type, concatenates them and returns a dataframe.
-
-        Returns:
-            A concatenated dataframe composed of all files read through local_batch.
-        """
-        local_batch_config = self.sources_config.local
-
-        file_type = local_batch_config.file_type
-        filtering_file_type = file_type.value
-        if filtering_file_type == "hdf":
-            filtering_file_type = "h5"
-
-        # Determine if the path is dynamic or static
-        if local_batch_config.dynamic_file_path:
-            # Dynamic path: use it directly
-            file_path = utils.resolve_template(local_batch_config.dynamic_file_path, self.options)
-            files = glob.glob(file_path, recursive=True)
-        elif local_batch_config.path_prefix:
-            # Static path: append the file type
-            file_path = utils.resolve_template(local_batch_config.path_prefix, self.options)
-            files = glob.glob(os.path.join(file_path, f"*.{filtering_file_type}"), recursive=True)
-        else:
-            raise ValueError("Either path_prefix or dynamic_path must be provided in local_batch_config")
-
-        dfs_to_concatenate = []
-        for file in files:
-            file_to_load = os.path.join(file_path, file)
-            dfs_to_concatenate.append(getattr(self, f"_read_{file_type}_file")(file_to_load, self.schema, **self.options))  # type: ignore
-
-        return pd.concat(dfs_to_concatenate).reset_index(drop=True)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class WithLocal -
-
-

Handles local I/O operations.

-
- -Expand source code - -
class WithLocal:
-    """Handles local I/O operations."""
-
-    schema: DataframeSchema
-    sources_config: LocalDataEnvironment
-    options: MutableMapping[str, Any]
-
-    def _read_from_local(self) -> pd.DataFrame:
-        """Read a local file as a `DataFrame`.
-
-        The configuration object is expected to have two keys:
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using
-        "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = local_config.file_type
-
-        return getattr(self, f"_read_{file_type}_file")(file_path, self.schema, **self.options)
-
-    def _write_to_local(self, df: pd.DataFrame):
-        """Write a dataframe locally based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using
-        "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out.
-        """
-        local_config = self.sources_config.local
-        file_path = utils.resolve_template(local_config.file_path, self.options)
-        file_type = local_config.file_type
-
-        getattr(self, f"_write_{file_type}_file")(df, file_path, **self.options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_hdf)
-    def _read_hdf_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a HDF file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be read sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            file_path: The path to the hdf file to be read.
-            options: The pandas `read_hdf` options.
-
-        Returns:
-            DataFrame: The dataframe read from the hdf file.
-        """
-        with hdf_lock:
-            df = pd.read_hdf(file_path, **options)
-
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    @utils.allow_options(pd.read_csv)
-    def _read_csv_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a CSV file as a DataFrame using `pd.read_csv`.
-
-        All `options` are passed directly to `pd.read_csv`.
-
-        Args:
-            file_path: The path to the csv file to be read.
-            options: The pandas `read_csv` options.
-
-        Returns:
-            DataFrame: The dataframe read from the csv file.
-        """
-        options["usecols"] = list(schema.column_names)
-        return pd.read_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.read_json)
-    def _read_json_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a json file as a DataFrame using `pd.read_hdf`.
-
-        All `options` are passed directly to `pd.read_hdf`.
-
-        Args:
-            file_path:
-            options:
-
-        Returns:
-            DataFrame
-        """
-        df = pd.read_json(file_path, **options)
-        columns = [column for column in df.columns.to_list() if column in schema.column_names]
-        df = df[columns]
-        return df
-
-    @staticmethod
-    def _read_parquet_file(file_path: str, schema: DataframeSchema, **options: Any) -> pd.DataFrame:
-        """Read a Parquet file as a DataFrame using `pd.read_parquet`.
-
-        All `options` are passed directly to `pd.read_parquet`.
-
-        Args:
-            file_path: The path to the parquet file to be read.
-            options: The pandas `read_parquet` options.
-
-        Returns:
-            DataFrame: The dataframe read from the parquet file.
-        """
-        options["columns"] = options.get("columns", list(schema.column_names))
-
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__read_with_fastparquet(file_path, **options)
-        return WithLocal.__read_with_pyarrow(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(read_table)])
-    def __read_with_pyarrow(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.read_parquet), *utils.args_of(ParquetFile)])
-    def __read_with_fastparquet(cls, file_path: str, **options: Any) -> pd.DataFrame:
-        return pd.read_parquet(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_hdf), *["protocol"]])
-    def _write_hdf_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe to hdf using `df.to_hdf`.
-
-        All `options` are passed directly to `df.to_hdf`.
-
-        Caveats: As HDFs are not thread-safe, we use a Lock on this operation. This, practically means
-            that when used with asyncio through `async_read()` HDF files will be written sequentially.
-            For more information see: https://pandas.pydata.org/pandas-docs/dev/user_guide/io.html#caveats
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: The pandas `to_hdf` options.
-
-                - The pandas `to_hdf` options, &;
-                - protocol: The pickle protocol to use for writing the hdf file out; a value <=5.
-        """
-        with utils.pickle_protocol(protocol=options.pop("protocol", None)), hdf_lock:
-            df.to_hdf(file_path, key="df", mode="w", **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_csv)
-    def _write_csv_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a CSV file using `df.to_csv`.
-
-        All `options` are passed directly to `df.to_csv`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a csv file.
-        """
-        df.to_csv(file_path, **options)
-
-    @staticmethod
-    @utils.allow_options(pd.DataFrame.to_json)
-    def _write_json_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a json file using `df.to_json`.
-
-        All `options` are passed directly to `df.to_json`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a json file.
-        """
-        df.to_json(file_path, **options)
-
-    @staticmethod
-    def _write_parquet_file(df: pd.DataFrame, file_path: str, **options: Any):
-        """Write a dataframe as a parquet file using `df.to_parquet`.
-
-        All `options` are passed directly to `df.to_parquet`.
-
-        Args:
-            df: A dataframe write out.
-            file_path: The location where the file needs to be written.
-            options: Options relative to writing a parquet file.
-        """
-        if options.get("engine") == "fastparquet":
-            return WithLocal.__write_with_fastparquet(df, file_path, **options)
-        return WithLocal.__write_with_pyarrow(df, file_path, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write_table)])
-    def __write_with_pyarrow(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-    @classmethod
-    @utils.allow_options([*utils.args_of(pd.DataFrame.to_parquet), *utils.args_of(write)])
-    def __write_with_fastparquet(cls, df: pd.DataFrame, filepath: str, **options: Any):
-        return df.to_parquet(filepath, **options)
-
-

Subclasses

- -

Class variables

-
-
var options : MutableMapping[str, Any]
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configLocalDataEnvironment
-
-
-
-
-
-
-class WithLocalBatch -
-
-

Responsible for batch reading local files.

-
- -Expand source code - -
class WithLocalBatch(WithLocal):
-    """Responsible for batch reading local files."""
-
-    sources_config: LocalBatchDataEnvironment  # type: ignore
-
-    def _read_from_local_batch(self) -> pd.DataFrame:
-        """Reads a set of files for a specified file type, concatenates them and returns a dataframe.
-
-        Returns:
-            A concatenated dataframe composed of all files read through local_batch.
-        """
-        local_batch_config = self.sources_config.local
-
-        file_type = local_batch_config.file_type
-        filtering_file_type = file_type.value
-        if filtering_file_type == "hdf":
-            filtering_file_type = "h5"
-
-        # Determine if the path is dynamic or static
-        if local_batch_config.dynamic_file_path:
-            # Dynamic path: use it directly
-            file_path = utils.resolve_template(local_batch_config.dynamic_file_path, self.options)
-            files = glob.glob(file_path, recursive=True)
-        elif local_batch_config.path_prefix:
-            # Static path: append the file type
-            file_path = utils.resolve_template(local_batch_config.path_prefix, self.options)
-            files = glob.glob(os.path.join(file_path, f"*.{filtering_file_type}"), recursive=True)
-        else:
-            raise ValueError("Either path_prefix or dynamic_path must be provided in local_batch_config")
-
-        dfs_to_concatenate = []
-        for file in files:
-            file_to_load = os.path.join(file_path, file)
-            dfs_to_concatenate.append(getattr(self, f"_read_{file_type}_file")(file_to_load, self.schema, **self.options))  # type: ignore
-
-        return pd.concat(dfs_to_concatenate).reset_index(drop=True)
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var sources_configLocalBatchDataEnvironment
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/mixins/with_postgres.html b/docs/mixins/with_postgres.html deleted file mode 100644 index 28dcec5..0000000 --- a/docs/mixins/with_postgres.html +++ /dev/null @@ -1,489 +0,0 @@ - - - - - - -dynamicio.mixins.with_postgres API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_postgres

-
-
-

This module provides mixins that are providing Postgres I/O support.

-
- -Expand source code - -
# pylint: disable=no-member, protected-access, too-few-public-methods
-
-"""This module provides mixins that are providing Postgres I/O support."""
-
-import csv
-import tempfile
-from contextlib import contextmanager
-from typing import Any, Dict, Generator, MutableMapping, Union
-
-import pandas as pd  # type: ignore
-from magic_logger import logger
-from sqlalchemy import BigInteger, Boolean, Column, create_engine, Date, DateTime, Float, Integer, String  # type: ignore
-from sqlalchemy.ext.declarative import declarative_base  # type: ignore
-from sqlalchemy.orm import Query  # type: ignore
-from sqlalchemy.orm.decl_api import DeclarativeMeta  # type: ignore
-from sqlalchemy.orm.session import Session as SqlAlchemySession  # type: ignore
-from sqlalchemy.orm.session import sessionmaker  # type: ignore
-
-from dynamicio.config.pydantic import DataframeSchema, PostgresDataEnvironment
-from dynamicio.mixins import utils
-
-Session = sessionmaker(autoflush=True)
-
-Base = declarative_base()
-_type_lookup = {
-    "bool": Boolean,
-    "boolean": Boolean,
-    "object": String(64),
-    "int64": Integer,
-    "float64": Float,
-    "int": Integer,
-    "date": Date,
-    "datetime64[ns]": DateTime,
-    "bigint": BigInteger,
-}
-
-
-@contextmanager
-def session_for(connection_string: str) -> Generator[SqlAlchemySession, None, None]:
-    """Connect to a database using `connection_string` and returns an active session to that connection.
-
-    Args:
-        connection_string:
-
-    Yields:
-        Active session
-    """
-    engine = create_engine(connection_string)
-    session = Session(bind=engine)
-
-    try:
-        yield session
-    finally:
-        session.close()  # pylint: disable=no-member
-
-
-class WithPostgres:
-    """Handles I/O operations for Postgres.
-
-    Args:
-       - options:
-           - `truncate_and_append: bool`: If set to `True`, truncates the table and then appends the new rows. Otherwise, it drops the table and recreates it with the new rows.
-    """
-
-    sources_config: PostgresDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-
-    def _read_from_postgres(self) -> pd.DataFrame:
-        """Read data from postgres as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `db_user`
-            - `db_password`
-            - `db_host`
-            - `db_port`
-            - `db_name`
-
-        Returns:
-            DataFrame
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        sql_query = self.options.pop("sql_query", None)
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        query = Query(self._get_table_columns(model))
-        if sql_query:
-            query = sql_query
-
-        logger.info(f"[postgres] Started downloading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            return self._read_database(session, query, **self.options)
-
-    @staticmethod
-    def _generate_model_from_schema(schema: DataframeSchema) -> DeclarativeMeta:
-        json_cls_schema: Dict[str, Any] = {"tablename": schema.name, "columns": []}
-
-        for col in schema.columns.values():
-            sql_type = _type_lookup.get(col.data_type)
-            if sql_type:
-                json_cls_schema["columns"].append({"name": col.name, "type": sql_type})
-
-        class_name = "".join(word.capitalize() or "_" for word in schema.name.split("_")) + "Model"
-
-        class_dict = {"clsname": class_name, "__tablename__": schema.name, "__table_args__": {"extend_existing": True}}
-        class_dict.update({column["name"]: Column(column["type"], primary_key=True) if idx == 0 else Column(column["type"]) for idx, column in enumerate(json_cls_schema["columns"])})
-
-        generated_model = type(class_name, (Base,), class_dict)
-        return generated_model
-
-    @staticmethod
-    def _get_table_columns(model):
-        tables_colums = []
-        if model:
-            for col in list(model.__table__.columns):
-                tables_colums.append(getattr(model, col.name))
-        return tables_colums
-
-    @staticmethod
-    @utils.allow_options(pd.read_sql)
-    def _read_database(session: SqlAlchemySession, query: Union[str, Query], **options: Any) -> pd.DataFrame:
-        """Run `query` against active `session` and returns the result as a `DataFrame`.
-
-        Args:
-            session: Active session
-            query: If a `Query` object is given, it should be unbound. If a `str` is given, the
-                value is used as-is.
-
-        Returns:
-            DataFrame
-        """
-        if isinstance(query, Query):
-            query = query.with_session(session).statement
-        return pd.read_sql(sql=query, con=session.get_bind(), **options)
-
-    def _write_to_postgres(self, df: pd.DataFrame):
-        """Write a dataframe to postgres based on the {file_type} of the config_io configuration.
-
-        Args:
-            df: The dataframe to be written
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        is_truncate_and_append = self.options.get("truncate_and_append", False)
-
-        logger.info(f"[postgres] Started downloading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            self._write_to_database(session, model.__tablename__, df, is_truncate_and_append)  # type: ignore
-
-    @staticmethod
-    def _write_to_database(session: SqlAlchemySession, table_name: str, df: pd.DataFrame, is_truncate_and_append: bool):
-        """Write a dataframe to any database provided a session with a data model and a table name.
-
-        Args:
-            session: Generated from a data model and a table name
-            table_name: The name of the table to read from a DB
-            df: The dataframe to be written out
-            is_truncate_and_append: Supply to truncate the table and append new rows to it; otherwise, delete and replace
-        """
-        if is_truncate_and_append:
-            session.execute(f"TRUNCATE TABLE {table_name};")
-
-            # Below is a speedup hack in place of `df.to_csv` with the multipart option. As of today, even with
-            # `method="multi"`, uploading to Postgres is painfully slow. Hence, we're resorting to dumping the file as
-            # csv and using Postgres's CSV import function.
-            # https://stackoverflow.com/questions/2987433/how-to-import-csv-file-data-into-a-postgresql-table
-            with tempfile.NamedTemporaryFile(mode="r+") as temp_file:
-                df.to_csv(temp_file, index=False, header=False, sep="\t", doublequote=False, escapechar="\\", quoting=csv.QUOTE_NONE)
-                temp_file.flush()
-                temp_file.seek(0)
-
-                cur = session.connection().connection.cursor()
-                cur.copy_from(temp_file, table_name, columns=df.columns, null="")
-        else:
-            df.to_sql(name=table_name, con=session.get_bind(), if_exists="replace", index=False)
-
-        session.commit()
-
-
-
-
-
-
-
-

Functions

-
-
-def session_for(connection_string: str) ‑> Generator[sqlalchemy.orm.session.Session, None, None] -
-
-

Connect to a database using connection_string and returns an active session to that connection.

-

Args

-

connection_string:

-

Yields

-

Active session

-
- -Expand source code - -
@contextmanager
-def session_for(connection_string: str) -> Generator[SqlAlchemySession, None, None]:
-    """Connect to a database using `connection_string` and returns an active session to that connection.
-
-    Args:
-        connection_string:
-
-    Yields:
-        Active session
-    """
-    engine = create_engine(connection_string)
-    session = Session(bind=engine)
-
-    try:
-        yield session
-    finally:
-        session.close()  # pylint: disable=no-member
-
-
-
-
-
-

Classes

-
-
-class WithPostgres -
-
-

Handles I/O operations for Postgres.

-

Args

-
    -
  • options:
      -
    • truncate_and_append: bool: If set to True, truncates the table and then appends the new rows. Otherwise, it drops the table and recreates it with the new rows.
    • -
    -
  • -
-
- -Expand source code - -
class WithPostgres:
-    """Handles I/O operations for Postgres.
-
-    Args:
-       - options:
-           - `truncate_and_append: bool`: If set to `True`, truncates the table and then appends the new rows. Otherwise, it drops the table and recreates it with the new rows.
-    """
-
-    sources_config: PostgresDataEnvironment
-    schema: DataframeSchema
-    options: MutableMapping[str, Any]
-
-    def _read_from_postgres(self) -> pd.DataFrame:
-        """Read data from postgres as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `db_user`
-            - `db_password`
-            - `db_host`
-            - `db_port`
-            - `db_name`
-
-        Returns:
-            DataFrame
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        sql_query = self.options.pop("sql_query", None)
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        query = Query(self._get_table_columns(model))
-        if sql_query:
-            query = sql_query
-
-        logger.info(f"[postgres] Started downloading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            return self._read_database(session, query, **self.options)
-
-    @staticmethod
-    def _generate_model_from_schema(schema: DataframeSchema) -> DeclarativeMeta:
-        json_cls_schema: Dict[str, Any] = {"tablename": schema.name, "columns": []}
-
-        for col in schema.columns.values():
-            sql_type = _type_lookup.get(col.data_type)
-            if sql_type:
-                json_cls_schema["columns"].append({"name": col.name, "type": sql_type})
-
-        class_name = "".join(word.capitalize() or "_" for word in schema.name.split("_")) + "Model"
-
-        class_dict = {"clsname": class_name, "__tablename__": schema.name, "__table_args__": {"extend_existing": True}}
-        class_dict.update({column["name"]: Column(column["type"], primary_key=True) if idx == 0 else Column(column["type"]) for idx, column in enumerate(json_cls_schema["columns"])})
-
-        generated_model = type(class_name, (Base,), class_dict)
-        return generated_model
-
-    @staticmethod
-    def _get_table_columns(model):
-        tables_colums = []
-        if model:
-            for col in list(model.__table__.columns):
-                tables_colums.append(getattr(model, col.name))
-        return tables_colums
-
-    @staticmethod
-    @utils.allow_options(pd.read_sql)
-    def _read_database(session: SqlAlchemySession, query: Union[str, Query], **options: Any) -> pd.DataFrame:
-        """Run `query` against active `session` and returns the result as a `DataFrame`.
-
-        Args:
-            session: Active session
-            query: If a `Query` object is given, it should be unbound. If a `str` is given, the
-                value is used as-is.
-
-        Returns:
-            DataFrame
-        """
-        if isinstance(query, Query):
-            query = query.with_session(session).statement
-        return pd.read_sql(sql=query, con=session.get_bind(), **options)
-
-    def _write_to_postgres(self, df: pd.DataFrame):
-        """Write a dataframe to postgres based on the {file_type} of the config_io configuration.
-
-        Args:
-            df: The dataframe to be written
-        """
-        postgres_config = self.sources_config.postgres
-        db_user = postgres_config.db_user
-        db_password = postgres_config.db_password
-        db_host = postgres_config.db_host
-        db_port = postgres_config.db_port
-        db_name = postgres_config.db_name
-
-        connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
-
-        assert self.sources_config.dynamicio_schema is not None, "The schema must be specified for SQL tables"
-        model = self._generate_model_from_schema(self.sources_config.dynamicio_schema)
-
-        is_truncate_and_append = self.options.get("truncate_and_append", False)
-
-        logger.info(f"[postgres] Started downloading table: {self.sources_config.dynamicio_schema.name} from: {db_host}:{db_name}")
-        with session_for(connection_string) as session:
-            self._write_to_database(session, model.__tablename__, df, is_truncate_and_append)  # type: ignore
-
-    @staticmethod
-    def _write_to_database(session: SqlAlchemySession, table_name: str, df: pd.DataFrame, is_truncate_and_append: bool):
-        """Write a dataframe to any database provided a session with a data model and a table name.
-
-        Args:
-            session: Generated from a data model and a table name
-            table_name: The name of the table to read from a DB
-            df: The dataframe to be written out
-            is_truncate_and_append: Supply to truncate the table and append new rows to it; otherwise, delete and replace
-        """
-        if is_truncate_and_append:
-            session.execute(f"TRUNCATE TABLE {table_name};")
-
-            # Below is a speedup hack in place of `df.to_csv` with the multipart option. As of today, even with
-            # `method="multi"`, uploading to Postgres is painfully slow. Hence, we're resorting to dumping the file as
-            # csv and using Postgres's CSV import function.
-            # https://stackoverflow.com/questions/2987433/how-to-import-csv-file-data-into-a-postgresql-table
-            with tempfile.NamedTemporaryFile(mode="r+") as temp_file:
-                df.to_csv(temp_file, index=False, header=False, sep="\t", doublequote=False, escapechar="\\", quoting=csv.QUOTE_NONE)
-                temp_file.flush()
-                temp_file.seek(0)
-
-                cur = session.connection().connection.cursor()
-                cur.copy_from(temp_file, table_name, columns=df.columns, null="")
-        else:
-            df.to_sql(name=table_name, con=session.get_bind(), if_exists="replace", index=False)
-
-        session.commit()
-
-

Subclasses

- -

Class variables

-
-
var options : MutableMapping[str, Any]
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configPostgresDataEnvironment
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/mixins/with_s3.html b/docs/mixins/with_s3.html deleted file mode 100644 index 152e1bb..0000000 --- a/docs/mixins/with_s3.html +++ /dev/null @@ -1,1127 +0,0 @@ - - - - - - -dynamicio.mixins.with_s3 API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.mixins.with_s3

-
-
-

This module provides mixins that are providing S3 I/O support.

-
- -Expand source code - -
# pylint: disable=no-member, protected-access, too-few-public-methods
-
-"""This module provides mixins that are providing S3 I/O support."""
-
-import dataclasses
-import io
-import os
-import tempfile
-import urllib.parse
-import uuid
-from contextlib import contextmanager
-from typing import IO, Generator, List, Optional, Union  # noqa: I101
-
-import boto3  # type: ignore
-import pandas as pd
-import s3transfer.futures  # type: ignore
-import tables  # type: ignore
-from awscli.clidriver import create_clidriver  # type: ignore
-from magic_logger import logger
-from pandas import DataFrame, Series
-
-from dynamicio.config.pydantic import DataframeSchema, S3DataEnvironment, S3PathPrefixEnvironment
-from dynamicio.mixins import utils, with_local
-
-
-class InMemStore(pd.io.pytables.HDFStore):
-    """A subclass of pandas HDFStore that does not manage the pytables File object."""
-
-    _in_mem_table = None
-
-    def __init__(self, path: str, table: tables.File, mode: str = "r"):
-        """Create a new HDFStore object."""
-        self._in_mem_table = table
-        super().__init__(path=path, mode=mode)
-
-    def open(self, *_args, **_kwargs):
-        """Open the in-memory table."""
-        pd.io.pytables._tables()
-        self._handle = self._in_mem_table
-
-    def close(self, *_args, **_kwargs):
-        """Close the in-memory table."""
-
-    @property
-    def is_open(self):
-        """Check if the in-memory table is open."""
-        return self._handle is not None
-
-
-class HdfIO:
-    """Class providing stream support for HDF tables."""
-
-    @contextmanager
-    def create_file(self, label: str, mode: str, data: Optional[bytes] = None) -> Generator[tables.File, None, None]:
-        """Create an in-memory pytables table."""
-        extra_kw = {}
-        if data:
-            extra_kw["driver_core_image"] = data
-        file_handle = tables.File(f"{label}_{uuid.uuid4()}.h5", mode, title=label, root_uep="/", filters=None, driver="H5FD_CORE", driver_core_backing_store=0, **extra_kw)
-        try:
-            yield file_handle
-        finally:
-            file_handle.close()
-
-    def load(self, fobj: IO[bytes], label: str = "unknown_file.h5") -> Union[DataFrame, Series]:
-        """Load the dataframe from an file-like object."""
-        with self.create_file(label, mode="r", data=fobj.read()) as file_handle:
-            return pd.read_hdf(InMemStore(label, file_handle))
-
-    def save(self, df: DataFrame, fobj: IO[bytes], label: str = "unknown_file.h5", options: Optional[dict] = None):
-        """Load the dataframe to a file-like object."""
-        if not options:
-            options = {}
-        with self.create_file(label, mode="w", data=fobj.read()) as file_handle:
-            store = InMemStore(path=label, table=file_handle, mode="w")
-            store.put(key="df", value=df, **options)
-            fobj.write(file_handle.get_file_image())
-
-
-def awscli_runner(*cmd: str):
-    """Runs the awscli command provided.
-
-    Args:
-        *cmd: A list of args used in the command.
-
-    Raises:
-        A runtime error exception is raised if download fails.
-
-    Example:
-
-        >>> awscli_runner("s3", "sync", "s3://mock-bucket/mock-key", ".")
-    """
-    # Run
-    exit_code = create_clidriver().main(cmd)
-
-    if exit_code > 0:
-        raise RuntimeError(f"AWS CLI exited with code {exit_code}")
-
-
-@dataclasses.dataclass
-class S3TransferHandle:
-    """A dataclass used to track an ongoing data download from the s3."""
-
-    s3_object: object  # boto3.resource('s3').ObjectSummary
-    fobj: IO[bytes]  # file-like object the data is being downloaded to
-    done_future: s3transfer.futures.BaseTransferFuture
-
-
-class WithS3PathPrefix(with_local.WithLocal):
-    """Handles I/O operations for AWS S3; implements read operations only.
-
-    This mixin assumes that the directories it reads from will only contain a single file-type.
-    """
-
-    sources_config: S3PathPrefixEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_resource = boto3.resource("s3")
-    boto3_client = boto3.client("s3")
-
-    def _write_to_s3_path_prefix(self, df: pd.DataFrame):
-        """Write a DataFrame to an S3 path prefix.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `path_prefix`
-            - `file_type`
-
-        Args:
-            df (pd.DataFrame): the DataFrame to be written to S3
-
-        Raises:
-            ValueError: In case `path_prefix` is missing from config
-            ValueError: In case the `partition_cols` arg is missing while trying to write a parquet file
-        """
-        s3_config = self.sources_config.s3
-
-        file_type = s3_config.file_type
-        if file_type != "parquet":
-            raise ValueError(f"File type not supported: {file_type}, only parquet files can be written to an S3 key")
-        if "partition_cols" not in self.options:
-            raise ValueError("`partition_cols` is required as an option to write partitioned parquet files to S3")
-
-        bucket = s3_config.bucket
-        path_prefix = s3_config.path_prefix
-        full_path_prefix = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            self._write_parquet_file(df, temp_dir, **self.options)
-            awscli_runner(
-                "s3",
-                "sync",
-                temp_dir,
-                full_path_prefix,
-                "--acl",
-                "bucket-owner-full-control",
-                "--only-show-errors",
-                "--exact-timestamps",
-            )
-
-    def _read_from_s3_path_prefix(self) -> pd.DataFrame:
-        """Read all files under a path prefix from an S3 bucket as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `path_prefix`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using
-        "_read_{file_type}_path_prefix".
-
-        Returns:
-            DataFrame
-        """
-        s3_config = self.sources_config.s3
-        file_type = s3_config.file_type
-        if file_type not in {"parquet", "csv", "hdf", "json"}:
-            raise ValueError(f"File type not supported: {file_type}")
-
-        bucket = s3_config.bucket
-        path_prefix = s3_config.path_prefix
-        full_path_prefix = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        # The `no_disk_space` option should be used only when reading a subset of columns from S3
-        if self.options.pop("no_disk_space", False):
-            if file_type == "parquet":
-                return self._read_parquet_file(full_path_prefix, self.schema, **self.options)
-            if file_type == "hdf":
-                dfs: List[DataFrame] = []
-                for fobj in self._iter_s3_files(full_path_prefix, file_ext=".h5", max_memory_use=1024**3):  # 1 gib
-                    dfs.append(HdfIO().load(fobj))
-                df = pd.concat(dfs, ignore_index=True)
-                columns = [column for column in df.columns.to_list() if column in self.schema.columns.keys()]
-                return df[columns]
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            # aws-cli is shown to be up to 6 times faster when downloading the complete dataset from S3 than using the boto3
-            # client or pandas directly. This is because aws-cli uses the parallel downloader, which is much faster than the
-            # boto3 client.
-            awscli_runner(
-                "s3",
-                "sync",
-                full_path_prefix,
-                temp_dir,
-                "--acl",
-                "bucket-owner-full-control",
-                "--only-show-errors",
-                "--exact-timestamps",
-            )
-
-            dfs: List[DataFrame] = []
-            for file in os.listdir(temp_dir):
-                df = getattr(self, f"_read_{file_type}_file")(os.path.join(temp_dir, file), self.schema, **self.options)  # type: ignore
-                if len(df) > 0:
-                    dfs.append(df)
-
-            return pd.concat(dfs, ignore_index=True)
-
-    def _iter_s3_files(self, s3_prefix: str, file_ext: Optional[str] = None, max_memory_use: int = -1) -> Generator[IO[bytes], None, None]:  # pylint: disable=too-many-locals
-        """Download sways of S3 objects.
-
-        Args:
-            s3_prefix: s3 url to fetch objects with
-            file_ext: extension of s3 objects to allow through
-            max_memory_use: The approximate number of bytes to allocate on each yield of Generator
-        """
-        parsed_url = urllib.parse.urlparse(s3_prefix)
-        assert parsed_url.scheme == "s3", f"{s3_prefix!r} should be an s3 url"
-        bucket_name = parsed_url.netloc
-        file_prefix = f"{parsed_url.path.strip('/')}/"
-        s3_objects_to_fetch = []
-        # Collect objects to be loaded
-        for s3_object in self.boto3_resource.Bucket(bucket_name).objects.filter(Prefix=file_prefix):
-            good_object = (not file_ext) or (s3_object.key.endswith(file_ext))
-            if good_object:
-                s3_objects_to_fetch.append(s3_object)
-
-        if max_memory_use < 0:
-            # Unlimited memory use - fetch ALL
-            max_memory_use = sum(s3_obj.size for s3_obj in s3_objects_to_fetch) * 2
-        transfer_config = boto3.s3.transfer.TransferConfig(max_concurrency=20)
-        while s3_objects_to_fetch:
-            mem_use_left = max_memory_use
-            handles = []
-            with boto3.s3.transfer.create_transfer_manager(self.boto3_client, transfer_config) as transfer_manager:
-                while mem_use_left > 0 and s3_objects_to_fetch:
-                    s3_object = s3_objects_to_fetch.pop()
-                    fobj = io.BytesIO()
-                    future = transfer_manager.download(bucket_name, s3_object.key, fobj)
-                    handles.append(S3TransferHandle(s3_object, fobj, future))
-                    mem_use_left -= s3_object.size
-                # Leaving the `transfer_manager` context implicitly waits for all downloads to complete
-            # Rewind and yield all fobjs
-            for handle in handles:
-                handle.fobj.seek(0)
-                yield handle.fobj
-
-
-class WithS3File(with_local.WithLocal):
-    """Handles I/O operations for AWS S3.
-
-    All files are persisted to disk first using boto3 as this has proven to be faster than reading them into memory.
-    Note that reading things into memory is available for csv, json and parquet types only. Unfortunately, until support
-    for generic buffer is added to read_hdf, we need to download and persists the file to disk first anyway.
-
-    Options:
-        no_disk_space: If `True`, then s3fs + fsspec will be used to read data directly into memory.
-    """
-
-    sources_config: S3DataEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_client = boto3.client("s3")
-
-    @contextmanager
-    def _s3_named_file_reader(self, s3_bucket: str, s3_key: str) -> Generator:
-        """Contextmanager to abstract reading different file types in S3.
-
-        This implementation saves the downloaded data to a temporary file.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        with tempfile.NamedTemporaryFile("wb") as target_file:
-            # Download the file from S3
-            self.boto3_client.download_fileobj(s3_bucket, s3_key, target_file)
-            # Yield local file path to body of `with` statement
-            target_file.flush()
-            yield target_file
-
-    @contextmanager
-    def _s3_reader(self, s3_bucket: str, s3_key: str) -> Generator[io.BytesIO, None, None]:
-        """Contextmanager to abstract reading different file types in S3.
-
-         This implementation only retains data in-memory, avoiding creating any temp files.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        fobj = io.BytesIO()
-        # Download the file from S3
-        self.boto3_client.download_fileobj(s3_bucket, s3_key, fobj)
-        # Yield the buffer
-        fobj.seek(0)
-        yield fobj
-
-    @contextmanager
-    def _s3_writer(self, s3_bucket: str, s3_key: str) -> Generator[IO[bytes], None, None]:
-        """Contextmanager to abstract loading different file types to S3.
-
-        Args:
-            s3_bucket: The S3 bucket to upload the file to.
-            s3_key: The file-path where the target file should be uploaded to.
-
-        Returns:
-            The local file path where to actually write the file, to be read and uploaded by boto3.client.
-        """
-        fobj = io.BytesIO()
-        yield fobj
-        fobj.seek(0)
-        self.boto3_client.upload_fileobj(fobj, s3_bucket, s3_key, ExtraArgs={"ACL": "bucket-owner-full-control"})
-
-    def _read_from_s3_file(self) -> pd.DataFrame:
-        """Read a file from an S3 bucket as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        s3_config = self.sources_config.s3
-        file_type = s3_config.file_type
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        bucket = s3_config.bucket
-
-        logger.info(f"[s3] Started downloading: s3://{s3_config.bucket}/{file_path}")
-        if self.options.pop("no_disk_space", None):
-            no_disk_space_rv = None
-            if file_type in ["csv", "json", "parquet"]:
-                no_disk_space_rv = getattr(self, f"_read_{file_type}_file")(f"s3://{s3_config.bucket}/{file_path}", self.schema, **self.options)  # type: ignore
-            elif file_type == "hdf":
-                with self._s3_reader(s3_bucket=bucket, s3_key=file_path) as fobj:  # type: ignore
-                    no_disk_space_rv = HdfIO().load(fobj)  # type: ignore
-            else:
-                raise NotImplementedError(f"Unsupported file type {file_type!r}.")
-            if no_disk_space_rv is not None:
-                return no_disk_space_rv
-        with self._s3_named_file_reader(s3_bucket=bucket, s3_key=file_path) as target_file:  # type: ignore
-            return getattr(self, f"_read_{file_type}_file")(target_file.name, self.schema, **self.options)  # type: ignore
-
-    def _write_to_s3_file(self, df: pd.DataFrame):
-        """Write a dataframe to s3 based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out
-        """
-        s3_config = self.sources_config.s3
-        bucket = s3_config.bucket
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        file_type = s3_config.file_type
-
-        logger.info(f"[s3] Started uploading: s3://{bucket}/{file_path}")
-        if file_type in ["csv", "json", "parquet"]:
-            getattr(self, f"_write_{file_type}_file")(df, f"s3://{bucket}/{file_path}", **self.options)  # type: ignore
-        elif file_type == "hdf":
-            hdf_options = dict(self.options)
-            pickle_protocol = hdf_options.pop("pickle_protocol", None)
-            with self._s3_writer(s3_bucket=s3_config.bucket, s3_key=file_path) as target_file, utils.pickle_protocol(protocol=pickle_protocol):
-                HdfIO().save(df, target_file, hdf_options)  # type: ignore
-        else:
-            raise ValueError(f"File type: {file_type} not supported!")
-        logger.info(f"[s3] Finished uploading: s3://{bucket}/{file_path}")
-
-
-
-
-
-
-
-

Functions

-
-
-def awscli_runner(*cmd: str) -
-
-

Runs the awscli command provided.

-

Args

-
-
*cmd
-
A list of args used in the command.
-
-

Raises

-

A runtime error exception is raised if download fails.

-

Example

-
>>> awscli_runner("s3", "sync", "s3://mock-bucket/mock-key", ".")
-
-
- -Expand source code - -
def awscli_runner(*cmd: str):
-    """Runs the awscli command provided.
-
-    Args:
-        *cmd: A list of args used in the command.
-
-    Raises:
-        A runtime error exception is raised if download fails.
-
-    Example:
-
-        >>> awscli_runner("s3", "sync", "s3://mock-bucket/mock-key", ".")
-    """
-    # Run
-    exit_code = create_clidriver().main(cmd)
-
-    if exit_code > 0:
-        raise RuntimeError(f"AWS CLI exited with code {exit_code}")
-
-
-
-
-
-

Classes

-
-
-class HdfIO -
-
-

Class providing stream support for HDF tables.

-
- -Expand source code - -
class HdfIO:
-    """Class providing stream support for HDF tables."""
-
-    @contextmanager
-    def create_file(self, label: str, mode: str, data: Optional[bytes] = None) -> Generator[tables.File, None, None]:
-        """Create an in-memory pytables table."""
-        extra_kw = {}
-        if data:
-            extra_kw["driver_core_image"] = data
-        file_handle = tables.File(f"{label}_{uuid.uuid4()}.h5", mode, title=label, root_uep="/", filters=None, driver="H5FD_CORE", driver_core_backing_store=0, **extra_kw)
-        try:
-            yield file_handle
-        finally:
-            file_handle.close()
-
-    def load(self, fobj: IO[bytes], label: str = "unknown_file.h5") -> Union[DataFrame, Series]:
-        """Load the dataframe from an file-like object."""
-        with self.create_file(label, mode="r", data=fobj.read()) as file_handle:
-            return pd.read_hdf(InMemStore(label, file_handle))
-
-    def save(self, df: DataFrame, fobj: IO[bytes], label: str = "unknown_file.h5", options: Optional[dict] = None):
-        """Load the dataframe to a file-like object."""
-        if not options:
-            options = {}
-        with self.create_file(label, mode="w", data=fobj.read()) as file_handle:
-            store = InMemStore(path=label, table=file_handle, mode="w")
-            store.put(key="df", value=df, **options)
-            fobj.write(file_handle.get_file_image())
-
-

Methods

-
-
-def create_file(self, label: str, mode: str, data: Optional[bytes] = None) ‑> Generator[tables.file.File, None, None] -
-
-

Create an in-memory pytables table.

-
- -Expand source code - -
@contextmanager
-def create_file(self, label: str, mode: str, data: Optional[bytes] = None) -> Generator[tables.File, None, None]:
-    """Create an in-memory pytables table."""
-    extra_kw = {}
-    if data:
-        extra_kw["driver_core_image"] = data
-    file_handle = tables.File(f"{label}_{uuid.uuid4()}.h5", mode, title=label, root_uep="/", filters=None, driver="H5FD_CORE", driver_core_backing_store=0, **extra_kw)
-    try:
-        yield file_handle
-    finally:
-        file_handle.close()
-
-
-
-def load(self, fobj: IO[bytes], label: str = 'unknown_file.h5') ‑> Union[pandas.core.frame.DataFrame, pandas.core.series.Series] -
-
-

Load the dataframe from an file-like object.

-
- -Expand source code - -
def load(self, fobj: IO[bytes], label: str = "unknown_file.h5") -> Union[DataFrame, Series]:
-    """Load the dataframe from an file-like object."""
-    with self.create_file(label, mode="r", data=fobj.read()) as file_handle:
-        return pd.read_hdf(InMemStore(label, file_handle))
-
-
-
-def save(self, df: pandas.core.frame.DataFrame, fobj: IO[bytes], label: str = 'unknown_file.h5', options: Optional[dict] = None) -
-
-

Load the dataframe to a file-like object.

-
- -Expand source code - -
def save(self, df: DataFrame, fobj: IO[bytes], label: str = "unknown_file.h5", options: Optional[dict] = None):
-    """Load the dataframe to a file-like object."""
-    if not options:
-        options = {}
-    with self.create_file(label, mode="w", data=fobj.read()) as file_handle:
-        store = InMemStore(path=label, table=file_handle, mode="w")
-        store.put(key="df", value=df, **options)
-        fobj.write(file_handle.get_file_image())
-
-
-
-
-
-class InMemStore -(path: str, table: tables.file.File, mode: str = 'r') -
-
-

A subclass of pandas HDFStore that does not manage the pytables File object.

-

Create a new HDFStore object.

-
- -Expand source code - -
class InMemStore(pd.io.pytables.HDFStore):
-    """A subclass of pandas HDFStore that does not manage the pytables File object."""
-
-    _in_mem_table = None
-
-    def __init__(self, path: str, table: tables.File, mode: str = "r"):
-        """Create a new HDFStore object."""
-        self._in_mem_table = table
-        super().__init__(path=path, mode=mode)
-
-    def open(self, *_args, **_kwargs):
-        """Open the in-memory table."""
-        pd.io.pytables._tables()
-        self._handle = self._in_mem_table
-
-    def close(self, *_args, **_kwargs):
-        """Close the in-memory table."""
-
-    @property
-    def is_open(self):
-        """Check if the in-memory table is open."""
-        return self._handle is not None
-
-

Ancestors

-
    -
  • pandas.io.pytables.HDFStore
  • -
-

Instance variables

-
-
var is_open
-
-

Check if the in-memory table is open.

-
- -Expand source code - -
@property
-def is_open(self):
-    """Check if the in-memory table is open."""
-    return self._handle is not None
-
-
-
-

Methods

-
-
-def close(self, *_args, **_kwargs) -
-
-

Close the in-memory table.

-
- -Expand source code - -
def close(self, *_args, **_kwargs):
-    """Close the in-memory table."""
-
-
-
-def open(self, *_args, **_kwargs) -
-
-

Open the in-memory table.

-
- -Expand source code - -
def open(self, *_args, **_kwargs):
-    """Open the in-memory table."""
-    pd.io.pytables._tables()
-    self._handle = self._in_mem_table
-
-
-
-
-
-class S3TransferHandle -(s3_object: object, fobj: IO[bytes], done_future: s3transfer.futures.BaseTransferFuture) -
-
-

A dataclass used to track an ongoing data download from the s3.

-
- -Expand source code - -
class S3TransferHandle:
-    """A dataclass used to track an ongoing data download from the s3."""
-
-    s3_object: object  # boto3.resource('s3').ObjectSummary
-    fobj: IO[bytes]  # file-like object the data is being downloaded to
-    done_future: s3transfer.futures.BaseTransferFuture
-
-

Class variables

-
-
var done_future : s3transfer.futures.BaseTransferFuture
-
-
-
-
var fobj : IO[bytes]
-
-
-
-
var s3_object : object
-
-
-
-
-
-
-class WithS3File -
-
-

Handles I/O operations for AWS S3.

-

All files are persisted to disk first using boto3 as this has proven to be faster than reading them into memory. -Note that reading things into memory is available for csv, json and parquet types only. Unfortunately, until support -for generic buffer is added to read_hdf, we need to download and persists the file to disk first anyway.

-

Options

-

no_disk_space: If True, then s3fs + fsspec will be used to read data directly into memory.

-
- -Expand source code - -
class WithS3File(with_local.WithLocal):
-    """Handles I/O operations for AWS S3.
-
-    All files are persisted to disk first using boto3 as this has proven to be faster than reading them into memory.
-    Note that reading things into memory is available for csv, json and parquet types only. Unfortunately, until support
-    for generic buffer is added to read_hdf, we need to download and persists the file to disk first anyway.
-
-    Options:
-        no_disk_space: If `True`, then s3fs + fsspec will be used to read data directly into memory.
-    """
-
-    sources_config: S3DataEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_client = boto3.client("s3")
-
-    @contextmanager
-    def _s3_named_file_reader(self, s3_bucket: str, s3_key: str) -> Generator:
-        """Contextmanager to abstract reading different file types in S3.
-
-        This implementation saves the downloaded data to a temporary file.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        with tempfile.NamedTemporaryFile("wb") as target_file:
-            # Download the file from S3
-            self.boto3_client.download_fileobj(s3_bucket, s3_key, target_file)
-            # Yield local file path to body of `with` statement
-            target_file.flush()
-            yield target_file
-
-    @contextmanager
-    def _s3_reader(self, s3_bucket: str, s3_key: str) -> Generator[io.BytesIO, None, None]:
-        """Contextmanager to abstract reading different file types in S3.
-
-         This implementation only retains data in-memory, avoiding creating any temp files.
-
-        Args:
-            s3_bucket: The S3 bucket from where to read the file.
-            s3_key: The file-path to the target file to be read.
-
-        Returns:
-            The local file path from where the file can be read, once it has been downloaded there by the boto3.client.
-
-        """
-        fobj = io.BytesIO()
-        # Download the file from S3
-        self.boto3_client.download_fileobj(s3_bucket, s3_key, fobj)
-        # Yield the buffer
-        fobj.seek(0)
-        yield fobj
-
-    @contextmanager
-    def _s3_writer(self, s3_bucket: str, s3_key: str) -> Generator[IO[bytes], None, None]:
-        """Contextmanager to abstract loading different file types to S3.
-
-        Args:
-            s3_bucket: The S3 bucket to upload the file to.
-            s3_key: The file-path where the target file should be uploaded to.
-
-        Returns:
-            The local file path where to actually write the file, to be read and uploaded by boto3.client.
-        """
-        fobj = io.BytesIO()
-        yield fobj
-        fobj.seek(0)
-        self.boto3_client.upload_fileobj(fobj, s3_bucket, s3_key, ExtraArgs={"ACL": "bucket-owner-full-control"})
-
-    def _read_from_s3_file(self) -> pd.DataFrame:
-        """Read a file from an S3 bucket as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `file_path`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using "_read_{file_type}_file".
-
-        Returns:
-            DataFrame
-        """
-        s3_config = self.sources_config.s3
-        file_type = s3_config.file_type
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        bucket = s3_config.bucket
-
-        logger.info(f"[s3] Started downloading: s3://{s3_config.bucket}/{file_path}")
-        if self.options.pop("no_disk_space", None):
-            no_disk_space_rv = None
-            if file_type in ["csv", "json", "parquet"]:
-                no_disk_space_rv = getattr(self, f"_read_{file_type}_file")(f"s3://{s3_config.bucket}/{file_path}", self.schema, **self.options)  # type: ignore
-            elif file_type == "hdf":
-                with self._s3_reader(s3_bucket=bucket, s3_key=file_path) as fobj:  # type: ignore
-                    no_disk_space_rv = HdfIO().load(fobj)  # type: ignore
-            else:
-                raise NotImplementedError(f"Unsupported file type {file_type!r}.")
-            if no_disk_space_rv is not None:
-                return no_disk_space_rv
-        with self._s3_named_file_reader(s3_bucket=bucket, s3_key=file_path) as target_file:  # type: ignore
-            return getattr(self, f"_read_{file_type}_file")(target_file.name, self.schema, **self.options)  # type: ignore
-
-    def _write_to_s3_file(self, df: pd.DataFrame):
-        """Write a dataframe to s3 based on the {file_type} of the config_io configuration.
-
-        The configuration object is expected to have two keys:
-
-            - `file_path`
-            - `file_type`
-
-        To actually write the file, a method is dynamically invoked by name, using "_write_{file_type}_file".
-
-        Args:
-            df: The dataframe to be written out
-        """
-        s3_config = self.sources_config.s3
-        bucket = s3_config.bucket
-        file_path = utils.resolve_template(s3_config.file_path, self.options)
-        file_type = s3_config.file_type
-
-        logger.info(f"[s3] Started uploading: s3://{bucket}/{file_path}")
-        if file_type in ["csv", "json", "parquet"]:
-            getattr(self, f"_write_{file_type}_file")(df, f"s3://{bucket}/{file_path}", **self.options)  # type: ignore
-        elif file_type == "hdf":
-            hdf_options = dict(self.options)
-            pickle_protocol = hdf_options.pop("pickle_protocol", None)
-            with self._s3_writer(s3_bucket=s3_config.bucket, s3_key=file_path) as target_file, utils.pickle_protocol(protocol=pickle_protocol):
-                HdfIO().save(df, target_file, hdf_options)  # type: ignore
-        else:
-            raise ValueError(f"File type: {file_type} not supported!")
-        logger.info(f"[s3] Finished uploading: s3://{bucket}/{file_path}")
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var boto3_client
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configS3DataEnvironment
-
-
-
-
-
-
-class WithS3PathPrefix -
-
-

Handles I/O operations for AWS S3; implements read operations only.

-

This mixin assumes that the directories it reads from will only contain a single file-type.

-
- -Expand source code - -
class WithS3PathPrefix(with_local.WithLocal):
-    """Handles I/O operations for AWS S3; implements read operations only.
-
-    This mixin assumes that the directories it reads from will only contain a single file-type.
-    """
-
-    sources_config: S3PathPrefixEnvironment  # type: ignore
-    schema: DataframeSchema
-
-    boto3_resource = boto3.resource("s3")
-    boto3_client = boto3.client("s3")
-
-    def _write_to_s3_path_prefix(self, df: pd.DataFrame):
-        """Write a DataFrame to an S3 path prefix.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `path_prefix`
-            - `file_type`
-
-        Args:
-            df (pd.DataFrame): the DataFrame to be written to S3
-
-        Raises:
-            ValueError: In case `path_prefix` is missing from config
-            ValueError: In case the `partition_cols` arg is missing while trying to write a parquet file
-        """
-        s3_config = self.sources_config.s3
-
-        file_type = s3_config.file_type
-        if file_type != "parquet":
-            raise ValueError(f"File type not supported: {file_type}, only parquet files can be written to an S3 key")
-        if "partition_cols" not in self.options:
-            raise ValueError("`partition_cols` is required as an option to write partitioned parquet files to S3")
-
-        bucket = s3_config.bucket
-        path_prefix = s3_config.path_prefix
-        full_path_prefix = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            self._write_parquet_file(df, temp_dir, **self.options)
-            awscli_runner(
-                "s3",
-                "sync",
-                temp_dir,
-                full_path_prefix,
-                "--acl",
-                "bucket-owner-full-control",
-                "--only-show-errors",
-                "--exact-timestamps",
-            )
-
-    def _read_from_s3_path_prefix(self) -> pd.DataFrame:
-        """Read all files under a path prefix from an S3 bucket as a `DataFrame`.
-
-        The configuration object is expected to have the following keys:
-            - `bucket`
-            - `path_prefix`
-            - `file_type`
-
-        To actually read the file, a method is dynamically invoked by name, using
-        "_read_{file_type}_path_prefix".
-
-        Returns:
-            DataFrame
-        """
-        s3_config = self.sources_config.s3
-        file_type = s3_config.file_type
-        if file_type not in {"parquet", "csv", "hdf", "json"}:
-            raise ValueError(f"File type not supported: {file_type}")
-
-        bucket = s3_config.bucket
-        path_prefix = s3_config.path_prefix
-        full_path_prefix = utils.resolve_template(f"s3://{bucket}/{path_prefix}", self.options)
-
-        # The `no_disk_space` option should be used only when reading a subset of columns from S3
-        if self.options.pop("no_disk_space", False):
-            if file_type == "parquet":
-                return self._read_parquet_file(full_path_prefix, self.schema, **self.options)
-            if file_type == "hdf":
-                dfs: List[DataFrame] = []
-                for fobj in self._iter_s3_files(full_path_prefix, file_ext=".h5", max_memory_use=1024**3):  # 1 gib
-                    dfs.append(HdfIO().load(fobj))
-                df = pd.concat(dfs, ignore_index=True)
-                columns = [column for column in df.columns.to_list() if column in self.schema.columns.keys()]
-                return df[columns]
-
-        with tempfile.TemporaryDirectory() as temp_dir:
-            # aws-cli is shown to be up to 6 times faster when downloading the complete dataset from S3 than using the boto3
-            # client or pandas directly. This is because aws-cli uses the parallel downloader, which is much faster than the
-            # boto3 client.
-            awscli_runner(
-                "s3",
-                "sync",
-                full_path_prefix,
-                temp_dir,
-                "--acl",
-                "bucket-owner-full-control",
-                "--only-show-errors",
-                "--exact-timestamps",
-            )
-
-            dfs: List[DataFrame] = []
-            for file in os.listdir(temp_dir):
-                df = getattr(self, f"_read_{file_type}_file")(os.path.join(temp_dir, file), self.schema, **self.options)  # type: ignore
-                if len(df) > 0:
-                    dfs.append(df)
-
-            return pd.concat(dfs, ignore_index=True)
-
-    def _iter_s3_files(self, s3_prefix: str, file_ext: Optional[str] = None, max_memory_use: int = -1) -> Generator[IO[bytes], None, None]:  # pylint: disable=too-many-locals
-        """Download sways of S3 objects.
-
-        Args:
-            s3_prefix: s3 url to fetch objects with
-            file_ext: extension of s3 objects to allow through
-            max_memory_use: The approximate number of bytes to allocate on each yield of Generator
-        """
-        parsed_url = urllib.parse.urlparse(s3_prefix)
-        assert parsed_url.scheme == "s3", f"{s3_prefix!r} should be an s3 url"
-        bucket_name = parsed_url.netloc
-        file_prefix = f"{parsed_url.path.strip('/')}/"
-        s3_objects_to_fetch = []
-        # Collect objects to be loaded
-        for s3_object in self.boto3_resource.Bucket(bucket_name).objects.filter(Prefix=file_prefix):
-            good_object = (not file_ext) or (s3_object.key.endswith(file_ext))
-            if good_object:
-                s3_objects_to_fetch.append(s3_object)
-
-        if max_memory_use < 0:
-            # Unlimited memory use - fetch ALL
-            max_memory_use = sum(s3_obj.size for s3_obj in s3_objects_to_fetch) * 2
-        transfer_config = boto3.s3.transfer.TransferConfig(max_concurrency=20)
-        while s3_objects_to_fetch:
-            mem_use_left = max_memory_use
-            handles = []
-            with boto3.s3.transfer.create_transfer_manager(self.boto3_client, transfer_config) as transfer_manager:
-                while mem_use_left > 0 and s3_objects_to_fetch:
-                    s3_object = s3_objects_to_fetch.pop()
-                    fobj = io.BytesIO()
-                    future = transfer_manager.download(bucket_name, s3_object.key, fobj)
-                    handles.append(S3TransferHandle(s3_object, fobj, future))
-                    mem_use_left -= s3_object.size
-                # Leaving the `transfer_manager` context implicitly waits for all downloads to complete
-            # Rewind and yield all fobjs
-            for handle in handles:
-                handle.fobj.seek(0)
-                yield handle.fobj
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var boto3_client
-
-
-
-
var boto3_resource
-
-
-
-
var schemaDataframeSchema
-
-
-
-
var sources_configS3PathPrefixEnvironment
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/validations.html b/docs/validations.html deleted file mode 100644 index 75e3df4..0000000 --- a/docs/validations.html +++ /dev/null @@ -1,1011 +0,0 @@ - - - - - - -dynamicio.validations API documentation - - - - - - - - - - - -
-
-
-

Module dynamicio.validations

-
-
-

Implements the Validator class responsible for various generic data validations and metrics generation.

-
- -Expand source code - -
"""Implements the Validator class responsible for various generic data validations and metrics generation."""
-import operator
-from typing import Callable, NamedTuple, Set
-
-import pandas as pd  # type: ignore
-
-ALL_VALIDATORS = {}  # name -> function
-
-
-def validator(func: Callable):
-    """A decorator to add the function to the ALL_VALIDATORS dict."""
-    name = func.__name__
-    assert name not in ALL_VALIDATORS
-    ALL_VALIDATORS[name] = func
-    return func
-
-
-class ValidationResult(NamedTuple):
-    """A NamedTuple for capturing different outputs after a validation."""
-
-    valid: bool
-    message: str
-    value: float
-
-
-@validator
-def has_unique_values(dataset: str, df: pd.DataFrame, column: str) -> ValidationResult:
-    """Checks if values in column are unique.
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A pandas DataFrame
-        column: The column to be validated
-
-    Returns:
-        An instance of  ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and  `Validation.Result.value` is no_of_duplicated_elements
-    """
-    counts = df[column].value_counts()
-    if not (counts > 1).any():
-        return ValidationResult(valid=True, message=f"{dataset}[{column}] has unique values", value=0)
-
-    duplicates = counts[counts > 1].index.to_list()
-    return ValidationResult(valid=False, message=f"Values {duplicates} for {dataset}[{column}] are duplicated!", value=len(duplicates))
-
-
-@validator
-def has_no_null_values(dataset: str, df: pd.DataFrame, column: str) -> ValidationResult:
-    """Checks if column has any null values (including NaN and NaT values).
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A pandas DataFrame
-        column: The column to be validated
-
-    Returns:
-        An instance of  ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and  `Validation.Result.value` is no_of_nulls
-    """
-    mask = df[column].isnull()
-    no_of_nulls = mask.sum()
-    return ValidationResult(valid=not mask.any(), message=f"{dataset}[{column}] has {no_of_nulls} nulls", value=no_of_nulls)
-
-
-@validator
-def has_acceptable_percentage_of_nulls(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Checks if a provided threshold of max nulls has been exceeded.
-
-    Note: For an empty df the validation will always be successful
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A pandas DataFrame
-        column: The column to be validated
-        threshold: Maximum allowed threshold
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and  `Validation.Result.value` is percentage_of_nulls
-    """
-    if threshold <= 0 or threshold >= 1:
-        raise ValueError(f"Threshold value: {threshold} must be a value between 0 and 1.")
-
-    no_of_nulls = df[column].isnull().sum()
-    if len(df) == 0:
-        percentage_of_nulls = 0.0
-    else:
-        percentage_of_nulls = no_of_nulls / len(df)
-
-    if percentage_of_nulls < threshold:
-        return ValidationResult(
-            valid=True,
-            message=f"Percentage of nulls of for {dataset}[{column}] is {percentage_of_nulls}",
-            value=percentage_of_nulls,
-        )
-    return ValidationResult(
-        valid=False,
-        message=f"Percentage of nulls of for {dataset}[{column}] is {percentage_of_nulls} which exceeds threshold: {threshold}",
-        value=percentage_of_nulls,
-    )
-
-
-@validator
-def is_in(dataset: str, df: pd.DataFrame, column: str, categorical_values: Set[str], match_all: bool = True) -> ValidationResult:
-    """Checks if the column only has allowed categorical values as per the set provided.
-
-    Note:
-        Ignores nulls
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        categorical_values: The allowed set of categorical values
-        match_all: If True, the categorical values must be a subset of the allowed set, otherwise they must be equal
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is no_of_not_acceptable
-    """
-    unique_values = set(df[column][df[column].notna()].unique())
-
-    if match_all:
-        return _validate_categoricals_are_a_subset_of_the_acceptable(categorical_values, unique_values, column, dataset, df)
-    return _validate_all_acceptable_categoricals_are_present(categorical_values, unique_values, column, dataset, df)
-
-
-@validator
-def _validate_all_acceptable_categoricals_are_present(acceptable_categoricals: Set[str], unique_values: Set[str], column: str, dataset: str, df: pd.DataFrame) -> ValidationResult:
-    if unique_values == acceptable_categoricals:
-        validation_result = ValidationResult(valid=True, message=f"All acceptable categorical values for {dataset}[{column}] are present", value=0)
-    elif unique_values < acceptable_categoricals:
-        validation_result = ValidationResult(
-            valid=False,
-            message=f"Missing categorical values for {dataset}[{column}]: {acceptable_categoricals - unique_values}",
-            value=len(acceptable_categoricals - unique_values),
-        )
-    else:
-        count_invalid = (~df[column].isin(acceptable_categoricals)).sum()
-        validation_result = ValidationResult(
-            valid=False,
-            message=f"Values {unique_values - set(acceptable_categoricals)} for {dataset}[{column}] are not acceptable for {count_invalid} cells",
-            value=count_invalid,
-        )
-    return validation_result
-
-
-@validator
-def _validate_categoricals_are_a_subset_of_the_acceptable(acceptable_categoricals: Set[str], unique_values: Set[str], column: str, dataset: str, df: pd.DataFrame) -> ValidationResult:
-    if unique_values.issubset(acceptable_categoricals):
-        return ValidationResult(valid=True, message=f"Categorical values for {dataset}[{column}] are acceptable", value=0)
-    count_invalid = (~df[column].isin(acceptable_categoricals)).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"Values {unique_values - set(acceptable_categoricals)} for {dataset}[{column}] are not acceptable for {count_invalid} cells",
-        value=count_invalid,
-    )
-
-
-@validator
-def is_greater_than(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are above a given threshold.
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the
-        percentage of invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df > threshold
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are above {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are below {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-@validator
-def is_greater_than_or_equal(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are above a given threshold.
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the
-        percentage of invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df >= threshold
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are above {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are below {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-@validator
-def is_lower_than(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are below a given threshold.
-
-    IMPORTANT NOTE: Ignores nulls!
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the percentage of
-        invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df < threshold  # pd.DataFrame
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are below {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are above {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-@validator
-def is_lower_than_or_equal(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are below a given threshold.
-
-    IMPORTANT NOTE: Ignores nulls!
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the percentage of
-        invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df <= threshold
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are below {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are above {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-@validator
-def is_between(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    lower: float,
-    upper: float,
-    include_left: bool = False,
-    include_right: bool = False,
-) -> ValidationResult:
-    """Confirms column values are between a lower bound and an upper bound thresholds.
-
-    IMPORTANT NOTE: Ignores nulls!
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        lower: The lower bound (left)
-        upper: The upper bound (right)
-        include_left: `left <= df[column]`
-        include_right: `df[column] <=right`
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the percentage of
-        invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    lower_bound_operator = operator.ge if include_left else operator.gt
-    upper_bound_operator = operator.le if include_right else operator.lt
-
-    valid = lower_bound_operator(no_nulls_for_column_df, lower) & upper_bound_operator(no_nulls_for_column_df, upper)
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] is between {lower} and {upper} thresholds", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are either below {lower} or above {upper}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-
-
-
-
-
-

Functions

-
-
-def has_acceptable_percentage_of_nulls(dataset: str, df: pandas.core.frame.DataFrame, column: str, threshold: float) ‑> ValidationResult -
-
-

Checks if a provided threshold of max nulls has been exceeded.

-

Note: For an empty df the validation will always be successful

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A pandas DataFrame
-
column
-
The column to be validated
-
threshold
-
Maximum allowed threshold
-
-

Returns

-

An instance of ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and -Validation.Result.value is percentage_of_nulls

-
- -Expand source code - -
@validator
-def has_acceptable_percentage_of_nulls(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Checks if a provided threshold of max nulls has been exceeded.
-
-    Note: For an empty df the validation will always be successful
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A pandas DataFrame
-        column: The column to be validated
-        threshold: Maximum allowed threshold
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and  `Validation.Result.value` is percentage_of_nulls
-    """
-    if threshold <= 0 or threshold >= 1:
-        raise ValueError(f"Threshold value: {threshold} must be a value between 0 and 1.")
-
-    no_of_nulls = df[column].isnull().sum()
-    if len(df) == 0:
-        percentage_of_nulls = 0.0
-    else:
-        percentage_of_nulls = no_of_nulls / len(df)
-
-    if percentage_of_nulls < threshold:
-        return ValidationResult(
-            valid=True,
-            message=f"Percentage of nulls of for {dataset}[{column}] is {percentage_of_nulls}",
-            value=percentage_of_nulls,
-        )
-    return ValidationResult(
-        valid=False,
-        message=f"Percentage of nulls of for {dataset}[{column}] is {percentage_of_nulls} which exceeds threshold: {threshold}",
-        value=percentage_of_nulls,
-    )
-
-
-
-def has_no_null_values(dataset: str, df: pandas.core.frame.DataFrame, column: str) ‑> ValidationResult -
-
-

Checks if column has any null values (including NaN and NaT values).

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A pandas DataFrame
-
column
-
The column to be validated
-
-

Returns

-

An instance of -ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and -Validation.Result.value is no_of_nulls

-
- -Expand source code - -
@validator
-def has_no_null_values(dataset: str, df: pd.DataFrame, column: str) -> ValidationResult:
-    """Checks if column has any null values (including NaN and NaT values).
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A pandas DataFrame
-        column: The column to be validated
-
-    Returns:
-        An instance of  ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and  `Validation.Result.value` is no_of_nulls
-    """
-    mask = df[column].isnull()
-    no_of_nulls = mask.sum()
-    return ValidationResult(valid=not mask.any(), message=f"{dataset}[{column}] has {no_of_nulls} nulls", value=no_of_nulls)
-
-
-
-def has_unique_values(dataset: str, df: pandas.core.frame.DataFrame, column: str) ‑> ValidationResult -
-
-

Checks if values in column are unique.

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A pandas DataFrame
-
column
-
The column to be validated
-
-

Returns

-

An instance of -ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and -Validation.Result.value is no_of_duplicated_elements

-
- -Expand source code - -
@validator
-def has_unique_values(dataset: str, df: pd.DataFrame, column: str) -> ValidationResult:
-    """Checks if values in column are unique.
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A pandas DataFrame
-        column: The column to be validated
-
-    Returns:
-        An instance of  ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and  `Validation.Result.value` is no_of_duplicated_elements
-    """
-    counts = df[column].value_counts()
-    if not (counts > 1).any():
-        return ValidationResult(valid=True, message=f"{dataset}[{column}] has unique values", value=0)
-
-    duplicates = counts[counts > 1].index.to_list()
-    return ValidationResult(valid=False, message=f"Values {duplicates} for {dataset}[{column}] are duplicated!", value=len(duplicates))
-
-
-
-def is_between(dataset: str, df: pandas.core.frame.DataFrame, column: str, lower: float, upper: float, include_left: bool = False, include_right: bool = False) ‑> ValidationResult -
-
-

Confirms column values are between a lower bound and an upper bound thresholds.

-

IMPORTANT NOTE: Ignores nulls!

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A DataFrame
-
column
-
The DataFrame column to be validated
-
lower
-
The lower bound (left)
-
upper
-
The upper bound (right)
-
include_left
-
left <= df[column]
-
include_right
-
df[column] <=right
-
-

Returns

-

An instance of ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and Validation.Result.value is the percentage of -invalid values

-
- -Expand source code - -
@validator
-def is_between(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    lower: float,
-    upper: float,
-    include_left: bool = False,
-    include_right: bool = False,
-) -> ValidationResult:
-    """Confirms column values are between a lower bound and an upper bound thresholds.
-
-    IMPORTANT NOTE: Ignores nulls!
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        lower: The lower bound (left)
-        upper: The upper bound (right)
-        include_left: `left <= df[column]`
-        include_right: `df[column] <=right`
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the percentage of
-        invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    lower_bound_operator = operator.ge if include_left else operator.gt
-    upper_bound_operator = operator.le if include_right else operator.lt
-
-    valid = lower_bound_operator(no_nulls_for_column_df, lower) & upper_bound_operator(no_nulls_for_column_df, upper)
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] is between {lower} and {upper} thresholds", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are either below {lower} or above {upper}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-
-def is_greater_than(dataset: str, df: pandas.core.frame.DataFrame, column: str, threshold: float) ‑> ValidationResult -
-
-

Confirms column values are above a given threshold.

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A DataFrame
-
column
-
The DataFrame column to be validated
-
threshold
-
A lower bound threshold not to be exceeded
-
-

Returns

-

An instance of ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and Validation.Result.value is the -percentage of invalid values

-
- -Expand source code - -
@validator
-def is_greater_than(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are above a given threshold.
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the
-        percentage of invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df > threshold
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are above {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are below {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-
-def is_greater_than_or_equal(dataset: str, df: pandas.core.frame.DataFrame, column: str, threshold: float) ‑> ValidationResult -
-
-

Confirms column values are above a given threshold.

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A DataFrame
-
column
-
The DataFrame column to be validated
-
threshold
-
A lower bound threshold not to be exceeded
-
-

Returns

-

An instance of ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and Validation.Result.value is the -percentage of invalid values

-
- -Expand source code - -
@validator
-def is_greater_than_or_equal(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are above a given threshold.
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the
-        percentage of invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df >= threshold
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are above {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are below {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-
-def is_in(dataset: str, df: pandas.core.frame.DataFrame, column: str, categorical_values: Set[str], match_all: bool = True) ‑> ValidationResult -
-
-

Checks if the column only has allowed categorical values as per the set provided.

-

Note

-

Ignores nulls

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A DataFrame
-
column
-
The DataFrame column to be validated
-
categorical_values
-
The allowed set of categorical values
-
match_all
-
If True, the categorical values must be a subset of the allowed set, otherwise they must be equal
-
-

Returns

-

An instance of ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and Validation.Result.value is no_of_not_acceptable

-
- -Expand source code - -
@validator
-def is_in(dataset: str, df: pd.DataFrame, column: str, categorical_values: Set[str], match_all: bool = True) -> ValidationResult:
-    """Checks if the column only has allowed categorical values as per the set provided.
-
-    Note:
-        Ignores nulls
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        categorical_values: The allowed set of categorical values
-        match_all: If True, the categorical values must be a subset of the allowed set, otherwise they must be equal
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is no_of_not_acceptable
-    """
-    unique_values = set(df[column][df[column].notna()].unique())
-
-    if match_all:
-        return _validate_categoricals_are_a_subset_of_the_acceptable(categorical_values, unique_values, column, dataset, df)
-    return _validate_all_acceptable_categoricals_are_present(categorical_values, unique_values, column, dataset, df)
-
-
-
-def is_lower_than(dataset: str, df: pandas.core.frame.DataFrame, column: str, threshold: float) ‑> ValidationResult -
-
-

Confirms column values are below a given threshold.

-

IMPORTANT NOTE: Ignores nulls!

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A DataFrame
-
column
-
The DataFrame column to be validated
-
threshold
-
A lower bound threshold not to be exceeded
-
-

Returns

-

An instance of ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and Validation.Result.value is the percentage of -invalid values

-
- -Expand source code - -
@validator
-def is_lower_than(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are below a given threshold.
-
-    IMPORTANT NOTE: Ignores nulls!
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the percentage of
-        invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df < threshold  # pd.DataFrame
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are below {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are above {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-
-def is_lower_than_or_equal(dataset: str, df: pandas.core.frame.DataFrame, column: str, threshold: float) ‑> ValidationResult -
-
-

Confirms column values are below a given threshold.

-

IMPORTANT NOTE: Ignores nulls!

-

Args

-
-
dataset
-
Name fo the dataset_name
-
df
-
A DataFrame
-
column
-
The DataFrame column to be validated
-
threshold
-
A lower bound threshold not to be exceeded
-
-

Returns

-

An instance of ValidationResult where Validation.Result.valid is a bool indicate the success of the validation, -Validation.Result.message is a message (usually used in exceptions), and Validation.Result.value is the percentage of -invalid values

-
- -Expand source code - -
@validator
-def is_lower_than_or_equal(
-    dataset: str,
-    df: pd.DataFrame,
-    column: str,
-    threshold: float,
-) -> ValidationResult:
-    """Confirms column values are below a given threshold.
-
-    IMPORTANT NOTE: Ignores nulls!
-
-    Args:
-        dataset: Name fo the dataset_name
-        df: A DataFrame
-        column: The DataFrame column to be validated
-        threshold: A lower bound threshold not to be exceeded
-
-    Returns:
-        An instance of ValidationResult where `Validation.Result.valid` is a bool indicate the success of the validation,
-        `Validation.Result.message` is a message (usually used in exceptions), and `Validation.Result.value` is the percentage of
-        invalid values
-    """
-    no_nulls_for_column_df = df[~df[column].isnull()][column]
-    valid = no_nulls_for_column_df <= threshold
-
-    if valid.all():
-        return ValidationResult(valid=True, message=f"All values of {dataset}[{column}] are below {threshold}", value=0)
-
-    no_of_invalid = (~valid).sum()
-    return ValidationResult(
-        valid=False,
-        message=f"{no_of_invalid} cell values for {dataset}[{column}] are above {threshold}",
-        value=no_of_invalid / len(no_nulls_for_column_df),
-    )
-
-
-
-def validator(func: Callable) -
-
-

A decorator to add the function to the ALL_VALIDATORS dict.

-
- -Expand source code - -
def validator(func: Callable):
-    """A decorator to add the function to the ALL_VALIDATORS dict."""
-    name = func.__name__
-    assert name not in ALL_VALIDATORS
-    ALL_VALIDATORS[name] = func
-    return func
-
-
-
-
-
-

Classes

-
-
-class ValidationResult -(valid: bool, message: str, value: float) -
-
-

A NamedTuple for capturing different outputs after a validation.

-
- -Expand source code - -
class ValidationResult(NamedTuple):
-    """A NamedTuple for capturing different outputs after a validation."""
-
-    valid: bool
-    message: str
-    value: float
-
-

Ancestors

-
    -
  • builtins.tuple
  • -
-

Instance variables

-
-
var message : str
-
-

Alias for field number 1

-
-
var valid : bool
-
-

Alias for field number 0

-
-
var value : float
-
-

Alias for field number 2

-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/mkdocs.yaml b/mkdocs.yaml new file mode 100644 index 0000000..8db2155 --- /dev/null +++ b/mkdocs.yaml @@ -0,0 +1,20 @@ +--- +site_name: dynamicio Documentation + +nav: + - Home: index.md + - API Reference: api.md + +plugins: + - techdocs-core + - mkdocstrings: + handlers: + python: + options: + docstring_style: google + show_source: false + show_signature_annotations: true + members_order: source + separate_signature: true + +docs_dir: docs diff --git a/pyproject.toml b/pyproject.toml index 314daad..dca0ded 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,10 @@ types-simplejson = "*" yamllint = "*" [tool.poetry.group.docs.dependencies] -pdoc3 = "*" +mkdocs = "*" +mkdocs-material = "*" +mkdocstrings = { version = "*", extras = ["python"] } +mkdocs-techdocs-core = "*" [tool.poetry.group.build.dependencies] setuptools = "==70.0.0"