kedro-sumz-utils 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kedro_sumz_utils-0.0.1/PKG-INFO +13 -0
- kedro_sumz_utils-0.0.1/pyproject.toml +24 -0
- kedro_sumz_utils-0.0.1/setup.cfg +4 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/__init__.py +0 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/__init__.py +0 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/context/__init__.py +81 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/datasets/__init__.py +12 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/datasets/delta.py +155 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/datasets/mlflow.py +277 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/datasets/spark.py +313 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/datasets/sparkml.py +262 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/extras/hooks/__init__.py +58 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/utils/__init__.py +299 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/utils/mlflow.py +130 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz/utils/scd.py +389 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz_utils.egg-info/PKG-INFO +13 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz_utils.egg-info/SOURCES.txt +18 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz_utils.egg-info/dependency_links.txt +1 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz_utils.egg-info/requires.txt +2 -0
- kedro_sumz_utils-0.0.1/src/kedro_sumz_utils.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: kedro-sumz-utils
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Utility functions and kedro datasets for Data projects.
|
|
5
|
+
Author-email: Juan Barreto <juan.barreto@sumz.co>
|
|
6
|
+
Project-URL: Homepage, https://github.com/pypa/sampleproject
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/pypa/sampleproject/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: kedro[spark]<0.19.0,>=0.18.0
|
|
13
|
+
Requires-Dist: kedro-mlflow==0.11.10
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kedro-sumz-utils"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Juan Barreto", email="juan.barreto@sumz.co" },
|
|
10
|
+
]
|
|
11
|
+
description = "Utility functions and kedro datasets for Data projects."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"kedro[spark]>=0.18.0,<0.19.0",
|
|
20
|
+
"kedro-mlflow==0.11.10"
|
|
21
|
+
]
|
|
22
|
+
[project.urls]
|
|
23
|
+
"Homepage" = "https://github.com/pypa/sampleproject"
|
|
24
|
+
"Bug Tracker" = "https://github.com/pypa/sampleproject/issues"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Custom Kedro-Virgo context class."""
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Union
|
|
5
|
+
|
|
6
|
+
from kedro.config import ConfigLoader
|
|
7
|
+
from kedro.framework.context import KedroContext
|
|
8
|
+
from pluggy import PluginManager
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class KedroSparkContext(KedroContext):
|
|
14
|
+
"""Custom kedro context which initializes the Spark Session."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
package_name: str,
|
|
19
|
+
project_path: Union[str, Path],
|
|
20
|
+
config_loader: ConfigLoader,
|
|
21
|
+
hook_manager: PluginManager,
|
|
22
|
+
env: str = None,
|
|
23
|
+
extra_params: Dict[str, Any] = None,
|
|
24
|
+
):
|
|
25
|
+
logger.info("Creating Kedro Context")
|
|
26
|
+
super().__init__(
|
|
27
|
+
package_name, project_path, config_loader, hook_manager, env, extra_params
|
|
28
|
+
)
|
|
29
|
+
self._spark_session = None
|
|
30
|
+
self._package_name = (
|
|
31
|
+
package_name if package_name else Path(__file__).parent.name
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if not self._init_databricks():
|
|
35
|
+
self._init_spark()
|
|
36
|
+
|
|
37
|
+
def _init_spark(self):
|
|
38
|
+
from pyspark import SparkConf
|
|
39
|
+
from pyspark.sql import SparkSession
|
|
40
|
+
|
|
41
|
+
if SparkSession.getActiveSession() is None:
|
|
42
|
+
logger.info("Initializing Spark session")
|
|
43
|
+
parameters = self._config_loader["spark"]
|
|
44
|
+
logger.info(parameters)
|
|
45
|
+
spark_conf = SparkConf().setAll(list(parameters.items()))
|
|
46
|
+
|
|
47
|
+
spark_session_conf = (
|
|
48
|
+
SparkSession.builder.appName(self._package_name)
|
|
49
|
+
.master("local[*, 4]")
|
|
50
|
+
.config(conf=spark_conf)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self._spark_session = spark_session_conf.getOrCreate()
|
|
54
|
+
self._spark_session.sparkContext.setLogLevel("WARN")
|
|
55
|
+
logger.info("Spark Web URL: %s", self._spark_session.sparkContext.uiWebUrl)
|
|
56
|
+
else:
|
|
57
|
+
self._spark_session = SparkSession.getActiveSession()
|
|
58
|
+
|
|
59
|
+
def _init_databricks(self) -> bool:
|
|
60
|
+
try:
|
|
61
|
+
from databricks.connect import DatabricksSession
|
|
62
|
+
from pyspark.sql import SparkSession
|
|
63
|
+
|
|
64
|
+
if SparkSession.getActiveSession() is None:
|
|
65
|
+
logger.info("Initializing Databricks session")
|
|
66
|
+
self._spark_session = DatabricksSession.builder.profile(
|
|
67
|
+
"connect"
|
|
68
|
+
).getOrCreate()
|
|
69
|
+
else:
|
|
70
|
+
self._spark_session = SparkSession.getActiveSession()
|
|
71
|
+
return True
|
|
72
|
+
except ImportError:
|
|
73
|
+
logger.info(
|
|
74
|
+
"Databricks Connect is not installed. Initializing Spark session instead."
|
|
75
|
+
)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def spark_session(self):
|
|
80
|
+
"""Spark session property."""
|
|
81
|
+
return self._spark_session
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Dummy dataset for testing purposes."""
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from kedro.io import MemoryDataSet
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DummyDataset(MemoryDataSet):
|
|
8
|
+
"""Dummy dataset for testing purposes."""
|
|
9
|
+
|
|
10
|
+
# pylint: disable=useless-super-delegation,too-few-public-methods
|
|
11
|
+
def __init__(self, data: Any = True, copy_mode: str = None):
|
|
12
|
+
super().__init__(data, copy_mode)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""``AbstractDataSet`` implementation to access DeltaTables using
|
|
2
|
+
``delta-spark``
|
|
3
|
+
"""
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import NoReturn
|
|
6
|
+
|
|
7
|
+
from delta.tables import DeltaTable
|
|
8
|
+
|
|
9
|
+
# pylint: disable=unused-import
|
|
10
|
+
from kedro.extras.datasets.spark import SparkHiveDataSet # noqa: F401
|
|
11
|
+
from kedro.extras.datasets.spark.spark_dataset import (
|
|
12
|
+
_split_filepath,
|
|
13
|
+
_strip_dbfs_prefix,
|
|
14
|
+
)
|
|
15
|
+
from kedro.io.core import AbstractDataSet, DataSetError
|
|
16
|
+
from pyspark.sql import SparkSession
|
|
17
|
+
from pyspark.sql.utils import AnalysisException
|
|
18
|
+
|
|
19
|
+
from kedro_sumz.utils import get_kedro_context
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DeltaTableDataSet(AbstractDataSet[None, DeltaTable]):
|
|
23
|
+
"""``DeltaTableDataSet`` loads data into DeltaTable objects.
|
|
24
|
+
|
|
25
|
+
Example usage for the
|
|
26
|
+
`YAML API <https://kedro.readthedocs.io/en/stable/data/\
|
|
27
|
+
data_catalog.html#use-the-data-catalog-with-the-yaml-api>`_:
|
|
28
|
+
|
|
29
|
+
.. code-block:: yaml
|
|
30
|
+
|
|
31
|
+
weather@spark:
|
|
32
|
+
type: spark.SparkDataSet
|
|
33
|
+
filepath: data/02_intermediate/data.parquet
|
|
34
|
+
file_format: "delta"
|
|
35
|
+
|
|
36
|
+
weather@delta:
|
|
37
|
+
type: virgo.extras.datasets.DeltaTableDataSet
|
|
38
|
+
directory: data/02_intermediate
|
|
39
|
+
table: "data.parquet"
|
|
40
|
+
|
|
41
|
+
weather_table@delta:
|
|
42
|
+
type: virgo.extras.datasets.delta.DeltaTableDataSet
|
|
43
|
+
directory: intermediate
|
|
44
|
+
table: data
|
|
45
|
+
|
|
46
|
+
Example usage for the
|
|
47
|
+
`Python API <https://kedro.readthedocs.io/en/stable/data/\
|
|
48
|
+
data_catalog.html#use-the-data-catalog-with-the-code-api>`_:
|
|
49
|
+
::
|
|
50
|
+
|
|
51
|
+
>>> from pyspark.sql import SparkSession
|
|
52
|
+
>>> from pyspark.sql.types import (StructField, StringType,
|
|
53
|
+
>>> IntegerType, StructType)
|
|
54
|
+
>>>
|
|
55
|
+
>>> from kedro.extras.datasets.spark import DeltaTableDataSet, SparkDataSet
|
|
56
|
+
>>>
|
|
57
|
+
>>> schema = StructType([StructField("name", StringType(), True),
|
|
58
|
+
>>> StructField("age", IntegerType(), True)])
|
|
59
|
+
>>>
|
|
60
|
+
>>> data = [('Alex', 31), ('Bob', 12), ('Clarke', 65), ('Dave', 29)]
|
|
61
|
+
>>>
|
|
62
|
+
>>> spark_df = SparkSession.builder.getOrCreate().createDataFrame(data, schema)
|
|
63
|
+
>>>
|
|
64
|
+
>>> data_set = SparkHiveDataSet(database="test_database", table="test_table",
|
|
65
|
+
>>> write_mode="overwrite")
|
|
66
|
+
>>> data_set.save(spark_df)
|
|
67
|
+
>>> delta_dataset = DeltaTableDataSet(directory="test_database", table="test_table")
|
|
68
|
+
>>> delta_table = delta_dataset.load()
|
|
69
|
+
>>>
|
|
70
|
+
>>> delta_table.update()
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# this dataset cannot be used with ``ParallelRunner``,
|
|
74
|
+
# therefore it has the attribute ``_SINGLE_PROCESS = True``
|
|
75
|
+
# for parallelism within a Spark pipeline please consider
|
|
76
|
+
# using ``ThreadRunner`` instead
|
|
77
|
+
_SINGLE_PROCESS = True
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
directory: str,
|
|
82
|
+
table: str,
|
|
83
|
+
dataset_type: str = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Creates a new instance of ``DeltaTableDataSet``.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
directory: Base path to a folder or a database.
|
|
89
|
+
table: Name of the table or file.
|
|
90
|
+
dataset_type: Type of the dataset. Can be either "table" or "file".
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if not dataset_type and ("/" in directory or "/" in table):
|
|
94
|
+
self._type = "file"
|
|
95
|
+
elif not dataset_type and "." in directory:
|
|
96
|
+
self._type = "table"
|
|
97
|
+
else:
|
|
98
|
+
self._type = dataset_type
|
|
99
|
+
if not self._type:
|
|
100
|
+
raise DataSetError(
|
|
101
|
+
"Could not determine dataset type. Please specify it explicitly."
|
|
102
|
+
)
|
|
103
|
+
if self._type == "table":
|
|
104
|
+
path = f"{directory}.{table}"
|
|
105
|
+
self._database = ".".join(path.split(".")[:-1])
|
|
106
|
+
self._table = table
|
|
107
|
+
self._full_table_address = f"{self._database}.{self._table}"
|
|
108
|
+
else:
|
|
109
|
+
path = f"{directory}/{table}"
|
|
110
|
+
fs_prefix, filepath = _split_filepath(path)
|
|
111
|
+
self._fs_prefix = fs_prefix
|
|
112
|
+
self._filepath = PurePosixPath(filepath)
|
|
113
|
+
self._full_table_address = (
|
|
114
|
+
f"delta.`{self._fs_prefix + str(self._filepath)}`"
|
|
115
|
+
)
|
|
116
|
+
self._type = "file"
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _get_spark() -> SparkSession:
|
|
120
|
+
"""Get the active Spark session."""
|
|
121
|
+
if hasattr(get_kedro_context(), "spark_session"):
|
|
122
|
+
return get_kedro_context().spark_session
|
|
123
|
+
else:
|
|
124
|
+
SparkSession.getActiveSession()
|
|
125
|
+
|
|
126
|
+
def _load(self) -> DeltaTable:
|
|
127
|
+
return DeltaTable.forName(self._get_spark(), self._full_table_address)
|
|
128
|
+
|
|
129
|
+
def _save(self, data: None) -> NoReturn:
|
|
130
|
+
raise DataSetError(f"{self.__class__.__name__} is a read only dataset type")
|
|
131
|
+
|
|
132
|
+
def _exists(self) -> bool:
|
|
133
|
+
# noqa # pylint:disable=protected-access
|
|
134
|
+
if self._type == "table":
|
|
135
|
+
return (
|
|
136
|
+
self._get_spark()
|
|
137
|
+
._jsparkSession.catalog()
|
|
138
|
+
.tableExists(self._database, self._table)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
load_path = _strip_dbfs_prefix(self._fs_prefix + str(self._filepath))
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
self._get_spark().read.load(path=load_path, format="delta")
|
|
145
|
+
except AnalysisException as exception:
|
|
146
|
+
if "is not a Delta table" in exception.desc:
|
|
147
|
+
return False
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
def _describe(self):
|
|
153
|
+
return {
|
|
154
|
+
"full_table_address": self._full_table_address,
|
|
155
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import mlflow
|
|
5
|
+
from kedro.io import AbstractDataSet, DataSetError
|
|
6
|
+
from mlflow.tracking import MlflowClient
|
|
7
|
+
from kedro_mlflow.io.models.mlflow_model_logger_dataset import (
|
|
8
|
+
MlflowModelLoggerDataSet
|
|
9
|
+
)
|
|
10
|
+
from kedro_sumz.utils.mlflow import ModelWithConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MlflowModelWithConfigDataSet(MlflowModelLoggerDataSet):
|
|
14
|
+
"""
|
|
15
|
+
Wrapper for saving, logging and loading for all MLFlow model flavor
|
|
16
|
+
including its configuration
|
|
17
|
+
"""
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
flavor: str,
|
|
21
|
+
run_id: Optional[str] = None,
|
|
22
|
+
artifact_path: Optional[str] = "model",
|
|
23
|
+
pyfunc_workflow: Optional[str] = None,
|
|
24
|
+
load_args: Optional[Dict[str, Any]] = None,
|
|
25
|
+
save_args: Optional[Dict[str, Any]] = None
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Initializes MlflowModelWithConfigDataSet
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
flavor (str): Flavor of the model
|
|
31
|
+
run_id (Optional[str], optional): Mlflow run id. Defaults to None.
|
|
32
|
+
artifact_path (Optional[str], optional): Path of the model artifact. Defaults to "model".
|
|
33
|
+
pyfunc_workflow (Optional[str], optional): Name of the pyfunc model. Defaults to None.
|
|
34
|
+
load_args (Optional[Dict[str, Any]], optional): Load args used in the
|
|
35
|
+
mlflow load_model function. Defaults to None.
|
|
36
|
+
save_args (Optional[Dict[str, Any]], optional): Save args used in the
|
|
37
|
+
mlflow log_model function. Defaults to None.
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(flavor, run_id, artifact_path, pyfunc_workflow, load_args, save_args)
|
|
40
|
+
|
|
41
|
+
def _save_model_in_run(self, model: ModelWithConfig):
|
|
42
|
+
"""
|
|
43
|
+
Saves a model in a run as an artifact
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
model (ModelWithConfig): Object which stores the model and its configuration
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
DataSetError: When the object passed is not an instance of the ModelWithConfig class
|
|
50
|
+
"""
|
|
51
|
+
if not isinstance(model, ModelWithConfig):
|
|
52
|
+
raise DataSetError(
|
|
53
|
+
"The model to be logged must be an instance of the ModelWithConfig class"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if not self._logging_activated:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
save_args = self._prepare_save_args(
|
|
60
|
+
model_with_config=model
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if self._flavor == "mlflow.pyfunc":
|
|
64
|
+
# PyFunc models utilise either `python_model` or `loader_module`
|
|
65
|
+
# workflow. We we assign the passed `model` object to one of those keys
|
|
66
|
+
# depending on the chosen `pyfunc_workflow`.
|
|
67
|
+
save_args[self._pyfunc_workflow] = model.model
|
|
68
|
+
self._mlflow_model_module.log_model(
|
|
69
|
+
**save_args
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
# Otherwise we save using the common workflow
|
|
73
|
+
self._mlflow_model_module.log_model(
|
|
74
|
+
model.model,
|
|
75
|
+
**save_args
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def _prepare_save_args(
|
|
79
|
+
self,
|
|
80
|
+
model_with_config: ModelWithConfig
|
|
81
|
+
) -> Dict[str, Any]:
|
|
82
|
+
save_args = deepcopy(self._save_args)
|
|
83
|
+
|
|
84
|
+
save_args["signature"] = self._save_arg_value(
|
|
85
|
+
catalog_save_arg=self._save_args.get("signature"),
|
|
86
|
+
model_with_config_arg=model_with_config.signature
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
save_args["conda_env"] = self._save_arg_value(
|
|
90
|
+
catalog_save_arg=self._save_args.get("conda_env"),
|
|
91
|
+
model_with_config_arg=model_with_config.conda_env
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
save_args["pip_requirements"] = self._save_arg_value(
|
|
95
|
+
catalog_save_arg=self._save_args.get("pip_requirements"),
|
|
96
|
+
model_with_config_arg=model_with_config.pip_requirements
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
save_args["extra_pip_requirements"] = self._save_arg_value(
|
|
100
|
+
catalog_save_arg=self._save_args.get("extra_pip_requirements"),
|
|
101
|
+
model_with_config_arg=model_with_config.extra_pip_requirements
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
save_args["artifact_path"] = self._artifact_path
|
|
105
|
+
|
|
106
|
+
return save_args
|
|
107
|
+
|
|
108
|
+
def _save_arg_value(
|
|
109
|
+
self,
|
|
110
|
+
catalog_save_arg: Any,
|
|
111
|
+
model_with_config_arg: Any
|
|
112
|
+
)->Any:
|
|
113
|
+
if catalog_save_arg is None and model_with_config_arg is None:
|
|
114
|
+
return None
|
|
115
|
+
elif catalog_save_arg is None:
|
|
116
|
+
return model_with_config_arg
|
|
117
|
+
else:
|
|
118
|
+
return catalog_save_arg
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class MlflowMetricsDictDataSet(AbstractDataSet):
|
|
122
|
+
"""This class represent MLflow metrics dictionary dataset."""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
prefix: str = None,
|
|
127
|
+
run_id: str = None,
|
|
128
|
+
):
|
|
129
|
+
"""Initialise MlflowMetricsDictDataSet.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
prefix (str, optional): Prefix for metrics logged in MLflow. Defaults to None
|
|
133
|
+
run_id (str, optional): ID of MLflow run. Defaults to None
|
|
134
|
+
"""
|
|
135
|
+
self._prefix = prefix
|
|
136
|
+
self.run_id = run_id
|
|
137
|
+
self._logging_activated = True # by default, logging is activated!
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def run_id(self):
|
|
141
|
+
"""Get run id.
|
|
142
|
+
|
|
143
|
+
If active run is not found, tries to find last experiment.
|
|
144
|
+
|
|
145
|
+
Raise `DataSetError` exception if run id can't be found.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
str: String contains run_id.
|
|
149
|
+
"""
|
|
150
|
+
if self._run_id is not None:
|
|
151
|
+
return self._run_id
|
|
152
|
+
run = mlflow.active_run()
|
|
153
|
+
if run:
|
|
154
|
+
return run.info.run_id
|
|
155
|
+
raise DataSetError("Cannot find run id.")
|
|
156
|
+
|
|
157
|
+
@run_id.setter
|
|
158
|
+
def run_id(self, run_id):
|
|
159
|
+
self._run_id = run_id
|
|
160
|
+
|
|
161
|
+
# we want to be able to turn logging off for an entire pipeline run
|
|
162
|
+
# To avoid that a single call to a dataset in the catalog creates a new run automatically
|
|
163
|
+
# we want to be able to turn everything off
|
|
164
|
+
@property
|
|
165
|
+
def _logging_activated(self):
|
|
166
|
+
return self.__logging_activated
|
|
167
|
+
|
|
168
|
+
@_logging_activated.setter
|
|
169
|
+
def _logging_activated(self, flag):
|
|
170
|
+
if not isinstance(flag, bool):
|
|
171
|
+
raise ValueError(f"_logging_activated must be a boolean, got {type(flag)}")
|
|
172
|
+
self.__logging_activated = flag
|
|
173
|
+
|
|
174
|
+
def _load(self) -> Dict[str, float]:
|
|
175
|
+
"""Load MlflowMetricsDictDataSet.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Dict[str, float]: Dictionary with MLflow metrics.
|
|
179
|
+
"""
|
|
180
|
+
all_metrics = self._get_all_metrics()
|
|
181
|
+
|
|
182
|
+
dataset_metrics = self._filter_dataset_metrics(all_metrics)
|
|
183
|
+
|
|
184
|
+
if not dataset_metrics:
|
|
185
|
+
raise DataSetError(
|
|
186
|
+
"Tried to load an empty metrics dictionary"
|
|
187
|
+
f"be sure that metrics with the prefix '{self._prefix}' exists"
|
|
188
|
+
"in the current run"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return dataset_metrics
|
|
192
|
+
|
|
193
|
+
def _save(self, data: Dict[str, float]) -> None:
|
|
194
|
+
"""
|
|
195
|
+
Save given MLflow metrics dictionary dataset
|
|
196
|
+
and log it in MLflow as metrics.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
data (Dict[str, float]): Metrics dictionary.
|
|
200
|
+
"""
|
|
201
|
+
if not data:
|
|
202
|
+
raise DataSetError(
|
|
203
|
+
"Metrics dictionary should have at least one metric"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
client = MlflowClient()
|
|
207
|
+
try:
|
|
208
|
+
run_id = self.run_id
|
|
209
|
+
except DataSetError:
|
|
210
|
+
# If run_id can't be found log_metric would create new run.
|
|
211
|
+
run_id = None
|
|
212
|
+
|
|
213
|
+
log_metric = (
|
|
214
|
+
partial(client.log_metric, run_id)
|
|
215
|
+
if run_id is not None
|
|
216
|
+
else mlflow.log_metric
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if not all([isinstance(value, float) for value in data.values()]):
|
|
220
|
+
raise DataSetError(f"All metric values should be of type `float`")
|
|
221
|
+
|
|
222
|
+
metrics = {f"{self._prefix}.{key}": value for key, value in data.items()}
|
|
223
|
+
|
|
224
|
+
if self._logging_activated:
|
|
225
|
+
for k, v in metrics.items():
|
|
226
|
+
log_metric(k, v)
|
|
227
|
+
|
|
228
|
+
def _exists(self) -> bool:
|
|
229
|
+
"""Check if MLflow metrics dataset exists.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
bool: Is MLflow metrics dataset exists?
|
|
233
|
+
"""
|
|
234
|
+
all_metrics = self._get_all_metrics()
|
|
235
|
+
dataset_metrics = self._filter_dataset_metrics(all_metrics)
|
|
236
|
+
return len(dataset_metrics.keys())>0
|
|
237
|
+
|
|
238
|
+
def _describe(self) -> Dict[str, Any]:
|
|
239
|
+
"""Describe MLflow metrics dataset.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Dict[str, Any]: Dictionary with MLflow metrics
|
|
243
|
+
dictionary dataset description.
|
|
244
|
+
"""
|
|
245
|
+
return {
|
|
246
|
+
"run_id": self._run_id,
|
|
247
|
+
"prefix": self._prefix,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
def _filter_dataset_metrics(self, metrics: Dict[str, float]) -> Dict[str, float]:
|
|
251
|
+
"""
|
|
252
|
+
Filter a metric dictionary by keeping only those that
|
|
253
|
+
belongs to the dataset
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
metric (Dict[str, float]): Metrics dictionary.
|
|
257
|
+
Returns:
|
|
258
|
+
Dict[str, float]: Dictionary with the metrics that belongs
|
|
259
|
+
to the dataset
|
|
260
|
+
"""
|
|
261
|
+
dataset_metrics = {
|
|
262
|
+
k: v for k, v in metrics.items()
|
|
263
|
+
if k.startswith(self._prefix)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return dataset_metrics
|
|
267
|
+
|
|
268
|
+
def _get_all_metrics(self)->Dict[str, float]:
|
|
269
|
+
"""Retrieve all metrics logged in the current run
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dict[str, float]: Dictionary with all the metrics logged
|
|
273
|
+
in the current run
|
|
274
|
+
"""
|
|
275
|
+
client = MlflowClient()
|
|
276
|
+
run = client.get_run(self.run_id)
|
|
277
|
+
return run.data.metrics
|