[Git][debian-gis-team/mapproxy][master] 6 commits: New upstream version 6.1.1+dfsg

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Wed Jun 17 14:35:24 BST 2026



Bas Couwenberg pushed to branch master at Debian GIS Project / mapproxy


Commits:
a4406513 by Bas Couwenberg at 2026-06-17T15:21:06+02:00
New upstream version 6.1.1+dfsg
- - - - -
307613b3 by Bas Couwenberg at 2026-06-17T15:21:11+02:00
Update upstream source from tag 'upstream/6.1.1+dfsg'

Update to upstream version '6.1.1+dfsg'
with Debian dir a0bbc1692b633c41cf6c5d60b2ee1499ce1e9801
- - - - -
ac035b41 by Bas Couwenberg at 2026-06-17T15:21:57+02:00
New upstream release.

- - - - -
19c123d1 by Bas Couwenberg at 2026-06-17T15:23:22+02:00
Refresh patches.

- - - - -
5879551c by Bas Couwenberg at 2026-06-17T15:28:01+02:00
Update lintian overrides.

- - - - -
da49ea1a by Bas Couwenberg at 2026-06-17T15:28:01+02:00
Set distribution to unstable.

- - - - -


22 changed files:

- .github/workflows/post-comment.yml
- .github/workflows/test.yml
- .gitignore
- CHANGES.txt
- debian/changelog
- debian/patches/xfail.patch
- debian/python3-mapproxy.lintian-overrides
- mapproxy/config/configuration/proxy.py
- mapproxy/image/__init__.py
- mapproxy/service/demo.py
- mapproxy/service/ogcapi/api.py
- mapproxy/service/ogcapi/server.py
- mapproxy/service/templates/ogcapi/_base.html
- mapproxy/service/templates/ogcapi/collections/collection.html
- mapproxy/service/templates/ogcapi/landing_page.html
- mapproxy/service/templates/ogcapi/openapi/redoc.html
- mapproxy/service/templates/ogcapi/openapi/swagger.html
- mapproxy/service/templates/ogcapi/tilesets/tileset.html
- mapproxy/test/unit/test_conf_loader.py
- mapproxy/test/unit/test_image.py
- requirements-tests.txt
- setup.py


Changes:

=====================================
.github/workflows/post-comment.yml
=====================================
@@ -15,7 +15,7 @@ jobs:
       pull-requests: write
     steps:
       - name: Download artifact
-        uses: actions/download-artifact at v4
+        uses: actions/download-artifact at v8
         with:
           name: coverage-report
           run-id: ${{ github.event.workflow_run.id }}


=====================================
.github/workflows/test.yml
=====================================
@@ -131,7 +131,7 @@ jobs:
           echo "${{ github.event.number }}" > pr_number.txt
 
       - name: Upload Coverage Artifact
-        uses: actions/upload-artifact at v4
+        uses: actions/upload-artifact at v7
         if: github.event_name == 'pull_request'
         with:
           name: coverage-report


=====================================
.gitignore
=====================================
@@ -16,7 +16,7 @@ nosetests*.xml
 .pydevproject
 .tox/
 /cache_data/
-
+venv/
 .idea/
 *.iml
 .cursor/


=====================================
CHANGES.txt
=====================================
@@ -1,3 +1,18 @@
+6.1.1 2026-06-17
+~~~~~~~~~~~~~~~~
+
+Maintenance:
+
+  - Update dependencies
+
+Fixes:
+
+  - ImageResult.as_buffer will always return image mode rgba if requested
+  - Restore context manager protocol on ProxyConfiguration
+  - Fix base path for rendering OGC API HTML pages in multi mapproxy setups
+  - Fix determination of resolutions in WMTS demo
+
+
 6.1.0 2026-06-03
 ~~~~~~~~~~~~~~~~
 


=====================================
debian/changelog
=====================================
@@ -1,3 +1,11 @@
+mapproxy (6.1.1+dfsg-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Refresh patches.
+  * Update lintian overrides.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Wed, 17 Jun 2026 15:23:23 +0200
+
 mapproxy (6.1.0+dfsg-1) unstable; urgency=medium
 
   * New upstream release.


=====================================
debian/patches/xfail.patch
=====================================
@@ -136,7 +136,7 @@ Forwarded: not-needed
      date = parsedate(date)
 --- a/mapproxy/test/unit/test_image.py
 +++ b/mapproxy/test/unit/test_image.py
-@@ -327,6 +327,7 @@ class TestMergeAll(object):
+@@ -355,6 +355,7 @@ class TestMergeAll(object):
          assert img.size == (300, 300)
          assert img.getcolors() == [(80000, (255, 255, 255)), (10000, (0, 0, 0))]
  
@@ -144,7 +144,7 @@ Forwarded: not-needed
      def test_invalid_tile(self):
          self.cleanup_tiles = [create_tmp_image_file((100, 100)) for _ in range(9)]
          self.tiles = [ImageResult(tile) for tile in self.cleanup_tiles]
-@@ -745,6 +746,7 @@ class TestMakeTransparent(object):
+@@ -789,6 +790,7 @@ class TestMakeTransparent(object):
          colors = img.getcolors()
          assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))]
  


=====================================
debian/python3-mapproxy.lintian-overrides
=====================================
@@ -4,3 +4,6 @@ privacy-breach-generic *
 # Not a problem
 package-contains-documentation-outside-usr-share-doc [usr/lib/python3/dist-packages/mapproxy/test/helper_scripts/README.md]
 
+# False positive
+package-contains-documentation-outside-usr-share-doc [usr/lib/python3/dist-packages/*.dist-info/*.txt]
+


=====================================
mapproxy/config/configuration/proxy.py
=====================================
@@ -186,11 +186,18 @@ class ProxyConfiguration(object):
         self.services = ServiceConfiguration(self.configuration.get('services', {}), context=self)
 
     def configured_services(self):
+        with self:
+            return self.services.services()
+
+    def __enter__(self):
+        # push local base_config onto config stack
         import mapproxy.config.config
         mapproxy.config.config._config.push(self.base_config)
-        services = self.services.services()
+
+    def __exit__(self, type, value, traceback):
+        # pop local base_config from config stack
+        import mapproxy.config.config
         mapproxy.config.config._config.pop()
-        return services
 
     @property
     def base_config(self):


=====================================
mapproxy/image/__init__.py
=====================================
@@ -242,7 +242,8 @@ class ImageResult(BaseImageResult):
                 # need actual image_opts.format for next check
                 self.image_opts = self.image_opts.copy()
                 self.image_opts.format = peek_image_format(buf)
-            if self.image_opts and image_opts and self.image_opts.format != image_opts.format:
+            if self.image_opts and image_opts and (
+                    self.image_opts.format != image_opts.format or self.image_opts.mode != image_opts.mode):
                 log.debug('converting image from %s -> %s' % (self.image_opts, image_opts))
                 self.image = self.as_image()
                 self._buf = None
@@ -419,6 +420,9 @@ def img_to_buf(img: Image.Image, image_opts, georef=None) -> BytesIO:
             and 'transparency' in img.info and isinstance(img.info['transparency'], tuple)):
         del img.info['transparency']
 
+    if image_opts.mode is not None and img.mode != image_opts.mode:
+        img = img.convert(image_opts.mode)
+
     img.save(buf, format, **defaults)
     buf.seek(0)
     return buf


=====================================
mapproxy/service/demo.py
=====================================
@@ -352,11 +352,13 @@ class DemoServer(Server):
         background_url = base_config().background.url
         if self.background:
             background_url = self.background["url"]
+
+        res = [wmts_layer.grid.resolutions[k] for k in list(wmts_layer.grid.resolutions)]
         return template.substitute(layer=wmts_layer,
                                    matrix_set=wmts_layer.grid.name,
                                    format=escape_html(req.args['format']),
                                    srs=escape_html(req.args['srs']),
-                                   resolutions=wmts_layer.grid.resolutions,
+                                   resolutions=res,
                                    units=units,
                                    all_tile_layers=self.tile_layers,
                                    rest_enabled=rest_enabled,


=====================================
mapproxy/service/ogcapi/api.py
=====================================
@@ -113,9 +113,13 @@ def api(server: OGCAPIServer, req: Request):
 
     oas["info"] = info
 
+    server_url = cfg["server"]["url"]
+    if not req.script_url.endswith("/ogcapi"):
+        server_url = server.create_href(req, "/ogcapi")
+
     oas["servers"] = [
         {
-            "url": cfg["server"]["url"],
+            "url": server_url,
         }
     ]
     server_description = _get(cfg, "metadata", "identification", "description")


=====================================
mapproxy/service/ogcapi/server.py
=====================================
@@ -307,6 +307,11 @@ class OGCAPIServer(Server):
         headers.update(self.response_headers)
 
         if self.is_html_req(req):
+            # Add the ogc_api_base to render the HTML response.
+            # In this case it is used in the templates to get the correct
+            # resource urls especially in multi-mapproxy setups
+            json_resp["ogc_api_base"] = self.create_href(req, "/ogcapi")
+
             content = render_j2_template(
                 self.get_pygeoapi_config(req),
                 service_package.__package__,


=====================================
mapproxy/service/templates/ogcapi/_base.html
=====================================
@@ -4,13 +4,13 @@
 {% if config['server']['icon'] %}
 {% set icon = config['server']['icon'] %}
 {% else %}
-{% set icon = config['server']['url'] + '/static/img/favicon.ico' %}
+{% set icon = data['ogc_api_base'] + '/static/img/favicon.ico' %}
 {% endif %}
 
 {% if config['server']['logo'] %}
 {% set logo = config['server']['logo'] %}
 {% else %}
-{% set logo = config['server']['url'] + '/static/img/logo.png' %}
+{% set logo = data['ogc_api_base'] + '/static/img/logo.png' %}
 {% endif %}
 
   <head>
@@ -21,8 +21,8 @@
     <meta name="description" content="{{ config['metadata']['identification']['title'] }}">
     <meta name="keywords" content="{{ config['metadata']['identification']['keywords']|join(',') }}">
     <link rel="shortcut icon" href="{{ icon }}" type="image/x-icon">
-    <link rel="stylesheet" href="{{ config['server']['url'] }}/static/css/bootstrap.min.css">
-    <link rel="stylesheet" href="{{ config['server']['url'] }}/static/css/default.css">
+    <link rel="stylesheet" href="{{ data['ogc_api_base'] }}/static/css/bootstrap.min.css">
+    <link rel="stylesheet" href="{{ data['ogc_api_base'] }}/static/css/default.css">
     <meta name="twitter:image:src"   content="{% block thumb %}{% endblock %}{% if not self.thumb() %}{{ logo }}{% endif %}" />
     <meta property="og:image"        content="{{ self.thumb() }}{% if not self.thumb() %}{{ logo }}{% endif %}" />
     <meta name="twitter:site"        content="{{ config['metadata']['identification']['title'] }}" />
@@ -48,7 +48,7 @@
   <div class="bg-light sticky-top border-bottom">
     <div class="container">
       <header class="d-flex flex-wrap align-items-center py-3 justify-content-between">
-        <a href="{{ config['server']['url'] }}"
+        <a href="{{ data['ogc_api_base'] }}"
           class="d-flex align-items-center mb-3 mb-md-0 text-dark text-decoration-none">
           <img src="{{ logo }}"
             title="{{ config['metadata']['identification']['title'] }}" style="height:40px;vertical-align: middle;" /></a>
@@ -60,7 +60,7 @@
           {% endif %}
           {% if config['server']['admin'] %}
           <li class="nav-item">
-            <a href="{{ config['server']['url'] }}/admin/config" class="nav-link" aria-current="page">{% trans %}Admin{% endtrans %}</a>
+            <a href="{{ data['ogc_api_base'] }}/admin/config" class="nav-link" aria-current="page">{% trans %}Admin{% endtrans %}</a>
           </li>
          {% endif %}
           <!--
@@ -76,7 +76,7 @@
       <div class="row">
         <div class="col-sm-12">
         {% block crumbs %}
-        <a href="{{ config['server']['url'] }}">{% trans %}Home{% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}">{% trans %}Home{% endtrans %}</a>
         {% endblock %}
         <span style="float: inline-end">
           {% set links_found = namespace(json=0, jsonld=0) %}
@@ -114,10 +114,10 @@
     <footer class="sticky-bottom bg-light d-flex justify-content-center align-items-center py-3 px-3 border-top">
       <div class="text-center w-100">
         {% trans %}Powered by {% endtrans %}
-        <a title="mapproxy" href="https://mapproxy.org/"><img src="{{ config['server']['url'] }}/static/img/logo.png" class="mx-1" title="mapproxy logo" style="height:24px;vertical-align: middle;" /> MapProxy</a>
+        <a title="mapproxy" href="https://mapproxy.org/"><img src="{{ data['ogc_api_base'] }}/static/img/logo.png" class="mx-1" title="mapproxy logo" alt="mapproxy logo" style="height:24px;vertical-align: middle;" /> MapProxy</a>
         {{ version }}
         <br>
-        (HTML theming based on <a title="mapproxy" href="https://pygeoapi.io/"><img src="/ogcapi/static/img/pygeoapi-logo.png" class="mx-1" title="pygeoapi logo" style="height:24px;vertical-align: middle;" /></a>)
+        (HTML theming based on <a title="mapproxy" href="https://pygeoapi.io/"><img src="{{ data['ogc_api_base'] }}/static/img/pygeoapi-logo.png" class="mx-1" title="pygeoapi logo" alt="pygeoapi logo" style="height:24px;vertical-align: middle;" /></a>)
       </div>
     </footer>
     {% block extrafoot %}


=====================================
mapproxy/service/templates/ogcapi/collections/collection.html
=====================================
@@ -9,9 +9,9 @@
 
 {% block extrahead %}
 
-<script src="{{ config['server']['url'] }}/static/ol.js"></script>
-<script src="{{ config['server']['url'] }}/static/proj4.min.js"></script>
-<link rel="stylesheet" href="{{ config['server']['url'] }}/static/ol.css"/>
+<script src="{{ data['ogc_api_base'] }}/static/ol.js"></script>
+<script src="{{ data['ogc_api_base'] }}/static/proj4.min.js"></script>
+<link rel="stylesheet" href="{{ data['ogc_api_base'] }}/static/ol.css"/>
 
 {% endblock %}
 
@@ -93,7 +93,7 @@ let init;
         const extent = {{ data['extent'] }};
 
         if (!ol.proj.get(srs)) {
-            const projDefsUrl = "{{ config['server']['url'] }}/static/proj4defs.js";
+            const projDefsUrl = "{{ data['ogc_api_base'] }}/static/proj4defs.js";
             const allDefs = await import(projDefsUrl);
             const srsNum = srs.indexOf(':') > -1 ? parseInt(srs.split(':')[1]) : parseInt(srs);
             if (!allDefs.defs[srsNum]) {
@@ -155,7 +155,7 @@ let init;
         const storageCrs = "{{ data['storageCrs'] }}";
 
         if (!ol.proj.get(srs)) {
-            const projDefsUrl = "{{ config['server']['url'] }}/static/proj4defs.js";
+            const projDefsUrl = "{{ data['ogc_api_base'] }}/static/proj4defs.js";
             const allDefs = await import(projDefsUrl);
             const srsNum = srs.indexOf(':') > -1 ? parseInt(srs.split(':')[1]) : parseInt(srs);
             if (!allDefs.defs[srsNum]) {


=====================================
mapproxy/service/templates/ogcapi/landing_page.html
=====================================
@@ -61,7 +61,7 @@
   <section id="collections">
     <h2>{% trans %}Collections{% endtrans %}</h2>
     <p>
-      <a href="{{ config['server']['url'] }}/collections?f=html">{% trans %}View the collections in this service{% endtrans %}</a>
+      <a href="{{ data['ogc_api_base'] }}/collections?f=html">{% trans %}View the collections in this service{% endtrans %}</a>
     </p>
   </section>
  {% endif %}
@@ -69,7 +69,7 @@
   <section id="collections">
     <h2>{% trans %}SpatioTemporal Assets{% endtrans %}</h2>
     <p>
-      <a href="{{ config['server']['url'] }}/stac?f=html">{% trans %}View the SpatioTemporal Assets in this service{% endtrans %}</a>
+      <a href="{{ data['ogc_api_base'] }}/stac?f=html">{% trans %}View the SpatioTemporal Assets in this service{% endtrans %}</a>
     </p>
   </section>
  {% endif %}
@@ -77,7 +77,7 @@
   <section id="processes">
       <h2>{% trans %}Processes{% endtrans %}</h2>
       <p>
-        <a href="{{ config['server']['url'] }}/processes?f=html">{% trans %}View the processes in this service{% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/processes?f=html">{% trans %}View the processes in this service{% endtrans %}</a>
       </p>
   </section>
   <section id="jobs">
@@ -90,23 +90,23 @@
   <section id="openapi">
       <h2>{% trans %}API Definition{% endtrans %}</h2>
       <p>
-        {% trans %}Documentation{% endtrans %}: <a href="{{ config['server']['url'] }}/api?f=html">{% trans %}Swagger UI{% endtrans %}</a> <a href="{{ config['server']['url'] }}/api?f=html&ui=redoc">{% trans %}ReDoc{% endtrans %}</a>
+        {% trans %}Documentation{% endtrans %}: <a href="{{ data['ogc_api_base'] }}/api?f=html">{% trans %}Swagger UI{% endtrans %}</a> | <a href="{{ data['ogc_api_base'] }}/api?f=html&ui=redoc">{% trans %}ReDoc{% endtrans %}</a>
       </p>
       <p>
-        <a href="{{ config['server']['url'] }}/api?f=json">{% trans %}OpenAPI Document{% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/api?f=json">{% trans %}OpenAPI Document{% endtrans %}</a>
       </p>
   </section>
   <section id="conformance">
       <h2>{% trans %}Conformance{% endtrans %}</h2>
       <p>
-        <a href="{{ config['server']['url'] }}/conformance?f=html">{% trans %}View the conformance classes of this service{% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/conformance?f=html">{% trans %}View the conformance classes of this service{% endtrans %}</a>
       </p>
   </section>
   {% if data['tile'] %}
   <section id="tilematrixsets">
       <h2>{% trans %}Tile Matrix Sets{% endtrans %}</h2>
       <p>
-        <a href="{{ config['server']['url'] }}/tileMatrixSets?f=html">{% trans %}View the Tile Matrix Sets available on this service{% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/tileMatrixSets?f=html">{% trans %}View the Tile Matrix Sets available on this service{% endtrans %}</a>
       </p>
   </section>
   {% endif %}
@@ -114,10 +114,10 @@
   <section id="dataset_map">
       <h2>{% trans %}Dataset maps{% endtrans %}</h2>
       <p>
-        <a href="{{ config['server']['url'] }}/map.png">{% trans %}Default map of the whole dataset (as PNG){% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/map.png">{% trans %}Default map of the whole dataset (as PNG){% endtrans %}</a>
       </p>
       <p>
-        <a href="{{ config['server']['url'] }}/map.jpg">{% trans %}Default map of the whole dataset (as JPEG){% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/map.jpg">{% trans %}Default map of the whole dataset (as JPEG){% endtrans %}</a>
       </p>
   </section>
   {% endif %}
@@ -125,10 +125,10 @@
   <section id="dataset_tiles">
       <h2>{% trans %}Dataset tilesets{% endtrans %}</h2>
       <p>
-        <a href="{{ config['server']['url'] }}/map/tiles?f=html">{% trans %}Default tilesets for the whole dataset (as HTML){% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/map/tiles?f=html">{% trans %}Default tilesets for the whole dataset (as HTML){% endtrans %}</a>
       </p>
       <p>
-        <a href="{{ config['server']['url'] }}/map/tiles?f=json">{% trans %}Default tilesets for the whole dataset (as JSON){% endtrans %}</a>
+        <a href="{{ data['ogc_api_base'] }}/map/tiles?f=json">{% trans %}Default tilesets for the whole dataset (as JSON){% endtrans %}</a>
       </p>
   </section>
   {% endif %}


=====================================
mapproxy/service/templates/ogcapi/openapi/redoc.html
=====================================
@@ -4,7 +4,7 @@
     <title>ReDoc - {{ config['metadata']['identification']['title'] }}</title>
     <meta charset="utf-8"/>
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <link href="{{ config['server']['url'] }}/static/fonts_montserra_roboto.css" rel="stylesheet">
+    <link href="{{ data['openapi-document-path'] }}/../static/fonts_montserra_roboto.css" rel="stylesheet">
     <style>
       body {
         margin: 0;
@@ -14,6 +14,6 @@
   </head>
   <body>
     <redoc spec-url='{{ data['openapi-document-path'] }}?f=json'></redoc>
-    <script src="{{ config['server']['url'] }}/static/redoc.standalone.js"> </script>
+    <script src="{{ data['openapi-document-path'] }}/../static/redoc.standalone.js"> </script>
   </body>
 </html>


=====================================
mapproxy/service/templates/ogcapi/openapi/swagger.html
=====================================
@@ -4,9 +4,9 @@
   <head>
     <meta charset="UTF-8">
     <title>Swagger UI - {{ config['metadata']['identification']['title'] }}</title>
-    <link rel="stylesheet" type="text/css" href="{{ config['server']['url'] }}/static/swagger-ui-dist/swagger-ui.css" >
-    <link rel="icon" type="image/png" href="{{ config['server']['url'] }}/static/swagger-ui-dist/favicon-32x32.png" sizes="32x32" />
-    <link rel="icon" type="image/png" href="{{ config['server']['url'] }}/static/swagger-ui-dist/favicon-16x16.png" sizes="16x16" />
+    <link rel="stylesheet" type="text/css" href="{{ data['openapi-document-path'] }}/../static/swagger-ui-dist/swagger-ui.css" >
+    <link rel="icon" type="image/png" href="{{ data['openapi-document-path'] }}/../static/swagger-ui-dist/favicon-32x32.png" sizes="32x32" />
+    <link rel="icon" type="image/png" href="{{ data['openapi-document-path'] }}/../static/swagger-ui-dist/favicon-16x16.png" sizes="16x16" />
     <style>
       html
       {
@@ -30,8 +30,8 @@
 
   <body>
     <div id="swagger-ui"></div>
-    <script src="{{ config['server']['url'] }}/static/swagger-ui-dist/swagger-ui-bundle.js"> </script>
-    <script src="{{ config['server']['url'] }}/static/swagger-ui-dist/swagger-ui-standalone-preset.js"> </script>
+    <script src="{{ data['openapi-document-path'] }}/../static/swagger-ui-dist/swagger-ui-bundle.js"> </script>
+    <script src="{{ data['openapi-document-path'] }}/../static/swagger-ui-dist/swagger-ui-standalone-preset.js"> </script>
     <script>
     window.onload = function() {
       // Begin Swagger UI call region


=====================================
mapproxy/service/templates/ogcapi/tilesets/tileset.html
=====================================
@@ -10,9 +10,9 @@
 {% endblock %}
 
 {% block extrahead %}
-<script src="{{ config['server']['url'] }}/static/ol.js"></script>
-<script src="{{ config['server']['url'] }}/static/proj4.min.js"></script>
-<link rel="stylesheet" href="{{ config['server']['url'] }}/static/ol.css"/>
+<script src="{{ data['ogc_api_base'] }}/static/ol.js"></script>
+<script src="{{ data['ogc_api_base'] }}/static/proj4.min.js"></script>
+<link rel="stylesheet" href="{{ data['ogc_api_base'] }}/static/ol.css"/>
 
 {% endblock %}
 
@@ -54,7 +54,7 @@
         const extent = {{ data['extent'] }};
 
         if (!ol.proj.get(srs)) {
-            const projDefsUrl = "{{ config['server']['url'] }}/static/proj4defs.js";
+            const projDefsUrl = "{{ data['ogc_api_base'] }}/static/proj4defs.js";
             const allDefs = await import(projDefsUrl);
             const srsNum = srs.indexOf(':') > -1 ? parseInt(srs.split(':')[1]) : parseInt(srs);
             if (!allDefs.defs[srsNum]) {


=====================================
mapproxy/test/unit/test_conf_loader.py
=====================================
@@ -267,6 +267,30 @@ class TestLayerConfiguration(object):
             assert False, 'expected ConfigurationError'
 
 
+class TestProxyConfigurationContextManager(object):
+    # mapproxy-seed enters the configuration with ``with mapproxy_conf:``;
+    # ProxyConfiguration must therefore support the context manager protocol
+    # (regression: it was lost when the loader was split, see #1442).
+    def test_context_manager_pushes_and_pops_base_config(self):
+        import mapproxy.config.config as mpconfig
+
+        conf = ProxyConfiguration({})
+        before = mpconfig._config.top
+        with conf:
+            assert mpconfig._config.top is conf.base_config
+        assert mpconfig._config.top is before
+
+    def test_context_manager_pops_on_exception(self):
+        import mapproxy.config.config as mpconfig
+
+        conf = ProxyConfiguration({})
+        before = mpconfig._config.top
+        with pytest.raises(ValueError):
+            with conf:
+                raise ValueError
+        assert mpconfig._config.top is before
+
+
 class TestGridConfiguration(object):
     def test_default_grids(self):
         conf = {}


=====================================
mapproxy/test/unit/test_image.py
=====================================
@@ -191,6 +191,34 @@ class TestImageResult(object):
         img = Image.open(ir.as_buffer())
         assert img.mode == "P"
 
+    def test_rgb_image_as_buffer_with_rgba_mode(self):
+        # Regression test for https://github.com/mapproxy/mapproxy/issues/1354
+        # When mode=RGBA is requested, an RGB source image must be returned as
+        # RGBA PNG, not RGB PNG, regardless of whether it contains transparency.
+        img = Image.new("RGB", (10, 10), (255, 0, 0))
+        ir = ImageResult(img, image_opts=PNG_FORMAT)
+        rgba_opts = ImageOptions(mode="RGBA", transparent=True, format="image/png")
+        result = Image.open(ir.as_buffer(rgba_opts))
+        assert result.mode == "RGBA", (
+            "Expected RGBA PNG when mode=RGBA requested, got %s" % result.mode
+        )
+
+    def test_rgb_buf_as_buffer_with_rgba_mode(self):
+        # Regression test for https://github.com/mapproxy/mapproxy/issues/1354
+        # When an upstream source returns an opaque RGB PNG buffer and mode=RGBA
+        # is explicitly configured, as_buffer must re-encode to RGBA, not pass
+        # through the RGB bytes unchanged.
+        src = BytesIO()
+        Image.new("RGB", (10, 10), (255, 0, 0)).save(src, "png")
+        src.seek(0)
+        src_opts = ImageOptions(format="image/png", transparent=False)
+        ir = ImageResult(src, image_opts=src_opts)
+        rgba_opts = ImageOptions(mode="RGBA", transparent=True, format="image/png")
+        result = Image.open(ir.as_buffer(rgba_opts))
+        assert result.mode == "RGBA", (
+            "Expected RGBA PNG when mode=RGBA requested, got %s" % result.mode
+        )
+
 
 class TestSubImageResult(object):
 
@@ -489,6 +517,22 @@ class TestLayerMerge(object):
         img = result.as_image()
         assert img.getpixel((0, 0)) == (255, 0, 255)
 
+    def test_single_opaque_rgb_layer_with_rgba_output(self):
+        # Regression test for https://github.com/mapproxy/mapproxy/issues/1354
+        # A single fully-opaque RGB source layer must produce an RGBA PNG when
+        # the requested ImageOptions specify mode=RGBA / transparent=True.
+        # Previously the single-layer shortcut in LayerMerger.merge() returned
+        # the source layer unchanged, so the RGB bytes were passed through
+        # without being converted.
+        source = ImageResult(Image.new("RGB", (10, 10), (255, 0, 0)))
+        rgba_opts = ImageOptions(mode="RGBA", transparent=True, format="image/png")
+        result = merge_images([(source, None)], rgba_opts)
+        output = Image.open(result.as_buffer(rgba_opts))
+        assert output.mode == "RGBA", (
+            "Expected RGBA PNG for opaque source when mode=RGBA requested, got %s"
+            % output.mode
+        )
+
 
 @pytest.mark.skipif(
     not hasattr(Image, "alpha_composite"), reason="PIL has no alpha_composite"


=====================================
requirements-tests.txt
=====================================
@@ -2,7 +2,7 @@ attrs==23.2.0
 aws-sam-translator==1.91.0
 aws-xray-sdk==2.12.1
 azure-storage-blob==12.27.1
-beautifulsoup4==4.12.3
+beautifulsoup4==4.14.3
 boto3==1.40.55
 botocore==1.40.55
 certifi==2025.4.26
@@ -10,10 +10,10 @@ cffi==2.0.0
 cfn-lint==0.80.3
 chardet==5.2.0
 coverage==7.11.3
-cryptography==46.0.5
+cryptography==46.0.7
 decorator==5.2.1
 docker==7.1.0
-docutils==0.21.2
+docutils==0.22.4
 ecdsa==0.18.0
 flake8==7.0.0
 flake8-toml-config==1.0.0
@@ -42,11 +42,11 @@ pluggy==1.6.0
 py==1.11.0
 pyasn1==0.6.3
 pycparser==2.22
-pyparsing==3.2.3
+pyparsing==3.3.2
 pyproj==3.7.1
 pyrsistent==0.20.0
 pytest-rerunfailures==16.0.1
-pytest==8.3.2
+pytest==9.0.3
 python-dateutil==2.9.0.post0
 python-jose==3.5.0
 pytz==2024.2


=====================================
setup.py
=====================================
@@ -44,7 +44,7 @@ def long_description(changelog_releases=10):
 
 setup(
     name='MapProxy',
-    version="6.1.0",
+    version="6.1.1",
     description='An accelerating proxy for tile and web map services',
     long_description=long_description(7),
     long_description_content_type='text/x-rst',



View it on GitLab: https://salsa.debian.org/debian-gis-team/mapproxy/-/compare/5620a3115fc082f48004a03b0343c893fe2a6f46...da49ea1a8c289b8d58eca953737135942edc62fe

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/mapproxy/-/compare/5620a3115fc082f48004a03b0343c893fe2a6f46...da49ea1a8c289b8d58eca953737135942edc62fe
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260617/7d3fd6b2/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list