prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Not working with older boto version
  Hello, we use it in Django==2.1.7 app and this row breaks the app.
  
  https://github.com/getsentry/sentry-python/blob/cc08a6bed116e09db41c712c20ab63eb0a839e41/sentry_sdk/integrations/boto3.py#L36
  
  For versions
  boto3==1.7.45
  botocore==1.10.84
  
  this throws
  `
  AttributeError: 'str' object has no attribute 'hyphenize'`
  
  I'm not sure the base of the integrations but I thought they must be enabled in settings, but this part of Boto3Integration is triggered even if we have not enabled it in django settings.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `sentry_sdk/integrations/boto3.py`
  Content:
  ```
  1 from __future__ import absolute_import
  2 
  3 from sentry_sdk import Hub
  4 from sentry_sdk.integrations import Integration, DidNotEnable
  5 from sentry_sdk.tracing import Span
  6 
  7 from sentry_sdk._functools import partial
  8 from sentry_sdk._types import MYPY
  9 
  10 if MYPY:
  11     from typing import Any
  12     from typing import Dict
  13     from typing import Optional
  14     from typing import Type
  15 
  16 try:
  17     from botocore.client import BaseClient  # type: ignore
  18     from botocore.response import StreamingBody  # type: ignore
  19     from botocore.awsrequest import AWSRequest  # type: ignore
  20 except ImportError:
  21     raise DidNotEnable("botocore is not installed")
  22 
  23 
  24 class Boto3Integration(Integration):
  25     identifier = "boto3"
  26 
  27     @staticmethod
  28     def setup_once():
  29         # type: () -> None
  30         orig_init = BaseClient.__init__
  31 
  32         def sentry_patched_init(self, *args, **kwargs):
  33             # type: (Type[BaseClient], *Any, **Any) -> None
  34             orig_init(self, *args, **kwargs)
  35             meta = self.meta
  36             service_id = meta.service_model.service_id.hyphenize()
  37             meta.events.register(
  38                 "request-created",
  39                 partial(_sentry_request_created, service_id=service_id),
  40             )
  41             meta.events.register("after-call", _sentry_after_call)
  42             meta.events.register("after-call-error", _sentry_after_call_error)
  43 
  44         BaseClient.__init__ = sentry_patched_init
  45 
  46 
  47 def _sentry_request_created(service_id, request, operation_name, **kwargs):
  48     # type: (str, AWSRequest, str, **Any) -> None
  49     hub = Hub.current
  50     if hub.get_integration(Boto3Integration) is None:
  51         return
  52 
  53     description = "aws.%s.%s" % (service_id, operation_name)
  54     span = hub.start_span(
  55         hub=hub,
  56         op="aws.request",
  57         description=description,
  58     )
  59     span.set_tag("aws.service_id", service_id)
  60     span.set_tag("aws.operation_name", operation_name)
  61     span.set_data("aws.request.url", request.url)
  62 
  63     # We do it in order for subsequent http calls/retries be
  64     # attached to this span.
  65     span.__enter__()
  66 
  67     # request.context is an open-ended data-structure
  68     # where we can add anything useful in request life cycle.
  69     request.context["_sentrysdk_span"] = span
  70 
  71 
  72 def _sentry_after_call(context, parsed, **kwargs):
  73     # type: (Dict[str, Any], Dict[str, Any], **Any) -> None
  74     span = context.pop("_sentrysdk_span", None)  # type: Optional[Span]
  75 
  76     # Span could be absent if the integration is disabled.
  77     if span is None:
  78         return
  79     span.__exit__(None, None, None)
  80 
  81     body = parsed.get("Body")
  82     if not isinstance(body, StreamingBody):
  83         return
  84 
  85     streaming_span = span.start_child(
  86         op="aws.request.stream",
  87         description=span.description,
  88     )
  89 
  90     orig_read = body.read
  91     orig_close = body.close
  92 
  93     def sentry_streaming_body_read(*args, **kwargs):
  94         # type: (*Any, **Any) -> bytes
  95         try:
  96             ret = orig_read(*args, **kwargs)
  97             if not ret:
  98                 streaming_span.finish()
  99             return ret
  100         except Exception:
  101             streaming_span.finish()
  102             raise
  103 
  104     body.read = sentry_streaming_body_read
  105 
  106     def sentry_streaming_body_close(*args, **kwargs):
  107         # type: (*Any, **Any) -> None
  108         streaming_span.finish()
  109         orig_close(*args, **kwargs)
  110 
  111     body.close = sentry_streaming_body_close
  112 
  113 
  114 def _sentry_after_call_error(context, exception, **kwargs):
  115     # type: (Dict[str, Any], Type[BaseException], **Any) -> None
  116     span = context.pop("_sentrysdk_span", None)  # type: Optional[Span]
  117 
  118     # Span could be absent if the integration is disabled.
  119     if span is None:
  120         return
  121     span.__exit__(type(exception), exception, None)
  122
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/sentry_sdk/integrations/boto3.py b/sentry_sdk/integrations/boto3.py
  --- a/sentry_sdk/integrations/boto3.py
  +++ b/sentry_sdk/integrations/boto3.py
  @@ -14,6 +14,7 @@
       from typing import Type
   
   try:
  +    from botocore import __version__ as BOTOCORE_VERSION  # type: ignore
       from botocore.client import BaseClient  # type: ignore
       from botocore.response import StreamingBody  # type: ignore
       from botocore.awsrequest import AWSRequest  # type: ignore
  @@ -27,6 +28,14 @@
       @staticmethod
       def setup_once():
           # type: () -> None
  +        try:
  +            version = tuple(map(int, BOTOCORE_VERSION.split(".")[:3]))
  +        except (ValueError, TypeError):
  +            raise DidNotEnable(
  +                "Unparsable botocore version: {}".format(BOTOCORE_VERSION)
  +            )
  +        if version < (1, 12):
  +            raise DidNotEnable("Botocore 1.12 or newer is required.")
           orig_init = BaseClient.__init__
   
           def sentry_patched_init(self, *args, **kwargs):
  
verification_info: {"golden_diff": "diff --git a/sentry_sdk/integrations/boto3.py b/sentry_sdk/integrations/boto3.py\n--- a/sentry_sdk/integrations/boto3.py\n+++ b/sentry_sdk/integrations/boto3.py\n@@ -14,6 +14,7 @@\n     from typing import Type\n \n try:\n+    from botocore import __version__ as BOTOCORE_VERSION  # type: ignore\n     from botocore.client import BaseClient  # type: ignore\n     from botocore.response import StreamingBody  # type: ignore\n     from botocore.awsrequest import AWSRequest  # type: ignore\n@@ -27,6 +28,14 @@\n     @staticmethod\n     def setup_once():\n         # type: () -> None\n+        try:\n+            version = tuple(map(int, BOTOCORE_VERSION.split(\".\")[:3]))\n+        except (ValueError, TypeError):\n+            raise DidNotEnable(\n+                \"Unparsable botocore version: {}\".format(BOTOCORE_VERSION)\n+            )\n+        if version < (1, 12):\n+            raise DidNotEnable(\"Botocore 1.12 or newer is required.\")\n         orig_init = BaseClient.__init__\n \n         def sentry_patched_init(self, *args, **kwargs):\n", "issue": "Not working with older boto version\nHello, we use it in Django==2.1.7 app and this row breaks the app.\r\n\r\nhttps://github.com/getsentry/sentry-python/blob/cc08a6bed116e09db41c712c20ab63eb0a839e41/sentry_sdk/integrations/boto3.py#L36\r\n\r\nFor versions\r\nboto3==1.7.45\r\nbotocore==1.10.84\r\n\r\nthis throws\r\n`\r\nAttributeError: 'str' object has no attribute 'hyphenize'`\r\n\r\nI'm not sure the base of the integrations but I thought they must be enabled in settings, but this part of Boto3Integration is triggered even if we have not enabled it in django settings.\r\n\n", "before_files": [{"content": "from __future__ import absolute_import\n\nfrom sentry_sdk import Hub\nfrom sentry_sdk.integrations import Integration, DidNotEnable\nfrom sentry_sdk.tracing import Span\n\nfrom sentry_sdk._functools import partial\nfrom sentry_sdk._types import MYPY\n\nif MYPY:\n    from typing import Any\n    from typing import Dict\n    from typing import Optional\n    from typing import Type\n\ntry:\n    from botocore.client import BaseClient  # type: ignore\n    from botocore.response import StreamingBody  # type: ignore\n    from botocore.awsrequest import AWSRequest  # type: ignore\nexcept ImportError:\n    raise DidNotEnable(\"botocore is not installed\")\n\n\nclass Boto3Integration(Integration):\n    identifier = \"boto3\"\n\n    @staticmethod\n    def setup_once():\n        # type: () -> None\n        orig_init = BaseClient.__init__\n\n        def sentry_patched_init(self, *args, **kwargs):\n            # type: (Type[BaseClient], *Any, **Any) -> None\n            orig_init(self, *args, **kwargs)\n            meta = self.meta\n            service_id = meta.service_model.service_id.hyphenize()\n            meta.events.register(\n                \"request-created\",\n                partial(_sentry_request_created, service_id=service_id),\n            )\n            meta.events.register(\"after-call\", _sentry_after_call)\n            meta.events.register(\"after-call-error\", _sentry_after_call_error)\n\n        BaseClient.__init__ = sentry_patched_init\n\n\ndef _sentry_request_created(service_id, request, operation_name, **kwargs):\n    # type: (str, AWSRequest, str, **Any) -> None\n    hub = Hub.current\n    if hub.get_integration(Boto3Integration) is None:\n        return\n\n    description = \"aws.%s.%s\" % (service_id, operation_name)\n    span = hub.start_span(\n        hub=hub,\n        op=\"aws.request\",\n        description=description,\n    )\n    span.set_tag(\"aws.service_id\", service_id)\n    span.set_tag(\"aws.operation_name\", operation_name)\n    span.set_data(\"aws.request.url\", request.url)\n\n    # We do it in order for subsequent http calls/retries be\n    # attached to this span.\n    span.__enter__()\n\n    # request.context is an open-ended data-structure\n    # where we can add anything useful in request life cycle.\n    request.context[\"_sentrysdk_span\"] = span\n\n\ndef _sentry_after_call(context, parsed, **kwargs):\n    # type: (Dict[str, Any], Dict[str, Any], **Any) -> None\n    span = context.pop(\"_sentrysdk_span\", None)  # type: Optional[Span]\n\n    # Span could be absent if the integration is disabled.\n    if span is None:\n        return\n    span.__exit__(None, None, None)\n\n    body = parsed.get(\"Body\")\n    if not isinstance(body, StreamingBody):\n        return\n\n    streaming_span = span.start_child(\n        op=\"aws.request.stream\",\n        description=span.description,\n    )\n\n    orig_read = body.read\n    orig_close = body.close\n\n    def sentry_streaming_body_read(*args, **kwargs):\n        # type: (*Any, **Any) -> bytes\n        try:\n            ret = orig_read(*args, **kwargs)\n            if not ret:\n                streaming_span.finish()\n            return ret\n        except Exception:\n            streaming_span.finish()\n            raise\n\n    body.read = sentry_streaming_body_read\n\n    def sentry_streaming_body_close(*args, **kwargs):\n        # type: (*Any, **Any) -> None\n        streaming_span.finish()\n        orig_close(*args, **kwargs)\n\n    body.close = sentry_streaming_body_close\n\n\ndef _sentry_after_call_error(context, exception, **kwargs):\n    # type: (Dict[str, Any], Type[BaseException], **Any) -> None\n    span = context.pop(\"_sentrysdk_span\", None)  # type: Optional[Span]\n\n    # Span could be absent if the integration is disabled.\n    if span is None:\n        return\n    span.__exit__(type(exception), exception, None)\n", "path": "sentry_sdk/integrations/boto3.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nfrom sentry_sdk import Hub\nfrom sentry_sdk.integrations import Integration, DidNotEnable\nfrom sentry_sdk.tracing import Span\n\nfrom sentry_sdk._functools import partial\nfrom sentry_sdk._types import MYPY\n\nif MYPY:\n    from typing import Any\n    from typing import Dict\n    from typing import Optional\n    from typing import Type\n\ntry:\n    from botocore import __version__ as BOTOCORE_VERSION  # type: ignore\n    from botocore.client import BaseClient  # type: ignore\n    from botocore.response import StreamingBody  # type: ignore\n    from botocore.awsrequest import AWSRequest  # type: ignore\nexcept ImportError:\n    raise DidNotEnable(\"botocore is not installed\")\n\n\nclass Boto3Integration(Integration):\n    identifier = \"boto3\"\n\n    @staticmethod\n    def setup_once():\n        # type: () -> None\n        try:\n            version = tuple(map(int, BOTOCORE_VERSION.split(\".\")[:3]))\n        except (ValueError, TypeError):\n            raise DidNotEnable(\n                \"Unparsable botocore version: {}\".format(BOTOCORE_VERSION)\n            )\n        if version < (1, 12):\n            raise DidNotEnable(\"Botocore 1.12 or newer is required.\")\n        orig_init = BaseClient.__init__\n\n        def sentry_patched_init(self, *args, **kwargs):\n            # type: (Type[BaseClient], *Any, **Any) -> None\n            orig_init(self, *args, **kwargs)\n            meta = self.meta\n            service_id = meta.service_model.service_id.hyphenize()\n            meta.events.register(\n                \"request-created\",\n                partial(_sentry_request_created, service_id=service_id),\n            )\n            meta.events.register(\"after-call\", _sentry_after_call)\n            meta.events.register(\"after-call-error\", _sentry_after_call_error)\n\n        BaseClient.__init__ = sentry_patched_init\n\n\ndef _sentry_request_created(service_id, request, operation_name, **kwargs):\n    # type: (str, AWSRequest, str, **Any) -> None\n    hub = Hub.current\n    if hub.get_integration(Boto3Integration) is None:\n        return\n\n    description = \"aws.%s.%s\" % (service_id, operation_name)\n    span = hub.start_span(\n        hub=hub,\n        op=\"aws.request\",\n        description=description,\n    )\n    span.set_tag(\"aws.service_id\", service_id)\n    span.set_tag(\"aws.operation_name\", operation_name)\n    span.set_data(\"aws.request.url\", request.url)\n\n    # We do it in order for subsequent http calls/retries be\n    # attached to this span.\n    span.__enter__()\n\n    # request.context is an open-ended data-structure\n    # where we can add anything useful in request life cycle.\n    request.context[\"_sentrysdk_span\"] = span\n\n\ndef _sentry_after_call(context, parsed, **kwargs):\n    # type: (Dict[str, Any], Dict[str, Any], **Any) -> None\n    span = context.pop(\"_sentrysdk_span\", None)  # type: Optional[Span]\n\n    # Span could be absent if the integration is disabled.\n    if span is None:\n        return\n    span.__exit__(None, None, None)\n\n    body = parsed.get(\"Body\")\n    if not isinstance(body, StreamingBody):\n        return\n\n    streaming_span = span.start_child(\n        op=\"aws.request.stream\",\n        description=span.description,\n    )\n\n    orig_read = body.read\n    orig_close = body.close\n\n    def sentry_streaming_body_read(*args, **kwargs):\n        # type: (*Any, **Any) -> bytes\n        try:\n            ret = orig_read(*args, **kwargs)\n            if not ret:\n                streaming_span.finish()\n            return ret\n        except Exception:\n            streaming_span.finish()\n            raise\n\n    body.read = sentry_streaming_body_read\n\n    def sentry_streaming_body_close(*args, **kwargs):\n        # type: (*Any, **Any) -> None\n        streaming_span.finish()\n        orig_close(*args, **kwargs)\n\n    body.close = sentry_streaming_body_close\n\n\ndef _sentry_after_call_error(context, exception, **kwargs):\n    # type: (Dict[str, Any], Type[BaseException], **Any) -> None\n    span = context.pop(\"_sentrysdk_span\", None)  # type: Optional[Span]\n\n    # Span could be absent if the integration is disabled.\n    if span is None:\n        return\n    span.__exit__(type(exception), exception, None)\n", "path": "sentry_sdk/integrations/boto3.py"}]}