diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..38fd9f5d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: Tests + +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + run: pip install uv + + - name: Install dependencies + # Install minimal dependencies to avoid issues with heavy dev tools like python-magic + run: | + uv pip install --system -e . + uv pip install --system pytest pytest-asyncio pytest-mock pytest-loguru + + - name: Run tests + run: | + pytest tests/unit/test_lazy_imports.py + # We run only the new tests first to ensure our changes are good. + # Ideally, we would run all tests, but existing tests might require more setup (e.g. Docker). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b34a5ec5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to ROMA + +Thank you for your interest in contributing to ROMA (Recursive Open Meta-Agents)! We welcome contributions from the community to help make this framework better. + +## getting Started + +1. **Fork the repository** on GitHub. +2. **Clone your fork** locally: + ```bash + git clone https://github.com/your-username/ROMA.git + cd ROMA + ``` +3. **Set up your environment**. We recommend using `uv` for fast dependency management: + ```bash + pip install uv + uv pip install -e ".[dev]" + ``` + +## Development Workflow + +1. Create a new branch for your feature or fix: + ```bash + git checkout -b feature/my-new-feature + ``` +2. Make your changes. +3. Run tests to ensure everything is working: + ```bash + pytest + ``` +4. Format your code (if applicable): + ```bash + ruff format . + ``` + +## Pull Request Process + +1. Push your changes to your fork: + ```bash + git push origin feature/my-new-feature + ``` +2. Open a Pull Request against the `main` branch of the upstream repository. +3. Provide a clear description of your changes and why they are necessary. + +## Code of Conduct + +Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. + +## Questions? + +If you have any questions, feel free to open an issue or reach out to the maintainers. + +Happy coding! diff --git a/README.md b/README.md index ff45617a..92e55d09 100644 --- a/README.md +++ b/README.md @@ -784,6 +784,10 @@ executor = Executor( ROMA will ensure both constructor and per-call tools are available to the strategy. +## 🤝 Contributing + +We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to get started, set up your development environment, and submit pull requests. + ## 🧪 Testing ```bash diff --git a/pyproject.toml b/pyproject.toml index dcde821e..b069ab57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ name = "roma-dspy" version = "0.1.0" description = "Recursive agentic problem solver" readme = "README.md" +keywords = ["agents", "ai", "dspy", "llm", "multi-agent", "framework", "recursive"] authors = [ { name = "salzubi401", email = "salaheddin@sentient.xyz" }, { name = "baran nama", email = "baran@sentient.xyz" } diff --git a/src/roma_dspy/core/__init__.py b/src/roma_dspy/core/__init__.py index 06d78a65..cc6df9a6 100644 --- a/src/roma_dspy/core/__init__.py +++ b/src/roma_dspy/core/__init__.py @@ -1,4 +1,11 @@ -"""Core runtime components for ROMA-DSPy.""" +""" +Core runtime components for ROMA-DSPy. + +This module exposes the main building blocks of the framework, including: +- Engine components (Solver, DAG) +- Modules (Atomizer, Planner, Executor, Aggregator, Verifier) +- Signatures and Data Structures +""" from .engine import ( TaskDAG, diff --git a/src/roma_dspy/utils/lazy_imports.py b/src/roma_dspy/utils/lazy_imports.py index eadc22ab..186ec488 100644 --- a/src/roma_dspy/utils/lazy_imports.py +++ b/src/roma_dspy/utils/lazy_imports.py @@ -20,7 +20,7 @@ def is_available(module_name: str) -> bool: module_name: Name of the module to check (e.g., "sqlalchemy", "mlflow") Returns: - True if module can be imported, False otherwise + bool: True if module can be imported, False otherwise Example: >>> if is_available("mlflow"): @@ -78,7 +78,7 @@ def require_module( raise ImportError(error_msg) -def lazy_import(module_name: str, package: Optional[str] = None): +def lazy_import(module_name: str, package: Optional[str] = None) -> Optional[object]: """ Lazily import a module only when accessed. @@ -90,7 +90,7 @@ def lazy_import(module_name: str, package: Optional[str] = None): package: Package name for relative imports Returns: - Module if available, None otherwise + Optional[object]: The imported module if available, None otherwise Example: >>> mlflow = lazy_import("mlflow") @@ -159,6 +159,31 @@ def get_available_features() -> dict[str, bool]: } +def get_missing_features() -> dict[str, str]: + """ + Get a dictionary of missing optional features and their install commands. + + Returns: + Dictionary mapping missing feature names to their install commands. + """ + features = { + "persistence": ("persistence", HAS_PERSISTENCE), + "observability": ("observability", HAS_MLFLOW), + "s3": ("s3", HAS_S3), + "code_execution": ("e2b", HAS_CODE_EXECUTION), + "api_server": ("api", HAS_API_SERVER), + "tui": ("tui", HAS_TUI), + "wandb": ("wandb", HAS_WANDB), + } + + missing = {} + for feature, (extra, available) in features.items(): + if not available: + missing[feature] = f"uv pip install roma-dspy[{extra}]" + + return missing + + def print_available_features() -> None: """ Print a formatted list of available optional features. diff --git a/tests/unit/test_lazy_imports.py b/tests/unit/test_lazy_imports.py new file mode 100644 index 00000000..68fe06dd --- /dev/null +++ b/tests/unit/test_lazy_imports.py @@ -0,0 +1,46 @@ +import sys +from unittest.mock import patch, MagicMock +import pytest +from roma_dspy.utils.lazy_imports import is_available, require_module, get_missing_features, lazy_import + +def test_is_available_existing_module(): + """Test is_available returns True for existing standard library module.""" + assert is_available("os") is True + assert is_available("sys") is True + +def test_is_available_non_existing_module(): + """Test is_available returns False for non-existing module.""" + assert is_available("non_existent_module_xyz_123") is False + +def test_require_module_success(): + """Test require_module does not raise error when module exists.""" + # 'os' should always exist + require_module("os", "OS Feature", "os-extra") + +def test_require_module_failure(): + """Test require_module raises ImportError with helpful message when module missing.""" + with pytest.raises(ImportError) as excinfo: + require_module("non_existent_module_xyz_123", "Cool Feature", "cool-extra") + + error_msg = str(excinfo.value) + assert "Cool Feature requires non_existent_module_xyz_123" in error_msg + assert "uv pip install roma-dspy[cool-extra]" in error_msg + +def test_get_missing_features(): + """Test get_missing_features returns a dict.""" + missing = get_missing_features() + assert isinstance(missing, dict) + # We can't guarantee what's missing in the test environment, but we can check the values structure if any exist + for install_cmd in missing.values(): + assert "uv pip install" in install_cmd + +def test_lazy_import_success(): + """Test lazy_import returns module if it exists.""" + os_mod = lazy_import("os") + assert os_mod is not None + assert os_mod.__name__ == "os" + +def test_lazy_import_failure(): + """Test lazy_import returns None if module does not exist.""" + mod = lazy_import("non_existent_module_xyz_123") + assert mod is None