mloda 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl
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.
- mloda/core/abstract_plugins/components/{feature_group_version.py → base_feature_group_version.py} +1 -1
- mloda/core/abstract_plugins/compute_framework.py +1 -1
- mloda/core/abstract_plugins/feature_group.py +3 -3
- mloda/core/api/feature_config/__init__.py +15 -0
- {mloda_plugins/config/feature → mloda/core/api/feature_config}/loader.py +19 -62
- {mloda_plugins/config/feature → mloda/core/api/feature_config}/models.py +2 -2
- {mloda_plugins/config/feature → mloda/core/api/feature_config}/parser.py +1 -1
- mloda/core/api/request.py +6 -1
- mloda/provider/__init__.py +2 -2
- mloda/provider/py.typed +0 -0
- mloda/steward/py.typed +0 -0
- mloda/user/__init__.py +10 -2
- mloda/user/py.typed +0 -0
- mloda-0.4.3.dist-info/METADATA +314 -0
- {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/RECORD +84 -81
- mloda_plugins/compute_framework/base_implementations/duckdb/duckdb_framework.py +1 -1
- mloda_plugins/compute_framework/base_implementations/iceberg/iceberg_framework.py +1 -1
- mloda_plugins/compute_framework/base_implementations/pandas/dataframe.py +1 -1
- mloda_plugins/compute_framework/base_implementations/polars/dataframe.py +1 -1
- mloda_plugins/compute_framework/base_implementations/pyarrow/table.py +1 -1
- mloda_plugins/compute_framework/base_implementations/python_dict/python_dict_framework.py +1 -1
- mloda_plugins/compute_framework/base_implementations/spark/spark_framework.py +1 -1
- mloda_plugins/feature_group/experimental/aggregated_feature_group/base.py +2 -2
- mloda_plugins/feature_group/experimental/aggregated_feature_group/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/aggregated_feature_group/polars_lazy.py +1 -1
- mloda_plugins/feature_group/experimental/aggregated_feature_group/pyarrow.py +1 -1
- mloda_plugins/feature_group/experimental/clustering/base.py +2 -2
- mloda_plugins/feature_group/experimental/clustering/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/data_quality/missing_value/base.py +5 -5
- mloda_plugins/feature_group/experimental/data_quality/missing_value/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/data_quality/missing_value/pyarrow.py +1 -1
- mloda_plugins/feature_group/experimental/data_quality/missing_value/python_dict.py +1 -1
- mloda_plugins/feature_group/experimental/dimensionality_reduction/base.py +3 -3
- mloda_plugins/feature_group/experimental/dimensionality_reduction/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/dynamic_feature_group_factory/dynamic_feature_group_factory.py +4 -4
- mloda_plugins/feature_group/experimental/forecasting/base.py +3 -3
- mloda_plugins/feature_group/experimental/forecasting/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/geo_distance/base.py +3 -3
- mloda_plugins/feature_group/experimental/geo_distance/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/llm/cli.py +1 -1
- mloda_plugins/feature_group/experimental/llm/cli_features/refactor_git_cached.py +4 -4
- mloda_plugins/feature_group/experimental/llm/installed_packages_feature_group.py +5 -5
- mloda_plugins/feature_group/experimental/llm/list_directory_feature_group.py +2 -2
- mloda_plugins/feature_group/experimental/llm/llm_api/claude.py +11 -11
- mloda_plugins/feature_group/experimental/llm/llm_api/gemini.py +10 -10
- mloda_plugins/feature_group/experimental/llm/llm_api/llm_base_request.py +2 -2
- mloda_plugins/feature_group/experimental/llm/llm_api/openai.py +11 -11
- mloda_plugins/feature_group/experimental/llm/llm_api/request_loop.py +2 -2
- mloda_plugins/feature_group/experimental/llm/llm_file_selector.py +9 -9
- mloda_plugins/feature_group/experimental/node_centrality/base.py +2 -2
- mloda_plugins/feature_group/experimental/node_centrality/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/sklearn/encoding/base.py +8 -8
- mloda_plugins/feature_group/experimental/sklearn/encoding/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/sklearn/pipeline/base.py +3 -3
- mloda_plugins/feature_group/experimental/sklearn/pipeline/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/sklearn/scaling/base.py +2 -2
- mloda_plugins/feature_group/experimental/sklearn/scaling/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/source_input_feature.py +3 -3
- mloda_plugins/feature_group/experimental/text_cleaning/base.py +2 -2
- mloda_plugins/feature_group/experimental/text_cleaning/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/text_cleaning/python_dict.py +1 -1
- mloda_plugins/feature_group/experimental/time_window/base.py +3 -3
- mloda_plugins/feature_group/experimental/time_window/pandas.py +1 -1
- mloda_plugins/feature_group/experimental/time_window/pyarrow.py +1 -1
- mloda_plugins/feature_group/input_data/api_data/api_data.py +27 -27
- mloda_plugins/feature_group/input_data/read_context_files.py +3 -3
- mloda_plugins/feature_group/input_data/read_db.py +1 -1
- mloda_plugins/feature_group/input_data/read_db_feature.py +1 -1
- mloda_plugins/feature_group/input_data/read_dbs/sqlite.py +4 -4
- mloda_plugins/feature_group/input_data/read_file.py +1 -1
- mloda_plugins/feature_group/input_data/read_file_feature.py +1 -1
- mloda_plugins/feature_group/input_data/read_files/csv.py +4 -4
- mloda_plugins/feature_group/input_data/read_files/feather.py +4 -4
- mloda_plugins/feature_group/input_data/read_files/json.py +4 -4
- mloda_plugins/feature_group/input_data/read_files/orc.py +4 -4
- mloda_plugins/feature_group/input_data/read_files/parquet.py +4 -4
- mloda_plugins/feature_group/input_data/read_files/text_file_reader.py +4 -4
- mloda_plugins/py.typed +0 -0
- mloda/__init__.py +0 -17
- mloda-0.4.1.dist-info/METADATA +0 -384
- mloda_plugins/config/__init__.py +0 -1
- /mloda_plugins/config/feature/__init__.py → /mloda/core/py.typed +0 -0
- {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/WHEEL +0 -0
- {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/entry_points.txt +0 -0
- {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/licenses/LICENSE.TXT +0 -0
- {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/licenses/NOTICE.md +0 -0
- {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/top_level.txt +0 -0
|
@@ -8,7 +8,7 @@ from mloda.core.abstract_plugins.components.data_access_collection import DataAc
|
|
|
8
8
|
from mloda.core.abstract_plugins.components.data_types import DataType
|
|
9
9
|
|
|
10
10
|
from mloda.core.abstract_plugins.components.domain import Domain
|
|
11
|
-
from mloda.core.abstract_plugins.components.
|
|
11
|
+
from mloda.core.abstract_plugins.components.base_feature_group_version import BaseFeatureGroupVersion
|
|
12
12
|
from mloda.core.abstract_plugins.components.feature_name import FeatureName
|
|
13
13
|
from mloda.core.abstract_plugins.components.input_data.api.api_input_data import ApiInputData
|
|
14
14
|
from mloda.core.abstract_plugins.components.input_data.base_input_data import BaseInputData
|
|
@@ -68,9 +68,9 @@ class FeatureGroup(ABC):
|
|
|
68
68
|
making it easier to detect changes, manage compatibility, and debug issues.
|
|
69
69
|
|
|
70
70
|
If you need to change the version of the feature group, you can do so by subclassing
|
|
71
|
-
|
|
71
|
+
BaseFeatureGroupVersion and overriding the version method. This allows you to create a new version system.
|
|
72
72
|
"""
|
|
73
|
-
return
|
|
73
|
+
return BaseFeatureGroupVersion.version(cls)
|
|
74
74
|
|
|
75
75
|
@classmethod
|
|
76
76
|
def input_data(cls) -> Optional[BaseInputData]:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Feature configuration loading from JSON.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for loading feature configurations from JSON files.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from mloda.core.api.feature_config.loader import load_features_from_config
|
|
7
|
+
from mloda.core.api.feature_config.models import FeatureConfig, feature_config_schema
|
|
8
|
+
from mloda.core.api.feature_config.parser import parse_json
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"load_features_from_config",
|
|
12
|
+
"FeatureConfig",
|
|
13
|
+
"feature_config_schema",
|
|
14
|
+
"parse_json",
|
|
15
|
+
]
|
|
@@ -6,10 +6,10 @@ to mloda Feature instances.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from typing import List, Union, Dict, Any
|
|
9
|
-
from mloda import Feature
|
|
10
|
-
from mloda import Options
|
|
11
|
-
from
|
|
12
|
-
from
|
|
9
|
+
from mloda.user import Feature
|
|
10
|
+
from mloda.user import Options
|
|
11
|
+
from mloda.core.api.feature_config.parser import parse_json
|
|
12
|
+
from mloda.core.api.feature_config.models import FeatureConfig
|
|
13
13
|
from mloda_plugins.feature_group.experimental.default_options_key import DefaultOptionKeys
|
|
14
14
|
|
|
15
15
|
|
|
@@ -34,20 +34,18 @@ def process_nested_features(options: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
34
34
|
nested_options = value.get("options", {})
|
|
35
35
|
processed_nested_options = process_nested_features(nested_options)
|
|
36
36
|
|
|
37
|
-
# Handle nested
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
if isinstance(
|
|
37
|
+
# Handle nested in_features (can also be a dict)
|
|
38
|
+
in_features = value.get("in_features")
|
|
39
|
+
if in_features:
|
|
40
|
+
if isinstance(in_features, list):
|
|
41
41
|
# For list, convert each to string (single sources) or keep as-is
|
|
42
|
-
processed_nested_options["in_features"] = (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Recursively create Feature for mloda_sources
|
|
47
|
-
in_features = process_nested_features({"in_features": mloda_sources})["in_features"]
|
|
42
|
+
processed_nested_options["in_features"] = in_features if len(in_features) > 1 else in_features[0]
|
|
43
|
+
elif isinstance(in_features, dict):
|
|
44
|
+
# Recursively create Feature for in_features
|
|
45
|
+
in_features = process_nested_features({"in_features": in_features})["in_features"]
|
|
48
46
|
processed_nested_options["in_features"] = in_features
|
|
49
47
|
else:
|
|
50
|
-
processed_nested_options["in_features"] =
|
|
48
|
+
processed_nested_options["in_features"] = in_features
|
|
51
49
|
|
|
52
50
|
# Create the Feature object
|
|
53
51
|
processed[key] = Feature(name=feature_name, options=processed_nested_options)
|
|
@@ -63,10 +61,6 @@ def process_nested_features(options: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
63
61
|
def load_features_from_config(config_str: str, format: str = "json") -> List[Union[Feature, str]]:
|
|
64
62
|
"""Load features from a configuration string.
|
|
65
63
|
|
|
66
|
-
Uses a two-pass strategy to support feature references:
|
|
67
|
-
- Pass 1: Create all Feature objects and build a name registry
|
|
68
|
-
- Pass 2: Resolve @feature_name references to actual Feature objects
|
|
69
|
-
|
|
70
64
|
Args:
|
|
71
65
|
config_str: Configuration string in the specified format
|
|
72
66
|
format: Configuration format (currently only "json" is supported)
|
|
@@ -79,16 +73,11 @@ def load_features_from_config(config_str: str, format: str = "json") -> List[Uni
|
|
|
79
73
|
|
|
80
74
|
config_items = parse_json(config_str)
|
|
81
75
|
|
|
82
|
-
# Pass 1: Create all Feature objects and build registry
|
|
83
76
|
features: List[Union[Feature, str]] = []
|
|
84
|
-
feature_registry: Dict[str, Feature] = {}
|
|
85
77
|
|
|
86
78
|
for item in config_items:
|
|
87
79
|
if isinstance(item, str):
|
|
88
|
-
# Create a Feature object for string entries so they can be referenced
|
|
89
|
-
feature = Feature(name=item, options={})
|
|
90
80
|
features.append(item)
|
|
91
|
-
feature_registry[item] = feature
|
|
92
81
|
elif isinstance(item, FeatureConfig):
|
|
93
82
|
# Build feature name with column index suffix if present
|
|
94
83
|
feature_name = item.name
|
|
@@ -99,60 +88,28 @@ def load_features_from_config(config_str: str, format: str = "json") -> List[Uni
|
|
|
99
88
|
if item.group_options is not None or item.context_options is not None:
|
|
100
89
|
# Use new Options architecture with group/context separation
|
|
101
90
|
context = item.context_options or {}
|
|
102
|
-
# Handle
|
|
103
|
-
if item.
|
|
91
|
+
# Handle in_features if present
|
|
92
|
+
if item.in_features:
|
|
104
93
|
# Always convert to frozenset for consistency
|
|
105
|
-
context[DefaultOptionKeys.in_features] = frozenset(item.
|
|
94
|
+
context[DefaultOptionKeys.in_features] = frozenset(item.in_features)
|
|
106
95
|
options = Options(group=item.group_options or {}, context=context)
|
|
107
96
|
feature = Feature(name=feature_name, options=options)
|
|
108
97
|
features.append(feature)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
elif item.mloda_sources:
|
|
98
|
+
# Check if in_features exists and create Options accordingly
|
|
99
|
+
elif item.in_features:
|
|
112
100
|
# Process nested features in options before creating Feature
|
|
113
101
|
processed_options = process_nested_features(item.options)
|
|
114
102
|
# Always convert to frozenset for consistency (even single items)
|
|
115
|
-
source_value = frozenset(item.
|
|
103
|
+
source_value = frozenset(item.in_features)
|
|
116
104
|
options = Options(group=processed_options, context={DefaultOptionKeys.in_features: source_value})
|
|
117
105
|
feature = Feature(name=feature_name, options=options)
|
|
118
106
|
features.append(feature)
|
|
119
|
-
feature_registry[feature_name] = feature
|
|
120
107
|
else:
|
|
121
108
|
# Process nested features in options before creating Feature
|
|
122
109
|
processed_options = process_nested_features(item.options)
|
|
123
110
|
feature = Feature(name=feature_name, options=processed_options)
|
|
124
111
|
features.append(feature)
|
|
125
|
-
feature_registry[feature_name] = feature
|
|
126
112
|
else:
|
|
127
113
|
raise ValueError(f"Unexpected config item type: {type(item)}")
|
|
128
114
|
|
|
129
|
-
# Pass 2: Resolve @feature_name references to Feature objects
|
|
130
|
-
for feat in features:
|
|
131
|
-
if isinstance(feat, Feature):
|
|
132
|
-
mloda_source = feat.options.context.get(DefaultOptionKeys.in_features)
|
|
133
|
-
if mloda_source:
|
|
134
|
-
# Handle both single string and frozenset of strings
|
|
135
|
-
if isinstance(mloda_source, str) and mloda_source.startswith("@"):
|
|
136
|
-
# Single reference string
|
|
137
|
-
referenced_name = mloda_source[1:]
|
|
138
|
-
if referenced_name in feature_registry:
|
|
139
|
-
feat.options.context[DefaultOptionKeys.in_features] = feature_registry[referenced_name]
|
|
140
|
-
else:
|
|
141
|
-
raise ValueError(f"Feature reference '@{referenced_name}' not found in configuration")
|
|
142
|
-
elif isinstance(mloda_source, frozenset):
|
|
143
|
-
# Frozenset of sources - resolve any @ references
|
|
144
|
-
resolved_sources = []
|
|
145
|
-
for source in mloda_source:
|
|
146
|
-
if isinstance(source, str) and source.startswith("@"):
|
|
147
|
-
referenced_name = source[1:]
|
|
148
|
-
if referenced_name in feature_registry:
|
|
149
|
-
resolved_sources.append(feature_registry[referenced_name])
|
|
150
|
-
else:
|
|
151
|
-
raise ValueError(f"Feature reference '@{referenced_name}' not found in configuration")
|
|
152
|
-
else:
|
|
153
|
-
resolved_sources.append(source)
|
|
154
|
-
# Only replace if we actually resolved any references
|
|
155
|
-
if any(isinstance(s, str) and s.startswith("@") for s in mloda_source):
|
|
156
|
-
feat.options.context[DefaultOptionKeys.in_features] = frozenset(resolved_sources)
|
|
157
|
-
|
|
158
115
|
return features
|
|
@@ -15,7 +15,7 @@ class FeatureConfig:
|
|
|
15
15
|
|
|
16
16
|
name: str
|
|
17
17
|
options: Dict[str, Any] = field(default_factory=dict)
|
|
18
|
-
|
|
18
|
+
in_features: Optional[List[str]] = None
|
|
19
19
|
group_options: Optional[Dict[str, Any]] = None
|
|
20
20
|
context_options: Optional[Dict[str, Any]] = None
|
|
21
21
|
column_index: Optional[int] = None
|
|
@@ -37,7 +37,7 @@ def feature_config_schema() -> Dict[str, Any]:
|
|
|
37
37
|
"properties": {
|
|
38
38
|
"name": {"type": "string"},
|
|
39
39
|
"options": {"type": "object", "default": {}},
|
|
40
|
-
"
|
|
40
|
+
"in_features": {"type": "array", "items": {"type": "string"}},
|
|
41
41
|
"group_options": {"type": "object"},
|
|
42
42
|
"context_options": {"type": "object"},
|
|
43
43
|
"column_index": {"type": "integer"},
|
|
@@ -6,7 +6,7 @@ This module provides parsers for configuration files in JSON format.
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
from typing import List
|
|
9
|
-
from
|
|
9
|
+
from mloda.core.api.feature_config.models import FeatureConfig, FeatureConfigItem
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def parse_json(config_str: str) -> List[FeatureConfigItem]:
|
mloda/core/api/request.py
CHANGED
|
@@ -20,6 +20,11 @@ from mloda_plugins.feature_group.experimental.default_options_key import Default
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class mlodaAPI:
|
|
23
|
+
"""Main API for executing mloda feature requests.
|
|
24
|
+
|
|
25
|
+
For JSON-based feature configuration, see `load_features_from_config()`.
|
|
26
|
+
"""
|
|
27
|
+
|
|
23
28
|
def __init__(
|
|
24
29
|
self,
|
|
25
30
|
requested_features: Union[Features, list[Union[Feature, str]]],
|
|
@@ -112,7 +117,7 @@ class mlodaAPI:
|
|
|
112
117
|
List of computed results.
|
|
113
118
|
|
|
114
119
|
Example:
|
|
115
|
-
result =
|
|
120
|
+
result = mlodamloda.run_all(
|
|
116
121
|
features,
|
|
117
122
|
api_data={"UserQuery": {"row_index": [0], "query": ["hello"]}}
|
|
118
123
|
)
|
mloda/provider/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from mloda.core.abstract_plugins.feature_group import FeatureGroup as FeatureGroup
|
|
3
3
|
|
|
4
4
|
# Versioning
|
|
5
|
-
from mloda.core.abstract_plugins.components.
|
|
5
|
+
from mloda.core.abstract_plugins.components.base_feature_group_version import BaseFeatureGroupVersion
|
|
6
6
|
from mloda.core.abstract_plugins.compute_framework import ComputeFramework as ComputeFramework
|
|
7
7
|
|
|
8
8
|
# Utilities
|
|
@@ -60,7 +60,7 @@ __all__ = [
|
|
|
60
60
|
# Base classes
|
|
61
61
|
"FeatureGroup",
|
|
62
62
|
# Versioning
|
|
63
|
-
"
|
|
63
|
+
"BaseFeatureGroupVersion",
|
|
64
64
|
"ComputeFramework",
|
|
65
65
|
# Utilities
|
|
66
66
|
"HashableDict",
|
mloda/provider/py.typed
ADDED
|
File without changes
|
mloda/steward/py.typed
ADDED
|
File without changes
|
mloda/user/__init__.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# API
|
|
2
|
-
from mloda.core.api.request import mlodaAPI
|
|
2
|
+
from mloda.core.api.request import mlodaAPI
|
|
3
|
+
|
|
4
|
+
mloda = mlodaAPI
|
|
3
5
|
|
|
4
6
|
# Features
|
|
5
7
|
from mloda.core.abstract_plugins.components.feature import Feature
|
|
@@ -28,9 +30,13 @@ from mloda.core.abstract_plugins.components.parallelization_modes import Paralle
|
|
|
28
30
|
from mloda.core.abstract_plugins.plugin_loader.plugin_loader import PluginLoader
|
|
29
31
|
from mloda.core.abstract_plugins.components.plugin_option.plugin_collector import PluginCollector
|
|
30
32
|
|
|
33
|
+
# Config loading
|
|
34
|
+
from mloda.core.api.feature_config.loader import load_features_from_config
|
|
35
|
+
|
|
31
36
|
__all__ = [
|
|
32
37
|
# API
|
|
33
|
-
"
|
|
38
|
+
"mlodaAPI",
|
|
39
|
+
"mloda",
|
|
34
40
|
# Features
|
|
35
41
|
"Feature",
|
|
36
42
|
"Features",
|
|
@@ -54,4 +60,6 @@ __all__ = [
|
|
|
54
60
|
# Plugin discovery
|
|
55
61
|
"PluginLoader",
|
|
56
62
|
"PluginCollector",
|
|
63
|
+
# Config loading
|
|
64
|
+
"load_features_from_config",
|
|
57
65
|
]
|
mloda/user/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mloda
|
|
3
|
+
Version: 0.4.3
|
|
4
|
+
Summary: mloda: One Data Access for ML and AI
|
|
5
|
+
Author-email: Tom Kaltofen <info@mloda.ai>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/mloda-ai/mloda/issues
|
|
8
|
+
Project-URL: Documentation, https://mloda-ai.github.io/mloda/
|
|
9
|
+
Project-URL: Source Code, https://github.com/mloda-ai/mloda
|
|
10
|
+
Project-URL: PyPI, https://pypi.org/project/mloda/
|
|
11
|
+
Project-URL: Homepage, https://mloda.ai
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: <3.14,>=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.TXT
|
|
16
|
+
License-File: NOTICE.md
|
|
17
|
+
Requires-Dist: pyarrow
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# [mloda.ai](https://mloda.ai): Open Data Access for ML & AI
|
|
21
|
+
|
|
22
|
+
[](https://mloda.ai)
|
|
23
|
+
[](https://mloda-ai.github.io/mloda/)
|
|
24
|
+
[](https://badge.fury.io/py/mloda)
|
|
25
|
+
[](https://github.com/mloda-ai/mloda/blob/main/LICENSE.TXT)
|
|
26
|
+
[](https://github.com/mloda-ai/mloda)
|
|
27
|
+
|
|
28
|
+
> **Declarative data access for AI agents. Describe what you need - mloda delivers it.**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install mloda
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 30-Second Example
|
|
35
|
+
|
|
36
|
+
Your AI describes what it needs. mloda figures out how to get it:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from mloda.user import PluginLoader, mloda
|
|
40
|
+
PluginLoader.all()
|
|
41
|
+
|
|
42
|
+
result = mloda.run_all(
|
|
43
|
+
features=["customer_id", "income", "income__sum_aggr", "age__avg_aggr"],
|
|
44
|
+
compute_frameworks=["PandasDataFrame"],
|
|
45
|
+
api_data={"SampleData": {
|
|
46
|
+
"customer_id": ["C001", "C002", "C003", "C004", "C005"],
|
|
47
|
+
"age": [25, 35, 45, 30, 50],
|
|
48
|
+
"income": [50000, 75000, 90000, 60000, 85000]
|
|
49
|
+
}}
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Copy, paste, run. mloda resolves dependencies, chains plugins, delivers data.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## What mloda Does
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
61
|
+
│ DATA USERS │
|
|
62
|
+
│ AI Agents • ML Pipelines • Data Science • Analytics │
|
|
63
|
+
└───────────────────────────┬─────────────────────────────────────┘
|
|
64
|
+
│ describe what they need
|
|
65
|
+
▼
|
|
66
|
+
┌───────────────┐
|
|
67
|
+
│ mloda │ ← resolves HOW from WHAT
|
|
68
|
+
│ [Plugins] │
|
|
69
|
+
└───────────────┘
|
|
70
|
+
│ delivers trusted data
|
|
71
|
+
▼
|
|
72
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
73
|
+
│ DATA SOURCES │
|
|
74
|
+
│ Databases • APIs • Files • Any source via plugins │
|
|
75
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Why mloda?
|
|
81
|
+
|
|
82
|
+
| You want to... | mloda gives you... |
|
|
83
|
+
|----------------|-------------------|
|
|
84
|
+
| Give AI agents data access | Declarative API - agents describe WHAT, not HOW |
|
|
85
|
+
| Trace every result | Built-in lineage back to source |
|
|
86
|
+
| Reuse across projects | Plugins work anywhere - notebook to production |
|
|
87
|
+
| Mix data sources | One interface for DBs, APIs, files, anything |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## AI Use Case: LLM Tool Function
|
|
92
|
+
|
|
93
|
+
Let LLMs request data without writing code:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# LLM generates this JSON
|
|
97
|
+
llm_request = '["customer_id", {"name": "income__sum_aggr"}]'
|
|
98
|
+
|
|
99
|
+
# mloda executes it
|
|
100
|
+
from mloda.user import load_features_from_config
|
|
101
|
+
features = load_features_from_config(llm_request, format="json")
|
|
102
|
+
result = mloda.run_all(
|
|
103
|
+
features=features,
|
|
104
|
+
compute_frameworks=["PandasDataFrame"],
|
|
105
|
+
api_data={"SampleData": {"customer_id": ["C001", "C002"], "income": [50000, 75000]}}
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
More patterns: [Context Window Assembly](#2-context-window-assembly) • [RAG Pipelines](#3-rag-with-feature-chaining)
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## How mloda is Different
|
|
114
|
+
|
|
115
|
+
mloda separates **WHAT** you need from **HOW** to get it - through plugins. Existing tools solve parts of this, but none bridge the full gap:
|
|
116
|
+
|
|
117
|
+
| Category | Products | What it does | Why it's not enough |
|
|
118
|
+
|----------|----------|--------------|---------------------|
|
|
119
|
+
| Feature Stores | Feast, Tecton, Featureform | Store + serve features | Infrastructure-tied, storage-only |
|
|
120
|
+
| Semantic Layers | dbt Semantic Layer, Cube | Declarative metrics | SQL-only, centralized |
|
|
121
|
+
| DAG Frameworks | Hamilton, Kedro | Dataflows as code | Function-first, no plugin abstraction |
|
|
122
|
+
| Data Catalogs | DataHub, Atlan | Metadata & discovery | No execution, no contracts |
|
|
123
|
+
| ORMs | SQLAlchemy, Django ORM | Database abstraction | Single database, no ML lifecycle |
|
|
124
|
+
|
|
125
|
+
**mloda is the connection layer** - separating WHAT you compute from HOW you compute it. Plugins define transformations. Users describe requirements. mloda resolves the pipeline.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Plugins: The Building Blocks
|
|
130
|
+
|
|
131
|
+
mloda's architecture follows three roles: **providers** (define plugins), **users** (access data), and **stewards** (govern execution). The module structure reflects this: `mloda.provider`, `mloda.user`, `mloda.steward`.
|
|
132
|
+
|
|
133
|
+
mloda uses three types of plugins:
|
|
134
|
+
|
|
135
|
+
| Type | What it does |
|
|
136
|
+
|------|--------------|
|
|
137
|
+
| **FeatureGroup** | Defines data transformations |
|
|
138
|
+
| **ComputeFramework** | Execution backend (Pandas, Spark, etc.) |
|
|
139
|
+
| **Extender** | Hooks for logging, validation, monitoring |
|
|
140
|
+
|
|
141
|
+
Most of the time, you'll work with **FeatureGroups** - Python classes that define how to access and transform data (see Quick Example above).
|
|
142
|
+
|
|
143
|
+
**Why plugins?**
|
|
144
|
+
- **Steps, not pipelines** - Build transformations. mloda wires them together.
|
|
145
|
+
- **Small and testable** - Each plugin is a focused unit. Easy to test, easy to debug.
|
|
146
|
+
- **AI-friendly** - Small, template-like structures. Let AI generate plugins for you.
|
|
147
|
+
- **Share what isn't secret** - Your pipeline runs steps a,b,c,d. Steps b,c,d have no proprietary logic? Share them across projects, teams, even organizations.
|
|
148
|
+
- **Experiment to production** - Same plugins in your notebook and your cluster. No rewrite.
|
|
149
|
+
- **Stand on shoulders** - Combine community plugins with your own. Build on what exists.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## AI Use Case Patterns
|
|
154
|
+
|
|
155
|
+
### 1. LLM Tool Function
|
|
156
|
+
|
|
157
|
+
Give LLMs deterministic data access - they declare what, mloda handles how:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from mloda.user import PluginLoader, load_features_from_config, mloda
|
|
161
|
+
PluginLoader.all()
|
|
162
|
+
|
|
163
|
+
# LLM generates this JSON (no Python code needed)
|
|
164
|
+
llm_output = '''
|
|
165
|
+
[
|
|
166
|
+
"customer_id",
|
|
167
|
+
{"name": "income__sum_aggr"},
|
|
168
|
+
{"name": "age__avg_aggr"},
|
|
169
|
+
{"name": "total_spend", "options": {"aggregation_type": "sum", "in_features": "income"}}
|
|
170
|
+
]
|
|
171
|
+
'''
|
|
172
|
+
|
|
173
|
+
# mloda parses JSON into Feature objects
|
|
174
|
+
features = load_features_from_config(llm_output, format="json")
|
|
175
|
+
|
|
176
|
+
result = mloda.run_all(
|
|
177
|
+
features=features,
|
|
178
|
+
compute_frameworks=["PandasDataFrame"],
|
|
179
|
+
api_data={"SampleData": {
|
|
180
|
+
"customer_id": ["C001", "C002", "C003"],
|
|
181
|
+
"income": [50000, 75000, 90000],
|
|
182
|
+
"age": [25, 35, 45]
|
|
183
|
+
}}
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**LLM-friendly:** The agent only declares what it needs - mloda handles the rest.
|
|
188
|
+
|
|
189
|
+
### 2. Context Window Assembly
|
|
190
|
+
|
|
191
|
+
Gather context from multiple sources declaratively - mloda validates and delivers. Why not let an AI agent do it?
|
|
192
|
+
|
|
193
|
+
*Example: This shows the API pattern. Requires custom FeatureGroup implementations for your data sources.*
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from mloda.user import Feature, mloda
|
|
197
|
+
|
|
198
|
+
# Build complete context from multiple sources
|
|
199
|
+
features = [
|
|
200
|
+
Feature(name="system_instructions", options={"template": "support_agent"}),
|
|
201
|
+
Feature(name="user_profile", options={"user_id": user_id, "include_preferences": True}),
|
|
202
|
+
Feature(name="knowledge_base", options={"query": user_query, "top_k": 5}),
|
|
203
|
+
Feature(name="conversation_history", options={"limit": 20, "summarize_old": True}),
|
|
204
|
+
Feature(name="available_tools", options={"category": "customer_service"}),
|
|
205
|
+
Feature(name="output_format", options={"format": "markdown", "max_length": 500}),
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
result = mloda.run_all(
|
|
209
|
+
features=features,
|
|
210
|
+
compute_frameworks=["PythonDictFramework"],
|
|
211
|
+
api_data={"UserQuery": {"query": [user_query]}}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Each feature resolved via its plugin, validated
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 3. RAG with Feature Chaining
|
|
218
|
+
|
|
219
|
+
Build RAG pipelines declaratively - mloda chains the steps for you.
|
|
220
|
+
|
|
221
|
+
*Example: This shows the chaining syntax. Requires custom FeatureGroup implementations for retrieval and processing.*
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
# String-based chaining: query -> validate -> retrieve -> redact
|
|
225
|
+
Feature(name="user_query__injection_checked__retrieved__pii_redacted")
|
|
226
|
+
|
|
227
|
+
# Configuration-based chaining: explicit pipeline
|
|
228
|
+
Feature(
|
|
229
|
+
name="safe_context",
|
|
230
|
+
options=Options(context={
|
|
231
|
+
"in_features": "documents__retrieved__pii_redacted",
|
|
232
|
+
"redact_types": ["email", "phone", "ssn"]
|
|
233
|
+
})
|
|
234
|
+
)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
mloda resolves the full chain - you declare the end result, not the steps.
|
|
238
|
+
|
|
239
|
+
**Automatic dependency resolution:** You only declare what you need. If `pii_redacted` depends on `retrieved` which depends on `documents`, just ask for `pii_redacted` - mloda traces back and resolves the full chain.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Compute Frameworks
|
|
244
|
+
|
|
245
|
+
Mix multiple backends in a single pipeline - mloda routes each feature to the right framework:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
result = mloda.run_all(
|
|
249
|
+
features=[...],
|
|
250
|
+
compute_frameworks=["PandasDataFrame", "PolarsDataFrame", "SparkFramework"]
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Results may come from different frameworks based on plugin compatibility
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Add your own frameworks - mloda is extensible.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Extenders
|
|
261
|
+
|
|
262
|
+
Wrap plugin execution for logging, validation, or lineage tracking:
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
import time
|
|
266
|
+
from mloda.steward import Extender, ExtenderHook
|
|
267
|
+
|
|
268
|
+
class LogExecutionTime(Extender):
|
|
269
|
+
def wraps(self):
|
|
270
|
+
return {ExtenderHook.FEATURE_GROUP_CALCULATE_FEATURE}
|
|
271
|
+
|
|
272
|
+
def __call__(self, func, *args, **kwargs):
|
|
273
|
+
start = time.time()
|
|
274
|
+
result = func(*args, **kwargs)
|
|
275
|
+
print(f"Took {time.time() - start:.2f}s")
|
|
276
|
+
return result
|
|
277
|
+
|
|
278
|
+
# Use it
|
|
279
|
+
result = mloda.run_all(features, function_extender={LogExecutionTime()})
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Built-in and custom extenders give you full lineage - trace any result back to its source.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## When to Use mloda
|
|
287
|
+
|
|
288
|
+
**Use mloda when:**
|
|
289
|
+
- Your agents need data from multiple sources
|
|
290
|
+
- You want consistent, validated data access
|
|
291
|
+
- You need traceability (audit, debugging)
|
|
292
|
+
- Multiple agents share the same data patterns
|
|
293
|
+
|
|
294
|
+
**Don't use mloda for:**
|
|
295
|
+
- Single database, simple queries → use an ORM
|
|
296
|
+
- One-off scripts → just write the code
|
|
297
|
+
- Real-time streaming (<5ms) → use Kafka/Flink
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Documentation
|
|
302
|
+
|
|
303
|
+
- **[Getting Started](https://mloda-ai.github.io/mloda/chapter1/installation/)** - Installation and first steps
|
|
304
|
+
- **[Plugin Development](https://mloda-ai.github.io/mloda/chapter1/feature-groups/)** - Build your own plugins
|
|
305
|
+
- **[API Reference](https://mloda-ai.github.io/mloda/in_depth/mloda-api/)** - Complete API docs
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Contributing
|
|
310
|
+
|
|
311
|
+
We welcome contributions! Build plugins, improve docs, or add features.
|
|
312
|
+
|
|
313
|
+
- **[GitHub Issues](https://github.com/mloda-ai/mloda/issues/)** - Report bugs or request features
|
|
314
|
+
- **[Development Guide](https://mloda-ai.github.io/mloda/development/)** - How to contribute
|