Skip to content

ref: Type hints for init() #401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)
from sentry_sdk.serializer import Serializer
from sentry_sdk.transport import make_transport
from sentry_sdk.consts import DEFAULT_OPTIONS, SDK_INFO
from sentry_sdk.consts import DEFAULT_OPTIONS, SDK_INFO, ClientConstructor
from sentry_sdk.integrations import setup_integrations
from sentry_sdk.utils import ContextVar

Expand All @@ -24,17 +24,16 @@
from typing import Dict
from typing import Optional

from sentry_sdk.consts import ClientOptions
from sentry_sdk.scope import Scope
from sentry_sdk.utils import Event, Hint


_client_init_debug = ContextVar("client_init_debug")


def get_options(*args, **kwargs):
# type: (*str, **ClientOptions) -> ClientOptions
if args and (isinstance(args[0], string_types) or args[0] is None):
def _get_options(*args, **kwargs):
# type: (*Optional[str], **Any) -> Dict[str, Any]
if args and (isinstance(args[0], str) or args[0] is None):
dsn = args[0] # type: Optional[str]
args = args[1:]
else:
Expand Down Expand Up @@ -62,18 +61,18 @@ def get_options(*args, **kwargs):
return rv # type: ignore


class Client(object):
class _Client(object):
"""The client is internally responsible for capturing the events and
forwarding them to sentry through the configured transport. It takes
the client options as keyword arguments and optionally the DSN as first
argument.
"""

def __init__(self, *args, **kwargs):
# type: (*str, **ClientOptions) -> None
# type: (*Optional[str], **Any) -> None
old_debug = _client_init_debug.get(False)
try:
self.options = options = get_options(*args, **kwargs)
self.options = options = get_options(*args, **kwargs) # type: ignore
_client_init_debug.set(options["debug"])
self.transport = make_transport(options)

Expand Down Expand Up @@ -261,9 +260,33 @@ def flush(self, timeout=None, callback=None):
self.transport.flush(timeout=timeout, callback=callback)

def __enter__(self):
# type: () -> Client
# type: () -> _Client
return self

def __exit__(self, exc_type, exc_value, tb):
# type: (Any, Any, Any) -> None
self.close()


if MYPY:
# Make mypy, PyCharm and other static analyzers think `get_options` is a
# type to have nicer autocompletion for params.
#
# Use `ClientConstructor` to define the argument types of `init` and
# `Dict[str, Any]` to tell static analyzers about the return type.

class get_options(ClientConstructor, Dict[str, Any]):
pass

class Client(ClientConstructor, _Client):
pass


else:
# Alias `get_options` for actual usage. Go through the lambda indirection
# to throw PyCharm off of the weakly typed signature (it would otherwise
# discover both the weakly typed signature of `_init` and our faked `init`
# type).

get_options = (lambda: _get_options)()
Client = (lambda: _Client)()
113 changes: 50 additions & 63 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,71 @@

MYPY = False
if MYPY:
from mypy_extensions import TypedDict
from typing import Optional
from typing import Callable
from typing import Union
from typing import List
from typing import Type
from typing import Dict
from typing import Any

from sentry_sdk.transport import Transport
from sentry_sdk.integrations import Integration

from sentry_sdk.utils import Event, EventProcessor, BreadcrumbProcessor

ClientOptions = TypedDict(
"ClientOptions",
{
"dsn": Optional[str],
"with_locals": bool,
"max_breadcrumbs": int,
"release": Optional[str],
"environment": Optional[str],
"server_name": Optional[str],
"shutdown_timeout": int,
"integrations": List[Integration],
"in_app_include": List[str],
"in_app_exclude": List[str],
"default_integrations": bool,
"dist": Optional[str],
"transport": Optional[
Union[Transport, Type[Transport], Callable[[Event], None]]
],
"sample_rate": int,
"send_default_pii": bool,
"http_proxy": Optional[str],
"https_proxy": Optional[str],
"ignore_errors": List[Union[type, str]],
"request_bodies": str,
"before_send": Optional[EventProcessor],
"before_breadcrumb": Optional[BreadcrumbProcessor],
"debug": bool,
"attach_stacktrace": bool,
"ca_certs": Optional[str],
"propagate_traces": bool,
},
total=False,
)


VERSION = "0.9.2"
DEFAULT_SERVER_NAME = socket.gethostname() if hasattr(socket, "gethostname") else None
DEFAULT_OPTIONS = {
"dsn": None,
"with_locals": True,
"max_breadcrumbs": 100,
"release": None,
"environment": None,
"server_name": DEFAULT_SERVER_NAME,
"shutdown_timeout": 2.0,
"integrations": [],
"in_app_include": [],
"in_app_exclude": [],
"default_integrations": True,
"dist": None,
"transport": None,
"sample_rate": 1.0,
"send_default_pii": False,
"http_proxy": None,
"https_proxy": None,
"ignore_errors": [],
"request_bodies": "medium",
"before_send": None,
"before_breadcrumb": None,
"debug": False,
"attach_stacktrace": False,
"ca_certs": None,
"propagate_traces": True,
}


# This type exists to trick mypy and PyCharm into thinking `init` and `Client`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trick huh?

# take these arguments (even though they take opaque **kwargs)
class ClientConstructor(object):
def __init__(
self,
dsn=None, # type: Optional[str]
with_locals=True, # type: bool
max_breadcrumbs=100, # type: int
release=None, # type: Optional[str]
environment=None, # type: Optional[str]
server_name=DEFAULT_SERVER_NAME, # type: Optional[str]
shutdown_timeout=2, # type: int
integrations=[], # type: List[Integration]
in_app_include=[], # type: List[str]
in_app_exclude=[], # type: List[str]
default_integrations=True, # type: bool
dist=None, # type: Optional[str]
transport=None, # type: Optional[Union[Transport, Type[Transport], Callable[[Event], None]]]
sample_rate=1.0, # type: float
send_default_pii=False, # type: bool
http_proxy=None, # type: Optional[str]
https_proxy=None, # type: Optional[str]
ignore_errors=[], # type: List[Union[type, str]]
request_bodies="medium", # type: str
before_send=None, # type: Optional[EventProcessor]
before_breadcrumb=None, # type: Optional[BreadcrumbProcessor]
debug=False, # type: bool
attach_stacktrace=False, # type: bool
ca_certs=None, # type: Optional[str]
propagate_traces=True, # type: bool
):
# type: (...) -> None
pass


def _get_default_options():
# type: () -> Dict[str, Any]
import inspect

a = inspect.getargspec(ClientConstructor.__init__)
return dict(zip(a.args[-len(a.defaults) :], a.defaults))


DEFAULT_OPTIONS = _get_default_options()
del _get_default_options


VERSION = "0.9.2"
SDK_INFO = {
"name": "sentry.python",
"version": VERSION,
Expand Down
28 changes: 24 additions & 4 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@

from sentry_sdk.integrations import Integration
from sentry_sdk.utils import Event, Hint, Breadcrumb, BreadcrumbHint
from sentry_sdk.consts import ClientConstructor

T = TypeVar("T")

else:

def overload(x):
Expand Down Expand Up @@ -71,22 +73,40 @@ def __exit__(self, exc_type, exc_value, tb):
c.close()


def init(*args, **kwargs):
# type: (*str, **Any) -> ContextManager[Any]
# TODO: https://github.com/getsentry/sentry-python/issues/272
def _init(*args, **kwargs):
# type: (*Optional[str], **Any) -> ContextManager[Any]
"""Initializes the SDK and optionally integrations.

This takes the same arguments as the client constructor.
"""
global _initial_client
client = Client(*args, **kwargs)
client = Client(*args, **kwargs) # type: ignore
Hub.current.bind_client(client)
rv = _InitGuard(client)
if client is not None:
_initial_client = weakref.ref(client)
return rv


if MYPY:
# Make mypy, PyCharm and other static analyzers think `init` is a type to
# have nicer autocompletion for params.
#
# Use `ClientConstructor` to define the argument types of `init` and
# `ContextManager[Any]` to tell static analyzers about the return type.

class init(ClientConstructor, ContextManager[Any]):
pass


else:
# Alias `init` for actual usage. Go through the lambda indirection to throw
# PyCharm off of the weakly typed signature (it would otherwise discover
# both the weakly typed signature of `_init` and our faked `init` type).

init = (lambda: _init)()


class HubMeta(type):
@property
def current(self):
Expand Down
9 changes: 4 additions & 5 deletions sentry_sdk/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

MYPY = False
if MYPY:
from sentry_sdk.consts import ClientOptions
from typing import Type
from typing import Any
from typing import Optional
Expand All @@ -41,7 +40,7 @@ class Transport(object):
parsed_dsn = None # type: Optional[Dsn]

def __init__(self, options=None):
# type: (Optional[ClientOptions]) -> None
# type: (Optional[Dict[str, Any]]) -> None
self.options = options
if options and options["dsn"] is not None and options["dsn"]:
self.parsed_dsn = Dsn(options["dsn"])
Expand Down Expand Up @@ -77,7 +76,7 @@ class HttpTransport(Transport):
"""The default HTTP transport."""

def __init__(self, options):
# type: (ClientOptions) -> None
# type: (Dict[str, Any]) -> None
Transport.__init__(self, options)
assert self.parsed_dsn is not None
self._worker = BackgroundWorker()
Expand Down Expand Up @@ -218,7 +217,7 @@ def capture_event(self, event):


def make_transport(options):
# type: (ClientOptions) -> Optional[Transport]
# type: (Dict[str, Any]) -> Optional[Transport]
ref_transport = options["transport"]

# If no transport is given, we use the http transport class
Expand All @@ -229,7 +228,7 @@ def make_transport(options):
elif isinstance(ref_transport, type) and issubclass(ref_transport, Transport):
transport_cls = ref_transport
elif callable(ref_transport):
return _FunctionTransport(ref_transport)
return _FunctionTransport(ref_transport) # type: ignore

# if a transport class is given only instanciate it if the dsn is not
# empty or None
Expand Down
7 changes: 3 additions & 4 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from typing import Type
from typing import Union

from sentry_sdk.consts import ClientOptions
from sentry_sdk.hub import Hub

ExcInfo = Tuple[
Expand Down Expand Up @@ -444,7 +443,7 @@ def single_exception_from_error_tuple(
exc_type, # type: Optional[type]
exc_value, # type: Optional[BaseException]
tb, # type: Optional[Any]
client_options=None, # type: Optional[ClientOptions]
client_options=None, # type: Optional[dict]
mechanism=None, # type: Optional[Dict[str, Any]]
):
# type: (...) -> Dict[str, Any]
Expand Down Expand Up @@ -517,7 +516,7 @@ def walk_exception_chain(exc_info):

def exceptions_from_error_tuple(
exc_info, # type: ExcInfo
client_options=None, # type: Optional[ClientOptions]
client_options=None, # type: Optional[dict]
mechanism=None, # type: Optional[Dict[str, Any]]
):
# type: (...) -> List[Dict[str, Any]]
Expand Down Expand Up @@ -630,7 +629,7 @@ def exc_info_from_error(error):

def event_from_exception(
exc_info, # type: Union[BaseException, ExcInfo]
client_options=None, # type: Optional[ClientOptions]
client_options=None, # type: Optional[dict]
mechanism=None, # type: Optional[Dict[str, Any]]
):
# type: (...) -> Tuple[Dict[str, Any], Dict[str, Any]]
Expand Down