drift-ml 0.2.8__tar.gz → 0.2.10__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.
Files changed (28) hide show
  1. {drift_ml-0.2.8 → drift_ml-0.2.10}/PKG-INFO +5 -2
  2. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/__init__.py +1 -1
  3. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/engine_launcher.py +6 -1
  4. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/PKG-INFO +5 -2
  5. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/SOURCES.txt +5 -1
  6. drift_ml-0.2.10/drift_ml.egg-info/requires.txt +5 -0
  7. {drift_ml-0.2.8 → drift_ml-0.2.10}/pyproject.toml +9 -2
  8. drift_ml-0.2.10/tests/test_api.py +29 -0
  9. drift_ml-0.2.10/tests/test_automl_agent.py +72 -0
  10. drift_ml-0.2.10/tests/test_evaluator.py +31 -0
  11. drift_ml-0.2.10/tests/test_plan_normalizer.py +52 -0
  12. drift_ml-0.2.8/drift_ml.egg-info/requires.txt +0 -1
  13. {drift_ml-0.2.8 → drift_ml-0.2.10}/LICENSE +0 -0
  14. {drift_ml-0.2.8 → drift_ml-0.2.10}/README.md +0 -0
  15. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/__main__.py +0 -0
  16. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/api.py +0 -0
  17. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/__init__.py +0 -0
  18. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/client.py +0 -0
  19. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/repl.py +0 -0
  20. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/session.py +0 -0
  21. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/__init__.py +0 -0
  22. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/base.py +0 -0
  23. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/gemini_cli.py +0 -0
  24. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/local_llm.py +0 -0
  25. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/dependency_links.txt +0 -0
  26. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/entry_points.txt +0 -0
  27. {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/top_level.txt +0 -0
  28. {drift_ml-0.2.8 → drift_ml-0.2.10}/setup.cfg +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drift-ml
3
- Version: 0.2.8
3
+ Version: 0.2.10
4
4
  Summary: drift — terminal-first, chat-based AutoML. Open source. No tokens. No auth.
5
+ License-Expression: MIT
5
6
  Project-URL: Homepage, https://github.com/lakshitsachdeva/intent2model
6
7
  Project-URL: Repository, https://github.com/lakshitsachdeva/intent2model
7
8
  Keywords: automl,ml,machine-learning,cli,chat,local-first
8
9
  Classifier: Development Status :: 4 - Beta
9
10
  Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
@@ -17,6 +17,9 @@ Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: requests>=2.28.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
22
+ Requires-Dist: httpx>=0.24.0; extra == "dev"
20
23
  Dynamic: license-file
21
24
 
22
25
  # drift
@@ -16,7 +16,7 @@ Library:
16
16
  result = d.train()
17
17
  """
18
18
 
19
- __version__ = "0.2.8"
19
+ __version__ = "0.2.10"
20
20
 
21
21
  from drift.api import Drift
22
22
  from drift.cli.client import BackendClient, BackendError
@@ -16,7 +16,7 @@ except ImportError:
16
16
  requests = None
17
17
 
18
18
  GITHUB_REPO = "lakshitsachdeva/intent2model" # Engine binaries (same repo)
19
- ENGINE_TAG = "v0.2.8" # Pinned — direct URL, no API, no rate limits
19
+ ENGINE_TAG = "v0.2.10" # Pinned — direct URL, no API, no rate limits
20
20
  ENGINE_PORT = os.environ.get("DRIFT_ENGINE_PORT", "8000")
21
21
  HEALTH_URL = f"http://127.0.0.1:{ENGINE_PORT}/health"
22
22
 
@@ -224,4 +224,9 @@ def ensure_engine() -> bool:
224
224
  err = stderr_file.read_text().strip() if stderr_file.exists() else ""
225
225
  if err:
226
226
  print(f"drift: Engine log: {err[-400:]}", file=sys.stderr)
227
+ if platform.system() == "Windows":
228
+ print("drift: On Windows, try running the backend manually:", file=sys.stderr)
229
+ print(" cd backend && python -m uvicorn main:app --host 0.0.0.0 --port 8000", file=sys.stderr)
230
+ print(" Create .env in project root with GEMINI_API_KEY=... for LLM planning.", file=sys.stderr)
231
+ print(" Then: set DRIFT_BACKEND_URL=http://localhost:8000", file=sys.stderr)
227
232
  return False
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drift-ml
3
- Version: 0.2.8
3
+ Version: 0.2.10
4
4
  Summary: drift — terminal-first, chat-based AutoML. Open source. No tokens. No auth.
5
+ License-Expression: MIT
5
6
  Project-URL: Homepage, https://github.com/lakshitsachdeva/intent2model
6
7
  Project-URL: Repository, https://github.com/lakshitsachdeva/intent2model
7
8
  Keywords: automl,ml,machine-learning,cli,chat,local-first
8
9
  Classifier: Development Status :: 4 - Beta
9
10
  Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
@@ -17,6 +17,9 @@ Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: requests>=2.28.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
22
+ Requires-Dist: httpx>=0.24.0; extra == "dev"
20
23
  Dynamic: license-file
21
24
 
22
25
  # drift
@@ -18,4 +18,8 @@ drift_ml.egg-info/SOURCES.txt
18
18
  drift_ml.egg-info/dependency_links.txt
19
19
  drift_ml.egg-info/entry_points.txt
20
20
  drift_ml.egg-info/requires.txt
21
- drift_ml.egg-info/top_level.txt
21
+ drift_ml.egg-info/top_level.txt
22
+ tests/test_api.py
23
+ tests/test_automl_agent.py
24
+ tests/test_evaluator.py
25
+ tests/test_plan_normalizer.py
@@ -0,0 +1,5 @@
1
+ requests>=2.28.0
2
+
3
+ [dev]
4
+ pytest>=7.0.0
5
+ httpx>=0.24.0
@@ -4,9 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "drift-ml"
7
- version = "0.2.8"
7
+ version = "0.2.10"
8
8
  description = "drift — terminal-first, chat-based AutoML. Open source. No tokens. No auth."
9
9
  requires-python = ">=3.10"
10
+ license = "MIT"
10
11
  dependencies = [
11
12
  "requests>=2.28.0",
12
13
  ]
@@ -15,7 +16,6 @@ keywords = ["automl", "ml", "machine-learning", "cli", "chat", "local-first"]
15
16
  classifiers = [
16
17
  "Development Status :: 4 - Beta",
17
18
  "Intended Audience :: Developers",
18
- "License :: OSI Approved :: MIT License",
19
19
  "Programming Language :: Python :: 3",
20
20
  "Programming Language :: Python :: 3.10",
21
21
  "Programming Language :: Python :: 3.11",
@@ -33,3 +33,10 @@ drift = "drift.__main__:main"
33
33
  [tool.setuptools.packages.find]
34
34
  where = ["."]
35
35
  include = ["drift*"]
36
+
37
+ [project.optional-dependencies]
38
+ dev = ["pytest>=7.0.0", "httpx>=0.24.0"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+ pythonpath = ["backend"]
@@ -0,0 +1,29 @@
1
+ """Tests for FastAPI endpoints."""
2
+ import pytest
3
+ from fastapi.testclient import TestClient
4
+
5
+
6
+ @pytest.fixture
7
+ def client():
8
+ """Create test client. Import app after path setup."""
9
+ import sys
10
+ from pathlib import Path
11
+ backend = Path(__file__).resolve().parent.parent / "backend"
12
+ if str(backend) not in sys.path:
13
+ sys.path.insert(0, str(backend))
14
+ from main import app
15
+ return TestClient(app)
16
+
17
+
18
+ def test_health(client):
19
+ """Health endpoint returns 200."""
20
+ r = client.get("/health")
21
+ assert r.status_code == 200
22
+ data = r.json()
23
+ assert "status" in data or "llm_available" in data
24
+
25
+
26
+ def test_root(client):
27
+ """Root returns message."""
28
+ r = client.get("/")
29
+ assert r.status_code == 200
@@ -0,0 +1,72 @@
1
+ """Tests for automl_agent."""
2
+ import pytest
3
+ from agents.automl_agent import _extract_json, _map_alternate_schema
4
+ from agents.plan_normalizer import normalize_plan_dict
5
+ from schemas.pipeline_schema import AutoMLPlan
6
+
7
+
8
+ def test_extract_json_from_fenced():
9
+ """Extract JSON from markdown fenced block."""
10
+ text = '''```json
11
+ {"inferred_target": "x", "task_type": "regression"}
12
+ ```'''
13
+ out = _extract_json(text)
14
+ assert out["inferred_target"] == "x"
15
+ assert out["task_type"] == "regression"
16
+
17
+
18
+ def test_extract_json_plain():
19
+ """Extract JSON from plain text."""
20
+ text = 'Some text {"inferred_target": "y", "task_type": "classification"} more'
21
+ out = _extract_json(text)
22
+ assert out["inferred_target"] == "y"
23
+ assert out["task_type"] == "classification"
24
+
25
+
26
+ def test_extract_json_raises_on_no_json():
27
+ """Raises when no JSON object found."""
28
+ with pytest.raises(ValueError, match="No JSON object"):
29
+ _extract_json("no json here at all")
30
+
31
+
32
+ def test_map_alternate_schema_dataset_config():
33
+ """Map wrong schema (dataset_config) to our schema."""
34
+ wrong = {
35
+ "task_description": None,
36
+ "dataset_config": {"target_column": None, "problem_type": None, "features": []},
37
+ "preprocessing_pipeline": [],
38
+ "model_selection_strategy": {"algorithms": []},
39
+ }
40
+ profile = {
41
+ "columns": ["a", "b", "variety"],
42
+ "dtypes": {"a": "float64", "b": "float64", "variety": "object"},
43
+ "nunique": {"a": 35, "b": 23, "variety": 3},
44
+ "numeric_cols": ["a", "b"],
45
+ "identifier_like_columns": [],
46
+ }
47
+ mapped = _map_alternate_schema(wrong, profile, "variety")
48
+ assert mapped["inferred_target"] == "variety"
49
+ assert mapped["task_type"] in ("binary_classification", "multiclass_classification")
50
+ assert len(mapped["model_candidates"]) > 0
51
+
52
+
53
+ def test_full_pipeline_alternate_schema_to_automl_plan():
54
+ """Map alternate schema -> normalize -> AutoMLPlan validates."""
55
+ wrong = {
56
+ "dataset_config": {"target_column": None, "problem_type": None},
57
+ "preprocessing_pipeline": [],
58
+ "model_selection_strategy": {"algorithms": []},
59
+ }
60
+ profile = {
61
+ "columns": ["a", "b", "y"],
62
+ "dtypes": {"a": "float64", "b": "float64", "y": "int64"},
63
+ "nunique": {"a": 10, "b": 8, "y": 2},
64
+ "numeric_cols": ["a", "b"],
65
+ "identifier_like_columns": [],
66
+ "missing_percent": {},
67
+ }
68
+ mapped = _map_alternate_schema(wrong, profile, "y")
69
+ norm = normalize_plan_dict(mapped, profile=profile, requested_target="y")
70
+ plan = AutoMLPlan(**norm)
71
+ assert plan.inferred_target == "y"
72
+ assert plan.task_type == "binary_classification"
@@ -0,0 +1,31 @@
1
+ """Tests for ml.evaluator."""
2
+ import pytest
3
+ import pandas as pd
4
+ from ml.evaluator import prune_features_aggressive, evaluate_dataset
5
+
6
+
7
+ def test_prune_features_drops_constant():
8
+ """Constant columns are dropped."""
9
+ df = pd.DataFrame({"a": [1, 2, 3], "b": [5, 5, 5], "target": [0, 1, 0]})
10
+ out, dropped = prune_features_aggressive(df, "target", "classification")
11
+ assert "b" in dropped
12
+ assert "b" not in out.columns
13
+
14
+
15
+ def test_prune_features_keeps_target():
16
+ """Target is never dropped."""
17
+ df = pd.DataFrame({"x": [1, 2, 3], "target": [0, 1, 0]})
18
+ out, _ = prune_features_aggressive(df, "target", "classification")
19
+ assert "target" in out.columns
20
+
21
+
22
+ def test_evaluate_dataset_regression():
23
+ """evaluate_dataset runs for regression."""
24
+ df = pd.DataFrame({
25
+ "a": [1.0, 2.0, 3.0, 4.0, 5.0] * 4,
26
+ "b": [2.0, 4.0, 6.0, 8.0, 10.0] * 4,
27
+ "target": [3.0, 6.0, 9.0, 12.0, 15.0] * 4,
28
+ })
29
+ result = evaluate_dataset(df, "target", "regression")
30
+ assert "warnings" in result
31
+ assert isinstance(result["warnings"], list)
@@ -0,0 +1,52 @@
1
+ """Tests for plan_normalizer."""
2
+ import pytest
3
+ from agents.plan_normalizer import normalize_plan_dict
4
+
5
+
6
+ def test_normalize_plan_dict_minimal():
7
+ """Minimal plan with profile gets filled correctly."""
8
+ profile = {
9
+ "columns": ["a", "b", "target"],
10
+ "dtypes": {"a": "float64", "b": "int64", "target": "object"},
11
+ "nunique": {"a": 10, "b": 5, "target": 3},
12
+ "numeric_cols": ["a", "b"],
13
+ "identifier_like_columns": [],
14
+ "missing_percent": {},
15
+ }
16
+ plan = {
17
+ "inferred_target": "target",
18
+ "task_type": "multiclass_classification",
19
+ "model_candidates": [{"model_name": "logistic_regression", "reason_md": "Baseline.", "params": {}}],
20
+ }
21
+ out = normalize_plan_dict(plan, profile=profile)
22
+ assert out["inferred_target"] == "target"
23
+ assert out["task_type"] == "multiclass_classification"
24
+ assert len(out["feature_transforms"]) >= 2 # a, b (target dropped from features)
25
+ assert len(out["model_candidates"]) > 0
26
+
27
+
28
+ def test_normalize_plan_dict_column_name_alias():
29
+ """column_name is normalized to name."""
30
+ profile = {"columns": ["x", "y"], "dtypes": {}, "nunique": {}, "numeric_cols": ["x", "y"], "identifier_like_columns": [], "missing_percent": {}}
31
+ plan = {
32
+ "inferred_target": "y",
33
+ "task_type": "regression",
34
+ "feature_transforms": [{"column_name": "x", "kind": "continuous", "drop": False}],
35
+ }
36
+ out = normalize_plan_dict(plan, profile=profile)
37
+ assert out["feature_transforms"][0]["name"] == "x"
38
+
39
+
40
+ def test_normalize_plan_dict_infers_target_from_profile():
41
+ """When inferred_target missing, uses profile."""
42
+ profile = {
43
+ "columns": ["a", "b", "c"],
44
+ "identifier_like_columns": [],
45
+ "dtypes": {},
46
+ "nunique": {"c": 2},
47
+ "numeric_cols": ["a", "b"],
48
+ "missing_percent": {},
49
+ }
50
+ plan = {"task_type": "binary_classification"}
51
+ out = normalize_plan_dict(plan, profile=profile)
52
+ assert out["inferred_target"] == "c"
@@ -1 +0,0 @@
1
- requests>=2.28.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes