modaic 0.1.0__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.

Potentially problematic release.


This version of modaic might be problematic. Click here for more details.

Files changed (39) hide show
  1. modaic/__init__.py +25 -0
  2. modaic/agents/rag_agent.py +33 -0
  3. modaic/agents/registry.py +84 -0
  4. modaic/auto_agent.py +228 -0
  5. modaic/context/__init__.py +34 -0
  6. modaic/context/base.py +1064 -0
  7. modaic/context/dtype_mapping.py +25 -0
  8. modaic/context/table.py +585 -0
  9. modaic/context/text.py +94 -0
  10. modaic/databases/__init__.py +35 -0
  11. modaic/databases/graph_database.py +269 -0
  12. modaic/databases/sql_database.py +355 -0
  13. modaic/databases/vector_database/__init__.py +12 -0
  14. modaic/databases/vector_database/benchmarks/baseline.py +123 -0
  15. modaic/databases/vector_database/benchmarks/common.py +48 -0
  16. modaic/databases/vector_database/benchmarks/fork.py +132 -0
  17. modaic/databases/vector_database/benchmarks/threaded.py +119 -0
  18. modaic/databases/vector_database/vector_database.py +722 -0
  19. modaic/databases/vector_database/vendors/milvus.py +408 -0
  20. modaic/databases/vector_database/vendors/mongodb.py +0 -0
  21. modaic/databases/vector_database/vendors/pinecone.py +0 -0
  22. modaic/databases/vector_database/vendors/qdrant.py +1 -0
  23. modaic/exceptions.py +38 -0
  24. modaic/hub.py +305 -0
  25. modaic/indexing.py +127 -0
  26. modaic/module_utils.py +341 -0
  27. modaic/observability.py +275 -0
  28. modaic/precompiled.py +429 -0
  29. modaic/query_language.py +321 -0
  30. modaic/storage/__init__.py +3 -0
  31. modaic/storage/file_store.py +239 -0
  32. modaic/storage/pickle_store.py +25 -0
  33. modaic/types.py +287 -0
  34. modaic/utils.py +21 -0
  35. modaic-0.1.0.dist-info/METADATA +281 -0
  36. modaic-0.1.0.dist-info/RECORD +39 -0
  37. modaic-0.1.0.dist-info/WHEEL +5 -0
  38. modaic-0.1.0.dist-info/licenses/LICENSE +31 -0
  39. modaic-0.1.0.dist-info/top_level.txt +1 -0
modaic/module_utils.py ADDED
@@ -0,0 +1,341 @@
1
+ import importlib.util
2
+ import os
3
+ import re
4
+ import shutil
5
+ import sys
6
+ import sysconfig
7
+ import warnings
8
+ from pathlib import Path
9
+ from types import ModuleType
10
+ from typing import Dict
11
+
12
+ import tomlkit as tomlk
13
+
14
+ from .utils import compute_cache_dir
15
+
16
+ MODAIC_CACHE = compute_cache_dir()
17
+ AGENTS_CACHE = Path(MODAIC_CACHE) / "agents"
18
+ EDITABLE_MODE = os.getenv("EDITABLE_MODE", "false").lower() == "true"
19
+
20
+
21
+ def is_builtin(module_name: str) -> bool:
22
+ """Check whether a module name refers to a built-in module.
23
+
24
+ Args:
25
+ module_name: The fully qualified module name.
26
+
27
+ Returns:
28
+ bool: True if the module is a Python built-in.
29
+ """
30
+
31
+ return module_name in sys.builtin_module_names
32
+
33
+
34
+ def is_stdlib(module_name: str) -> bool:
35
+ """Check whether a module belongs to the Python standard library.
36
+
37
+ Args:
38
+ module_name: The fully qualified module name.
39
+
40
+ Returns:
41
+ bool: True if the module is part of the stdlib (including built-ins).
42
+ """
43
+
44
+ try:
45
+ spec = importlib.util.find_spec(module_name)
46
+ except ValueError:
47
+ return False
48
+ except Exception:
49
+ return False
50
+ if not spec:
51
+ return False
52
+ if spec.origin == "built-in":
53
+ return True
54
+ origin = spec.origin or ""
55
+ stdlib_dir = Path(sysconfig.get_paths()["stdlib"]).resolve()
56
+ try:
57
+ origin_path = Path(origin).resolve()
58
+ except OSError:
59
+ return False
60
+ return stdlib_dir in origin_path.parents or origin_path == stdlib_dir
61
+
62
+
63
+ def is_builtin_or_frozen(mod: ModuleType) -> bool:
64
+ """Check whether a module object is built-in or frozen.
65
+
66
+ Args:
67
+ mod: The module object.
68
+
69
+ Returns:
70
+ bool: True if the module is built-in or frozen.
71
+ """
72
+
73
+ spec = getattr(mod, "__spec__", None)
74
+ origin = getattr(spec, "origin", None)
75
+ name = getattr(mod, "__name__", None)
76
+ return (name in sys.builtin_module_names) or (origin in ("built-in", "frozen"))
77
+
78
+
79
+ def get_internal_imports() -> Dict[str, ModuleType]:
80
+ """Return only internal modules currently loaded in sys.modules.
81
+
82
+ Internal modules are defined as those not installed in site/dist packages
83
+ (covers virtualenv `.venv` cases as well).
84
+
85
+ If the environment variable `EDITABLE_MODE` is set to "true" (case-insensitive),
86
+ modules located under `src/modaic/` are also excluded.
87
+
88
+ Args:
89
+ None
90
+
91
+ Returns:
92
+ Dict[str, ModuleType]: Mapping of module names to module objects that are
93
+ not located under any "site-packages" or "dist-packages" directory.
94
+ """
95
+
96
+ internal: Dict[str, ModuleType] = {}
97
+
98
+ seen: set[int] = set()
99
+ for name, module in list(sys.modules.items()):
100
+ if module is None:
101
+ continue
102
+ module_id = id(module)
103
+ if module_id in seen:
104
+ continue
105
+ seen.add(module_id)
106
+
107
+ if is_builtin_or_frozen(module):
108
+ continue
109
+
110
+ module_file = getattr(module, "__file__", None)
111
+ if not module_file:
112
+ continue
113
+ try:
114
+ module_path = Path(module_file).resolve()
115
+ except OSError:
116
+ continue
117
+
118
+ if is_builtin(name) or is_stdlib(name):
119
+ continue
120
+ if is_external_package(module_path):
121
+ continue
122
+ if EDITABLE_MODE:
123
+ posix_path = module_path.as_posix().lower()
124
+ if "src/modaic" in posix_path:
125
+ continue
126
+ normalized_name = name
127
+
128
+ internal[normalized_name] = module
129
+
130
+ return internal
131
+
132
+
133
+ def resolve_project_root() -> Path:
134
+ """
135
+ Return the project root directory, must be a directory containing a pyproject.toml file.
136
+
137
+ Raises:
138
+ FileNotFoundError: If pyproject.toml is not found in the current directory.
139
+ """
140
+ pyproject_path = Path("pyproject.toml")
141
+ if not pyproject_path.exists():
142
+ raise FileNotFoundError("pyproject.toml not found in current directory")
143
+ return pyproject_path.resolve().parent
144
+
145
+
146
+ def is_path_ignored(target_path: Path, ignored_paths: list[Path]) -> bool:
147
+ """Return True if target_path matches or is contained within any ignored path."""
148
+ try:
149
+ absolute_target = target_path.resolve()
150
+ except OSError:
151
+ return False
152
+ for ignored in ignored_paths:
153
+ if absolute_target == ignored:
154
+ return True
155
+ try:
156
+ absolute_target.relative_to(ignored)
157
+ return True
158
+ except Exception:
159
+ pass
160
+ return False
161
+
162
+
163
+ def copy_module_layout(base_dir: Path, name_parts: list[str]) -> None:
164
+ """
165
+ Create ancestor package directories and ensure each contains an __init__.py file.
166
+ Example:
167
+ Given a base_dir of "/tmp/modaic" and name_parts of ["agent","indexer"],
168
+ creates the following layout:
169
+ | /tmp/modaic/
170
+ | | agent/
171
+ | | | __init__.py
172
+ | | indexer/
173
+ | | | __init__.py
174
+ """
175
+ current = base_dir
176
+ for part in name_parts:
177
+ current = current / part
178
+ current.mkdir(parents=True, exist_ok=True)
179
+ init_file = current / "__init__.py"
180
+ if not init_file.exists():
181
+ init_file.touch()
182
+
183
+
184
+ def is_external_package(path: Path) -> bool:
185
+ """Return True if the path is under site-packages or dist-packages."""
186
+ parts = {p.lower() for p in path.parts}
187
+ return "site-packages" in parts or "dist-packages" in parts
188
+
189
+
190
+ def init_agent_repo(repo_path: str, with_code: bool = True) -> Path:
191
+ """Create a local repository staging directory for agent modules and files, excluding ignored files and folders."""
192
+ repo_dir = Path(AGENTS_CACHE) / repo_path
193
+ repo_dir.mkdir(parents=True, exist_ok=True)
194
+
195
+ internal_imports = get_internal_imports()
196
+ ignored_paths = get_ignored_files()
197
+
198
+ seen_files: set[Path] = set()
199
+
200
+ readme_src = Path("README.md")
201
+ if readme_src.exists() and not is_path_ignored(readme_src, ignored_paths):
202
+ readme_dest = repo_dir / "README.md"
203
+ shutil.copy2(readme_src, readme_dest)
204
+ else:
205
+ warnings.warn("README.md not found in current directory. Please add one when pushing to the hub.", stacklevel=4)
206
+
207
+ if not with_code:
208
+ return repo_dir
209
+
210
+ for module_name, module in internal_imports.items():
211
+ module_file = getattr(module, "__file__", None)
212
+ if not module_file:
213
+ continue
214
+ try:
215
+ src_path = Path(module_file).resolve()
216
+ except OSError:
217
+ continue
218
+ if src_path.suffix != ".py":
219
+ continue
220
+ if is_path_ignored(src_path, ignored_paths):
221
+ continue
222
+ if src_path in seen_files:
223
+ continue
224
+ seen_files.add(src_path)
225
+
226
+ # Split modul_name to get the relative path
227
+ name_parts = module_name.split(".")
228
+ if src_path.name == "__init__.py":
229
+ copy_module_layout(repo_dir, name_parts)
230
+ dest_path = repo_dir.joinpath(*name_parts) / "__init__.py"
231
+ else:
232
+ if len(name_parts) > 1:
233
+ copy_module_layout(repo_dir, name_parts[:-1])
234
+ else:
235
+ repo_dir.mkdir(parents=True, exist_ok=True)
236
+ # use the file name to name the file
237
+ dest_path = repo_dir.joinpath(*name_parts[:-1]) / src_path.name
238
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
239
+ shutil.copy2(src_path, dest_path)
240
+ return repo_dir
241
+
242
+
243
+ def create_agent_repo(repo_path: str, with_code: bool = True) -> Path:
244
+ """
245
+ Create a temporary directory inside the Modaic cache. Containing everything that will be pushed to the hub. This function adds the following files:
246
+ - All internal modules used to run the agent
247
+ - The pyproject.toml
248
+ - The README.md
249
+ """
250
+ package_name = repo_path.split("/")[-1]
251
+ repo_dir = init_agent_repo(repo_path, with_code=with_code)
252
+ if with_code:
253
+ create_pyproject_toml(repo_dir, package_name)
254
+
255
+ return repo_dir
256
+
257
+
258
+ def get_ignored_files() -> list[Path]:
259
+ """Return a list of absolute Paths that should be excluded from staging."""
260
+ project_root = resolve_project_root()
261
+ pyproject_path = Path("pyproject.toml")
262
+ doc = tomlk.parse(pyproject_path.read_text(encoding="utf-8"))
263
+
264
+ # Safely get [tool.modaic.ignore]
265
+ ignore_table = (
266
+ doc.get("tool", {}) # [tool]
267
+ .get("modaic", {}) # [tool.modaic]
268
+ .get("ignore") # [tool.modaic.ignore]
269
+ )
270
+
271
+ if ignore_table is None or "files" not in ignore_table:
272
+ return []
273
+
274
+ ignored: list[Path] = []
275
+ for entry in ignore_table["files"]:
276
+ try:
277
+ ignored.append((project_root / entry).resolve())
278
+ except OSError:
279
+ continue
280
+ return ignored
281
+
282
+
283
+ def create_pyproject_toml(repo_dir: Path, package_name: str):
284
+ """
285
+ Create a new pyproject.toml for the bundled agent in the temp directory.
286
+ """
287
+ old = Path("pyproject.toml").read_text(encoding="utf-8")
288
+ new = repo_dir / "pyproject.toml"
289
+
290
+ doc_old = tomlk.parse(old)
291
+ doc_new = tomlk.document()
292
+
293
+ if "project" not in doc_old:
294
+ raise KeyError("No [project] table in old TOML")
295
+ doc_new["project"] = doc_old["project"]
296
+ doc_new["project"]["dependencies"] = get_filtered_dependencies(doc_old["project"]["dependencies"])
297
+ if "tool" in doc_old and "uv" in doc_old["tool"] and "sources" in doc_old["tool"]["uv"]:
298
+ doc_new["tool"] = {"uv": {"sources": doc_old["tool"]["uv"]["sources"]}}
299
+ warn_if_local(doc_new["tool"]["uv"]["sources"])
300
+
301
+ doc_new["project"]["name"] = package_name
302
+
303
+ with open(new, "w") as fp:
304
+ tomlk.dump(doc_new, fp)
305
+
306
+
307
+ def get_filtered_dependencies(dependencies: list[str]) -> list[str]:
308
+ """
309
+ Get the dependencies that should be included in the bundled agent.
310
+ """
311
+ pyproject_path = Path("pyproject.toml")
312
+ doc = tomlk.parse(pyproject_path.read_text(encoding="utf-8"))
313
+
314
+ # Safely get [tool.modaic.ignore]
315
+ ignore_table = (
316
+ doc.get("tool", {}) # [tool]
317
+ .get("modaic", {}) # [tool.modaic]
318
+ .get("ignore", {}) # [tool.modaic.ignore]
319
+ )
320
+
321
+ if "dependencies" not in ignore_table:
322
+ return dependencies
323
+
324
+ ignored_dependencies = ignore_table["dependencies"]
325
+ if not ignored_dependencies:
326
+ return dependencies
327
+ pattern = re.compile(r"\b(" + "|".join(map(re.escape, ignored_dependencies)) + r")\b")
328
+ filtered_dependencies = [pkg for pkg in dependencies if not pattern.search(pkg)]
329
+ return filtered_dependencies
330
+
331
+
332
+ def warn_if_local(sources: dict[str, dict]):
333
+ """
334
+ Warn if the agent is bundled with a local package.
335
+ """
336
+ for source, config in sources.items():
337
+ if "path" in config:
338
+ warnings.warn(
339
+ f"Bundling agent with local package {source} installed from {config['path']}. This is not recommended.",
340
+ stacklevel=5,
341
+ )
@@ -0,0 +1,275 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from functools import wraps
5
+ from typing import Any, Callable, Dict, Optional, TypeVar, cast
6
+
7
+ import opik
8
+ from opik import Opik, config
9
+ from typing_extensions import Concatenate, ParamSpec
10
+
11
+ from .utils import validate_project_name
12
+
13
+ P = ParamSpec("P") # params of the function
14
+ R = TypeVar("R") # return type of the function
15
+ T = TypeVar("T", bound="Trackable") # an instance of a class that inherits from Trackable
16
+
17
+
18
+ @dataclass
19
+ class ModaicSettings:
20
+ """Global settings for Modaic observability."""
21
+
22
+ tracing: bool = False
23
+ repo: Optional[str] = None
24
+ project: Optional[str] = None
25
+ base_url: str = "https://api.modaic.dev"
26
+ modaic_token: Optional[str] = None
27
+ default_tags: Dict[str, str] = field(default_factory=dict)
28
+ log_inputs: bool = True
29
+ log_outputs: bool = True
30
+ max_input_size: int = 10000
31
+ max_output_size: int = 10000
32
+
33
+
34
+ # global settings instance
35
+ _settings = ModaicSettings()
36
+ _opik_client: Optional[Opik] = None
37
+ _configured = False
38
+
39
+
40
+ def configure(
41
+ tracing: bool = True,
42
+ repo: Optional[str] = None,
43
+ project: Optional[str] = None,
44
+ base_url: str = "https://api.modaic.dev",
45
+ modaic_token: Optional[str] = None,
46
+ default_tags: Optional[Dict[str, str]] = None,
47
+ log_inputs: bool = True,
48
+ log_outputs: bool = True,
49
+ max_input_size: int = 10000,
50
+ max_output_size: int = 10000,
51
+ **opik_kwargs,
52
+ ) -> None:
53
+ """Configure Modaic observability settings globally.
54
+
55
+ Args:
56
+ tracing: Whether observability is enabled
57
+ repo: Default repository name (e.g., 'user/repo')
58
+ project: Default project name
59
+ base_url: Opik server URL
60
+ modaic_token: Authentication token for Opik
61
+ default_tags: Default tags to apply to all traces
62
+ log_inputs: Whether to log function inputs
63
+ log_outputs: Whether to log function outputs
64
+ max_input_size: Maximum size of logged inputs
65
+ max_output_size: Maximum size of logged outputs
66
+ **opik_kwargs: Additional arguments passed to opik.configure()
67
+ """
68
+ global _settings, _opik_client, _configured
69
+ if project and not repo:
70
+ raise ValueError("You cannot specify a project without a repo")
71
+
72
+ # update global settings
73
+ _settings.tracing = tracing
74
+ _settings.repo = repo
75
+ _settings.project = project
76
+ _settings.base_url = base_url
77
+ _settings.modaic_token = modaic_token
78
+ _settings.default_tags = default_tags or {}
79
+ _settings.log_inputs = log_inputs
80
+ _settings.log_outputs = log_outputs
81
+ _settings.max_input_size = max_input_size
82
+ _settings.max_output_size = max_output_size
83
+
84
+ if tracing:
85
+ # configure Opik
86
+ opik_config = {"use_local": True, "url": base_url, "force": True, "automatic_approvals": True, **opik_kwargs}
87
+
88
+ opik.configure(**opik_config)
89
+
90
+ # create client with project if specified
91
+ project_name = f"{repo}-{project}" if project else repo
92
+ if project_name:
93
+ _opik_client = Opik(host=base_url, project_name=project_name)
94
+
95
+ config.update_session_config("track_disable", not tracing)
96
+
97
+ _configured = True
98
+
99
+
100
+ def _get_effective_settings(
101
+ repo: Optional[str] = None, project: Optional[str] = None, tags: Optional[Dict[str, str]] = None
102
+ ) -> Dict[str, Any]:
103
+ """Get effective settings by merging global and local parameters."""
104
+ effective_repo = repo or _settings.repo
105
+ effective_project = (f"{repo}-{project}" if project else effective_repo) or _settings.project
106
+
107
+ # validate project name if provided
108
+ if effective_project:
109
+ validate_project_name(effective_project)
110
+
111
+ # merge tags
112
+ effective_tags = {**_settings.default_tags}
113
+ if tags:
114
+ effective_tags.update(tags)
115
+
116
+ return {"repo": effective_repo, "project": effective_project, "tags": effective_tags}
117
+
118
+
119
+ def _truncate_data(data: Any, max_size: int) -> Any:
120
+ """Truncate data if it exceeds max_size when serialized."""
121
+ try:
122
+ import json
123
+
124
+ serialized = json.dumps(data, default=str)
125
+ if len(serialized) > max_size:
126
+ return f"<Data truncated: {len(serialized)} chars>"
127
+ return data
128
+ except Exception:
129
+ # if serialization fails, convert to string and truncate
130
+ str_data = str(data)
131
+ if len(str_data) > max_size:
132
+ return str_data[:max_size] + "..."
133
+ return str_data
134
+
135
+
136
+ def track( # noqa: ANN201
137
+ name: Optional[str] = None,
138
+ repo: Optional[str] = None,
139
+ project: Optional[str] = None,
140
+ tags: Optional[Dict[str, str]] = None,
141
+ span_type: str = "general",
142
+ capture_input: Optional[bool] = None,
143
+ capture_output: Optional[bool] = None,
144
+ metadata: Optional[Dict[str, Any]] = None,
145
+ **opik_kwargs,
146
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
147
+ """Decorator to track function calls with Opik.
148
+
149
+ Args:
150
+ name: Custom name for the tracked operation
151
+ repo: Repository name (overrides global setting)
152
+ project: Project name (overrides global setting)
153
+ tags: Additional tags for this operation
154
+ span_type: Type of span ('general', 'tool', 'llm', 'guardrail')
155
+ capture_input: Whether to capture input (overrides global setting)
156
+ capture_output: Whether to capture output (overrides global setting)
157
+ metadata: Additional metadata
158
+ **opik_kwargs: Additional arguments passed to opik.track
159
+ """
160
+
161
+ def decorator(func: Callable) -> Callable:
162
+ if not _settings.tracing:
163
+ return func
164
+
165
+ # get effective settings
166
+ settings = _get_effective_settings(repo, project, tags)
167
+
168
+ # determine capture settings
169
+ should_capture_input = capture_input if capture_input is not None else _settings.log_inputs
170
+ should_capture_output = capture_output if capture_output is not None else _settings.log_outputs
171
+
172
+ # build opik.track arguments
173
+ track_args: Dict[str, Any] = {
174
+ "type": span_type,
175
+ "capture_input": should_capture_input,
176
+ "capture_output": should_capture_output,
177
+ **opik_kwargs,
178
+ }
179
+
180
+ # add project if available
181
+ if settings["project"]:
182
+ track_args["project_name"] = settings["project"]
183
+
184
+ if name:
185
+ track_args["name"] = name
186
+
187
+ # add tags and metadata
188
+ if settings["tags"] or metadata:
189
+ combined_metadata = {**(metadata or {})}
190
+ if settings["tags"]:
191
+ combined_metadata["tags"] = settings["tags"]
192
+ if settings["repo"]:
193
+ combined_metadata["repo"] = settings["repo"]
194
+ track_args["metadata"] = combined_metadata
195
+
196
+ # apply opik.track decorator
197
+ # Return function with type annotations persisted for static type checking
198
+ return cast(Callable[P, R], opik.track(**track_args)(func))
199
+
200
+ return decorator
201
+
202
+
203
+ class Trackable:
204
+ """Base class for objects that support automatic tracking.
205
+
206
+ Manages the attributes repo, project, and commit for classes that subclass it.
207
+ All Modaic classes except PrecompiledAgent should inherit from this class.
208
+ """
209
+
210
+ def __init__(self, repo: Optional[str] = None, project: Optional[str] = None, commit: Optional[str] = None):
211
+ self.repo = repo
212
+ self.project = project
213
+ self.commit = commit
214
+
215
+ def set_repo_project(self, repo: Optional[str] = None, project: Optional[str] = None, trace: bool = True):
216
+ """Update the repo and project for this trackable object."""
217
+ if repo is not None:
218
+ self.repo = repo
219
+
220
+ self.project = f"{self.repo}-{project}" if project else self.repo
221
+
222
+ # configure global tracing
223
+ if trace and (repo or project):
224
+ configure(tracing=trace, repo=repo or self.repo, project=project or self.project)
225
+
226
+
227
+ MethodDecorator = Callable[
228
+ [Callable[Concatenate[T, P], R]],
229
+ Callable[Concatenate[T, P], R],
230
+ ]
231
+
232
+
233
+ def track_modaic_obj(func: Callable[Concatenate[T, P], R]) -> Callable[Concatenate[T, P], R]:
234
+ """Method decorator for Trackable objects to automatically track method calls.
235
+
236
+ Uses self.repo and self.project to automatically set repository and project
237
+ for modaic.track, then wraps the function with modaic.track.
238
+
239
+ Usage:
240
+ class Retriever(Trackable):
241
+ @track_modaic_obj
242
+ def retrieve(self, query: str):
243
+ ...
244
+ """
245
+
246
+ @wraps(func)
247
+ def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> R:
248
+ # self should be a Trackable instance
249
+ # TODO: may want to get rid of this type check for hot paths
250
+ if not isinstance(self, Trackable):
251
+ raise ValueError("@track_modaic_obj can only be used on methods of Trackable subclasses")
252
+
253
+ # get repo and project from self
254
+ repo = getattr(self, "repo", None)
255
+ project = getattr(self, "project", None)
256
+
257
+ # check if tracking is enabled globally
258
+ if not _settings.tracing:
259
+ # binds the method to self so it can be called with args and kwars, also type cast's it to callable with type vars for static type checking
260
+ bound = cast(Callable[P, R], func.__get__(self, type(self)))
261
+ return bound(*args, **kwargs)
262
+
263
+ # create tracking decorator with automatic name generation
264
+ tracker = track(
265
+ name=f"{self.__class__.__name__}.{func.__name__}", repo=repo, project=project, span_type="general"
266
+ )
267
+
268
+ # apply tracking and call method
269
+ # type casts the 'track' decorator static type checking
270
+ tracked_func = cast(MethodDecorator, tracker)(func)
271
+ # binds the method to self so it can be called with args and kwars, also type cast's it to callable with type vars for static type checking
272
+ bound_tracked = cast(Callable[P, R], tracked_func.__get__(self, type(self)))
273
+ return bound_tracked(*args, **kwargs)
274
+
275
+ return cast(Callable[Concatenate[T, P], R], wrapper)