diff --git a/ddtrace/_hooks.py b/ddtrace/_hooks.py index c182e543866..5ef50cc0f84 100644 --- a/ddtrace/_hooks.py +++ b/ddtrace/_hooks.py @@ -1,5 +1,7 @@ import collections from copy import deepcopy +from typing import DefaultDict +from typing import Set from .internal.logger import get_logger from .vendor import attr @@ -20,7 +22,7 @@ def on_request(span, request, response): pass """ - _hooks = attr.ib(init=False, factory=lambda: collections.defaultdict(set)) + _hooks = attr.ib(init=False, factory=lambda: collections.defaultdict(set), type=DefaultDict[str, Set]) def __deepcopy__(self, memodict=None): hooks = Hooks() diff --git a/ddtrace/compat.py b/ddtrace/compat.py index e69614c3e67..c3ea37f0b03 100644 --- a/ddtrace/compat.py +++ b/ddtrace/compat.py @@ -4,6 +4,9 @@ import sys import textwrap import threading +from typing import Any +from typing import AnyStr +from typing import Text from ddtrace.vendor import six @@ -29,17 +32,17 @@ PYTHON_INTERPRETER = platform.python_implementation() try: - StringIO = six.moves.cStringIO + StringIO = six.moves.cStringIO # type: ignore[attr-defined] except ImportError: StringIO = six.StringIO -httplib = six.moves.http_client -urlencode = six.moves.urllib.parse.urlencode -parse = six.moves.urllib.parse -Queue = six.moves.queue.Queue +httplib = six.moves.http_client # type: ignore[attr-defined] +urlencode = six.moves.urllib.parse.urlencode # type: ignore[attr-defined] +parse = six.moves.urllib.parse # type: ignore[attr-defined] +Queue = six.moves.queue.Queue # type: ignore[attr-defined] iteritems = six.iteritems reraise = six.reraise -reload_module = six.moves.reload_module +reload_module = six.moves.reload_module # type: ignore[attr-defined] stringify = six.text_type string_type = six.string_types[0] @@ -52,10 +55,11 @@ if PYTHON_VERSION_INFO >= (3, 7): pattern_type = re.Pattern else: - pattern_type = re._pattern_type + pattern_type = re._pattern_type # type: ignore[misc,attr-defined] def is_integer(obj): + # type: (Any) -> bool """Helper to determine if the provided ``obj`` is an integer type or not""" # DEV: We have to make sure it is an integer and not a boolean # >>> type(True) @@ -71,6 +75,7 @@ def is_integer(obj): from time import time as _time def time_ns(): + # type: () -> int return int(_time() * 10e5) * 1000 @@ -85,15 +90,17 @@ def time_ns(): except ImportError: def monotonic_ns(): + # type: () -> int return int(monotonic() * 1e9) try: from time import process_time_ns except ImportError: - from time import clock as _process_time + from time import clock as _process_time # type: ignore[attr-defined] def process_time_ns(): + # type: () -> int return int(_process_time() * 1e9) @@ -104,10 +111,10 @@ def process_time_ns(): if sys.version_info.major < 3: - if isinstance(threading.current_thread(), threading._MainThread): + if isinstance(threading.current_thread(), threading._MainThread): # type: ignore[attr-defined] main_thread = threading.current_thread() else: - main_thread = threading._shutdown.im_self + main_thread = threading._shutdown.im_self # type: ignore[attr-defined] else: main_thread = threading.main_thread() @@ -152,7 +159,7 @@ def func_wrapper(*args, **kwargs): # asyncio is missing so we can't have coroutines; these # functions are used only to ensure code executions in case # of an unexpected behavior - def iscoroutinefunction(fn): + def iscoroutinefunction(fn): # type: ignore return False def make_async_decorator(tracer, fn, *params, **kw_params): @@ -161,6 +168,7 @@ def make_async_decorator(tracer, fn, *params, **kw_params): # DEV: There is `six.u()` which does something similar, but doesn't have the guard around `hasattr(s, 'decode')` def to_unicode(s): + # type: (AnyStr) -> Text """ Return a unicode string for the given bytes or string instance. """ # No reason to decode if we already have the unicode compatible object we expect # DEV: `six.text_type` will be a `str` for python 3 and `unicode` for python 2 @@ -200,7 +208,7 @@ def get_connection_response(conn): try: import contextvars # noqa except ImportError: - from ddtrace.vendor import contextvars # noqa + from ddtrace.vendor import contextvars # type: ignore # noqa CONTEXTVARS_IS_AVAILABLE = False else: diff --git a/ddtrace/filters.py b/ddtrace/filters.py index 5ab2bc29a61..5d674bba8e2 100644 --- a/ddtrace/filters.py +++ b/ddtrace/filters.py @@ -9,7 +9,7 @@ from .ext import http -class TraceFilter(six.with_metaclass(abc.ABCMeta)): +class TraceFilter(six.with_metaclass(abc.ABCMeta)): # type: ignore[misc] @abc.abstractmethod def process_trace(self, trace): # type: (List[Span]) -> Optional[List[Span]] @@ -52,6 +52,7 @@ def __init__(self, regexps): self._regexps = [re.compile(regexp) for regexp in regexps] def process_trace(self, trace): + # type: (List[Span]) -> Optional[List[Span]] """ When the filter is registered in the tracer, process_trace is called by on each trace before it is sent to the agent, the returned value will diff --git a/ddtrace/internal/_encoding.pyi b/ddtrace/internal/_encoding.pyi new file mode 100644 index 00000000000..a49baba15c1 --- /dev/null +++ b/ddtrace/internal/_encoding.pyi @@ -0,0 +1,6 @@ +from typing import Any +from typing import Union + +class MsgpackEncoder(object): + content_type: str + def _decode(self, data: Union[str, bytes]) -> Any: ... diff --git a/ddtrace/internal/_rand.pyi b/ddtrace/internal/_rand.pyi new file mode 100644 index 00000000000..c8ecd497792 --- /dev/null +++ b/ddtrace/internal/_rand.pyi @@ -0,0 +1,2 @@ +def seed() -> None: ... +def rand64bits(check_pid: bool = True) -> int: ... diff --git a/ddtrace/internal/writer.py b/ddtrace/internal/writer.py index ed9d0b71ef1..48b4908b4e6 100644 --- a/ddtrace/internal/writer.py +++ b/ddtrace/internal/writer.py @@ -133,7 +133,7 @@ def stop(self, timeout=None): @abc.abstractmethod def write(self, trace): - # type: (List[Span]) -> None + # type: (Optional[List[Span]]) -> None pass @@ -159,7 +159,7 @@ def stop(self, timeout=None): return def write(self, spans): - # type: (List[Span]) -> None + # type: (Optional[List[Span]]) -> None if not spans: return diff --git a/ddtrace/provider.py b/ddtrace/provider.py index 7fac295351c..a556767c104 100644 --- a/ddtrace/provider.py +++ b/ddtrace/provider.py @@ -9,7 +9,7 @@ _DD_CONTEXTVAR = contextvars.ContextVar("datadog_contextvar", default=None) -class BaseContextProvider(six.with_metaclass(abc.ABCMeta)): +class BaseContextProvider(six.with_metaclass(abc.ABCMeta)): # type: ignore[misc] """ A ``ContextProvider`` is an interface that provides the blueprint for a callable class, capable to retrieve the current active diff --git a/ddtrace/sampler.py b/ddtrace/sampler.py index f64fd3cd8e7..1eed2115168 100644 --- a/ddtrace/sampler.py +++ b/ddtrace/sampler.py @@ -26,13 +26,13 @@ KNUTH_FACTOR = 1111111111111111111 -class BaseSampler(six.with_metaclass(abc.ABCMeta)): +class BaseSampler(six.with_metaclass(abc.ABCMeta)): # type: ignore[misc] @abc.abstractmethod def sample(self, span): pass -class BasePrioritySampler(six.with_metaclass(abc.ABCMeta)): +class BasePrioritySampler(six.with_metaclass(abc.ABCMeta)): # type: ignore[misc] @abc.abstractmethod def update_rate_by_service_sample_rates(self, sample_rates): pass diff --git a/ddtrace/tracer.py b/ddtrace/tracer.py index ce1d6c88e3d..96d3bc322ca 100644 --- a/ddtrace/tracer.py +++ b/ddtrace/tracer.py @@ -4,6 +4,13 @@ from os import environ from os import getpid import sys +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Optional +from typing import Set +from typing import Union from ddtrace import config @@ -15,9 +22,11 @@ from .constants import SAMPLE_RATE_METRIC_KEY from .constants import VERSION_KEY from .context import Context +from .ext import SpanTypes from .ext import system from .ext.priority import AUTO_KEEP from .ext.priority import AUTO_REJECT +from .filters import TraceFilter from .internal import _rand from .internal import agent from .internal import debug @@ -30,7 +39,9 @@ from .internal.runtime import get_runtime_id from .internal.writer import AgentWriter from .internal.writer import LogWriter +from .internal.writer import TraceWriter from .provider import DefaultContextProvider +from .sampler import BaseSampler from .sampler import DatadogSampler from .sampler import RateByServiceSampler from .sampler import RateSampler @@ -70,7 +81,12 @@ class Tracer(object): trace = tracer.trace('app.request', 'web-server').finish() """ - def __init__(self, url=None, dogstatsd_url=None): + def __init__( + self, + url=None, # type: Optional[str] + dogstatsd_url=None, # type: Optional[str] + ): + # type: (...) -> None """ Create a new ``Tracer`` instance. A global tracer is already initialized for common usage, so there is no need to initialize your own ``Tracer``. @@ -79,16 +95,16 @@ def __init__(self, url=None, dogstatsd_url=None): :param url: The DogStatsD URL. """ self.log = log - self.sampler = None + self.sampler = None # type: Optional[BaseSampler] self.priority_sampler = None self._runtime_worker = None - self._filters = [] + self._filters = [] # type: List[TraceFilter] # globally set tags self.tags = config.tags.copy() # a buffer for service info so we don't perpetually send the same things - self._services = set() + self._services = set() # type: Set[str] # Runtime id used for associating data collected during runtime to # traces @@ -113,10 +129,11 @@ def __init__(self, url=None, dogstatsd_url=None): report_metrics=config.health_metrics_enabled, ) self.writer = writer - self.processor = TraceProcessor([]) + self.processor = TraceProcessor([]) # type: ignore[call-arg] self._hooks = _hooks.Hooks() def on_start_span(self, func): + # type: (Callable) -> Callable """Register a function to execute when a span start. Can be used as a decorator. @@ -128,6 +145,7 @@ def on_start_span(self, func): return func def deregister_on_start_span(self, func): + # type: (Callable) -> Callable """Unregister a function registered to execute when a span starts. Can be used as a decorator. @@ -142,9 +160,10 @@ def deregister_on_start_span(self, func): def debug_logging(self): return self.log.isEnabledFor(logging.DEBUG) - @debug_logging.setter + @debug_logging.setter # type: ignore[misc] @deprecated(message="Use logging.setLevel instead", version="1.0.0") def debug_logging(self, value): + # type: (bool) -> None self.log.setLevel(logging.DEBUG if value else logging.WARN) @deprecated("Use .tracer, not .tracer()", "1.0.0") @@ -156,6 +175,7 @@ def global_excepthook(self, tp, value, traceback): """The global tracer except hook.""" def get_call_context(self, *args, **kwargs): + # type: (...) -> Context """ Return the current active ``Context`` for this traced execution. This method is automatically called in the ``tracer.trace()``, but it can be used in the application @@ -171,25 +191,26 @@ async def web_handler(request): This method makes use of a ``ContextProvider`` that is automatically set during the tracer initialization, or while using a library instrumentation. """ - return self.context_provider.active(*args, **kwargs) + return self.context_provider.active(*args, **kwargs) # type: ignore # TODO: deprecate this method and make sure users create a new tracer if they need different parameters def configure( self, - enabled=None, - hostname=None, - port=None, - uds_path=None, - https=None, - sampler=None, - context_provider=None, - wrap_executor=None, - priority_sampling=None, - settings=None, - collect_metrics=None, - dogstatsd_url=None, - writer=None, + enabled=None, # type: Optional[bool] + hostname=None, # type: Optional[str] + port=None, # type: Optional[int] + uds_path=None, # type: Optional[str] + https=None, # type: Optional[bool] + sampler=None, # type: Optional[BaseSampler] + context_provider=None, # type: Optional[DefaultContextProvider] + wrap_executor=None, # type: Optional[Callable] + priority_sampling=None, # type: Optional[bool] + settings=None, # type: Optional[Dict[str, Any]] + collect_metrics=None, # type: Optional[bool] + dogstatsd_url=None, # type: Optional[str] + writer=None, # type: Optional[TraceWriter] ): + # type: (...) -> None """ Configure an existing Tracer the easy way. Allow to configure or reconfigure a Tracer instance. @@ -259,7 +280,7 @@ def configure( else: # No URL parts have updated and there's no previous writer to # get the URL from. - url = None + url = None # type: ignore self.writer.stop() if url: @@ -272,7 +293,7 @@ def configure( report_metrics=config.health_metrics_enabled, ) self.writer.dogstatsd = get_dogstatsd_client(self._dogstatsd_url) - self.processor = TraceProcessor(filters=self._filters) + self.processor = TraceProcessor(filters=self._filters) # type: ignore[call-arg] if context_provider is not None: self.context_provider = context_provider @@ -309,7 +330,15 @@ def configure( msg = "- DATADOG TRACER DIAGNOSTIC - %s" % agent_error self._log_compat(logging.WARNING, msg) - def start_span(self, name, child_of=None, service=None, resource=None, span_type=None): + def start_span( + self, + name, # type: str + child_of=None, # type: Optional[Union[Span, Context]] + service=None, # type: Optional[str] + resource=None, # type: Optional[str] + span_type=None, # type: Optional[Union[str, SpanTypes]] + ): + # type: (...) -> Span """ Return a span that will trace an operation called `name`. This method allows parenting using the ``child_of`` kwarg. If it's missing, the newly created span is a @@ -405,8 +434,8 @@ def start_span(self, name, child_of=None, service=None, resource=None, span_type # only applied to spans with types that are internal to applications if self._runtime_worker and self._is_span_internal(span): span.meta["language"] = "python" - - span.sampled = self.sampler.sample(span) + # TODO: Can remove below type ignore once sampler is mypy type hinted + span.sampled = self.sampler.sample(span) # type: ignore[union-attr] # Old behavior # DEV: The new sampler sets metrics and priority sampling on the span for us if not isinstance(self.sampler, DatadogSampler): @@ -531,6 +560,7 @@ def _log_compat(self, level, msg): self.log.log(level, msg) def trace(self, name, service=None, resource=None, span_type=None): + # type: (str, Optional[str], Optional[str], Optional[Union[str, SpanTypes]]) -> Span """ Return a span that will trace an operation called `name`. The context that created the span as well as the span parenting, are automatically handled by the tracing @@ -578,6 +608,7 @@ def trace(self, name, service=None, resource=None, span_type=None): ) def current_root_span(self): + # type: () -> Optional[Span] """Returns the root span of the current context. This is useful for attaching information related to the trace as a @@ -597,6 +628,7 @@ def current_root_span(self): return None def current_span(self): + # type: () -> Optional[Span] """ Return the active span for the current call context or ``None`` if no spans are available. @@ -607,6 +639,7 @@ def current_span(self): return None def write(self, spans): + # type: (Optional[List[Span]]) -> None """ Send the trace to the writer to enqueue the spans list in the agent sending queue. @@ -631,7 +664,14 @@ def set_service_info(self, *args, **kwargs): """Set the information about the given service.""" return - def wrap(self, name=None, service=None, resource=None, span_type=None): + def wrap( + self, + name=None, # type: Optional[str] + service=None, # type: Optional[str] + resource=None, # type: Optional[str] + span_type=None, # type: Optional[str] + ): + # type: (...) -> Callable[[Callable[..., Any]], Callable[..., Any]] """ A decorator used to trace an entire function. If the traced function is a coroutine, it traces the coroutine execution when is awaited. @@ -721,6 +761,7 @@ def func_wrapper(*args, **kwargs): return wrap_decorator def set_tags(self, tags): + # type: (Dict[str, str]) -> None """Set some tags at the tracer level. This will append those tags to each span created by the tracer. @@ -729,6 +770,7 @@ def set_tags(self, tags): self.tags.update(tags) def shutdown(self, timeout=None): + # type: (Optional[float]) -> None """Shutdown the tracer. This will stop the background writer/worker and flush any finished traces in the buffer. diff --git a/mypy.ini b/mypy.ini index 8c936b341bb..3e30555a231 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,5 @@ [mypy] -files = ddtrace/profiling +files = ddtrace/profiling, ddtrace/*.py show_error_codes = true warn_unused_ignores = true warn_unused_configs = true