We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Cannot delete bento that was created with same name:version as an older (deleted) bento
**Describe the bug**
After deleting a bento, I cannot delete another bento that was created with that same name:version.

**To Reproduce**
1. Create a bento with `name:1` through `model_service.save(version='1')`.
2. In a shell, `bentoml delete name:1`
3. Create a new bento with the same name, again with  `model_service.save(version='1')`.
4. Try to `bentoml delete name:1`

The error is the following:
```
Are you sure about delete name:1? This will delete the BentoService saved bundle files permanently [y/N]: y                                                                                                                                            
[2020-10-27 15:22:33,477] ERROR - RPC ERROR DangerouslyDeleteBento: Multiple rows were found for one()                                                                                                                                                    
Error: bentoml-cli delete failed: INTERNAL:Multiple rows were found for one() 
```
**Expected behavior**
I can endlessly delete and recreate bentos with the same name/version for testing.

**Environment:**
 - OS: Ubuntu 20.04
 - Python 3.8.5
 - BentoML Version 0.9.2


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bentoml/yatai/repository/metadata_store.py`
Content:
```
1 # Copyright 2019 Atalaya Tech, Inc.
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 import logging
16 import datetime
17 
18 from sqlalchemy import (
19     Column,
20     Enum,
21     String,
22     Integer,
23     JSON,
24     Boolean,
25     DateTime,
26     UniqueConstraint,
27     desc,
28 )
29 from sqlalchemy.orm.exc import NoResultFound
30 from google.protobuf.json_format import ParseDict
31 
32 from bentoml.utils import ProtoMessageToDict
33 from bentoml.exceptions import YataiRepositoryException
34 from bentoml.yatai.db import Base, create_session
35 from bentoml.yatai.label_store import (
36     filter_label_query,
37     get_labels,
38     list_labels,
39     add_or_update_labels,
40     RESOURCE_TYPE,
41 )
42 from bentoml.yatai.proto.repository_pb2 import (
43     UploadStatus,
44     BentoUri,
45     BentoServiceMetadata,
46     Bento as BentoPB,
47     ListBentoRequest,
48 )
49 
50 logger = logging.getLogger(__name__)
51 
52 
53 DEFAULT_UPLOAD_STATUS = UploadStatus(status=UploadStatus.UNINITIALIZED)
54 DEFAULT_LIST_LIMIT = 40
55 
56 
57 class Bento(Base):
58     __tablename__ = 'bentos'
59     __table_args__ = tuple(UniqueConstraint('name', 'version', name='_name_version_uc'))
60 
61     id = Column(Integer, primary_key=True)
62     name = Column(String, nullable=False)
63     version = Column(String, nullable=False)
64 
65     # Storage URI for this Bento
66     uri = Column(String, nullable=False)
67 
68     # Name is required for PostgreSQL and any future supported database which
69     # requires an explicitly named type, or an explicitly named constraint in order to
70     # generate the type and/or a table that uses it.
71     uri_type = Column(
72         Enum(*BentoUri.StorageType.keys(), name='uri_type'), default=BentoUri.UNSET
73     )
74 
75     # JSON filed mapping directly to BentoServiceMetadata proto message
76     bento_service_metadata = Column(JSON, nullable=False, default={})
77 
78     # Time of AddBento call, the time of Bento creation can be found in metadata field
79     created_at = Column(DateTime, default=datetime.datetime.utcnow)
80 
81     # latest upload status, JSON message also includes last update timestamp
82     upload_status = Column(
83         JSON, nullable=False, default=ProtoMessageToDict(DEFAULT_UPLOAD_STATUS)
84     )
85 
86     # mark as deleted
87     deleted = Column(Boolean, default=False)
88 
89 
90 def _bento_orm_obj_to_pb(bento_obj, labels=None):
91     # Backwards compatible support loading saved bundle created before 0.8.0
92     if (
93         'apis' in bento_obj.bento_service_metadata
94         and bento_obj.bento_service_metadata['apis']
95     ):
96         for api in bento_obj.bento_service_metadata['apis']:
97             if 'handler_type' in api:
98                 api['input_type'] = api['handler_type']
99                 del api['handler_type']
100             if 'handler_config' in api:
101                 api['input_config'] = api['handler_config']
102                 del api['handler_config']
103             if 'output_type' not in api:
104                 api['output_type'] = 'DefaultOutput'
105 
106     bento_service_metadata_pb = ParseDict(
107         bento_obj.bento_service_metadata, BentoServiceMetadata()
108     )
109     bento_uri = BentoUri(
110         uri=bento_obj.uri, type=BentoUri.StorageType.Value(bento_obj.uri_type)
111     )
112     if labels is not None:
113         bento_service_metadata_pb.labels.update(labels)
114     return BentoPB(
115         name=bento_obj.name,
116         version=bento_obj.version,
117         uri=bento_uri,
118         bento_service_metadata=bento_service_metadata_pb,
119     )
120 
121 
122 class BentoMetadataStore(object):
123     def __init__(self, sess_maker):
124         self.sess_maker = sess_maker
125 
126     def add(self, bento_name, bento_version, uri, uri_type):
127         with create_session(self.sess_maker) as sess:
128             bento_obj = Bento()
129             bento_obj.name = bento_name
130             bento_obj.version = bento_version
131             bento_obj.uri = uri
132             bento_obj.uri_type = BentoUri.StorageType.Name(uri_type)
133             return sess.add(bento_obj)
134 
135     def _get_latest(self, bento_name):
136         with create_session(self.sess_maker) as sess:
137             query = (
138                 sess.query(Bento)
139                 .filter_by(name=bento_name, deleted=False)
140                 .order_by(desc(Bento.created_at))
141                 .limit(1)
142             )
143 
144             query_result = query.all()
145             if len(query_result) == 1:
146                 labels = get_labels(sess, RESOURCE_TYPE.bento, query_result[0].id)
147                 return _bento_orm_obj_to_pb(query_result[0], labels)
148             else:
149                 return None
150 
151     def get(self, bento_name, bento_version="latest"):
152         if bento_version.lower() == "latest":
153             return self._get_latest(bento_name)
154 
155         with create_session(self.sess_maker) as sess:
156             try:
157                 bento_obj = (
158                     sess.query(Bento)
159                     .filter_by(name=bento_name, version=bento_version)
160                     .one()
161                 )
162                 if bento_obj.deleted:
163                     # bento has been marked as deleted
164                     return None
165                 labels = get_labels(sess, RESOURCE_TYPE.bento, bento_obj.id)
166                 return _bento_orm_obj_to_pb(bento_obj, labels)
167             except NoResultFound:
168                 return None
169 
170     def update_bento_service_metadata(
171         self, bento_name, bento_version, bento_service_metadata_pb
172     ):
173         with create_session(self.sess_maker) as sess:
174             try:
175                 bento_obj = (
176                     sess.query(Bento)
177                     .filter_by(name=bento_name, version=bento_version, deleted=False)
178                     .one()
179                 )
180                 service_metadata = ProtoMessageToDict(bento_service_metadata_pb)
181                 bento_obj.bento_service_metadata = service_metadata
182                 if service_metadata.get('labels', None) is not None:
183                     bento = (
184                         sess.query(Bento)
185                         .filter_by(name=bento_name, version=bento_version)
186                         .one()
187                     )
188                     add_or_update_labels(
189                         sess, RESOURCE_TYPE.bento, bento.id, service_metadata['labels']
190                     )
191             except NoResultFound:
192                 raise YataiRepositoryException(
193                     "Bento %s:%s is not found in repository" % bento_name, bento_version
194                 )
195 
196     def update_upload_status(self, bento_name, bento_version, upload_status_pb):
197         with create_session(self.sess_maker) as sess:
198             try:
199                 bento_obj = (
200                     sess.query(Bento)
201                     .filter_by(name=bento_name, version=bento_version, deleted=False)
202                     .one()
203                 )
204                 # TODO:
205                 # if bento_obj.upload_status and bento_obj.upload_status.updated_at >
206                 # upload_status_pb.updated_at, update should be ignored
207                 bento_obj.upload_status = ProtoMessageToDict(upload_status_pb)
208             except NoResultFound:
209                 raise YataiRepositoryException(
210                     "Bento %s:%s is not found in repository" % bento_name, bento_version
211                 )
212 
213     def dangerously_delete(self, bento_name, bento_version):
214         with create_session(self.sess_maker) as sess:
215             try:
216                 bento_obj = (
217                     sess.query(Bento)
218                     .filter_by(name=bento_name, version=bento_version)
219                     .one()
220                 )
221                 if bento_obj.deleted:
222                     raise YataiRepositoryException(
223                         "Bento {}:{} has already been deleted".format(
224                             bento_name, bento_version
225                         )
226                     )
227                 bento_obj.deleted = True
228             except NoResultFound:
229                 raise YataiRepositoryException(
230                     "Bento %s:%s is not found in repository" % bento_name, bento_version
231                 )
232 
233     def list(
234         self,
235         bento_name=None,
236         offset=None,
237         limit=None,
238         label_selectors=None,
239         order_by=ListBentoRequest.created_at,
240         ascending_order=False,
241     ):
242         with create_session(self.sess_maker) as sess:
243             query = sess.query(Bento)
244             order_by = ListBentoRequest.SORTABLE_COLUMN.Name(order_by)
245             order_by_field = getattr(Bento, order_by)
246             order_by_action = (
247                 order_by_field if ascending_order else desc(order_by_field)
248             )
249             query = query.order_by(order_by_action)
250             if bento_name:
251                 # filter_by apply filtering criterion to a copy of the query
252                 query = query.filter_by(name=bento_name)
253             query = query.filter_by(deleted=False)
254             if label_selectors.match_labels or label_selectors.match_expressions:
255                 bento_ids = filter_label_query(
256                     sess, RESOURCE_TYPE.bento, label_selectors
257                 )
258                 query = query.filter(Bento.id.in_(bento_ids))
259 
260             # We are not defaulting limit to 200 in the signature,
261             # because protobuf will pass 0 as value
262             limit = limit or DEFAULT_LIST_LIMIT
263             # Limit and offset need to be called after order_by filter/filter_by is
264             # called
265             query = query.limit(limit)
266             if offset:
267                 query = query.offset(offset)
268 
269             query_result = query.all()
270             bento_ids = [bento_obj.id for bento_obj in query_result]
271             labels = list_labels(sess, RESOURCE_TYPE.bento, bento_ids)
272             result = [
273                 _bento_orm_obj_to_pb(bento_obj, labels.get(str(bento_obj.id)))
274                 for bento_obj in query_result
275             ]
276             return result
277
```
--- 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/bentoml/yatai/repository/metadata_store.py b/bentoml/yatai/repository/metadata_store.py
--- a/bentoml/yatai/repository/metadata_store.py
+++ b/bentoml/yatai/repository/metadata_store.py
@@ -156,7 +156,7 @@
             try:
                 bento_obj = (
                     sess.query(Bento)
-                    .filter_by(name=bento_name, version=bento_version)
+                    .filter_by(name=bento_name, version=bento_version, deleted=False)
                     .one()
                 )
                 if bento_obj.deleted:
@@ -215,7 +215,7 @@
             try:
                 bento_obj = (
                     sess.query(Bento)
-                    .filter_by(name=bento_name, version=bento_version)
+                    .filter_by(name=bento_name, version=bento_version, deleted=False)
                     .one()
                 )
                 if bento_obj.deleted:

{"golden_diff": "diff --git a/bentoml/yatai/repository/metadata_store.py b/bentoml/yatai/repository/metadata_store.py\n--- a/bentoml/yatai/repository/metadata_store.py\n+++ b/bentoml/yatai/repository/metadata_store.py\n@@ -156,7 +156,7 @@\n             try:\n                 bento_obj = (\n                     sess.query(Bento)\n-                    .filter_by(name=bento_name, version=bento_version)\n+                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                     .one()\n                 )\n                 if bento_obj.deleted:\n@@ -215,7 +215,7 @@\n             try:\n                 bento_obj = (\n                     sess.query(Bento)\n-                    .filter_by(name=bento_name, version=bento_version)\n+                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                     .one()\n                 )\n                 if bento_obj.deleted:\n", "issue": "Cannot delete bento that was created with same name:version as an older (deleted) bento\n**Describe the bug**\r\nAfter deleting a bento, I cannot delete another bento that was created with that same name:version.\r\n\r\n**To Reproduce**\r\n1. Create a bento with `name:1` through `model_service.save(version='1')`.\r\n2. In a shell, `bentoml delete name:1`\r\n3. Create a new bento with the same name, again with  `model_service.save(version='1')`.\r\n4. Try to `bentoml delete name:1`\r\n\r\nThe error is the following:\r\n```\r\nAre you sure about delete name:1? This will delete the BentoService saved bundle files permanently [y/N]: y                                                                                                                                            \r\n[2020-10-27 15:22:33,477] ERROR - RPC ERROR DangerouslyDeleteBento: Multiple rows were found for one()                                                                                                                                                    \r\nError: bentoml-cli delete failed: INTERNAL:Multiple rows were found for one() \r\n```\r\n**Expected behavior**\r\nI can endlessly delete and recreate bentos with the same name/version for testing.\r\n\r\n**Environment:**\r\n - OS: Ubuntu 20.04\r\n - Python 3.8.5\r\n - BentoML Version 0.9.2\r\n\n", "before_files": [{"content": "# Copyright 2019 Atalaya Tech, Inc.\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\nimport logging\nimport datetime\n\nfrom sqlalchemy import (\n    Column,\n    Enum,\n    String,\n    Integer,\n    JSON,\n    Boolean,\n    DateTime,\n    UniqueConstraint,\n    desc,\n)\nfrom sqlalchemy.orm.exc import NoResultFound\nfrom google.protobuf.json_format import ParseDict\n\nfrom bentoml.utils import ProtoMessageToDict\nfrom bentoml.exceptions import YataiRepositoryException\nfrom bentoml.yatai.db import Base, create_session\nfrom bentoml.yatai.label_store import (\n    filter_label_query,\n    get_labels,\n    list_labels,\n    add_or_update_labels,\n    RESOURCE_TYPE,\n)\nfrom bentoml.yatai.proto.repository_pb2 import (\n    UploadStatus,\n    BentoUri,\n    BentoServiceMetadata,\n    Bento as BentoPB,\n    ListBentoRequest,\n)\n\nlogger = logging.getLogger(__name__)\n\n\nDEFAULT_UPLOAD_STATUS = UploadStatus(status=UploadStatus.UNINITIALIZED)\nDEFAULT_LIST_LIMIT = 40\n\n\nclass Bento(Base):\n    __tablename__ = 'bentos'\n    __table_args__ = tuple(UniqueConstraint('name', 'version', name='_name_version_uc'))\n\n    id = Column(Integer, primary_key=True)\n    name = Column(String, nullable=False)\n    version = Column(String, nullable=False)\n\n    # Storage URI for this Bento\n    uri = Column(String, nullable=False)\n\n    # Name is required for PostgreSQL and any future supported database which\n    # requires an explicitly named type, or an explicitly named constraint in order to\n    # generate the type and/or a table that uses it.\n    uri_type = Column(\n        Enum(*BentoUri.StorageType.keys(), name='uri_type'), default=BentoUri.UNSET\n    )\n\n    # JSON filed mapping directly to BentoServiceMetadata proto message\n    bento_service_metadata = Column(JSON, nullable=False, default={})\n\n    # Time of AddBento call, the time of Bento creation can be found in metadata field\n    created_at = Column(DateTime, default=datetime.datetime.utcnow)\n\n    # latest upload status, JSON message also includes last update timestamp\n    upload_status = Column(\n        JSON, nullable=False, default=ProtoMessageToDict(DEFAULT_UPLOAD_STATUS)\n    )\n\n    # mark as deleted\n    deleted = Column(Boolean, default=False)\n\n\ndef _bento_orm_obj_to_pb(bento_obj, labels=None):\n    # Backwards compatible support loading saved bundle created before 0.8.0\n    if (\n        'apis' in bento_obj.bento_service_metadata\n        and bento_obj.bento_service_metadata['apis']\n    ):\n        for api in bento_obj.bento_service_metadata['apis']:\n            if 'handler_type' in api:\n                api['input_type'] = api['handler_type']\n                del api['handler_type']\n            if 'handler_config' in api:\n                api['input_config'] = api['handler_config']\n                del api['handler_config']\n            if 'output_type' not in api:\n                api['output_type'] = 'DefaultOutput'\n\n    bento_service_metadata_pb = ParseDict(\n        bento_obj.bento_service_metadata, BentoServiceMetadata()\n    )\n    bento_uri = BentoUri(\n        uri=bento_obj.uri, type=BentoUri.StorageType.Value(bento_obj.uri_type)\n    )\n    if labels is not None:\n        bento_service_metadata_pb.labels.update(labels)\n    return BentoPB(\n        name=bento_obj.name,\n        version=bento_obj.version,\n        uri=bento_uri,\n        bento_service_metadata=bento_service_metadata_pb,\n    )\n\n\nclass BentoMetadataStore(object):\n    def __init__(self, sess_maker):\n        self.sess_maker = sess_maker\n\n    def add(self, bento_name, bento_version, uri, uri_type):\n        with create_session(self.sess_maker) as sess:\n            bento_obj = Bento()\n            bento_obj.name = bento_name\n            bento_obj.version = bento_version\n            bento_obj.uri = uri\n            bento_obj.uri_type = BentoUri.StorageType.Name(uri_type)\n            return sess.add(bento_obj)\n\n    def _get_latest(self, bento_name):\n        with create_session(self.sess_maker) as sess:\n            query = (\n                sess.query(Bento)\n                .filter_by(name=bento_name, deleted=False)\n                .order_by(desc(Bento.created_at))\n                .limit(1)\n            )\n\n            query_result = query.all()\n            if len(query_result) == 1:\n                labels = get_labels(sess, RESOURCE_TYPE.bento, query_result[0].id)\n                return _bento_orm_obj_to_pb(query_result[0], labels)\n            else:\n                return None\n\n    def get(self, bento_name, bento_version=\"latest\"):\n        if bento_version.lower() == \"latest\":\n            return self._get_latest(bento_name)\n\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version)\n                    .one()\n                )\n                if bento_obj.deleted:\n                    # bento has been marked as deleted\n                    return None\n                labels = get_labels(sess, RESOURCE_TYPE.bento, bento_obj.id)\n                return _bento_orm_obj_to_pb(bento_obj, labels)\n            except NoResultFound:\n                return None\n\n    def update_bento_service_metadata(\n        self, bento_name, bento_version, bento_service_metadata_pb\n    ):\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                    .one()\n                )\n                service_metadata = ProtoMessageToDict(bento_service_metadata_pb)\n                bento_obj.bento_service_metadata = service_metadata\n                if service_metadata.get('labels', None) is not None:\n                    bento = (\n                        sess.query(Bento)\n                        .filter_by(name=bento_name, version=bento_version)\n                        .one()\n                    )\n                    add_or_update_labels(\n                        sess, RESOURCE_TYPE.bento, bento.id, service_metadata['labels']\n                    )\n            except NoResultFound:\n                raise YataiRepositoryException(\n                    \"Bento %s:%s is not found in repository\" % bento_name, bento_version\n                )\n\n    def update_upload_status(self, bento_name, bento_version, upload_status_pb):\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                    .one()\n                )\n                # TODO:\n                # if bento_obj.upload_status and bento_obj.upload_status.updated_at >\n                # upload_status_pb.updated_at, update should be ignored\n                bento_obj.upload_status = ProtoMessageToDict(upload_status_pb)\n            except NoResultFound:\n                raise YataiRepositoryException(\n                    \"Bento %s:%s is not found in repository\" % bento_name, bento_version\n                )\n\n    def dangerously_delete(self, bento_name, bento_version):\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version)\n                    .one()\n                )\n                if bento_obj.deleted:\n                    raise YataiRepositoryException(\n                        \"Bento {}:{} has already been deleted\".format(\n                            bento_name, bento_version\n                        )\n                    )\n                bento_obj.deleted = True\n            except NoResultFound:\n                raise YataiRepositoryException(\n                    \"Bento %s:%s is not found in repository\" % bento_name, bento_version\n                )\n\n    def list(\n        self,\n        bento_name=None,\n        offset=None,\n        limit=None,\n        label_selectors=None,\n        order_by=ListBentoRequest.created_at,\n        ascending_order=False,\n    ):\n        with create_session(self.sess_maker) as sess:\n            query = sess.query(Bento)\n            order_by = ListBentoRequest.SORTABLE_COLUMN.Name(order_by)\n            order_by_field = getattr(Bento, order_by)\n            order_by_action = (\n                order_by_field if ascending_order else desc(order_by_field)\n            )\n            query = query.order_by(order_by_action)\n            if bento_name:\n                # filter_by apply filtering criterion to a copy of the query\n                query = query.filter_by(name=bento_name)\n            query = query.filter_by(deleted=False)\n            if label_selectors.match_labels or label_selectors.match_expressions:\n                bento_ids = filter_label_query(\n                    sess, RESOURCE_TYPE.bento, label_selectors\n                )\n                query = query.filter(Bento.id.in_(bento_ids))\n\n            # We are not defaulting limit to 200 in the signature,\n            # because protobuf will pass 0 as value\n            limit = limit or DEFAULT_LIST_LIMIT\n            # Limit and offset need to be called after order_by filter/filter_by is\n            # called\n            query = query.limit(limit)\n            if offset:\n                query = query.offset(offset)\n\n            query_result = query.all()\n            bento_ids = [bento_obj.id for bento_obj in query_result]\n            labels = list_labels(sess, RESOURCE_TYPE.bento, bento_ids)\n            result = [\n                _bento_orm_obj_to_pb(bento_obj, labels.get(str(bento_obj.id)))\n                for bento_obj in query_result\n            ]\n            return result\n", "path": "bentoml/yatai/repository/metadata_store.py"}], "after_files": [{"content": "# Copyright 2019 Atalaya Tech, Inc.\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\nimport logging\nimport datetime\n\nfrom sqlalchemy import (\n    Column,\n    Enum,\n    String,\n    Integer,\n    JSON,\n    Boolean,\n    DateTime,\n    UniqueConstraint,\n    desc,\n)\nfrom sqlalchemy.orm.exc import NoResultFound\nfrom google.protobuf.json_format import ParseDict\n\nfrom bentoml.utils import ProtoMessageToDict\nfrom bentoml.exceptions import YataiRepositoryException\nfrom bentoml.yatai.db import Base, create_session\nfrom bentoml.yatai.label_store import (\n    filter_label_query,\n    get_labels,\n    list_labels,\n    add_or_update_labels,\n    RESOURCE_TYPE,\n)\nfrom bentoml.yatai.proto.repository_pb2 import (\n    UploadStatus,\n    BentoUri,\n    BentoServiceMetadata,\n    Bento as BentoPB,\n    ListBentoRequest,\n)\n\nlogger = logging.getLogger(__name__)\n\n\nDEFAULT_UPLOAD_STATUS = UploadStatus(status=UploadStatus.UNINITIALIZED)\nDEFAULT_LIST_LIMIT = 40\n\n\nclass Bento(Base):\n    __tablename__ = 'bentos'\n    __table_args__ = tuple(UniqueConstraint('name', 'version', name='_name_version_uc'))\n\n    id = Column(Integer, primary_key=True)\n    name = Column(String, nullable=False)\n    version = Column(String, nullable=False)\n\n    # Storage URI for this Bento\n    uri = Column(String, nullable=False)\n\n    # Name is required for PostgreSQL and any future supported database which\n    # requires an explicitly named type, or an explicitly named constraint in order to\n    # generate the type and/or a table that uses it.\n    uri_type = Column(\n        Enum(*BentoUri.StorageType.keys(), name='uri_type'), default=BentoUri.UNSET\n    )\n\n    # JSON filed mapping directly to BentoServiceMetadata proto message\n    bento_service_metadata = Column(JSON, nullable=False, default={})\n\n    # Time of AddBento call, the time of Bento creation can be found in metadata field\n    created_at = Column(DateTime, default=datetime.datetime.utcnow)\n\n    # latest upload status, JSON message also includes last update timestamp\n    upload_status = Column(\n        JSON, nullable=False, default=ProtoMessageToDict(DEFAULT_UPLOAD_STATUS)\n    )\n\n    # mark as deleted\n    deleted = Column(Boolean, default=False)\n\n\ndef _bento_orm_obj_to_pb(bento_obj, labels=None):\n    # Backwards compatible support loading saved bundle created before 0.8.0\n    if (\n        'apis' in bento_obj.bento_service_metadata\n        and bento_obj.bento_service_metadata['apis']\n    ):\n        for api in bento_obj.bento_service_metadata['apis']:\n            if 'handler_type' in api:\n                api['input_type'] = api['handler_type']\n                del api['handler_type']\n            if 'handler_config' in api:\n                api['input_config'] = api['handler_config']\n                del api['handler_config']\n            if 'output_type' not in api:\n                api['output_type'] = 'DefaultOutput'\n\n    bento_service_metadata_pb = ParseDict(\n        bento_obj.bento_service_metadata, BentoServiceMetadata()\n    )\n    bento_uri = BentoUri(\n        uri=bento_obj.uri, type=BentoUri.StorageType.Value(bento_obj.uri_type)\n    )\n    if labels is not None:\n        bento_service_metadata_pb.labels.update(labels)\n    return BentoPB(\n        name=bento_obj.name,\n        version=bento_obj.version,\n        uri=bento_uri,\n        bento_service_metadata=bento_service_metadata_pb,\n    )\n\n\nclass BentoMetadataStore(object):\n    def __init__(self, sess_maker):\n        self.sess_maker = sess_maker\n\n    def add(self, bento_name, bento_version, uri, uri_type):\n        with create_session(self.sess_maker) as sess:\n            bento_obj = Bento()\n            bento_obj.name = bento_name\n            bento_obj.version = bento_version\n            bento_obj.uri = uri\n            bento_obj.uri_type = BentoUri.StorageType.Name(uri_type)\n            return sess.add(bento_obj)\n\n    def _get_latest(self, bento_name):\n        with create_session(self.sess_maker) as sess:\n            query = (\n                sess.query(Bento)\n                .filter_by(name=bento_name, deleted=False)\n                .order_by(desc(Bento.created_at))\n                .limit(1)\n            )\n\n            query_result = query.all()\n            if len(query_result) == 1:\n                labels = get_labels(sess, RESOURCE_TYPE.bento, query_result[0].id)\n                return _bento_orm_obj_to_pb(query_result[0], labels)\n            else:\n                return None\n\n    def get(self, bento_name, bento_version=\"latest\"):\n        if bento_version.lower() == \"latest\":\n            return self._get_latest(bento_name)\n\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                    .one()\n                )\n                if bento_obj.deleted:\n                    # bento has been marked as deleted\n                    return None\n                labels = get_labels(sess, RESOURCE_TYPE.bento, bento_obj.id)\n                return _bento_orm_obj_to_pb(bento_obj, labels)\n            except NoResultFound:\n                return None\n\n    def update_bento_service_metadata(\n        self, bento_name, bento_version, bento_service_metadata_pb\n    ):\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                    .one()\n                )\n                service_metadata = ProtoMessageToDict(bento_service_metadata_pb)\n                bento_obj.bento_service_metadata = service_metadata\n                if service_metadata.get('labels', None) is not None:\n                    bento = (\n                        sess.query(Bento)\n                        .filter_by(name=bento_name, version=bento_version)\n                        .one()\n                    )\n                    add_or_update_labels(\n                        sess, RESOURCE_TYPE.bento, bento.id, service_metadata['labels']\n                    )\n            except NoResultFound:\n                raise YataiRepositoryException(\n                    \"Bento %s:%s is not found in repository\" % bento_name, bento_version\n                )\n\n    def update_upload_status(self, bento_name, bento_version, upload_status_pb):\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                    .one()\n                )\n                # TODO:\n                # if bento_obj.upload_status and bento_obj.upload_status.updated_at >\n                # upload_status_pb.updated_at, update should be ignored\n                bento_obj.upload_status = ProtoMessageToDict(upload_status_pb)\n            except NoResultFound:\n                raise YataiRepositoryException(\n                    \"Bento %s:%s is not found in repository\" % bento_name, bento_version\n                )\n\n    def dangerously_delete(self, bento_name, bento_version):\n        with create_session(self.sess_maker) as sess:\n            try:\n                bento_obj = (\n                    sess.query(Bento)\n                    .filter_by(name=bento_name, version=bento_version, deleted=False)\n                    .one()\n                )\n                if bento_obj.deleted:\n                    raise YataiRepositoryException(\n                        \"Bento {}:{} has already been deleted\".format(\n                            bento_name, bento_version\n                        )\n                    )\n                bento_obj.deleted = True\n            except NoResultFound:\n                raise YataiRepositoryException(\n                    \"Bento %s:%s is not found in repository\" % bento_name, bento_version\n                )\n\n    def list(\n        self,\n        bento_name=None,\n        offset=None,\n        limit=None,\n        label_selectors=None,\n        order_by=ListBentoRequest.created_at,\n        ascending_order=False,\n    ):\n        with create_session(self.sess_maker) as sess:\n            query = sess.query(Bento)\n            order_by = ListBentoRequest.SORTABLE_COLUMN.Name(order_by)\n            order_by_field = getattr(Bento, order_by)\n            order_by_action = (\n                order_by_field if ascending_order else desc(order_by_field)\n            )\n            query = query.order_by(order_by_action)\n            if bento_name:\n                # filter_by apply filtering criterion to a copy of the query\n                query = query.filter_by(name=bento_name)\n            query = query.filter_by(deleted=False)\n            if label_selectors.match_labels or label_selectors.match_expressions:\n                bento_ids = filter_label_query(\n                    sess, RESOURCE_TYPE.bento, label_selectors\n                )\n                query = query.filter(Bento.id.in_(bento_ids))\n\n            # We are not defaulting limit to 200 in the signature,\n            # because protobuf will pass 0 as value\n            limit = limit or DEFAULT_LIST_LIMIT\n            # Limit and offset need to be called after order_by filter/filter_by is\n            # called\n            query = query.limit(limit)\n            if offset:\n                query = query.offset(offset)\n\n            query_result = query.all()\n            bento_ids = [bento_obj.id for bento_obj in query_result]\n            labels = list_labels(sess, RESOURCE_TYPE.bento, bento_ids)\n            result = [\n                _bento_orm_obj_to_pb(bento_obj, labels.get(str(bento_obj.id)))\n                for bento_obj in query_result\n            ]\n            return result\n", "path": "bentoml/yatai/repository/metadata_store.py"}]}