We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Crash in automatic update handler when submitting work_on_bugs_task
From bodhi-consumer logs:
```
2020-10-25 11:17:14,460 INFO  [fedora_messaging.twisted.protocol][MainThread] Consuming message from topic org.fedoraproject.prod.buildsys.tag (message id c2d97737-444f-49b4-b4ca-1efb3a05e941)
2020-10-25 11:17:14,463 INFO  [bodhi][PoolThread-twisted.internet.reactor-1] Received message from fedora-messaging with topic: org.fedoraproject.prod.buildsys.tag
2020-10-25 11:17:14,463 INFO  [bodhi][PoolThread-twisted.internet.reactor-1] ginac-1.7.9-5.fc34 tagged into f34-updates-candidate
2020-10-25 11:17:14,469 INFO  [bodhi][PoolThread-twisted.internet.reactor-1] Build was not submitted, skipping
2020-10-25 11:17:14,838 INFO  [bodhi.server][PoolThread-twisted.internet.reactor-1] Sending mail to ---@posteo.de: [Fedora Update] [comment] ginac-1.7.9-5.fc34
2020-10-25 11:17:15,016 ERROR [bodhi][PoolThread-twisted.internet.reactor-1] Instance <Update at 0x7fa3740f5910> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/13/bhk3):  Unable to handle message in Automatic Update handler: Id: c2d97737-444f-49b4-b4ca-1efb3a05e941
Topic: org.fedoraproject.prod.buildsys.tag
Headers: {
    "fedora_messaging_schema": "base.message",
    "fedora_messaging_severity": 20,
    "sent-at": "2020-10-25T11:17:14+00:00"
}
Body: {
    "build_id": 1634116,
    "instance": "primary",
    "name": "ginac",
    "owner": "---",
    "release": "5.fc34",
    "tag": "f34-updates-candidate",
    "tag_id": 27040,
    "user": "---",
    "version": "1.7.9"
}
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/bodhi/server/consumers/__init__.py", line 79, in __call__
    handler_info.handler(msg)
  File "/usr/local/lib/python3.8/site-packages/bodhi/server/consumers/automatic_updates.py", line 197, in __call__
    alias = update.alias
  File "/usr/lib64/python3.8/site-packages/sqlalchemy/orm/attributes.py", line 287, in __get__
    return self.impl.get(instance_state(instance), dict_)
  File "/usr/lib64/python3.8/site-packages/sqlalchemy/orm/attributes.py", line 718, in get
    value = state._load_expired(state, passive)
  File "/usr/lib64/python3.8/site-packages/sqlalchemy/orm/state.py", line 652, in _load_expired
    self.manager.deferred_scalar_loader(self, toload)
  File "/usr/lib64/python3.8/site-packages/sqlalchemy/orm/loading.py", line 944, in load_scalar_attributes
    raise orm_exc.DetachedInstanceError(
sqlalchemy.orm.exc.DetachedInstanceError: Instance <Update at 0x7fa3740f5910> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/13/bhk3 )
2020-10-25 11:17:15,053 WARNI [fedora_messaging.twisted.protocol][MainThread] Returning message id c2d97737-444f-49b4-b4ca-1efb3a05e941 to the queue
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bodhi/server/consumers/automatic_updates.py`
Content:
```
1 # Copyright © 2019 Red Hat, Inc. and others.
2 #
3 # This file is part of Bodhi.
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc., 51
17 # Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 """
19 The Bodhi handler that creates updates automatically from tagged builds.
20 
21 This module is responsible for the process of creating updates when builds are
22 tagged with certain tags.
23 """
24 
25 import logging
26 import re
27 
28 import fedora_messaging
29 
30 from bodhi.server import buildsys
31 from bodhi.server.config import config
32 from bodhi.server.models import (
33     Bug, Build, ContentType, Package, Release, Update, UpdateStatus, UpdateType, User)
34 from bodhi.server.tasks import work_on_bugs_task
35 from bodhi.server.util import transactional_session_maker
36 
37 log = logging.getLogger('bodhi')
38 
39 
40 class AutomaticUpdateHandler:
41     """
42     The Bodhi Automatic Update Handler.
43 
44     A consumer that listens for messages about tagged builds and creates
45     updates from them.
46     """
47 
48     def __init__(self, db_factory: transactional_session_maker = None):
49         """
50         Initialize the Automatic Update Handler.
51 
52         Args:
53             db_factory: If given, used as the db_factory for this handler. If
54             None (the default), a new TransactionalSessionMaker is created and
55             used.
56         """
57         if not db_factory:
58             self.db_factory = transactional_session_maker()
59         else:
60             self.db_factory = db_factory
61 
62     def __call__(self, message: fedora_messaging.api.Message) -> None:
63         """Create updates from appropriately tagged builds.
64 
65         Args:
66             message: The message we are processing.
67         """
68         body = message.body
69 
70         missing = []
71         for mandatory in ('tag', 'build_id', 'name', 'version', 'release'):
72             if mandatory not in body:
73                 missing.append(mandatory)
74         if missing:
75             log.debug(f"Received incomplete tag message. Missing: {', '.join(missing)}")
76             return
77 
78         btag = body['tag']
79         bnvr = '{name}-{version}-{release}'.format(**body)
80 
81         koji = buildsys.get_session()
82 
83         kbuildinfo = koji.getBuild(bnvr)
84         if not kbuildinfo:
85             log.debug(f"Can't find Koji build for {bnvr}.")
86             return
87 
88         if 'nvr' not in kbuildinfo:
89             log.debug(f"Koji build info for {bnvr} doesn't contain 'nvr'.")
90             return
91 
92         if 'owner_name' not in kbuildinfo:
93             log.debug(f"Koji build info for {bnvr} doesn't contain 'owner_name'.")
94             return
95 
96         if kbuildinfo['owner_name'] in config.get('automatic_updates_blacklist'):
97             log.debug(f"{bnvr} owned by {kbuildinfo['owner_name']} who is listed in "
98                       "automatic_updates_blacklist, skipping.")
99             return
100 
101         # some APIs want the Koji build info, some others want the same
102         # wrapped in a larger (request?) structure
103         rbuildinfo = {
104             'info': kbuildinfo,
105             'nvr': kbuildinfo['nvr'].rsplit('-', 2),
106         }
107 
108         with self.db_factory() as dbsession:
109             rel = dbsession.query(Release).filter_by(create_automatic_updates=True,
110                                                      candidate_tag=btag).first()
111             if not rel:
112                 log.debug(f"Ignoring build being tagged into {btag!r}, no release configured for "
113                           "automatic updates for it found.")
114                 return
115 
116             bcls = ContentType.infer_content_class(Build, kbuildinfo)
117             build = bcls.get(bnvr)
118             if build and build.update:
119                 log.info(f"Build, active update for {bnvr} exists already, skipping.")
120                 return
121 
122             if not build:
123                 log.debug(f"Build for {bnvr} doesn't exist yet, creating.")
124 
125                 # Package.get_or_create() infers content type already
126                 log.debug("Getting/creating related package object.")
127                 pkg = Package.get_or_create(dbsession, rbuildinfo)
128 
129                 log.debug("Creating build object, adding it to the DB.")
130                 build = bcls(nvr=bnvr, package=pkg, release=rel)
131                 dbsession.add(build)
132 
133             owner_name = kbuildinfo['owner_name']
134             user = User.get(owner_name)
135             if not user:
136                 log.debug(f"Creating bodhi user for '{owner_name}'.")
137                 # Leave email, groups blank, these will be filled
138                 # in or updated when they log into Bodhi next time, see
139                 # bodhi.server.security:remember_me().
140                 user = User(name=owner_name)
141                 dbsession.add(user)
142 
143             log.debug(f"Creating new update for {bnvr}.")
144             changelog = build.get_changelog(lastupdate=True)
145             closing_bugs = []
146             if changelog:
147                 log.debug("Adding changelog to update notes.")
148                 notes = f"""Automatic update for {bnvr}.
149 
150 ##### **Changelog**
151 
152 ```
153 {changelog}
154 ```"""
155 
156                 for b in re.finditer(config.get('bz_regex'), changelog, re.IGNORECASE):
157                     idx = int(b.group(1))
158                     log.debug(f'Adding bug #{idx} to the update.')
159                     bug = Bug.get(idx)
160                     if bug is None:
161                         bug = Bug(bug_id=idx)
162                         dbsession.add(bug)
163                         dbsession.flush()
164                     if bug not in closing_bugs:
165                         closing_bugs.append(bug)
166             else:
167                 notes = f"Automatic update for {bnvr}."
168             update = Update(
169                 release=rel,
170                 builds=[build],
171                 bugs=closing_bugs,
172                 notes=notes,
173                 type=UpdateType.unspecified,
174                 stable_karma=3,
175                 unstable_karma=-3,
176                 autokarma=False,
177                 user=user,
178                 status=UpdateStatus.pending,
179             )
180 
181             # Comment on the update that it was automatically created.
182             update.comment(
183                 dbsession,
184                 str("This update was automatically created"),
185                 author="bodhi",
186             )
187 
188             update.add_tag(update.release.pending_signing_tag)
189 
190             log.debug("Adding new update to the database.")
191             dbsession.add(update)
192 
193             log.debug("Flushing changes to the database.")
194             dbsession.flush()
195 
196             # Obsolete older updates which may be stuck in testing due to failed gating
197             try:
198                 update.obsolete_older_updates(dbsession)
199             except Exception as e:
200                 log.error(f'Problem obsoleting older updates: {e}')
201 
202         # This must be run after dbsession is closed so changes are committed to db
203         alias = update.alias
204         work_on_bugs_task.delay(alias, closing_bugs)
205
```
--- 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/bodhi/server/consumers/automatic_updates.py b/bodhi/server/consumers/automatic_updates.py
--- a/bodhi/server/consumers/automatic_updates.py
+++ b/bodhi/server/consumers/automatic_updates.py
@@ -199,6 +199,7 @@
             except Exception as e:
                 log.error(f'Problem obsoleting older updates: {e}')
 
+            alias = update.alias
+
         # This must be run after dbsession is closed so changes are committed to db
-        alias = update.alias
         work_on_bugs_task.delay(alias, closing_bugs)

{"golden_diff": "diff --git a/bodhi/server/consumers/automatic_updates.py b/bodhi/server/consumers/automatic_updates.py\n--- a/bodhi/server/consumers/automatic_updates.py\n+++ b/bodhi/server/consumers/automatic_updates.py\n@@ -199,6 +199,7 @@\n             except Exception as e:\n                 log.error(f'Problem obsoleting older updates: {e}')\n \n+            alias = update.alias\n+\n         # This must be run after dbsession is closed so changes are committed to db\n-        alias = update.alias\n         work_on_bugs_task.delay(alias, closing_bugs)\n", "issue": "Crash in automatic update handler when submitting work_on_bugs_task\nFrom bodhi-consumer logs:\r\n```\r\n2020-10-25 11:17:14,460 INFO  [fedora_messaging.twisted.protocol][MainThread] Consuming message from topic org.fedoraproject.prod.buildsys.tag (message id c2d97737-444f-49b4-b4ca-1efb3a05e941)\r\n2020-10-25 11:17:14,463 INFO  [bodhi][PoolThread-twisted.internet.reactor-1] Received message from fedora-messaging with topic: org.fedoraproject.prod.buildsys.tag\r\n2020-10-25 11:17:14,463 INFO  [bodhi][PoolThread-twisted.internet.reactor-1] ginac-1.7.9-5.fc34 tagged into f34-updates-candidate\r\n2020-10-25 11:17:14,469 INFO  [bodhi][PoolThread-twisted.internet.reactor-1] Build was not submitted, skipping\r\n2020-10-25 11:17:14,838 INFO  [bodhi.server][PoolThread-twisted.internet.reactor-1] Sending mail to ---@posteo.de: [Fedora Update] [comment] ginac-1.7.9-5.fc34\r\n2020-10-25 11:17:15,016 ERROR [bodhi][PoolThread-twisted.internet.reactor-1] Instance <Update at 0x7fa3740f5910> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/13/bhk3):  Unable to handle message in Automatic Update handler: Id: c2d97737-444f-49b4-b4ca-1efb3a05e941\r\nTopic: org.fedoraproject.prod.buildsys.tag\r\nHeaders: {\r\n    \"fedora_messaging_schema\": \"base.message\",\r\n    \"fedora_messaging_severity\": 20,\r\n    \"sent-at\": \"2020-10-25T11:17:14+00:00\"\r\n}\r\nBody: {\r\n    \"build_id\": 1634116,\r\n    \"instance\": \"primary\",\r\n    \"name\": \"ginac\",\r\n    \"owner\": \"---\",\r\n    \"release\": \"5.fc34\",\r\n    \"tag\": \"f34-updates-candidate\",\r\n    \"tag_id\": 27040,\r\n    \"user\": \"---\",\r\n    \"version\": \"1.7.9\"\r\n}\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.8/site-packages/bodhi/server/consumers/__init__.py\", line 79, in __call__\r\n    handler_info.handler(msg)\r\n  File \"/usr/local/lib/python3.8/site-packages/bodhi/server/consumers/automatic_updates.py\", line 197, in __call__\r\n    alias = update.alias\r\n  File \"/usr/lib64/python3.8/site-packages/sqlalchemy/orm/attributes.py\", line 287, in __get__\r\n    return self.impl.get(instance_state(instance), dict_)\r\n  File \"/usr/lib64/python3.8/site-packages/sqlalchemy/orm/attributes.py\", line 718, in get\r\n    value = state._load_expired(state, passive)\r\n  File \"/usr/lib64/python3.8/site-packages/sqlalchemy/orm/state.py\", line 652, in _load_expired\r\n    self.manager.deferred_scalar_loader(self, toload)\r\n  File \"/usr/lib64/python3.8/site-packages/sqlalchemy/orm/loading.py\", line 944, in load_scalar_attributes\r\n    raise orm_exc.DetachedInstanceError(\r\nsqlalchemy.orm.exc.DetachedInstanceError: Instance <Update at 0x7fa3740f5910> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/13/bhk3 )\r\n2020-10-25 11:17:15,053 WARNI [fedora_messaging.twisted.protocol][MainThread] Returning message id c2d97737-444f-49b4-b4ca-1efb3a05e941 to the queue\r\n```\n", "before_files": [{"content": "# Copyright \u00a9 2019 Red Hat, Inc. and others.\n#\n# This file is part of Bodhi.\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n\"\"\"\nThe Bodhi handler that creates updates automatically from tagged builds.\n\nThis module is responsible for the process of creating updates when builds are\ntagged with certain tags.\n\"\"\"\n\nimport logging\nimport re\n\nimport fedora_messaging\n\nfrom bodhi.server import buildsys\nfrom bodhi.server.config import config\nfrom bodhi.server.models import (\n    Bug, Build, ContentType, Package, Release, Update, UpdateStatus, UpdateType, User)\nfrom bodhi.server.tasks import work_on_bugs_task\nfrom bodhi.server.util import transactional_session_maker\n\nlog = logging.getLogger('bodhi')\n\n\nclass AutomaticUpdateHandler:\n    \"\"\"\n    The Bodhi Automatic Update Handler.\n\n    A consumer that listens for messages about tagged builds and creates\n    updates from them.\n    \"\"\"\n\n    def __init__(self, db_factory: transactional_session_maker = None):\n        \"\"\"\n        Initialize the Automatic Update Handler.\n\n        Args:\n            db_factory: If given, used as the db_factory for this handler. If\n            None (the default), a new TransactionalSessionMaker is created and\n            used.\n        \"\"\"\n        if not db_factory:\n            self.db_factory = transactional_session_maker()\n        else:\n            self.db_factory = db_factory\n\n    def __call__(self, message: fedora_messaging.api.Message) -> None:\n        \"\"\"Create updates from appropriately tagged builds.\n\n        Args:\n            message: The message we are processing.\n        \"\"\"\n        body = message.body\n\n        missing = []\n        for mandatory in ('tag', 'build_id', 'name', 'version', 'release'):\n            if mandatory not in body:\n                missing.append(mandatory)\n        if missing:\n            log.debug(f\"Received incomplete tag message. Missing: {', '.join(missing)}\")\n            return\n\n        btag = body['tag']\n        bnvr = '{name}-{version}-{release}'.format(**body)\n\n        koji = buildsys.get_session()\n\n        kbuildinfo = koji.getBuild(bnvr)\n        if not kbuildinfo:\n            log.debug(f\"Can't find Koji build for {bnvr}.\")\n            return\n\n        if 'nvr' not in kbuildinfo:\n            log.debug(f\"Koji build info for {bnvr} doesn't contain 'nvr'.\")\n            return\n\n        if 'owner_name' not in kbuildinfo:\n            log.debug(f\"Koji build info for {bnvr} doesn't contain 'owner_name'.\")\n            return\n\n        if kbuildinfo['owner_name'] in config.get('automatic_updates_blacklist'):\n            log.debug(f\"{bnvr} owned by {kbuildinfo['owner_name']} who is listed in \"\n                      \"automatic_updates_blacklist, skipping.\")\n            return\n\n        # some APIs want the Koji build info, some others want the same\n        # wrapped in a larger (request?) structure\n        rbuildinfo = {\n            'info': kbuildinfo,\n            'nvr': kbuildinfo['nvr'].rsplit('-', 2),\n        }\n\n        with self.db_factory() as dbsession:\n            rel = dbsession.query(Release).filter_by(create_automatic_updates=True,\n                                                     candidate_tag=btag).first()\n            if not rel:\n                log.debug(f\"Ignoring build being tagged into {btag!r}, no release configured for \"\n                          \"automatic updates for it found.\")\n                return\n\n            bcls = ContentType.infer_content_class(Build, kbuildinfo)\n            build = bcls.get(bnvr)\n            if build and build.update:\n                log.info(f\"Build, active update for {bnvr} exists already, skipping.\")\n                return\n\n            if not build:\n                log.debug(f\"Build for {bnvr} doesn't exist yet, creating.\")\n\n                # Package.get_or_create() infers content type already\n                log.debug(\"Getting/creating related package object.\")\n                pkg = Package.get_or_create(dbsession, rbuildinfo)\n\n                log.debug(\"Creating build object, adding it to the DB.\")\n                build = bcls(nvr=bnvr, package=pkg, release=rel)\n                dbsession.add(build)\n\n            owner_name = kbuildinfo['owner_name']\n            user = User.get(owner_name)\n            if not user:\n                log.debug(f\"Creating bodhi user for '{owner_name}'.\")\n                # Leave email, groups blank, these will be filled\n                # in or updated when they log into Bodhi next time, see\n                # bodhi.server.security:remember_me().\n                user = User(name=owner_name)\n                dbsession.add(user)\n\n            log.debug(f\"Creating new update for {bnvr}.\")\n            changelog = build.get_changelog(lastupdate=True)\n            closing_bugs = []\n            if changelog:\n                log.debug(\"Adding changelog to update notes.\")\n                notes = f\"\"\"Automatic update for {bnvr}.\n\n##### **Changelog**\n\n```\n{changelog}\n```\"\"\"\n\n                for b in re.finditer(config.get('bz_regex'), changelog, re.IGNORECASE):\n                    idx = int(b.group(1))\n                    log.debug(f'Adding bug #{idx} to the update.')\n                    bug = Bug.get(idx)\n                    if bug is None:\n                        bug = Bug(bug_id=idx)\n                        dbsession.add(bug)\n                        dbsession.flush()\n                    if bug not in closing_bugs:\n                        closing_bugs.append(bug)\n            else:\n                notes = f\"Automatic update for {bnvr}.\"\n            update = Update(\n                release=rel,\n                builds=[build],\n                bugs=closing_bugs,\n                notes=notes,\n                type=UpdateType.unspecified,\n                stable_karma=3,\n                unstable_karma=-3,\n                autokarma=False,\n                user=user,\n                status=UpdateStatus.pending,\n            )\n\n            # Comment on the update that it was automatically created.\n            update.comment(\n                dbsession,\n                str(\"This update was automatically created\"),\n                author=\"bodhi\",\n            )\n\n            update.add_tag(update.release.pending_signing_tag)\n\n            log.debug(\"Adding new update to the database.\")\n            dbsession.add(update)\n\n            log.debug(\"Flushing changes to the database.\")\n            dbsession.flush()\n\n            # Obsolete older updates which may be stuck in testing due to failed gating\n            try:\n                update.obsolete_older_updates(dbsession)\n            except Exception as e:\n                log.error(f'Problem obsoleting older updates: {e}')\n\n        # This must be run after dbsession is closed so changes are committed to db\n        alias = update.alias\n        work_on_bugs_task.delay(alias, closing_bugs)\n", "path": "bodhi/server/consumers/automatic_updates.py"}], "after_files": [{"content": "# Copyright \u00a9 2019 Red Hat, Inc. and others.\n#\n# This file is part of Bodhi.\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n\"\"\"\nThe Bodhi handler that creates updates automatically from tagged builds.\n\nThis module is responsible for the process of creating updates when builds are\ntagged with certain tags.\n\"\"\"\n\nimport logging\nimport re\n\nimport fedora_messaging\n\nfrom bodhi.server import buildsys\nfrom bodhi.server.config import config\nfrom bodhi.server.models import (\n    Bug, Build, ContentType, Package, Release, Update, UpdateStatus, UpdateType, User)\nfrom bodhi.server.tasks import work_on_bugs_task\nfrom bodhi.server.util import transactional_session_maker\n\nlog = logging.getLogger('bodhi')\n\n\nclass AutomaticUpdateHandler:\n    \"\"\"\n    The Bodhi Automatic Update Handler.\n\n    A consumer that listens for messages about tagged builds and creates\n    updates from them.\n    \"\"\"\n\n    def __init__(self, db_factory: transactional_session_maker = None):\n        \"\"\"\n        Initialize the Automatic Update Handler.\n\n        Args:\n            db_factory: If given, used as the db_factory for this handler. If\n            None (the default), a new TransactionalSessionMaker is created and\n            used.\n        \"\"\"\n        if not db_factory:\n            self.db_factory = transactional_session_maker()\n        else:\n            self.db_factory = db_factory\n\n    def __call__(self, message: fedora_messaging.api.Message) -> None:\n        \"\"\"Create updates from appropriately tagged builds.\n\n        Args:\n            message: The message we are processing.\n        \"\"\"\n        body = message.body\n\n        missing = []\n        for mandatory in ('tag', 'build_id', 'name', 'version', 'release'):\n            if mandatory not in body:\n                missing.append(mandatory)\n        if missing:\n            log.debug(f\"Received incomplete tag message. Missing: {', '.join(missing)}\")\n            return\n\n        btag = body['tag']\n        bnvr = '{name}-{version}-{release}'.format(**body)\n\n        koji = buildsys.get_session()\n\n        kbuildinfo = koji.getBuild(bnvr)\n        if not kbuildinfo:\n            log.debug(f\"Can't find Koji build for {bnvr}.\")\n            return\n\n        if 'nvr' not in kbuildinfo:\n            log.debug(f\"Koji build info for {bnvr} doesn't contain 'nvr'.\")\n            return\n\n        if 'owner_name' not in kbuildinfo:\n            log.debug(f\"Koji build info for {bnvr} doesn't contain 'owner_name'.\")\n            return\n\n        if kbuildinfo['owner_name'] in config.get('automatic_updates_blacklist'):\n            log.debug(f\"{bnvr} owned by {kbuildinfo['owner_name']} who is listed in \"\n                      \"automatic_updates_blacklist, skipping.\")\n            return\n\n        # some APIs want the Koji build info, some others want the same\n        # wrapped in a larger (request?) structure\n        rbuildinfo = {\n            'info': kbuildinfo,\n            'nvr': kbuildinfo['nvr'].rsplit('-', 2),\n        }\n\n        with self.db_factory() as dbsession:\n            rel = dbsession.query(Release).filter_by(create_automatic_updates=True,\n                                                     candidate_tag=btag).first()\n            if not rel:\n                log.debug(f\"Ignoring build being tagged into {btag!r}, no release configured for \"\n                          \"automatic updates for it found.\")\n                return\n\n            bcls = ContentType.infer_content_class(Build, kbuildinfo)\n            build = bcls.get(bnvr)\n            if build and build.update:\n                log.info(f\"Build, active update for {bnvr} exists already, skipping.\")\n                return\n\n            if not build:\n                log.debug(f\"Build for {bnvr} doesn't exist yet, creating.\")\n\n                # Package.get_or_create() infers content type already\n                log.debug(\"Getting/creating related package object.\")\n                pkg = Package.get_or_create(dbsession, rbuildinfo)\n\n                log.debug(\"Creating build object, adding it to the DB.\")\n                build = bcls(nvr=bnvr, package=pkg, release=rel)\n                dbsession.add(build)\n\n            owner_name = kbuildinfo['owner_name']\n            user = User.get(owner_name)\n            if not user:\n                log.debug(f\"Creating bodhi user for '{owner_name}'.\")\n                # Leave email, groups blank, these will be filled\n                # in or updated when they log into Bodhi next time, see\n                # bodhi.server.security:remember_me().\n                user = User(name=owner_name)\n                dbsession.add(user)\n\n            log.debug(f\"Creating new update for {bnvr}.\")\n            changelog = build.get_changelog(lastupdate=True)\n            closing_bugs = []\n            if changelog:\n                log.debug(\"Adding changelog to update notes.\")\n                notes = f\"\"\"Automatic update for {bnvr}.\n\n##### **Changelog**\n\n```\n{changelog}\n```\"\"\"\n\n                for b in re.finditer(config.get('bz_regex'), changelog, re.IGNORECASE):\n                    idx = int(b.group(1))\n                    log.debug(f'Adding bug #{idx} to the update.')\n                    bug = Bug.get(idx)\n                    if bug is None:\n                        bug = Bug(bug_id=idx)\n                        dbsession.add(bug)\n                        dbsession.flush()\n                    if bug not in closing_bugs:\n                        closing_bugs.append(bug)\n            else:\n                notes = f\"Automatic update for {bnvr}.\"\n            update = Update(\n                release=rel,\n                builds=[build],\n                bugs=closing_bugs,\n                notes=notes,\n                type=UpdateType.unspecified,\n                stable_karma=3,\n                unstable_karma=-3,\n                autokarma=False,\n                user=user,\n                status=UpdateStatus.pending,\n            )\n\n            # Comment on the update that it was automatically created.\n            update.comment(\n                dbsession,\n                str(\"This update was automatically created\"),\n                author=\"bodhi\",\n            )\n\n            update.add_tag(update.release.pending_signing_tag)\n\n            log.debug(\"Adding new update to the database.\")\n            dbsession.add(update)\n\n            log.debug(\"Flushing changes to the database.\")\n            dbsession.flush()\n\n            # Obsolete older updates which may be stuck in testing due to failed gating\n            try:\n                update.obsolete_older_updates(dbsession)\n            except Exception as e:\n                log.error(f'Problem obsoleting older updates: {e}')\n\n            alias = update.alias\n\n        # This must be run after dbsession is closed so changes are committed to db\n        work_on_bugs_task.delay(alias, closing_bugs)\n", "path": "bodhi/server/consumers/automatic_updates.py"}]}