Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-khard for openSUSE:Factory 
checked in at 2026-10-05 12:56:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-khard (Old)
 and      /work/SRC/openSUSE:Factory/.python-khard.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-khard"

Mon Oct  5 12:56:48 2026 rev:6 rq:1382329 version:0.22.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-khard/python-khard.changes        
2026-06-28 21:07:55.384882215 +0200
+++ /work/SRC/openSUSE:Factory/.python-khard.new.3698736/python-khard.changes   
2026-10-05 12:57:52.728405175 +0200
@@ -1,0 +2,9 @@
+Sun Oct  4 14:43:24 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.22.0:
+  * Support vcard IMPP field (#359)
+  * Fix YAML output for single organisation (#358)
+  * Require setuptools >= 77 (60be3cb)
+  * deps: remove dependency on sphinx_autodoc_typehints extension
+
+-------------------------------------------------------------------

Old:
----
  khard-0.21.0.tar.gz

New:
----
  khard-0.22.0.tar.gz

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

Other differences:
------------------
++++++ python-khard.spec ++++++
--- /var/tmp/diff_new_pack.p9Aj8c/_old  2026-10-05 12:57:53.527438691 +0200
+++ /var/tmp/diff_new_pack.p9Aj8c/_new  2026-10-05 12:57:53.530438817 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-khard
-Version:        0.21.0
+Version:        0.22.0
 Release:        0
 Summary:        Console carddav client
 License:        GPL-3.0-only

++++++ khard-0.21.0.tar.gz -> khard-0.22.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/CHANGES new/khard-0.22.0/CHANGES
--- old/khard-0.21.0/CHANGES    2026-06-15 15:39:35.000000000 +0200
+++ new/khard-0.22.0/CHANGES    2026-10-02 09:11:55.000000000 +0200
@@ -1,6 +1,14 @@
 Change Log
 ==========
 
+v0.22.0  2026-10-02
+
+- Support vcard IMPP field (#359)
+- Fix YAML output for single organisation (#358)
+- Require setuptools >= 77 (60be3cb)
+- deps: remove dependency on sphinx_autodoc_typehints extension (#356)
+
+
 v0.21.0 2026-06-15
 
 - Remove support for python 3.9
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/PKG-INFO new/khard-0.22.0/PKG-INFO
--- old/khard-0.21.0/PKG-INFO   2026-06-15 15:42:00.077532300 +0200
+++ new/khard-0.22.0/PKG-INFO   2026-10-02 09:15:32.092392000 +0200
@@ -1,9 +1,9 @@
 Metadata-Version: 2.4
 Name: khard
-Version: 0.21.0
+Version: 0.22.0
 Summary: A console address book manager
 Author-email: Eric Scheibler <[email protected]>
-License: GPL
+License-Expression: GPL-3.0-only
 Project-URL: homepage, https://github.com/lucc/khard
 Project-URL: documentation, https://khard.readthedocs.io/en/latest/
 Project-URL: repository, https://github.com/lucc/khard.git
@@ -13,7 +13,6 @@
 Classifier: Environment :: Console
 Classifier: Topic :: Utilities
 Classifier: Topic :: Communications :: Email :: Address Book
-Classifier: License :: OSI Approved :: GNU General Public License (GPL)
 Classifier: Intended Audience :: End Users/Desktop
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python :: 3 :: Only
@@ -27,7 +26,6 @@
 Requires-Dist: sphinx; extra == "doc"
 Requires-Dist: sphinx-argparse; extra == "doc"
 Requires-Dist: sphinx-autoapi; extra == "doc"
-Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
 Provides-Extra: typing
 Requires-Dist: mypy; extra == "typing"
 Dynamic: license-file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard/cli.py 
new/khard-0.22.0/khard/cli.py
--- old/khard-0.21.0/khard/cli.py       2026-06-15 15:13:21.000000000 +0200
+++ new/khard-0.22.0/khard/cli.py       2026-10-02 08:54:01.000000000 +0200
@@ -457,6 +457,6 @@
         sys.exit(str(err))
 
 
-def _sphinxarg_helper():
+def _sphinxarg_helper() -> argparse.ArgumentParser:
     """Helper function to return the correct argument parser for the docs."""
     return create_parsers()[1]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard/contacts.py 
new/khard-0.22.0/khard/contacts.py
--- old/khard-0.21.0/khard/contacts.py  2026-06-15 15:13:34.000000000 +0200
+++ new/khard-0.22.0/khard/contacts.py  2026-10-02 08:54:01.000000000 +0200
@@ -807,6 +807,47 @@
             label_obj.value = custom_types[0]
 
     @property
+    def impp(self) -> dict[str, list[str]]:
+        """
+        :returns: dict of type and instant message handle list
+        """
+        impp_dict: dict[str, list[str]] = {}
+        try:
+            impp = self.vcard.impp_list
+        except AttributeError:
+            return {}
+        for child in impp:
+            try:
+                type, value = child.value.split(":", maxsplit=1)
+            except ValueError:
+                continue
+            try:
+                pref = int(child.params.get("PREF")[0])
+                type += f", pref={pref}"
+            except (IndexError, TypeError, ValueError):
+                pass
+            if type not in impp_dict:
+                impp_dict[type] = []
+            impp_dict[type].append(value)
+        # sort impp handles lists
+        for impp_list in impp_dict.values():
+            impp_list.sort()
+        return impp_dict
+
+    def _add_impp(self, type: str, handle: str) -> None:
+        _, types, pref = self._parse_type_value(string_to_list(type, ","), [])
+        if not types:
+            raise ValueError(f"Type for impp handle {handle} is missing.")
+        for type in types:
+            if ":" in type:
+                raise ValueError(f"Impp type \"{type}\" must not contain a 
colon.")
+            impp_obj = self.vcard.add('impp')
+            handle = convert_to_vcard("impp handle", handle, str)
+            impp_obj.value = f"{type}:{handle}"
+            if pref > 0:
+                impp_obj.params['PREF'] = str(pref)
+
+    @property
     def post_addresses(self) -> dict[str, list[PostAddress]]:
         """
         :returns: dict of type and post address list
@@ -1176,6 +1217,25 @@
             else:
                 raise ValueError("Missing type value for email address field")
 
+        # impp
+        self._delete_vcard_object("IMPP")
+        impp_data = contact_data.get("Impp")
+        if impp_data:
+            if isinstance(impp_data, dict):
+                for type, impp_list in impp_data.items():
+                    if isinstance(impp_list, str):
+                        impp_list = [impp_list]
+                    if isinstance(impp_list, list):
+                        for impp in impp_list:
+                            if impp:
+                                self._add_impp(type, impp)
+                    else:
+                        raise ValueError(
+                            "Got no impp or list of impps for the "
+                            f"impp address type {type}")
+            else:
+                raise ValueError("Missing type value for impp handle field")
+
         # post addresses
         self._delete_vcard_object("ADR")
         address_data = contact_data.get("Address")
@@ -1308,6 +1368,8 @@
                 self.phone_numbers, defaults=["cell", "home"]),
             "Email": helpers.yaml_dicts(
                 self.emails, defaults=["home", "work"]),
+            "Impp": helpers.yaml_dicts(
+                self.impp, defaults=["xmpp", "irc"]),
             "Categories": self.categories,
             "Note": self.notes,
             "Webpage": self.webpages,
@@ -1550,6 +1612,14 @@
                 strings += helpers.convert_to_yaml(
                     type, email_list, 4, -1, False)
 
+        # impp addresses
+        if self.impp:
+            strings.append("IMPP")
+            for type, impp_list in sorted(self.impp.items(),
+                                           key=lambda k: k[0].lower()):
+                strings += helpers.convert_to_yaml(
+                    type, impp_list, 4, -1, False)
+
         # post addresses
         if self.post_addresses:
             strings.append("Address")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard/data/template.yaml 
new/khard-0.22.0/khard/data/template.yaml
--- old/khard-0.21.0/khard/data/template.yaml   2025-01-03 10:45:04.000000000 
+0100
+++ new/khard-0.22.0/khard/data/template.yaml   2026-10-02 08:54:01.000000000 
+0200
@@ -86,6 +86,12 @@
     home : 
     work : 
 
+# impp handles
+# format like phone numbers above
+Impp  :
+    xmpp : 
+    irc  : 
+
 # post addresses
 # allowed types:
 #   vcard 3.0: At least one of dom, intl, home, parcel, postal, pref, work
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard/formatter.py 
new/khard-0.22.0/khard/formatter.py
--- old/khard-0.21.0/khard/formatter.py 2026-02-04 09:01:43.000000000 +0100
+++ new/khard-0.22.0/khard/formatter.py 2026-10-02 09:01:58.000000000 +0200
@@ -69,6 +69,9 @@
             if vcard.emails:
                 return self.format_labeled_field(vcard.emails,
                                                  self._preferred_email)
+        if field == 'impp':
+            if vcard.impp:
+                return self.format_labeled_field(vcard.impp, [])
         if field == 'kind':
             return vcard.kind
         return ""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard/helpers/__init__.py 
new/khard-0.22.0/khard/helpers/__init__.py
--- old/khard-0.21.0/khard/helpers/__init__.py  2026-06-15 15:13:34.000000000 
+0200
+++ new/khard-0.22.0/khard/helpers/__init__.py  2026-10-02 08:54:01.000000000 
+0200
@@ -71,7 +71,7 @@
     """
     sanitize yaml values according to some simple principles:
       1. empty values are none, so ruamel does not print an empty list/str
-      2. list with only one item become this item
+      2. simple single-item lists are flattened
       3. multiline strings use the YAML literal style:
          https://yaml.org/spec/1.2.2/#literal-style
 
@@ -83,9 +83,19 @@
         return None
 
     if isinstance(value, list):
-        # special case for single item lists:
+        # Flatten a single string value, e.g. ["foo"] -> "foo"
         if len(value) == 1 and isinstance(value[0], str):
             return value[0]
+
+        # Flatten a nested single string value, e.g. [["foo"]] -> "foo"
+        if (
+            len(value) == 1
+            and isinstance(value[0], list)
+            and len(value[0]) == 1
+            and isinstance(value[0][0], str)
+        ):
+            return value[0][0]
+
     elif isinstance(value, str):
         if "\n" in value:
             return LiteralScalarString(value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard/khard.py 
new/khard-0.22.0/khard/khard.py
--- old/khard-0.21.0/khard/khard.py     2026-06-15 15:13:21.000000000 +0200
+++ new/khard-0.22.0/khard/khard.py     2026-10-02 08:54:01.000000000 +0200
@@ -11,7 +11,7 @@
 import os
 import sys
 import textwrap
-from typing import cast, Callable, Iterable
+from typing import Any, cast, Callable, Iterable
 
 from . import helpers
 from .address_book import AddressBookCollection, VdirAddressBook
@@ -205,7 +205,7 @@
         for field in table_header:
             if field == 'index':
                 row.append(str(index + 1))
-            elif field in ['name', 'phone', 'email', 'kind']:
+            elif field in ['name', 'phone', 'email', 'kind', 'impp']:
                 row.append(formatter.get_special_field(vcard, field))
             elif field == 'uid':
                 if parsable:
@@ -656,15 +656,14 @@
     """
     message = message_from_string(text, policy=SMTP_POLICY)
 
-    def extract_addresses(header) -> list[Address]:
+    def extract_addresses(header: Any) -> list[Address]:
         if header and isinstance(header, (AddressHeader, Group)):
             return list(header.addresses)
         return []
 
     email_addresses = []
 
-    _all = any([f == "all" for f in fields])
-    if _all:
+    if "all" in fields:
         for _, value in message.items():
             email_addresses.extend(extract_addresses(value))
     else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard/version.py 
new/khard-0.22.0/khard/version.py
--- old/khard-0.21.0/khard/version.py   2026-06-15 15:41:59.000000000 +0200
+++ new/khard-0.22.0/khard/version.py   2026-10-02 09:15:31.000000000 +0200
@@ -28,7 +28,7 @@
 commit_id: COMMIT_ID
 __commit_id__: COMMIT_ID
 
-__version__ = version = '0.21.0'
-__version_tuple__ = version_tuple = (0, 21, 0)
+__version__ = version = '0.22.0'
+__version_tuple__ = version_tuple = (0, 22, 0)
 
-__commit_id__ = commit_id = 'gfc30a66ca'
+__commit_id__ = commit_id = 'ga644aa0f4'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard.egg-info/PKG-INFO 
new/khard-0.22.0/khard.egg-info/PKG-INFO
--- old/khard-0.21.0/khard.egg-info/PKG-INFO    2026-06-15 15:41:59.000000000 
+0200
+++ new/khard-0.22.0/khard.egg-info/PKG-INFO    2026-10-02 09:15:31.000000000 
+0200
@@ -1,9 +1,9 @@
 Metadata-Version: 2.4
 Name: khard
-Version: 0.21.0
+Version: 0.22.0
 Summary: A console address book manager
 Author-email: Eric Scheibler <[email protected]>
-License: GPL
+License-Expression: GPL-3.0-only
 Project-URL: homepage, https://github.com/lucc/khard
 Project-URL: documentation, https://khard.readthedocs.io/en/latest/
 Project-URL: repository, https://github.com/lucc/khard.git
@@ -13,7 +13,6 @@
 Classifier: Environment :: Console
 Classifier: Topic :: Utilities
 Classifier: Topic :: Communications :: Email :: Address Book
-Classifier: License :: OSI Approved :: GNU General Public License (GPL)
 Classifier: Intended Audience :: End Users/Desktop
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python :: 3 :: Only
@@ -27,7 +26,6 @@
 Requires-Dist: sphinx; extra == "doc"
 Requires-Dist: sphinx-argparse; extra == "doc"
 Requires-Dist: sphinx-autoapi; extra == "doc"
-Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
 Provides-Extra: typing
 Requires-Dist: mypy; extra == "typing"
 Dynamic: license-file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard.egg-info/SOURCES.txt 
new/khard-0.22.0/khard.egg-info/SOURCES.txt
--- old/khard-0.21.0/khard.egg-info/SOURCES.txt 2026-06-15 15:41:59.000000000 
+0200
+++ new/khard-0.22.0/khard.egg-info/SOURCES.txt 2026-10-02 09:15:31.000000000 
+0200
@@ -41,6 +41,8 @@
 khard.egg-info/dependency_links.txt
 khard.egg-info/entry_points.txt
 khard.egg-info/requires.txt
+khard.egg-info/scm_file_list.json
+khard.egg-info/scm_version.json
 khard.egg-info/top_level.txt
 khard/data/config.spec
 khard/data/template.yaml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard.egg-info/requires.txt 
new/khard-0.22.0/khard.egg-info/requires.txt
--- old/khard-0.21.0/khard.egg-info/requires.txt        2026-06-15 
15:41:59.000000000 +0200
+++ new/khard-0.22.0/khard.egg-info/requires.txt        2026-10-02 
09:15:31.000000000 +0200
@@ -6,7 +6,6 @@
 sphinx
 sphinx-argparse
 sphinx-autoapi
-sphinx-autodoc-typehints
 
 [typing]
 mypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard.egg-info/scm_file_list.json 
new/khard-0.22.0/khard.egg-info/scm_file_list.json
--- old/khard-0.21.0/khard.egg-info/scm_file_list.json  1970-01-01 
01:00:00.000000000 +0100
+++ new/khard-0.22.0/khard.egg-info/scm_file_list.json  2026-08-17 
22:30:38.000000000 +0200
@@ -0,0 +1,106 @@
+{
+  "files": [
+    ".github/workflows/ci.yml",
+    ".gitignore",
+    ".readthedocs.yml",
+    "CHANGES",
+    "CONTRIBUTING.rst",
+    "LICENSE",
+    "README.md",
+    "doc/Makefile",
+    "doc/source/bench.rst",
+    "doc/source/commandline.rst",
+    "doc/source/conf.py",
+    "doc/source/contributing.rst",
+    "doc/source/davcontroller.rst",
+    "doc/source/examples/khard.conf.example",
+    "doc/source/index.rst",
+    "doc/source/indices.rst",
+    "doc/source/man.rst",
+    "doc/source/man/khard-subcommands.rst",
+    "doc/source/man/khard.conf.rst",
+    "doc/source/man/khard.rst",
+    "doc/source/scripting.rst",
+    "flake.lock",
+    "flake.nix",
+    "khard/__init__.py",
+    "khard/__main__.py",
+    "khard/actions.py",
+    "khard/address_book.py",
+    "khard/cli.py",
+    "khard/config.py",
+    "khard/contacts.py",
+    "khard/data/config.spec",
+    "khard/data/template.yaml",
+    "khard/exceptions.py",
+    "khard/formatter.py",
+    "khard/helpers/__init__.py",
+    "khard/helpers/interactive.py",
+    "khard/helpers/typing.py",
+    "khard/khard.py",
+    "khard/query.py",
+    "misc/davcontroller/davcontroller.py",
+    "misc/sdiff/sdiff_khard_wrapper.sh",
+    "misc/twinkle/scripts/config.py",
+    "misc/twinkle/scripts/incoming_call.py",
+    "misc/twinkle/scripts/incoming_call_ended.py",
+    "misc/twinkle/scripts/incoming_call_failed.py",
+    "misc/twinkle/sounds/incoming_call.ogg",
+    "misc/twinkle/sounds/outgoing_call.ogg",
+    "misc/twinkle/sounds/ringtone_segment.ogg",
+    "misc/zsh/_email-khard",
+    "misc/zsh/_khard",
+    "pyproject.toml",
+    "test/__init__.py",
+    "test/fixture/broken.abook/unparsable.vcf",
+    "test/fixture/discover.conf",
+    "test/fixture/minimal.abook/minimal.vcf",
+    "test/fixture/minimal.conf",
+    "test/fixture/multiple_values.yaml",
+    "test/fixture/nick.abook/joe.vcf",
+    "test/fixture/nick.abook/nickname.vcf",
+    "test/fixture/nick.conf",
+    "test/fixture/single_values.yaml",
+    "test/fixture/test.abook/contact1.vcf",
+    "test/fixture/test.abook/contact2.vcf",
+    "test/fixture/test.abook/text-bday.vcf",
+    "test/fixture/vcards/altid.vcf",
+    "test/fixture/vcards/category.vcf",
+    "test/fixture/vcards/contact1.vcf",
+    "test/fixture/vcards/contact2.vcf",
+    "test/fixture/vcards/individual.vcf",
+    "test/fixture/vcards/issue249.vcf",
+    "test/fixture/vcards/joe.vcf",
+    "test/fixture/vcards/korean-c.vcf",
+    "test/fixture/vcards/korean-j.vcf",
+    "test/fixture/vcards/labels.vcf",
+    "test/fixture/vcards/minimal.vcf",
+    "test/fixture/vcards/nickname.vcf",
+    "test/fixture/vcards/no-nickname.vcf",
+    "test/fixture/vcards/org.vcf",
+    "test/fixture/vcards/photov3.vcf",
+    "test/fixture/vcards/photov4.vcf",
+    "test/fixture/vcards/post.vcf",
+    "test/fixture/vcards/tel-value-uri.vcf",
+    "test/fixture/vcards/text-bday.vcf",
+    "test/fixture/vcards/unparsable.vcf",
+    "test/helpers.py",
+    "test/test_actions.py",
+    "test/test_address_book.py",
+    "test/test_cli.py",
+    "test/test_command_line_interface.py",
+    "test/test_config.py",
+    "test/test_contacts.py",
+    "test/test_editor.py",
+    "test/test_formatter.py",
+    "test/test_helpers.py",
+    "test/test_helpers_interactive.py",
+    "test/test_helpers_typing.py",
+    "test/test_khard.py",
+    "test/test_query.py",
+    "test/test_vcard_wrapper.py",
+    "test/test_yaml.py",
+    "test/test_yaml_editable.py",
+    "todo.txt"
+  ]
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/khard.egg-info/scm_version.json 
new/khard-0.22.0/khard.egg-info/scm_version.json
--- old/khard-0.21.0/khard.egg-info/scm_version.json    1970-01-01 
01:00:00.000000000 +0100
+++ new/khard-0.22.0/khard.egg-info/scm_version.json    2026-08-17 
22:30:38.000000000 +0200
@@ -0,0 +1,8 @@
+{
+  "tag": "0.21.0",
+  "distance": 4,
+  "node": "g214becc0ebee2dbbf21817602f8167cb356f7cd6",
+  "dirty": false,
+  "branch": "main",
+  "node_date": "2026-06-19"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/pyproject.toml 
new/khard-0.22.0/pyproject.toml
--- old/khard-0.21.0/pyproject.toml     2026-06-15 15:13:21.000000000 +0200
+++ new/khard-0.22.0/pyproject.toml     2026-10-02 08:54:01.000000000 +0200
@@ -13,14 +13,13 @@
     "Environment :: Console",
     "Topic :: Utilities",
     "Topic :: Communications :: Email :: Address Book",
-    "License :: OSI Approved :: GNU General Public License (GPL)",
     "Intended Audience :: End Users/Desktop",
     "Operating System :: POSIX",
     "Programming Language :: Python :: 3 :: Only",
 ]
 
 keywords = ["vcard", "console", "addressbook"]
-license = {text = "GPL"}
+license = "GPL-3.0-only"
 
 dependencies = [
     "configobj == 5.*, >= 5.0.6",
@@ -33,7 +32,6 @@
     'sphinx',
     'sphinx-argparse',
     'sphinx-autoapi',
-    'sphinx-autodoc-typehints',
 ]
 typing = [
     "mypy",
@@ -51,7 +49,7 @@
 khard = "khard.khard:main"
 
 [build-system]
-requires = ["setuptools>=61.0", "setuptools_scm[toml]>=6.2"]
+requires = ["setuptools>=77.0", "setuptools_scm[toml]>=6.2"]
 build-backend = "setuptools.build_meta"
 
 [tool.setuptools]
@@ -64,7 +62,7 @@
 packages = ["khard", "test"]
 check_untyped_defs = true
 disallow_untyped_calls = true
-#disallow_untyped_defs = true
+disallow_untyped_defs = true
 warn_redundant_casts = true
 warn_unused_configs = true
 warn_unused_ignores = true
@@ -91,6 +89,7 @@
 module = "test.*"
 check_untyped_defs = false
 disallow_untyped_calls = false
+disallow_untyped_defs = false
 
 [tool.pylint.main]
 py-version = "3.10"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/test/helpers.py 
new/khard-0.22.0/test/helpers.py
--- old/khard-0.21.0/test/helpers.py    2025-05-03 09:04:23.000000000 +0200
+++ new/khard-0.22.0/test/helpers.py    2026-10-02 08:54:01.000000000 +0200
@@ -7,17 +7,19 @@
 import os
 import shutil
 import tempfile
-from typing import Generator
+from collections.abc import Generator
 from unittest import SkipTest, mock
 
 import vobject
 
-from khard import address_book
-from khard import contacts
+from khard import address_book, contacts
 
 
-def vCard(**kwargs):
-    """Create a simple vobject.vCard for tests."""
+def vCard(**kwargs) -> vobject.base.Component:
+    """Create a simple vobject.vCard for tests.
+
+    The default version is 3.0.
+    """
     vcard = vobject.vCard()
     if 'fn' not in kwargs:
         kwargs['fn'] = 'Test vCard'
@@ -28,19 +30,19 @@
     return vcard
 
 
-def TestVCardWrapper(**kwargs):
+def TestVCardWrapper(**kwargs) -> contacts.VCardWrapper:
     """Create a simple VCardWrapper for tests."""
     return contacts.VCardWrapper(vCard(**kwargs))
 
 
-def TestYAMLEditable(**kwargs):
+def TestYAMLEditable(**kwargs) -> contacts.YAMLEditable:
     """Create a simple YAMLEditable for tests."""
     return contacts.YAMLEditable(vCard(**kwargs))
 
 
-def TestContact(**kwargs):
+def TestContact(**kwargs) -> contacts.Contact:
     """Create a simple Contact for tests."""
-    return contacts.Contact(vCard(**kwargs), None, None)
+    return contacts.Contact(vCard(**kwargs), None, None)  # type: ignore
 
 
 def mock_stream(name="stdout"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/test/test_vcard_wrapper.py 
new/khard-0.22.0/test/test_vcard_wrapper.py
--- old/khard-0.21.0/test/test_vcard_wrapper.py 2026-06-15 15:13:21.000000000 
+0200
+++ new/khard-0.22.0/test/test_vcard_wrapper.py 2026-10-02 08:54:01.000000000 
+0200
@@ -311,6 +311,29 @@
         self.assertDictEqual(wrapper.emails, {'home': ['[email protected]'],
                                               'home, pref': ['[email protected]']})
 
+    def test_adding_a_simple_impp(self):
+        wrapper = TestVCardWrapper()
+        wrapper._add_impp('xmpp', '[email protected]')
+        self.assertDictEqual(wrapper.impp, {'xmpp': ['[email protected]']})
+
+    def test_adding_multiple_impp(self):
+        wrapper = TestVCardWrapper()
+        wrapper._add_impp('matrix', '@foo:example.com')
+        wrapper._add_impp('xmpp', '[email protected]')
+        wrapper._add_impp('xmpp', '[email protected]')
+        self.assertDictEqual(
+            wrapper.impp,
+            # The lists are sorted!
+            {'matrix': ['@foo:example.com'], 'xmpp': ['[email protected]', 
'[email protected]']})
+
+    def test_adding_preferred_impp(self):
+        wrapper = TestVCardWrapper()
+        wrapper._add_impp('xmpp', '[email protected]')
+        wrapper._add_impp('pref,xmpp', '[email protected]')
+        self.assertDictEqual(
+            wrapper.impp, {'xmpp': ['[email protected]'],
+                                    'xmpp, pref=1': ['[email protected]']})
+
     def test_adding_a_simple_address(self):
         wrapper = TestVCardWrapper()
         components = ('box', 'extended', 'street', 'code', 'city', 'region',
@@ -389,6 +412,24 @@
     def test_list_of_strings_as_country(self):
         self._test_list_of_strings_as("country")
 
+    def test_pref_formatting_for_v3_vcards(self):
+        vcard = TestVCardWrapper(version="3.0")
+        vcard.add_email("pref", "[email protected]")
+        line = self._find_line("EMAIL", vcard.vcard.serialize())
+        self.assertEqual(line, ["EMAIL;TYPE=pref:[email protected]"])
+
+    def test_pref_formatting_for_v4_vcards(self):
+        vcard = TestVCardWrapper(version="4.0")
+        vcard.add_email("pref", "[email protected]")
+        line = self._find_line("EMAIL", vcard.vcard.serialize())
+        self.assertEqual(line, ["EMAIL;PREF=1:[email protected]"])
+
+    @staticmethod
+    def _find_line(pattern: str, lines: str | list[str]) -> list[str]:
+        if isinstance(lines, str):
+            lines = lines.splitlines()
+        return [line for line in lines if pattern in line]
+
 
 class OtherProperties(unittest.TestCase):
 
@@ -578,7 +619,7 @@
 
     LIST_PROPERTIES = ["categories", "titles", "webpages", "organisations",
                        "notes", "roles", "nicknames"]
-    DICT_PROPERTIES = ["post_addresses", "emails", "phone_numbers"]
+    DICT_PROPERTIES = ["post_addresses", "emails", "phone_numbers", "impp"]
     BASE_PROPERTIES = ["formatted_name", "kind", "version"]
 
     def test_for_non_existing_attributes(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/test/test_yaml.py 
new/khard-0.22.0/test/test_yaml.py
--- old/khard-0.21.0/test/test_yaml.py  2025-01-03 10:45:05.000000000 +0100
+++ new/khard-0.22.0/test/test_yaml.py  2026-10-02 08:54:01.000000000 +0200
@@ -149,6 +149,14 @@
         card.update(data)
         self.assertListEqual(card.organisations, [org])
 
+    def test_org_simple_to_yaml(self):
+        card = create_test_card()
+        card._add_organisation("Foo")
+
+        data = YAML().load(card.to_yaml())
+
+        self.assertEqual(data["Organisation"], "Foo")
+
     def test_update_categories_simple(self):
         card = create_test_card()
         data = {"Categories": "foo"}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/khard-0.21.0/test/test_yaml_editable.py 
new/khard-0.22.0/test/test_yaml_editable.py
--- old/khard-0.21.0/test/test_yaml_editable.py 2024-12-20 18:36:07.000000000 
+0100
+++ new/khard-0.22.0/test/test_yaml_editable.py 2026-10-02 08:54:01.000000000 
+0200
@@ -47,6 +47,14 @@
         self.assertIn("home1", yaml_dump)
         self.assertIn("home2", yaml_dump)
 
+    def test_dumping_multiple_impp_handles_to_yaml(self):
+        yaml_editable = TestYAMLEditable()
+        yaml_editable._add_impp("xmpp", "[email protected]")
+        yaml_editable._add_impp("xmpp", "[email protected]")
+        yaml_dump = yaml_editable.to_yaml()
+        self.assertIn("[email protected]", yaml_dump)
+        self.assertIn("[email protected]", yaml_dump)
+
     def test_empty_kind_is_included_in_yaml_format(self):
         contact = TestYAMLEditable()
         yaml = contact.to_yaml()

Reply via email to