Repository: holoviz/holoviews
Pull request #6459 (2024-11-27 16:44:02)

refactor: Ruff 0.8
Changes are mainly related to f-string conversion. 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) Report
Attention: Patch coverage is `67.27273%` with `18 lines` in your changes missing coverage. Please review.
> Project coverage is 88.49%. Comparing base [(`93040aa`)](https://app.codecov.io/gh/holoviz/holoviews/commit/93040aa71a19f01dfaf1458772239a49ea5b20cf?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) to head [(`e3cd48e`)](https://app.codecov.io/gh/holoviz/holoviews/commit/e3cd48efc08b440ecd0c3bb583769f3241e2186c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) | Patch % | Lines |
|---|---|---|
| [holoviews/element/raster.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Felement%2Fraster.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2VsZW1lbnQvcmFzdGVyLnB5) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/core/element.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fcore%2Felement.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvZWxlbWVudC5weQ==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/core/options.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fcore%2Foptions.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvb3B0aW9ucy5weQ==) | 50.00% | [2 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/ipython/widgets.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fipython%2Fwidgets.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2lweXRob24vd2lkZ2V0cy5weQ==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/core/data/dictionary.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fcore%2Fdata%2Fdictionary.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvZGF0YS9kaWN0aW9uYXJ5LnB5) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/core/data/pandas.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fcore%2Fdata%2Fpandas.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvZGF0YS9wYW5kYXMucHk=) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/core/data/spatialpandas.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fcore%2Fdata%2Fspatialpandas.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvZGF0YS9zcGF0aWFscGFuZGFzLnB5) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/core/ndmapping.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fcore%2Fndmapping.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvbmRtYXBwaW5nLnB5) | 50.00% | [1 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/core/util.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Fcore%2Futil.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvdXRpbC5weQ==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| [holoviews/element/tabular.py](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&filepath=holoviews%2Felement%2Ftabular.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2VsZW1lbnQvdGFidWxhci5weQ==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) |
| ... and [3 more](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) | |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #6459      +/-   ##
==========================================
- Coverage   88.50%   88.49%   -0.02%     
==========================================
  Files         323      323              
  Lines       68631    68630       -1     
==========================================
- Hits        60741    60732       -9     
- Misses       7890     7898       +8     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/holoviz/holoviews/pull/6459?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bc75780b0..9ff3ee2db 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,7 +17,7 @@ repos:
       - id: check-json
       - id: detect-private-key
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.7.1
+    rev: v0.8.1
     hooks:
       - id: ruff
         files: holoviews/|scripts/
@@ -43,7 +43,7 @@ repos:
     hooks:
       - id: shellcheck
   - repo: https://github.com/hoxbro/prettier-pre-commit
-    rev: v3.3.3
+    rev: v3.4.1
     hooks:
       - id: prettier
         exclude: conda.recipe/meta.yaml
diff --git a/holoviews/__init__.py b/holoviews/__init__.py
index a8b3f5868..6182110f4 100644
--- a/holoviews/__init__.py
+++ b/holoviews/__init__.py
@@ -76,6 +76,7 @@ To report issues go to https://github.com/holoviz/holoviews.
 import builtins
 import os
 import sys
+from typing import TYPE_CHECKING
 
 import param
 
@@ -132,6 +133,11 @@ if '_pyodide' in sys.modules:
         extension = pyodide_extension
     del pyodide_extension, in_jupyterlite
 
+
+if TYPE_CHECKING:
+    # Adding this here to have better docstring in LSP
+    from .util import extension
+
 # A single holoviews.rc file may be executed if found.
 for rcfile in [os.environ.get("HOLOVIEWSRC", ''),
                os.path.abspath(os.path.join(os.path.split(__file__)[0],
@@ -186,7 +192,7 @@ def __getattr__(name):
     raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
 
 __all__ = [k for k in locals() if not k.startswith('_')]
-__all__ += ['annotate', '__version__']
+__all__ += ['__version__', 'annotate']
 
 def __dir__():
     return __all__
diff --git a/holoviews/core/boundingregion.py b/holoviews/core/boundingregion.py
index b790ce519..1394ec782 100644
--- a/holoviews/core/boundingregion.py
+++ b/holoviews/core/boundingregion.py
@@ -253,7 +253,7 @@ class AARectangle:
 
     Usage:  aar = AARectangle( (x1,y1),(x2,y2), ... , (xN,yN) )
     """
-    __slots__ = ['_left', '_bottom', '_right', '_top']
+    __slots__ = ['_bottom', '_left', '_right', '_top']
 
 
     def __init__(self, *points):
diff --git a/holoviews/core/data/dictionary.py b/holoviews/core/data/dictionary.py
index 4c2b95e7a..9db91c758 100644
--- a/holoviews/core/data/dictionary.py
+++ b/holoviews/core/data/dictionary.py
@@ -128,7 +128,7 @@ class DictInterface(Interface):
         lengths = [(dim, 1 if isscalar(dataset.data[dim]) else len(dataset.data[dim]))
                    for dim in dimensions]
         if len({l for d, l in lengths if l > 1}) > 1:
-            lengths = ', '.join(['%s: %d' % l for l in sorted(lengths)])
+            lengths = ', '.join(['{}: {}'.format(*l) for l in sorted(lengths)])
             raise DataError('Length of columns must be equal or scalar, '
                             f'columns have lengths: {lengths}', cls)
 
diff --git a/holoviews/core/data/grid.py b/holoviews/core/data/grid.py
index 71d389857..9fa1a5f59 100644
--- a/holoviews/core/data/grid.py
+++ b/holoviews/core/data/grid.py
@@ -126,10 +126,10 @@ class GridInterface(DictInterface):
                 # If empty or an irregular mesh
                 pass
             elif len(shape) != len(expected):
-                raise error('The shape of the %s value array does not '
+                raise error(f'The shape of the {vdim} value array does not '
                             'match the expected dimensionality indicated '
-                            'by the key dimensions. Expected %d-D array, '
-                            'found %d-D array.' % (vdim, len(expected), len(shape)))
+                            f'by the key dimensions. Expected {len(expected)}-D array, '
+                            f'found {len(shape)}-D array.')
             elif any((e not in (s, s + 1)) for s, e in zip(shape, valid_shape)):
                 raise error(f'Key dimension values and value array {vdim} '
                             f'shapes do not match. Expected shape {valid_shape}, '
diff --git a/holoviews/core/data/pandas.py b/holoviews/core/data/pandas.py
index 9db9ca488..dd7bad5e8 100644
--- a/holoviews/core/data/pandas.py
+++ b/holoviews/core/data/pandas.py
@@ -122,9 +122,8 @@ class PandasInterface(Interface, PandasAPI):
                 if any(d.ndim > 1 for d in data):
                     raise ValueError('PandasInterface cannot interpret multi-dimensional arrays.')
                 elif len(data) < min_dims:
-                    raise DataError('Data contains fewer columns than the %s element expects. Expected '
-                                    'at least %d columns but found only %d columns.' %
-                                    (eltype.__name__, min_dims, len(data)))
+                    raise DataError(f'Data contains fewer columns than the {eltype.__name__} element expects. Expected '
+                                    f'at least {min_dims} columns but found only {len(data)} columns.')
                 elif not cls.expanded(data):
                     raise ValueError('PandasInterface expects data to be of uniform shape.')
                 data = pd.DataFrame(dict(zip(columns, data)), columns=columns)
diff --git a/holoviews/core/data/spatialpandas.py b/holoviews/core/data/spatialpandas.py
index c7c339ce9..66d3e621c 100644
--- a/holoviews/core/data/spatialpandas.py
+++ b/holoviews/core/data/spatialpandas.py
@@ -118,11 +118,10 @@ class SpatialPandasInterface(MultiInterface):
         dim_types = 'key' if vdims else 'all'
         geom_dims = cls.geom_dims(dataset)
         if len(geom_dims) != 2:
-            raise DataError('Expected %s instance to declare two key '
+            raise DataError(f'Expected {type(dataset).__name__} instance to declare two key '
                             'dimensions corresponding to the geometry '
-                            'coordinates but %d dimensions were found '
-                            'which did not refer to any columns.'
-                            % (type(dataset).__name__, len(geom_dims)), cls)
+                            f'coordinates but {len(geom_dims)} dimensions were found '
+                            'which did not refer to any columns.', cls)
         not_found = [d.name for d in dataset.dimensions(dim_types)
                      if d not in geom_dims and d.name not in dataset.data]
         if not_found:
diff --git a/holoviews/core/data/xarray.py b/holoviews/core/data/xarray.py
index a9dc58a43..38d25f964 100644
--- a/holoviews/core/data/xarray.py
+++ b/holoviews/core/data/xarray.py
@@ -180,7 +180,7 @@ class XArrayInterface(GridInterface):
             data = {d: np.asarray(values) if d in kdims else values
                     for d, values in data.items()}
             coord_dims = [data[kd.name].ndim for kd in kdims]
-            dims = tuple('dim_%d' % i for i in range(max(coord_dims)))[::-1]
+            dims = tuple(f'dim_{i}' for i in range(max(coord_dims)))[::-1]
             coords = {}
             for kd in kdims:
                 coord_vals = data[kd.name]
diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py
index 5fa322b85..c1c7a1999 100644
--- a/holoviews/core/dimension.py
+++ b/holoviews/core/dimension.py
@@ -700,7 +700,7 @@ class LabelledData(param.Parameterized):
         obj_dict = self.__dict__.copy()
         try:
             if Store.save_option_state and (obj_dict.get('_id', None) is not None):
-                custom_key = '_custom_option_%d' % obj_dict['_id']
+                custom_key = '_custom_option_{}'.format(obj_dict['_id'])
                 if custom_key not in obj_dict:
                     obj_dict[custom_key] = {backend:s[obj_dict['_id']]
                                             for backend,s in Store._custom_options.items()
diff --git a/holoviews/core/element.py b/holoviews/core/element.py
index c463254b9..b89176a7b 100644
--- a/holoviews/core/element.py
+++ b/holoviews/core/element.py
@@ -277,9 +277,9 @@ class Tabular(Element):
         """
         ndims = self.ndims
         if col >= self.cols:
-            raise Exception("Maximum column index is %d" % self.cols-1)
+            raise Exception(f"Maximum column index is {self.cols-1}")
         elif row >= self.rows:
-            raise Exception("Maximum row index is %d" % self.rows-1)
+            raise Exception(f"Maximum row index is {self.cols-1}")
         elif row == 0:
             if col >= ndims:
                 if self.vdims:
diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py
index f66877e4f..78fb1b3aa 100644
--- a/holoviews/core/ndmapping.py
+++ b/holoviews/core/ndmapping.py
@@ -147,10 +147,9 @@ class MultiDimensionalMapping(Dimensioned):
             raise TypeError(f'{slf} does not accept {data} type, data elements have '
                             f'to be a {data_type}.')
         elif not len(dim_vals) == self.ndims:
-            raise KeyError('The data contains keys of length %d, but the kdims '
-                           'only declare %d dimensions. Ensure that the number '
-                           'of kdims match the length of the keys in your data.'
-                           % (len(dim_vals), self.ndims))
+            raise KeyError(f'The data contains keys of length {len(dim_vals)}, but the kdims '
+                           f'only declare {self.ndims} dimensions. Ensure that the number '
+                           'of kdims match the length of the keys in your data.')
 
 
     def _add_item(self, dim_vals, data, sort=True, update=True):
@@ -475,8 +474,7 @@ class MultiDimensionalMapping(Dimensioned):
         """
         if (len(self.values()) > 0):
             info_str = self.__class__.__name__ +\
-                       " containing %d items of type %s\n" % (len(self.keys()),
-                                                              type(self.values()[0]).__name__)
+                       f" containing {len(self.keys())} items of type {type(self.values()[0]).__name__}\n"
         else:
             info_str = self.__class__.__name__ + " containing no items\n"
         info_str += ('-' * (len(info_str)-1)) + "\n\n"
@@ -920,7 +918,7 @@ class UniformNdMapping(NdMapping):
                 for dim, val in key_dims:
                     dimn = 1
                     while dim in df:
-                        dim = dim+'_%d' % dimn
+                        dim = dim+f'_{dimn}'
                         if dim in df:
                             dimn += 1
                     df.insert(0, dim, val)
diff --git a/holoviews/core/options.py b/holoviews/core/options.py
index 821fc4776..db2ddf4c7 100644
--- a/holoviews/core/options.py
+++ b/holoviews/core/options.py
@@ -1522,9 +1522,8 @@ class StoreOptions:
 
         # Clean up the custom tree if it was not applied
         if new_id not in Store.custom_options(backend=backend):
-            raise AssertionError("New option id %d does not match any "
-                                 "option trees in Store.custom_options."
-                                 % new_id)
+            raise AssertionError(f"New option id {new_id} does not match any "
+                                 "option trees in Store.custom_options.")
         return applied
 
     @classmethod
@@ -1722,11 +1721,11 @@ class StoreOptions:
                     additions.update({k:kws})
             if spec_key not in options:
                 options[spec_key] = {}
-            for key in additions:
+            for key, value in additions.items():
                 if key in options[spec_key]:
-                    options[spec_key][key].update(additions[key])
+                    options[spec_key][key].update(value)
                 else:
-                    options[spec_key][key] = additions[key]
+                    options[spec_key][key] = value
         return options
 
 
diff --git a/holoviews/core/pprint.py b/holoviews/core/pprint.py
index 7faa42356..1475819ae 100644
--- a/holoviews/core/pprint.py
+++ b/holoviews/core/pprint.py
@@ -412,4 +412,4 @@ class PrettyPrinter(param.Parameterized):
         return level, lines
 
 
-__all__ = ['PrettyPrinter', 'InfoPrinter']
+__all__ = ['InfoPrinter', 'PrettyPrinter']
diff --git a/holoviews/core/util.py b/holoviews/core/util.py
index 97b87eec5..05daabb7a 100644
--- a/holoviews/core/util.py
+++ b/holoviews/core/util.py
@@ -1976,8 +1976,7 @@ def cross_index(values, index):
     lengths = [len(v) for v in values]
     length = np.prod(lengths)
     if index >= length:
-        raise IndexError('Index %d out of bounds for cross-product of size %d'
-                         % (index, length))
+        raise IndexError(f'Index {index} out of bounds for cross-product of size {lengths}')
     indexes = []
     for i in range(1, len(values))[::-1]:
         p = np.prod(lengths[-i:])
diff --git a/holoviews/element/comparison.py b/holoviews/element/comparison.py
index 8ff379851..2d753c6d8 100644
--- a/holoviews/element/comparison.py
+++ b/holoviews/element/comparison.py
@@ -532,8 +532,7 @@ class Comparison(ComparisonInterface):
                             el2.dimension_values(d, flat=tabular))
                             for d in el1.vdims]
         if el1.shape[0] != el2.shape[0]:
-            raise AssertionError("%s not of matching length, %d vs. %d."
-                                 % (msg, el1.shape[0], el2.shape[0]))
+            raise AssertionError(f"{msg} not of matching length, {el1.shape[0]} vs. {el2.shape[0]}.")
         for dim, d1, d2 in dimension_data:
             with contextlib.suppress(Exception):
                 np.testing.assert_equal(d1, d2)
diff --git a/holoviews/element/graphs.py b/holoviews/element/graphs.py
index 49d569d13..b25f9c48f 100644
--- a/holoviews/element/graphs.py
+++ b/holoviews/element/graphs.py
@@ -223,8 +223,8 @@ class Graph(Dataset, Element2D):
                 mismatch = npaths != nedges
             if mismatch:
                 raise ValueError('Ensure that the number of edges supplied '
-                                 'to the Graph (%d) matches the number of '
-                                 'edgepaths (%d)' % (nedges, npaths))
+                                 f'to the Graph ({nedges}) matches the number of '
+                                 f'edgepaths ({npaths})')
 
     def clone(self, data=None, shared_data=True, new_type=None, link=True,
               *args, **overrides):
diff --git a/holoviews/element/raster.py b/holoviews/element/raster.py
index caf8e1b36..2d9b2ac4a 100644
--- a/holoviews/element/raster.py
+++ b/holoviews/element/raster.py
@@ -57,7 +57,7 @@ class Raster(Element2D):
         if not isinstance(slices, tuple):
             slices = (slices, slice(None))
         elif len(slices) > (2 + self.depth):
-            raise KeyError("Can only slice %d dimensions" % 2 + self.depth)
+            raise KeyError(f"Can only slice {2 + self.depth} dimensions")
         elif len(slices) == 3 and slices[-1] not in [self.vdims[0].name, slice(None)]:
             raise KeyError(f"{self.vdims[0].name!r} is the only selectable value dimension")
 
@@ -268,8 +268,8 @@ class Image(Selection2DExpr, Dataset, Raster, SheetCoordinateSystem):
             if not xdensity: xdensity = 1
             if not ydensity: ydensity = 1
         elif isinstance(data, np.ndarray) and data.ndim < self._ndim:
-            raise ValueError('%s type expects %d-D array received %d-D '
-                             'array.' % (type(self).__name__, self._ndim, data.ndim))
+            raise ValueError(f'{type(self).__name__} type expects {self._ndim}-D array received {data.ndim}-D '
+                             'array.')
 
         if rtol is not None:
             params['rtol'] = rtol
@@ -319,8 +319,7 @@ class Image(Selection2DExpr, Dataset, Raster, SheetCoordinateSystem):
     def _validate(self, data_bounds, supplied_bounds):
         if len(self.shape) == 3:
             if self.shape[2] != len(self.vdims):
-                raise ValueError("Input array has shape %r but %d value dimensions defined"
-                                 % (self.shape, len(self.vdims)))
+                raise ValueError(f"Input array has shape {self.shape!r} but {len(self.vdims)} value dimensions defined")
 
         # Ensure coordinates are regularly sampled
         clsname = type(self).__name__
diff --git a/holoviews/element/tabular.py b/holoviews/element/tabular.py
index 1c64d4c36..a34929873 100644
--- a/holoviews/element/tabular.py
+++ b/holoviews/element/tabular.py
@@ -88,7 +88,7 @@ class ItemTable(Element):
         if col > 2:
             raise Exception("Only two columns available in a ItemTable.")
         elif row >= self.rows:
-            raise Exception("Maximum row index is %d" % self.rows-1)
+            raise Exception(f"Maximum row index is {self.rows-1}")
         elif col == 0:
             return self.dimensions('value')[row].pprint_label
         else:
diff --git a/holoviews/ipython/magics.py b/holoviews/ipython/magics.py
index 0cc1dfd8f..99bc2ae18 100644
--- a/holoviews/ipython/magics.py
+++ b/holoviews/ipython/magics.py
@@ -368,7 +368,7 @@ class TimerMagic(Magics):
         seconds = time.time() -  TimerMagic.start_time
         minutes = seconds // 60
         hours = minutes // 60
-        return "Timer elapsed: %02d:%02d:%02d" % (hours, minutes % 60, seconds % 60)
+        return f"Timer elapsed: {hours:02d}:{minutes % 60:02d}:{seconds % 60:02d}"
 
     @classmethod
     def option_completer(cls, k,v):
diff --git a/holoviews/ipython/widgets.py b/holoviews/ipython/widgets.py
index bb004628d..72f2f49b1 100644
--- a/holoviews/ipython/widgets.py
+++ b/holoviews/ipython/widgets.py
@@ -64,9 +64,7 @@ class ProgressBar(ProgressIndicator):
                 elapsed = time.time() -  self.start_time
                 if clear_output:
                     clear_output()
-                self.out = '\r' + ('100%% %s %02d:%02d:%02d'
-                                   % (self.label.lower(), elapsed//3600,
-                                      elapsed//60, elapsed%60))
+                self.out = f'\r100% {self.label.lower()} {elapsed//3600:02d}:{elapsed//60:02d}:{elapsed%60:02d}'
                 output = ''.join([pg.out for pg in self.current_progress])
                 sys.stdout.write(output)
             else:
@@ -109,7 +107,7 @@ class ProgressBar(ProgressIndicator):
                                             min_port=min_port,
                                             max_port=max_port,
                                             max_tries=max_tries)
-            self.param.message("Progress broadcast bound to port %d" % port)
+            self.param.message(f"Progress broadcast bound to port {port}")
             return sock
         except Exception:
             self.param.message("No suitable port found for progress broadcast.")
diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py
index cc54521c7..bf32bb70e 100644
--- a/holoviews/plotting/bokeh/annotation.py
+++ b/holoviews/plotting/bokeh/annotation.py
@@ -138,7 +138,7 @@ class TextPlot(ElementPlot, AnnotationPlot):
         if 'text_baseline' not in style:
             style['text_baseline'] = baseline
         if 'text_font_size' not in style:
-            style['text_font_size'] = '%dPt' % element.fontsize
+            style['text_font_size'] = f"{int(element.fontsize)}Pt"
         if 'color' in style:
             style['text_color'] = style.pop('color')
         style['angle'] = np.deg2rad(style.get('angle', element.rotation))
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 01e10a3c9..eb8b0b66e 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -2848,8 +2848,7 @@ class ColorbarPlot(ElementPlot):
                 if isinstance(cmap, list) and len(cmap) != ncolors:
                     raise ValueError('The number of colors in the colormap '
                                      'must match the intervals defined in the '
-                                     'color_levels, expected %d colors found %d.'
-                                     % (ncolors, len(cmap)))
+                                     f'color_levels, expected {ncolors} colors found {len(cmap)}.')
             palette = process_cmap(cmap, ncolors, categorical=categorical)
             if isinstance(self.color_levels, list):
                 palette, (low, high) = color_intervals(palette, self.color_levels, clip=(low, high))
diff --git a/holoviews/plotting/bokeh/graphs.py b/holoviews/plotting/bokeh/graphs.py
index 23c084bd8..e469948f0 100644
--- a/holoviews/plotting/bokeh/graphs.py
+++ b/holoviews/plotting/bokeh/graphs.py
@@ -161,7 +161,7 @@ class GraphPlot(GraphMixin, CompositeElementPlot, ColorbarPlot, LegendPlot):
                 mapping = {'xs': 'xs', 'ys': 'ys'}
             else:
                 raise ValueError("Edge paths do not match the number of supplied edges."
-                                 "Expected %d, found %d paths." % (len(element), len(edges)))
+                                 f"Expected {len(element)}, found {len(edges)} paths.")
         elif self.directed:
             xdim, ydim = element.nodes.kdims[:2]
             x_range = ranges[xdim.label]['combined']
diff --git a/holoviews/plotting/bokeh/links.py b/holoviews/plotting/bokeh/links.py
index d60f2f5af..6fd18a773 100644
--- a/holoviews/plotting/bokeh/links.py
+++ b/holoviews/plotting/bokeh/links.py
@@ -235,8 +235,8 @@ class DataLinkCallback(LinkCallback):
         tgt_len = [len(v) for v in tgt_cds.data.values()]
         if src_len and tgt_len and (src_len[0] != tgt_len[0]):
             raise ValueError('DataLink source data length must match target '
-                            'data length, found source length of %d and '
-                            'target length of %d.' % (src_len[0], tgt_len[0]))
+                            f'data length, found source length of {src_len[0]} and '
+                            f'target length of {tgt_len[0]}.')
 
         # Ensure the data sources are compatible (i.e. overlapping columns are equal)
         for k, v in tgt_cds.data.items():
diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py
index 056e49e5c..a4b2a6c24 100644
--- a/holoviews/plotting/bokeh/raster.py
+++ b/holoviews/plotting/bokeh/raster.py
@@ -81,8 +81,7 @@ class RasterPlot(ColorbarPlot):
                 return date.toISOString().slice(0, 19).replace('T', ' ')
             }
             """
-            for key in formatters:
-                formatter = formatters[key]
+            for key, formatter in formatters.values():
                 if isinstance(formatter, str) and formatter.lower() == "datetime":
                     formatters[key] = CustomJSHover(code=datetime_code)
 
@@ -363,7 +362,7 @@ class QuadMeshPlot(ColorbarPlot):
 
         if self.invert_axes: x, y = y, x
         cmapper = self._get_colormapper(z, element, ranges, style)
-        cmapper = {'field': z.name, 'transform': cmapper}
+        cmapper = {'field': dimension_sanitizer(z.name), 'transform': cmapper}
 
         irregular = (element.interface.irregular(element, x) or
                      element.interface.irregular(element, y))
@@ -405,11 +404,10 @@ class QuadMeshPlot(ColorbarPlot):
                     mask.append(False)
             mask = np.array(mask)
 
-            data = {'xs': XS, 'ys': YS, z.name: zvals[mask]}
+            data = {'xs': XS, 'ys': YS, dimension_sanitizer(z.name): zvals[mask]}
             if 'hover' in self.handles:
                 if not self.static_source:
-                    hover_data = self._collect_hover_data(
-                            element, mask, irregular=True)
+                    hover_data = self._collect_hover_data(element, mask, irregular=True)
                 hover_data[x] = np.array(xc)
                 hover_data[y] = np.array(yc)
         else:
diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py
index b3d7e31a7..b9adc14c1 100644
--- a/holoviews/plotting/mpl/element.py
+++ b/holoviews/plotting/mpl/element.py
@@ -997,8 +997,7 @@ class ColorbarPlot(ElementPlot):
                 if isinstance(cmap, list) and len(cmap) != ncolors:
                     raise ValueError('The number of colors in the colormap '
                                      'must match the intervals defined in the '
-                                     'color_levels, expected %d colors found %d.'
-                                     % (ncolors, len(cmap)))
+                                     f'color_levels, expected {ncolors} colors found {len(cmap)}.')
             try:
                 el_min, el_max = np.nanmin(values), np.nanmax(values)
             except ValueError:
diff --git a/holoviews/plotting/plotly/util.py b/holoviews/plotting/plotly/util.py
index f1902dd24..96b19050c 100644
--- a/holoviews/plotting/plotly/util.py
+++ b/holoviews/plotting/plotly/util.py
@@ -817,8 +817,7 @@ def get_colorscale(cmap, levels=None, cmin=None, cmax=None):
         if isinstance(cmap, list) and len(cmap) != ncolors:
             raise ValueError('The number of colors in the colormap '
                              'must match the intervals defined in the '
-                             'color_levels, expected %d colors found %d.'
-                             % (ncolors, len(cmap)))
+                             f'color_levels, expected {ncolors} colors found {len(cmap)}.')
     try:
         palette = process_cmap(cmap, ncolors)
     except Exception as e:
diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py
index 4974260eb..3aa406669 100644
--- a/holoviews/plotting/renderer.py
+++ b/holoviews/plotting/renderer.py
@@ -352,7 +352,7 @@ class Renderer(Exporter):
             figdata = figdata.encode("utf-8")
         elif fmt == 'pdf' and 'height' not in css:
             _, h = self.get_size(plot)
-            css['height'] = '%dpx' % (h*self.dpi*1.15)
+            css['height'] = f"{int(h*self.dpi*1.15)}px"
 
         if isinstance(css, dict):
             css = '; '.join(f"{k}: {v}" for k, v in css.items())
diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py
index 1c043193e..c514475bc 100644
--- a/holoviews/plotting/util.py
+++ b/holoviews/plotting/util.py
@@ -985,8 +985,7 @@ def color_intervals(colors, levels, clip=None, N=255):
     if len(colors) != len(levels)-1:
         raise ValueError('The number of colors in the colormap '
                          'must match the intervals defined in the '
-                         'color_levels, expected %d colors found %d.'
-                         % (N, len(colors)))
+                         f'color_levels, expected {N} colors found {len(colors)}.')
     intervals = np.diff(levels)
     cmin, cmax = min(levels), max(levels)
     interval = cmax-cmin
diff --git a/holoviews/streams.py b/holoviews/streams.py
index 34299d8d8..158bf9140 100644
--- a/holoviews/streams.py
+++ b/holoviews/streams.py
@@ -593,8 +593,8 @@ class Buffer(Pipe):
             if x.ndim != 2:
                 raise ValueError('Streamed array data must be two-dimensional')
             elif x.shape[1] != self.data.shape[1]:
-                raise ValueError("Streamed array data expected to have %d columns, "
-                                 "got %d." % (self.data.shape[1], x.shape[1]))
+                raise ValueError(f"Streamed array data expected to have {self.data.shape[1]} columns, "
+                                 f"got {x.shape[1]}.")
         elif isinstance(x, pd.DataFrame) and list(x.columns) != list(self.data.columns):
             raise IndexError(f"Input expected to have columns {list(self.data.columns)}, got {list(x.columns)}")
         elif isinstance(x, dict):
diff --git a/holoviews/util/__init__.py b/holoviews/util/__init__.py
index 30cadf8b5..f0e7e1251 100644
--- a/holoviews/util/__init__.py
+++ b/holoviews/util/__init__.py
@@ -659,6 +659,20 @@ class extension(_pyviz_extension):
     Helper utility used to load holoviews extensions. These can be
     plotting extensions, element extensions or anything else that can be
     registered to work with HoloViews.
+
+    The plotting extension is the most commonly used and is
+    used to select the plotting backend. The plotting extension can be
+    loaded using the backend name, e.g. 'bokeh', 'matplotlib' or 'plotly'.
+
+    Examples
+    --------
+    Activate the bokeh plotting extension:
+
+    ```python
+    import holoviews as hv
+    hv.extension("bokeh")
+    ```
+
     """
 
     # Mapping between backend name and module name
diff --git a/holoviews/util/warnings.py b/holoviews/util/warnings.py
index 2bf6366eb..50a459eb8 100644
--- a/holoviews/util/warnings.py
+++ b/holoviews/util/warnings.py
@@ -6,10 +6,10 @@ import param
 from packaging.version import Version
 
 __all__ = (
-    "deprecated",
-    "find_stack_level",
     "HoloviewsDeprecationWarning",
     "HoloviewsUserWarning",
+    "deprecated",
+    "find_stack_level",
     "warn",
 )
 
diff --git a/pyproject.toml b/pyproject.toml
index f943b7e99..9328d2c70 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -170,6 +170,7 @@ ignore = [
     "PLR2004", # Magic value used in comparison
     "PLW2901", # `for` loop variable is overwritten
     "RUF012", # Mutable class attributes should use `typing.ClassVar`
+    "RUF021", # parenthesize-chained-operators
 ]
 extend-unsafe-fixes = [
     "F401", # Unused imports


Test patch:
diff --git a/holoviews/tests/plotting/bokeh/test_curveplot.py b/holoviews/tests/plotting/bokeh/test_curveplot.py
index 042d72fb8..28eca82cf 100644
--- a/holoviews/tests/plotting/bokeh/test_curveplot.py
+++ b/holoviews/tests/plotting/bokeh/test_curveplot.py
@@ -323,7 +323,7 @@ class TestCurvePlot(TestBokehPlot):
         self.assertEqual(y_range.end, 3.3483695221017129)
 
     def test_curve_padding_datetime_square(self):
-        curve = Curve([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        curve = Curve([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = bokeh_renderer.get_plot(curve)
@@ -334,7 +334,7 @@ class TestCurvePlot(TestBokehPlot):
         self.assertEqual(y_range.end, 3.2)
 
     def test_curve_padding_datetime_nonsquare(self):
-        curve = Curve([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        curve = Curve([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1, width=600
         )
         plot = bokeh_renderer.get_plot(curve)
diff --git a/holoviews/tests/plotting/bokeh/test_histogramplot.py b/holoviews/tests/plotting/bokeh/test_histogramplot.py
index 995eb6ea3..176d37efc 100644
--- a/holoviews/tests/plotting/bokeh/test_histogramplot.py
+++ b/holoviews/tests/plotting/bokeh/test_histogramplot.py
@@ -135,7 +135,7 @@ class TestSideHistogramPlot(LoggingComparisonTestCase, TestBokehPlot):
         self.log_handler.assertContains('WARNING', 'Logarithmic axis range encountered value less than')
 
     def test_histogram_padding_datetime_square(self):
-        histogram = Histogram([(np.datetime64('2016-04-0%d' % i, 'ns'), i) for i in range(1, 4)]).opts(
+        histogram = Histogram([(np.datetime64(f'2016-04-0{i}', 'ns'), i) for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = bokeh_renderer.get_plot(histogram)
@@ -146,7 +146,7 @@ class TestSideHistogramPlot(LoggingComparisonTestCase, TestBokehPlot):
         self.assertEqual(y_range.end, 3.2)
 
     def test_histogram_padding_datetime_nonsquare(self):
-        histogram = Histogram([(np.datetime64('2016-04-0%d' % i, 'ns'), i) for i in range(1, 4)]).opts(
+        histogram = Histogram([(np.datetime64(f'2016-04-0{i}', 'ns'), i) for i in range(1, 4)]).opts(
             padding=0.1, width=600
         )
         plot = bokeh_renderer.get_plot(histogram)
diff --git a/holoviews/tests/plotting/bokeh/test_pointplot.py b/holoviews/tests/plotting/bokeh/test_pointplot.py
index 95efecc2b..f1d7fa3fe 100644
--- a/holoviews/tests/plotting/bokeh/test_pointplot.py
+++ b/holoviews/tests/plotting/bokeh/test_pointplot.py
@@ -270,7 +270,7 @@ class TestPointPlot(TestBokehPlot):
         self.assertEqual(y_range.end, 3.3483695221017129)
 
     def test_points_padding_datetime_square(self):
-        points = Points([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        points = Points([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = bokeh_renderer.get_plot(points)
@@ -281,7 +281,7 @@ class TestPointPlot(TestBokehPlot):
         self.assertEqual(y_range.end, 3.2)
 
     def test_points_padding_datetime_nonsquare(self):
-        points = Points([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        points = Points([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1, width=600
         )
         plot = bokeh_renderer.get_plot(points)
diff --git a/holoviews/tests/plotting/bokeh/test_quadmeshplot.py b/holoviews/tests/plotting/bokeh/test_quadmeshplot.py
index 81b9a467d..1b56f7a37 100644
--- a/holoviews/tests/plotting/bokeh/test_quadmeshplot.py
+++ b/holoviews/tests/plotting/bokeh/test_quadmeshplot.py
@@ -55,6 +55,33 @@ class TestQuadMeshPlot(TestBokehPlot):
         source = plot.handles['source']
         self.assertEqual(source.data['z'], flattened)
 
+    def test_quadmesh_non_sanitized_name(self):
+        # https://github.com/holoviz/holoviews/issues/6460
+        xs = [0, 1, 2]
+        ys = [2, 1, 0]
+        data = np.array([[0,1,2], [3,4,5], [6,7,8]])
+        qmesh = QuadMesh((xs, ys, data), vdims="z (x,y)")
+        plot = bokeh_renderer.get_plot(qmesh)
+        source = plot.handles['source']
+
+        flattened = np.array([6, 3, 0, 7, 4, 1, 8, 5, 2])
+        sanitized_name = 'z_left_parenthesis_x_comma_y_right_parenthesis'
+        np.testing.assert_equal(source.data[sanitized_name], flattened)
+
+    def test_quadmesh_non_sanitized_name_grid(self):
+        # https://github.com/holoviz/holoviews/issues/6460
+        xs = [0, 1, 2]
+        ys = [2, 1, 0]
+        data = np.array([[0,1,2], [3,4,5], [6,7,8]])
+        XX, YY = np.meshgrid(xs, ys)
+        qmesh = QuadMesh((XX, YY, data), vdims="z (x,y)")
+        plot = bokeh_renderer.get_plot(qmesh)
+        source = plot.handles['source']
+
+        flattened = data.flatten()
+        sanitized_name = 'z_left_parenthesis_x_comma_y_right_parenthesis'
+        np.testing.assert_equal(source.data[sanitized_name], flattened)
+
     def test_quadmesh_nodata_uint(self):
         xs = [0, 1, 2]
         ys = [2, 1, 0]
@@ -83,7 +110,7 @@ class TestQuadMeshPlot(TestBokehPlot):
                 'x': [0.5, 0.5, 0.5, 1.5, 1.5, 1.5],
                 'y': [0.5, 1.5, 2.5, 0.5, 1.5, 2.5]}
         self.assertEqual(source.data.keys(), expected.keys())
-        for key in expected:
+        for key in expected:  # noqa: PLC0206
             self.assertEqual(list(source.data[key]), expected[key])
 
     def test_quadmesh_irregular_centers(self):
@@ -103,7 +130,7 @@ class TestQuadMeshPlot(TestBokehPlot):
                     'x': [0.5, 0.5, 0.5, 1.5, 1.5],
                     'y': [0.5, 1.5, 2.5, 0.5, 1.5]}
         self.assertEqual(source.data.keys(), expected.keys())
-        for key in expected:
+        for key in expected:  # noqa: PLC0206
             self.assertEqual(list(source.data[key]), expected[key])
 
     def test_quadmesh_irregular_edges(self):
@@ -123,5 +150,5 @@ class TestQuadMeshPlot(TestBokehPlot):
                     'x': [0.5, 0.5, 0.5, 1.5, 1.5],
                     'y': [0.5, 1.5, 2.5, 0.5, 1.5]}
         self.assertEqual(source.data.keys(), expected.keys())
-        for key in expected:
+        for key in expected:  # noqa: PLC0206
             self.assertEqual(list(source.data[key]), expected[key])
diff --git a/holoviews/tests/plotting/bokeh/test_sankey.py b/holoviews/tests/plotting/bokeh/test_sankey.py
index 4a1ea73da..e78d99c16 100644
--- a/holoviews/tests/plotting/bokeh/test_sankey.py
+++ b/holoviews/tests/plotting/bokeh/test_sankey.py
@@ -27,7 +27,7 @@ class TestSankeyPlot(TestBokehPlot):
         text_data = {'x': np.array([18.75, 18.75, 1003.75, 1003.75, 1003.75]),
                      'y': np.array([125.454545, 375.454545,  48.787879, 229.090909, 430.30303 ]),
                      'text': ['A - 18', 'B - 15', 'X - 7', 'Y - 16', 'Z - 10']}
-        for k in text_data:
+        for k in text_data:  # noqa: PLC0206
             self.assertEqual(text_source.data[k], text_data[k])
 
         quad_data = {
@@ -37,7 +37,7 @@ class TestSankeyPlot(TestBokehPlot):
             'y0': [0.0, 270.9090909090908, -7.105427357601002e-15, 117.57575757575756, 360.6060606060606],
             'y1': [250.909091, 480.0, 97.575758, 340.606061, 500.0]
         }
-        for k in quad_data:
+        for k in quad_data:  # noqa: PLC0206
             self.assertEqual(quad_source.data[k], quad_data[k])
 
         self.assertEqual(patch_source.data['Value'], np.array([5, 7, 6, 2, 9, 4]))
@@ -68,13 +68,13 @@ class TestSankeyPlot(TestBokehPlot):
         text_data = {'x': np.array([18.75, 18.75, 1003.75, 1003.75, 1003.75]),
                      'y': np.array([125.454545, 375.454545,  48.787879, 229.090909, 430.30303 ]),
                      'text': ['A - 18', 'B - 15', 'X - 7', 'Y - 16', 'Z - 10']}
-        for k in text_data:
+        for k in text_data:  # noqa: PLC0206
             self.assertEqual(text_source.data[k], text_data[k])
 
         patch_data = {'start_values': ['A', 'A', 'A', 'B', 'B', 'B'],
                       'end_values': ['X', 'Y', 'Z', 'X', 'Y', 'Z'],
                       'Value': np.array([5, 7, 6, 2, 9, 4])}
-        for k in patch_data:
+        for k in patch_data:  # noqa: PLC0206
             self.assertEqual(patch_source.data[k], patch_data[k])
 
         renderers = plot.state.renderers
diff --git a/holoviews/tests/plotting/bokeh/test_spikesplot.py b/holoviews/tests/plotting/bokeh/test_spikesplot.py
index 1d5dd835c..443123430 100644
--- a/holoviews/tests/plotting/bokeh/test_spikesplot.py
+++ b/holoviews/tests/plotting/bokeh/test_spikesplot.py
@@ -88,7 +88,7 @@ class TestSpikesPlot(TestBokehPlot):
         self.assertEqual(x_range.end, 3.3483695221017129)
 
     def test_spikes_padding_datetime_square(self):
-        spikes = Spikes([np.datetime64('2016-04-0%d' % i) for i in range(1, 4)]).opts(
+        spikes = Spikes([np.datetime64(f'2016-04-0{i}') for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = bokeh_renderer.get_plot(spikes)
@@ -97,7 +97,7 @@ class TestSpikesPlot(TestBokehPlot):
         self.assertEqual(x_range.end, np.datetime64('2016-04-03T04:48:00.000000000'))
 
     def test_spikes_padding_datetime_square_heights(self):
-        spikes = Spikes([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)], vdims=['Height']).opts(
+        spikes = Spikes([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)], vdims=['Height']).opts(
             padding=0.1
         )
         plot = bokeh_renderer.get_plot(spikes)
@@ -108,7 +108,7 @@ class TestSpikesPlot(TestBokehPlot):
         self.assertEqual(y_range.end, 3.2)
 
     def test_spikes_padding_datetime_nonsquare(self):
-        spikes = Spikes([np.datetime64('2016-04-0%d' % i) for i in range(1, 4)]).opts(
+        spikes = Spikes([np.datetime64(f'2016-04-0{i}') for i in range(1, 4)]).opts(
             padding=0.1, width=600
         )
         plot = bokeh_renderer.get_plot(spikes)
diff --git a/holoviews/tests/plotting/matplotlib/test_curveplot.py b/holoviews/tests/plotting/matplotlib/test_curveplot.py
index a6500702c..706bc0c9b 100644
--- a/holoviews/tests/plotting/matplotlib/test_curveplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_curveplot.py
@@ -123,7 +123,7 @@ class TestCurvePlot(TestMPLPlot):
         self.assertEqual(y_range[1], 3.3483695221017129)
 
     def test_curve_padding_datetime_square(self):
-        curve = Curve([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        curve = Curve([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = mpl_renderer.get_plot(curve)
@@ -134,7 +134,7 @@ class TestCurvePlot(TestMPLPlot):
         self.assertEqual(y_range[1], 3.2)
 
     def test_curve_padding_datetime_nonsquare(self):
-        curve = Curve([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        curve = Curve([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1, aspect=2
         )
         plot = mpl_renderer.get_plot(curve)
diff --git a/holoviews/tests/plotting/matplotlib/test_histogramplot.py b/holoviews/tests/plotting/matplotlib/test_histogramplot.py
index a9257f5a9..bd0ad8f7d 100644
--- a/holoviews/tests/plotting/matplotlib/test_histogramplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_histogramplot.py
@@ -81,7 +81,7 @@ class TestHistogramPlot(LoggingComparisonTestCase, TestMPLPlot):
         self.log_handler.assertContains('WARNING', 'Logarithmic axis range encountered value less than')
 
     def test_histogram_padding_datetime_square(self):
-        histogram = Histogram([(np.datetime64('2016-04-0%d' % i, 'ns'), i) for i in range(1, 4)]).opts(
+        histogram = Histogram([(np.datetime64(f'2016-04-0{i}', 'ns'), i) for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = mpl_renderer.get_plot(histogram)
@@ -92,7 +92,7 @@ class TestHistogramPlot(LoggingComparisonTestCase, TestMPLPlot):
         self.assertEqual(y_range[1], 3.2)
 
     def test_histogram_padding_datetime_nonsquare(self):
-        histogram = Histogram([(np.datetime64('2016-04-0%d' % i, 'ns'), i) for i in range(1, 4)]).opts(
+        histogram = Histogram([(np.datetime64(f'2016-04-0{i}', 'ns'), i) for i in range(1, 4)]).opts(
             padding=0.1, aspect=2
         )
         plot = mpl_renderer.get_plot(histogram)
diff --git a/holoviews/tests/plotting/matplotlib/test_pointplot.py b/holoviews/tests/plotting/matplotlib/test_pointplot.py
index 8579ac881..282f95290 100644
--- a/holoviews/tests/plotting/matplotlib/test_pointplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_pointplot.py
@@ -132,7 +132,7 @@ class TestPointPlot(TestMPLPlot):
         self.assertEqual(y_range[1], 3.3483695221017129)
 
     def test_points_padding_datetime_square(self):
-        points = Points([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        points = Points([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = mpl_renderer.get_plot(points)
@@ -143,7 +143,7 @@ class TestPointPlot(TestMPLPlot):
         self.assertEqual(y_range[1], 3.2)
 
     def test_points_padding_datetime_nonsquare(self):
-        points = Points([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)]).opts(
+        points = Points([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)]).opts(
             padding=0.1, aspect=2
         )
         plot = mpl_renderer.get_plot(points)
diff --git a/holoviews/tests/plotting/matplotlib/test_spikeplot.py b/holoviews/tests/plotting/matplotlib/test_spikeplot.py
index e8cb40503..9fc2d2641 100644
--- a/holoviews/tests/plotting/matplotlib/test_spikeplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_spikeplot.py
@@ -64,7 +64,7 @@ class TestSpikesPlot(TestMPLPlot):
         self.assertEqual(x_range[1], 3.3483695221017129)
 
     def test_spikes_padding_datetime_square(self):
-        spikes = Spikes([np.datetime64('2016-04-0%d' % i) for i in range(1, 4)]).opts(
+        spikes = Spikes([np.datetime64(f'2016-04-0{i}') for i in range(1, 4)]).opts(
             padding=0.1
         )
         plot = mpl_renderer.get_plot(spikes)
@@ -73,7 +73,7 @@ class TestSpikesPlot(TestMPLPlot):
         self.assertEqual(x_range[1], 16894.2)
 
     def test_spikes_padding_datetime_square_heights(self):
-        spikes = Spikes([(np.datetime64('2016-04-0%d' % i), i) for i in range(1, 4)], vdims=['Height']).opts(
+        spikes = Spikes([(np.datetime64(f'2016-04-0{i}'), i) for i in range(1, 4)], vdims=['Height']).opts(
             padding=0.1
         )
         plot = mpl_renderer.get_plot(spikes)
@@ -84,7 +84,7 @@ class TestSpikesPlot(TestMPLPlot):
         self.assertEqual(y_range[1], 3.2)
 
     def test_spikes_padding_datetime_nonsquare(self):
-        spikes = Spikes([np.datetime64('2016-04-0%d' % i) for i in range(1, 4)]).opts(
+        spikes = Spikes([np.datetime64(f'2016-04-0{i}') for i in range(1, 4)]).opts(
             padding=0.1, aspect=2
         )
         plot = mpl_renderer.get_plot(spikes)