modusa 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.
Files changed (42) hide show
  1. modusa-0.1.0/LICENSE.md +9 -0
  2. modusa-0.1.0/PKG-INFO +86 -0
  3. modusa-0.1.0/README.md +58 -0
  4. modusa-0.1.0/pyproject.toml +48 -0
  5. modusa-0.1.0/src/modusa/.DS_Store +0 -0
  6. modusa-0.1.0/src/modusa/__init__.py +1 -0
  7. modusa-0.1.0/src/modusa/config.py +18 -0
  8. modusa-0.1.0/src/modusa/decorators.py +176 -0
  9. modusa-0.1.0/src/modusa/devtools/generate_template.py +79 -0
  10. modusa-0.1.0/src/modusa/devtools/list_authors.py +2 -0
  11. modusa-0.1.0/src/modusa/devtools/list_plugins.py +60 -0
  12. modusa-0.1.0/src/modusa/devtools/main.py +42 -0
  13. modusa-0.1.0/src/modusa/devtools/templates/engines.py +28 -0
  14. modusa-0.1.0/src/modusa/devtools/templates/generators.py +26 -0
  15. modusa-0.1.0/src/modusa/devtools/templates/plugins.py +40 -0
  16. modusa-0.1.0/src/modusa/devtools/templates/signals.py +63 -0
  17. modusa-0.1.0/src/modusa/engines/__init__.py +4 -0
  18. modusa-0.1.0/src/modusa/engines/base.py +14 -0
  19. modusa-0.1.0/src/modusa/engines/plot_1dsignal.py +130 -0
  20. modusa-0.1.0/src/modusa/engines/plot_2dmatrix.py +159 -0
  21. modusa-0.1.0/src/modusa/generators/__init__.py +3 -0
  22. modusa-0.1.0/src/modusa/generators/base.py +40 -0
  23. modusa-0.1.0/src/modusa/generators/basic_waveform.py +185 -0
  24. modusa-0.1.0/src/modusa/main.py +35 -0
  25. modusa-0.1.0/src/modusa/plugins/__init__.py +7 -0
  26. modusa-0.1.0/src/modusa/plugins/base.py +100 -0
  27. modusa-0.1.0/src/modusa/plugins/plot_1dsignal.py +59 -0
  28. modusa-0.1.0/src/modusa/plugins/plot_2dmatrix.py +76 -0
  29. modusa-0.1.0/src/modusa/plugins/plot_time_domain_signal.py +59 -0
  30. modusa-0.1.0/src/modusa/signals/__init__.py +9 -0
  31. modusa-0.1.0/src/modusa/signals/audio_signal.py +230 -0
  32. modusa-0.1.0/src/modusa/signals/base.py +294 -0
  33. modusa-0.1.0/src/modusa/signals/signal1d.py +311 -0
  34. modusa-0.1.0/src/modusa/signals/signal2d.py +226 -0
  35. modusa-0.1.0/src/modusa/signals/uniform_time_domain_signal.py +212 -0
  36. modusa-0.1.0/src/modusa/utils/.DS_Store +0 -0
  37. modusa-0.1.0/src/modusa/utils/__init__.py +1 -0
  38. modusa-0.1.0/src/modusa/utils/config.py +25 -0
  39. modusa-0.1.0/src/modusa/utils/excp.py +71 -0
  40. modusa-0.1.0/src/modusa/utils/logger.py +18 -0
  41. modusa-0.1.0/tests/__init__.py +0 -0
  42. modusa-0.1.0/tests/test_signals/test_signal1d.py +52 -0
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 [Ankit Anand @meluron]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
modusa-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.1
2
+ Name: modusa
3
+ Version: 0.1.0
4
+ Summary: A modular signal analysis python library.
5
+ Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: jupyter>=1.1.1
9
+ Requires-Dist: pytest>=8.4.0
10
+ Requires-Dist: numpy>=2.2.6
11
+ Requires-Dist: librosa>=0.11.0
12
+ Requires-Dist: matplotlib>=3.10.3
13
+ Requires-Dist: pandas>=2.3.0
14
+ Requires-Dist: pydantic>=2.11.5
15
+ Requires-Dist: sqlalchemy>=2.0.41
16
+ Requires-Dist: tqdm>=4.67.1
17
+ Requires-Dist: sphinx==8.1.2
18
+ Requires-Dist: sphinx-autodoc-typehints==2.1.0
19
+ Requires-Dist: sphinx-copybutton>=0.5.2
20
+ Requires-Dist: furo>=2024.8.6
21
+ Requires-Dist: questionary>=2.1.0
22
+ Requires-Dist: rich>=14.0.0
23
+ Requires-Dist: snakeviz>=2.2.2
24
+ Requires-Dist: line-profiler>=4.2.0
25
+ Requires-Dist: nbsphinx==0.9.7
26
+ Requires-Dist: ghp-import>=2.1.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # modusa
30
+
31
+ **modusa**: **Mod**ular **U**nified **S**ignal **A**rchitecture* is a flexible, extensible Python framework for building, transforming, and analyzing different signal representations. It is a domain-agnostic core architecture for modern signal processing workflows.
32
+
33
+ ---
34
+
35
+ ## ๐Ÿ”ง Features
36
+
37
+ - โš™๏ธ **modusa Signals**
38
+ - ๐Ÿงฉ **modusa Plugins**
39
+ - ๐Ÿ“Š **modusa Genetators**
40
+ - โ™ป๏ธ **modusa Engine**
41
+
42
+ ---
43
+
44
+ ## ๐Ÿš€ Installation
45
+
46
+ > modusa is under active development. You can install the latest version via:
47
+
48
+ ```bash
49
+ git clone https://github.com/meluron/modusa.git
50
+ cd modusa
51
+ pdm install
52
+ ```
53
+
54
+ ---
55
+
56
+ ## ๐Ÿงช Tests
57
+
58
+ ```bash
59
+ pytest tests/
60
+ ```
61
+
62
+ ---
63
+
64
+ ## ๐ŸงŠ Status
65
+
66
+ modusa is in **early alpha**. Expect rapid iteration, breaking changes, and big ideas.
67
+ If you like the direction, consider โญ starring the repo and opening issues or ideas.
68
+
69
+ ---
70
+
71
+ ## ๐Ÿง  About
72
+
73
+ **modusa** is developed and maintained by [meluron](https://www.github.com/meluron),
74
+
75
+ ---
76
+
77
+ ## ๐Ÿ“œ License
78
+
79
+ MIT License. See `LICENSE` for details.
80
+
81
+ ---
82
+
83
+ ## ๐Ÿ™Œ Contributions
84
+
85
+ Pull requests, ideas, and discussions are welcome!
86
+ No matter which domain you are in, if you work with any signal, we'd love your input.
modusa-0.1.0/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # modusa
2
+
3
+ **modusa**: **Mod**ular **U**nified **S**ignal **A**rchitecture* is a flexible, extensible Python framework for building, transforming, and analyzing different signal representations. It is a domain-agnostic core architecture for modern signal processing workflows.
4
+
5
+ ---
6
+
7
+ ## ๐Ÿ”ง Features
8
+
9
+ - โš™๏ธ **modusa Signals**
10
+ - ๐Ÿงฉ **modusa Plugins**
11
+ - ๐Ÿ“Š **modusa Genetators**
12
+ - โ™ป๏ธ **modusa Engine**
13
+
14
+ ---
15
+
16
+ ## ๐Ÿš€ Installation
17
+
18
+ > modusa is under active development. You can install the latest version via:
19
+
20
+ ```bash
21
+ git clone https://github.com/meluron/modusa.git
22
+ cd modusa
23
+ pdm install
24
+ ```
25
+
26
+ ---
27
+
28
+ ## ๐Ÿงช Tests
29
+
30
+ ```bash
31
+ pytest tests/
32
+ ```
33
+
34
+ ---
35
+
36
+ ## ๐ŸงŠ Status
37
+
38
+ modusa is in **early alpha**. Expect rapid iteration, breaking changes, and big ideas.
39
+ If you like the direction, consider โญ starring the repo and opening issues or ideas.
40
+
41
+ ---
42
+
43
+ ## ๐Ÿง  About
44
+
45
+ **modusa** is developed and maintained by [meluron](https://www.github.com/meluron),
46
+
47
+ ---
48
+
49
+ ## ๐Ÿ“œ License
50
+
51
+ MIT License. See `LICENSE` for details.
52
+
53
+ ---
54
+
55
+ ## ๐Ÿ™Œ Contributions
56
+
57
+ Pull requests, ideas, and discussions are welcome!
58
+ No matter which domain you are in, if you work with any signal, we'd love your input.
@@ -0,0 +1,48 @@
1
+ [project]
2
+ name = "modusa"
3
+ version = "0.1.0"
4
+ description = "A modular signal analysis python library."
5
+ authors = [
6
+ { name = "Ankit Anand", email = "ankit0.anand0@gmail.com" },
7
+ ]
8
+ dependencies = [
9
+ "jupyter>=1.1.1",
10
+ "pytest>=8.4.0",
11
+ "numpy>=2.2.6",
12
+ "librosa>=0.11.0",
13
+ "matplotlib>=3.10.3",
14
+ "pandas>=2.3.0",
15
+ "pydantic>=2.11.5",
16
+ "sqlalchemy>=2.0.41",
17
+ "tqdm>=4.67.1",
18
+ "sphinx==8.1.2",
19
+ "sphinx-autodoc-typehints==2.1.0",
20
+ "sphinx-copybutton>=0.5.2",
21
+ "furo>=2024.8.6",
22
+ "questionary>=2.1.0",
23
+ "rich>=14.0.0",
24
+ "snakeviz>=2.2.2",
25
+ "line-profiler>=4.2.0",
26
+ "nbsphinx==0.9.7",
27
+ "ghp-import>=2.1.0",
28
+ ]
29
+ requires-python = ">=3.12"
30
+ readme = "README.md"
31
+
32
+ [project.license]
33
+ text = "MIT"
34
+
35
+ [project.scripts]
36
+ modusa-dev = "modusa.devtools.main:main"
37
+
38
+ [build-system]
39
+ requires = [
40
+ "pdm-backend",
41
+ ]
42
+ build-backend = "pdm.backend"
43
+
44
+ [tool.pdm]
45
+ distribution = true
46
+
47
+ [tool.pdm.package-dir]
48
+ "" = "src"
Binary file
@@ -0,0 +1 @@
1
+ from modusa.utils import excp, config
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+
5
+ class Config:
6
+ LOG_LEVEL = logging.WARNING
7
+ SR = 44100 # Default sampling rate
8
+ TIME_UNIT = "sec"
9
+
10
+
11
+ def __str__(self):
12
+ return self.__dict__
13
+
14
+ def __repr__(self):
15
+ return self.__dict__
16
+
17
+ # Create a singleton instance
18
+ config = Config()
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from modusa import excp
4
+ from functools import wraps
5
+ from typing import Any, Callable, Type
6
+ from inspect import signature, Parameter
7
+ from typing import get_origin, get_args, Union
8
+ import types
9
+
10
+
11
+ #----------------------------------------------------
12
+ # Safety check for plugin (apply method)
13
+ # Check if the input type, output type is allowed,
14
+ # Also logs plugin usage.
15
+ #----------------------------------------------------
16
+ def plugin_safety_check(
17
+ validate_plugin_input: bool = True,
18
+ validate_plugin_output: bool = True,
19
+ track_plugin_usage: bool = True
20
+ ):
21
+ def decorator(func: Callable) -> Callable:
22
+ @wraps(func)
23
+ def wrapper(self, signal: Any, *args, **kwargs):
24
+
25
+ if validate_plugin_input:
26
+ if not hasattr(self, 'allowed_input_signal_types'):
27
+ raise excp.AttributeNotFoundError(f"{self.__class__.__name__} must define `allowed_input_signal_types`.")
28
+
29
+ if type(signal) not in self.allowed_input_signal_types:
30
+ raise excp.PluginInputError(f"{self.__class__.__name__} must take input signal of type {self.allowed_input_signal_types} but got {type(signal)}")
31
+
32
+ if track_plugin_usage:
33
+ if not hasattr(signal, '_plugin_chain'):
34
+ raise excp.AttributeNotFoundError(f"Signal of type {type(signal).__name__} must have a `_plugin_chain` attribute for plugin tracking.")
35
+
36
+ if not isinstance(signal._plugin_chain, list):
37
+ raise excp.TypeError(f"`_plugin_chain` must be a list, but got {type(signal._plugin_chain)}")
38
+
39
+ signal._plugin_chain.append(self.__class__.__name__)
40
+
41
+ result = func(self, signal, *args, **kwargs)
42
+
43
+ if validate_plugin_output:
44
+ if not hasattr(self, 'allowed_output_signal_types'):
45
+ raise excp.AttributeNotFoundError(f"{self.__class__.__name__} must define `allowed_output_signal_types`.")
46
+ if type(result) not in self.allowed_output_signal_types:
47
+ raise excp.PluginInputError(f"{self.__class__.__name__} must return output of type {self.allowed_output_signal_types} but returned {type(result)}")
48
+ return result
49
+
50
+ return wrapper
51
+ return decorator
52
+
53
+
54
+ #----------------------------------------------------
55
+ # Safety check for generators (generate method)
56
+ # Check if the ouput type is allowed.
57
+ #----------------------------------------------------
58
+ def generator_safety_check():
59
+ """
60
+ We assume that the first argument is self, so that we can actually extract properties to
61
+ validate.
62
+ """
63
+ def decorator(func: Callable) -> Callable:
64
+ @wraps(func)
65
+ def wrapper(self, *args, **kwargs):
66
+ result = func(self, *args, **kwargs)
67
+
68
+ if not hasattr(self, 'allowed_output_signal_types'):
69
+ raise excp.AttributeNotFoundError(
70
+ f"{self.__class__.__name__} must define `allowed_output_signal_types`."
71
+ )
72
+ if type(result) not in self.allowed_output_signal_types:
73
+ raise excp.PluginInputError(
74
+ f"{self.__class__.__name__} must return output of type {self.allowed_output_signal_types}, "
75
+ f"but returned {type(result)}"
76
+ )
77
+
78
+ return result
79
+ return wrapper
80
+ return decorator
81
+
82
+
83
+ #----------------------------------------------------
84
+ # Validation for args type
85
+ # When this decorator is added to a function, it
86
+ # automatically checks all the arguments with their
87
+ # expected types. (self, forward type references are
88
+ # ignored)
89
+ #----------------------------------------------------
90
+
91
+ def validate_arg(arg_name: str, value: Any, expected_type: Any) -> None:
92
+ """
93
+ Checks if `value_type` matches `expected_type`.
94
+ Raises TypeError if not.
95
+ """
96
+ import types
97
+ from typing import get_origin, get_args, Union
98
+
99
+ origin = get_origin(expected_type)
100
+
101
+ # Handle Union (e.g. int | None)
102
+ if origin in (Union, types.UnionType):
103
+ union_args = get_args(expected_type)
104
+ for typ in union_args:
105
+ typ_origin = get_origin(typ) or typ
106
+ if type(value) is typ_origin:
107
+ return
108
+
109
+ # โŒ If none match
110
+ expected_names = ", ".join(
111
+ get_origin(t).__name__ if get_origin(t) else t.__name__ for t in union_args
112
+ )
113
+ raise excp.ValidationError(
114
+ f"Argument '{arg_name}' must be one of ({expected_names}), got {type(value).__name__}"
115
+ )
116
+
117
+ # Handle generic types like list[float], tuple[int, str]
118
+ elif origin is not None:
119
+ if type(value) is not origin:
120
+ raise excp.ValidationError(
121
+ f"Argument '{arg_name}' must be exactly of type {origin.__name__}, got {type(value).__name__}"
122
+ )
123
+ return
124
+
125
+ # โœ… Handle plain types
126
+ elif isinstance(expected_type, type):
127
+ if type(value) is not expected_type:
128
+ raise excp.ValidationError(
129
+ f"Argument '{arg_name}' must be exactly {expected_type.__name__}, got {type(value).__name__}"
130
+ )
131
+ return
132
+ # โŒ Unsupported type structure
133
+ else:
134
+ raise excp.ValidationError(f"Unsupported annotation for '{arg_name}': {expected_type}")
135
+
136
+ def validate_args_type() -> Callable:
137
+ def decorator(func: Callable) -> Callable:
138
+ @wraps(func)
139
+ def wrapper(*args, **kwargs):
140
+ sig = signature(func)
141
+ bound = sig.bind(*args, **kwargs)
142
+ bound.apply_defaults()
143
+
144
+ for arg_name, value in bound.arguments.items():
145
+ param = sig.parameters[arg_name]
146
+ expected_type = param.annotation
147
+
148
+ # Skip unannotated or special args
149
+ if expected_type is Parameter.empty or arg_name in ("self", "cls") or isinstance(expected_type, str):
150
+ continue
151
+
152
+ validate_arg(arg_name, value, expected_type) # <- this is assumed to be defined elsewhere
153
+
154
+ return func(*args, **kwargs)
155
+ return wrapper
156
+ return decorator
157
+
158
+ #-----------------------------------
159
+ # Making a property immutable
160
+ # and raising custom error message
161
+ # during attempt to modify the values
162
+ #-----------------------------------
163
+ def immutable_property(error_msg: str):
164
+ """
165
+ Returns a read-only property. Raises an error with a custom message on mutation.
166
+ """
167
+ def decorator(getter):
168
+ name = getter.__name__
169
+ private_name = f"_{name}"
170
+
171
+ def setter(self, value):
172
+ raise excp.ImmutableAttributeError(error_msg)
173
+
174
+ return property(getter, setter)
175
+
176
+ return decorator
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from datetime import date
4
+ from pathlib import Path
5
+ import questionary
6
+ import sys
7
+
8
+ ROOT_DIR = Path(__file__).parents[3].resolve()
9
+ TEMPLATES_DIR = ROOT_DIR / "src/modusa/devtools/templates"
10
+
11
+ class TemplateGenerator():
12
+ """
13
+ Generates template for `plugin`, `engine`, `signal`, `generator`.
14
+ """
15
+
16
+ @staticmethod
17
+ def ask_questions(for_what: str) -> dict:
18
+ print("----------------------")
19
+ print(for_what.upper())
20
+ print("----------------------")
21
+ module_name = questionary.text("Module name (snake_case): ").ask()
22
+ if module_name is None:
23
+ sys.exit(1)
24
+ if Path(f"src/modusa/{for_what}/{module_name}.py").exists():
25
+ print(f"โš ๏ธ File already exists, choose another name.")
26
+ sys.exit(1)
27
+
28
+ class_name = questionary.text("Class name (CamelCase): ").ask()
29
+ if class_name is None:
30
+ sys.exit(1)
31
+
32
+ author_name = questionary.text("Author name: ").ask()
33
+ if author_name is None:
34
+ sys.exit(1)
35
+
36
+ author_email = questionary.text("Author email: ").ask()
37
+ if author_email is None:
38
+ sys.exit(1)
39
+
40
+ answers = {"module_name": module_name, "class_name": class_name, "author_name": author_name, "author_email": author_email, "date_created": date.today()}
41
+
42
+ return answers
43
+
44
+ @staticmethod
45
+ def load_template_file(for_what: str) -> str:
46
+ template_path = TEMPLATES_DIR / f"{for_what}.py"
47
+ if not template_path.exists():
48
+ print(f"โŒ Template not found: {template_path}")
49
+ sys.exit(1)
50
+
51
+ template_code = template_path.read_text()
52
+
53
+ return template_code
54
+
55
+ @staticmethod
56
+ def fill_placeholders(template_code: str, placehoders_dict: dict) -> str:
57
+ template_code = template_code.format(**placehoders_dict) # Fill placeholders
58
+ return template_code
59
+
60
+ @staticmethod
61
+ def save_file(content: str, output_path: Path) -> None:
62
+ output_path.parent.mkdir(parents=True, exist_ok=True)
63
+ output_path.write_text(content)
64
+ print(f"โœ… Successfully created.\n\n open {output_path.resolve()}")
65
+
66
+ @staticmethod
67
+ def create_template(for_what: str) -> None:
68
+
69
+ # Ask basic questions to create the template for `plugin`, `generator`, ...
70
+ answers: dict = TemplateGenerator.ask_questions(for_what)
71
+
72
+ # Load the correct template file
73
+ template_code: str = TemplateGenerator.load_template_file(for_what)
74
+
75
+ # Update the dynamic values based on the answers
76
+ template_code: str = TemplateGenerator.fill_placeholders(template_code, answers)
77
+
78
+ # Save it to a file and put it in the correct folder
79
+ TemplateGenerator.save_file(content=template_code, output_path=ROOT_DIR / f"src/modusa/{for_what}/{answers['module_name']}.py")
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env python3
2
+
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import inspect
4
+ from pathlib import Path
5
+
6
+ PLUGIN_PATH = Path(__file__).parent.parent / "plugins" # This directory contains all the plugins
7
+
8
+ def find_plugin_files():
9
+ return [
10
+ path for path in PLUGIN_PATH.rglob("*.py")
11
+ if path.name not in {"__init__.py", "base.py"} # We do not want to show base plugins
12
+ ]
13
+
14
+ def load_plugin_class_from_file(file_path):
15
+ import importlib.util
16
+ from modusa.plugins.base import ModusaPlugin
17
+
18
+ spec = importlib.util.spec_from_file_location(file_path.stem, file_path)
19
+ module = importlib.util.module_from_spec(spec)
20
+ try:
21
+ spec.loader.exec_module(module)
22
+ except Exception as e:
23
+ print(f"โŒ Error loading {file_path}: {e}")
24
+ return []
25
+
26
+ plugin_classes = []
27
+ for _, obj in inspect.getmembers(module, inspect.isclass):
28
+ if issubclass(obj, ModusaPlugin) and obj is not ModusaPlugin:
29
+ plugin_classes.append(obj)
30
+
31
+ return plugin_classes
32
+
33
+ def list_plugins():
34
+ from rich.console import Console
35
+ from rich.table import Table
36
+ from modusa.plugins.base import ModusaPlugin
37
+
38
+ console = Console()
39
+ table = Table(title="๐Ÿ”Œ Available Modusa Plugins")
40
+
41
+ table.add_column("Plugin", style="bold green")
42
+ table.add_column("Module", style="dim")
43
+ table.add_column("Description", style="white")
44
+
45
+ all_plugins = []
46
+
47
+ for file_path in find_plugin_files():
48
+ plugin_classes = load_plugin_class_from_file(file_path)
49
+ for cls in plugin_classes:
50
+ name = cls.__name__
51
+ module = file_path.relative_to(PLUGIN_PATH.parent)
52
+ author = getattr(cls, "author_name", "โ€”")
53
+ email = getattr(cls, "author_email", "โ€”")
54
+ desc = getattr(cls, "description", "โ€”")
55
+ table.add_row(name, str(module), desc)
56
+ table.add_row("")
57
+ all_plugins.append(cls)
58
+
59
+ console.print(table)
60
+
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from .generate_template import TemplateGenerator
5
+ from .list_plugins import list_plugins
6
+ from . import list_authors
7
+
8
+ import argparse
9
+ import sys
10
+
11
+ def main():
12
+ try:
13
+ parser = argparse.ArgumentParser(
14
+ prog="modusa-dev",
15
+ description="Modusa CLI Tools"
16
+ )
17
+ subparsers = parser.add_subparsers(dest="group", required=True)
18
+
19
+ # --- CREATE group ---
20
+ create_parser = subparsers.add_parser("create", help="Create new Modusa components")
21
+ create_subparsers = create_parser.add_subparsers(dest="what", required=True)
22
+
23
+ create_subparsers.add_parser("engine", help="Create a new engine class").set_defaults(func=lambda:TemplateGenerator.create_template("engines"))
24
+ create_subparsers.add_parser("plugin", help="Create a new plugin class").set_defaults(func=lambda:TemplateGenerator.create_template("plugins"))
25
+ create_subparsers.add_parser("signal", help="Create a new signal class").set_defaults(func=lambda:TemplateGenerator.create_template("signals"))
26
+ create_subparsers.add_parser("generator", help="Create a new signal generator class").set_defaults(func=lambda:TemplateGenerator.create_template("generators"))
27
+
28
+ # --- LIST group ---
29
+ list_parser = subparsers.add_parser("list", help="List information about Modusa components")
30
+ list_subparsers = list_parser.add_subparsers(dest="what", required=True)
31
+
32
+ list_subparsers.add_parser("plugins", help="List available plugins").set_defaults(func=list_plugins)
33
+ list_subparsers.add_parser("authors", help="List plugin authors").set_defaults(func=list_authors)
34
+
35
+ # --- Parse and execute ---
36
+ args = parser.parse_args()
37
+ args.func()
38
+
39
+ except KeyboardInterrupt:
40
+ print("\nโŒ Aborted by user.")
41
+ sys.exit(1)
42
+
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from modusa import excp
5
+ from modusa.decorators import validate_args_type
6
+ from modusa.engines.base import ModusaEngine
7
+ from typing import Any
8
+
9
+ class {class_name}(ModusaEngine):
10
+ """
11
+
12
+ """
13
+
14
+ #--------Meta Information----------
15
+ name = ""
16
+ description = ""
17
+ author_name = "{author_name}"
18
+ author_email = "{author_email}"
19
+ created_at = "{date_created}"
20
+ #----------------------------------
21
+
22
+ def __init__(self):
23
+ super().__init__()
24
+
25
+
26
+ @validate_args_type()
27
+ def run(self) -> Any:
28
+ pass
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from modusa.decorators import validate_args_type
5
+ from modusa.generators.base import ModusaGenerator
6
+
7
+
8
+ class {class_name}(ModusaGenerator):
9
+ """
10
+
11
+ """
12
+
13
+ #--------Meta Information----------
14
+ name = ""
15
+ description = ""
16
+ author_name = "{author_name}"
17
+ author_email = "{author_email}"
18
+ created_at = "{date_created}"
19
+ #----------------------------------
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+
24
+
25
+ def generate(self) -> Any:
26
+ pass
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from modusa.plugins.base import ModusaPlugin
5
+ from modusa.decorators import immutable_property, validate_args_type, plugin_safety_check
6
+
7
+
8
+ class {class_name}(ModusaPlugin):
9
+ """
10
+
11
+ """
12
+
13
+ #--------Meta Information----------
14
+ name = ""
15
+ description = ""
16
+ author_name = "{author_name}"
17
+ author_email = "{author_email}"
18
+ created_at = "{date_created}"
19
+ #----------------------------------
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+
24
+ @immutable_property(error_msg="Mutation not allowed.")
25
+ def allowed_input_signal_types(self) -> tuple[type, ...]:
26
+ return ()
27
+
28
+
29
+ @immutable_property(error_msg="Mutation not allowed.")
30
+ def allowed_output_signal_types(self) -> tuple[type, ...]:
31
+ return ()
32
+
33
+
34
+ @plugin_safety_check()
35
+ @validate_args_type()
36
+ def apply(self, signal: "") -> "":
37
+
38
+ # Run the engine here
39
+
40
+ return