11import datetime
22import importlib
3+ import importlib .util
34from unittest .mock import MagicMock , patch
45
6+ import pytest
7+
8+ # opik and comet_mpm are OPTIONAL extras (cometx[all]); CI installs neither by
9+ # default (opik happens to be in requirements.txt, comet_mpm is not). Tests that
10+ # patch those SDKs must be skipped when the extra isn't importable, otherwise
11+ # `@patch("comet_mpm.API")` errors at setup with ModuleNotFoundError.
12+ requires_opik = pytest .mark .skipif (
13+ importlib .util .find_spec ("opik" ) is None ,
14+ reason = "opik extra not installed (cometx[all])" ,
15+ )
16+ requires_mpm = pytest .mark .skipif (
17+ importlib .util .find_spec ("comet_mpm" ) is None ,
18+ reason = "comet_mpm extra not installed (cometx[all])" ,
19+ )
20+
521
622def test_creation_event_and_window ():
723 from cometx .cli .admin_growth_report import CreationEvent , Window
@@ -300,6 +316,7 @@ def _make_opik_metrics_response(datapoints):
300316 "cometx.cli.smoke_test.get_opik_config" ,
301317 return_value = "https://example.com/opik/api/" ,
302318)
319+ @requires_opik
303320@patch ("opik.Opik" )
304321def test_collect_opik_creation_events_and_span_count_usage (mock_opik_ctor , _mock_host ):
305322 from cometx .cli .admin_growth_report import GrowthReporter
@@ -371,6 +388,7 @@ def get_project_metrics(project_id, **kwargs):
371388 "cometx.cli.smoke_test.get_opik_config" ,
372389 return_value = "https://example.com/opik/api/" ,
373390)
391+ @requires_opik
374392@patch ("opik.Opik" )
375393def test_collect_opik_respects_limit_on_workspaces (mock_opik_ctor , _mock_host ):
376394 from cometx .cli .admin_growth_report import GrowthReporter
@@ -404,6 +422,7 @@ def test_collect_opik_respects_limit_on_workspaces(mock_opik_ctor, _mock_host):
404422 "cometx.cli.smoke_test.get_opik_config" ,
405423 return_value = "https://example.com/opik/api/" ,
406424)
425+ @requires_opik
407426@patch ("opik.Opik" )
408427def test_collect_opik_skips_bad_workspace_and_continues (
409428 mock_opik_ctor , _mock_host , capsys
@@ -516,6 +535,7 @@ def _mpm_workspaces_resp(models_by_ws):
516535 }
517536
518537
538+ @requires_mpm
519539@patch ("comet_mpm.API" )
520540def test_collect_mpm_creation_scenarios_a_b_c (mock_api_ctor ):
521541 from cometx .cli .admin_growth_report import GrowthReporter
@@ -579,6 +599,7 @@ def test_collect_mpm_creation_scenarios_a_b_c(mock_api_ctor):
579599 assert modelC_metric .platform == "mpm" and modelC_metric .workspace == "ws1"
580600
581601
602+ @requires_mpm
582603@patch ("comet_mpm.API" )
583604def test_collect_mpm_prediction_volume_usage_per_model_and_per_workspace (
584605 mock_api_ctor ,
@@ -626,6 +647,7 @@ def test_collect_mpm_prediction_volume_usage_per_model_and_per_workspace(
626647 assert client .get_nb_predictions .call_count == 2
627648
628649
650+ @requires_mpm
629651@patch ("comet_mpm.API" )
630652def test_collect_mpm_respects_limit_on_workspaces (mock_api_ctor ):
631653 from cometx .cli .admin_growth_report import GrowthReporter
@@ -652,6 +674,7 @@ def test_collect_mpm_respects_limit_on_workspaces(mock_api_ctor):
652674 assert not any (e .use_case == "modelZ" for e in events )
653675
654676
677+ @requires_mpm
655678@patch ("comet_mpm.API" )
656679def test_collect_mpm_skips_bad_model_and_continues (mock_api_ctor , capsys ):
657680 from cometx .cli .admin_growth_report import GrowthReporter
@@ -686,6 +709,7 @@ def fake_get_nb_predictions(model_id, *args, **kwargs):
686709 assert not any (m .project == "modelBad" for m in usage )
687710
688711
712+ @requires_mpm
689713@patch ("comet_mpm.API" )
690714def test_collect_mpm_workspaces_endpoint_error_returns_empty (mock_api_ctor ):
691715 from cometx .cli .admin_growth_report import GrowthReporter
@@ -704,6 +728,7 @@ def test_collect_mpm_workspaces_endpoint_error_returns_empty(mock_api_ctor):
704728 assert usage == []
705729
706730
731+ @requires_mpm
707732@patch ("comet_mpm.API" )
708733def test_collect_mpm_skips_malformed_enumeration_elements (mock_api_ctor ):
709734 # The MPM inventory shape is unverifiable live; malformed workspace/model
@@ -1308,7 +1333,7 @@ def _usage_metric(platform, ws, metric, value, project=None, series=None):
13081333 )
13091334
13101335
1311- def _patch_collectors (monkeypatch , em = None , opik = None , mpm = None ):
1336+ def _patch_collectors (monkeypatch , em = None , opik = None , mpm = None , force_platforms = None ):
13121337 from cometx .cli .admin_growth_report import GrowthReporter
13131338
13141339 monkeypatch .setattr (
@@ -1320,6 +1345,14 @@ def _patch_collectors(monkeypatch, em=None, opik=None, mpm=None):
13201345 monkeypatch .setattr (
13211346 GrowthReporter , "_collect_mpm" , lambda self , ws : (mpm or ([], []))
13221347 )
1348+ # Decouple platform resolution from the environment: `_resolve_platforms`
1349+ # imports opik/comet_mpm to decide availability, but these are optional
1350+ # extras not always installed in CI. Force the set so cross-platform
1351+ # assembly tests are deterministic regardless of what's pip-installed.
1352+ if force_platforms is not None :
1353+ monkeypatch .setattr (
1354+ GrowthReporter , "_resolve_platforms" , lambda self : list (force_platforms )
1355+ )
13231356
13241357
13251358def test_build_assembles_report_data_matching_c8_contract (monkeypatch ):
@@ -1402,6 +1435,7 @@ def test_build_assembles_report_data_matching_c8_contract(monkeypatch):
14021435 em = (em_events , em_usage ),
14031436 opik = (opik_events , opik_usage ),
14041437 mpm = (mpm_events , mpm_usage ),
1438+ force_platforms = ["opik" , "em" , "mpm" ],
14051439 )
14061440
14071441 reporter = GrowthReporter (
@@ -1589,6 +1623,7 @@ def test_generate_growth_report_full_chain_all_platforms(monkeypatch, tmp_path):
15891623 em = (em_events , []),
15901624 opik = (opik_events , []),
15911625 mpm = (mpm_events , []),
1626+ force_platforms = ["opik" , "em" , "mpm" ],
15921627 )
15931628
15941629 out = tmp_path / "growth-full-chain.html"
0 commit comments