[med-svn] [Git][med-team/augur][master] 4 commits: routine-update: New upstream version
Andreas Tille (@tille)
gitlab at salsa.debian.org
Fri Oct 28 08:37:25 BST 2022
Andreas Tille pushed to branch master at Debian Med / augur
Commits:
ac972c38 by Andreas Tille at 2022-10-28T09:29:38+02:00
routine-update: New upstream version
- - - - -
797cec5e by Andreas Tille at 2022-10-28T09:29:39+02:00
New upstream version 18.1.0
- - - - -
26546f97 by Andreas Tille at 2022-10-28T09:31:20+02:00
Update upstream source from tag 'upstream/18.1.0'
Update to upstream version '18.1.0'
with Debian dir 6927fbb5f7be4e727aca63cda69fa09c92c0af1b
- - - - -
10e63a1e by Andreas Tille at 2022-10-28T09:33:37+02:00
routine-update: Ready to upload to unstable
- - - - -
11 changed files:
- .gitignore
- CHANGES.md
- augur/__version__.py
- augur/dates.py
- augur/filter.py
- debian/changelog
- tests/functional/filter/cram/subsample-group-by-missing-error.t
- + tests/functional/filter/cram/subsample-group-by-week.t
- tests/functional/filter/cram/subsample-group-by-with-custom-year-column.t
- tests/functional/filter/cram/subsample-skip-ambiguous-dates.t
- tests/test_filter.py
Changes:
=====================================
.gitignore
=====================================
@@ -16,6 +16,13 @@ dist/
*stderr
/docs/_build/
+# Output files from running tests
+tests/functional/tree/full_aligned-delim.fasta.contree
+tests/functional/tree/full_aligned-delim.fasta.splits.nex
+tests/functional/tree/full_aligned-delim.fasta.ufboot
+tests/functional/tree/masked_aligned-delim.iqtree.log.xz
+tests/functional/tree/masked_aligned.fasta.xz
+
# For Python #
##############
*.pyc
=====================================
CHANGES.md
=====================================
@@ -3,6 +3,21 @@
## __NEXT__
+## 18.1.0 (26 October 2022)
+
+### Features
+
+* filter: Add support to group by ISO week (`--group-by week`) during subsampling. [#1067][] (@victorlin)
+
+### Bug Fixes
+
+* filter: Fixed unintended behavior in which grouping by `day` would "work" when used with `month` and/or `year`. Updated so it will be ignored. [#1070][] (@victorlin)
+* filter: Fixed unintended behavior in which grouping by `month` with ambiguous years would "work". Updated so date ambiguity is checked properly for all generated columns. [#1072][] (@victorlin)
+
+[#1067]: https://github.com/nextstrain/augur/pull/1067
+[#1070]: https://github.com/nextstrain/augur/pull/1070
+[#1072]: https://github.com/nextstrain/augur/pull/1072
+
## 18.0.0 (21 September 2022)
### Major Changes
=====================================
augur/__version__.py
=====================================
@@ -1,4 +1,4 @@
-__version__ = '18.0.0'
+__version__ = '18.1.0'
def is_augur_version_compatible(version):
=====================================
augur/dates.py
=====================================
@@ -137,3 +137,6 @@ def get_numerical_dates(metadata:pd.DataFrame, name_col = None, date_col='date',
strains = metadata.index.values
dates = metadata[date_col].astype(float)
return dict(zip(strains, dates))
+
+def get_iso_year_week(year, month, day):
+ return datetime.date(year, month, day).isocalendar()[:2]
=====================================
augur/filter.py
=====================================
@@ -14,10 +14,11 @@ import pandas as pd
import random
import re
import sys
+import uuid
from tempfile import NamedTemporaryFile
from typing import Collection
-from .dates import numeric_date, numeric_date_type, SUPPORTED_DATE_HELP_TEXT, is_date_ambiguous, get_numerical_dates
+from .dates import numeric_date, numeric_date_type, SUPPORTED_DATE_HELP_TEXT, is_date_ambiguous, get_numerical_dates, get_iso_year_week
from .errors import AugurError
from .index import index_sequences, index_vcf
from .io import open_file, read_metadata, read_sequences, write_sequences, is_vcf as filename_is_vcf, write_vcf
@@ -30,6 +31,9 @@ SEQUENCE_ONLY_FILTERS = (
"non_nucleotide",
)
+# Use sorted() for reproducible output
+GROUP_BY_GENERATED_COLUMNS = {'year', 'month', 'week'}
+
def register_arguments(parser):
"""
Add arguments to parser.
@@ -67,10 +71,13 @@ def register_arguments(parser):
sequence_filter_group.add_argument('--non-nucleotide', action='store_true', help="exclude sequences that contain illegal characters")
subsample_group = parser.add_argument_group("subsampling", "options to subsample filtered data")
- subsample_group.add_argument('--group-by', nargs='+', help="""
+ subsample_group.add_argument('--group-by', nargs='+', help=f"""
categories with respect to subsample.
- Grouping by 'year' and/or 'month' is only supported when there is a 'date' column in the metadata.
- Custom 'year' and 'month' columns in the metadata are ignored for grouping. Please rename them if you want to use their values for grouping.""")
+ Notes:
+ (1) Grouping by {sorted(GROUP_BY_GENERATED_COLUMNS)} is only supported when there is a 'date' column in the metadata.
+ (2) 'week' uses the ISO week numbering system, where a week starts on a Monday and ends on a Sunday.
+ (3) 'month' and 'week' grouping cannot be used together.
+ (4) Custom columns {sorted(GROUP_BY_GENERATED_COLUMNS)} in the metadata are ignored for grouping. Please rename them if you want to use their values for grouping.""")
subsample_limits_group = subsample_group.add_mutually_exclusive_group()
subsample_limits_group.add_argument('--sequences-per-group', type=int, help="subsample to no more than this number of sequences per category")
subsample_limits_group.add_argument('--subsample-max-sequences', type=int, help="subsample to no more than this number of sequences; can be used without the group_by argument")
@@ -994,63 +1001,85 @@ def get_groups_for_subsampling(strains, metadata, group_by=None):
return group_by_strain, skipped_strains
group_by_set = set(group_by)
+ generated_columns_requested = GROUP_BY_GENERATED_COLUMNS & group_by_set
# If we could not find any requested categories, we cannot complete subsampling.
- if 'date' not in metadata and group_by_set <= {'year', 'month'}:
- raise FilterException(f"The specified group-by categories ({group_by}) were not found. Note that using 'year' or 'year month' requires a column called 'date'.")
- if not group_by_set & (set(metadata.columns) | {'year', 'month'}):
+ if 'date' not in metadata and group_by_set <= GROUP_BY_GENERATED_COLUMNS:
+ raise FilterException(f"The specified group-by categories ({group_by}) were not found. Note that using any of {sorted(GROUP_BY_GENERATED_COLUMNS)} requires a column called 'date'.")
+ if not group_by_set & (set(metadata.columns) | GROUP_BY_GENERATED_COLUMNS):
raise FilterException(f"The specified group-by categories ({group_by}) were not found.")
- # date requested
- if 'year' in group_by_set or 'month' in group_by_set:
+ # Warn/error based on other columns grouped with 'week'.
+ if 'week' in group_by_set:
+ if 'year' in group_by_set:
+ print(f"WARNING: 'year' grouping will be ignored since 'week' includes ISO year.", file=sys.stderr)
+ group_by.remove('year')
+ group_by_set.remove('year')
+ generated_columns_requested.remove('year')
+ if 'month' in group_by_set:
+ raise AugurError("'month' and 'week' grouping cannot be used together.")
+
+ if generated_columns_requested:
- if 'year' in metadata.columns and 'year' in group_by_set:
- print(f"WARNING: `--group-by year` uses the generated year value from the 'date' column. The custom 'year' column in the metadata is ignored for grouping purposes.", file=sys.stderr)
- metadata.drop('year', axis=1, inplace=True)
- if 'month' in metadata.columns and 'month' in group_by_set:
- print(f"WARNING: `--group-by month` uses the generated month value from the 'date' column. The custom 'month' column in the metadata is ignored for grouping purposes.", file=sys.stderr)
- metadata.drop('month', axis=1, inplace=True)
+ for col in sorted(generated_columns_requested):
+ if col in metadata.columns:
+ print(f"WARNING: `--group-by {col}` uses a generated {col} value from the 'date' column. The custom '{col}' column in the metadata is ignored for grouping purposes.", file=sys.stderr)
+ metadata.drop(col, axis=1, inplace=True)
if 'date' not in metadata:
- # set year/month/day = unknown
- print(f"WARNING: A 'date' column could not be found to group-by year or month.", file=sys.stderr)
+ # Set generated columns to 'unknown'.
+ print(f"WARNING: A 'date' column could not be found to group-by {sorted(generated_columns_requested)}.", file=sys.stderr)
print(f"Filtering by group may behave differently than expected!", file=sys.stderr)
- df_dates = pd.DataFrame({'year': 'unknown', 'month': 'unknown'}, index=metadata.index)
+ df_dates = pd.DataFrame({col: 'unknown' for col in GROUP_BY_GENERATED_COLUMNS}, index=metadata.index)
metadata = pd.concat([metadata, df_dates], axis=1)
else:
- # replace date with year/month/day as nullable ints
- date_cols = ['year', 'month', 'day']
+ # Create a DataFrame with year/month/day columns as nullable ints.
+ # These columns are prefixed to note temporary usage. They are used
+ # to generate other columns, and will be discarded at the end.
+ temp_prefix = str(uuid.uuid4())
+ temp_date_cols = [f'{temp_prefix}year', f'{temp_prefix}month', f'{temp_prefix}day']
df_dates = metadata['date'].str.split('-', n=2, expand=True)
- df_dates = df_dates.set_axis(date_cols[:len(df_dates.columns)], axis=1)
- missing_date_cols = set(date_cols) - set(df_dates.columns)
+ df_dates = df_dates.set_axis(temp_date_cols[:len(df_dates.columns)], axis=1)
+ missing_date_cols = set(temp_date_cols) - set(df_dates.columns)
for col in missing_date_cols:
df_dates[col] = pd.NA
- for col in date_cols:
+ for col in temp_date_cols:
df_dates[col] = pd.to_numeric(df_dates[col], errors='coerce').astype(pd.Int64Dtype())
+
+ # Extend metadata with generated date columns
+ # Drop the 'date' column since it should not be used for grouping.
metadata = pd.concat([metadata.drop('date', axis=1), df_dates], axis=1)
- if 'year' in group_by_set:
- # skip ambiguous years
- df_skip = metadata[metadata['year'].isnull()]
- metadata.dropna(subset=['year'], inplace=True)
- for strain in df_skip.index:
- skipped_strains.append({
- "strain": strain,
- "filter": "skip_group_by_with_ambiguous_year",
- "kwargs": "",
- })
- if 'month' in group_by_set:
- # skip ambiguous months
- df_skip = metadata[metadata['month'].isnull()]
- metadata.dropna(subset=['month'], inplace=True)
- for strain in df_skip.index:
- skipped_strains.append({
- "strain": strain,
- "filter": "skip_group_by_with_ambiguous_month",
- "kwargs": "",
- })
- # month = (year, month)
- metadata['month'] = list(zip(metadata['year'], metadata['month']))
- # TODO: support group by day
+
+ ambiguous_date_strains = list(_get_ambiguous_date_skipped_strains(
+ metadata,
+ temp_prefix,
+ generated_columns_requested
+ ))
+ metadata.drop([record['strain'] for record in ambiguous_date_strains], inplace=True)
+ skipped_strains.extend(ambiguous_date_strains)
+
+ # Generate columns.
+ if 'year' in generated_columns_requested:
+ metadata['year'] = metadata[f'{temp_prefix}year']
+ if 'month' in generated_columns_requested:
+ metadata['month'] = list(zip(
+ metadata[f'{temp_prefix}year'],
+ metadata[f'{temp_prefix}month']
+ ))
+ if 'week' in generated_columns_requested:
+ # Note that week = (year, week) from the date.isocalendar().
+ # Do not combine the raw year with the ISO week number alone,
+ # since raw year ≠ ISO year.
+ metadata['week'] = metadata.apply(lambda row: get_iso_year_week(
+ row[f'{temp_prefix}year'],
+ row[f'{temp_prefix}month'],
+ row[f'{temp_prefix}day']
+ ), axis=1
+ )
+
+ # Drop the internally used columns.
+ for col in temp_date_cols:
+ metadata.drop(col, axis=1, inplace=True)
unknown_groups = group_by_set - set(metadata.columns)
if unknown_groups:
@@ -1059,10 +1088,59 @@ def get_groups_for_subsampling(strains, metadata, group_by=None):
for group in unknown_groups:
metadata[group] = 'unknown'
+ # Finally, determine groups.
group_by_strain = dict(zip(metadata.index, metadata[group_by].apply(tuple, axis=1)))
return group_by_strain, skipped_strains
+def _get_ambiguous_date_skipped_strains(
+ metadata, temp_prefix, generated_columns_requested):
+ """Get strains skipped due to date ambiguity.
+
+ Each value is a dictionary with keys:
+ - `strain`: strain name
+ - `filter`: filter reason. Used for the final report output.
+ - `kwargs`: Empty string since filter reason does not represent a function.
+ """
+ # Don't yield the same strain twice.
+ already_skipped_strains = set()
+
+ if generated_columns_requested:
+ # Skip ambiguous years.
+ df_skip = metadata[metadata[f'{temp_prefix}year'].isnull()]
+ for strain in df_skip.index:
+ if strain not in already_skipped_strains:
+ yield {
+ "strain": strain,
+ "filter": "skip_group_by_with_ambiguous_year",
+ "kwargs": "",
+ }
+ already_skipped_strains.update(df_skip.index)
+ if 'month' in generated_columns_requested or 'week' in generated_columns_requested:
+ # Skip ambiguous months.
+ df_skip = metadata[metadata[f'{temp_prefix}month'].isnull()]
+ for strain in df_skip.index:
+ if strain not in already_skipped_strains:
+ yield {
+ "strain": strain,
+ "filter": "skip_group_by_with_ambiguous_month",
+ "kwargs": "",
+ }
+ already_skipped_strains.update(df_skip.index)
+ if 'week' in generated_columns_requested:
+ # Skip ambiguous days.
+ df_skip = metadata[metadata[f'{temp_prefix}day'].isnull()]
+ for strain in df_skip.index:
+ if strain not in already_skipped_strains:
+ yield {
+ "strain": strain,
+ "filter": "skip_group_by_with_ambiguous_day",
+ "kwargs": "",
+ }
+ # TODO: uncomment if another filter reason is ever added.
+ # already_skipped_strains.update(df_skip.index)
+
+
class PriorityQueue:
"""A priority queue implementation that automatically replaces lower priority
items in the heap with incoming higher priority items.
@@ -1713,6 +1791,7 @@ def run(args):
"filter_by_non_nucleotide": "{count} of these were dropped because they had non-nucleotide characters",
"skip_group_by_with_ambiguous_year": "{count} were dropped during grouping due to ambiguous year information",
"skip_group_by_with_ambiguous_month": "{count} were dropped during grouping due to ambiguous month information",
+ "skip_group_by_with_ambiguous_day": "{count} were dropped during grouping due to ambiguous day information",
"force_include_strains": "{count} strains were added back because they were in {include_file}",
"force_include_where": "{count} sequences were added back because of '{include_where}'",
}
=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+augur (18.1.0-1) unstable; urgency=medium
+
+ * New upstream version
+
+ -- Andreas Tille <tille at debian.org> Fri, 28 Oct 2022 09:31:41 +0200
+
augur (18.0.0-1) unstable; urgency=medium
* New upstream version
=====================================
tests/functional/filter/cram/subsample-group-by-missing-error.t
=====================================
@@ -16,7 +16,7 @@ Error on missing group-by columns.
> --group-by year \
> --sequences-per-group 1 \
> --output-metadata $TMP/metadata-filtered.tsv > /dev/null
- ERROR: The specified group-by categories (['year']) were not found. Note that using 'year' or 'year month' requires a column called 'date'.
+ ERROR: The specified group-by categories (['year']) were not found. Note that using any of ['month', 'week', 'year'] requires a column called 'date'.
[2]
$ cat $TMP/metadata-filtered.tsv
cat: .*: No such file or directory (re)
=====================================
tests/functional/filter/cram/subsample-group-by-week.t
=====================================
@@ -0,0 +1,54 @@
+Setup
+
+ $ pushd "$TESTDIR" > /dev/null
+ $ source _setup.sh
+
+SEQ1 and SEQ2 translate to week=(2003, 1).
+SEQ3 and SEQ4 translate to week=(2004, 1).
+These should be in separate groups.
+
+ $ cat >$TMP/metadata.tsv <<~~
+ > strain date
+ > SEQ1 2003-01-01
+ > SEQ2 2003-01-02
+ > SEQ3 2003-12-30
+ > SEQ4 2003-12-31
+ > ~~
+
+ $ ${AUGUR} filter \
+ > --metadata $TMP/metadata.tsv \
+ > --group-by week \
+ > --sequences-per-group 1 \
+ > --subsample-seed 0 \
+ > --output-metadata $TMP/metadata-filtered.tsv
+ 2 strains were dropped during filtering
+ \t2 of these were dropped because of subsampling criteria (esc)
+ 2 strains passed all filters
+ $ cat $TMP/metadata-filtered.tsv
+ strain date
+ SEQ1 2003-01-01
+ SEQ3 2003-12-30
+
+ISO year from 'week' takes precedence over 'year'.
+
+ $ cat >$TMP/metadata.tsv <<~~
+ > strain date
+ > SEQ1 2003-12-30
+ > SEQ2 2003-12-31
+ > SEQ3 2004-01-01
+ > SEQ4 2004-01-02
+ > ~~
+
+ $ ${AUGUR} filter \
+ > --metadata $TMP/metadata.tsv \
+ > --group-by year week \
+ > --sequences-per-group 1 \
+ > --subsample-seed 0 \
+ > --output-metadata $TMP/metadata-filtered.tsv
+ WARNING: 'year' grouping will be ignored since 'week' includes ISO year.
+ 3 strains were dropped during filtering
+ \t3 of these were dropped because of subsampling criteria (esc)
+ 1 strains passed all filters
+ $ cat $TMP/metadata-filtered.tsv
+ strain date
+ SEQ1 2003-12-30
=====================================
tests/functional/filter/cram/subsample-group-by-with-custom-year-column.t
=====================================
@@ -22,7 +22,7 @@ Group by generated year column, and ensure all original columns are still in the
> --sequences-per-group 1 \
> --subsample-seed 0 \
> --output-metadata "$TMP/filtered_metadata.tsv" > /dev/null
- WARNING: `--group-by year` uses the generated year value from the 'date' column. The custom 'year' column in the metadata is ignored for grouping purposes.
+ WARNING: `--group-by year` uses a generated year value from the 'date' column. The custom 'year' column in the metadata is ignored for grouping purposes.
$ cat "$TMP/filtered_metadata.tsv"
strain\tdate\tyear\tmonth (esc)
SEQ1\t2021-01-01\todd\tJanuary (esc)
@@ -36,8 +36,8 @@ Group by generated year and month columns, and ensure all original columns are s
> --sequences-per-group 1 \
> --subsample-seed 0 \
> --output-metadata "$TMP/filtered_metadata.tsv" > /dev/null
- WARNING: `--group-by year` uses the generated year value from the 'date' column. The custom 'year' column in the metadata is ignored for grouping purposes.
- WARNING: `--group-by month` uses the generated month value from the 'date' column. The custom 'month' column in the metadata is ignored for grouping purposes.
+ WARNING: `--group-by month` uses a generated month value from the 'date' column. The custom 'month' column in the metadata is ignored for grouping purposes.
+ WARNING: `--group-by year` uses a generated year value from the 'date' column. The custom 'year' column in the metadata is ignored for grouping purposes.
$ cat "$TMP/filtered_metadata.tsv"
strain\tdate\tyear\tmonth (esc)
SEQ1\t2021-01-01\todd\tJanuary (esc)
=====================================
tests/functional/filter/cram/subsample-skip-ambiguous-dates.t
=====================================
@@ -17,3 +17,30 @@ Strains with ambiguous years or months should be dropped and logged.
SG_018\tskip_group_by_with_ambiguous_month (esc)
$ grep "COL/FLR_00024/2015" "$TMP/filtered_log.tsv" | cut -f 1-2
COL/FLR_00024/2015\tskip_group_by_with_ambiguous_year (esc)
+
+Group by 'year month week'. Using 'week' has some restrictions - 'year' should warn and 'month' should error.
+
+ $ ${AUGUR} filter \
+ > --metadata filter/data/metadata.tsv \
+ > --group-by year month week \
+ > --sequences-per-group 1 \
+ > --output-strains "$TMP/filtered_strains.txt" > /dev/null
+ WARNING: 'year' grouping will be ignored since 'week' includes ISO year.
+ ERROR: 'month' and 'week' grouping cannot be used together.
+ [2]
+
+Group by 'week'. Check the number of strains that have been dropped due to ambiguous day.
+
+ $ ${AUGUR} filter \
+ > --metadata filter/data/metadata.tsv \
+ > --group-by week \
+ > --sequences-per-group 1 \
+ > --subsample-seed 0 \
+ > --output-strains "$TMP/filtered_strains.txt" \
+ > --output-log "$TMP/filtered_log.tsv" > /dev/null
+ $ grep "skip_group_by_with_ambiguous_year" "$TMP/filtered_log.tsv" | wc -l
+ \s*1 (re)
+ $ grep "skip_group_by_with_ambiguous_month" "$TMP/filtered_log.tsv" | wc -l
+ \s*1 (re)
+ $ grep "skip_group_by_with_ambiguous_day" "$TMP/filtered_log.tsv" | wc -l
+ \s*3 (re)
=====================================
tests/test_filter.py
=====================================
@@ -473,7 +473,7 @@ class TestFilterGroupBy:
strains = metadata.index.tolist()
with pytest.raises(FilterException) as e_info:
augur.filter.get_groups_for_subsampling(strains, metadata, group_by=groups)
- assert str(e_info.value) == "The specified group-by categories (['year']) were not found. Note that using 'year' or 'year month' requires a column called 'date'."
+ assert str(e_info.value) == "The specified group-by categories (['year']) were not found. Note that using any of ['month', 'week', 'year'] requires a column called 'date'."
def test_filter_groupby_missing_month_error(self, valid_metadata: pd.DataFrame):
groups = ['month']
@@ -482,7 +482,7 @@ class TestFilterGroupBy:
strains = metadata.index.tolist()
with pytest.raises(FilterException) as e_info:
augur.filter.get_groups_for_subsampling(strains, metadata, group_by=groups)
- assert str(e_info.value) == "The specified group-by categories (['month']) were not found. Note that using 'year' or 'year month' requires a column called 'date'."
+ assert str(e_info.value) == "The specified group-by categories (['month']) were not found. Note that using any of ['month', 'week', 'year'] requires a column called 'date'."
def test_filter_groupby_missing_year_and_month_error(self, valid_metadata: pd.DataFrame):
groups = ['year', 'month']
@@ -491,7 +491,7 @@ class TestFilterGroupBy:
strains = metadata.index.tolist()
with pytest.raises(FilterException) as e_info:
augur.filter.get_groups_for_subsampling(strains, metadata, group_by=groups)
- assert str(e_info.value) == "The specified group-by categories (['year', 'month']) were not found. Note that using 'year' or 'year month' requires a column called 'date'."
+ assert str(e_info.value) == "The specified group-by categories (['year', 'month']) were not found. Note that using any of ['month', 'week', 'year'] requires a column called 'date'."
def test_filter_groupby_missing_date_warn(self, valid_metadata: pd.DataFrame, capsys):
groups = ['country', 'year', 'month']
@@ -507,7 +507,7 @@ class TestFilterGroupBy:
'SEQ_5': ('B', 'unknown', 'unknown')
}
captured = capsys.readouterr()
- assert captured.err == "WARNING: A 'date' column could not be found to group-by year or month.\nFiltering by group may behave differently than expected!\n"
+ assert captured.err == "WARNING: A 'date' column could not be found to group-by ['month', 'year'].\nFiltering by group may behave differently than expected!\n"
assert skipped_strains == []
def test_filter_groupby_no_strains(self, valid_metadata: pd.DataFrame):
View it on GitLab: https://salsa.debian.org/med-team/augur/-/compare/2cd127596ed35723c2dc28adaa548f20d33b4d13...10e63a1e62046d9b221fefdba42ff58fe1a80157
--
View it on GitLab: https://salsa.debian.org/med-team/augur/-/compare/2cd127596ed35723c2dc28adaa548f20d33b4d13...10e63a1e62046d9b221fefdba42ff58fe1a80157
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/20221028/0c97fd40/attachment-0001.htm>
More information about the debian-med-commit
mailing list