[med-svn] [Git][med-team/q2-phylogeny][upstream] New upstream version 2022.2.0

Andreas Tille (@tille) gitlab at salsa.debian.org
Wed Jul 20 15:17:07 BST 2022



Andreas Tille pushed to branch upstream at Debian Med / q2-phylogeny


Commits:
1c29f148 by Andreas Tille at 2022-07-20T14:54:54+02:00
New upstream version 2022.2.0
- - - - -


25 changed files:

- .github/workflows/ci.yml
- LICENSE
- − ci/recipe/conda_build_config.yaml
- ci/recipe/meta.yaml
- q2_phylogeny/__init__.py
- q2_phylogeny/_align_to_tree_mafft_fasttree.py
- q2_phylogeny/_align_to_tree_mafft_iqtree.py
- q2_phylogeny/_align_to_tree_mafft_raxml.py
- q2_phylogeny/_fasttree.py
- q2_phylogeny/_filter.py
- q2_phylogeny/_iqtree.py
- q2_phylogeny/_raxml.py
- q2_phylogeny/_util.py
- q2_phylogeny/_version.py
- q2_phylogeny/plugin_setup.py
- q2_phylogeny/tests/__init__.py
- q2_phylogeny/tests/test_align_to_tree_mafft_fasttree.py
- q2_phylogeny/tests/test_align_to_tree_mafft_iqtree.py
- q2_phylogeny/tests/test_align_to_tree_mafft_raxml.py
- q2_phylogeny/tests/test_fasttree.py
- q2_phylogeny/tests/test_filter.py
- q2_phylogeny/tests/test_iqtree.py
- q2_phylogeny/tests/test_raxml.py
- q2_phylogeny/tests/test_util.py
- setup.py


Changes:

=====================================
.github/workflows/ci.yml
=====================================
@@ -50,6 +50,6 @@ jobs:
     - uses: qiime2/action-library-packaging at alpha1
       with:
         package-name: q2-phylogeny
-        build-target: tested
+        build-target: dev
         additional-tests: py.test --pyargs q2_phylogeny
         library-token: ${{ secrets.LIBRARY_TOKEN }}


=====================================
LICENSE
=====================================
@@ -1,6 +1,6 @@
 BSD 3-Clause License
 
-Copyright (c) 2016-2021, QIIME 2 development team.
+Copyright (c) 2016-2022, QIIME 2 development team.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without


=====================================
ci/recipe/conda_build_config.yaml deleted
=====================================
@@ -1,2 +0,0 @@
-python:
-  - 3.8


=====================================
ci/recipe/meta.yaml
=====================================
@@ -1,6 +1,5 @@
 {% set data = load_setup_py_data() %}
 {% set version = data.get('version') or 'placehold' %}
-{% set release = '.'.join(version.split('.')[:2]) %}
 
 package:
   name: q2-phylogeny
@@ -20,14 +19,23 @@ requirements:
   run:
     - python {{ python }}
     - scikit-bio
-    - qiime2 {{ release }}.*
-    - q2-types {{ release }}.*
-    - q2-alignment {{ release }}.*
-    - fasttree 2.1.10=0
+    - qiime2 {{ qiime2_epoch }}.*
+    - q2-types {{ qiime2_epoch }}.*
+    - q2-alignment {{ qiime2_epoch }}.*
+    # fasttree pinned because newer versions don't include fasttreemp on osx:
+    # https://github.com/bioconda/bioconda-recipes/blob/master/recipes/fasttree/build.sh
+    - fasttree ==2.1.10=0
     - raxml
     - iqtree >=1.6.4
+    - pytest
 
 test:
+  requires:
+    - qiime2 >={{ qiime2 }}
+    - q2-types >={{ q2_types }}
+    - q2-alignment >={{ q2_alignment }}
+    - pytest
+
   imports:
     - q2_phylogeny
     - qiime2.plugins.phylogeny


=====================================
q2_phylogeny/__init__.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #
@@ -10,7 +10,7 @@ from ._util import midpoint_root, robinson_foulds
 from ._fasttree import fasttree
 from ._raxml import raxml, raxml_rapid_bootstrap
 from ._iqtree import iqtree, iqtree_ultrafast_bootstrap
-from ._filter import filter_table
+from ._filter import filter_table, filter_tree
 from ._version import get_versions
 from ._align_to_tree_mafft_fasttree import align_to_tree_mafft_fasttree
 from ._align_to_tree_mafft_iqtree import align_to_tree_mafft_iqtree
@@ -22,4 +22,4 @@ del get_versions
 __all__ = ["midpoint_root", "fasttree", "align_to_tree_mafft_fasttree",
            "raxml", "raxml_rapid_bootstrap", "iqtree", "filter_table",
            "iqtree_ultrafast_bootstrap", "align_to_tree_mafft_iqtree",
-           "align_to_tree_mafft_raxml", "robinson_foulds"]
+           "align_to_tree_mafft_raxml", "robinson_foulds", 'filter_tree']


=====================================
q2_phylogeny/_align_to_tree_mafft_fasttree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/_align_to_tree_mafft_iqtree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/_align_to_tree_mafft_raxml.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/_fasttree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #
@@ -38,8 +38,10 @@ def fasttree(alignment: AlignedDNAFASTAFormat,
         cmd = ['FastTree']
     else:
         env = os.environ.copy()
-        n_threads = 0 if n_threads == 'auto' else n_threads
-        env.update({'OMP_NUM_THREADS': str(n_threads)})
+        if n_threads == 'auto':
+            env.pop('OMP_NUM_THREADS', 0)
+        else:
+            env.update({'OMP_NUM_THREADS': str(n_threads)})
         cmd = ['FastTreeMP']
 
     cmd.extend(['-quote', '-nt', aligned_fp])


=====================================
q2_phylogeny/_filter.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #
@@ -7,6 +7,8 @@
 # ----------------------------------------------------------------------------
 
 import biom
+import numpy as np
+import qiime2
 import skbio
 
 
@@ -19,3 +21,42 @@ def filter_table(table: biom.Table, tree: skbio.TreeNode) -> biom.Table:
     ids_to_keep = tip_ids & feature_ids
     table.filter(ids_to_keep, axis='observation', inplace=True)
     return table
+
+
+def filter_tree(tree: skbio.TreeNode,
+                table: biom.Table = None,
+                metadata: qiime2.Metadata = None,
+                where: str = None,
+                ) -> skbio.TreeNode:
+    """
+    Prunes a phylogenetic tree to match the input ids
+    """
+    # Checks the input metadata
+    if ((table is None) & (metadata is None)):
+        raise ValueError('A feature table, sequences or metadata must be '
+                         'provided for filtering.')
+    filter_refs = [table, metadata]
+    if np.sum([(ref is not None) for ref in filter_refs]).sum() > 1:
+        raise ValueError('Filtering can only be performed using one reference'
+                         ' file. Please choose between filtering with a '
+                         'feature table, sequences, or metadata.')
+    if (where is not None) & (metadata is None):
+        raise ValueError("Metadata must be provided if 'where' is specified")
+
+    # Gets the list of IDs to keep
+    if table is not None:
+        ids_to_keep = table.ids(axis='observation')
+    if metadata is not None:
+        ids_to_keep = metadata.get_ids(where)
+
+    # Gets the list of tips
+    tip_ids = set([t.name for t in tree.tips()])
+
+    # Checks for an intersection between ids
+    if not set(tip_ids).issuperset(set(ids_to_keep)):
+        raise ValueError('The ids for filtering must be a subset of '
+                         'the tips in the tree.')
+    sub_tree = tree.shear(ids_to_keep)
+    sub_tree.prune()
+
+    return sub_tree


=====================================
q2_phylogeny/_iqtree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/_raxml.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/_util.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/_version.py
=====================================
@@ -23,9 +23,9 @@ def get_keywords():
     # setup.py/versioneer.py will grep for the variable names, so they must
     # each be defined on a line of their own. _version.py will just call
     # get_keywords().
-    git_refnames = " (tag: 2021.4.0)"
-    git_full = "0396f2627d5b25594d68850f070a73645720386b"
-    git_date = "2021-04-20 15:27:09 +0000"
+    git_refnames = " (tag: 2022.2.0)"
+    git_full = "527838b0abdd3754b1fd98709e9cb97e262e6059"
+    git_date = "2022-02-18 19:38:00 +0000"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 


=====================================
q2_phylogeny/plugin_setup.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #
@@ -7,10 +7,12 @@
 # ----------------------------------------------------------------------------
 
 from qiime2.plugin import (Plugin, Citations, Int, Range, Str, Choices, Bool,
-                           Float, List)
+                           Float, List, TypeMatch, Metadata)
 from q2_types.tree import Phylogeny, Unrooted, Rooted
 from q2_types.feature_data import FeatureData, AlignedSequence, Sequence
-from q2_types.feature_table import FeatureTable, Frequency
+from q2_types.feature_table import (FeatureTable, Frequency,
+                                    RelativeFrequency, PresenceAbsence,
+                                    Composition)
 from q2_types.distance_matrix import DistanceMatrix
 
 import q2_phylogeny
@@ -400,12 +402,14 @@ plugin.methods.register_function(
     deprecated=True
 )
 
+T1 = TypeMatch([Frequency, RelativeFrequency, PresenceAbsence])
+
 plugin.methods.register_function(
     function=q2_phylogeny.filter_table,
-    inputs={'table': FeatureTable[Frequency],
+    inputs={'table': FeatureTable[T1],
             'tree': Phylogeny[Rooted | Unrooted]},
     parameters={},
-    outputs=[('filtered_table', FeatureTable[Frequency])],
+    outputs=[('filtered_table', FeatureTable[T1])],
     input_descriptions={
         'table': 'Feature table that features should be filtered from.',
         'tree': ('Tree where tip identifiers are the feature identifiers that '
@@ -418,6 +422,41 @@ plugin.methods.register_function(
                  "are not tip identifiers in tree.")
 )
 
+T2 = TypeMatch([Rooted, Unrooted])
+
+T3 = (Frequency | RelativeFrequency | PresenceAbsence | Composition)
+
+plugin.methods.register_function(
+    function=q2_phylogeny.filter_tree,
+    inputs={'tree': Phylogeny[T2],
+            'table': FeatureTable[T3],
+            },
+    parameters={'metadata': Metadata,
+                'where': Str
+                },
+    outputs=[('filtered_tree', Phylogeny[T2])],
+    input_descriptions={
+        'tree': ('Tree that should be filtered'),
+        'table': ('Feature table which contains the identifier that should be'
+                  ' retained in the tree'),
+    },
+    parameter_descriptions={
+        'metadata': ("Feature metadata to use with the 'where' statement or "
+                     "to select tips to be retained. Metadata objects could "
+                     "also include FeatureData[Sequence] data types, if, for"
+                     "instance, you want to filter to match represenative "
+                     "sequencces."),
+        'where': ('SQLite WHERE clause specifying sample metadata criteria '
+                  'that must be met to be included in the filtered feature '
+                  'table. If not provided, all samples in `metadata` that'
+                  ' are also in the feature table will be retained.'),
+    },
+    output_descriptions={'filtered_tree': 'The resulting phylogenetic tree.'},
+    name="Remove features from tree based on metadata",
+    description=("Remove tips from a tree if their identifiers based on a "
+                 "set of provided identifiers.")
+)
+
 plugin.methods.register_function(
     function=q2_phylogeny.robinson_foulds,
     inputs={'trees': List[Phylogeny[Rooted | Unrooted]]},


=====================================
q2_phylogeny/tests/__init__.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/tests/test_align_to_tree_mafft_fasttree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/tests/test_align_to_tree_mafft_iqtree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/tests/test_align_to_tree_mafft_raxml.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/tests/test_fasttree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #
@@ -52,7 +52,7 @@ class FastTreeTests(TestPluginBase):
         tip_names.sort()
         self.assertEqual(tip_names, ['_s_e_q_1_', '_s_e_q_2_'])
 
-    def test_fasttree_n_threads(self):
+    def test_fasttree_n_tips(self):
         input_fp = self.get_data_path('aligned-dna-sequences-1.fasta')
         input_sequences = AlignedDNAFASTAFormat(input_fp, mode='r')
         with redirected_stdio(stderr=os.devnull):
@@ -68,6 +68,51 @@ class FastTreeTests(TestPluginBase):
         self.assertEqual(tip_names, ['seq1', 'seq2'])
 
 
+# We are using pytest vs. unittest for this test
+# The capfd arg below is a built-in pytest fixture that allows for
+# captured stderr from all subprocesses that directly write
+# to operating system level output
+def test_fasttree_num_threads(capfd):
+    # Rather than providing an actual filepath, we just run a 'help' command
+    # Ideally, we supply --help in this param, but not available in FastTree
+    # So we are using -expert instead, so that FastTree doesn't fail
+    # ######################
+    # Output preview/example:
+    # ######################
+    # Detailed usage for FastTree 2.1.10 Double precision (No SSE3):
+    # FastTree [-nt] [-n 100] [-quote] [-pseudo | -pseudo 1.0]
+    #            [-boot 1000 | -nosupport]
+    #            [-intree starting_trees_file | -intree1 starting_tree_file]
+    #            [-quiet | -nopr]
+    #            [-nni 10] [-spr 2] [-noml | -mllen | -mlnni 10]
+    #            [-mlacc 2] [-cat 20 | -nocat] [-gamma]
+    #            [-slow | -fastest] [-2nd | -no2nd] [-slownni] [-seed 1253]
+    #            [-top | -notop] [-topm 1.0 [-close 0.75] [-refresh 0.8]]
+    #            [-matrix Matrix | -nomatrix] [-nj | -bionj]
+    #            [-lg] [-wag] [-nt] [-gtr] [-gtrrates ac ag at cg ct gt]
+    #            [-gtrfreq A C G T]
+    #            [ -constraints constraintAlignment
+    #            [ -constraintWeight 100.0 ] ]
+    #            [-log logfile]
+    #          [ alignment_file ]
+    #         [ -out output_newick_file | > newick_tree]
+    fasttree('-expert', n_threads=1)
+    captured = capfd.readouterr()
+    assert 'OpenMP' not in captured.err
+
+    fasttree('-expert', n_threads=20)
+    captured = capfd.readouterr()
+    assert 'OpenMP (20 threads)' in captured.err
+
+    # This test case ensures that when a user enters 'auto', the n_threads
+    # var will still be set to the max available on their machine, even if
+    # the OMP_NUM_THREADS env var has been set on their machine
+    os.environ['OMP_NUM_THREADS'] = '2560'
+    fasttree('-expert', n_threads='auto')
+    captured = capfd.readouterr()
+    assert 'OpenMP (2560 threads)' not in captured.err
+
+
 class RunCommandTests(TestPluginBase):
 
     package = 'q2_phylogeny.tests'


=====================================
q2_phylogeny/tests/test_filter.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #
@@ -10,10 +10,13 @@ import unittest
 import io
 
 import numpy as np
+import pandas as pd
 from biom.table import Table
 import skbio
 
-from q2_phylogeny import filter_table
+from qiime2 import Metadata
+
+from q2_phylogeny import (filter_table, filter_tree)
 
 
 class FilterTableTests(unittest.TestCase):
@@ -49,5 +52,76 @@ class FilterTableTests(unittest.TestCase):
         self.assertEqual(actual, expected)
 
 
+class FilterTreeTests(unittest.TestCase):
+    def setUp(self):
+        rooted_nwk = io.StringIO("((A:0.1, B:0.2)C:0.3, D:0.4, E:0.5)root;")
+        self.tree = skbio.TreeNode.read(rooted_nwk)
+        self.metadata = Metadata(pd.DataFrame(
+                data=np.array([['Bacteria', '1'],
+                               ['Archea', '1']], dtype=object),
+                index=pd.Index(['A', 'D'], name='Feature ID'),
+                columns=['kingdom', 'keep'],
+            ))
+        self.table = Table(data=np.array([[0, 1, 2], [2, 2, 2]]),
+                           observation_ids=['A', 'D'],
+                           sample_ids=['S1', 'S2', 'S3']
+                           )
+        self.filtered_tree = self.tree.copy().shear(['A', 'D'])
+        self.filtered_tree.prune()
+
+    def test_filter_tree_error_no_filter_art(self):
+        with self.assertRaises(ValueError) as err:
+            filter_tree(self.tree)
+        self.assertEqual(
+            str(err.exception),
+            ('A feature table, sequences or metadata must be provided for '
+                'filtering.')
+            )
+
+    def test_filter_tree_error_multiple_filter_arts(self):
+        with self.assertRaises(ValueError) as err:
+            filter_tree(self.tree,
+                        table=self.table,
+                        metadata=self.metadata)
+        self.assertEqual(
+            str(err.exception),
+            ('Filtering can only be performed using one reference file. '
+             'Please choose between filtering with a feature table, '
+             'sequences, or metadata.')
+        )
+
+    def test_filter_tree_error_where_no_metadata(self):
+        with self.assertRaises(ValueError) as err:
+            filter_tree(self.tree,
+                        table=self.table,
+                        where='[kingdom]="Archea"')
+        self.assertEqual(
+            str(err.exception),
+            ("Metadata must be provided if 'where' is specified")
+            )
+
+    def test_filter_tree_error_filter_superset(self):
+        metadata = Metadata(pd.DataFrame(
+            data=np.array([[1, 1, 0]]).T,
+            index=pd.Index(['A', 'D', 'F'], name='feature-id'),
+            columns=['keep']
+        ))
+        with self.assertRaises(ValueError) as err:
+            filter_tree(self.tree,
+                        metadata=metadata)
+        self.assertEqual(
+            str(err.exception),
+            'The ids for filtering must be a subset of the tips in the tree.'
+            )
+
+    def test_filter_tree_metadata(self):
+        test = filter_tree(self.tree, metadata=self.metadata)
+        self.assertEqual(str(test), str(self.filtered_tree))
+
+    def test_filter_tree_table(self):
+        test = filter_tree(self.tree, table=self.table)
+        self.assertEqual(str(test), str(self.filtered_tree))
+
+
 if __name__ == "__main__":
     unittest.main()


=====================================
q2_phylogeny/tests/test_iqtree.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/tests/test_raxml.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
q2_phylogeny/tests/test_util.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #


=====================================
setup.py
=====================================
@@ -1,5 +1,5 @@
 # ----------------------------------------------------------------------------
-# Copyright (c) 2016-2021, QIIME 2 development team.
+# Copyright (c) 2016-2022, QIIME 2 development team.
 #
 # Distributed under the terms of the Modified BSD License.
 #



View it on GitLab: https://salsa.debian.org/med-team/q2-phylogeny/-/commit/1c29f1488a498f6f0cd2a94b11e7860dc819f1de

-- 
View it on GitLab: https://salsa.debian.org/med-team/q2-phylogeny/-/commit/1c29f1488a498f6f0cd2a94b11e7860dc819f1de
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/debian-med-commit/attachments/20220720/5361b357/attachment-0001.htm>


More information about the debian-med-commit mailing list