We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
sqlalchemy: TypeError: cannot create weak reference to 'pyodbc.Cursor' object
**Describe your environment** 
See the repo provided below for a simple reproduction environment. I was using Python 3.8.8 when running it locally.

**Steps to reproduce**
Provided a reproduction repo: https://github.com/jomasti/opentelemetry-instrumentation-sqlalchemy-pyodbc-bug

**What is the expected behavior?**
I expected the query to work successfully when the engine is instrumented. The code in the repo above works fine with 0.18b1.

**What is the actual behavior?**
Ran into `TypeError: cannot create weak reference to 'pyodbc.Cursor' object`.

**Additional context**
#315 appears to be the culprit.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 from threading import local
16 from weakref import WeakKeyDictionary
17 
18 from sqlalchemy.event import listen  # pylint: disable=no-name-in-module
19 
20 from opentelemetry import trace
21 from opentelemetry.instrumentation.sqlalchemy.version import __version__
22 from opentelemetry.semconv.trace import SpanAttributes
23 from opentelemetry.trace.status import Status, StatusCode
24 
25 
26 def _normalize_vendor(vendor):
27     """Return a canonical name for a type of database."""
28     if not vendor:
29         return "db"  # should this ever happen?
30 
31     if "sqlite" in vendor:
32         return "sqlite"
33 
34     if "postgres" in vendor or vendor == "psycopg2":
35         return "postgresql"
36 
37     return vendor
38 
39 
40 def _get_tracer(engine, tracer_provider=None):
41     return trace.get_tracer(
42         _normalize_vendor(engine.name),
43         __version__,
44         tracer_provider=tracer_provider,
45     )
46 
47 
48 # pylint: disable=unused-argument
49 def _wrap_create_engine(func, module, args, kwargs):
50     """Trace the SQLAlchemy engine, creating an `EngineTracer`
51     object that will listen to SQLAlchemy events.
52     """
53     engine = func(*args, **kwargs)
54     EngineTracer(_get_tracer(engine), engine)
55     return engine
56 
57 
58 class EngineTracer:
59     def __init__(self, tracer, engine):
60         self.tracer = tracer
61         self.engine = engine
62         self.vendor = _normalize_vendor(engine.name)
63         self.cursor_mapping = WeakKeyDictionary()
64         self.local = local()
65 
66         listen(engine, "before_cursor_execute", self._before_cur_exec)
67         listen(engine, "after_cursor_execute", self._after_cur_exec)
68         listen(engine, "handle_error", self._handle_error)
69 
70     @property
71     def current_thread_span(self):
72         return getattr(self.local, "current_span", None)
73 
74     @current_thread_span.setter
75     def current_thread_span(self, span):
76         setattr(self.local, "current_span", span)
77 
78     def _operation_name(self, db_name, statement):
79         parts = []
80         if isinstance(statement, str):
81             # otel spec recommends against parsing SQL queries. We are not trying to parse SQL
82             # but simply truncating the statement to the first word. This covers probably >95%
83             # use cases and uses the SQL statement in span name correctly as per the spec.
84             # For some very special cases it might not record the correct statement if the SQL
85             # dialect is too weird but in any case it shouldn't break anything.
86             parts.append(statement.split()[0])
87         if db_name:
88             parts.append(db_name)
89         if not parts:
90             return self.vendor
91         return " ".join(parts)
92 
93     # pylint: disable=unused-argument
94     def _before_cur_exec(self, conn, cursor, statement, *args):
95         attrs, found = _get_attributes_from_url(conn.engine.url)
96         if not found:
97             attrs = _get_attributes_from_cursor(self.vendor, cursor, attrs)
98 
99         db_name = attrs.get(SpanAttributes.DB_NAME, "")
100         span = self.tracer.start_span(
101             self._operation_name(db_name, statement),
102             kind=trace.SpanKind.CLIENT,
103         )
104         self.current_thread_span = self.cursor_mapping[cursor] = span
105         with trace.use_span(span, end_on_exit=False):
106             if span.is_recording():
107                 span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
108                 span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
109                 for key, value in attrs.items():
110                     span.set_attribute(key, value)
111 
112     # pylint: disable=unused-argument
113     def _after_cur_exec(self, conn, cursor, statement, *args):
114         span = self.cursor_mapping.get(cursor, None)
115         if span is None:
116             return
117 
118         span.end()
119 
120     def _handle_error(self, context):
121         span = self.current_thread_span
122         if span is None:
123             return
124 
125         try:
126             if span.is_recording():
127                 span.set_status(
128                     Status(StatusCode.ERROR, str(context.original_exception),)
129                 )
130         finally:
131             span.end()
132 
133 
134 def _get_attributes_from_url(url):
135     """Set connection tags from the url. return true if successful."""
136     attrs = {}
137     if url.host:
138         attrs[SpanAttributes.NET_PEER_NAME] = url.host
139     if url.port:
140         attrs[SpanAttributes.NET_PEER_PORT] = url.port
141     if url.database:
142         attrs[SpanAttributes.DB_NAME] = url.database
143     if url.username:
144         attrs[SpanAttributes.DB_USER] = url.username
145     return attrs, bool(url.host)
146 
147 
148 def _get_attributes_from_cursor(vendor, cursor, attrs):
149     """Attempt to set db connection attributes by introspecting the cursor."""
150     if vendor == "postgresql":
151         # pylint: disable=import-outside-toplevel
152         from psycopg2.extensions import parse_dsn
153 
154         if hasattr(cursor, "connection") and hasattr(cursor.connection, "dsn"):
155             dsn = getattr(cursor.connection, "dsn", None)
156             if dsn:
157                 data = parse_dsn(dsn)
158                 attrs[SpanAttributes.DB_NAME] = data.get("dbname")
159                 attrs[SpanAttributes.NET_PEER_NAME] = data.get("host")
160                 attrs[SpanAttributes.NET_PEER_PORT] = int(data.get("port"))
161     return attrs
162
```
--- 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()

```

diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py
--- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py
+++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 from threading import local
-from weakref import WeakKeyDictionary
 
 from sqlalchemy.event import listen  # pylint: disable=no-name-in-module
 
@@ -60,7 +59,7 @@
         self.tracer = tracer
         self.engine = engine
         self.vendor = _normalize_vendor(engine.name)
-        self.cursor_mapping = WeakKeyDictionary()
+        self.cursor_mapping = {}
         self.local = local()
 
         listen(engine, "before_cursor_execute", self._before_cur_exec)
@@ -116,6 +115,7 @@
             return
 
         span.end()
+        self._cleanup(cursor)
 
     def _handle_error(self, context):
         span = self.current_thread_span
@@ -129,6 +129,13 @@
                 )
         finally:
             span.end()
+            self._cleanup(context.cursor)
+
+    def _cleanup(self, cursor):
+        try:
+            del self.cursor_mapping[cursor]
+        except KeyError:
+            pass
 
 
 def _get_attributes_from_url(url):

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py\n--- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py\n+++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py\n@@ -13,7 +13,6 @@\n # limitations under the License.\n \n from threading import local\n-from weakref import WeakKeyDictionary\n \n from sqlalchemy.event import listen  # pylint: disable=no-name-in-module\n \n@@ -60,7 +59,7 @@\n         self.tracer = tracer\n         self.engine = engine\n         self.vendor = _normalize_vendor(engine.name)\n-        self.cursor_mapping = WeakKeyDictionary()\n+        self.cursor_mapping = {}\n         self.local = local()\n \n         listen(engine, \"before_cursor_execute\", self._before_cur_exec)\n@@ -116,6 +115,7 @@\n             return\n \n         span.end()\n+        self._cleanup(cursor)\n \n     def _handle_error(self, context):\n         span = self.current_thread_span\n@@ -129,6 +129,13 @@\n                 )\n         finally:\n             span.end()\n+            self._cleanup(context.cursor)\n+\n+    def _cleanup(self, cursor):\n+        try:\n+            del self.cursor_mapping[cursor]\n+        except KeyError:\n+            pass\n \n \n def _get_attributes_from_url(url):\n", "issue": "sqlalchemy: TypeError: cannot create weak reference to 'pyodbc.Cursor' object\n**Describe your environment** \r\nSee the repo provided below for a simple reproduction environment. I was using Python 3.8.8 when running it locally.\r\n\r\n**Steps to reproduce**\r\nProvided a reproduction repo: https://github.com/jomasti/opentelemetry-instrumentation-sqlalchemy-pyodbc-bug\r\n\r\n**What is the expected behavior?**\r\nI expected the query to work successfully when the engine is instrumented. The code in the repo above works fine with 0.18b1.\r\n\r\n**What is the actual behavior?**\r\nRan into `TypeError: cannot create weak reference to 'pyodbc.Cursor' object`.\r\n\r\n**Additional context**\r\n#315 appears to be the culprit.\r\n\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom threading import local\nfrom weakref import WeakKeyDictionary\n\nfrom sqlalchemy.event import listen  # pylint: disable=no-name-in-module\n\nfrom opentelemetry import trace\nfrom opentelemetry.instrumentation.sqlalchemy.version import __version__\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace.status import Status, StatusCode\n\n\ndef _normalize_vendor(vendor):\n    \"\"\"Return a canonical name for a type of database.\"\"\"\n    if not vendor:\n        return \"db\"  # should this ever happen?\n\n    if \"sqlite\" in vendor:\n        return \"sqlite\"\n\n    if \"postgres\" in vendor or vendor == \"psycopg2\":\n        return \"postgresql\"\n\n    return vendor\n\n\ndef _get_tracer(engine, tracer_provider=None):\n    return trace.get_tracer(\n        _normalize_vendor(engine.name),\n        __version__,\n        tracer_provider=tracer_provider,\n    )\n\n\n# pylint: disable=unused-argument\ndef _wrap_create_engine(func, module, args, kwargs):\n    \"\"\"Trace the SQLAlchemy engine, creating an `EngineTracer`\n    object that will listen to SQLAlchemy events.\n    \"\"\"\n    engine = func(*args, **kwargs)\n    EngineTracer(_get_tracer(engine), engine)\n    return engine\n\n\nclass EngineTracer:\n    def __init__(self, tracer, engine):\n        self.tracer = tracer\n        self.engine = engine\n        self.vendor = _normalize_vendor(engine.name)\n        self.cursor_mapping = WeakKeyDictionary()\n        self.local = local()\n\n        listen(engine, \"before_cursor_execute\", self._before_cur_exec)\n        listen(engine, \"after_cursor_execute\", self._after_cur_exec)\n        listen(engine, \"handle_error\", self._handle_error)\n\n    @property\n    def current_thread_span(self):\n        return getattr(self.local, \"current_span\", None)\n\n    @current_thread_span.setter\n    def current_thread_span(self, span):\n        setattr(self.local, \"current_span\", span)\n\n    def _operation_name(self, db_name, statement):\n        parts = []\n        if isinstance(statement, str):\n            # otel spec recommends against parsing SQL queries. We are not trying to parse SQL\n            # but simply truncating the statement to the first word. This covers probably >95%\n            # use cases and uses the SQL statement in span name correctly as per the spec.\n            # For some very special cases it might not record the correct statement if the SQL\n            # dialect is too weird but in any case it shouldn't break anything.\n            parts.append(statement.split()[0])\n        if db_name:\n            parts.append(db_name)\n        if not parts:\n            return self.vendor\n        return \" \".join(parts)\n\n    # pylint: disable=unused-argument\n    def _before_cur_exec(self, conn, cursor, statement, *args):\n        attrs, found = _get_attributes_from_url(conn.engine.url)\n        if not found:\n            attrs = _get_attributes_from_cursor(self.vendor, cursor, attrs)\n\n        db_name = attrs.get(SpanAttributes.DB_NAME, \"\")\n        span = self.tracer.start_span(\n            self._operation_name(db_name, statement),\n            kind=trace.SpanKind.CLIENT,\n        )\n        self.current_thread_span = self.cursor_mapping[cursor] = span\n        with trace.use_span(span, end_on_exit=False):\n            if span.is_recording():\n                span.set_attribute(SpanAttributes.DB_STATEMENT, statement)\n                span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)\n                for key, value in attrs.items():\n                    span.set_attribute(key, value)\n\n    # pylint: disable=unused-argument\n    def _after_cur_exec(self, conn, cursor, statement, *args):\n        span = self.cursor_mapping.get(cursor, None)\n        if span is None:\n            return\n\n        span.end()\n\n    def _handle_error(self, context):\n        span = self.current_thread_span\n        if span is None:\n            return\n\n        try:\n            if span.is_recording():\n                span.set_status(\n                    Status(StatusCode.ERROR, str(context.original_exception),)\n                )\n        finally:\n            span.end()\n\n\ndef _get_attributes_from_url(url):\n    \"\"\"Set connection tags from the url. return true if successful.\"\"\"\n    attrs = {}\n    if url.host:\n        attrs[SpanAttributes.NET_PEER_NAME] = url.host\n    if url.port:\n        attrs[SpanAttributes.NET_PEER_PORT] = url.port\n    if url.database:\n        attrs[SpanAttributes.DB_NAME] = url.database\n    if url.username:\n        attrs[SpanAttributes.DB_USER] = url.username\n    return attrs, bool(url.host)\n\n\ndef _get_attributes_from_cursor(vendor, cursor, attrs):\n    \"\"\"Attempt to set db connection attributes by introspecting the cursor.\"\"\"\n    if vendor == \"postgresql\":\n        # pylint: disable=import-outside-toplevel\n        from psycopg2.extensions import parse_dsn\n\n        if hasattr(cursor, \"connection\") and hasattr(cursor.connection, \"dsn\"):\n            dsn = getattr(cursor.connection, \"dsn\", None)\n            if dsn:\n                data = parse_dsn(dsn)\n                attrs[SpanAttributes.DB_NAME] = data.get(\"dbname\")\n                attrs[SpanAttributes.NET_PEER_NAME] = data.get(\"host\")\n                attrs[SpanAttributes.NET_PEER_PORT] = int(data.get(\"port\"))\n    return attrs\n", "path": "instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom threading import local\n\nfrom sqlalchemy.event import listen  # pylint: disable=no-name-in-module\n\nfrom opentelemetry import trace\nfrom opentelemetry.instrumentation.sqlalchemy.version import __version__\nfrom opentelemetry.semconv.trace import SpanAttributes\nfrom opentelemetry.trace.status import Status, StatusCode\n\n\ndef _normalize_vendor(vendor):\n    \"\"\"Return a canonical name for a type of database.\"\"\"\n    if not vendor:\n        return \"db\"  # should this ever happen?\n\n    if \"sqlite\" in vendor:\n        return \"sqlite\"\n\n    if \"postgres\" in vendor or vendor == \"psycopg2\":\n        return \"postgresql\"\n\n    return vendor\n\n\ndef _get_tracer(engine, tracer_provider=None):\n    return trace.get_tracer(\n        _normalize_vendor(engine.name),\n        __version__,\n        tracer_provider=tracer_provider,\n    )\n\n\n# pylint: disable=unused-argument\ndef _wrap_create_engine(func, module, args, kwargs):\n    \"\"\"Trace the SQLAlchemy engine, creating an `EngineTracer`\n    object that will listen to SQLAlchemy events.\n    \"\"\"\n    engine = func(*args, **kwargs)\n    EngineTracer(_get_tracer(engine), engine)\n    return engine\n\n\nclass EngineTracer:\n    def __init__(self, tracer, engine):\n        self.tracer = tracer\n        self.engine = engine\n        self.vendor = _normalize_vendor(engine.name)\n        self.cursor_mapping = {}\n        self.local = local()\n\n        listen(engine, \"before_cursor_execute\", self._before_cur_exec)\n        listen(engine, \"after_cursor_execute\", self._after_cur_exec)\n        listen(engine, \"handle_error\", self._handle_error)\n\n    @property\n    def current_thread_span(self):\n        return getattr(self.local, \"current_span\", None)\n\n    @current_thread_span.setter\n    def current_thread_span(self, span):\n        setattr(self.local, \"current_span\", span)\n\n    def _operation_name(self, db_name, statement):\n        parts = []\n        if isinstance(statement, str):\n            # otel spec recommends against parsing SQL queries. We are not trying to parse SQL\n            # but simply truncating the statement to the first word. This covers probably >95%\n            # use cases and uses the SQL statement in span name correctly as per the spec.\n            # For some very special cases it might not record the correct statement if the SQL\n            # dialect is too weird but in any case it shouldn't break anything.\n            parts.append(statement.split()[0])\n        if db_name:\n            parts.append(db_name)\n        if not parts:\n            return self.vendor\n        return \" \".join(parts)\n\n    # pylint: disable=unused-argument\n    def _before_cur_exec(self, conn, cursor, statement, *args):\n        attrs, found = _get_attributes_from_url(conn.engine.url)\n        if not found:\n            attrs = _get_attributes_from_cursor(self.vendor, cursor, attrs)\n\n        db_name = attrs.get(SpanAttributes.DB_NAME, \"\")\n        span = self.tracer.start_span(\n            self._operation_name(db_name, statement),\n            kind=trace.SpanKind.CLIENT,\n        )\n        self.current_thread_span = self.cursor_mapping[cursor] = span\n        with trace.use_span(span, end_on_exit=False):\n            if span.is_recording():\n                span.set_attribute(SpanAttributes.DB_STATEMENT, statement)\n                span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)\n                for key, value in attrs.items():\n                    span.set_attribute(key, value)\n\n    # pylint: disable=unused-argument\n    def _after_cur_exec(self, conn, cursor, statement, *args):\n        span = self.cursor_mapping.get(cursor, None)\n        if span is None:\n            return\n\n        span.end()\n        self._cleanup(cursor)\n\n    def _handle_error(self, context):\n        span = self.current_thread_span\n        if span is None:\n            return\n\n        try:\n            if span.is_recording():\n                span.set_status(\n                    Status(StatusCode.ERROR, str(context.original_exception),)\n                )\n        finally:\n            span.end()\n            self._cleanup(context.cursor)\n\n    def _cleanup(self, cursor):\n        try:\n            del self.cursor_mapping[cursor]\n        except KeyError:\n            pass\n\n\ndef _get_attributes_from_url(url):\n    \"\"\"Set connection tags from the url. return true if successful.\"\"\"\n    attrs = {}\n    if url.host:\n        attrs[SpanAttributes.NET_PEER_NAME] = url.host\n    if url.port:\n        attrs[SpanAttributes.NET_PEER_PORT] = url.port\n    if url.database:\n        attrs[SpanAttributes.DB_NAME] = url.database\n    if url.username:\n        attrs[SpanAttributes.DB_USER] = url.username\n    return attrs, bool(url.host)\n\n\ndef _get_attributes_from_cursor(vendor, cursor, attrs):\n    \"\"\"Attempt to set db connection attributes by introspecting the cursor.\"\"\"\n    if vendor == \"postgresql\":\n        # pylint: disable=import-outside-toplevel\n        from psycopg2.extensions import parse_dsn\n\n        if hasattr(cursor, \"connection\") and hasattr(cursor.connection, \"dsn\"):\n            dsn = getattr(cursor.connection, \"dsn\", None)\n            if dsn:\n                data = parse_dsn(dsn)\n                attrs[SpanAttributes.DB_NAME] = data.get(\"dbname\")\n                attrs[SpanAttributes.NET_PEER_NAME] = data.get(\"host\")\n                attrs[SpanAttributes.NET_PEER_PORT] = int(data.get(\"port\"))\n    return attrs\n", "path": "instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py"}]}