Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-glanceclient for 
openSUSE:Factory checked in at 2026-09-08 16:55:33
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-glanceclient (Old)
 and      /work/SRC/openSUSE:Factory/.python-glanceclient.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-glanceclient"

Tue Sep  8 16:55:33 2026 rev:46 rq:1376145 version:4.13.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-glanceclient/python-glanceclient.changes  
2026-08-09 21:37:35.974859754 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-glanceclient.new.1265/python-glanceclient.changes
        2026-09-08 16:57:41.830938880 +0200
@@ -1,0 +2,14 @@
+Mon Sep  7 09:42:52 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.13.0:
+  * Remove Python 3.10 support
+  * Add pyproject.toml to support pip 23.1
+  * Fix outdated default envlist
+  * Replace deprecated datetime.utcfromtimestamp
+  * Drop fallback to UPPER_CONSTRAINTS_FILE
+  * Apply upper constraints to build documentation
+  * Add support for cache clean and prune APIs
+  * Add support for GET /v2/cache/nodes/{image_id}
+  * Zuul: do not use USE_PYTHON3
+
+-------------------------------------------------------------------

Old:
----
  python_glanceclient-4.12.0.tar.gz

New:
----
  python_glanceclient-4.13.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-glanceclient.spec ++++++
--- /var/tmp/diff_new_pack.XmdePO/_old  2026-09-08 16:57:43.525009481 +0200
+++ /var/tmp/diff_new_pack.XmdePO/_new  2026-09-08 16:57:43.527009564 +0200
@@ -18,7 +18,7 @@
 
 %global pythons %{primary_python}
 Name:           python-glanceclient
-Version:        4.12.0
+Version:        4.13.0
 Release:        0
 Summary:        Python API and CLI for OpenStack Glance
 License:        Apache-2.0

++++++ python_glanceclient-4.12.0.tar.gz -> python_glanceclient-4.13.0.tar.gz 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/.zuul.yaml 
new/python_glanceclient-4.13.0/.zuul.yaml
--- old/python_glanceclient-4.12.0/.zuul.yaml   2026-05-19 11:25:56.000000000 
+0200
+++ new/python_glanceclient-4.13.0/.zuul.yaml   2026-08-27 12:28:29.282551500 
+0200
@@ -59,13 +59,6 @@
     vars:
       tox_envlist: py3
 
-- job:
-    name: glanceclient-dsvm-functional-py3
-    parent: glanceclient-dsvm-functional
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: true
-
 - project:
     templates:
       - check-requirements
@@ -98,6 +91,3 @@
             branches: master
         - glanceclient-tox-py3-oslo-tips:
             branches: master
-    experimental:
-      jobs:
-        - glanceclient-dsvm-functional-py3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/ChangeLog 
new/python_glanceclient-4.13.0/ChangeLog
--- old/python_glanceclient-4.12.0/ChangeLog    2026-05-19 11:26:32.000000000 
+0200
+++ new/python_glanceclient-4.13.0/ChangeLog    2026-08-27 12:29:02.659659000 
+0200
@@ -1,6 +1,19 @@
 CHANGES
 =======
 
+4.13.0
+------
+
+* Remove Python 3.10 support
+* Add pyproject.toml to support pip 23.1
+* Fix outdated default envlist
+* Replace deprecated datetime.utcfromtimestamp
+* Drop fallback to UPPER\_CONSTRAINTS\_FILE
+* Apply upper constraints to build documentation
+* Add support for cache clean and prune APIs
+* Add support for GET /v2/cache/nodes/{image\_id}
+* Zuul: do not use USE\_PYTHON3
+
 4.12.0
 ------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/PKG-INFO 
new/python_glanceclient-4.13.0/PKG-INFO
--- old/python_glanceclient-4.12.0/PKG-INFO     2026-05-19 11:26:32.340457000 
+0200
+++ new/python_glanceclient-4.13.0/PKG-INFO     2026-08-27 12:29:02.760661000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: python-glanceclient
-Version: 4.12.0
+Version: 4.13.0
 Summary: OpenStack Image API Client Library
 Home-page: https://docs.openstack.org/python-glanceclient/latest/
 Author: OpenStack
@@ -17,10 +17,10 @@
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
-Requires-Python: >=3.10
+Classifier: Programming Language :: Python :: 3.13
+Requires-Python: >=3.11
 License-File: LICENSE
 Requires-Dist: pbr!=2.1.0,>=2.0.0
 Requires-Dist: PrettyTable>=0.7.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/glanceclient/common/utils.py 
new/python_glanceclient-4.13.0/glanceclient/common/utils.py
--- old/python_glanceclient-4.12.0/glanceclient/common/utils.py 2026-05-19 
11:25:56.000000000 +0200
+++ new/python_glanceclient-4.13.0/glanceclient/common/utils.py 2026-08-27 
12:28:29.284551600 +0200
@@ -208,15 +208,18 @@
                 if last_accessed == 0:
                     last_accessed = "N/A"
                 else:
-                    last_accessed = datetime.datetime.utcfromtimestamp(
-                        last_accessed).isoformat()
+                    last_accessed = datetime.datetime.fromtimestamp(
+                        last_accessed, tz=datetime.timezone.utc
+                    ).replace(tzinfo=None).isoformat()
 
-                cache_pt.add_row((image['image_id'], state,
-                                  last_accessed,
-                                  datetime.datetime.utcfromtimestamp(
-                                      image['last_modified']).isoformat(),
-                                  image['size'],
-                                  image['hits']))
+                cache_pt.add_row((
+                    image['image_id'], state,
+                    last_accessed,
+                    datetime.datetime.fromtimestamp(
+                        image['last_modified'], tz=datetime.timezone.utc
+                    ).replace(tzinfo=None).isoformat(),
+                    image['size'],
+                    image['hits']))
         else:
             for image in cached_images[item]:
                 cache_pt.add_row((image,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/glanceclient/tests/unit/v2/test_cache.py 
new/python_glanceclient-4.13.0/glanceclient/tests/unit/v2/test_cache.py
--- old/python_glanceclient-4.12.0/glanceclient/tests/unit/v2/test_cache.py     
2026-05-19 11:25:56.000000000 +0200
+++ new/python_glanceclient-4.13.0/glanceclient/tests/unit/v2/test_cache.py     
2026-08-27 12:28:29.287551600 +0200
@@ -64,6 +64,27 @@
             '',
         ),
     },
+    '/v2/cache/nodes/3a4560a1-e585-443e-9b39-553b46ec92d1': {
+        'GET': (
+            {},
+            ['http://node1.example', 'http://node2.example'],
+        ),
+    },
+    '/v2/cache/clean': {
+        'POST': (
+            {},
+            '',
+        ),
+    },
+    '/v2/cache/prune': {
+        'POST': (
+            {},
+            {
+                'total_files_pruned': 5,
+                'total_bytes_pruned': 104857600,
+            },
+        ),
+    },
 }
 
 
@@ -133,3 +154,68 @@
         mock_has_version.return_value = False
         self.assertRaises(exc.HTTPNotImplemented,
                           self.controller.list)
+
+    @mock.patch.object(common_utils, 'has_version')
+    def test_list_cached_nodes(self, mock_has_version):
+        mock_has_version.return_value = True
+        image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
+        nodes = self.controller.list_cached_nodes(image_id)
+        self.assertEqual(['http://node1.example', 'http://node2.example'],
+                         list(nodes))
+        expect = [('GET', '/v2/cache/nodes/%s' % image_id, {}, None)]
+        self.assertEqual(expect, self.api.calls)
+
+    @mock.patch.object(common_utils, 'has_version')
+    def test_list_cached_nodes_empty(self, mock_has_version):
+        mock_has_version.return_value = True
+        image_id = 'df601a47-7251-4d20-84ae-07de335af424'
+        dummy_fixtures = {
+            '/v2/cache/nodes/%s' % image_id: {
+                'GET': (
+                    {},
+                    [],
+                ),
+            }
+        }
+        dummy_api = utils.FakeAPI(dummy_fixtures)
+        dummy_controller = cache.Controller(dummy_api)
+        nodes = dummy_controller.list_cached_nodes(image_id)
+        self.assertEqual([], list(nodes))
+        self.assertEqual(
+            [('GET', '/v2/cache/nodes/%s' % image_id, {}, None)],
+            dummy_api.calls)
+
+    @mock.patch.object(common_utils, 'has_version')
+    def test_list_cached_nodes_not_supported(self, mock_has_version):
+        mock_has_version.return_value = False
+        self.assertRaises(exc.HTTPNotImplemented,
+                          self.controller.list_cached_nodes,
+                          '3a4560a1-e585-443e-9b39-553b46ec92d1')
+
+    @mock.patch.object(common_utils, 'has_version')
+    def test_cache_clean(self, mock_has_version):
+        mock_has_version.return_value = True
+        self.controller.clean()
+        expect = [('POST', '/v2/cache/clean', {}, None)]
+        self.assertEqual(expect, self.api.calls)
+
+    @mock.patch.object(common_utils, 'has_version')
+    def test_cache_prune(self, mock_has_version):
+        mock_has_version.return_value = True
+        result = self.controller.prune()
+        expect = [('POST', '/v2/cache/prune', {}, None)]
+        self.assertEqual(expect, self.api.calls)
+        self.assertEqual(5, result['total_files_pruned'])
+        self.assertEqual(104857600, result['total_bytes_pruned'])
+
+    @mock.patch.object(common_utils, 'has_version')
+    def test_cache_clean_not_supported(self, mock_has_version):
+        mock_has_version.return_value = False
+        self.assertRaises(exc.HTTPNotImplemented,
+                          self.controller.clean)
+
+    @mock.patch.object(common_utils, 'has_version')
+    def test_cache_prune_not_supported(self, mock_has_version):
+        mock_has_version.return_value = False
+        self.assertRaises(exc.HTTPNotImplemented,
+                          self.controller.prune)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/glanceclient/tests/unit/v2/test_shell_v2.py 
new/python_glanceclient-4.13.0/glanceclient/tests/unit/v2/test_shell_v2.py
--- old/python_glanceclient-4.12.0/glanceclient/tests/unit/v2/test_shell_v2.py  
2026-05-19 11:25:56.000000000 +0200
+++ new/python_glanceclient-4.13.0/glanceclient/tests/unit/v2/test_shell_v2.py  
2026-08-27 12:28:29.311551600 +0200
@@ -3799,6 +3799,106 @@
                 'Direct server endpoint needs to be provided. Do '
                 'not use loadbalanced or catalog endpoints.')
 
+    def test_do_cache_nodes_list(self):
+        args = argparse.Namespace(
+            id='3a4560a1-e585-443e-9b39-553b46ec92d1')
+        expected_nodes = ['http://node1.example', 'http://node2.example']
+        with mock.patch.object(
+                self.gc.cache, 'list_cached_nodes') as mocked_list_nodes:
+            mocked_list_nodes.return_value = expected_nodes
+            test_shell.do_cache_nodes_list(self.gc, args)
+            mocked_list_nodes.assert_called_once_with(args.id)
+        objs, fields = utils.print_list.call_args[0]
+        self.assertEqual(['Node Reference URL'], fields)
+        self.assertEqual(2, len(objs))
+        self.assertEqual('http://node1.example', objs[0].node_reference_url)
+        self.assertEqual('http://node2.example', objs[1].node_reference_url)
+
+    def test_do_cache_nodes_list_empty(self):
+        args = argparse.Namespace(
+            id='3a4560a1-e585-443e-9b39-553b46ec92d1')
+        with mock.patch.object(
+                self.gc.cache, 'list_cached_nodes') as mocked_list_nodes:
+            mocked_list_nodes.return_value = []
+            test_shell.do_cache_nodes_list(self.gc, args)
+        objs, fields = utils.print_list.call_args[0]
+        self.assertEqual(['Node Reference URL'], fields)
+        self.assertEqual(0, len(objs))
+
+    def test_do_cache_nodes_list_unsupported(self):
+        args = argparse.Namespace(
+            id='3a4560a1-e585-443e-9b39-553b46ec92d1')
+        with mock.patch.object(
+                self.gc.cache, 'list_cached_nodes') as mocked_list_nodes:
+            mocked_list_nodes.side_effect = exc.HTTPNotImplemented
+            self.assertRaises(exc.HTTPNotImplemented,
+                              test_shell.do_cache_nodes_list,
+                              self.gc, args)
+
+    def test_do_cache_nodes_list_forbidden(self):
+        image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
+        args = argparse.Namespace(id=image_id)
+        with mock.patch.object(
+                self.gc.cache, 'list_cached_nodes') as mocked_list_nodes:
+            mocked_list_nodes.side_effect = exc.HTTPForbidden
+            with mock.patch(
+                    'glanceclient.common.utils.print_err') as mock_print_err:
+                test_shell.do_cache_nodes_list(self.gc, args)
+            mock_print_err.assert_called_once_with(
+                "You are not permitted to list cached nodes for image '%s'."
+                % image_id)
+
+    def test_do_cache_nodes_list_conflict(self):
+        image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
+        args = argparse.Namespace(id=image_id)
+        with mock.patch.object(
+                self.gc.cache, 'list_cached_nodes') as mocked_list_nodes:
+            mocked_list_nodes.side_effect = exc.HTTPConflict
+            with mock.patch(
+                    'glanceclient.common.utils.print_err') as mock_print_err:
+                test_shell.do_cache_nodes_list(self.gc, args)
+            mock_print_err.assert_called_once_with(
+                "'%s': Unable to list cached nodes for image '%s'."
+                % (exc.HTTPConflict(), image_id))
+
+    def test_do_cache_nodes_list_not_found(self):
+        image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
+        args = argparse.Namespace(id=image_id)
+        with mock.patch.object(
+                self.gc.cache, 'list_cached_nodes') as mocked_list_nodes:
+            mocked_list_nodes.side_effect = exc.HTTPNotFound
+            with mock.patch(
+                    'glanceclient.common.utils.print_err') as mock_print_err:
+                test_shell.do_cache_nodes_list(self.gc, args)
+            mock_print_err.assert_called_once_with(
+                "'%s': Unable to list cached nodes for image '%s'."
+                % (exc.HTTPNotFound(), image_id))
+
+    def test_do_cache_nodes_list_http_exception(self):
+        image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
+        args = argparse.Namespace(id=image_id)
+        with mock.patch.object(
+                self.gc.cache, 'list_cached_nodes') as mocked_list_nodes:
+            mocked_list_nodes.side_effect = exc.HTTPBadRequest
+            with mock.patch(
+                    'glanceclient.common.utils.print_err') as mock_print_err:
+                test_shell.do_cache_nodes_list(self.gc, args)
+            mock_print_err.assert_called_once_with(
+                "'%s': Unable to list cached nodes for image '%s'."
+                % (exc.HTTPBadRequest(), image_id))
+
+    def test_do_cache_nodes_list_endpoint_not_provided(self):
+        args = argparse.Namespace(
+            id='3a4560a1-e585-443e-9b39-553b46ec92d1')
+        self.gc.endpoint_provided = False
+        with mock.patch('glanceclient.common.utils.exit') as mock_exit:
+            mock_exit.side_effect = self._mock_utils_exit
+            with self.assertRaises(SystemExit):
+                test_shell.do_cache_nodes_list(self.gc, args)
+            mock_exit.assert_called_once_with(
+                'Direct server endpoint needs to be provided. Do '
+                'not use loadbalanced or catalog endpoints.')
+
     def _test_cache_queue(self, supported=True, forbidden=False,):
         args = argparse.Namespace(id=['image1'])
         with mock.patch.object(self.gc.cache, 'queue') as mocked_cache_queue:
@@ -3931,3 +4031,91 @@
             mock_exit.assert_called_once_with(
                 'Direct server endpoint needs to be provided. Do '
                 'not use loadbalanced or catalog endpoints.')
+
+    def _test_cache_clean(self, supported=True, forbidden=False):
+        args = self._make_args({})
+        with mock.patch.object(self.gc.cache, 'clean') as mocked_cache_clean:
+            if supported:
+                mocked_cache_clean.return_value = None
+            else:
+                mocked_cache_clean.side_effect = exc.HTTPNotImplemented
+            if forbidden:
+                mocked_cache_clean.side_effect = exc.HTTPForbidden
+
+            test_shell.do_cache_clean(self.gc, args)
+            if supported:
+                mocked_cache_clean.assert_called_once_with()
+
+    def test_do_cache_clean(self):
+        self._test_cache_clean()
+
+    def test_do_cache_clean_unsupported(self):
+        with mock.patch(
+                'glanceclient.common.utils.print_err') as mock_print_err:
+            self._test_cache_clean(supported=False)
+            mock_print_err.assert_called_once_with(
+                "'HTTP HTTPNotImplemented': Unable to clean the image cache.")
+
+    def test_do_cache_clean_forbidden(self):
+        with mock.patch(
+                'glanceclient.common.utils.print_err') as mock_print_err:
+            self._test_cache_clean(forbidden=True)
+            mock_print_err.assert_called_once_with(
+                "You are not permitted to clean the image cache.")
+
+    def test_do_cache_clean_endpoint_not_provided(self):
+        args = self._make_args({})
+        self.gc.endpoint_provided = False
+        with mock.patch('glanceclient.common.utils.exit') as mock_exit:
+            test_shell.do_cache_clean(self.gc, args)
+            mock_exit.assert_called_once_with(
+                'Direct server endpoint needs to be provided. Do '
+                'not use loadbalanced or catalog endpoints.')
+
+    def _test_cache_prune(self, supported=True, forbidden=False):
+        args = self._make_args({})
+        with mock.patch.object(self.gc.cache, 'prune') as mocked_cache_prune:
+            if supported:
+                mocked_cache_prune.return_value = {
+                    'total_files_pruned': 5,
+                    'total_bytes_pruned': 104857600,
+                }
+            else:
+                mocked_cache_prune.side_effect = exc.HTTPNotImplemented
+            if forbidden:
+                mocked_cache_prune.side_effect = exc.HTTPForbidden
+
+            with mock.patch('builtins.print') as mock_print:
+                test_shell.do_cache_prune(self.gc, args)
+                if supported and not forbidden:
+                    mocked_cache_prune.assert_called_once_with()
+                    mock_print.assert_called_once_with(
+                        'Pruned 5 file(s), 104857600 byte(s).')
+
+    def test_do_cache_prune(self):
+        self._test_cache_prune()
+
+    def test_do_cache_prune_unsupported(self):
+        with mock.patch(
+                'glanceclient.common.utils.print_err') as mock_print_err:
+            self._test_cache_prune(supported=False)
+            mock_print_err.assert_called_once_with(
+                "'HTTP HTTPNotImplemented': Unable to prune the image cache.")
+
+    def test_do_cache_prune_forbidden(self):
+        with mock.patch(
+                'glanceclient.common.utils.print_err') as mock_print_err:
+            self._test_cache_prune(forbidden=True)
+            mock_print_err.assert_called_once_with(
+                "You are not permitted to prune the image cache.")
+
+    def test_do_cache_prune_endpoint_not_provided(self):
+        args = self._make_args({})
+        self.gc.endpoint_provided = False
+        with mock.patch('glanceclient.common.utils.exit') as mock_exit:
+            mock_exit.side_effect = SystemExit
+            self.assertRaises(SystemExit,
+                              test_shell.do_cache_prune, self.gc, args)
+            mock_exit.assert_called_once_with(
+                'Direct server endpoint needs to be provided. Do not use '
+                'loadbalanced or catalog endpoints.')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/glanceclient/v2/cache.py 
new/python_glanceclient-4.13.0/glanceclient/v2/cache.py
--- old/python_glanceclient-4.12.0/glanceclient/v2/cache.py     2026-05-19 
11:25:56.000000000 +0200
+++ new/python_glanceclient-4.13.0/glanceclient/v2/cache.py     2026-08-27 
12:28:29.291551600 +0200
@@ -28,7 +28,7 @@
             return True
         else:
             raise exc.HTTPNotImplemented(
-                'Glance does not support image caching API (v2.14)')
+                'Glance does not support image caching API (%s)' % version)
 
     @utils.add_req_id_to_object()
     def list(self):
@@ -60,3 +60,24 @@
             url = '/v2/cache/%s' % image_id
             resp, body = self.http_client.put(url)
             return body, resp
+
+    @utils.add_req_id_to_object()
+    def list_cached_nodes(self, image_id):
+        if self.is_supported('v2.14'):
+            url = '/v2/cache/nodes/%s' % image_id
+            resp, body = self.http_client.get(url)
+            return body, resp
+
+    @utils.add_req_id_to_object()
+    def clean(self):
+        if self.is_supported('v2.18'):
+            url = '/v2/cache/clean'
+            resp, body = self.http_client.post(url)
+            return body, resp
+
+    @utils.add_req_id_to_object()
+    def prune(self):
+        if self.is_supported('v2.18'):
+            url = '/v2/cache/prune'
+            resp, body = self.http_client.post(url)
+            return body, resp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/glanceclient/v2/shell.py 
new/python_glanceclient-4.13.0/glanceclient/v2/shell.py
--- old/python_glanceclient-4.12.0/glanceclient/v2/shell.py     2026-05-19 
11:25:56.000000000 +0200
+++ new/python_glanceclient-4.13.0/glanceclient/v2/shell.py     2026-08-27 
12:28:29.292551500 +0200
@@ -16,6 +16,7 @@
 import json
 import os
 import sys
+from types import SimpleNamespace
 
 from oslo_utils import strutils
 
@@ -1642,6 +1643,41 @@
             utils.print_err(msg)
 
 
+def do_cache_clean(gc, args):
+    """Clean invalid and stalled cached images."""
+    if not gc.endpoint_provided:
+        utils.exit("Direct server endpoint needs to be provided. Do not use "
+                   "loadbalanced or catalog endpoints.")
+    try:
+        gc.cache.clean()
+    except exc.HTTPForbidden:
+        msg = _("You are not permitted to clean the image cache.")
+        utils.print_err(msg)
+    except exc.HTTPException as e:
+        msg = _("'%s': Unable to clean the image cache." % e)
+        utils.print_err(msg)
+
+
+def do_cache_prune(gc, args):
+    """Prune cached images to reduce cache size."""
+    if not gc.endpoint_provided:
+        utils.exit("Direct server endpoint needs to be provided. Do not use "
+                   "loadbalanced or catalog endpoints.")
+    try:
+        result = gc.cache.prune()
+        if result:
+            print(_("Pruned %(files)d file(s), %(bytes)d byte(s).") % {
+                'files': result.get('total_files_pruned', 0),
+                'bytes': result.get('total_bytes_pruned', 0),
+            })
+    except exc.HTTPForbidden:
+        msg = _("You are not permitted to prune the image cache.")
+        utils.print_err(msg)
+    except exc.HTTPException as e:
+        msg = _("'%s': Unable to prune the image cache." % e)
+        utils.print_err(msg)
+
+
 def do_cache_list(gc, args):
     """Get cache state."""
     if not gc.endpoint_provided:
@@ -1651,6 +1687,38 @@
     utils.print_cached_images(cached_images)
 
 
[email protected]('id', metavar='<IMAGE_ID>',
+           help=_('ID of the image.'))
+def do_cache_nodes_list(gc, args):
+    """List node reference URLs where an image is cached."""
+    if not gc.endpoint_provided:
+        utils.exit("Direct server endpoint needs to be provided. Do not use "
+                   "loadbalanced or catalog endpoints.")
+    try:
+        nodes = gc.cache.list_cached_nodes(args.id)
+    except exc.HTTPForbidden:
+        msg = _("You are not permitted to list cached nodes for image '%s'.")
+        utils.print_err(msg % args.id)
+        return
+    except exc.HTTPConflict as e:
+        msg = _("'%s': Unable to list cached nodes for image '%s'.")
+        utils.print_err(msg % (e, args.id))
+        return
+    except exc.HTTPNotFound as e:
+        msg = _("'%s': Unable to list cached nodes for image '%s'.")
+        utils.print_err(msg % (e, args.id))
+        return
+    except exc.HTTPNotImplemented:
+        raise
+    except exc.HTTPException as e:
+        msg = _("'%s': Unable to list cached nodes for image '%s'.")
+        utils.print_err(msg % (e, args.id))
+        return
+
+    rows = [SimpleNamespace(node_reference_url=u) for u in nodes]
+    utils.print_list(rows, ['Node Reference URL'])
+
+
 @utils.arg('id', metavar='<IMAGE_ID>', nargs='+',
            help=_('ID of image(s) to queue for caching.'))
 def do_cache_queue(gc, args):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/pyproject.toml 
new/python_glanceclient-4.13.0/pyproject.toml
--- old/python_glanceclient-4.12.0/pyproject.toml       1970-01-01 
01:00:00.000000000 +0100
+++ new/python_glanceclient-4.13.0/pyproject.toml       2026-08-27 
12:28:29.293551400 +0200
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["pbr>=6.1.1"]
+build-backend = "pbr.build"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/python_glanceclient.egg-info/PKG-INFO 
new/python_glanceclient-4.13.0/python_glanceclient.egg-info/PKG-INFO
--- old/python_glanceclient-4.12.0/python_glanceclient.egg-info/PKG-INFO        
2026-05-19 11:26:32.000000000 +0200
+++ new/python_glanceclient-4.13.0/python_glanceclient.egg-info/PKG-INFO        
2026-08-27 12:29:02.707660000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: python-glanceclient
-Version: 4.12.0
+Version: 4.13.0
 Summary: OpenStack Image API Client Library
 Home-page: https://docs.openstack.org/python-glanceclient/latest/
 Author: OpenStack
@@ -17,10 +17,10 @@
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
-Requires-Python: >=3.10
+Classifier: Programming Language :: Python :: 3.13
+Requires-Python: >=3.11
 License-File: LICENSE
 Requires-Dist: pbr!=2.1.0,>=2.0.0
 Requires-Dist: PrettyTable>=0.7.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/python_glanceclient.egg-info/SOURCES.txt 
new/python_glanceclient-4.13.0/python_glanceclient.egg-info/SOURCES.txt
--- old/python_glanceclient-4.12.0/python_glanceclient.egg-info/SOURCES.txt     
2026-05-19 11:26:32.000000000 +0200
+++ new/python_glanceclient-4.13.0/python_glanceclient.egg-info/SOURCES.txt     
2026-08-27 12:29:02.734660400 +0200
@@ -8,6 +8,7 @@
 HACKING.rst
 LICENSE
 README.rst
+pyproject.toml
 requirements.txt
 run_tests.sh
 setup.cfg
@@ -126,6 +127,8 @@
 releasenotes/notes/add_new_locations_apis_support-1ceb47178d384d58.yaml
 releasenotes/notes/boolean-properties-strict-checking-bdd624b5da81e723.yaml
 releasenotes/notes/bp-use-keystoneauth-e12f300e58577b13.yaml
+releasenotes/notes/cache-clean-prune-api-support.yaml
+releasenotes/notes/cache-nodes-list-command.yaml
 releasenotes/notes/check-for-md5-59db8fd67870b214.yaml
 releasenotes/notes/copy-existing-image-619b7e6bc3394446.yaml
 releasenotes/notes/del_from_store-2d807c3038283907.yaml
@@ -145,6 +148,7 @@
 releasenotes/notes/multihash-filter-ef2a48dc48fae9dc.yaml
 releasenotes/notes/multihash-support-f1474590cf3ef5cf.yaml
 releasenotes/notes/pike-relnote-2c77b01aa8799f35.yaml
+releasenotes/notes/remove-py310-5ee8bf147676093b.yaml
 releasenotes/notes/remove-py38-e14e0516991682eb.yaml
 releasenotes/notes/remove-py39-63731125072287a3.yaml
 releasenotes/notes/return-request-id-to-caller-47f4c0a684b1d88e.yaml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/python_glanceclient.egg-info/pbr.json 
new/python_glanceclient-4.13.0/python_glanceclient.egg-info/pbr.json
--- old/python_glanceclient-4.12.0/python_glanceclient.egg-info/pbr.json        
2026-05-19 11:26:32.000000000 +0200
+++ new/python_glanceclient-4.13.0/python_glanceclient.egg-info/pbr.json        
2026-08-27 12:29:02.707660000 +0200
@@ -1 +1 @@
-{"git_version": "0b06bb4", "is_release": true}
\ No newline at end of file
+{"git_version": "4fb657b", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/releasenotes/notes/cache-clean-prune-api-support.yaml
 
new/python_glanceclient-4.13.0/releasenotes/notes/cache-clean-prune-api-support.yaml
--- 
old/python_glanceclient-4.12.0/releasenotes/notes/cache-clean-prune-api-support.yaml
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/python_glanceclient-4.13.0/releasenotes/notes/cache-clean-prune-api-support.yaml
        2026-08-27 12:28:29.293551400 +0200
@@ -0,0 +1,14 @@
+---
+features:
+  - |
+    Client support has been added for the Glance Image API v2.18 cache
+    maintenance endpoints. The following commands have been added to the
+    command line interface:
+
+    * ``cache-clean`` - Clean invalid cache entries and stalled incomplete
+      images
+    * ``cache-prune`` - Prune cached images when the cache size exceeds the
+      maximum configured size
+
+    These commands require a direct glance-api server endpoint and are only
+    available when image caching is enabled on the Glance service.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/releasenotes/notes/cache-nodes-list-command.yaml 
new/python_glanceclient-4.13.0/releasenotes/notes/cache-nodes-list-command.yaml
--- 
old/python_glanceclient-4.12.0/releasenotes/notes/cache-nodes-list-command.yaml 
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/python_glanceclient-4.13.0/releasenotes/notes/cache-nodes-list-command.yaml 
    2026-08-27 12:28:29.293551400 +0200
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add support for the Glance image-cache API ``GET 
/v2/cache/nodes/{image_id}``
+    (list node reference URLs where an image is cached when centralized caching
+    is enabled). This adds the ``cache-nodes-list`` shell command and the
+    ``list_cached_nodes`` method on the v2 cache controller.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python_glanceclient-4.12.0/releasenotes/notes/remove-py310-5ee8bf147676093b.yaml
 
new/python_glanceclient-4.13.0/releasenotes/notes/remove-py310-5ee8bf147676093b.yaml
--- 
old/python_glanceclient-4.12.0/releasenotes/notes/remove-py310-5ee8bf147676093b.yaml
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/python_glanceclient-4.13.0/releasenotes/notes/remove-py310-5ee8bf147676093b.yaml
        2026-08-27 12:28:29.294551600 +0200
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - |
+    Support for Python 3.10 has been removed. Now the minimum python version
+    supported is 3.11.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/setup.cfg 
new/python_glanceclient-4.13.0/setup.cfg
--- old/python_glanceclient-4.12.0/setup.cfg    2026-05-19 11:26:32.340457000 
+0200
+++ new/python_glanceclient-4.13.0/setup.cfg    2026-08-27 12:29:02.761661000 
+0200
@@ -7,7 +7,7 @@
 author = OpenStack
 author_email = [email protected]
 home_page = https://docs.openstack.org/python-glanceclient/latest/
-python_requires = >=3.10
+python_requires = >=3.11
 classifier = 
        Development Status :: 5 - Production/Stable
        Environment :: Console
@@ -20,9 +20,9 @@
        Programming Language :: Python :: Implementation :: CPython
        Programming Language :: Python :: 3 :: Only
        Programming Language :: Python :: 3
-       Programming Language :: Python :: 3.10
        Programming Language :: Python :: 3.11
        Programming Language :: Python :: 3.12
+       Programming Language :: Python :: 3.13
 
 [files]
 packages = 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python_glanceclient-4.12.0/tox.ini 
new/python_glanceclient-4.13.0/tox.ini
--- old/python_glanceclient-4.12.0/tox.ini      2026-05-19 11:25:56.000000000 
+0200
+++ new/python_glanceclient-4.13.0/tox.ini      2026-08-27 12:28:29.298551600 
+0200
@@ -1,5 +1,5 @@
 [tox]
-envlist = py39,pep8
+envlist = py3,pep8
 minversion = 3.18.0
 
 [testenv]
@@ -7,15 +7,10 @@
 setenv = OS_STDOUT_NOCAPTURE=False
          OS_STDERR_NOCAPTURE=False
          PYTHONDONTWRITEBYTECODE=1
-
-# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might
-# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the
-# first one that is defined. If none of them is defined, we fallback to the
-# default value.
 deps =
-       
-c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}}
-       -r{toxinidir}/requirements.txt
-       -r{toxinidir}/test-requirements.txt
+  
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+  -r{toxinidir}/requirements.txt
+  -r{toxinidir}/test-requirements.txt
 commands = stestr run --slowest {posargs}
 
 [testenv:pep8]
@@ -54,6 +49,7 @@
 [testenv:docs]
 basepython = python3
 deps =
+  
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/requirements.txt
   -r{toxinidir}/doc/requirements.txt
 commands =
@@ -62,12 +58,8 @@
 
 [testenv:releasenotes]
 basepython = python3
-# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might
-# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the
-# first one that is defined. If none of them is defined, we fallback to the
-# default value.
 deps =
-  
-c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}}
+  
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/doc/requirements.txt
 commands =
   sphinx-build -a -E -W -d releasenotes/build/doctrees -b html 
releasenotes/source releasenotes/build/html

Reply via email to