Repository: DataDog/datadogpy

Issue:
"import datadog" executes statements
**Describe the bug**
`import datadog` executes statements. In particular, it runs `atexit.register(lambda: self.flush(float('inf')))` in `datadog/threadstats/base.py`. This results in errors in our unit tests because the exit handler, `datadog.threadstats.ThreadStats.flush`, calls `log.debug` after the logging capture file has been closed.

**To Reproduce**
Steps to reproduce the behavior:
1. Create a Python file, p.py, containing only `import datadog`
2. Run pdb on p.py
3. Set a breakpoint on `.../datadog/threadstats/base.py:337`
4. Enter `c` to start execution
5. Once the breakpoint is hit, enter `bt` to see the backtrace leading back to `import datadog`

**Expected behavior**
`import datadog` should not execute any statements.

**Environment and Versions (please complete the following information):**
- datadogpy 0.36.0
- python 3.7

Comments:

Patch:
diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py
index fa24951..5a3c6de 100644
--- a/datadog/threadstats/aws_lambda.py
+++ b/datadog/threadstats/aws_lambda.py
@@ -44,6 +44,9 @@ class _LambdaDecorator(object):
                 # This avoids adding execution time at the end of the lambda run
                 t = Thread(target=_init_api_client)
                 t.start()
+
+                # Make sure the global ThreadStats has been created
+                _get_lambda_stats()
             cls._counter = cls._counter + 1
 
     @classmethod
@@ -63,7 +66,7 @@ class _LambdaDecorator(object):
                     if cls._counter > 0:
                         should_flush = False
                 if should_flush:
-                    _lambda_stats.flush(float("inf"))
+                    _get_lambda_stats().flush(float("inf"))
 
     def __call__(self, *args, **kw):
         warnings.warn("datadog_lambda_wrapper() is relocated to https://git.io/fjy8o", DeprecationWarning)
@@ -74,14 +77,22 @@ class _LambdaDecorator(object):
             _LambdaDecorator._close()
 
 
-_lambda_stats = ThreadStats()
-_lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False)
+_lambda_stats = None
 datadog_lambda_wrapper = _LambdaDecorator
 
 
+def _get_lambda_stats():
+    global _lambda_stats
+    # This is not thread-safe, it should be called first by _LambdaDecorator
+    if _lambda_stats is None:
+        _lambda_stats = ThreadStats()
+        _lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False)
+    return _lambda_stats
+
+
 def lambda_metric(*args, **kw):
     """ Alias to expose only distributions for lambda functions"""
-    _lambda_stats.distribution(*args, **kw)
+    _get_lambda_stats().distribution(*args, **kw)
 
 
 def _init_api_client():


Test patch:
diff --git a/tests/unit/threadstats/test_threadstats.py b/tests/unit/threadstats/test_threadstats.py
index 40c74a4..bc1b9c1 100644
--- a/tests/unit/threadstats/test_threadstats.py
+++ b/tests/unit/threadstats/test_threadstats.py
@@ -17,7 +17,7 @@ from mock import patch
 
 # datadog
 from datadog import ThreadStats, lambda_metric, datadog_lambda_wrapper
-from datadog.threadstats.aws_lambda import _lambda_stats
+from datadog.threadstats.aws_lambda import _get_lambda_stats
 from tests.util.contextmanagers import preserve_environment_variable
 
 # Silence the logger.
@@ -808,11 +808,11 @@ class TestUnitThreadStats(unittest.TestCase):
         def basic_wrapped_function():
             lambda_metric("lambda.somemetric", 100)
 
-        _lambda_stats.reporter = self.reporter
+        _get_lambda_stats().reporter = self.reporter
         basic_wrapped_function()
 
-        assert _lambda_stats.reporter.dist_flush_counter == 1
-        dists = self.sort_metrics(_lambda_stats.reporter.distributions)
+        assert _get_lambda_stats().reporter.dist_flush_counter == 1
+        dists = self.sort_metrics(_get_lambda_stats().reporter.distributions)
         assert len(dists) == 1
 
     def test_embedded_lambda_decorator(self):
@@ -829,9 +829,9 @@ class TestUnitThreadStats(unittest.TestCase):
             wrapped_function_1()
             lambda_metric("lambda.dist.2", 30)
 
-        _lambda_stats.reporter = self.reporter
+        _get_lambda_stats().reporter = self.reporter
         wrapped_function_2()
-        assert _lambda_stats.reporter.dist_flush_counter == 1
+        assert _get_lambda_stats().reporter.dist_flush_counter == 1
 
-        dists = self.sort_metrics(_lambda_stats.reporter.distributions)
+        dists = self.sort_metrics(_get_lambda_stats().reporter.distributions)
         assert len(dists) == 2