mindtrace 0.1.0__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.
- mindtrace-0.1.0/PKG-INFO +121 -0
- mindtrace-0.1.0/README.md +97 -0
- mindtrace-0.1.0/mindtrace/apps/mindtrace/apps/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/automation/mindtrace/automation/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/cluster/mindtrace/cluster/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/__init__.py +34 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/base/__init__.py +7 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/base/mindtrace_base.py +374 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/config/__init__.py +3 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/config/config.py +27 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/logging/__init__.py +3 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/logging/logger.py +112 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/observables/context_listener.py +73 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/observables/event_bus.py +72 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/observables/observable_context.py +140 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/types/task_schema.py +11 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/utils/__init__.py +12 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/utils/checks.py +39 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/utils/dynamic.py +57 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/utils/lambdas.py +35 -0
- mindtrace-0.1.0/mindtrace/core/mindtrace/core/utils/timers.py +291 -0
- mindtrace-0.1.0/mindtrace/database/mindtrace/database/__init__.py +13 -0
- mindtrace-0.1.0/mindtrace/database/mindtrace/database/backends/local_odm_backend.py +23 -0
- mindtrace-0.1.0/mindtrace/database/mindtrace/database/backends/mindtrace_odm_backend.py +27 -0
- mindtrace-0.1.0/mindtrace/database/mindtrace/database/backends/mongo_odm_backend.py +20 -0
- mindtrace-0.1.0/mindtrace/database/mindtrace/database/backends/redis_odm_backend.py +20 -0
- mindtrace-0.1.0/mindtrace/database/mindtrace/database/core/mindtrace_odm.py +24 -0
- mindtrace-0.1.0/mindtrace/datalake/mindtrace/datalake/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/hardware/mindtrace/hardware/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/jobs/mindtrace/jobs/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/models/mindtrace/models/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/__init__.py +18 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/archivers/config_archiver.py +26 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/backends/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/backends/gcp_registry_backend.py +24 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/backends/local_registry_backend.py +548 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/backends/minio_registry_backend.py +647 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/backends/registry_backend.py +233 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/core/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/core/archiver.py +33 -0
- mindtrace-0.1.0/mindtrace/registry/mindtrace/registry/core/registry.py +1032 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/__init__.py +27 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/core/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/core/connection_manager.py +140 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/core/launcher.py +72 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/core/service.py +456 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/core/types.py +104 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/core/utils.py +187 -0
- mindtrace-0.1.0/mindtrace/services/mindtrace/services/sample/echo_service.py +35 -0
- mindtrace-0.1.0/mindtrace/storage/mindtrace/storage/__init__.py +3 -0
- mindtrace-0.1.0/mindtrace/storage/mindtrace/storage/base.py +286 -0
- mindtrace-0.1.0/mindtrace/storage/mindtrace/storage/gcs.py +196 -0
- mindtrace-0.1.0/mindtrace/ui/mindtrace/ui/__init__.py +0 -0
- mindtrace-0.1.0/mindtrace.egg-info/PKG-INFO +121 -0
- mindtrace-0.1.0/mindtrace.egg-info/SOURCES.txt +58 -0
- mindtrace-0.1.0/mindtrace.egg-info/dependency_links.txt +1 -0
- mindtrace-0.1.0/mindtrace.egg-info/requires.txt +13 -0
- mindtrace-0.1.0/mindtrace.egg-info/top_level.txt +1 -0
- mindtrace-0.1.0/pyproject.toml +178 -0
- mindtrace-0.1.0/setup.cfg +4 -0
mindtrace-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mindtrace
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Mindtrace monorepo with modular packages
|
|
5
|
+
Author: Mindtrace Team
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: mindtrace-core>=0.1.0
|
|
12
|
+
Requires-Dist: mindtrace-jobs>=0.1.0
|
|
13
|
+
Requires-Dist: mindtrace-registry>=0.1.0
|
|
14
|
+
Requires-Dist: mindtrace-database>=0.1.0
|
|
15
|
+
Requires-Dist: mindtrace-services>=0.1.0
|
|
16
|
+
Requires-Dist: mindtrace-hardware>=0.1.0
|
|
17
|
+
Requires-Dist: mindtrace-cluster>=0.1.0
|
|
18
|
+
Requires-Dist: mindtrace-models>=0.1.0
|
|
19
|
+
Requires-Dist: mindtrace-automation>=0.1.0
|
|
20
|
+
Requires-Dist: mindtrace-apps>=0.1.0
|
|
21
|
+
Requires-Dist: mindtrace-ui>=0.1.0
|
|
22
|
+
Requires-Dist: mindtrace-datalake>=0.1.0
|
|
23
|
+
Requires-Dist: mindtrace-storage>=0.1.0
|
|
24
|
+
|
|
25
|
+
# Mindtrace Module Dependency Structure
|
|
26
|
+
|
|
27
|
+
Mindtrace is organized into a layered workspace to support ML components as Python modules with clearly defined boundaries and dependencies.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 📐 Layered Architecture
|
|
32
|
+
|
|
33
|
+
We use a level-based system for organizing modules based on dependency direction and build order.
|
|
34
|
+
|
|
35
|
+
### **Level 1: Core**
|
|
36
|
+
- `core`: Foundational utilities and base classes used across all other modules.
|
|
37
|
+
|
|
38
|
+
### **Level 2: Core Consumers**
|
|
39
|
+
- `jobs`: Job execution and backend interfaces.
|
|
40
|
+
- `registry`: Artifact and metadata management.
|
|
41
|
+
- `database`: Redis, Mongo, and DB access layers.
|
|
42
|
+
- `services`: Service base classes, authentication, and gateways.
|
|
43
|
+
- `ui`: Optional UI libraries and components.
|
|
44
|
+
|
|
45
|
+
### **Level 3: Infrastructure Modules**
|
|
46
|
+
- `hardware`: Interfaces for cameras, PLCs, scanners, etc.
|
|
47
|
+
- `cluster`: Runtime cluster management, nodes, and workers.
|
|
48
|
+
- `datalake`: Dataset interfaces for HuggingFace and Mindtrace datasets.
|
|
49
|
+
- `models`: Core model definitions and leaderboard utilities.
|
|
50
|
+
|
|
51
|
+
### **Level 4: Automation**
|
|
52
|
+
- `automation`: Integration of pipelines and orchestration using level 2–3 modules.
|
|
53
|
+
|
|
54
|
+
### **Level 5: Applications**
|
|
55
|
+
- `apps`: End-user applications composed of all previous levels.
|
|
56
|
+
- E.g., Demo pipelines
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🔄 Dependency Flow
|
|
61
|
+
|
|
62
|
+
Each layer only depends on modules in lower levels.
|
|
63
|
+
|
|
64
|
+
| Module | Depends On |
|
|
65
|
+
|------------|------------------------------------------------------|
|
|
66
|
+
| `core` | – |
|
|
67
|
+
| `jobs` | `core`, `services` |
|
|
68
|
+
| `registry` | `core` |
|
|
69
|
+
| `database` | `core` |
|
|
70
|
+
| `services` | `core` |
|
|
71
|
+
| `ui` | `core` |
|
|
72
|
+
| `cluster` | `jobs`, `registry`, `database`, `services` |
|
|
73
|
+
| `datalake` | `registry`, `database`, `services` |
|
|
74
|
+
| `models` | `registry`, `services` |
|
|
75
|
+
| `automation` | `jobs`, `registry`, `database`, `services`, `datalake`, `models`, `cluster` |
|
|
76
|
+
| `apps` | Everything |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## 🛠️ Build
|
|
82
|
+
|
|
83
|
+
Building wheels and source distributions, from the root of the repo:
|
|
84
|
+
```bash
|
|
85
|
+
uv build --all-packages
|
|
86
|
+
ls dist/
|
|
87
|
+
```
|
|
88
|
+
For building only wheels:
|
|
89
|
+
```bash
|
|
90
|
+
uv build --all-packages --wheel
|
|
91
|
+
ls dist/
|
|
92
|
+
```
|
|
93
|
+
They may then be installed in a new venv (the entire `mindtrace` package or any submodule `mindtrace-core`) via:
|
|
94
|
+
```bash
|
|
95
|
+
uv pip install mindtrace --find-links /path/to/dist
|
|
96
|
+
# or
|
|
97
|
+
uv pip install /path/to/dist/mindtrace.whl
|
|
98
|
+
```
|
|
99
|
+
Note: You may need to use `uv pip install --force-reinstall` in case you encounter `ModuleNotFoundError`.
|
|
100
|
+
Checking the installation:
|
|
101
|
+
```bash
|
|
102
|
+
uv run python -c "from mindtrace.core import Mindtrace; print('OK')"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## 🛠️ Usage Examples
|
|
107
|
+
|
|
108
|
+
Installing the full Mindtrace package:
|
|
109
|
+
```bash
|
|
110
|
+
uv add mindtrace
|
|
111
|
+
```
|
|
112
|
+
Installing a minimal dependency chain (e.g., for Datalake development):
|
|
113
|
+
```bash
|
|
114
|
+
uv add mindtrace-datalake
|
|
115
|
+
```
|
|
116
|
+
Python Imports
|
|
117
|
+
```python
|
|
118
|
+
from mindtrace import core, registry, database, services
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Mindtrace Module Dependency Structure
|
|
2
|
+
|
|
3
|
+
Mindtrace is organized into a layered workspace to support ML components as Python modules with clearly defined boundaries and dependencies.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📐 Layered Architecture
|
|
8
|
+
|
|
9
|
+
We use a level-based system for organizing modules based on dependency direction and build order.
|
|
10
|
+
|
|
11
|
+
### **Level 1: Core**
|
|
12
|
+
- `core`: Foundational utilities and base classes used across all other modules.
|
|
13
|
+
|
|
14
|
+
### **Level 2: Core Consumers**
|
|
15
|
+
- `jobs`: Job execution and backend interfaces.
|
|
16
|
+
- `registry`: Artifact and metadata management.
|
|
17
|
+
- `database`: Redis, Mongo, and DB access layers.
|
|
18
|
+
- `services`: Service base classes, authentication, and gateways.
|
|
19
|
+
- `ui`: Optional UI libraries and components.
|
|
20
|
+
|
|
21
|
+
### **Level 3: Infrastructure Modules**
|
|
22
|
+
- `hardware`: Interfaces for cameras, PLCs, scanners, etc.
|
|
23
|
+
- `cluster`: Runtime cluster management, nodes, and workers.
|
|
24
|
+
- `datalake`: Dataset interfaces for HuggingFace and Mindtrace datasets.
|
|
25
|
+
- `models`: Core model definitions and leaderboard utilities.
|
|
26
|
+
|
|
27
|
+
### **Level 4: Automation**
|
|
28
|
+
- `automation`: Integration of pipelines and orchestration using level 2–3 modules.
|
|
29
|
+
|
|
30
|
+
### **Level 5: Applications**
|
|
31
|
+
- `apps`: End-user applications composed of all previous levels.
|
|
32
|
+
- E.g., Demo pipelines
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🔄 Dependency Flow
|
|
37
|
+
|
|
38
|
+
Each layer only depends on modules in lower levels.
|
|
39
|
+
|
|
40
|
+
| Module | Depends On |
|
|
41
|
+
|------------|------------------------------------------------------|
|
|
42
|
+
| `core` | – |
|
|
43
|
+
| `jobs` | `core`, `services` |
|
|
44
|
+
| `registry` | `core` |
|
|
45
|
+
| `database` | `core` |
|
|
46
|
+
| `services` | `core` |
|
|
47
|
+
| `ui` | `core` |
|
|
48
|
+
| `cluster` | `jobs`, `registry`, `database`, `services` |
|
|
49
|
+
| `datalake` | `registry`, `database`, `services` |
|
|
50
|
+
| `models` | `registry`, `services` |
|
|
51
|
+
| `automation` | `jobs`, `registry`, `database`, `services`, `datalake`, `models`, `cluster` |
|
|
52
|
+
| `apps` | Everything |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## 🛠️ Build
|
|
58
|
+
|
|
59
|
+
Building wheels and source distributions, from the root of the repo:
|
|
60
|
+
```bash
|
|
61
|
+
uv build --all-packages
|
|
62
|
+
ls dist/
|
|
63
|
+
```
|
|
64
|
+
For building only wheels:
|
|
65
|
+
```bash
|
|
66
|
+
uv build --all-packages --wheel
|
|
67
|
+
ls dist/
|
|
68
|
+
```
|
|
69
|
+
They may then be installed in a new venv (the entire `mindtrace` package or any submodule `mindtrace-core`) via:
|
|
70
|
+
```bash
|
|
71
|
+
uv pip install mindtrace --find-links /path/to/dist
|
|
72
|
+
# or
|
|
73
|
+
uv pip install /path/to/dist/mindtrace.whl
|
|
74
|
+
```
|
|
75
|
+
Note: You may need to use `uv pip install --force-reinstall` in case you encounter `ModuleNotFoundError`.
|
|
76
|
+
Checking the installation:
|
|
77
|
+
```bash
|
|
78
|
+
uv run python -c "from mindtrace.core import Mindtrace; print('OK')"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## 🛠️ Usage Examples
|
|
83
|
+
|
|
84
|
+
Installing the full Mindtrace package:
|
|
85
|
+
```bash
|
|
86
|
+
uv add mindtrace
|
|
87
|
+
```
|
|
88
|
+
Installing a minimal dependency chain (e.g., for Datalake development):
|
|
89
|
+
```bash
|
|
90
|
+
uv add mindtrace-datalake
|
|
91
|
+
```
|
|
92
|
+
Python Imports
|
|
93
|
+
```python
|
|
94
|
+
from mindtrace import core, registry, database, services
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from mindtrace.core.base import Mindtrace, MindtraceABC, MindtraceMeta
|
|
2
|
+
from mindtrace.core.config import Config
|
|
3
|
+
from mindtrace.core.logging.logger import setup_logger
|
|
4
|
+
from mindtrace.core.observables.context_listener import ContextListener
|
|
5
|
+
from mindtrace.core.observables.event_bus import EventBus
|
|
6
|
+
from mindtrace.core.observables.observable_context import ObservableContext
|
|
7
|
+
from mindtrace.core.types.task_schema import TaskSchema
|
|
8
|
+
from mindtrace.core.utils.checks import check_libs, first_not_none, ifnone, ifnone_url
|
|
9
|
+
from mindtrace.core.utils.dynamic import get_class, instantiate_target
|
|
10
|
+
from mindtrace.core.utils.lambdas import named_lambda
|
|
11
|
+
from mindtrace.core.utils.timers import Timeout, Timer, TimerCollection
|
|
12
|
+
|
|
13
|
+
setup_logger() # Initialize the default logger
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"check_libs",
|
|
17
|
+
"ContextListener",
|
|
18
|
+
"Config",
|
|
19
|
+
"EventBus",
|
|
20
|
+
"first_not_none",
|
|
21
|
+
"get_class",
|
|
22
|
+
"ifnone",
|
|
23
|
+
"ifnone_url",
|
|
24
|
+
"instantiate_target",
|
|
25
|
+
"Mindtrace",
|
|
26
|
+
"MindtraceABC",
|
|
27
|
+
"MindtraceMeta",
|
|
28
|
+
"named_lambda",
|
|
29
|
+
"ObservableContext",
|
|
30
|
+
"TaskSchema",
|
|
31
|
+
"Timer",
|
|
32
|
+
"TimerCollection",
|
|
33
|
+
"Timeout",
|
|
34
|
+
]
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"""Mindtrace class. Provides unified configuration, logging and context management."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
import traceback
|
|
6
|
+
from abc import ABC, ABCMeta
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import Callable, Optional
|
|
9
|
+
|
|
10
|
+
from mindtrace.core.config import Config
|
|
11
|
+
from mindtrace.core.logging.logger import get_logger
|
|
12
|
+
from mindtrace.core.utils import ifnone
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MindtraceMeta(type):
|
|
16
|
+
"""Metaclass for Mindtrace class.
|
|
17
|
+
|
|
18
|
+
The MindtraceMeta metaclass enables classes deriving from Mindtrace to automatically use the same default logger within
|
|
19
|
+
class methods as it does within instance methods. I.e. consider the following class:
|
|
20
|
+
|
|
21
|
+
Example, logging in both class methods and instance methods::
|
|
22
|
+
|
|
23
|
+
from mindtrace.core import Mindtrace
|
|
24
|
+
|
|
25
|
+
class MyClass(Mindtrace):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
super().__init__()
|
|
28
|
+
|
|
29
|
+
def instance_method(self):
|
|
30
|
+
self.logger.info(f"Using logger: {self.logger.name}") # Using logger: mindtrace.my_module.MyClass
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def class_method(cls):
|
|
34
|
+
cls.logger.info(f"Using logger: {cls.logger.name}") # Using logger: mindtrace.my_module.MyClass
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(cls, name, bases, attr_dict):
|
|
38
|
+
super().__init__(name, bases, attr_dict)
|
|
39
|
+
cls._logger = None
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def logger(cls):
|
|
43
|
+
if cls._logger is None:
|
|
44
|
+
cls._logger = get_logger(cls.unique_name)
|
|
45
|
+
return cls._logger
|
|
46
|
+
|
|
47
|
+
@logger.setter
|
|
48
|
+
def logger(cls, new_logger):
|
|
49
|
+
cls._logger = new_logger
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def unique_name(self) -> str:
|
|
53
|
+
return self.__module__ + "." + self.__name__
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Mindtrace(metaclass=MindtraceMeta):
|
|
57
|
+
"""Base class for all Mindtrace package core classes.
|
|
58
|
+
|
|
59
|
+
The Mindtrace class adds default context manager and logging methods. All classes that derive from Mindtrace can be
|
|
60
|
+
used as context managers and will use a unified logging format.
|
|
61
|
+
|
|
62
|
+
The class automatically provides logging capabilities for both class methods and instance methods.
|
|
63
|
+
For example:
|
|
64
|
+
|
|
65
|
+
.. code-block:: python
|
|
66
|
+
|
|
67
|
+
from mindtrace.core import Mindtrace
|
|
68
|
+
|
|
69
|
+
class MyClass(Mindtrace):
|
|
70
|
+
def __init__(self):
|
|
71
|
+
super().__init__()
|
|
72
|
+
|
|
73
|
+
def instance_method(self):
|
|
74
|
+
self.logger.info(f"Using logger: {self.logger.name}") # Using logger: mindtrace.my_module.MyClass
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def class_method(cls):
|
|
78
|
+
cls.logger.info(f"Using logger: {cls.logger.name}") # Using logger: mindtrace.my_module.MyClass
|
|
79
|
+
|
|
80
|
+
The logging functionality is automatically provided through the MindtraceMeta metaclass,
|
|
81
|
+
which ensures consistent logging behavior across all method types.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
config = Config()
|
|
85
|
+
|
|
86
|
+
def __init__(self, suppress: bool = False, **kwargs):
|
|
87
|
+
"""
|
|
88
|
+
Initialize the Mindtrace object.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
suppress (bool): Whether to suppress exceptions in context manager use.
|
|
92
|
+
**kwargs: Additional keyword arguments. Logger-related kwargs are passed to `get_logger`.
|
|
93
|
+
Valid logger kwargs: log_dir, logger_level, stream_level, file_level,
|
|
94
|
+
file_mode, propagate, max_bytes, backup_count
|
|
95
|
+
"""
|
|
96
|
+
# Initialize parent classes first (cooperative inheritance)
|
|
97
|
+
try:
|
|
98
|
+
super().__init__(**kwargs)
|
|
99
|
+
except TypeError:
|
|
100
|
+
# If parent classes don't accept some kwargs, try without logger-specific ones
|
|
101
|
+
logger_param_names = {
|
|
102
|
+
"log_dir",
|
|
103
|
+
"logger_level",
|
|
104
|
+
"stream_level",
|
|
105
|
+
"file_level",
|
|
106
|
+
"file_mode",
|
|
107
|
+
"propagate",
|
|
108
|
+
"max_bytes",
|
|
109
|
+
"backup_count",
|
|
110
|
+
}
|
|
111
|
+
remaining_kwargs = {k: v for k, v in kwargs.items() if k not in logger_param_names}
|
|
112
|
+
try:
|
|
113
|
+
super().__init__(**remaining_kwargs)
|
|
114
|
+
except TypeError:
|
|
115
|
+
# If that still fails, try with no kwargs
|
|
116
|
+
super().__init__()
|
|
117
|
+
|
|
118
|
+
# Set Mindtrace-specific attributes
|
|
119
|
+
self.suppress = suppress
|
|
120
|
+
|
|
121
|
+
# Filter logger-specific kwargs for logger setup
|
|
122
|
+
logger_param_names = {
|
|
123
|
+
"log_dir",
|
|
124
|
+
"logger_level",
|
|
125
|
+
"stream_level",
|
|
126
|
+
"file_level",
|
|
127
|
+
"file_mode",
|
|
128
|
+
"propagate",
|
|
129
|
+
"max_bytes",
|
|
130
|
+
"backup_count",
|
|
131
|
+
}
|
|
132
|
+
logger_kwargs = {k: v for k, v in kwargs.items() if k in logger_param_names}
|
|
133
|
+
|
|
134
|
+
# Set up the logger
|
|
135
|
+
self.logger = get_logger(self.unique_name, **logger_kwargs)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def unique_name(self) -> str:
|
|
139
|
+
return self.__module__ + "." + type(self).__name__
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def name(self) -> str:
|
|
143
|
+
return type(self).__name__
|
|
144
|
+
|
|
145
|
+
def __enter__(self):
|
|
146
|
+
self.logger.debug(f"Initializing {self.name} as a context manager.")
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
150
|
+
self.logger.debug(f"Exiting context manager for {self.name}.")
|
|
151
|
+
if exc_type is not None:
|
|
152
|
+
info = (exc_type, exc_val, exc_tb)
|
|
153
|
+
self.logger.exception("Exception occurred", exc_info=info)
|
|
154
|
+
return self.suppress
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def autolog(
|
|
159
|
+
cls,
|
|
160
|
+
log_level=logging.DEBUG,
|
|
161
|
+
prefix_formatter: Optional[Callable] = None,
|
|
162
|
+
suffix_formatter: Optional[Callable] = None,
|
|
163
|
+
exception_formatter: Optional[Callable] = None,
|
|
164
|
+
self: Optional["Mindtrace"] = None,
|
|
165
|
+
):
|
|
166
|
+
"""Decorator that adds logger.log calls to the decorated method before and after the method is called.
|
|
167
|
+
|
|
168
|
+
By default, the autolog decorator will log the method name, arguments and keyword arguments before the method
|
|
169
|
+
is called, and the method name and result after the method completes. This behavior can be modified by passing
|
|
170
|
+
in prefix and suffix formatters.
|
|
171
|
+
|
|
172
|
+
The autolog decorator will also catch and log all Exceptions, re-raising any exception after logging it. The
|
|
173
|
+
behavior for autologging exceptions can be modified by passing in an exception_formatter.
|
|
174
|
+
|
|
175
|
+
The autolog decorator expects a logger to exist at self.logger, and hence can only be used by Mindtrace
|
|
176
|
+
subclasses or classes that have a logger attribute.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
log_level: The log_level passed to logger.log().
|
|
180
|
+
prefix_formatter: The formatter used to log the command before the wrapped method runs. The prefix_formatter
|
|
181
|
+
will be given (and must accept) three arguments, in the following order:
|
|
182
|
+
- function: The function being wrapped.
|
|
183
|
+
- args: The args passed into the function.
|
|
184
|
+
- kwargs: The kwargs passed into the function.
|
|
185
|
+
suffix_formatter: The formatter used to log the command after the wrapped method runs. The suffix_formatter
|
|
186
|
+
will be given (and must accept) two arguments, in the following order:
|
|
187
|
+
- function: The function being wrapped.
|
|
188
|
+
- result: The result returned from the wrapped method.
|
|
189
|
+
exception_formatter: The formatter used to log any errors. The exception_formatter will be given (and must
|
|
190
|
+
accept) three arguments, in the following order:
|
|
191
|
+
- function: The function being wrapped.
|
|
192
|
+
- error: The caught Exception.
|
|
193
|
+
- stack trace: The stack trace, as provided by traceback.format_exc().
|
|
194
|
+
self: The instance of the class that the method is being called on. Self only needs to be passed in if the
|
|
195
|
+
wrapped method does not have self as the first argument. Refer to the example below for more details.
|
|
196
|
+
|
|
197
|
+
Example::
|
|
198
|
+
|
|
199
|
+
from mindtrace.core import Mindtrace
|
|
200
|
+
|
|
201
|
+
class MyClass(Mindtrace):
|
|
202
|
+
def __init__(self):
|
|
203
|
+
super().__init__()
|
|
204
|
+
|
|
205
|
+
@Mindtrace.autolog()
|
|
206
|
+
def divide(self, arg1, arg2):
|
|
207
|
+
self.logger.info("We are about to divide")
|
|
208
|
+
result = arg1 / arg2
|
|
209
|
+
self.logger.info("We have divided")
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
my_instance = MyClass()
|
|
213
|
+
my_instance.divide(1, 2)
|
|
214
|
+
my_instance.divide(1, 0)
|
|
215
|
+
|
|
216
|
+
The resulting log file should contain something similar to the following:
|
|
217
|
+
|
|
218
|
+
.. code-block:: text
|
|
219
|
+
|
|
220
|
+
MyClass - DEBUG - Calling divide with args: (1, 2) and kwargs: {}
|
|
221
|
+
MyClass - INFO - We are about to divide
|
|
222
|
+
MyClass - INFO - We have divided
|
|
223
|
+
MyClass - DEBUG - Finished divide with result: 0.5
|
|
224
|
+
MyClass - DEBUG - Calling divide with args: (1, 0) and kwargs: {}
|
|
225
|
+
MyClass - INFO - We are about to divide
|
|
226
|
+
MyClass - ERROR - division by zero
|
|
227
|
+
Traceback (most recent call last):
|
|
228
|
+
...
|
|
229
|
+
|
|
230
|
+
If the wrapped method does not have self as the first argument, self must be passed in as an argument to the
|
|
231
|
+
autolog decorator.
|
|
232
|
+
|
|
233
|
+
.. code-block:: python
|
|
234
|
+
|
|
235
|
+
from fastapi import FastAPI
|
|
236
|
+
from mindtrace.core import Mindtrace
|
|
237
|
+
|
|
238
|
+
class MyClass(Mindtrace):
|
|
239
|
+
def __init__():
|
|
240
|
+
super().__init__()
|
|
241
|
+
|
|
242
|
+
def create_app(self):
|
|
243
|
+
app_ = FastAPI()
|
|
244
|
+
|
|
245
|
+
@Mindtrace.autolog(self=self) # self must be passed in as an argument as it is not captured in status()
|
|
246
|
+
@app_.post("/status")
|
|
247
|
+
def status():
|
|
248
|
+
return {"status": "Available"}
|
|
249
|
+
|
|
250
|
+
return app_
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
prefix_formatter = ifnone(
|
|
254
|
+
prefix_formatter,
|
|
255
|
+
default=lambda function,
|
|
256
|
+
args,
|
|
257
|
+
kwargs: f"Calling {function.__name__} with args: {args} and kwargs: {kwargs}",
|
|
258
|
+
)
|
|
259
|
+
suffix_formatter = ifnone(
|
|
260
|
+
suffix_formatter, default=lambda function, result: f"Finished {function.__name__} with result: {result}"
|
|
261
|
+
)
|
|
262
|
+
exception_formatter = ifnone(
|
|
263
|
+
exception_formatter,
|
|
264
|
+
default=lambda function,
|
|
265
|
+
e,
|
|
266
|
+
stack_trace: f"{function.__name__} failed to complete with the following error: {e}\n{stack_trace}",
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def decorator(function):
|
|
270
|
+
is_async = inspect.iscoroutinefunction(function)
|
|
271
|
+
|
|
272
|
+
if self is None:
|
|
273
|
+
if is_async:
|
|
274
|
+
|
|
275
|
+
@wraps(function)
|
|
276
|
+
async def wrapper(self, *args, **kwargs):
|
|
277
|
+
self.logger.log(log_level, prefix_formatter(function, args, kwargs))
|
|
278
|
+
try:
|
|
279
|
+
result = await function(self, *args, **kwargs)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
self.logger.error(exception_formatter(function, e, traceback.format_exc()))
|
|
282
|
+
raise
|
|
283
|
+
else:
|
|
284
|
+
self.logger.log(log_level, suffix_formatter(function, result))
|
|
285
|
+
return result
|
|
286
|
+
else:
|
|
287
|
+
|
|
288
|
+
@wraps(function)
|
|
289
|
+
def wrapper(self, *args, **kwargs):
|
|
290
|
+
self.logger.log(log_level, prefix_formatter(function, args, kwargs))
|
|
291
|
+
try:
|
|
292
|
+
result = function(self, *args, **kwargs)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
self.logger.error(exception_formatter(function, e, traceback.format_exc()))
|
|
295
|
+
raise
|
|
296
|
+
else:
|
|
297
|
+
self.logger.log(log_level, suffix_formatter(function, result))
|
|
298
|
+
return result
|
|
299
|
+
|
|
300
|
+
else:
|
|
301
|
+
if is_async:
|
|
302
|
+
|
|
303
|
+
@wraps(function)
|
|
304
|
+
async def wrapper(*args, **kwargs):
|
|
305
|
+
self.logger.log(log_level, prefix_formatter(function, args, kwargs))
|
|
306
|
+
try:
|
|
307
|
+
result = await function(*args, **kwargs)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
self.logger.error(exception_formatter(function, e, traceback.format_exc()))
|
|
310
|
+
raise
|
|
311
|
+
else:
|
|
312
|
+
self.logger.log(log_level, suffix_formatter(function, result))
|
|
313
|
+
return result
|
|
314
|
+
else:
|
|
315
|
+
|
|
316
|
+
@wraps(function)
|
|
317
|
+
def wrapper(*args, **kwargs):
|
|
318
|
+
self.logger.log(log_level, prefix_formatter(function, args, kwargs))
|
|
319
|
+
try:
|
|
320
|
+
result = function(*args, **kwargs)
|
|
321
|
+
except Exception as e:
|
|
322
|
+
self.logger.error(exception_formatter(function, e, traceback.format_exc()))
|
|
323
|
+
raise
|
|
324
|
+
else:
|
|
325
|
+
self.logger.log(log_level, suffix_formatter(function, result))
|
|
326
|
+
return result
|
|
327
|
+
|
|
328
|
+
return wrapper
|
|
329
|
+
|
|
330
|
+
return decorator
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class MindtraceABCMeta(MindtraceMeta, ABCMeta):
|
|
334
|
+
"""Metaclass that combines MindtraceMeta and ABC metaclasses.
|
|
335
|
+
|
|
336
|
+
This metaclass resolves metaclass conflicts when creating classes that need to be both
|
|
337
|
+
abstract (using ABC) and have MindtraceMeta functionality. Python only allows a class to
|
|
338
|
+
have one metaclass, so this combined metaclass allows classes to inherit from both
|
|
339
|
+
Mindtrace class and ABC simultaneously.
|
|
340
|
+
|
|
341
|
+
Without this combined metaclass, trying to create a class that inherits from both Mindtrace class
|
|
342
|
+
and ABC would raise a metaclass conflict error since they each have different metaclasses.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class MindtraceABC(Mindtrace, ABC, metaclass=MindtraceABCMeta):
|
|
349
|
+
"""Abstract base class combining Mindtrace class functionality with ABC support.
|
|
350
|
+
|
|
351
|
+
This class enables creating abstract classes that also have access to all Mindtrace features
|
|
352
|
+
such as logging, configuration, and context management. Use this class instead of
|
|
353
|
+
Mindtrace when you need to define abstract methods or properties in your class.
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
from mindtrace.core import MindtraceABC
|
|
357
|
+
from abc import abstractmethod
|
|
358
|
+
|
|
359
|
+
class MyAbstractService(MindtraceABC):
|
|
360
|
+
def __init__(self):
|
|
361
|
+
super().__init__()
|
|
362
|
+
|
|
363
|
+
@abstractmethod
|
|
364
|
+
def process_data(self, data):
|
|
365
|
+
'''Must be implemented by concrete subclasses.'''
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
Note:
|
|
369
|
+
Without this class, attempting to create a class that inherits from both Mindtrace class and ABC
|
|
370
|
+
would fail due to metaclass conflicts. MindtraceABC resolves this by using the CombinedABCMeta.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
def __init__(self, *args, **kwargs):
|
|
374
|
+
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Config(dict):
|
|
5
|
+
"""Template Config class."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, **kwargs):
|
|
8
|
+
default_config = {
|
|
9
|
+
"MINDTRACE_TEMP_DIR": "~/.cache/mindtrace/temp",
|
|
10
|
+
"MINDTRACE_DEFAULT_REGISTRY_DIR": "~/.cache/mindtrace/registry",
|
|
11
|
+
"MINDTRACE_DEFAULT_HOST_URLS": {
|
|
12
|
+
"Service": "http://localhost:8000",
|
|
13
|
+
},
|
|
14
|
+
"MINDTRACE_MINIO_REGISTRY_URI": "~/.cache/mindtrace/minio-registry",
|
|
15
|
+
"MINDTRACE_MINIO_ENDPOINT": "localhost:9000",
|
|
16
|
+
"MINDTRACE_MINIO_ACCESS_KEY": "minioadmin",
|
|
17
|
+
"MINDTRACE_MINIO_SECRET_KEY": "minioadmin",
|
|
18
|
+
"MINDTRACE_SERVER_PIDS_DIR_PATH": "~/.cache/mindtrace/pids",
|
|
19
|
+
"MINDTRACE_LOGGER_DIR": "~/.cache/mindtrace/logs",
|
|
20
|
+
}
|
|
21
|
+
# Update defaults with any provided kwargs
|
|
22
|
+
default_config.update(kwargs)
|
|
23
|
+
# Expand ~ only if it is at the start of the string
|
|
24
|
+
for k, v in default_config.items():
|
|
25
|
+
if isinstance(v, str) and v.startswith("~/"):
|
|
26
|
+
default_config[k] = os.path.expanduser(v)
|
|
27
|
+
super().__init__(default_config)
|