[Git][debian-gis-team/python-deprecated][upstream] New upstream version 1.3.0
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Thu Oct 30 04:20:06 GMT 2025
Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-deprecated
Commits:
334deee7 by Bas Couwenberg at 2025-10-30T05:15:49+01:00
New upstream version 1.3.0
- - - - -
20 changed files:
- .bumpversion.cfg
- .github/workflows/python-package.yml
- CHANGELOG.rst
- deprecated/__init__.py
- deprecated/classic.py
- + deprecated/params.py
- docs/source/_static/rusty-tools-background.svg
- docs/source/api.rst
- docs/source/conf.py
- docs/source/installation.rst
- docs/source/tutorial.rst
- pyproject.toml
- python-deprecated.spec
- setup.py
- + tests/deprecated_params/__init__.py
- + tests/deprecated_params/test_demo_area.py
- + tests/deprecated_params/test_demo_paragraph.py
- + tests/deprecated_params/test_demo_pow2.py
- + tests/deprecated_params/test_demo_versions.py
- tox.ini
Changes:
=====================================
.bumpversion.cfg
=====================================
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 1.2.18
+current_version = 1.3.0
commit = True
tag = False
message = Prepare next version {new_version} (unreleased)
=====================================
.github/workflows/python-package.yml
=====================================
@@ -4,10 +4,8 @@
name: Python package
on:
- push:
- branches: [ master ]
pull_request:
- branches: [ master ]
+ branches: ['master', 'main', 'develop', 'release/**', 'hotfix/**']
jobs:
pytest:
@@ -15,7 +13,7 @@ jobs:
fail-fast: false
matrix:
platform: [ ubuntu-latest, macos-latest, windows-latest ]
- python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
+ python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ]
runs-on: ${{ matrix.platform }}
@@ -33,4 +31,3 @@ jobs:
- name: Test with pytest
run: |
pytest tests/
-
=====================================
CHANGELOG.rst
=====================================
@@ -17,6 +17,29 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
- I decided to keep the same version number because there is really no change in the source code
(only in comment or documentation).
+v1.3.0 (unreleased)
+===================
+
+Minor release: Parameters deprecation
+
+Added
+-----
+
+- Add compatibility tests and adjustments for Wrapt v2.0. See PR #88 (musicinmybrain).
+
+- Add experimental `@deprecated_params` decorator to mark function parameters as deprecated at call-time; emits warnings when deprecated parameters are used with optional messages and configurable warning categories. See PR #93.
+
+Documentation
+-------------
+
+- Update the Wrapt compatibility matrix to include Python 3.13 and 3.14. See PR #91
+
+Changed
+-------
+
+- Limit test coverage collection to the dedicated ``coverage`` tox environment to avoid collecting coverage across all test environments and reduce cross-environment coverage noise. See PR #92.
+
+
v1.2.18 (2024-01-25)
====================
=====================================
deprecated/__init__.py
=====================================
@@ -7,9 +7,10 @@ Python ``@deprecated`` decorator to deprecate old python classes, functions or m
"""
-__version__ = "1.2.18"
-__author__ = u"Laurent LAPORTE <laurent.laporte.pro at gmail.com>"
-__date__ = "2025-01-27"
+__version__ = "1.3.0"
+__author__ = u"Laurent LAPORTE <tantale.solutions at gmail.com>"
+__date__ = "unreleased"
__credits__ = "(c) Laurent LAPORTE"
from deprecated.classic import deprecated
+from deprecated.params import deprecated_params
=====================================
deprecated/classic.py
=====================================
@@ -22,7 +22,7 @@ try:
_routine_stacklevel = 2
_class_stacklevel = 2
-except ImportError:
+except ImportError: # pragma: no cover
_routine_stacklevel = 3
if platform.python_implementation() == "PyPy":
_class_stacklevel = 2
@@ -200,7 +200,7 @@ class ClassicAdapter(wrapt.AdapterFactory):
return wrapper_function(wrapped)
- else:
+ else: # pragma: no cover
raise TypeError(repr(type(wrapped)))
return wrapped
=====================================
deprecated/params.py
=====================================
@@ -0,0 +1,79 @@
+# coding: utf-8
+"""
+Parameters deprecation
+======================
+
+.. _Tantale's Blog: https://tantale.github.io/
+.. _Deprecated Parameters: https://tantale.github.io/articles/deprecated_params/
+
+This module introduces a :class:`deprecated_params` decorator to specify that one (or more)
+parameter(s) are deprecated: when the user executes a function with a deprecated parameter,
+he will see a warning message in the console.
+
+The decorator is customizable, the user can specify the deprecated parameter names
+and associate to each of them a message providing the reason of the deprecation.
+As with the :func:`~deprecated.classic.deprecated` decorator, the user can specify
+a version number (using the *version* parameter) and also define the warning message category
+(a subclass of :class:`Warning`) and when to display the messages (using the *action* parameter).
+
+The complete study concerning the implementation of this decorator is available on the `Tantale's blog`_,
+on the `Deprecated Parameters`_ page.
+"""
+import collections
+import functools
+import warnings
+
+try:
+ # noinspection PyPackageRequirements
+ import inspect2 as inspect
+except ImportError:
+ import inspect
+
+
+class DeprecatedParams(object):
+ """
+ Decorator used to decorate a function which at least one
+ of the parameters is deprecated.
+ """
+
+ def __init__(self, param, reason="", category=DeprecationWarning):
+ self.messages = {} # type: dict[str, str]
+ self.category = category
+ self.populate_messages(param, reason=reason)
+
+ def populate_messages(self, param, reason=""):
+ if isinstance(param, dict):
+ self.messages.update(param)
+ elif isinstance(param, str):
+ fmt = "'{param}' parameter is deprecated"
+ reason = reason or fmt.format(param=param)
+ self.messages[param] = reason
+ else:
+ raise TypeError(param)
+
+ def check_params(self, signature, *args, **kwargs):
+ binding = signature.bind(*args, **kwargs)
+ bound = collections.OrderedDict(binding.arguments, **binding.kwargs)
+ return [param for param in bound if param in self.messages]
+
+ def warn_messages(self, messages):
+ # type: (list[str]) -> None
+ for message in messages:
+ warnings.warn(message, category=self.category, stacklevel=3)
+
+ def __call__(self, f):
+ # type: (callable) -> callable
+ signature = inspect.signature(f)
+
+ @functools.wraps(f)
+ def wrapper(*args, **kwargs):
+ invalid_params = self.check_params(signature, *args, **kwargs)
+ self.warn_messages([self.messages[param] for param in invalid_params])
+ return f(*args, **kwargs)
+
+ return wrapper
+
+
+#: Decorator used to decorate a function which at least one
+#: of the parameters is deprecated.
+deprecated_params = DeprecatedParams
=====================================
docs/source/_static/rusty-tools-background.svg
=====================================
@@ -69,4 +69,4 @@
transform="matrix(1.087825,0,0,1.0878179,-44.130182,-11.147489)"><tspan
x="315.95117"
y="195.35708"
- id="deprecated-version">v1.2.18</tspan></text></g></svg>
+ id="deprecated-version">v1.3.0</tspan></text></g></svg>
=====================================
docs/source/api.rst
=====================================
@@ -11,5 +11,8 @@ This part of the documentation covers all the interfaces of the Deprecated Libra
.. automodule:: deprecated.classic
:members:
+.. automodule:: deprecated.params
+ :members:
+
.. automodule:: deprecated.sphinx
:members:
=====================================
docs/source/conf.py
=====================================
@@ -61,7 +61,7 @@ author = 'Marcos CARDOSO & Laurent LAPORTE'
# built documents.
#
# The full version, including alpha/beta/rc tags.
-release = "1.2.18"
+release = "1.3.0"
# The short X.Y version.
version = release.rpartition('.')[0]
=====================================
docs/source/installation.rst
=====================================
@@ -17,6 +17,107 @@ function wrappers and decorator functions.
.. _Wrapt: http://wrapt.readthedocs.io/en/latest/
+The table below shows the compatibility matrix between Python versions and the `wrapt` versions that have been
+tested to date. Recent versions are listed first.
+
+.. list-table:: Compatibility matrix (tested versions)
+ :header-rows: 1
+ :widths: 25 9 9 9 9 9 9 9 9 9
+
+ * - Python / wrapt
+ - 2.0
+ - 1.17
+ - 1.16
+ - 1.15
+ - 1.14
+ - 1.13
+ - 1.12
+ - 1.11
+ - 1.10
+ * - py3.14
+ - ✓
+ - ✓
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ * - py3.13
+ - ✓
+ - ✓
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ * - py3.12
+ - ✓
+ - ✓
+ - ✓
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ * - py3.11
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✗
+ - ✗
+ - ✗
+ - ✗
+ * - py3.10
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ * - py3.9
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ * - py3.8
+ - ?
+ - ?
+ - ?
+ - ?
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ * - py3.7
+ - ?
+ - ?
+ - ?
+ - ?
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+ - ✓
+
+Legend: ✓ = tested and compatible ; ✗ = incompatible, ? = untested but expected to work
+
+
Development dependencies
~~~~~~~~~~~~~~~~~~~~~~~~
=====================================
docs/source/tutorial.rst
=====================================
@@ -123,6 +123,50 @@ the :func:`~deprecated.deprecated` decorator patches the ``__new__`` method in o
emmit the warning message before instance creation.
+Deprecated parameters
+---------------------
+
+It is also possible to mark one or more parameters of a function as deprecated using the
+:func:`deprecated.params.deprecated_params` decorator.
+
+Example:
+
+.. code-block:: python
+
+ import warnings
+ from deprecated.params import deprecated_params
+
+ class V2DeprecationWarning(DeprecationWarning):
+ pass
+
+ # noinspection PyUnusedLocal
+ @deprecated_params(
+ {
+ "epsilon": "epsilon is deprecated in version v2",
+ "start": "start is removed in version v2",
+ },
+ category=V2DeprecationWarning,
+ )
+ @deprecated_params("epsilon", reason="epsilon is deprecated in version v1.1")
+ def integrate(f, a, b, n=0, epsilon=0.0, start=None):
+ epsilon = epsilon or (b - a) / n
+ n = n or int((b - a) / epsilon)
+ return sum((f(a + (i * epsilon)) + f(a + (i * epsilon) + epsilon)) * epsilon / 2 for i in range(n))
+
+When the function is called, parameters marked as deprecated will emit deprecation
+warnings (using the provided category and message). This allows you to inform users
+about alternatives or the versions in which parameters were changed or removed.
+
+.. code-block:: sh
+
+ $ python use_deprecated_params.py
+
+ use_deprecated_params.py:48: V2DeprecationWarning: epsilon is deprecated in version v2
+ integrate(lambda x: x**2, 0, 2, epsilon=0.0012, start=123)
+ use_deprecated_params.py:48: V2DeprecationWarning: start is removed in version v2
+ integrate(lambda x: x**2, 0, 2, epsilon=0.0012, start=123)
+
+
Controlling warnings
--------------------
@@ -193,7 +237,6 @@ When the user runs this script, the deprecation warnings for the 3.0 version are
warning_classes_demo.py:30: DeprecatedIn26: Call to deprecated function (or staticmethod) foo. (deprecated function)
foo()
-
Filtering warnings locally
--------------------------
@@ -243,7 +286,6 @@ function will raise an exception because the *action* is set to "error".
warnings.warn(msg, category=category, stacklevel=_stacklevel)
DeprecationWarning: Call to deprecated function (or staticmethod) foo. (do not call it)
-
Modifying the deprecated code reference
---------------------------------------
=====================================
pyproject.toml
=====================================
@@ -1,7 +1,7 @@
[tool.black]
line-length = 120
skip-string-normalization = true
-target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
+target-version = ['py37', 'py38', 'py39', 'py310', 'py311', 'py312', "py313", "py314"]
include = '\.pyi?$'
[tool.isort]
=====================================
python-deprecated.spec
=====================================
@@ -2,7 +2,7 @@
%global pkgname deprecated
Name: python-%{pkgname}
-Version: 1.2.18
+Version: 1.3.0
Release: 1%{?dist}
Summary: Python decorator to deprecate old python classes, functions or methods
License: MIT
=====================================
setup.py
=====================================
@@ -144,7 +144,7 @@ from setuptools import setup
setup(
name="Deprecated",
- version="1.2.18",
+ version="1.3.0",
url="https://github.com/laurent-laporte-pro/deprecated",
project_urls={
"Documentation": "https://deprecated.readthedocs.io/en/latest/",
@@ -159,7 +159,7 @@ setup(
long_description_content_type="text/x-rst",
keywords="deprecate,deprecated,deprecation,warning,warn,decorator",
packages=["deprecated"],
- install_requires=["wrapt < 2, >= 1.10"],
+ install_requires=["wrapt < 3, >= 1.10", "inspect2; python_version < '3'"],
zip_safe=False,
include_package_data=True,
platforms="any",
@@ -182,6 +182,8 @@ setup(
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Libraries :: Python Modules",
],
extras_require={
=====================================
tests/deprecated_params/__init__.py
=====================================
=====================================
tests/deprecated_params/test_demo_area.py
=====================================
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+"""
+This example shows how to implement a function that accepts two positional
+arguments or two keyword arguments. A warning message should be emitted
+if `x` and `y` are used instead of `width` and `height`.
+"""
+import warnings
+
+import pytest
+
+from deprecated.params import deprecated_params
+
+
+ at deprecated_params(
+ {
+ "x": "use `width` instead or `x`",
+ "y": "use `height` instead or `y`",
+ },
+)
+def area(*args, **kwargs):
+ def _area_impl(width, height):
+ return width * height
+
+ if args:
+ # positional arguments (no checking)
+ return _area_impl(*args)
+ elif set(kwargs) == {"width", "height"}:
+ # nominal case: no warning
+ return _area_impl(kwargs["width"], kwargs["height"])
+ elif set(kwargs) == {"x", "y"}:
+ # old case: deprecation warning
+ return _area_impl(kwargs["x"], kwargs["y"])
+ else:
+ raise TypeError("invalid arguments")
+
+
+ at pytest.mark.parametrize(
+ "args, kwargs, expected",
+ [
+ pytest.param((4, 6), {}, [], id="positional arguments: no warning"),
+ pytest.param((), {"width": 3, "height": 6}, [], id="correct keyword arguments"),
+ pytest.param(
+ (),
+ {"x": 2, "y": 7},
+ ['use `width` instead or `x`', 'use `height` instead or `y`'],
+ id="wrong keyword arguments",
+ ),
+ pytest.param(
+ (),
+ {"x": 2, "height": 7},
+ [],
+ id="invalid arguments is raised",
+ marks=pytest.mark.xfail(raises=TypeError, strict=True),
+ ),
+ ],
+)
+def test_area(args, kwargs, expected):
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter("always")
+ area(*args, **kwargs)
+ actual = [str(warn.message) for warn in warns]
+ assert actual == expected
=====================================
tests/deprecated_params/test_demo_paragraph.py
=====================================
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+"""
+This example shows a function with a keyword-only parameter. A warning
+message should be emitted if `color` is used (as a positional or keyword parameter).
+"""
+import sys
+import warnings
+import xml.sax.saxutils
+
+import pytest
+
+from deprecated.params import deprecated_params
+
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ # Keyword-Only Arguments are only available for Python >= 3
+ # On Python 2.7, this code raises a SyntaxError exception.
+ exec(
+ '''
+ at deprecated_params("color", reason="you should use 'styles' instead of 'color'")
+def paragraph(text, *, color=None, styles=None):
+ """Create a styled HTML paragraphe."""
+ styles = styles or {}
+ if color:
+ styles['color'] = color
+ html_styles = " ".join("{k}: {v};".format(k=k, v=v) for k, v in styles.items())
+ html_text = xml.sax.saxutils.escape(text)
+ fmt = '<p styles="{html_styles}">{html_text}</p>'
+ return fmt.format(html_styles=html_styles, html_text=html_text)
+ '''
+ )
+
+else:
+
+ @deprecated_params("color", reason="you should use 'styles' instead of 'color'")
+ def paragraph(text, color=None, styles=None):
+ """Create a styled HTML paragraphe."""
+ styles = styles or {}
+ if color:
+ styles['color'] = color
+ html_styles = " ".join("{k}: {v};".format(k=k, v=v) for k, v in styles.items())
+ html_text = xml.sax.saxutils.escape(text)
+ fmt = '<p styles="{html_styles}">{html_text}</p>'
+ return fmt.format(html_styles=html_styles, html_text=html_text)
+
+
+ at pytest.mark.parametrize(
+ "args, kwargs, expected",
+ [
+ pytest.param(("Hello",), {}, [], id="'color' not used: no warnings"),
+ pytest.param(("Hello",), {'styles': {'color': 'blue'}}, [], id="regular usage: no warnings"),
+ pytest.param(
+ ("Hello",),
+ {'color': 'blue'},
+ ["you should use 'styles' instead of 'color'"],
+ id="'color' used in keyword-argument",
+ ),
+ ],
+)
+def test_paragraph(args, kwargs, expected):
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter("always")
+ paragraph(*args, **kwargs)
+ actual = [str(warn.message) for warn in warns]
+ assert actual == expected
=====================================
tests/deprecated_params/test_demo_pow2.py
=====================================
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+"""
+This example shows a function with an unused optional parameter. A warning
+message should be emitted if `z` is used (as a positional or keyword parameter).
+"""
+
+import sys
+import warnings
+
+import pytest
+
+from deprecated.params import deprecated_params
+
+PY38 = sys.version_info[0:2] >= (3, 8)
+
+if PY38:
+ # Positional-Only Arguments are only available for Python >= 3
+ # On other version, this code raises a SyntaxError exception.
+ exec (
+ """
+ at deprecated_params("z")
+def pow2(x, y, z=None, /):
+ return x ** y
+ """
+ )
+
+else:
+
+ @deprecated_params("z")
+ def pow2(x, y, z=None):
+ return x ** y
+
+
+ at pytest.mark.parametrize(
+ "args, kwargs, expected",
+ [
+ pytest.param((5, 6), {}, [], id="'z' not used: no warnings"),
+ pytest.param(
+ (5, 6, 8),
+ {},
+ ["'z' parameter is deprecated"],
+ id="'z' used in positional params",
+ ),
+ pytest.param(
+ (5, 6),
+ {"z": 8},
+ ["'z' parameter is deprecated"],
+ id="'z' used in keyword params",
+ marks=pytest.mark.skipif(PY38, reason="'z' parameter is positional only"),
+ ),
+ ],
+)
+def test_pow2(args, kwargs, expected):
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter("always")
+ pow2(*args, **kwargs)
+ actual = [str(warn.message) for warn in warns]
+ assert actual == expected
=====================================
tests/deprecated_params/test_demo_versions.py
=====================================
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+"""
+This example shows a function with an unused optional parameter. A warning
+message should be emitted if `z` is used (as a positional or keyword parameter).
+"""
+import warnings
+
+from deprecated.params import deprecated_params
+
+
+class V2DeprecationWarning(DeprecationWarning):
+ pass
+
+
+# noinspection PyUnusedLocal
+ at deprecated_params(
+ {
+ "epsilon": "epsilon is deprecated in version v2",
+ "start": "start is removed in version v2",
+ },
+ category=V2DeprecationWarning,
+)
+ at deprecated_params("epsilon", reason="epsilon is deprecated in version v1.1")
+def integrate(f, a, b, n=0, epsilon=0.0, start=None):
+ epsilon = epsilon or (b - a) / n
+ n = n or int((b - a) / epsilon)
+ return sum((f(a + (i * epsilon)) + f(a + (i * epsilon) + epsilon)) * epsilon / 2 for i in range(n))
+
+
+def test_only_one_warning_for_each_parameter():
+ """
+ This unit test checks that only one warning message is emitted for each deprecated parameter.
+
+ However, we notice that the current implementation generates two warning messages for the `epsilon` parameter.
+ We should therefore improve the implementation to avoid this.
+ """
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter("always")
+ integrate(lambda x: x**2, 0, 2, epsilon=0.0012, start=123)
+ actual = [{"message": str(w.message), "category": w.category} for w in warns]
+ assert actual == [
+ {"category": V2DeprecationWarning, "message": "epsilon is deprecated in version v2"},
+ {"category": V2DeprecationWarning, "message": "start is removed in version v2"},
+ {"category": DeprecationWarning, "message": "epsilon is deprecated in version v1.1"},
+ ]
=====================================
tox.ini
=====================================
@@ -10,22 +10,38 @@
# PyPy configuration (on Linux/OSX):
# - /usr/local/bin/pypy3 -> /opt/pypy3.6-v7.3.0-osx64/bin/pypy3
envlist =
- py{37,38,39,310}-wrapt{1.10,1.11,1.12,1.13,1.14}
- py{311,312}-wrapt{1.14}
+ py{37,38}-wrapt{1.10,1.11,1.12,1.13,1.14}
+ py{39,310}-wrapt{1.10,1.11,1.12,1.13,1.14,1.15,1.16,1.17,2.0}
+ py{311}-wrapt{1.14,1.15,1.16,1.17,2.0}
+ py{312}-wrapt{1.16,1.17,2.0}
+ py{313,314}-wrapt{1.17,2.0}
pypy3
+ coverage
docs
[testenv]
-commands = pytest --cov-report term-missing --cov=deprecated tests/
+commands = pytest tests/
deps =
- py{37,38,39,310,311,312,py3}: PyTest
- py{37,38,39,310,311,312,py3}: PyTest-Cov
+ py{37,38,39,310,311,312,313,314,py3}: PyTest
wrapt1.10: wrapt ~= 1.10.0
wrapt1.11: wrapt ~= 1.11.0
wrapt1.12: wrapt ~= 1.12.0
wrapt1.13: wrapt ~= 1.13.0
wrapt1.14: wrapt ~= 1.14.0
+ wrapt1.15: wrapt ~= 1.15.0
+ wrapt1.16: wrapt ~= 1.16.0
+ wrapt1.17: wrapt ~= 1.17.0
+ wrapt2.0: wrapt ~= 2.0.0
+ setuptools; python_version>="3.12"
+
+[testenv:coverage]
+basepython = python
+commands =
+ pytest --cov-report=term-missing --cov=deprecated --cov-report=html tests/
+deps =
coverage
+ pytest
+ pytest-cov
setuptools; python_version>="3.12"
[testenv:docs]
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-deprecated/-/commit/334deee72d93f8c3162d14acbf7bcef2e8169649
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-deprecated/-/commit/334deee72d93f8c3162d14acbf7bcef2e8169649
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20251030/dd850a37/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list