diff --git a/MANIFEST.in b/MANIFEST.in index 72ce170db..9c7227b08 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include AUTHORS CHANGES LICENSE MANIFEST.in README.rst include setup.py +include statsd/py.typed recursive-include docs * diff --git a/requirements.txt b/requirements.txt index 6aa9af6b5..d3f5f6073 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ mock==1.0.1 nose==1.2.1 -flake8==1.7.0 +flake8 +mypy==0.910 diff --git a/setup.py b/setup.py index 197757f58..6be33d6a2 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,6 @@ license='MIT', packages=find_packages(), include_package_data=True, - package_data={'': ['README.rst']}, test_suite='nose.collector', classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/statsd/client/__init__.py b/statsd/client/__init__.py index 62cd202c7..c5ea3259f 100644 --- a/statsd/client/__init__.py +++ b/statsd/client/__init__.py @@ -1,4 +1,11 @@ from __future__ import absolute_import, division, unicode_literals -from .stream import TCPStatsClient, UnixSocketStatsClient # noqa -from .udp import StatsClient # noqa +from .stream import TCPStatsClient, UnixSocketStatsClient +from .udp import StatsClient + + +__all__ = [ + 'TCPStatsClient', + 'UnixSocketStatsClient', + 'StatsClient', +] diff --git a/statsd/client/base.py b/statsd/client/base.py index e273b417f..9a614f17e 100644 --- a/statsd/client/base.py +++ b/statsd/client/base.py @@ -3,27 +3,37 @@ import random from collections import deque from datetime import timedelta +from typing import Deque, Optional, TypeVar, Union from .timer import Timer +P = TypeVar('P', bound='PipelineBase') + + class StatsClientBase(object): """A Base class for various statsd clients.""" + _prefix: Optional[str] - def close(self): + def close(self) -> None: """Used to close and clean up any underlying resources.""" raise NotImplementedError() - def _send(self): + def _send(self) -> None: raise NotImplementedError() - def pipeline(self): + def pipeline(self) -> 'PipelineBase': raise NotImplementedError() - def timer(self, stat, rate=1): + def timer(self, stat: str, rate: float = 1) -> Timer: return Timer(self, stat, rate) - def timing(self, stat, delta, rate=1): + def timing( + self, + stat: str, + delta: Union[timedelta, float], + rate: float = 1, + ) -> None: """ Send new timing information. @@ -34,15 +44,21 @@ def timing(self, stat, delta, rate=1): delta = delta.total_seconds() * 1000. self._send_stat(stat, '%0.6f|ms' % delta, rate) - def incr(self, stat, count=1, rate=1): + def incr(self, stat: str, count: int = 1, rate: float = 1) -> None: """Increment a stat by `count`.""" self._send_stat(stat, '%s|c' % count, rate) - def decr(self, stat, count=1, rate=1): + def decr(self, stat: str, count: int = 1, rate: float = 1) -> None: """Decrement a stat by `count`.""" self.incr(stat, -count, rate) - def gauge(self, stat, value, rate=1, delta=False): + def gauge( + self, + stat: str, + value: float, + rate: float = 1, + delta: bool = False, + ) -> None: """Set a gauge value.""" if value < 0 and not delta: if rate < 1: @@ -55,17 +71,17 @@ def gauge(self, stat, value, rate=1, delta=False): prefix = '+' if delta and value >= 0 else '' self._send_stat(stat, '%s%s|g' % (prefix, value), rate) - def set(self, stat, value, rate=1): + def set(self, stat: str, value: str, rate: float = 1) -> None: """Set a set value.""" self._send_stat(stat, '%s|s' % value, rate) - def _send_stat(self, stat, value, rate): + def _send_stat(self, stat: str, value: str, rate: float) -> None: self._after(self._prepare(stat, value, rate)) - def _prepare(self, stat, value, rate): + def _prepare(self, stat: str, value: str, rate: float) -> Optional[str]: if rate < 1: if random.random() > rate: - return + return None value = '%s|@%s' % (value, rate) if self._prefix: @@ -73,35 +89,38 @@ def _prepare(self, stat, value, rate): return '%s:%s' % (stat, value) - def _after(self, data): + def _after(self, data: Optional[str]) -> None: if data: self._send(data) class PipelineBase(StatsClientBase): + _prefix: Optional[str] + _stats: Deque[str] + _client: StatsClientBase - def __init__(self, client): + def __init__(self, client: StatsClientBase) -> None: self._client = client self._prefix = client._prefix self._stats = deque() - def _send(self): + def _send(self) -> None: raise NotImplementedError() - def _after(self, data): + def _after(self, data: Optional[str]) -> None: if data is not None: self._stats.append(data) - def __enter__(self): + def __enter__(self: P) -> P: return self - def __exit__(self, typ, value, tb): + def __exit__(self, *exc_info) -> None: self.send() - def send(self): + def send(self) -> None: if not self._stats: return self._send() - def pipeline(self): + def pipeline(self: P) -> P: return self.__class__(self) diff --git a/statsd/client/stream.py b/statsd/client/stream.py index 76c216f56..973915e3c 100644 --- a/statsd/client/stream.py +++ b/statsd/client/stream.py @@ -1,47 +1,63 @@ from __future__ import absolute_import, division, unicode_literals import socket +from typing import Optional, Union from .base import StatsClientBase, PipelineBase class StreamPipeline(PipelineBase): - def _send(self): + def _send(self) -> None: self._client._after('\n'.join(self._stats)) self._stats.clear() class StreamClientBase(StatsClientBase): - def connect(self): + _sock: Optional[socket.socket] + + def connect(self) -> None: raise NotImplementedError() - def close(self): + def close(self) -> None: if self._sock and hasattr(self._sock, 'close'): self._sock.close() self._sock = None - def reconnect(self): + def reconnect(self) -> None: self.close() self.connect() - def pipeline(self): + def pipeline(self) -> StreamPipeline: return StreamPipeline(self) - def _send(self, data): + def _send(self, data: str) -> None: """Send data to statsd.""" if not self._sock: self.connect() self._do_send(data) - def _do_send(self, data): + def _do_send(self, data: str) -> None: + assert self._sock is not None self._sock.sendall(data.encode('ascii') + b'\n') class TCPStatsClient(StreamClientBase): """TCP version of StatsClient.""" - - def __init__(self, host='localhost', port=8125, prefix=None, - timeout=None, ipv6=False): + _host: str + _port: int + _prefix: Optional[str] + _timeout: Optional[float] + _ipv6: bool + _sock: Optional[socket.socket] + + def __init__( + self, + host: str = 'localhost', + port: int = 8125, + prefix: Optional[str] = None, + timeout: Optional[float] = None, + ipv6: bool = False, + ) -> None: """Create a new client.""" self._host = host self._port = port @@ -50,7 +66,7 @@ def __init__(self, host='localhost', port=8125, prefix=None, self._prefix = prefix self._sock = None - def connect(self): + def connect(self) -> None: fam = socket.AF_INET6 if self._ipv6 else socket.AF_INET family, _, _, _, addr = socket.getaddrinfo( self._host, self._port, fam, socket.SOCK_STREAM)[0] @@ -61,15 +77,24 @@ def connect(self): class UnixSocketStatsClient(StreamClientBase): """Unix domain socket version of StatsClient.""" - - def __init__(self, socket_path, prefix=None, timeout=None): + _socket_path: Union[str, bytes, tuple] + _prefix: Optional[str] + _timeout: Optional[float] + _sock: Optional[socket.socket] + + def __init__( + self, + socket_path: Union[str, bytes, tuple], + prefix: Optional[str] = None, + timeout: Optional[float] = None, + ) -> None: """Create a new client.""" self._socket_path = socket_path self._timeout = timeout self._prefix = prefix self._sock = None - def connect(self): + def connect(self) -> None: self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._sock.settimeout(self._timeout) self._sock.connect(self._socket_path) diff --git a/statsd/client/timer.py b/statsd/client/timer.py index fefc9d042..c18e2c434 100644 --- a/statsd/client/timer.py +++ b/statsd/client/timer.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, division, unicode_literals import functools +from typing import TYPE_CHECKING, Callable, Optional, TypeVar # Use timer that's not susceptible to time of day adjustments. try: @@ -11,17 +12,33 @@ from time import time as time_now -def safe_wraps(wrapper, *args, **kwargs): +if TYPE_CHECKING: + from .base import StatsClientBase + + +F = TypeVar('F', bound=Callable) +C = TypeVar('C', bound=Callable) +T = TypeVar('T', bound='Timer') + + +def safe_wraps(wrapper: F, *args, **kwargs) -> Callable[[C], C]: """Safely wraps partial functions.""" while isinstance(wrapper, functools.partial): - wrapper = wrapper.func + wrapper = wrapper.func # type: ignore[assignment] return functools.wraps(wrapper, *args, **kwargs) class Timer(object): """A context manager/decorator for statsd.timing().""" + client: 'StatsClientBase' + stat: str + rate: float + ms: Optional[float] + _sent: bool + _start_time: Optional[float] - def __init__(self, client, stat, rate=1): + def __init__(self, client: 'StatsClientBase', + stat: str, rate: float = 1) -> None: self.client = client self.stat = stat self.rate = rate @@ -29,7 +46,7 @@ def __init__(self, client, stat, rate=1): self._sent = False self._start_time = None - def __call__(self, f): + def __call__(self, f: F) -> F: """Thread-safe timing function decorator.""" @safe_wraps(f) def _wrapped(*args, **kwargs): @@ -39,21 +56,21 @@ def _wrapped(*args, **kwargs): finally: elapsed_time_ms = 1000.0 * (time_now() - start_time) self.client.timing(self.stat, elapsed_time_ms, self.rate) - return _wrapped + return _wrapped # type: ignore[return-value] - def __enter__(self): + def __enter__(self: T) -> T: return self.start() - def __exit__(self, typ, value, tb): + def __exit__(self, *exc_info) -> None: self.stop() - def start(self): + def start(self: T) -> T: self.ms = None self._sent = False self._start_time = time_now() return self - def stop(self, send=True): + def stop(self: T, send: bool = True) -> T: if self._start_time is None: raise RuntimeError('Timer has not started.') dt = time_now() - self._start_time @@ -62,7 +79,7 @@ def stop(self, send=True): self.send() return self - def send(self): + def send(self) -> None: if self.ms is None: raise RuntimeError('No data recorded.') if self._sent: diff --git a/statsd/client/udp.py b/statsd/client/udp.py index ddb9d222d..a33786787 100644 --- a/statsd/client/udp.py +++ b/statsd/client/udp.py @@ -1,17 +1,18 @@ from __future__ import absolute_import, division, unicode_literals import socket +from typing import Optional from .base import StatsClientBase, PipelineBase class Pipeline(PipelineBase): - def __init__(self, client): + def __init__(self, client: 'StatsClient') -> None: super(Pipeline, self).__init__(client) self._maxudpsize = client._maxudpsize - def _send(self): + def _send(self) -> None: data = self._stats.popleft() while self._stats: # Use popleft to preserve the order of the stats. @@ -27,8 +28,18 @@ def _send(self): class StatsClient(StatsClientBase): """A client for statsd.""" - def __init__(self, host='localhost', port=8125, prefix=None, - maxudpsize=512, ipv6=False): + _addr: tuple + _sock: Optional[socket.socket] + _maxudpsize: int + + def __init__( + self, + host: str = 'localhost', + port: int = 8125, + prefix: Optional[str] = None, + maxudpsize: int = 512, + ipv6: bool = False, + ) -> None: """Create a new client.""" fam = socket.AF_INET6 if ipv6 else socket.AF_INET family, _, _, _, addr = socket.getaddrinfo( @@ -38,7 +49,7 @@ def __init__(self, host='localhost', port=8125, prefix=None, self._prefix = prefix self._maxudpsize = maxudpsize - def _send(self, data): + def _send(self, data: str) -> None: """Send data to statsd.""" try: self._sock.sendto(data.encode('ascii'), self._addr) @@ -46,10 +57,10 @@ def _send(self, data): # No time for love, Dr. Jones! pass - def close(self): + def close(self) -> None: if self._sock and hasattr(self._sock, 'close'): self._sock.close() self._sock = None - def pipeline(self): + def pipeline(self) -> Pipeline: return Pipeline(self) diff --git a/statsd/py.typed b/statsd/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/statsd/tests.py b/statsd/tests.py index 12fc410cd..4e93d1231 100644 --- a/statsd/tests.py +++ b/statsd/tests.py @@ -34,7 +34,7 @@ } -def _udp_client(prefix=None, addr=None, port=None, ipv6=False): +def _udp_client(prefix=None, addr=None, port=None, ipv6: bool = False): if not addr: addr = ADDR[0] if not port: @@ -44,7 +44,8 @@ def _udp_client(prefix=None, addr=None, port=None, ipv6=False): return sc -def _tcp_client(prefix=None, addr=None, port=None, timeout=None, ipv6=False): +def _tcp_client(prefix=None, addr=None, port=None, + timeout=None, ipv6: bool = False): if not addr: addr = ADDR[0] if not port: @@ -64,7 +65,7 @@ def _unix_socket_client(prefix=None, socket_path=None): return sc -def _timer_check(sock, count, proto, start, end): +def _timer_check(sock, count, proto, start, end) -> None: send = send_method[proto](sock) eq_(send.call_count, count) value = send.call_args[0][0].decode('ascii') @@ -72,7 +73,7 @@ def _timer_check(sock, count, proto, start, end): assert exp.match(value) -def _sock_check(sock, count, proto, val=None, addr=None): +def _sock_check(sock, count, proto, val=None, addr=None) -> None: send = send_method[proto](sock) eq_(send.call_count, count) if not addr: @@ -122,14 +123,14 @@ class assert_raises(object): """ - def __init__(self, *exc_cls): + def __init__(self, *exc_cls) -> None: self.exc_cls = exc_cls def __enter__(self): # For access to the exception later. return self - def __exit__(self, typ, value, tb): + def __exit__(self, typ, value, tb) -> bool: assert typ, 'No exception raised.' assert typ in self.exc_cls, '%s not in %s' % ( typ.__name__, [e.__name__ for e in self.exc_cls]) @@ -141,7 +142,7 @@ def __exit__(self, typ, value, tb): return True -def _test_incr(cl, proto): +def _test_incr(cl, proto) -> None: cl.incr('foo') _sock_check(cl._sock, 1, proto, val='foo:1|c') @@ -156,27 +157,27 @@ def _test_incr(cl, proto): @mock.patch.object(random, 'random', lambda: -1) -def test_incr_udp(): +def test_incr_udp() -> None: """StatsClient.incr works.""" cl = _udp_client() _test_incr(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_incr_tcp(): +def test_incr_tcp() -> None: """TCPStatsClient.incr works.""" cl = _tcp_client() _test_incr(cl, 'tcp') @mock.patch.object(random, 'random', lambda: -1) -def test_incr_unix_socket(): +def test_incr_unix_socket() -> None: """TCPStatsClient.incr works.""" cl = _unix_socket_client() _test_incr(cl, 'unix') -def _test_decr(cl, proto): +def _test_decr(cl, proto) -> None: cl.decr('foo') _sock_check(cl._sock, 1, proto, 'foo:-1|c') @@ -191,27 +192,27 @@ def _test_decr(cl, proto): @mock.patch.object(random, 'random', lambda: -1) -def test_decr_udp(): +def test_decr_udp() -> None: """StatsClient.decr works.""" cl = _udp_client() _test_decr(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_decr_tcp(): +def test_decr_tcp() -> None: """TCPStatsClient.decr works.""" cl = _tcp_client() _test_decr(cl, 'tcp') @mock.patch.object(random, 'random', lambda: -1) -def test_decr_unix_socket(): +def test_decr_unix_socket() -> None: """TCPStatsClient.decr works.""" cl = _unix_socket_client() _test_decr(cl, 'unix') -def _test_gauge(cl, proto): +def _test_gauge(cl, proto) -> None: cl.gauge('foo', 30) _sock_check(cl._sock, 1, proto, 'foo:30|g') @@ -223,46 +224,46 @@ def _test_gauge(cl, proto): @mock.patch.object(random, 'random', lambda: -1) -def test_gauge_udp(): +def test_gauge_udp() -> None: """StatsClient.gauge works.""" cl = _udp_client() _test_gauge(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_gauge_tcp(): +def test_gauge_tcp() -> None: """TCPStatsClient.gauge works.""" cl = _tcp_client() _test_gauge(cl, 'tcp') @mock.patch.object(random, 'random', lambda: -1) -def test_gauge_unix_socket(): +def test_gauge_unix_socket() -> None: """TCPStatsClient.decr works.""" cl = _unix_socket_client() _test_gauge(cl, 'unix') -def _test_ipv6(cl, proto, addr): +def _test_ipv6(cl, proto, addr) -> None: cl.gauge('foo', 30) _sock_check(cl._sock, 1, proto, 'foo:30|g', addr=addr) -def test_ipv6_udp(): +def test_ipv6_udp() -> None: """StatsClient can use to IPv6 address.""" addr = ('::1', 8125, 0, 0) cl = _udp_client(addr=addr[0], ipv6=True) _test_ipv6(cl, 'udp', addr) -def test_ipv6_tcp(): +def test_ipv6_tcp() -> None: """TCPStatsClient can use to IPv6 address.""" addr = ('::1', 8125, 0, 0) cl = _tcp_client(addr=addr[0], ipv6=True) _test_ipv6(cl, 'tcp', addr) -def _test_resolution(cl, proto, addr): +def _test_resolution(cl, proto, addr) -> None: cl.incr('foo') _sock_check(cl._sock, 1, proto, 'foo:1|c', addr=addr) @@ -273,22 +274,22 @@ def test_ipv6_resolution_udp(): _test_resolution(cl, 'udp', ('::1', 8125, 0, 0)) -def test_ipv6_resolution_tcp(): +def test_ipv6_resolution_tcp() -> None: cl = _tcp_client(addr='localhost', ipv6=True) _test_resolution(cl, 'tcp', ('::1', 8125, 0, 0)) -def test_ipv4_resolution_udp(): +def test_ipv4_resolution_udp() -> None: cl = _udp_client(addr='localhost') _test_resolution(cl, 'udp', ('127.0.0.1', 8125)) -def test_ipv4_resolution_tcp(): +def test_ipv4_resolution_tcp() -> None: cl = _tcp_client(addr='localhost') _test_resolution(cl, 'tcp', ('127.0.0.1', 8125)) -def _test_gauge_delta(cl, proto): +def _test_gauge_delta(cl, proto) -> None: tests = ( (12, '+12'), (-13, '-13'), @@ -296,7 +297,7 @@ def _test_gauge_delta(cl, proto): (-1.3, '-1.3'), ) - def _check(num, result): + def _check(num, result) -> None: cl._sock.reset_mock() cl.gauge('foo', num, delta=True) _sock_check(cl._sock, 1, proto, 'foo:%s|g' % result) @@ -306,39 +307,39 @@ def _check(num, result): @mock.patch.object(random, 'random', lambda: -1) -def test_gauge_delta_udp(): +def test_gauge_delta_udp() -> None: """StatsClient.gauge works with delta values.""" cl = _udp_client() _test_gauge_delta(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_gauge_delta_tcp(): +def test_gauge_delta_tcp() -> None: """TCPStatsClient.gauge works with delta values.""" cl = _tcp_client() _test_gauge_delta(cl, 'tcp') -def _test_gauge_absolute_negative(cl, proto): +def _test_gauge_absolute_negative(cl, proto) -> None: cl.gauge('foo', -5, delta=False) _sock_check(cl._sock, 1, 'foo:0|g\nfoo:-5|g') @mock.patch.object(random, 'random', lambda: -1) -def test_gauge_absolute_negative_udp(): +def test_gauge_absolute_negative_udp() -> None: """StatsClient.gauge works with absolute negative value.""" cl = _udp_client() _test_gauge_delta(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_gauge_absolute_negative_tcp(): +def test_gauge_absolute_negative_tcp() -> None: """TCPStatsClient.gauge works with absolute negative value.""" cl = _tcp_client() _test_gauge_delta(cl, 'tcp') -def _test_gauge_absolute_negative_rate(cl, proto, mock_random): +def _test_gauge_absolute_negative_rate(cl, proto, mock_random) -> None: mock_random.return_value = -1 cl.gauge('foo', -1, rate=0.5, delta=False) _sock_check(cl._sock, 1, proto, 'foo:0|g\nfoo:-1|g') @@ -350,20 +351,20 @@ def _test_gauge_absolute_negative_rate(cl, proto, mock_random): @mock.patch.object(random, 'random') -def test_gauge_absolute_negative_rate_udp(mock_random): +def test_gauge_absolute_negative_rate_udp(mock_random) -> None: """StatsClient.gauge works with absolute negative value and rate.""" cl = _udp_client() _test_gauge_absolute_negative_rate(cl, 'udp', mock_random) @mock.patch.object(random, 'random') -def test_gauge_absolute_negative_rate_tcp(mock_random): +def test_gauge_absolute_negative_rate_tcp(mock_random) -> None: """TCPStatsClient.gauge works with absolute negative value and rate.""" cl = _tcp_client() _test_gauge_absolute_negative_rate(cl, 'tcp', mock_random) -def _test_set(cl, proto): +def _test_set(cl, proto) -> None: cl.set('foo', 10) _sock_check(cl._sock, 1, proto, 'foo:10|s') @@ -378,20 +379,20 @@ def _test_set(cl, proto): @mock.patch.object(random, 'random', lambda: -1) -def test_set_udp(): +def test_set_udp() -> None: """StatsClient.set works.""" cl = _udp_client() _test_set(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_set_tcp(): +def test_set_tcp() -> None: """TCPStatsClient.set works.""" cl = _tcp_client() _test_set(cl, 'tcp') -def _test_timing(cl, proto): +def _test_timing(cl, proto) -> None: cl.timing('foo', 100) _sock_check(cl._sock, 1, proto, 'foo:100.000000|ms') @@ -403,20 +404,20 @@ def _test_timing(cl, proto): @mock.patch.object(random, 'random', lambda: -1) -def test_timing_udp(): +def test_timing_udp() -> None: """StatsClient.timing works.""" cl = _udp_client() _test_timing(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_timing_tcp(): +def test_timing_tcp() -> None: """TCPStatsClient.timing works.""" cl = _tcp_client() _test_timing(cl, 'tcp') -def test_timing_supports_timedelta(): +def test_timing_supports_timedelta() -> None: cl = _udp_client() proto = 'udp' @@ -428,20 +429,20 @@ def test_timing_supports_timedelta(): @mock.patch.object(random, 'random', lambda: -1) -def test_timing_unix_socket(): +def test_timing_unix_socket() -> None: """UnixSocketStatsClient.timing works.""" cl = _unix_socket_client() _test_timing(cl, 'unix') -def _test_prepare(cl, proto): +def _test_prepare(cl, proto) -> None: tests = ( ('foo:1|c', ('foo', '1|c', 1)), ('bar:50|ms|@0.5', ('bar', '50|ms', 0.5)), ('baz:23|g', ('baz', '23|g', 1)), ) - def _check(o, s, v, r): + def _check(o, s, v, r) -> None: with mock.patch.object(random, 'random', lambda: -1): eq_(o, cl._prepare(s, v, r)) @@ -450,65 +451,65 @@ def _check(o, s, v, r): @mock.patch.object(random, 'random', lambda: -1) -def test_prepare_udp(): +def test_prepare_udp() -> None: """Test StatsClient._prepare method.""" cl = _udp_client() _test_prepare(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_prepare_tcp(): +def test_prepare_tcp() -> None: """Test TCPStatsClient._prepare method.""" cl = _tcp_client() _test_prepare(cl, 'tcp') -def _test_prefix(cl, proto): +def _test_prefix(cl, proto) -> None: cl.incr('bar') _sock_check(cl._sock, 1, proto, 'foo.bar:1|c') @mock.patch.object(random, 'random', lambda: -1) -def test_prefix_udp(): +def test_prefix_udp() -> None: """StatsClient.incr works.""" cl = _udp_client(prefix='foo') _test_prefix(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_prefix_tcp(): +def test_prefix_tcp() -> None: """TCPStatsClient.incr works.""" cl = _tcp_client(prefix='foo') _test_prefix(cl, 'tcp') @mock.patch.object(random, 'random', lambda: -1) -def test_prefix_unix_socket(): +def test_prefix_unix_socket() -> None: """UnixSocketStatsClient.incr works.""" cl = _unix_socket_client(prefix='foo') _test_prefix(cl, 'unix') -def _test_timer_manager(cl, proto): +def _test_timer_manager(cl, proto) -> None: with cl.timer('foo'): pass _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_timer_manager_udp(): +def test_timer_manager_udp() -> None: """StatsClient.timer can be used as manager.""" cl = _udp_client() _test_timer_manager(cl, 'udp') -def test_timer_manager_tcp(): +def test_timer_manager_tcp() -> None: """TCPStatsClient.timer can be used as manager.""" cl = _tcp_client() _test_timer_manager(cl, 'tcp') -def _test_timer_decorator(cl, proto): +def _test_timer_decorator(cl, proto) -> None: @cl.timer('foo') def foo(a, b): return [a, b] @@ -529,37 +530,37 @@ def bar(a, b): _timer_check(cl._sock, 3, proto, 'bar', 'ms') -def test_timer_decorator_udp(): +def test_timer_decorator_udp() -> None: """StatsClient.timer is a thread-safe decorator (UDP).""" cl = _udp_client() _test_timer_decorator(cl, 'udp') -def test_timer_decorator_tcp(): +def test_timer_decorator_tcp() -> None: """StatsClient.timer is a thread-safe decorator (TCP).""" cl = _tcp_client() _test_timer_decorator(cl, 'tcp') -def _test_timer_capture(cl, proto): +def _test_timer_capture(cl, proto) -> None: with cl.timer('woo') as result: eq_(result.ms, None) assert isinstance(result.ms, float) -def test_timer_capture_udp(): +def test_timer_capture_udp() -> None: """You can capture the output of StatsClient.timer (UDP).""" cl = _udp_client() _test_timer_capture(cl, 'udp') -def test_timer_capture_tcp(): +def test_timer_capture_tcp() -> None: """You can capture the output of StatsClient.timer (TCP).""" cl = _tcp_client() _test_timer_capture(cl, 'tcp') -def _test_timer_context_rate(cl, proto): +def _test_timer_context_rate(cl, proto) -> None: with cl.timer('foo', rate=0.5): pass @@ -567,20 +568,20 @@ def _test_timer_context_rate(cl, proto): @mock.patch.object(random, 'random', lambda: -1) -def test_timer_context_rate_udp(): +def test_timer_context_rate_udp() -> None: """StatsClient.timer can be used as manager with rate.""" cl = _udp_client() _test_timer_context_rate(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_timer_context_rate_tcp(): +def test_timer_context_rate_tcp() -> None: """TCPStatsClient.timer can be used as manager with rate.""" cl = _tcp_client() _test_timer_context_rate(cl, 'tcp') -def test_timer_decorator_partial_function(): +def test_timer_decorator_partial_function() -> None: """TCPStatsClient.timer can be used as decorator on a partial function.""" cl = _tcp_client() @@ -592,13 +593,13 @@ def test_timer_decorator_partial_function(): _timer_check(cl._sock, 1, 'tcp', 'foo', 'ms|@0.1') -def _test_timer_decorator_rate(cl, proto): +def _test_timer_decorator_rate(cl, proto) -> None: @cl.timer('foo', rate=0.1) def foo(a, b): return [b, a] @cl.timer('bar', rate=0.2) - def bar(a, b=2, c=3): + def bar(a, b: int = 2, c: int = 3): return [c, b, a] eq_([2, 4], foo(4, 2)) @@ -609,14 +610,14 @@ def bar(a, b=2, c=3): @mock.patch.object(random, 'random', lambda: -1) -def test_timer_decorator_rate_udp(): +def test_timer_decorator_rate_udp() -> None: """StatsClient.timer can be used as decorator with rate.""" cl = _udp_client() _test_timer_decorator_rate(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_timer_decorator_rate_tcp(): +def test_timer_decorator_rate_tcp() -> None: """TCPStatsClient.timer can be used as decorator with rate.""" cl = _tcp_client() _test_timer_decorator_rate(cl, 'tcp') @@ -630,17 +631,17 @@ def _test_timer_context_exceptions(cl, proto): _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_timer_context_exceptions_udp(): +def test_timer_context_exceptions_udp() -> None: cl = _udp_client() _test_timer_context_exceptions(cl, 'udp') -def test_timer_context_exceptions_tcp(): +def test_timer_context_exceptions_tcp() -> None: cl = _tcp_client() _test_timer_context_exceptions(cl, 'tcp') -def _test_timer_decorator_exceptions(cl, proto): +def _test_timer_decorator_exceptions(cl, proto) -> None: @cl.timer('foo') def foo(): raise ValueError() @@ -651,36 +652,36 @@ def foo(): _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_timer_decorator_exceptions_udp(): +def test_timer_decorator_exceptions_udp() -> None: cl = _udp_client() _test_timer_decorator_exceptions(cl, 'udp') -def test_timer_decorator_exceptions_tcp(): +def test_timer_decorator_exceptions_tcp() -> None: cl = _tcp_client() _test_timer_decorator_exceptions(cl, 'tcp') -def _test_timer_object(cl, proto): +def _test_timer_object(cl, proto) -> None: t = cl.timer('foo').start() t.stop() _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_timer_object_udp(): +def test_timer_object_udp() -> None: """StatsClient.timer works.""" cl = _udp_client() _test_timer_object(cl, 'udp') -def test_timer_object_tcp(): +def test_timer_object_tcp() -> None: """TCPStatsClient.timer works.""" cl = _tcp_client() _test_timer_object(cl, 'tcp') -def _test_timer_object_no_send(cl, proto): +def _test_timer_object_no_send(cl, proto) -> None: t = cl.timer('foo').start() t.stop(send=False) _sock_check(cl._sock, 0, proto) @@ -689,19 +690,19 @@ def _test_timer_object_no_send(cl, proto): _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_timer_object_no_send_udp(): +def test_timer_object_no_send_udp() -> None: """Stop StatsClient.timer without sending.""" cl = _udp_client() _test_timer_object_no_send(cl, 'udp') -def test_timer_object_no_send_tcp(): +def test_timer_object_no_send_tcp() -> None: """Stop TCPStatsClient.timer without sending.""" cl = _tcp_client() _test_timer_object_no_send(cl, 'tcp') -def _test_timer_object_rate(cl, proto): +def _test_timer_object_rate(cl, proto) -> None: t = cl.timer('foo', rate=0.5) t.start() t.stop() @@ -710,20 +711,20 @@ def _test_timer_object_rate(cl, proto): @mock.patch.object(random, 'random', lambda: -1) -def test_timer_object_rate_udp(): +def test_timer_object_rate_udp() -> None: """StatsClient.timer works with rate.""" cl = _udp_client() _test_timer_object_rate(cl, 'udp') @mock.patch.object(random, 'random', lambda: -1) -def test_timer_object_rate_tcp(): +def test_timer_object_rate_tcp() -> None: """TCPStatsClient.timer works with rate.""" cl = _tcp_client() _test_timer_object_rate(cl, 'tcp') -def _test_timer_object_no_send_twice(cl): +def _test_timer_object_no_send_twice(cl) -> None: t = cl.timer('foo').start() t.stop() @@ -731,19 +732,19 @@ def _test_timer_object_no_send_twice(cl): t.send() -def test_timer_object_no_send_twice_udp(): +def test_timer_object_no_send_twice_udp() -> None: """StatsClient.timer raises RuntimeError if send is called twice.""" cl = _udp_client() _test_timer_object_no_send_twice(cl) -def test_timer_object_no_send_twice_tcp(): +def test_timer_object_no_send_twice_tcp() -> None: """TCPStatsClient.timer raises RuntimeError if send is called twice.""" cl = _tcp_client() _test_timer_object_no_send_twice(cl) -def _test_timer_send_without_stop(cl): +def _test_timer_send_without_stop(cl) -> None: with cl.timer('foo') as t: assert t.ms is None with assert_raises(RuntimeError): @@ -755,36 +756,36 @@ def _test_timer_send_without_stop(cl): t.send() -def test_timer_send_without_stop_udp(): +def test_timer_send_without_stop_udp() -> None: """StatsClient.timer raises error if send is called before stop.""" cl = _udp_client() _test_timer_send_without_stop(cl) -def test_timer_send_without_stop_tcp(): +def test_timer_send_without_stop_tcp() -> None: """TCPStatsClient.timer raises error if send is called before stop.""" cl = _tcp_client() _test_timer_send_without_stop(cl) -def _test_timer_object_stop_without_start(cl): +def _test_timer_object_stop_without_start(cl) -> None: with assert_raises(RuntimeError): cl.timer('foo').stop() -def test_timer_object_stop_without_start_udp(): +def test_timer_object_stop_without_start_udp() -> None: """StatsClient.timer raises error if stop is called before start.""" cl = _udp_client() _test_timer_object_stop_without_start(cl) -def test_timer_object_stop_without_start_tcp(): +def test_timer_object_stop_without_start_tcp() -> None: """TCPStatsClient.timer raises error if stop is called before start.""" cl = _tcp_client() _test_timer_object_stop_without_start(cl) -def _test_pipeline(cl, proto): +def _test_pipeline(cl, proto) -> None: pipe = cl.pipeline() pipe.incr('foo') pipe.decr('bar') @@ -793,37 +794,37 @@ def _test_pipeline(cl, proto): _sock_check(cl._sock, 1, proto, 'foo:1|c\nbar:-1|c\nbaz:320.000000|ms') -def test_pipeline_udp(): +def test_pipeline_udp() -> None: """StatsClient.pipeline works.""" cl = _udp_client() _test_pipeline(cl, 'udp') -def test_pipeline_tcp(): +def test_pipeline_tcp() -> None: """TCPStatsClient.pipeline works.""" cl = _tcp_client() _test_pipeline(cl, 'tcp') -def _test_pipeline_null(cl, proto): +def _test_pipeline_null(cl, proto) -> None: pipe = cl.pipeline() pipe.send() _sock_check(cl._sock, 0, proto) -def test_pipeline_null_udp(): +def test_pipeline_null_udp() -> None: """Ensure we don't error on an empty pipeline (UDP).""" cl = _udp_client() _test_pipeline_null(cl, 'udp') -def test_pipeline_null_tcp(): +def test_pipeline_null_tcp() -> None: """Ensure we don't error on an empty pipeline (TCP).""" cl = _tcp_client() _test_pipeline_null(cl, 'tcp') -def _test_pipeline_manager(cl, proto): +def _test_pipeline_manager(cl, proto) -> None: with cl.pipeline() as pipe: pipe.incr('foo') pipe.decr('bar') @@ -831,59 +832,59 @@ def _test_pipeline_manager(cl, proto): _sock_check(cl._sock, 1, proto, 'foo:1|c\nbar:-1|c\nbaz:15|g') -def test_pipeline_manager_udp(): +def test_pipeline_manager_udp() -> None: """StatsClient.pipeline can be used as manager.""" cl = _udp_client() _test_pipeline_manager(cl, 'udp') -def test_pipeline_manager_tcp(): +def test_pipeline_manager_tcp() -> None: """TCPStatsClient.pipeline can be used as manager.""" cl = _tcp_client() _test_pipeline_manager(cl, 'tcp') -def _test_pipeline_timer_manager(cl, proto): +def _test_pipeline_timer_manager(cl, proto) -> None: with cl.pipeline() as pipe: with pipe.timer('foo'): pass _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_pipeline_timer_manager_udp(): +def test_pipeline_timer_manager_udp() -> None: """Timer manager can be retrieve from UDP Pipeline manager.""" cl = _udp_client() _test_pipeline_timer_manager(cl, 'udp') -def test_pipeline_timer_manager_tcp(): +def test_pipeline_timer_manager_tcp() -> None: """Timer manager can be retrieve from TCP Pipeline manager.""" cl = _tcp_client() _test_pipeline_timer_manager(cl, 'tcp') -def _test_pipeline_timer_decorator(cl, proto): +def _test_pipeline_timer_decorator(cl, proto) -> None: with cl.pipeline() as pipe: @pipe.timer('foo') - def foo(): + def foo() -> None: pass foo() _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_pipeline_timer_decorator_udp(): +def test_pipeline_timer_decorator_udp() -> None: """UDP Pipeline manager can be used as decorator.""" cl = _udp_client() _test_pipeline_timer_decorator(cl, 'udp') -def test_pipeline_timer_decorator_tcp(): +def test_pipeline_timer_decorator_tcp() -> None: """TCP Pipeline manager can be used as decorator.""" cl = _tcp_client() _test_pipeline_timer_decorator(cl, 'tcp') -def _test_pipeline_timer_object(cl, proto): +def _test_pipeline_timer_object(cl, proto) -> None: with cl.pipeline() as pipe: t = pipe.timer('foo').start() t.stop() @@ -891,57 +892,57 @@ def _test_pipeline_timer_object(cl, proto): _timer_check(cl._sock, 1, proto, 'foo', 'ms') -def test_pipeline_timer_object_udp(): +def test_pipeline_timer_object_udp() -> None: """Timer from UDP Pipeline manager works.""" cl = _udp_client() _test_pipeline_timer_object(cl, 'udp') -def test_pipeline_timer_object_tcp(): +def test_pipeline_timer_object_tcp() -> None: """Timer from TCP Pipeline manager works.""" cl = _tcp_client() _test_pipeline_timer_object(cl, 'tcp') -def _test_pipeline_empty(cl): +def _test_pipeline_empty(cl) -> None: with cl.pipeline() as pipe: pipe.incr('foo') eq_(1, len(pipe._stats)) eq_(0, len(pipe._stats)) -def test_pipeline_empty_udp(): +def test_pipeline_empty_udp() -> None: """Pipelines should be empty after a send() call (UDP).""" cl = _udp_client() _test_pipeline_empty(cl) -def test_pipeline_empty_tcp(): +def test_pipeline_empty_tcp() -> None: """Pipelines should be empty after a send() call (TCP).""" cl = _tcp_client() _test_pipeline_empty(cl) -def _test_pipeline_negative_absolute_gauge(cl, proto): +def _test_pipeline_negative_absolute_gauge(cl, proto) -> None: with cl.pipeline() as pipe: pipe.gauge('foo', -10, delta=False) pipe.incr('bar') _sock_check(cl._sock, 1, proto, 'foo:0|g\nfoo:-10|g\nbar:1|c') -def test_pipeline_negative_absolute_gauge_udp(): +def test_pipeline_negative_absolute_gauge_udp() -> None: """Negative absolute gauges use an internal pipeline (UDP).""" cl = _udp_client() _test_pipeline_negative_absolute_gauge(cl, 'udp') -def test_pipeline_negative_absolute_gauge_tcp(): +def test_pipeline_negative_absolute_gauge_tcp() -> None: """Negative absolute gauges use an internal pipeline (TCP).""" cl = _tcp_client() _test_pipeline_negative_absolute_gauge(cl, 'tcp') -def _test_big_numbers(cl, proto): +def _test_big_numbers(cl, proto) -> None: num = 1234568901234 tests = ( # Explicitly create strings so we avoid the bug we're trying to test. @@ -950,7 +951,7 @@ def _test_big_numbers(cl, proto): ('timing', 'foo:1234568901234.000000|ms'), ) - def _check(method, result): + def _check(method, result) -> None: cl._sock.reset_mock() getattr(cl, method)('foo', num) _sock_check(cl._sock, 1, proto, result) @@ -959,38 +960,38 @@ def _check(method, result): _check(method, result) -def test_big_numbers_udp(): +def test_big_numbers_udp() -> None: """Test big numbers with UDP client.""" cl = _udp_client() _test_big_numbers(cl, 'udp') -def test_big_numbers_tcp(): +def test_big_numbers_tcp() -> None: """Test big numbers with TCP client.""" cl = _tcp_client() _test_big_numbers(cl, 'tcp') -def _test_rate_no_send(cl, proto): +def _test_rate_no_send(cl, proto) -> None: cl.incr('foo', rate=0.5) _sock_check(cl._sock, 0, proto) @mock.patch.object(random, 'random', lambda: 2) -def test_rate_no_send_udp(): +def test_rate_no_send_udp() -> None: """Rate below random value prevents sending with StatsClient.incr.""" cl = _udp_client() _test_rate_no_send(cl, 'udp') @mock.patch.object(random, 'random', lambda: 2) -def test_rate_no_send_tcp(): +def test_rate_no_send_tcp() -> None: """Rate below random value prevents sending with TCPStatsClient.incr.""" cl = _tcp_client() _test_rate_no_send(cl, 'tcp') -def test_socket_error(): +def test_socket_error() -> None: """Socket error on StatsClient should be ignored.""" cl = _udp_client() cl._sock.sendto.side_effect = socket.timeout() @@ -998,7 +999,7 @@ def test_socket_error(): _sock_check(cl._sock, 1, 'udp', 'foo:1|c') -def test_pipeline_packet_size(): +def test_pipeline_packet_size() -> None: """Pipelines shouldn't send packets larger than 512 bytes (UDP only).""" sc = _udp_client() pipe = sc.pipeline() @@ -1012,7 +1013,7 @@ def test_pipeline_packet_size(): @mock.patch.object(socket, 'socket') -def test_tcp_raises_exception_to_user(mock_socket): +def test_tcp_raises_exception_to_user(mock_socket) -> None: """Socket errors in TCPStatsClient should be raised to user.""" addr = ('127.0.0.1', 1234) cl = _tcp_client(addr=addr[0], port=addr[1]) @@ -1024,18 +1025,20 @@ def test_tcp_raises_exception_to_user(mock_socket): @mock.patch.object(socket, 'socket') -def test_tcp_timeout(mock_socket): +def test_tcp_timeout(mock_socket) -> None: """Timeout on TCPStatsClient should be set on socket.""" test_timeout = 321 cl = TCPStatsClient(timeout=test_timeout) cl.incr('foo') + assert cl._sock is not None cl._sock.settimeout.assert_called_once_with(test_timeout) @mock.patch.object(socket, 'socket') -def test_unix_socket_timeout(mock_socket): +def test_unix_socket_timeout(mock_socket) -> None: """Timeout on UnixSocketStatsClient should be set on socket.""" test_timeout = 321 cl = UnixSocketStatsClient(UNIX_SOCKET, timeout=test_timeout) cl.incr('foo') + assert cl._sock is not None cl._sock.settimeout.assert_called_once_with(test_timeout) diff --git a/tox.ini b/tox.ini index f75a9787b..1731ded49 100644 --- a/tox.ini +++ b/tox.ini @@ -2,10 +2,16 @@ envlist = py27,pypy,py34,py35,py36,py37 [testenv] -deps= - mock==1.0.1 - nose==1.2.1 - coverage==3.5.2 - -commands= +deps = -r requirements.txt +commands = nosetests statsd --with-coverage --cover-package=statsd [] + +[testenv:mypy] +deps = -r requirements.txt +commands = + mypy statsd/ + +[testenv:flake8] +deps = -r requirements.txt +commands = + flake8 statsd/