gitlabds 2.1.7__tar.gz → 2.1.8__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.
- {gitlabds-2.1.7/gitlabds.egg-info → gitlabds-2.1.8}/PKG-INFO +150 -10
- gitlabds-2.1.7/PKG-INFO → gitlabds-2.1.8/README.md +130 -22
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/__init__.py +15 -0
- gitlabds-2.1.8/gitlabds/serving_features.py +386 -0
- gitlabds-2.1.7/README.md → gitlabds-2.1.8/gitlabds.egg-info/PKG-INFO +162 -2
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds.egg-info/SOURCES.txt +3 -2
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds.egg-info/requires.txt +6 -1
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds.egg-info/top_level.txt +0 -1
- gitlabds-2.1.8/pyproject.toml +48 -0
- gitlabds-2.1.8/setup.py +11 -0
- gitlabds-2.1.7/setup.py +0 -41
- gitlabds-2.1.7/tests/__init__.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/LICENSE +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/baselines.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/config_generator.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/dummy.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/feature_reduction.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/insights.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/memory_optimization.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/missing.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/missing_check.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/missing_fill.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/model_evaluator.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/monitoring_metrics.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/outliers.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/split_data.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds/trends.py +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/gitlabds.egg-info/dependency_links.txt +0 -0
- {gitlabds-2.1.7 → gitlabds-2.1.8}/setup.cfg +0 -0
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlabds
|
|
3
|
-
Version: 2.1.
|
|
4
|
-
Summary:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Version: 2.1.8
|
|
4
|
+
Summary: GitLab Data Science Tools
|
|
5
|
+
Author-email: Kevin Dietz <kdietz@gitlab.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/gitlab-data/gitlabds
|
|
8
8
|
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
12
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
13
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
14
|
Classifier: Topic :: Software Development :: Libraries
|
|
16
15
|
Classifier: Topic :: Utilities
|
|
17
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.11
|
|
18
17
|
Description-Content-Type: text/markdown
|
|
19
18
|
License-File: LICENSE
|
|
19
|
+
Requires-Dist: pandas>=1.5.3
|
|
20
|
+
Requires-Dist: numpy>=1.23.5
|
|
21
|
+
Requires-Dist: scipy>=1.13.1
|
|
22
|
+
Requires-Dist: scikit-learn>=1.1.1
|
|
23
|
+
Requires-Dist: imbalanced-learn>=0.9.1
|
|
24
|
+
Requires-Dist: seaborn>=0.13.2
|
|
25
|
+
Requires-Dist: shap>=0.50.0
|
|
26
|
+
Requires-Dist: tqdm>=4.66.2
|
|
27
|
+
Requires-Dist: xgboost>=1.6.1
|
|
28
|
+
Provides-Extra: feature-store
|
|
29
|
+
Requires-Dist: snowflake-snowpark-python; extra == "feature-store"
|
|
30
|
+
Requires-Dist: snowflake-ml-python; extra == "feature-store"
|
|
31
|
+
Dynamic: license-file
|
|
20
32
|
|
|
21
33
|
[](https://badge.fury.io/py/gitlabds)
|
|
22
34
|
[](https://www.python.org/downloads/)
|
|
@@ -35,7 +47,7 @@ pip install gitlabds
|
|
|
35
47
|
|
|
36
48
|
### Requirements
|
|
37
49
|
|
|
38
|
-
- Python 3.
|
|
50
|
+
- Python 3.11 or later
|
|
39
51
|
- Core dependencies:
|
|
40
52
|
- pandas>=1.5.3
|
|
41
53
|
- numpy>=1.23.5
|
|
@@ -43,8 +55,27 @@ pip install gitlabds
|
|
|
43
55
|
- scikit-learn>=1.1.1
|
|
44
56
|
- imbalanced-learn>=0.9.1
|
|
45
57
|
- seaborn>=0.13.2
|
|
46
|
-
- shap>=0.
|
|
58
|
+
- shap>=0.50.0
|
|
47
59
|
- tqdm>=4.66.2
|
|
60
|
+
- xgboost>=1.6.1
|
|
61
|
+
|
|
62
|
+
### Optional Dependencies
|
|
63
|
+
|
|
64
|
+
#### Snowflake Feature Store
|
|
65
|
+
|
|
66
|
+
The `serve_features` and `clear_feature_serving_locks` functions require additional Snowflake dependencies.
|
|
67
|
+
|
|
68
|
+
To use these functions, install with:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install gitlabds[feature-store]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This will install:
|
|
75
|
+
- `snowflake-snowpark-python`
|
|
76
|
+
- `snowflake-ml-python`
|
|
77
|
+
|
|
78
|
+
If you try to use these functions without installing the optional dependencies, you'll get an error message with installation instructions.
|
|
48
79
|
|
|
49
80
|
## Main Features by Category
|
|
50
81
|
|
|
@@ -903,6 +934,115 @@ feature_drift = results['feature_drift']
|
|
|
903
934
|
```
|
|
904
935
|
</details>
|
|
905
936
|
|
|
937
|
+
### Deployment
|
|
938
|
+
|
|
939
|
+
#### Feature Serving
|
|
940
|
+
|
|
941
|
+
<details><summary> Serve Features </summary>
|
|
942
|
+
|
|
943
|
+
#### Description
|
|
944
|
+
Serve features from Snowflake Feature Store with built-in distributed locking and retry logic. This function orchestrates feature retrieval from multiple feature views with support for point-in-time lookups and flexible parameter customization.
|
|
945
|
+
|
|
946
|
+
`gitlabds.serve_features(session, feature_store, feature_views_dict, spine_df=None, feature_date=None, spine_timestamp_col=None, include_feature_view_timestamp_col=False, lookback_window_value=None, lookback_window_unit=None, lock_timeout_seconds=300):`
|
|
947
|
+
|
|
948
|
+
#### Parameters:
|
|
949
|
+
- **_session_** : Active Snowflake Session object
|
|
950
|
+
- **_feature_store_** : Initialized FeatureStore instance
|
|
951
|
+
- **_feature_views_dict_** : Dictionary mapping feature view names to versions (e.g., {"sales_activities": "1.0"})
|
|
952
|
+
- **_spine_df_** : One of the following:
|
|
953
|
+
- SQL query string to generate the spine DataFrame
|
|
954
|
+
- pandas DataFrame (will be converted to Snowpark DataFrame)
|
|
955
|
+
- Snowpark DataFrame (used directly)
|
|
956
|
+
- **_feature_date_** : Date for feature retrieval (e.g., '2025-04-08'). If None, uses UDF default.
|
|
957
|
+
- **_spine_timestamp_col_** : Name of timestamp column in spine DataFrame for point-in-time lookup. Used for when multiple feature_dates may be present in the feature view. If only one feature_date is present, can set value to None for faster execution.
|
|
958
|
+
- **_include_feature_view_timestamp_col_** : Whether to include timestamp column from feature views in output. Default is False.
|
|
959
|
+
- **_lookback_window_value_** : Either a global integer value (e.g., 6) or a dict with feature-view-specific values
|
|
960
|
+
- **_lookback_window_unit_** : Either a global string value (e.g., 'months') or a dict with feature-view-specific values
|
|
961
|
+
- **_lock_timeout_seconds_** : Timeout in seconds for acquiring the Snowflake lock. Default is 300.
|
|
962
|
+
|
|
963
|
+
#### Returns
|
|
964
|
+
- DataFrame with combined features from all feature views
|
|
965
|
+
|
|
966
|
+
#### Requirements
|
|
967
|
+
This function requires Snowflake packages. Install with:
|
|
968
|
+
```bash
|
|
969
|
+
pip install snowflake-snowpark-python snowflake-ml-python
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
#### Examples:
|
|
973
|
+
|
|
974
|
+
```python
|
|
975
|
+
# Basic usage with SQL spine
|
|
976
|
+
import gitlabds
|
|
977
|
+
from snowflake.snowpark import Session
|
|
978
|
+
from snowflake.ml.feature_store import FeatureStore
|
|
979
|
+
|
|
980
|
+
session = Session.builder.configs(...).create()
|
|
981
|
+
fs = FeatureStore(session, ...)
|
|
982
|
+
|
|
983
|
+
spine_sql = "SELECT account_id FROM accounts WHERE active = TRUE"
|
|
984
|
+
features = gitlabds.serve_features(
|
|
985
|
+
session=session,
|
|
986
|
+
feature_store=fs,
|
|
987
|
+
feature_views_dict={"sales_activities": "1.0", "marketing_activity": "2.1"},
|
|
988
|
+
spine_df=spine_sql,
|
|
989
|
+
feature_date=None
|
|
990
|
+
)
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
```python
|
|
994
|
+
# With per-feature-view parameters
|
|
995
|
+
features = gitlabds.serve_features(
|
|
996
|
+
session=session,
|
|
997
|
+
feature_store=fs,
|
|
998
|
+
feature_views_dict={"sales_activities": "1.0", "product_stage": "2.1"},
|
|
999
|
+
spine_df=spine_df,
|
|
1000
|
+
lookback_window_value={"sales_activities": 3, "product_stage": 6},
|
|
1001
|
+
lookback_window_unit={"sales_activities": "days", "product_stage": "months"}
|
|
1002
|
+
)
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
```python
|
|
1006
|
+
# With point-in-time lookup
|
|
1007
|
+
features = gitlabds.serve_features(
|
|
1008
|
+
session=session,
|
|
1009
|
+
feature_store=fs,
|
|
1010
|
+
feature_views_dict={"sales_activities": "1.0"},
|
|
1011
|
+
spine_df=spine_df,
|
|
1012
|
+
spine_timestamp_col="snapshot_date",
|
|
1013
|
+
feature_date="2025-04-08"
|
|
1014
|
+
)
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
</details>
|
|
1018
|
+
|
|
1019
|
+
<details><summary> Clear Feature Serving Locks </summary>
|
|
1020
|
+
|
|
1021
|
+
#### Description
|
|
1022
|
+
Utility function to manually clear all feature serving locks. Use this if locks get stuck due to killed processes.
|
|
1023
|
+
|
|
1024
|
+
`gitlabds.clear_feature_serving_locks(session):`
|
|
1025
|
+
|
|
1026
|
+
#### Parameters:
|
|
1027
|
+
- **_session_** : Active Snowflake Session object
|
|
1028
|
+
|
|
1029
|
+
#### Returns
|
|
1030
|
+
- int: Number of locks cleared
|
|
1031
|
+
|
|
1032
|
+
#### Examples:
|
|
1033
|
+
|
|
1034
|
+
```python
|
|
1035
|
+
# Clear stuck locks
|
|
1036
|
+
import gitlabds
|
|
1037
|
+
from snowflake.snowpark import Session
|
|
1038
|
+
|
|
1039
|
+
session = Session.builder.configs(...).create()
|
|
1040
|
+
locks_cleared = gitlabds.clear_feature_serving_locks(session)
|
|
1041
|
+
print(f"Cleared {locks_cleared} locks")
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
</details>
|
|
1045
|
+
|
|
906
1046
|
### SQL and Trend Analysis
|
|
907
1047
|
|
|
908
1048
|
<details><summary> SQL Trend Query Generator </summary>
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: gitlabds
|
|
3
|
-
Version: 2.1.7
|
|
4
|
-
Summary: Gitlab Data Science and Modeling Tools
|
|
5
|
-
Home-page: https://gitlab.com/gitlab-data/gitlabds
|
|
6
|
-
Author: Kevin Dietz
|
|
7
|
-
Author-email: kdietz@gitlab.com
|
|
8
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
-
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
-
Classifier: Topic :: Utilities
|
|
17
|
-
Requires-Python: >=3.10
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
License-File: LICENSE
|
|
20
|
-
|
|
21
1
|
[](https://badge.fury.io/py/gitlabds)
|
|
22
2
|
[](https://www.python.org/downloads/)
|
|
23
3
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -35,7 +15,7 @@ pip install gitlabds
|
|
|
35
15
|
|
|
36
16
|
### Requirements
|
|
37
17
|
|
|
38
|
-
- Python 3.
|
|
18
|
+
- Python 3.11 or later
|
|
39
19
|
- Core dependencies:
|
|
40
20
|
- pandas>=1.5.3
|
|
41
21
|
- numpy>=1.23.5
|
|
@@ -43,8 +23,27 @@ pip install gitlabds
|
|
|
43
23
|
- scikit-learn>=1.1.1
|
|
44
24
|
- imbalanced-learn>=0.9.1
|
|
45
25
|
- seaborn>=0.13.2
|
|
46
|
-
- shap>=0.
|
|
26
|
+
- shap>=0.50.0
|
|
47
27
|
- tqdm>=4.66.2
|
|
28
|
+
- xgboost>=1.6.1
|
|
29
|
+
|
|
30
|
+
### Optional Dependencies
|
|
31
|
+
|
|
32
|
+
#### Snowflake Feature Store
|
|
33
|
+
|
|
34
|
+
The `serve_features` and `clear_feature_serving_locks` functions require additional Snowflake dependencies.
|
|
35
|
+
|
|
36
|
+
To use these functions, install with:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install gitlabds[feature-store]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This will install:
|
|
43
|
+
- `snowflake-snowpark-python`
|
|
44
|
+
- `snowflake-ml-python`
|
|
45
|
+
|
|
46
|
+
If you try to use these functions without installing the optional dependencies, you'll get an error message with installation instructions.
|
|
48
47
|
|
|
49
48
|
## Main Features by Category
|
|
50
49
|
|
|
@@ -903,6 +902,115 @@ feature_drift = results['feature_drift']
|
|
|
903
902
|
```
|
|
904
903
|
</details>
|
|
905
904
|
|
|
905
|
+
### Deployment
|
|
906
|
+
|
|
907
|
+
#### Feature Serving
|
|
908
|
+
|
|
909
|
+
<details><summary> Serve Features </summary>
|
|
910
|
+
|
|
911
|
+
#### Description
|
|
912
|
+
Serve features from Snowflake Feature Store with built-in distributed locking and retry logic. This function orchestrates feature retrieval from multiple feature views with support for point-in-time lookups and flexible parameter customization.
|
|
913
|
+
|
|
914
|
+
`gitlabds.serve_features(session, feature_store, feature_views_dict, spine_df=None, feature_date=None, spine_timestamp_col=None, include_feature_view_timestamp_col=False, lookback_window_value=None, lookback_window_unit=None, lock_timeout_seconds=300):`
|
|
915
|
+
|
|
916
|
+
#### Parameters:
|
|
917
|
+
- **_session_** : Active Snowflake Session object
|
|
918
|
+
- **_feature_store_** : Initialized FeatureStore instance
|
|
919
|
+
- **_feature_views_dict_** : Dictionary mapping feature view names to versions (e.g., {"sales_activities": "1.0"})
|
|
920
|
+
- **_spine_df_** : One of the following:
|
|
921
|
+
- SQL query string to generate the spine DataFrame
|
|
922
|
+
- pandas DataFrame (will be converted to Snowpark DataFrame)
|
|
923
|
+
- Snowpark DataFrame (used directly)
|
|
924
|
+
- **_feature_date_** : Date for feature retrieval (e.g., '2025-04-08'). If None, uses UDF default.
|
|
925
|
+
- **_spine_timestamp_col_** : Name of timestamp column in spine DataFrame for point-in-time lookup. Used for when multiple feature_dates may be present in the feature view. If only one feature_date is present, can set value to None for faster execution.
|
|
926
|
+
- **_include_feature_view_timestamp_col_** : Whether to include timestamp column from feature views in output. Default is False.
|
|
927
|
+
- **_lookback_window_value_** : Either a global integer value (e.g., 6) or a dict with feature-view-specific values
|
|
928
|
+
- **_lookback_window_unit_** : Either a global string value (e.g., 'months') or a dict with feature-view-specific values
|
|
929
|
+
- **_lock_timeout_seconds_** : Timeout in seconds for acquiring the Snowflake lock. Default is 300.
|
|
930
|
+
|
|
931
|
+
#### Returns
|
|
932
|
+
- DataFrame with combined features from all feature views
|
|
933
|
+
|
|
934
|
+
#### Requirements
|
|
935
|
+
This function requires Snowflake packages. Install with:
|
|
936
|
+
```bash
|
|
937
|
+
pip install snowflake-snowpark-python snowflake-ml-python
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
#### Examples:
|
|
941
|
+
|
|
942
|
+
```python
|
|
943
|
+
# Basic usage with SQL spine
|
|
944
|
+
import gitlabds
|
|
945
|
+
from snowflake.snowpark import Session
|
|
946
|
+
from snowflake.ml.feature_store import FeatureStore
|
|
947
|
+
|
|
948
|
+
session = Session.builder.configs(...).create()
|
|
949
|
+
fs = FeatureStore(session, ...)
|
|
950
|
+
|
|
951
|
+
spine_sql = "SELECT account_id FROM accounts WHERE active = TRUE"
|
|
952
|
+
features = gitlabds.serve_features(
|
|
953
|
+
session=session,
|
|
954
|
+
feature_store=fs,
|
|
955
|
+
feature_views_dict={"sales_activities": "1.0", "marketing_activity": "2.1"},
|
|
956
|
+
spine_df=spine_sql,
|
|
957
|
+
feature_date=None
|
|
958
|
+
)
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
```python
|
|
962
|
+
# With per-feature-view parameters
|
|
963
|
+
features = gitlabds.serve_features(
|
|
964
|
+
session=session,
|
|
965
|
+
feature_store=fs,
|
|
966
|
+
feature_views_dict={"sales_activities": "1.0", "product_stage": "2.1"},
|
|
967
|
+
spine_df=spine_df,
|
|
968
|
+
lookback_window_value={"sales_activities": 3, "product_stage": 6},
|
|
969
|
+
lookback_window_unit={"sales_activities": "days", "product_stage": "months"}
|
|
970
|
+
)
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
```python
|
|
974
|
+
# With point-in-time lookup
|
|
975
|
+
features = gitlabds.serve_features(
|
|
976
|
+
session=session,
|
|
977
|
+
feature_store=fs,
|
|
978
|
+
feature_views_dict={"sales_activities": "1.0"},
|
|
979
|
+
spine_df=spine_df,
|
|
980
|
+
spine_timestamp_col="snapshot_date",
|
|
981
|
+
feature_date="2025-04-08"
|
|
982
|
+
)
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
</details>
|
|
986
|
+
|
|
987
|
+
<details><summary> Clear Feature Serving Locks </summary>
|
|
988
|
+
|
|
989
|
+
#### Description
|
|
990
|
+
Utility function to manually clear all feature serving locks. Use this if locks get stuck due to killed processes.
|
|
991
|
+
|
|
992
|
+
`gitlabds.clear_feature_serving_locks(session):`
|
|
993
|
+
|
|
994
|
+
#### Parameters:
|
|
995
|
+
- **_session_** : Active Snowflake Session object
|
|
996
|
+
|
|
997
|
+
#### Returns
|
|
998
|
+
- int: Number of locks cleared
|
|
999
|
+
|
|
1000
|
+
#### Examples:
|
|
1001
|
+
|
|
1002
|
+
```python
|
|
1003
|
+
# Clear stuck locks
|
|
1004
|
+
import gitlabds
|
|
1005
|
+
from snowflake.snowpark import Session
|
|
1006
|
+
|
|
1007
|
+
session = Session.builder.configs(...).create()
|
|
1008
|
+
locks_cleared = gitlabds.clear_feature_serving_locks(session)
|
|
1009
|
+
print(f"Cleared {locks_cleared} locks")
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
</details>
|
|
1013
|
+
|
|
906
1014
|
### SQL and Trend Analysis
|
|
907
1015
|
|
|
908
1016
|
<details><summary> SQL Trend Query Generator </summary>
|
|
@@ -10,3 +10,18 @@ from .monitoring_metrics import calculate_monitoring_metrics, calculate_baseline
|
|
|
10
10
|
from .outliers import mad_outliers, apply_outliers
|
|
11
11
|
from .split_data import split_data
|
|
12
12
|
from .trends import generate_sql_trend_query, trend_analysis
|
|
13
|
+
|
|
14
|
+
# Optional imports for Snowflake feature serving
|
|
15
|
+
try:
|
|
16
|
+
from .serving_features import clear_feature_serving_locks, serve_features
|
|
17
|
+
except ImportError as e:
|
|
18
|
+
# Snowflake packages not installed - create placeholder functions with helpful error messages
|
|
19
|
+
def _snowflake_not_installed(*args, **kwargs):
|
|
20
|
+
raise ImportError(
|
|
21
|
+
"Snowflake feature serving functions require additional dependencies. "
|
|
22
|
+
"Install them with: pip install snowflake-snowpark-python snowflake-ml-python\n\n"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
clear_feature_serving_locks = _snowflake_not_installed
|
|
26
|
+
serve_features = _snowflake_not_installed
|
|
27
|
+
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Feature serving functionality for Snowflake Feature Store integration.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for serving features from Snowflake Feature Store
|
|
5
|
+
in production environments, including distributed locking mechanisms and retry logic.
|
|
6
|
+
|
|
7
|
+
Note: This module requires snowflake-snowpark-python and snowflake-ml-python packages.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Dict, Optional, Union
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
# Optional imports with graceful fallback
|
|
16
|
+
try:
|
|
17
|
+
from snowflake.snowpark import Session, DataFrame as SnowparkDataFrame
|
|
18
|
+
from snowflake.ml.feature_store import FeatureStore
|
|
19
|
+
SNOWFLAKE_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
SNOWFLAKE_AVAILABLE = False
|
|
22
|
+
SnowparkDataFrame = None
|
|
23
|
+
Session = None
|
|
24
|
+
FeatureStore = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _check_snowflake_available():
|
|
28
|
+
"""Check if Snowflake packages are available."""
|
|
29
|
+
if not SNOWFLAKE_AVAILABLE:
|
|
30
|
+
raise ImportError(
|
|
31
|
+
"Snowflake packages are required for feature serving. "
|
|
32
|
+
"Install with: pip install snowflake-snowpark-python snowflake-ml-python"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def clear_feature_serving_locks(session: 'Session') -> int:
|
|
37
|
+
"""
|
|
38
|
+
Utility function to manually clear all feature serving locks.
|
|
39
|
+
|
|
40
|
+
Use this if locks get stuck due to killed processes. This function checks
|
|
41
|
+
for existing locks, cleans up expired ones, and provides detailed status
|
|
42
|
+
information.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
session: Snowflake Session object
|
|
46
|
+
Returns:
|
|
47
|
+
Number of locks cleared
|
|
48
|
+
"""
|
|
49
|
+
_check_snowflake_available()
|
|
50
|
+
|
|
51
|
+
# Constants - these are safe, not user-controlled
|
|
52
|
+
LOCK_TABLE = "FEATURE_SERVING_LOCKS"
|
|
53
|
+
LOCK_NAME = "FEATURE_SERVING"
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
# Check existing locks first
|
|
57
|
+
existing_locks = session.sql(f"""
|
|
58
|
+
SELECT session_id, acquired_at, expires_at,
|
|
59
|
+
CASE WHEN expires_at > CURRENT_TIMESTAMP() THEN 'ACTIVE' ELSE 'EXPIRED' END as status
|
|
60
|
+
FROM {LOCK_TABLE}
|
|
61
|
+
WHERE lock_name = '{LOCK_NAME}'
|
|
62
|
+
ORDER BY acquired_at DESC
|
|
63
|
+
""").collect()
|
|
64
|
+
|
|
65
|
+
if existing_locks:
|
|
66
|
+
print("🔍 Current feature serving locks:")
|
|
67
|
+
for lock in existing_locks:
|
|
68
|
+
print(f" Session: {lock['SESSION_ID']}, "
|
|
69
|
+
f"Acquired: {lock['ACQUIRED_AT']}, "
|
|
70
|
+
f"Expires: {lock['EXPIRES_AT']}, "
|
|
71
|
+
f"Status: {lock['STATUS']}")
|
|
72
|
+
else:
|
|
73
|
+
print("✅ No existing locks found")
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
# Clear all locks
|
|
77
|
+
session.sql(f"""
|
|
78
|
+
DELETE FROM {LOCK_TABLE}
|
|
79
|
+
WHERE lock_name = '{LOCK_NAME}'
|
|
80
|
+
""").collect()
|
|
81
|
+
|
|
82
|
+
locks_cleared = len(existing_locks)
|
|
83
|
+
print(f"🧹 Cleared {locks_cleared} feature serving locks")
|
|
84
|
+
return locks_cleared
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"❌ Error clearing locks: {e}")
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
def serve_features(
|
|
91
|
+
session: 'Session',
|
|
92
|
+
feature_store: 'FeatureStore',
|
|
93
|
+
feature_views_dict: Dict[str, str],
|
|
94
|
+
spine_df: Optional[Union[str, pd.DataFrame, 'SnowparkDataFrame']] = None,
|
|
95
|
+
feature_date: Optional[str] = None,
|
|
96
|
+
spine_timestamp_col: Optional[str] = None,
|
|
97
|
+
include_feature_view_timestamp_col: bool = False,
|
|
98
|
+
lookback_window_value: Optional[Union[int, Dict[str, int]]] = None,
|
|
99
|
+
lookback_window_unit: Optional[Union[str, Dict[str, str]]] = None,
|
|
100
|
+
lock_timeout_seconds: int = 300
|
|
101
|
+
) -> pd.DataFrame:
|
|
102
|
+
"""
|
|
103
|
+
Serve features using simplified workflow with Snowflake-based locking and retry mechanism.
|
|
104
|
+
|
|
105
|
+
This function orchestrates feature serving from Snowflake Feature Store with built-in
|
|
106
|
+
distributed locking to prevent concurrent executions and automatic retry logic for
|
|
107
|
+
robustness. It supports flexible spine DataFrame inputs and per-feature-view parameter
|
|
108
|
+
customization.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
session : snowflake.snowpark.Session
|
|
113
|
+
Active Snowflake Session object
|
|
114
|
+
feature_store : snowflake.ml.feature_store.FeatureStore
|
|
115
|
+
Initialized FeatureStore instance
|
|
116
|
+
feature_views_dict : Dict[str, str]
|
|
117
|
+
Dictionary mapping feature view names to versions (e.g., {"sales_activities": "1.0"})
|
|
118
|
+
spine_df : Union[str, pd.DataFrame, snowflake.snowpark.DataFrame], optional
|
|
119
|
+
One of the following:
|
|
120
|
+
- SQL query string to generate the spine DataFrame
|
|
121
|
+
- pandas DataFrame (will be converted to Snowpark DataFrame)
|
|
122
|
+
- Snowpark DataFrame (used directly)
|
|
123
|
+
- None will raise an error (spine is required by Feature Store SDK)
|
|
124
|
+
feature_date : str, optional
|
|
125
|
+
Snapshot date for features (e.g., '2025-04-08'). If None, uses UDF default.
|
|
126
|
+
spine_timestamp_col : str, optional
|
|
127
|
+
Name of timestamp column in spine DataFrame for point-in-time lookup.
|
|
128
|
+
If None, point-in-time lookup is disabled.
|
|
129
|
+
include_feature_view_timestamp_col : bool, default=False
|
|
130
|
+
Whether to include timestamp column from feature views in output
|
|
131
|
+
lookback_window_value : Union[int, Dict[str, int]], optional
|
|
132
|
+
Either a global integer value (e.g., 6) or a dict with feature-view-specific values
|
|
133
|
+
(e.g., {"sales_activities_by_account": 3, "product_stage_by_namespace": 2})
|
|
134
|
+
lookback_window_unit : Union[str, Dict[str, str]], optional
|
|
135
|
+
Either a global string value (e.g., 'months') or a dict with feature-view-specific values
|
|
136
|
+
(e.g., {"sales_activities_by_account": 'days', "product_stage_by_namespace": 'weeks'})
|
|
137
|
+
lock_timeout_seconds : int, default=300
|
|
138
|
+
Timeout in seconds for acquiring the Snowflake lock
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
pd.DataFrame
|
|
143
|
+
DataFrame with combined features from all feature views, with column names
|
|
144
|
+
converted to lowercase
|
|
145
|
+
"""
|
|
146
|
+
_check_snowflake_available()
|
|
147
|
+
|
|
148
|
+
# Constants - these are safe, not user-controlled
|
|
149
|
+
LOCK_TABLE = "FEATURE_SERVING_LOCKS"
|
|
150
|
+
LOCK_NAME = "FEATURE_SERVING"
|
|
151
|
+
session_id = f"session_{uuid.uuid4().hex[:8]}"
|
|
152
|
+
|
|
153
|
+
# Retry configuration
|
|
154
|
+
max_attempts = 10
|
|
155
|
+
retry_interval_seconds = 60
|
|
156
|
+
|
|
157
|
+
for attempt in range(1, max_attempts + 1):
|
|
158
|
+
try:
|
|
159
|
+
# Try to acquire lock using lock table approach
|
|
160
|
+
if attempt == 1:
|
|
161
|
+
print(f"🔒 [{session_id}] Attempting to acquire Snowflake lock: {LOCK_NAME}")
|
|
162
|
+
else:
|
|
163
|
+
print(f"🔒 [{session_id}] Retry attempt {attempt}/{max_attempts} to acquire lock: {LOCK_NAME}")
|
|
164
|
+
|
|
165
|
+
# Create lock table if it doesn't exist
|
|
166
|
+
session.sql(f"""
|
|
167
|
+
CREATE TABLE IF NOT EXISTS {LOCK_TABLE} (
|
|
168
|
+
lock_name VARCHAR,
|
|
169
|
+
session_id VARCHAR,
|
|
170
|
+
acquired_at TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),
|
|
171
|
+
expires_at TIMESTAMP_NTZ
|
|
172
|
+
)
|
|
173
|
+
""").collect()
|
|
174
|
+
|
|
175
|
+
# Clean up expired locks first (this handles killed processes)
|
|
176
|
+
session.sql(f"""
|
|
177
|
+
DELETE FROM {LOCK_TABLE}
|
|
178
|
+
WHERE expires_at < CURRENT_TIMESTAMP()
|
|
179
|
+
""").collect()
|
|
180
|
+
print(f"🧹 [{session_id}] Cleaned up expired locks")
|
|
181
|
+
|
|
182
|
+
# Try to acquire lock - session_id is UUID-generated, safe from injection
|
|
183
|
+
session.sql(f"""
|
|
184
|
+
INSERT INTO {LOCK_TABLE} (lock_name, session_id, expires_at)
|
|
185
|
+
SELECT '{LOCK_NAME}', '{session_id}', DATEADD(second, {lock_timeout_seconds}, CURRENT_TIMESTAMP())
|
|
186
|
+
WHERE NOT EXISTS (
|
|
187
|
+
SELECT 1 FROM {LOCK_TABLE}
|
|
188
|
+
WHERE lock_name = '{LOCK_NAME}'
|
|
189
|
+
AND expires_at > CURRENT_TIMESTAMP()
|
|
190
|
+
)
|
|
191
|
+
""").collect()
|
|
192
|
+
|
|
193
|
+
# Check if we got the lock - session_id is UUID-generated, safe from injection
|
|
194
|
+
lock_check = session.sql(f"""
|
|
195
|
+
SELECT COUNT(*) as lock_count
|
|
196
|
+
FROM {LOCK_TABLE}
|
|
197
|
+
WHERE lock_name = '{LOCK_NAME}'
|
|
198
|
+
AND session_id = '{session_id}'
|
|
199
|
+
AND expires_at > CURRENT_TIMESTAMP()
|
|
200
|
+
""").collect()
|
|
201
|
+
|
|
202
|
+
if lock_check[0]['LOCK_COUNT'] > 0:
|
|
203
|
+
# Successfully acquired lock
|
|
204
|
+
print(f"✅ [{session_id}] Acquired Snowflake lock: {LOCK_NAME}")
|
|
205
|
+
break
|
|
206
|
+
else:
|
|
207
|
+
# Lock is held by another session
|
|
208
|
+
existing_locks = session.sql(f"""
|
|
209
|
+
SELECT session_id, acquired_at, expires_at,
|
|
210
|
+
DATEDIFF(second, CURRENT_TIMESTAMP(), expires_at) as seconds_until_expiry
|
|
211
|
+
FROM {LOCK_TABLE}
|
|
212
|
+
WHERE lock_name = '{LOCK_NAME}'
|
|
213
|
+
AND expires_at > CURRENT_TIMESTAMP()
|
|
214
|
+
""").collect()
|
|
215
|
+
|
|
216
|
+
if existing_locks:
|
|
217
|
+
lock_info = existing_locks[0]
|
|
218
|
+
if attempt < max_attempts:
|
|
219
|
+
print(f"⏳ [{session_id}] Lock held by session {lock_info['SESSION_ID']} "
|
|
220
|
+
f"(expires in {lock_info['SECONDS_UNTIL_EXPIRY']} seconds). "
|
|
221
|
+
f"Retrying in {retry_interval_seconds} seconds... (attempt {attempt}/{max_attempts})")
|
|
222
|
+
time.sleep(retry_interval_seconds)
|
|
223
|
+
continue
|
|
224
|
+
else:
|
|
225
|
+
raise TimeoutError(f"Could not acquire lock after {max_attempts} attempts over {max_attempts * retry_interval_seconds // 60} minutes. "
|
|
226
|
+
f"Lock is held by session {lock_info['SESSION_ID']}, "
|
|
227
|
+
f"acquired at {lock_info['ACQUIRED_AT']}, expires in {lock_info['SECONDS_UNTIL_EXPIRY']} seconds. "
|
|
228
|
+
f"Please try again later.")
|
|
229
|
+
else:
|
|
230
|
+
if attempt < max_attempts:
|
|
231
|
+
print(f"⏳ [{session_id}] Could not acquire lock (unknown reason). "
|
|
232
|
+
f"Retrying in {retry_interval_seconds} seconds... (attempt {attempt}/{max_attempts})")
|
|
233
|
+
time.sleep(retry_interval_seconds)
|
|
234
|
+
continue
|
|
235
|
+
else:
|
|
236
|
+
raise TimeoutError(f"Could not acquire lock after {max_attempts} attempts for unknown reason. Please try again later.")
|
|
237
|
+
|
|
238
|
+
except TimeoutError:
|
|
239
|
+
# Re-raise TimeoutError to exit the retry loop
|
|
240
|
+
raise
|
|
241
|
+
except ValueError:
|
|
242
|
+
# Re-raise specific exceptions that shouldn't be retried
|
|
243
|
+
raise
|
|
244
|
+
except Exception as e:
|
|
245
|
+
if attempt < max_attempts:
|
|
246
|
+
print(f"❌ [{session_id}] Error during lock acquisition attempt {attempt}/{max_attempts}: {e}")
|
|
247
|
+
print(f"⏳ [{session_id}] Retrying in {retry_interval_seconds} seconds...")
|
|
248
|
+
time.sleep(retry_interval_seconds)
|
|
249
|
+
continue
|
|
250
|
+
else:
|
|
251
|
+
raise Exception(f"Failed to acquire lock after {max_attempts} attempts. Last error: {e}")
|
|
252
|
+
|
|
253
|
+
# If we get here, we have the lock - proceed with feature serving
|
|
254
|
+
try:
|
|
255
|
+
feature_views_list = []
|
|
256
|
+
|
|
257
|
+
# Loop over all feature views to create the dynamic views
|
|
258
|
+
for fv_name, version in feature_views_dict.items():
|
|
259
|
+
fv = feature_store.get_feature_view(fv_name, version)
|
|
260
|
+
feature_views_list.append(fv)
|
|
261
|
+
|
|
262
|
+
# Derive dynamic view and UDF names from feature view name using naming convention
|
|
263
|
+
# Convention: feature_view_name -> feature_view_name_dynamic (view) and feature_view_name_udf (function)
|
|
264
|
+
dynamic_view = f"{fv_name.upper()}_DYNAMIC"
|
|
265
|
+
udf_name = f"{fv_name.upper()}_UDF"
|
|
266
|
+
|
|
267
|
+
# Extract metadata from FeatureView
|
|
268
|
+
entity_id = fv.entities[0].join_keys[0]
|
|
269
|
+
timestamp_col = fv.timestamp_col
|
|
270
|
+
|
|
271
|
+
print(f"[{session_id}] {fv_name} v{version}:")
|
|
272
|
+
print(f" Dynamic View: {dynamic_view}")
|
|
273
|
+
print(f" UDF: {udf_name}")
|
|
274
|
+
print(f" Entity: {entity_id}")
|
|
275
|
+
print(f" Timestamp: {timestamp_col}")
|
|
276
|
+
|
|
277
|
+
# Build parameters in the new order: SNAPSHOT_DATE, LOOKBACK_WINDOW_VALUE, LOOKBACK_WINDOW_UNIT
|
|
278
|
+
udf_params = []
|
|
279
|
+
|
|
280
|
+
# First parameter: SNAPSHOT_DATE (feature_date) - pass as DATE type, not string
|
|
281
|
+
if feature_date:
|
|
282
|
+
date_str = str(feature_date)
|
|
283
|
+
udf_params.append(f"'{date_str}'::DATE")
|
|
284
|
+
print(f" Using snapshot date: {date_str}")
|
|
285
|
+
else:
|
|
286
|
+
print(f" Using UDF default snapshot date")
|
|
287
|
+
|
|
288
|
+
# Second parameter: LOOKBACK_WINDOW_VALUE
|
|
289
|
+
if lookback_window_value is not None:
|
|
290
|
+
if isinstance(lookback_window_value, dict):
|
|
291
|
+
if fv_name in lookback_window_value:
|
|
292
|
+
value_param = lookback_window_value[fv_name]
|
|
293
|
+
udf_params.append(str(value_param))
|
|
294
|
+
print(f" Using per-UDF lookback window value: {value_param}")
|
|
295
|
+
else:
|
|
296
|
+
print(f" Using UDF default lookback window value")
|
|
297
|
+
else:
|
|
298
|
+
# Global value
|
|
299
|
+
udf_params.append(str(lookback_window_value))
|
|
300
|
+
print(f" Using global lookback window value: {lookback_window_value}")
|
|
301
|
+
else:
|
|
302
|
+
print(f" Using UDF default lookback window value")
|
|
303
|
+
|
|
304
|
+
# Third parameter: LOOKBACK_WINDOW_UNIT
|
|
305
|
+
if lookback_window_unit is not None:
|
|
306
|
+
if isinstance(lookback_window_unit, dict):
|
|
307
|
+
if fv_name in lookback_window_unit:
|
|
308
|
+
unit_param = lookback_window_unit[fv_name]
|
|
309
|
+
udf_params.append(f"'{unit_param}'")
|
|
310
|
+
print(f" Using per-UDF lookback window unit: {unit_param}")
|
|
311
|
+
else:
|
|
312
|
+
print(f" Using UDF default lookback window unit")
|
|
313
|
+
else:
|
|
314
|
+
# Global value
|
|
315
|
+
udf_params.append(f"'{lookback_window_unit}'")
|
|
316
|
+
print(f" Using global lookback window unit: {lookback_window_unit}")
|
|
317
|
+
else:
|
|
318
|
+
print(f" Using UDF default lookback window unit")
|
|
319
|
+
|
|
320
|
+
# Create the UDF call with parameters in the correct order
|
|
321
|
+
params_str = ', '.join(udf_params) if udf_params else ''
|
|
322
|
+
update_view_sql = f"""
|
|
323
|
+
CREATE OR REPLACE VIEW {dynamic_view} AS
|
|
324
|
+
SELECT * FROM TABLE({udf_name}({params_str}))
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
print(f" {update_view_sql.strip()}")
|
|
328
|
+
|
|
329
|
+
session.sql(update_view_sql).collect()
|
|
330
|
+
print(f" [{session_id}] Updated {dynamic_view}")
|
|
331
|
+
|
|
332
|
+
# Handle spine_df - convert to Snowpark DataFrame as needed
|
|
333
|
+
if spine_df is None:
|
|
334
|
+
raise ValueError("spine_df is required - the Snowflake Feature Store SDK requires a spine DataFrame")
|
|
335
|
+
elif isinstance(spine_df, str):
|
|
336
|
+
# SQL query string
|
|
337
|
+
snowpark_spine_df = session.sql(spine_df)
|
|
338
|
+
print(f"[{session_id}] Created spine from SQL query")
|
|
339
|
+
elif isinstance(spine_df, pd.DataFrame):
|
|
340
|
+
# pandas DataFrame - convert to Snowpark
|
|
341
|
+
snowpark_spine_df = session.create_dataframe(spine_df)
|
|
342
|
+
print(f"[{session_id}] Created spine from pandas DataFrame")
|
|
343
|
+
elif SNOWFLAKE_AVAILABLE and isinstance(spine_df, SnowparkDataFrame):
|
|
344
|
+
# Already a Snowpark DataFrame
|
|
345
|
+
snowpark_spine_df = spine_df
|
|
346
|
+
print(f"[{session_id}] Using provided Snowpark DataFrame")
|
|
347
|
+
else:
|
|
348
|
+
raise TypeError(f"spine_df must be a SQL query string, pandas DataFrame, or Snowpark DataFrame. Got: {type(spine_df)}")
|
|
349
|
+
|
|
350
|
+
print(f"[{session_id}] Spine contains {snowpark_spine_df.count()} entities")
|
|
351
|
+
|
|
352
|
+
# Build retrieve_feature_values arguments conditionally
|
|
353
|
+
retrieve_kwargs = {
|
|
354
|
+
"spine_df": snowpark_spine_df,
|
|
355
|
+
"features": feature_views_list,
|
|
356
|
+
"include_feature_view_timestamp_col": include_feature_view_timestamp_col
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# Only include spine_timestamp_col if provided
|
|
360
|
+
if spine_timestamp_col is not None:
|
|
361
|
+
retrieve_kwargs["spine_timestamp_col"] = spine_timestamp_col
|
|
362
|
+
print(f"[{session_id}] Using point-in-time lookup with timestamp column: {spine_timestamp_col}")
|
|
363
|
+
else:
|
|
364
|
+
print(f"[{session_id}] Point-in-time lookup disabled (no spine_timestamp_col)")
|
|
365
|
+
|
|
366
|
+
# Retrieve combined features
|
|
367
|
+
print(f"[{session_id}] Starting retrieve_feature_values (this may take a while)...")
|
|
368
|
+
combined_features = feature_store.retrieve_feature_values(**retrieve_kwargs)
|
|
369
|
+
|
|
370
|
+
combined_features = combined_features.to_pandas()
|
|
371
|
+
combined_features.columns = map(str.lower, combined_features.columns)
|
|
372
|
+
|
|
373
|
+
print(f"✅ [{session_id}] Feature serving completed successfully")
|
|
374
|
+
return combined_features
|
|
375
|
+
|
|
376
|
+
finally:
|
|
377
|
+
# Always release the lock
|
|
378
|
+
try:
|
|
379
|
+
session.sql(f"""
|
|
380
|
+
DELETE FROM {LOCK_TABLE}
|
|
381
|
+
WHERE lock_name = '{LOCK_NAME}'
|
|
382
|
+
AND session_id = '{session_id}'
|
|
383
|
+
""").collect()
|
|
384
|
+
print(f"🔓 [{session_id}] Released Snowflake lock: {LOCK_NAME}")
|
|
385
|
+
except Exception as e:
|
|
386
|
+
print(f"⚠️ [{session_id}] Error releasing lock: {e}")
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitlabds
|
|
3
|
+
Version: 2.1.8
|
|
4
|
+
Summary: GitLab Data Science Tools
|
|
5
|
+
Author-email: Kevin Dietz <kdietz@gitlab.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/gitlab-data/gitlabds
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Topic :: Utilities
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: pandas>=1.5.3
|
|
20
|
+
Requires-Dist: numpy>=1.23.5
|
|
21
|
+
Requires-Dist: scipy>=1.13.1
|
|
22
|
+
Requires-Dist: scikit-learn>=1.1.1
|
|
23
|
+
Requires-Dist: imbalanced-learn>=0.9.1
|
|
24
|
+
Requires-Dist: seaborn>=0.13.2
|
|
25
|
+
Requires-Dist: shap>=0.50.0
|
|
26
|
+
Requires-Dist: tqdm>=4.66.2
|
|
27
|
+
Requires-Dist: xgboost>=1.6.1
|
|
28
|
+
Provides-Extra: feature-store
|
|
29
|
+
Requires-Dist: snowflake-snowpark-python; extra == "feature-store"
|
|
30
|
+
Requires-Dist: snowflake-ml-python; extra == "feature-store"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
1
33
|
[](https://badge.fury.io/py/gitlabds)
|
|
2
34
|
[](https://www.python.org/downloads/)
|
|
3
35
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -15,7 +47,7 @@ pip install gitlabds
|
|
|
15
47
|
|
|
16
48
|
### Requirements
|
|
17
49
|
|
|
18
|
-
- Python 3.
|
|
50
|
+
- Python 3.11 or later
|
|
19
51
|
- Core dependencies:
|
|
20
52
|
- pandas>=1.5.3
|
|
21
53
|
- numpy>=1.23.5
|
|
@@ -23,8 +55,27 @@ pip install gitlabds
|
|
|
23
55
|
- scikit-learn>=1.1.1
|
|
24
56
|
- imbalanced-learn>=0.9.1
|
|
25
57
|
- seaborn>=0.13.2
|
|
26
|
-
- shap>=0.
|
|
58
|
+
- shap>=0.50.0
|
|
27
59
|
- tqdm>=4.66.2
|
|
60
|
+
- xgboost>=1.6.1
|
|
61
|
+
|
|
62
|
+
### Optional Dependencies
|
|
63
|
+
|
|
64
|
+
#### Snowflake Feature Store
|
|
65
|
+
|
|
66
|
+
The `serve_features` and `clear_feature_serving_locks` functions require additional Snowflake dependencies.
|
|
67
|
+
|
|
68
|
+
To use these functions, install with:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install gitlabds[feature-store]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This will install:
|
|
75
|
+
- `snowflake-snowpark-python`
|
|
76
|
+
- `snowflake-ml-python`
|
|
77
|
+
|
|
78
|
+
If you try to use these functions without installing the optional dependencies, you'll get an error message with installation instructions.
|
|
28
79
|
|
|
29
80
|
## Main Features by Category
|
|
30
81
|
|
|
@@ -883,6 +934,115 @@ feature_drift = results['feature_drift']
|
|
|
883
934
|
```
|
|
884
935
|
</details>
|
|
885
936
|
|
|
937
|
+
### Deployment
|
|
938
|
+
|
|
939
|
+
#### Feature Serving
|
|
940
|
+
|
|
941
|
+
<details><summary> Serve Features </summary>
|
|
942
|
+
|
|
943
|
+
#### Description
|
|
944
|
+
Serve features from Snowflake Feature Store with built-in distributed locking and retry logic. This function orchestrates feature retrieval from multiple feature views with support for point-in-time lookups and flexible parameter customization.
|
|
945
|
+
|
|
946
|
+
`gitlabds.serve_features(session, feature_store, feature_views_dict, spine_df=None, feature_date=None, spine_timestamp_col=None, include_feature_view_timestamp_col=False, lookback_window_value=None, lookback_window_unit=None, lock_timeout_seconds=300):`
|
|
947
|
+
|
|
948
|
+
#### Parameters:
|
|
949
|
+
- **_session_** : Active Snowflake Session object
|
|
950
|
+
- **_feature_store_** : Initialized FeatureStore instance
|
|
951
|
+
- **_feature_views_dict_** : Dictionary mapping feature view names to versions (e.g., {"sales_activities": "1.0"})
|
|
952
|
+
- **_spine_df_** : One of the following:
|
|
953
|
+
- SQL query string to generate the spine DataFrame
|
|
954
|
+
- pandas DataFrame (will be converted to Snowpark DataFrame)
|
|
955
|
+
- Snowpark DataFrame (used directly)
|
|
956
|
+
- **_feature_date_** : Date for feature retrieval (e.g., '2025-04-08'). If None, uses UDF default.
|
|
957
|
+
- **_spine_timestamp_col_** : Name of timestamp column in spine DataFrame for point-in-time lookup. Used for when multiple feature_dates may be present in the feature view. If only one feature_date is present, can set value to None for faster execution.
|
|
958
|
+
- **_include_feature_view_timestamp_col_** : Whether to include timestamp column from feature views in output. Default is False.
|
|
959
|
+
- **_lookback_window_value_** : Either a global integer value (e.g., 6) or a dict with feature-view-specific values
|
|
960
|
+
- **_lookback_window_unit_** : Either a global string value (e.g., 'months') or a dict with feature-view-specific values
|
|
961
|
+
- **_lock_timeout_seconds_** : Timeout in seconds for acquiring the Snowflake lock. Default is 300.
|
|
962
|
+
|
|
963
|
+
#### Returns
|
|
964
|
+
- DataFrame with combined features from all feature views
|
|
965
|
+
|
|
966
|
+
#### Requirements
|
|
967
|
+
This function requires Snowflake packages. Install with:
|
|
968
|
+
```bash
|
|
969
|
+
pip install snowflake-snowpark-python snowflake-ml-python
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
#### Examples:
|
|
973
|
+
|
|
974
|
+
```python
|
|
975
|
+
# Basic usage with SQL spine
|
|
976
|
+
import gitlabds
|
|
977
|
+
from snowflake.snowpark import Session
|
|
978
|
+
from snowflake.ml.feature_store import FeatureStore
|
|
979
|
+
|
|
980
|
+
session = Session.builder.configs(...).create()
|
|
981
|
+
fs = FeatureStore(session, ...)
|
|
982
|
+
|
|
983
|
+
spine_sql = "SELECT account_id FROM accounts WHERE active = TRUE"
|
|
984
|
+
features = gitlabds.serve_features(
|
|
985
|
+
session=session,
|
|
986
|
+
feature_store=fs,
|
|
987
|
+
feature_views_dict={"sales_activities": "1.0", "marketing_activity": "2.1"},
|
|
988
|
+
spine_df=spine_sql,
|
|
989
|
+
feature_date=None
|
|
990
|
+
)
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
```python
|
|
994
|
+
# With per-feature-view parameters
|
|
995
|
+
features = gitlabds.serve_features(
|
|
996
|
+
session=session,
|
|
997
|
+
feature_store=fs,
|
|
998
|
+
feature_views_dict={"sales_activities": "1.0", "product_stage": "2.1"},
|
|
999
|
+
spine_df=spine_df,
|
|
1000
|
+
lookback_window_value={"sales_activities": 3, "product_stage": 6},
|
|
1001
|
+
lookback_window_unit={"sales_activities": "days", "product_stage": "months"}
|
|
1002
|
+
)
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
```python
|
|
1006
|
+
# With point-in-time lookup
|
|
1007
|
+
features = gitlabds.serve_features(
|
|
1008
|
+
session=session,
|
|
1009
|
+
feature_store=fs,
|
|
1010
|
+
feature_views_dict={"sales_activities": "1.0"},
|
|
1011
|
+
spine_df=spine_df,
|
|
1012
|
+
spine_timestamp_col="snapshot_date",
|
|
1013
|
+
feature_date="2025-04-08"
|
|
1014
|
+
)
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
</details>
|
|
1018
|
+
|
|
1019
|
+
<details><summary> Clear Feature Serving Locks </summary>
|
|
1020
|
+
|
|
1021
|
+
#### Description
|
|
1022
|
+
Utility function to manually clear all feature serving locks. Use this if locks get stuck due to killed processes.
|
|
1023
|
+
|
|
1024
|
+
`gitlabds.clear_feature_serving_locks(session):`
|
|
1025
|
+
|
|
1026
|
+
#### Parameters:
|
|
1027
|
+
- **_session_** : Active Snowflake Session object
|
|
1028
|
+
|
|
1029
|
+
#### Returns
|
|
1030
|
+
- int: Number of locks cleared
|
|
1031
|
+
|
|
1032
|
+
#### Examples:
|
|
1033
|
+
|
|
1034
|
+
```python
|
|
1035
|
+
# Clear stuck locks
|
|
1036
|
+
import gitlabds
|
|
1037
|
+
from snowflake.snowpark import Session
|
|
1038
|
+
|
|
1039
|
+
session = Session.builder.configs(...).create()
|
|
1040
|
+
locks_cleared = gitlabds.clear_feature_serving_locks(session)
|
|
1041
|
+
print(f"Cleared {locks_cleared} locks")
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
</details>
|
|
1045
|
+
|
|
886
1046
|
### SQL and Trend Analysis
|
|
887
1047
|
|
|
888
1048
|
<details><summary> SQL Trend Query Generator </summary>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
|
+
pyproject.toml
|
|
3
4
|
setup.py
|
|
4
5
|
gitlabds/__init__.py
|
|
5
6
|
gitlabds/baselines.py
|
|
@@ -14,11 +15,11 @@ gitlabds/missing_fill.py
|
|
|
14
15
|
gitlabds/model_evaluator.py
|
|
15
16
|
gitlabds/monitoring_metrics.py
|
|
16
17
|
gitlabds/outliers.py
|
|
18
|
+
gitlabds/serving_features.py
|
|
17
19
|
gitlabds/split_data.py
|
|
18
20
|
gitlabds/trends.py
|
|
19
21
|
gitlabds.egg-info/PKG-INFO
|
|
20
22
|
gitlabds.egg-info/SOURCES.txt
|
|
21
23
|
gitlabds.egg-info/dependency_links.txt
|
|
22
24
|
gitlabds.egg-info/requires.txt
|
|
23
|
-
gitlabds.egg-info/top_level.txt
|
|
24
|
-
tests/__init__.py
|
|
25
|
+
gitlabds.egg-info/top_level.txt
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gitlabds"
|
|
7
|
+
description = "GitLab Data Science Tools"
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Kevin Dietz", email = "kdietz@gitlab.com"}
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
"Topic :: Utilities",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"pandas>=1.5.3",
|
|
27
|
+
"numpy>=1.23.5",
|
|
28
|
+
"scipy>=1.13.1",
|
|
29
|
+
"scikit-learn>=1.1.1",
|
|
30
|
+
"imbalanced-learn>=0.9.1",
|
|
31
|
+
"seaborn>=0.13.2",
|
|
32
|
+
"shap>=0.50.0",
|
|
33
|
+
"tqdm>=4.66.2",
|
|
34
|
+
"xgboost>=1.6.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
feature-store = [
|
|
39
|
+
"snowflake-snowpark-python",
|
|
40
|
+
"snowflake-ml-python",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://gitlab.com/gitlab-data/gitlabds"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools]
|
|
47
|
+
packages = ["gitlabds"]
|
|
48
|
+
|
gitlabds-2.1.8/setup.py
ADDED
gitlabds-2.1.7/setup.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import setuptools, os
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
if os.environ.get('CI_COMMIT_TAG'):
|
|
5
|
-
version = os.environ['CI_COMMIT_TAG']
|
|
6
|
-
elif os.environ.get('CI_JOB_ID'):
|
|
7
|
-
version = os.environ['CI_JOB_ID']
|
|
8
|
-
else:
|
|
9
|
-
version = "0.0.0-dev"
|
|
10
|
-
|
|
11
|
-
requires = ["pandas>=1.5.3", "numpy>=1.23.5", "scipy>=1.13.1", "scikit-learn>=1.1.1", "imbalanced-learn>=0.9.1", "seaborn>=0.13.2", "shap>=0.41.0", "tqdm>=4.66.2"]
|
|
12
|
-
|
|
13
|
-
# read the contents of README file
|
|
14
|
-
this_directory = Path(__file__).parent
|
|
15
|
-
long_description = (this_directory / "README.md").read_text()
|
|
16
|
-
|
|
17
|
-
setuptools.setup(
|
|
18
|
-
name='gitlabds',
|
|
19
|
-
version=version,
|
|
20
|
-
description='Gitlab Data Science and Modeling Tools',
|
|
21
|
-
long_description = long_description,
|
|
22
|
-
long_description_content_type ='text/markdown',
|
|
23
|
-
author='Kevin Dietz',
|
|
24
|
-
author_email='kdietz@gitlab.com',
|
|
25
|
-
packages=setuptools.find_packages(),
|
|
26
|
-
url='https://gitlab.com/gitlab-data/gitlabds',
|
|
27
|
-
classifiers=[
|
|
28
|
-
'Development Status :: 5 - Production/Stable',
|
|
29
|
-
'Intended Audience :: Developers',
|
|
30
|
-
'Intended Audience :: Science/Research',
|
|
31
|
-
'License :: OSI Approved :: MIT License',
|
|
32
|
-
'Programming Language :: Python :: 3',
|
|
33
|
-
'Topic :: Scientific/Engineering :: Artificial Intelligence',
|
|
34
|
-
'Topic :: Scientific/Engineering :: Information Analysis',
|
|
35
|
-
'Topic :: Software Development :: Libraries',
|
|
36
|
-
'Topic :: Utilities',
|
|
37
|
-
],
|
|
38
|
-
python_requires= '>=3.10',
|
|
39
|
-
install_requires=requires,
|
|
40
|
-
)
|
|
41
|
-
|
gitlabds-2.1.7/tests/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|