rakam-eval-sdk 0.1.16__tar.gz → 0.1.16rc1__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.
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rakam-eval-sdk
3
- Version: 0.1.16
3
+ Version: 0.1.16rc1
4
4
  Summary: Evaluation Framework SDK
5
5
  Author: Mohamed Bachar Touil
6
6
  License: MIT
7
+ Requires-Dist: psutil>=7.2.1
7
8
  Requires-Dist: pydantic>=2.10.6
8
9
  Requires-Dist: requests
9
10
  Requires-Dist: typer>=0.20.1
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "rakam-eval-sdk"
7
- version = "0.1.16"
7
+ version = "0.1.16rc1"
8
8
  description = "Evaluation Framework SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -13,6 +13,7 @@ authors = [
13
13
  { name = "Mohamed Bachar Touil" }
14
14
  ]
15
15
  dependencies = [
16
+ "psutil>=7.2.1",
16
17
  "pydantic>=2.10.6",
17
18
  "requests",
18
19
  "typer>=0.20.1",
@@ -40,4 +41,4 @@ publish-url = "https://test.pypi.org/legacy/"
40
41
  explicit = true
41
42
 
42
43
  [project.scripts]
43
- mycli = "rakam_eval_sdk.cli:main"
44
+ rakam_eval = "rakam_eval_sdk.cli:main"
@@ -0,0 +1,119 @@
1
+ # cli.py
2
+ from pathlib import Path
3
+
4
+ import typer
5
+
6
+ from rakam_eval_sdk.utils.decorator_utils import find_decorated_functions, load_module_from_path
7
+ from rakam_eval_sdk.decorators import eval_run
8
+ app = typer.Typer(help="CLI tools for evaluation utilities")
9
+
10
+
11
+ @app.command()
12
+ def find_eval_run_by_name(
13
+ directory: Path = typer.Argument(
14
+ Path("./eval"),
15
+ exists=True,
16
+ file_okay=False,
17
+ dir_okay=True,
18
+ help="Directory to scan (default: ./eval)",
19
+ ),
20
+ recursive: bool = typer.Option(
21
+ False,
22
+ "--recursive",
23
+ "-r",
24
+ help="Recursively search for Python files",
25
+ ),
26
+ ):
27
+ """
28
+ Find functions decorated with @track.
29
+ """
30
+ TARGET_DECORATOR = eval_run.__name__
31
+ files = (
32
+ directory.rglob("*.py")
33
+ if recursive
34
+ else directory.glob("*.py")
35
+ )
36
+
37
+ found = False
38
+
39
+ for file in sorted(files):
40
+ functions = find_decorated_functions(file, TARGET_DECORATOR)
41
+ for fn in functions:
42
+ found = True
43
+ typer.echo(f"{file}:{fn}")
44
+
45
+ if not found:
46
+ typer.echo(f"No @{TARGET_DECORATOR} functions found.")
47
+
48
+
49
+ @app.command("run")
50
+ def run_eval_runs(
51
+ directory: Path = typer.Argument(
52
+ Path("./eval"),
53
+ exists=True,
54
+ file_okay=False,
55
+ dir_okay=True,
56
+ help="Directory to scan (default: ./eval)",
57
+ ),
58
+ recursive: bool = typer.Option(
59
+ False,
60
+ "-r",
61
+ "--recursive",
62
+ help="Recursively search for Python files",
63
+ ),
64
+ dry_run: bool = typer.Option(
65
+ False,
66
+ "--dry-run",
67
+ help="Only list functions without executing them",
68
+ ),
69
+ ):
70
+ """
71
+ Find and execute all functions decorated with @eval_run.
72
+ """
73
+ files = (
74
+ directory.rglob("*.py")
75
+ if recursive
76
+ else directory.glob("*.py")
77
+ )
78
+ TARGET_DECORATOR = eval_run.__name__
79
+
80
+ executed_any = False
81
+
82
+ for file in sorted(files):
83
+ functions = find_decorated_functions(file, TARGET_DECORATOR)
84
+ if not functions:
85
+ continue
86
+
87
+ typer.echo(f"\n📄 {file}")
88
+
89
+ module = None
90
+ if not dry_run:
91
+ try:
92
+ module = load_module_from_path(file)
93
+ except Exception as e:
94
+ typer.echo(f" ❌ Failed to import module: {e}")
95
+ continue
96
+
97
+ for fn_name in functions:
98
+ typer.echo(f" ▶ {fn_name}")
99
+
100
+ if dry_run:
101
+ continue
102
+
103
+ try:
104
+ func = getattr(module, fn_name)
105
+ func() # <-- actual execution
106
+ executed_any = True
107
+ except Exception as e:
108
+ typer.echo(f" ❌ Execution failed: {e}")
109
+
110
+ if not executed_any and not dry_run:
111
+ typer.echo("\nNo @eval_run functions executed.")
112
+
113
+
114
+ def main():
115
+ app()
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()
@@ -0,0 +1,44 @@
1
+
2
+ import time
3
+ import os
4
+ import psutil
5
+ import functools
6
+
7
+
8
+ def eval_run(*dargs, **dkwargs):
9
+ def wrapper(func):
10
+ @functools.wraps(func)
11
+ def inner(*args, **kwargs):
12
+ process = psutil.Process(os.getpid())
13
+
14
+ # Start metrics
15
+ start_time = time.perf_counter()
16
+ start_cpu = process.cpu_times()
17
+ start_mem = process.memory_info().rss
18
+
19
+ try:
20
+ result = func(*args, **kwargs)
21
+ return result
22
+ finally:
23
+ # End metrics
24
+ end_time = time.perf_counter()
25
+ end_cpu = process.cpu_times()
26
+ end_mem = process.memory_info().rss
27
+
28
+ elapsed = end_time - start_time
29
+ cpu_used = (
30
+ (end_cpu.user + end_cpu.system)
31
+ - (start_cpu.user + start_cpu.system)
32
+ )
33
+ mem_diff_mb = (end_mem - start_mem) / (1024 * 1024)
34
+
35
+ print(
36
+ f"[eval_run] {func.__module__}.{func.__name__} | "
37
+ f"time={elapsed:.4f}s | "
38
+ f"cpu={cpu_used:.4f}s | "
39
+ f"mem_delta={mem_diff_mb:.2f}MB"
40
+ )
41
+
42
+ return inner
43
+
44
+ return wrapper
@@ -0,0 +1,69 @@
1
+ import ast
2
+ import importlib
3
+ import importlib.util
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from types import ModuleType
7
+ from typing import Callable, Iterable, List, Tuple
8
+
9
+
10
+ class DecoratedFunctionVisitor(ast.NodeVisitor):
11
+ def __init__(self, decorator_name: str):
12
+ self.decorator_name = decorator_name
13
+ self.results: List[str] = []
14
+
15
+ def visit_FunctionDef(self, node: ast.FunctionDef):
16
+ for deco in node.decorator_list:
17
+ if self._matches(deco):
18
+ self.results.append(node.name)
19
+ self.generic_visit(node)
20
+
21
+ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
22
+ for deco in node.decorator_list:
23
+ if self._matches(deco):
24
+ self.results.append(node.name)
25
+ self.generic_visit(node)
26
+
27
+ def _matches(self, deco: ast.expr) -> bool:
28
+ # @deco
29
+ if isinstance(deco, ast.Name):
30
+ return deco.id == self.decorator_name
31
+
32
+ # @module.deco
33
+ if isinstance(deco, ast.Attribute):
34
+ return deco.attr == self.decorator_name
35
+
36
+ # @deco(...)
37
+ if isinstance(deco, ast.Call):
38
+ return self._matches(deco.func)
39
+
40
+ return False
41
+
42
+
43
+ def find_decorated_functions(
44
+ file_path: Path,
45
+ decorator_name: str,
46
+ ) -> List[str]:
47
+ tree = ast.parse(file_path.read_text(encoding="utf-8"))
48
+ visitor = DecoratedFunctionVisitor(decorator_name)
49
+ visitor.visit(tree)
50
+ return visitor.results
51
+
52
+
53
+ def load_module_from_path(file_path: Path) -> ModuleType:
54
+ spec = importlib.util.spec_from_file_location(file_path.stem, file_path)
55
+ if spec is None or spec.loader is None:
56
+ raise ImportError(f"Cannot import {file_path}")
57
+ module = importlib.util.module_from_spec(spec)
58
+ spec.loader.exec_module(module)
59
+ return module
60
+
61
+
62
+
63
+
64
+
65
+ def get_function(module: ModuleType, function_name: str) -> Callable:
66
+ func = getattr(module, function_name, None)
67
+ if func is None:
68
+ raise AttributeError(f"{function_name} not found in {module.__name__}")
69
+ return func
@@ -1,17 +0,0 @@
1
- # cli.py
2
- import typer
3
- from pathlib import Path
4
-
5
- app = typer.Typer()
6
-
7
-
8
- @app.command()
9
- def read(file: Path):
10
- """Read a Python file"""
11
- if file.suffix != ".py":
12
- raise typer.BadParameter("Must be a .py file")
13
- typer.echo(file.read_text())
14
-
15
-
16
- def main():
17
- app()