### Overview Major restructuring from monolithic `arcade-ai` package to modular library architecture with standardized uv-based dependency management.  ### New Package Structure - **`arcade-tdk`** - Lightweight toolkit development kit (core decorators, auth) - **`arcade-core`** - Core execution engine and catalog functionality - **`arcade-serve`** - FastAPI/MCP server components - **`arcade-ai`** - Meta package that includes CLI functionality. Optionally include evals via the `evals` extra. Optionally include all packages via the `all` extra. ### Key Benefits - **Lighter Dependencies**: Toolkits now depend only on `arcade-tdk` (~2 deps) vs full `arcade-ai` (~30+ deps) - **Faster Builds**: uv provides 10-100x faster dependency resolution and installation - **Better Modularity**: Clear separation of concerns, consumers import only what they need - **Standard Tooling**: Eliminates custom poetry scripts, uses standard Python packaging ### Migration Impact - All 20 toolkits converted from poetry → uv with `arcade-tdk` dependencies plus `arcade-ai[evals]` and `arcade-serve` dev dependencies. When developing locally, devs should install toolkits via `make install-local`. - Modern Python 3.10+ type hints throughout - Standardized build system with hatchling backend - Enhanced Makefile with robust toolkit management commands - Removed `arcade dev` CLI command - Reduce the number of files created by `arcade new` and add an option to not generate a tests and evals folder. This foundation enables faster development cycles and cleaner dependency chains for the growing toolkit ecosystem. ### Todo After this PR is merged - [ ] Post-merge workflow(s) (release & publish containers, etc) - [ ] Release order plan. @EricGustin suggests releasing in the following order: 1. `arcade-core` version 0.1.0 2. `arcade-serve` version 0.1.0 and `arcade-tdk` version 0.1.0 3. `arcade-ai` version 2.0.0 4. Patch release for all toolkits (all changes in toolkits are internal refactors) - [ ] [Update docs](https://github.com/ArcadeAI/docs/pull/318) --------- Co-authored-by: Eric Gustin <eric@arcade.dev> Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
142 lines
4.8 KiB
Python
142 lines
4.8 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from arcade_core.telemetry import OTELHandler, ShutdownError
|
|
from fastapi import FastAPI
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
return FastAPI()
|
|
|
|
|
|
@pytest.fixture
|
|
def handler_disabled(app):
|
|
return OTELHandler(enable=False)
|
|
|
|
|
|
@patch("arcade_core.telemetry.logging")
|
|
@patch("arcade_core.telemetry.FastAPIInstrumentor")
|
|
@patch("arcade_core.telemetry.OTLPLogExporter")
|
|
@patch("arcade_core.telemetry.OTLPMetricExporter")
|
|
@patch("arcade_core.telemetry.OTLPSpanExporter")
|
|
def test_init_with_enable_true(
|
|
mock_span_exporter,
|
|
mock_metric_exporter,
|
|
mock_log_exporter,
|
|
mock_instrumentor,
|
|
mock_logging,
|
|
app,
|
|
):
|
|
# Mock the methods that may cause network calls
|
|
mock_span_exporter.return_value.shutdown = MagicMock()
|
|
mock_metric_exporter.return_value.shutdown = MagicMock()
|
|
mock_log_exporter.return_value.shutdown = MagicMock()
|
|
|
|
# Initialize OTELHandler within the scope of the mocks
|
|
handler = OTELHandler(enable=True)
|
|
handler.instrument_app(app)
|
|
|
|
# Verify that the resource is set correctly
|
|
assert handler.resource.attributes["service.name"] == "arcade-worker"
|
|
assert "environment" in handler.resource.attributes
|
|
|
|
# Verify that initialization methods are called
|
|
assert handler._tracer_provider is not None
|
|
assert handler._tracer_span_exporter is not None
|
|
assert handler._meter_provider is not None
|
|
assert handler._meter_reader is not None
|
|
assert handler._logger_provider is not None
|
|
assert handler._log_processor is not None
|
|
|
|
# Verify that FastAPIInstrumentor is used
|
|
mock_instrumentor.return_value.instrument_app.assert_called_once_with(app)
|
|
|
|
|
|
@patch("arcade_core.telemetry.logging")
|
|
@patch("arcade_core.telemetry.FastAPIInstrumentor")
|
|
def test_init_with_enable_false(mock_instrumentor, mock_logging, app):
|
|
handler = OTELHandler(enable=False)
|
|
handler.instrument_app(app)
|
|
|
|
# Verify that resources are not initialized
|
|
assert handler._tracer_provider is None
|
|
assert handler._tracer_span_exporter is None
|
|
assert handler._meter_provider is None
|
|
assert handler._meter_reader is None
|
|
assert handler._logger_provider is None
|
|
assert handler._log_processor is None
|
|
|
|
# Verify that FastAPIInstrumentor is not called
|
|
mock_instrumentor.return_value.instrument_app.assert_not_called()
|
|
|
|
|
|
def test_init_tracer_export_exception(app):
|
|
# Simulate an exception during exporter initialization
|
|
|
|
with pytest.raises(ConnectionError) as exc_info:
|
|
handler = OTELHandler(enable=True)
|
|
handler.instrument_app(app)
|
|
|
|
assert "Could not connect to OpenTelemetry Tracer endpoint" in str(exc_info.value)
|
|
|
|
|
|
@patch("arcade_core.telemetry.OTLPLogExporter")
|
|
@patch("arcade_core.telemetry.OTLPMetricExporter")
|
|
@patch("arcade_core.telemetry.OTLPSpanExporter")
|
|
def test_shutdown(mock_span_exporter, mock_metric_exporter, mock_log_exporter, app):
|
|
# Mock the shutdown methods
|
|
mock_span_exporter.return_value.shutdown = MagicMock()
|
|
mock_metric_exporter.return_value.shutdown = MagicMock()
|
|
mock_log_exporter.return_value.shutdown = MagicMock()
|
|
|
|
handler = OTELHandler(enable=True)
|
|
handler.instrument_app(app)
|
|
|
|
# Call shutdown method
|
|
handler.shutdown()
|
|
|
|
# Verify that shutdown methods are called
|
|
mock_span_exporter.return_value.shutdown.assert_called_once()
|
|
mock_metric_exporter.return_value.shutdown.assert_called_once()
|
|
mock_log_exporter.return_value.shutdown.assert_called_once()
|
|
|
|
|
|
def test_shutdown_tracer_not_initialized(handler_disabled):
|
|
with pytest.raises(ShutdownError) as exc_info:
|
|
handler_disabled._shutdown_tracer()
|
|
assert "Tracer provider not initialized" in str(exc_info.value)
|
|
|
|
|
|
def test_shutdown_metrics_not_initialized(handler_disabled):
|
|
with pytest.raises(ShutdownError) as exc_info:
|
|
handler_disabled._shutdown_metrics()
|
|
assert "Meter provider not initialized" in str(exc_info.value)
|
|
|
|
|
|
def test_shutdown_logging_not_initialized(handler_disabled):
|
|
with pytest.raises(ShutdownError) as exc_info:
|
|
handler_disabled._shutdown_logging()
|
|
assert "Log provider not initialized" in str(exc_info.value)
|
|
|
|
|
|
@patch("arcade_core.telemetry.get_meter_provider")
|
|
@patch("arcade_core.telemetry.OTLPLogExporter")
|
|
@patch("arcade_core.telemetry.OTLPMetricExporter")
|
|
@patch("arcade_core.telemetry.OTLPSpanExporter")
|
|
def test_get_meter(
|
|
mock_span_exporter, mock_metric_exporter, mock_log_exporter, mock_get_meter_provider, app
|
|
):
|
|
# Mock the methods that may cause network calls
|
|
mock_span_exporter.return_value.shutdown = MagicMock()
|
|
mock_metric_exporter.return_value.shutdown = MagicMock()
|
|
mock_log_exporter.return_value.shutdown = MagicMock()
|
|
|
|
handler = OTELHandler(enable=True)
|
|
handler.instrument_app(app)
|
|
|
|
# Call get_meter method
|
|
handler.get_meter()
|
|
|
|
# Verify that get_meter_provider is called
|
|
mock_get_meter_provider.assert_called_once()
|