Make us compatible with oslo.db 12.1.0

With oslo.db 12.1.0 the following sqlalchemy warning become an error:

  sqlalchemy.exc.RemovedIn20Warning: Retrieving row members using strings
  or other non-integers is deprecated; use row._mapping for a dictionary
  interface to the row (Background on SQLAlchemy 2.0 at:
  https://sqlalche.me/e/b8d9)

We tried to fix this before but missed a test_case that still used dict
access to get the fields of a Row instead of attribute access. We fixed
that test here. Also while fixed it I noticed that the generic
_AttributeCache object states that it stores dicts but actually it
sometimes stores dict but sometimes it stores Row objects. So the doc is
updated and the dict path converted to store namedtuple objects in the
cache instead. Note that Row is also acts like a namedtuple except that
._mapping does not exists in namedtuple but exists in Row. The trait
object assumed it gets a Row object with ._mapping from the cache so
that is adjusted to only assume a namedtuple and use _asdict() to covert
it to dict which is available both in Row and namedtuple.

Change-Id: I23ac1d85290a2dec307f8e76aafb02096259b605
This commit is contained in:
Balazs Gibizer
2022-09-05 11:33:11 +02:00
parent 723da65faf
commit 5ec38f6d3b
3 changed files with 24 additions and 19 deletions

View File

@@ -9,6 +9,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections
import sqlalchemy as sa import sqlalchemy as sa
@@ -41,6 +42,12 @@ class _AttributeCache(object):
_table = None _table = None
_not_found = None _not_found = None
# The cache internally stores either sqlalchemy Row objects or
# Attrs namedtuples but Row is compatible with namedtuple interface too.
Attrs = collections.namedtuple(
"Attrs", ["id", "name", "updated_at", "created_at"]
)
def __init__(self, ctx): def __init__(self, ctx):
"""Initialize the cache of resource class identifiers. """Initialize the cache of resource class identifiers.
@@ -87,14 +94,14 @@ class _AttributeCache(object):
:param attr_str: The string representation of the attribute for which :param attr_str: The string representation of the attribute for which
to look up the object. to look up the object.
:returns: dict representing the attribute fields, if the attribute was :returns: namedtuple representing the attribute fields, if the
found in the appropriate database table. attribute was found in the appropriate database table.
:raises An instance of the subclass' _not_found exception if attr_str :raises An instance of the subclass' _not_found exception if attr_str
cannot be found in the DB. cannot be found in the DB.
""" """
attr_id_str = self._all_cache.get(attr_str) attrs = self._all_cache.get(attr_str)
if attr_id_str is not None: if attrs is not None:
return attr_id_str return attrs
# Otherwise, check the database table # Otherwise, check the database table
self._refresh_from_db(self._ctx) self._refresh_from_db(self._ctx)
@@ -126,7 +133,7 @@ class _AttributeCache(object):
def get_all(self): def get_all(self):
"""Return an iterator of all the resources in the cache with all their """Return an iterator of all the resources in the cache with all their
attributes. attributes as a namedtuple.
In Python3 the return value is a generator. In Python3 the return value is a generator.
""" """
@@ -152,6 +159,8 @@ class _AttributeCache(object):
res = ctx.session.execute(sel).fetchall() res = ctx.session.execute(sel).fetchall()
self._id_cache = {r[1]: r[0] for r in res} self._id_cache = {r[1]: r[0] for r in res}
self._str_cache = {r[0]: r[1] for r in res} self._str_cache = {r[0]: r[1] for r in res}
# Note that r is Row object that is compatible with the namedtuple
# interface of the cache
self._all_cache = {r[1]: r for r in res} self._all_cache = {r[1]: r for r in res}
def _add_attribute(self, attr_id, name, created_at, updated_at): def _add_attribute(self, attr_id, name, created_at, updated_at):
@@ -160,12 +169,8 @@ class _AttributeCache(object):
""" """
self._id_cache[name] = attr_id self._id_cache[name] = attr_id
self._str_cache[attr_id] = name self._str_cache[attr_id] = name
self._all_cache[name] = { attrs = self.Attrs(attr_id, name, updated_at, created_at)
'id': attr_id, self._all_cache[name] = attrs
'name': name,
'created_at': created_at,
'updated_at': updated_at,
}
class ConsumerTypeCache(_AttributeCache): class ConsumerTypeCache(_AttributeCache):

View File

@@ -90,8 +90,8 @@ class Trait(object):
@classmethod @classmethod
def get_by_name(cls, context, name): def get_by_name(cls, context, name):
db_trait = context.trait_cache.all_from_string(name)._mapping trait = context.trait_cache.all_from_string(name)
return cls._from_db_object(context, cls(context), db_trait) return cls._from_db_object(context, cls(context), trait._asdict())
@staticmethod @staticmethod
@db_api.placement_context_manager.writer @db_api.placement_context_manager.writer

View File

@@ -93,11 +93,11 @@ class TestResourceClassCache(base.TestCase):
# Verify all fields available from all_from_string # Verify all fields available from all_from_string
iron_nfv_class = cache.all_from_string('IRON_NFV') iron_nfv_class = cache.all_from_string('IRON_NFV')
self.assertEqual(1001, iron_nfv_class['id']) self.assertEqual(1001, iron_nfv_class.id)
self.assertEqual('IRON_NFV', iron_nfv_class['name']) self.assertEqual('IRON_NFV', iron_nfv_class.name)
# updated_at not set on insert # updated_at not set on insert
self.assertIsNone(iron_nfv_class['updated_at']) self.assertIsNone(iron_nfv_class.updated_at)
self.assertIsInstance(iron_nfv_class['created_at'], datetime.datetime) self.assertIsInstance(iron_nfv_class.created_at, datetime.datetime)
# Update IRON_NFV (this is a no-op but will set updated_at) # Update IRON_NFV (this is a no-op but will set updated_at)
with self.placement_db.get_engine().connect() as conn: with self.placement_db.get_engine().connect() as conn:
@@ -116,7 +116,7 @@ class TestResourceClassCache(base.TestCase):
iron_nfv_class = cache.all_from_string('IRON_NFV') iron_nfv_class = cache.all_from_string('IRON_NFV')
# updated_at set on update # updated_at set on update
self.assertIsInstance(iron_nfv_class['updated_at'], datetime.datetime) self.assertIsInstance(iron_nfv_class.updated_at, datetime.datetime)
def test_rc_cache_miss(self): def test_rc_cache_miss(self):
"""Test that we raise ResourceClassNotFound if an unknown resource """Test that we raise ResourceClassNotFound if an unknown resource