Skip to content

Commit a7a7594

Browse files
authored
Lazy load packages in compat for faster import. (#881)
* Streamline usage of ConfigParser for lazy loading. * Do not import all of asyncio. * Remove unused imports * Defer import of urllib.request to when its actually needed. * Test to ensure slow imports do not reappear. * Use other configparser. * Return test to original state. * Skip test for python2. * Update comment text.
1 parent fc4c147 commit a7a7594

File tree

2 files changed

+57
-12
lines changed

2 files changed

+57
-12
lines changed

datadog/util/compat.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
"""
66
Imports for compatibility with Python 2, Python 3 and Google App Engine.
77
"""
8-
from functools import wraps
98
import logging
10-
import socket
119
import sys
1210

1311
# Logging
@@ -23,11 +21,25 @@
2321
if sys.version_info[0] >= 3:
2422
import builtins
2523
from collections import UserDict as IterableUserDict
26-
import configparser
27-
from configparser import ConfigParser
2824
from io import StringIO
29-
from urllib.parse import urljoin, urlparse
30-
import urllib.request as url_lib, urllib.error, urllib.parse
25+
from urllib.parse import urlparse
26+
27+
class LazyLoader(object):
28+
def __init__(self, module_name):
29+
self.module_name = module_name
30+
31+
def __getattr__(self, name):
32+
# defer the importing of the module to when one of its attributes
33+
# is accessed
34+
import importlib
35+
mod = importlib.import_module(self.module_name)
36+
return getattr(mod, name)
37+
38+
url_lib = LazyLoader('urllib.request')
39+
configparser = LazyLoader('configparser')
40+
41+
def ConfigParser():
42+
return configparser.ConfigParser()
3143

3244
imap = map
3345
get_input = input
@@ -48,7 +60,7 @@ def iternext(iter):
4860
from cStringIO import StringIO
4961
from itertools import imap
5062
import urllib2 as url_lib
51-
from urlparse import urljoin, urlparse
63+
from urlparse import urlparse
5264
from UserDict import IterableUserDict
5365

5466
get_input = raw_input
@@ -63,7 +75,7 @@ def iternext(iter):
6375

6476
# Python >= 3.5
6577
if sys.version_info >= (3, 5):
66-
from asyncio import iscoroutinefunction
78+
from inspect import iscoroutinefunction
6779
# Others
6880
else:
6981

@@ -76,9 +88,7 @@ def iscoroutinefunction(*args, **kwargs):
7688
from logging import NullHandler
7789
# Python 2.6.x
7890
else:
79-
from logging import Handler
80-
81-
class NullHandler(Handler):
91+
class NullHandler(logging.Handler):
8292
def emit(self, record):
8393
pass
8494

tests/unit/util/test_compat.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
# This product includes software developed at Datadog (https://www.datadoghq.com/).
44
# Copyright 2015-Present Datadog, Inc
55
import logging
6+
import pytest
7+
import sys
68
import unittest
79

810
from mock import patch
911

10-
from datadog.util.compat import conditional_lru_cache, is_higher_py32
12+
from datadog.util.compat import conditional_lru_cache, is_higher_py32, is_p3k
1113

1214
class TestConditionalLRUCache(unittest.TestCase):
1315
def test_normal_usage(self):
@@ -48,3 +50,36 @@ def test_function():
4850
mock_debug.assert_called_once()
4951
else:
5052
mock_debug.assert_not_called()
53+
54+
@pytest.mark.skipif(not is_p3k(), reason='Python 3 only')
55+
def test_slow_imports(monkeypatch):
56+
# We should lazy load certain modules to avoid slowing down the startup
57+
# time when running in a serverless environment. This test will fail if
58+
# any of those modules are imported during the import of datadogpy.
59+
60+
blocklist = [
61+
'configparser',
62+
'email.mime.application',
63+
'email.mime.multipart',
64+
'importlib.metadata',
65+
'importlib_metadata',
66+
'logging.handlers',
67+
'multiprocessing',
68+
'urllib.request',
69+
]
70+
71+
class BlockListFinder:
72+
def find_spec(self, fullname, *args):
73+
for lib in blocklist:
74+
if fullname == lib:
75+
raise ImportError('module %s was imported!' % fullname)
76+
return None
77+
find_module = find_spec # Python 2
78+
79+
monkeypatch.setattr('sys.meta_path', [BlockListFinder()] + sys.meta_path)
80+
81+
for mod in sys.modules.copy():
82+
if mod in blocklist or mod.startswith('datadog'):
83+
del sys.modules[mod]
84+
85+
import datadog

0 commit comments

Comments
 (0)