1010import os
1111import re
1212from collections import deque
13+ from collections .abc import Iterable
1314from os .path import basename
1415from pathlib import Path
1516from typing import (
6566 import unicodedata as _module_unicodedata
6667
6768 from datadog_checks .base .utils .diagnose import Diagnosis
69+ from datadog_checks .base .utils .discovery import Service
6870 from datadog_checks .base .utils .http import RequestsWrapper
6971 from datadog_checks .base .utils .metadata import MetadataManager
7072
@@ -179,6 +181,33 @@ def __init_subclass__(cls, *args, **kwargs):
179181 except Exception :
180182 return cls
181183
184+ @classmethod
185+ def generate_configs (cls , service : Service ) -> Iterable [dict [str , Any ]]:
186+ """
187+ Yield candidate full configurations for service discovery.
188+
189+ Integrations can opt into config discovery by declaring a discovery
190+ stanza in their spec and generating config_models.discovery.
191+ """
192+ from datadog_checks .base .utils .discovery .probe import generated_discovery_candidates
193+
194+ return generated_discovery_candidates (cls , service )
195+
196+ @classmethod
197+ def discover_config (cls , service_json : str ) -> str :
198+ """
199+ Return discovered configurations for an AD service payload.
200+
201+ The Agent calls this classmethod through rtloader. Candidate configs
202+ are generated by ``generate_configs`` and accepted only when the real
203+ check can run against their metric instances successfully. Returns the
204+ first accepted candidate only (first-match-wins); remaining candidates
205+ are not evaluated.
206+ """
207+ from datadog_checks .base .utils .discovery .probe import run_discovery
208+
209+ return run_discovery (cls , service_json )
210+
182211 def __init__ (self , * args , ** kwargs ):
183212 # type: (*Any, **Any) -> None
184213 """
@@ -524,9 +553,8 @@ def check_version(self):
524553 Return the dynamically detected integration version.
525554 """
526555 if not hasattr (self , '_check_version' ):
527- # 'datadog_checks.<PACKAGE>.<MODULE>...'
528- module_parts = self .__module__ .split ('.' )
529- package_path = '.' .join (module_parts [:2 ])
556+ pkg = _package_name (self .__module__ )
557+ package_path = f'datadog_checks.{ pkg } ' if pkg else self .__module__
530558 package = importlib .import_module (package_path )
531559
532560 # Provide a default just in case
@@ -545,8 +573,8 @@ def _get_package_dir(self) -> Path:
545573 used by :attr:`check_version`.
546574 """
547575 if not hasattr (self , '_package_dir' ):
548- module_parts = self .__module__ . split ( '.' )
549- package_path = '.' . join ( module_parts [: 2 ])
576+ pkg = _package_name ( self .__module__ )
577+ package_path = f'datadog_checks. { pkg } ' if pkg else self . __module__
550578 package = importlib .import_module (package_path )
551579 if package .__file__ is not None :
552580 self ._package_dir = Path (package .__file__ ).parent
@@ -613,9 +641,8 @@ def log_typos_in_options(self, user_config, models_config, level):
613641
614642 def load_configuration_models (self , package_path = None ):
615643 if package_path is None :
616- # 'datadog_checks.<PACKAGE>.<MODULE>...'
617- module_parts = self .__module__ .split ('.' )
618- package_path = '{}.config_models' .format ('.' .join (module_parts [:2 ]))
644+ pkg = _package_name (self .__module__ )
645+ package_path = f'datadog_checks.{ pkg } .config_models' if pkg else f'{ self .__module__ } .config_models'
619646 if self ._config_model_shared is None :
620647 shared_config = copy .deepcopy (self .init_config )
621648 context = self ._get_config_model_context (shared_config )
@@ -752,7 +779,7 @@ def submit_histogram_bucket(
752779 if hostname is None :
753780 hostname = ''
754781
755- aggregator .submit_histogram_bucket (
782+ self . _agg () .submit_histogram_bucket (
756783 self ,
757784 self .check_id ,
758785 self ._format_namespace (name , raw ),
@@ -770,28 +797,28 @@ def database_monitoring_query_sample(self, raw_event):
770797 if raw_event is None :
771798 return
772799
773- aggregator .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-samples" )
800+ self . _agg () .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-samples" )
774801
775802 def database_monitoring_query_metrics (self , raw_event ):
776803 # type: (str) -> None
777804 if raw_event is None :
778805 return
779806
780- aggregator .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-metrics" )
807+ self . _agg () .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-metrics" )
781808
782809 def database_monitoring_query_activity (self , raw_event ):
783810 # type: (str) -> None
784811 if raw_event is None :
785812 return
786813
787- aggregator .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-activity" )
814+ self . _agg () .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-activity" )
788815
789816 def database_monitoring_metadata (self , raw_event ):
790817 # type: (str) -> None
791818 if raw_event is None :
792819 return
793820
794- aggregator .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-metadata" )
821+ self . _agg () .submit_event_platform_event (self , self .check_id , to_native_string (raw_event ), "dbm-metadata" )
795822
796823 def event_platform_event (self , raw_event , event_track_type ):
797824 # type: (str | bytes, str) -> None
@@ -810,7 +837,7 @@ def event_platform_event(self, raw_event, event_track_type):
810837 raw_event = bytes (raw_event )
811838 elif not isinstance (raw_event , bytes ):
812839 raw_event = to_native_string (raw_event )
813- aggregator .submit_event_platform_event (self , self .check_id , raw_event , event_track_type )
840+ self . _agg () .submit_event_platform_event (self , self .check_id , raw_event , event_track_type )
814841
815842 def submit_generic_resource (self , * , type , key , fields , include , seen_at = None , expire_at = None ):
816843 # type: (str, str, dict | None, dict, int | None, int | None) -> None
@@ -974,6 +1001,10 @@ def _metric_excluded(self, metric_name):
9741001
9751002 return self .exclude_metrics_pattern .search (metric_name ) is not None
9761003
1004+ def _agg (self ) -> Any :
1005+ """Return the active aggregator: proxy during a discovery probe, module singleton otherwise."""
1006+ return getattr (self , '_discovery_aggregator' , None ) or aggregator
1007+
9771008 def _submit_metric (
9781009 self , mtype , name , value , tags = None , hostname = None , device_name = None , raw = False , flush_first_value = False
9791010 ):
@@ -1012,7 +1043,7 @@ def _submit_metric(
10121043 self .warning (err_msg )
10131044 return
10141045
1015- aggregator .submit_metric (self , self .check_id , mtype , name , value , tags , hostname , flush_first_value )
1046+ self . _agg () .submit_metric (self , self .check_id , mtype , name , value , tags , hostname , flush_first_value )
10161047
10171048 def gauge (self , name , value , tags = None , hostname = None , device_name = None , raw = False ):
10181049 # type: (str, float, Sequence[str], str, str, bool) -> None
@@ -1233,7 +1264,7 @@ def service_check(self, name, status, tags=None, hostname=None, message=None, ra
12331264
12341265 message = self .sanitize (message )
12351266
1236- aggregator .submit_service_check (
1267+ self . _agg () .submit_service_check (
12371268 self , self .check_id , self ._format_namespace (name , raw ), status , tags , hostname , message
12381269 )
12391270
@@ -1649,7 +1680,7 @@ def event(self, event):
16491680 if self .__NAMESPACE__ :
16501681 event .setdefault ('source_type_name' , self .__NAMESPACE__ )
16511682
1652- aggregator .submit_event (self , self .check_id , event )
1683+ self . _agg () .submit_event (self , self .check_id , event )
16531684
16541685 def _normalize_tags_type (self , tags , device_name = None , metric_name = None ):
16551686 # type: (Sequence[Union[None, str, bytes]], str, str) -> List[str]
@@ -1759,3 +1790,10 @@ def load_config(yaml_str: str) -> Any:
17591790 raise ValueError (f'Failed to load config: { stderr .decode ("utf-8" , errors = "replace" )} ' )
17601791
17611792 return _parse_ast_config (stdout .strip ().decode ('utf-8' ))
1793+
1794+
1795+ def _package_name (module : str ) -> str | None :
1796+ parts = module .split ('.' )
1797+ if len (parts ) >= 2 and parts [0 ] == 'datadog_checks' :
1798+ return parts [1 ]
1799+ return None
0 commit comments