Repository: xmlsec/python-xmlsec (language: python)

Issue:
[self-tests] test_xmlsec_constants_stub fails on master
`test_xmlsec_constants_stub` fails on `master` with:
```
_____________________________ test_xmlsec_constants_stub _____________________________

request = <FixtureRequest for <Function test_xmlsec_constants_stub>>

    def test_xmlsec_constants_stub(request):
        """
        Generate the stub file for :mod:`xmlsec.constants` from existing code.
    
        Compare it against the existing stub :file:`xmlsec/constants.pyi`.
        """
        rootdir = Path(str(request.config.rootdir))
        stub = rootdir / 'src' / 'xmlsec' / 'constants.pyi'
        mode = black.FileMode(target_versions=[black.TargetVersion.PY38], line_length=130, is_pyi=True, string_normalization=False)
        formatted = black.format_file_contents(gen_constants_stub(), fast=False, mode=mode)
>       assert formatted == stub.read_text()
E       assert "import sys\n...-more/xptr'\n" == "import sys\n...-more/xptr'\n"
E           import sys
E         - from typing import NamedTuple, Optional
E         ?                              ----------
E         + from typing import NamedTuple
E           
E           if sys.version_info >= (3, 8):
E               from typing import Final...
E         
E         ...Full output truncated (153 lines hidden), use '-vv' to show

tests/test_type_stubs.py:72: AssertionError
```

note: this test is skipped on upstream's CI due to missing optional `black`.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/sdist.yml b/.github/workflows/sdist.yml
index 6082fcd6..28fe79fe 100644
--- a/.github/workflows/sdist.yml
+++ b/.github/workflows/sdist.yml
@@ -20,6 +20,7 @@ jobs:
           PYXMLSEC_STATIC_DEPS: true
         run: |
           pip install --upgrade -r requirements-test.txt
+          pip install black  # for stub generation tests
           pip install dist/xmlsec-$(python setup.py --version).tar.gz
       - name: Run tests
         run: |
diff --git a/pyproject.toml b/pyproject.toml
index 9262cbf5..2bfc3d21 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [tool.black]
 line_length = 130
 skip-string-normalization = true
-target_version = ['py38']
+target_version = ['py39']
 include = '\.pyi?$'
 exclude = '''
 
diff --git a/setup.cfg b/setup.cfg
index c61fd65b..88d815b0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,4 +16,7 @@ all_files  = 1
 upload_dir = doc/build/html
 
 [flake8]
+per-file-ignores =
+  *.pyi: E301, E302, E305, E501, E701, F401, F822
+exclude = .venv*,.git,*_pb2.pyi,build,dist,libs,.eggs,.direnv*
 max-line-length = 130
diff --git a/src/xmlsec/__init__.pyi b/src/xmlsec/__init__.pyi
index 93ff8658..90881894 100644
--- a/src/xmlsec/__init__.pyi
+++ b/src/xmlsec/__init__.pyi
@@ -1,27 +1,13 @@
-import sys
-from typing import (
-    Any, AnyStr, Callable, IO, Iterable, Optional, Type, TypeVar, Union,
-    overload)
+from collections.abc import Callable, Iterable
+from typing import Any, AnyStr, IO, TypeVar, overload
+from _typeshed import GenericPath, Self, StrOrBytesPath
 
 from lxml.etree import _Element
 
-from xmlsec import constants, template, tree
+from xmlsec import constants as constants, tree as tree
 from xmlsec.constants import __KeyData as KeyData, __Transform as Transform
 
-if sys.version_info >= (3, 6):
-    from os import PathLike
-    from pathlib import PurePath
-
-    _Path = Union[str, bytes, PurePath, PathLike[str], PathLike[bytes]]
-elif sys.version_info >= (3, 4):
-    from pathlib import PurePath
-
-    _Path = Union[str, bytes, PurePath]
-else:
-    _Path = Union[str, bytes]
-
 _E = TypeVar('_E', bound=_Element)
-_K = TypeVar('_K', bound=Key)
 
 def enable_debug_trace(enabled: bool = ...) -> None: ...
 def init() -> None: ...
@@ -29,10 +15,10 @@ def shutdown() -> None: ...
 def cleanup_callbacks() -> None: ...
 def register_default_callbacks() -> None: ...
 def register_callbacks(
-        input_match_callback: Callable[[bytes], bool],
-        input_open_callback: Callable[[bytes], Any],
-        input_read_callback: Callable[[Any, memoryview], int],
-        input_close_callback: Callable[[Any], None],
+    input_match_callback: Callable[[bytes], bool],
+    input_open_callback: Callable[[bytes], Any],
+    input_read_callback: Callable[[Any, memoryview], int],
+    input_close_callback: Callable[[Any], None],
 ) -> None: ...
 @overload
 def base64_default_line_size() -> int: ...
@@ -40,8 +26,8 @@ def base64_default_line_size() -> int: ...
 def base64_default_line_size(size: int) -> None: ...
 
 class EncryptionContext:
-    key: Optional[Key]
-    def __init__(self, manager: Optional[KeysManager] = None) -> None: ...
+    key: Key | None
+    def __init__(self, manager: KeysManager | None = ...) -> None: ...
     def decrypt(self, node: _Element) -> _Element: ...
     def encrypt_binary(self, template: _E, data: bytes) -> _E: ...
     def encrypt_uri(self, template: _E, uri: str) -> _E: ...
@@ -54,30 +40,30 @@ class InternalError(Error): ...
 class Key:
     name: str
     @classmethod
-    def from_binary_data(cls: Type[_K], klass: KeyData, data: AnyStr) -> _K: ...
+    def from_binary_data(cls: type[Self], klass: KeyData, data: AnyStr) -> Self: ...
     @classmethod
-    def from_binary_file(cls: Type[_K], klass: KeyData, filename: _Path) -> _K: ...
+    def from_binary_file(cls: type[Self], klass: KeyData, filename: StrOrBytesPath) -> Self: ...
     @classmethod
-    def from_file(cls: Type[_K], file: Union[_Path, IO[AnyStr]], format: int, password: Optional[str] = ...) -> _K: ...
+    def from_file(cls: type[Self], file: GenericPath | IO[AnyStr], format: int, password: str | None = ...) -> Self: ...
     @classmethod
-    def from_memory(cls: Type[_K], data: AnyStr, format: int, password: Optional[str] = ...) -> _K: ...
+    def from_memory(cls: type[Self], data: AnyStr, format: int, password: str | None = ...) -> Self: ...
     @classmethod
-    def generate(cls: Type[_K], klass: KeyData, size: int, type: int) -> _K: ...
-    def load_cert_from_file(self, file: Union[_Path, IO[AnyStr]], format: int) -> None: ...
+    def generate(cls: type[Self], klass: KeyData, size: int, type: int) -> Self: ...
+    def load_cert_from_file(self, file: GenericPath | IO[AnyStr], format: int) -> None: ...
     def load_cert_from_memory(self, data: AnyStr, format: int) -> None: ...
-    def __copy__(self: _K) -> _K: ...
-    def __deepcopy__(self: _K) -> _K: ...
+    def __copy__(self: Self) -> Self: ...
+    def __deepcopy__(self: Self) -> Self: ...
 
 class KeysManager:
     def add_key(self, key: Key) -> None: ...
-    def load_cert(self, filename: _Path, format: int, type: int) -> None: ...
+    def load_cert(self, filename: StrOrBytesPath, format: int, type: int) -> None: ...
     def load_cert_from_memory(self, data: AnyStr, format: int, type: int) -> None: ...
 
 class SignatureContext:
-    key: Optional[Key]
+    key: Key | None
     def enable_reference_transform(self, transform: Transform) -> None: ...
     def enable_signature_transform(self, transform: Transform) -> None: ...
-    def register_id(self, node: _Element, id_attr: str = "ID", id_ns: Optional[str] = None) -> None: ...
+    def register_id(self, node: _Element, id_attr: str = ..., id_ns: str | None = ...) -> None: ...
     def set_enabled_key_data(self, keydata_list: Iterable[KeyData]) -> None: ...
     def sign(self, node: _Element) -> None: ...
     def sign_binary(self, bytes: bytes, transform: Transform) -> bytes: ...
diff --git a/src/xmlsec/constants.pyi b/src/xmlsec/constants.pyi
index 97e00544..3430a027 100644
--- a/src/xmlsec/constants.pyi
+++ b/src/xmlsec/constants.pyi
@@ -1,5 +1,5 @@
 import sys
-from typing import NamedTuple, Optional
+from typing import NamedTuple
 
 if sys.version_info >= (3, 8):
     from typing import Final
@@ -7,147 +7,148 @@ else:
     from typing_extensions import Final
 
 class __KeyData(NamedTuple):  # __KeyData type
+    href: str
+    name: str
+
+class __KeyDataNoHref(NamedTuple):  # __KeyData type
+    href: None
     name: str
-    href: Optional[str]
 
 class __Transform(NamedTuple):  # __Transform type
+    href: str
+    name: str
+    usage: int
+
+class __TransformNoHref(NamedTuple):  # __Transform type
+    href: None
     name: str
-    href: Optional[str]
     usage: int
 
-DSigNs: Final = 'http://www.w3.org/2000/09/xmldsig#'
-EncNs: Final = 'http://www.w3.org/2001/04/xmlenc#'
-KeyDataAes: Final = __KeyData('aes', 'http://www.aleksey.com/xmlsec/2002#AESKeyValue')
-KeyDataDes: Final = __KeyData('des', 'http://www.aleksey.com/xmlsec/2002#DESKeyValue')
-KeyDataDsa: Final = __KeyData('dsa', 'http://www.w3.org/2000/09/xmldsig#DSAKeyValue')
-KeyDataEcdsa: Final = __KeyData('ecdsa', 'http://scap.nist.gov/specifications/tmsad/#resource-1.0')
-KeyDataEncryptedKey: Final = __KeyData('enc-key', 'http://www.w3.org/2001/04/xmlenc#EncryptedKey')
-KeyDataFormatBinary: Final = 1
-KeyDataFormatCertDer: Final = 8
-KeyDataFormatCertPem: Final = 7
-KeyDataFormatDer: Final = 3
-KeyDataFormatPem: Final = 2
-KeyDataFormatPkcs12: Final = 6
-KeyDataFormatPkcs8Der: Final = 5
-KeyDataFormatPkcs8Pem: Final = 4
-KeyDataFormatUnknown: Final = 0
-KeyDataHmac: Final = __KeyData('hmac', 'http://www.aleksey.com/xmlsec/2002#HMACKeyValue')
-KeyDataName: Final = __KeyData('key-name', None)
-KeyDataRawX509Cert: Final = __KeyData('raw-x509-cert', 'http://www.w3.org/2000/09/xmldsig#rawX509Certificate')
-KeyDataRetrievalMethod: Final = __KeyData('retrieval-method', None)
-KeyDataRsa: Final = __KeyData('rsa', 'http://www.w3.org/2000/09/xmldsig#RSAKeyValue')
-KeyDataTypeAny: Final = 65535
-KeyDataTypeNone: Final = 0
-KeyDataTypePermanent: Final = 16
-KeyDataTypePrivate: Final = 2
-KeyDataTypePublic: Final = 1
-KeyDataTypeSession: Final = 8
-KeyDataTypeSymmetric: Final = 4
-KeyDataTypeTrusted: Final = 256
-KeyDataTypeUnknown: Final = 0
-KeyDataValue: Final = __KeyData('key-value', None)
-KeyDataX509: Final = __KeyData('x509', 'http://www.w3.org/2000/09/xmldsig#X509Data')
-NodeCanonicalizationMethod: Final = 'CanonicalizationMethod'
-NodeCipherData: Final = 'CipherData'
-NodeCipherReference: Final = 'CipherReference'
-NodeCipherValue: Final = 'CipherValue'
-NodeDataReference: Final = 'DataReference'
-NodeDigestMethod: Final = 'DigestMethod'
-NodeDigestValue: Final = 'DigestValue'
-NodeEncryptedData: Final = 'EncryptedData'
-NodeEncryptedKey: Final = 'EncryptedKey'
-NodeEncryptionMethod: Final = 'EncryptionMethod'
-NodeEncryptionProperties: Final = 'EncryptionProperties'
-NodeEncryptionProperty: Final = 'EncryptionProperty'
-NodeKeyInfo: Final = 'KeyInfo'
-NodeKeyName: Final = 'KeyName'
-NodeKeyReference: Final = 'KeyReference'
-NodeKeyValue: Final = 'KeyValue'
-NodeManifest: Final = 'Manifest'
-NodeObject: Final = 'Object'
-NodeReference: Final = 'Reference'
-NodeReferenceList: Final = 'ReferenceList'
-NodeSignature: Final = 'Signature'
-NodeSignatureMethod: Final = 'SignatureMethod'
-NodeSignatureProperties: Final = 'SignatureProperties'
-NodeSignatureValue: Final = 'SignatureValue'
-NodeSignedInfo: Final = 'SignedInfo'
-NodeX509Data: Final = 'X509Data'
-Ns: Final = 'http://www.aleksey.com/xmlsec/2002'
-NsExcC14N: Final = 'http://www.w3.org/2001/10/xml-exc-c14n#'
-NsExcC14NWithComments: Final = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments'
-Soap11Ns: Final = 'http://schemas.xmlsoap.org/soap/envelope/'
-Soap12Ns: Final = 'http://www.w3.org/2002/06/soap-envelope'
-TransformAes128Cbc: Final = __Transform('aes128-cbc', 'http://www.w3.org/2001/04/xmlenc#aes128-cbc', 16)
-TransformAes128Gcm: Final = __Transform('aes128-gcm', 'http://www.w3.org/2009/xmlenc11#aes128-gcm', 16)
-TransformAes192Cbc: Final = __Transform('aes192-cbc', 'http://www.w3.org/2001/04/xmlenc#aes192-cbc', 16)
-TransformAes192Gcm: Final = __Transform('aes192-gcm', 'http://www.w3.org/2009/xmlenc11#aes192-gcm', 16)
-TransformAes256Cbc: Final = __Transform('aes256-cbc', 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', 16)
-TransformAes256Gcm: Final = __Transform('aes256-gcm', 'http://www.w3.org/2009/xmlenc11#aes256-gcm', 16)
-TransformDes3Cbc: Final = __Transform('tripledes-cbc', 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc', 16)
-TransformDsaSha1: Final = __Transform('dsa-sha1', 'http://www.w3.org/2000/09/xmldsig#dsa-sha1', 8)
-TransformEcdsaSha1: Final = __Transform('ecdsa-sha1', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1', 8)
-TransformEcdsaSha224: Final = __Transform('ecdsa-sha224', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224', 8)
-TransformEcdsaSha256: Final = __Transform('ecdsa-sha256', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256', 8)
-TransformEcdsaSha384: Final = __Transform('ecdsa-sha384', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384', 8)
-TransformEcdsaSha512: Final = __Transform('ecdsa-sha512', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512', 8)
-TransformEnveloped: Final = __Transform('enveloped-signature', 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', 1)
-TransformExclC14N: Final = __Transform('exc-c14n', 'http://www.w3.org/2001/10/xml-exc-c14n#', 3)
-TransformExclC14NWithComments: Final = __Transform(
-    'exc-c14n-with-comments', 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments', 3
-)
-TransformHmacMd5: Final = __Transform('hmac-md5', 'http://www.w3.org/2001/04/xmldsig-more#hmac-md5', 8)
-TransformHmacRipemd160: Final = __Transform('hmac-ripemd160', 'http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160', 8)
-TransformHmacSha1: Final = __Transform('hmac-sha1', 'http://www.w3.org/2000/09/xmldsig#hmac-sha1', 8)
-TransformHmacSha224: Final = __Transform('hmac-sha224', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224', 8)
-TransformHmacSha256: Final = __Transform('hmac-sha256', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256', 8)
-TransformHmacSha384: Final = __Transform('hmac-sha384', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha384', 8)
-TransformHmacSha512: Final = __Transform('hmac-sha512', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha512', 8)
-TransformInclC14N: Final = __Transform('c14n', 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315', 3)
-TransformInclC14N11: Final = __Transform('c14n11', 'http://www.w3.org/2006/12/xml-c14n11', 3)
-TransformInclC14N11WithComments: Final = __Transform(
-    'c14n11-with-comments', 'http://www.w3.org/2006/12/xml-c14n11#WithComments', 3
-)
-TransformInclC14NWithComments: Final = __Transform(
-    'c14n-with-comments',
-    'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments',
-    3,
-)
-TransformKWAes128: Final = __Transform('kw-aes128', 'http://www.w3.org/2001/04/xmlenc#kw-aes128', 16)
-TransformKWAes192: Final = __Transform('kw-aes192', 'http://www.w3.org/2001/04/xmlenc#kw-aes192', 16)
-TransformKWAes256: Final = __Transform('kw-aes256', 'http://www.w3.org/2001/04/xmlenc#kw-aes256', 16)
-TransformKWDes3: Final = __Transform('kw-tripledes', 'http://www.w3.org/2001/04/xmlenc#kw-tripledes', 16)
-TransformMd5: Final = __Transform('md5', 'http://www.w3.org/2001/04/xmldsig-more#md5', 4)
-TransformRemoveXmlTagsC14N: Final = __Transform('remove-xml-tags-transform', None, 3)
-TransformRipemd160: Final = __Transform('ripemd160', 'http://www.w3.org/2001/04/xmlenc#ripemd160', 4)
-TransformRsaMd5: Final = __Transform('rsa-md5', 'http://www.w3.org/2001/04/xmldsig-more#rsa-md5', 8)
-TransformRsaOaep: Final = __Transform('rsa-oaep-mgf1p', 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', 16)
-TransformRsaPkcs1: Final = __Transform('rsa-1_5', 'http://www.w3.org/2001/04/xmlenc#rsa-1_5', 16)
-TransformRsaRipemd160: Final = __Transform('rsa-ripemd160', 'http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160', 8)
-TransformRsaSha1: Final = __Transform('rsa-sha1', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 8)
-TransformRsaSha224: Final = __Transform('rsa-sha224', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224', 8)
-TransformRsaSha256: Final = __Transform('rsa-sha256', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', 8)
-TransformRsaSha384: Final = __Transform('rsa-sha384', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384', 8)
-TransformRsaSha512: Final = __Transform('rsa-sha512', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512', 8)
-TransformSha1: Final = __Transform('sha1', 'http://www.w3.org/2000/09/xmldsig#sha1', 4)
-TransformSha224: Final = __Transform('sha224', 'http://www.w3.org/2001/04/xmldsig-more#sha224', 4)
-TransformSha256: Final = __Transform('sha256', 'http://www.w3.org/2001/04/xmlenc#sha256', 4)
-TransformSha384: Final = __Transform('sha384', 'http://www.w3.org/2001/04/xmldsig-more#sha384', 4)
-TransformSha512: Final = __Transform('sha512', 'http://www.w3.org/2001/04/xmlenc#sha512', 4)
-TransformUsageAny: Final = 65535
-TransformUsageC14NMethod: Final = 2
-TransformUsageDSigTransform: Final = 1
-TransformUsageDigestMethod: Final = 4
-TransformUsageEncryptionMethod: Final = 16
-TransformUsageSignatureMethod: Final = 8
-TransformUsageUnknown: Final = 0
-TransformVisa3DHack: Final = __Transform('Visa3DHackTransform', None, 1)
-TransformXPath: Final = __Transform('xpath', 'http://www.w3.org/TR/1999/REC-xpath-19991116', 1)
-TransformXPath2: Final = __Transform('xpath2', 'http://www.w3.org/2002/06/xmldsig-filter2', 1)
-TransformXPointer: Final = __Transform('xpointer', 'http://www.w3.org/2001/04/xmldsig-more/xptr', 1)
-TransformXslt: Final = __Transform('xslt', 'http://www.w3.org/TR/1999/REC-xslt-19991116', 1)
-TypeEncContent: Final = 'http://www.w3.org/2001/04/xmlenc#Content'
-TypeEncElement: Final = 'http://www.w3.org/2001/04/xmlenc#Element'
-XPath2Ns: Final = 'http://www.w3.org/2002/06/xmldsig-filter2'
-XPathNs: Final = 'http://www.w3.org/TR/1999/REC-xpath-19991116'
-XPointerNs: Final = 'http://www.w3.org/2001/04/xmldsig-more/xptr'
+DSigNs: Final[str]
+EncNs: Final[str]
+KeyDataAes: Final[__KeyData]
+KeyDataDes: Final[__KeyData]
+KeyDataDsa: Final[__KeyData]
+KeyDataEcdsa: Final[__KeyData]
+KeyDataEncryptedKey: Final[__KeyData]
+KeyDataFormatBinary: Final[int]
+KeyDataFormatCertDer: Final[int]
+KeyDataFormatCertPem: Final[int]
+KeyDataFormatDer: Final[int]
+KeyDataFormatPem: Final[int]
+KeyDataFormatPkcs12: Final[int]
+KeyDataFormatPkcs8Der: Final[int]
+KeyDataFormatPkcs8Pem: Final[int]
+KeyDataFormatUnknown: Final[int]
+KeyDataHmac: Final[__KeyData]
+KeyDataName: Final[__KeyDataNoHref]
+KeyDataRawX509Cert: Final[__KeyData]
+KeyDataRetrievalMethod: Final[__KeyDataNoHref]
+KeyDataRsa: Final[__KeyData]
+KeyDataTypeAny: Final[int]
+KeyDataTypeNone: Final[int]
+KeyDataTypePermanent: Final[int]
+KeyDataTypePrivate: Final[int]
+KeyDataTypePublic: Final[int]
+KeyDataTypeSession: Final[int]
+KeyDataTypeSymmetric: Final[int]
+KeyDataTypeTrusted: Final[int]
+KeyDataTypeUnknown: Final[int]
+KeyDataValue: Final[__KeyDataNoHref]
+KeyDataX509: Final[__KeyData]
+NodeCanonicalizationMethod: Final[str]
+NodeCipherData: Final[str]
+NodeCipherReference: Final[str]
+NodeCipherValue: Final[str]
+NodeDataReference: Final[str]
+NodeDigestMethod: Final[str]
+NodeDigestValue: Final[str]
+NodeEncryptedData: Final[str]
+NodeEncryptedKey: Final[str]
+NodeEncryptionMethod: Final[str]
+NodeEncryptionProperties: Final[str]
+NodeEncryptionProperty: Final[str]
+NodeKeyInfo: Final[str]
+NodeKeyName: Final[str]
+NodeKeyReference: Final[str]
+NodeKeyValue: Final[str]
+NodeManifest: Final[str]
+NodeObject: Final[str]
+NodeReference: Final[str]
+NodeReferenceList: Final[str]
+NodeSignature: Final[str]
+NodeSignatureMethod: Final[str]
+NodeSignatureProperties: Final[str]
+NodeSignatureValue: Final[str]
+NodeSignedInfo: Final[str]
+NodeX509Data: Final[str]
+Ns: Final[str]
+NsExcC14N: Final[str]
+NsExcC14NWithComments: Final[str]
+Soap11Ns: Final[str]
+Soap12Ns: Final[str]
+TransformAes128Cbc: Final[__Transform]
+TransformAes128Gcm: Final[__Transform]
+TransformAes192Cbc: Final[__Transform]
+TransformAes192Gcm: Final[__Transform]
+TransformAes256Cbc: Final[__Transform]
+TransformAes256Gcm: Final[__Transform]
+TransformDes3Cbc: Final[__Transform]
+TransformDsaSha1: Final[__Transform]
+TransformEcdsaSha1: Final[__Transform]
+TransformEcdsaSha224: Final[__Transform]
+TransformEcdsaSha256: Final[__Transform]
+TransformEcdsaSha384: Final[__Transform]
+TransformEcdsaSha512: Final[__Transform]
+TransformEnveloped: Final[__Transform]
+TransformExclC14N: Final[__Transform]
+TransformExclC14NWithComments: Final[__Transform]
+TransformHmacMd5: Final[__Transform]
+TransformHmacRipemd160: Final[__Transform]
+TransformHmacSha1: Final[__Transform]
+TransformHmacSha224: Final[__Transform]
+TransformHmacSha256: Final[__Transform]
+TransformHmacSha384: Final[__Transform]
+TransformHmacSha512: Final[__Transform]
+TransformInclC14N: Final[__Transform]
+TransformInclC14N11: Final[__Transform]
+TransformInclC14N11WithComments: Final[__Transform]
+TransformInclC14NWithComments: Final[__Transform]
+TransformKWAes128: Final[__Transform]
+TransformKWAes192: Final[__Transform]
+TransformKWAes256: Final[__Transform]
+TransformKWDes3: Final[__Transform]
+TransformMd5: Final[__Transform]
+TransformRemoveXmlTagsC14N: Final[__TransformNoHref]
+TransformRipemd160: Final[__Transform]
+TransformRsaMd5: Final[__Transform]
+TransformRsaOaep: Final[__Transform]
+TransformRsaPkcs1: Final[__Transform]
+TransformRsaRipemd160: Final[__Transform]
+TransformRsaSha1: Final[__Transform]
+TransformRsaSha224: Final[__Transform]
+TransformRsaSha256: Final[__Transform]
+TransformRsaSha384: Final[__Transform]
+TransformRsaSha512: Final[__Transform]
+TransformSha1: Final[__Transform]
+TransformSha224: Final[__Transform]
+TransformSha256: Final[__Transform]
+TransformSha384: Final[__Transform]
+TransformSha512: Final[__Transform]
+TransformUsageAny: Final[int]
+TransformUsageC14NMethod: Final[int]
+TransformUsageDSigTransform: Final[int]
+TransformUsageDigestMethod: Final[int]
+TransformUsageEncryptionMethod: Final[int]
+TransformUsageSignatureMethod: Final[int]
+TransformUsageUnknown: Final[int]
+TransformVisa3DHack: Final[__TransformNoHref]
+TransformXPath: Final[__Transform]
+TransformXPath2: Final[__Transform]
+TransformXPointer: Final[__Transform]
+TransformXslt: Final[__Transform]
+TypeEncContent: Final[str]
+TypeEncElement: Final[str]
+XPath2Ns: Final[str]
+XPathNs: Final[str]
+XPointerNs: Final[str]
diff --git a/src/xmlsec/template.pyi b/src/xmlsec/template.pyi
index 162fe25d..a5199fc8 100644
--- a/src/xmlsec/template.pyi
+++ b/src/xmlsec/template.pyi
@@ -1,16 +1,17 @@
-from typing import Any, Optional, Sequence, Union
+from collections.abc import Sequence
+from typing import Any
 
 from lxml.etree import _Element
 
 from xmlsec.constants import __Transform as Transform
 
 def add_encrypted_key(
-    node: _Element, method: Transform, id: Optional[str] = None, type: Optional[str] = None, recipient: Optional[str] = None
+    node: _Element, method: Transform, id: str | None = ..., type: str | None = ..., recipient: str | None = ...
 ) -> _Element: ...
-def add_key_name(node: _Element, name: Optional[str] = ...) -> _Element: ...
+def add_key_name(node: _Element, name: str | None = ...) -> _Element: ...
 def add_key_value(node: _Element) -> _Element: ...
 def add_reference(
-    node: _Element, digest_method: Transform, id: Optional[str] = ..., uri: Optional[str] = ..., type: Optional[str] = ...
+    node: _Element, digest_method: Transform, id: str | None = ..., uri: str | None = ..., type: str | None = ...
 ) -> _Element: ...
 def add_transform(node: _Element, transform: Transform) -> Any: ...
 def add_x509_data(node: _Element) -> _Element: ...
@@ -18,20 +19,20 @@ def create(node: _Element, c14n_method: Transform, sign_method: Transform) -> _E
 def encrypted_data_create(
     node: _Element,
     method: Transform,
-    id: Optional[str] = ...,
-    type: Optional[str] = ...,
-    mime_type: Optional[str] = ...,
-    encoding: Optional[str] = ...,
-    ns: Optional[str] = ...,
+    id: str | None = ...,
+    type: str | None = ...,
+    mime_type: str | None = ...,
+    encoding: str | None = ...,
+    ns: str | None = ...,
 ) -> _Element: ...
 def encrypted_data_ensure_cipher_value(node: _Element) -> _Element: ...
-def encrypted_data_ensure_key_info(node: _Element, id: Optional[str] = ..., ns: Optional[str] = ...) -> _Element: ...
-def ensure_key_info(node: _Element, id: Optional[str] = ...) -> _Element: ...
-def transform_add_c14n_inclusive_namespaces(node: _Element, prefixes: Union[str, Sequence[str]]) -> None: ...
+def encrypted_data_ensure_key_info(node: _Element, id: str | None = ..., ns: str | None = ...) -> _Element: ...
+def ensure_key_info(node: _Element, id: str | None = ...) -> _Element: ...
+def transform_add_c14n_inclusive_namespaces(node: _Element, prefixes: str | Sequence[str]) -> None: ...
 def x509_data_add_certificate(node: _Element) -> _Element: ...
 def x509_data_add_crl(node: _Element) -> _Element: ...
 def x509_data_add_issuer_serial(node: _Element) -> _Element: ...
 def x509_data_add_ski(node: _Element) -> _Element: ...
 def x509_data_add_subject_name(node: _Element) -> _Element: ...
-def x509_issuer_serial_add_issuer_name(node: _Element, name: Optional[str] = ...) -> _Element: ...
-def x509_issuer_serial_add_serial_number(node: _Element, serial: Optional[str] = ...) -> _Element: ...
+def x509_issuer_serial_add_issuer_name(node: _Element, name: str | None = ...) -> _Element: ...
+def x509_issuer_serial_add_serial_number(node: _Element, serial: str | None = ...) -> _Element: ...
diff --git a/src/xmlsec/tree.pyi b/src/xmlsec/tree.pyi
index 6447fd08..9f96e447 100644
--- a/src/xmlsec/tree.pyi
+++ b/src/xmlsec/tree.pyi
@@ -1,17 +1,18 @@
-from typing import Optional, overload, Sequence
+from collections.abc import Sequence
+from typing import overload
 
 from lxml.etree import _Element
 
 def add_ids(node: _Element, ids: Sequence[str]) -> None: ...
 @overload
-def find_child(parent: _Element, name: str) -> Optional[_Element]: ...
+def find_child(parent: _Element, name: str) -> _Element | None: ...
 @overload
-def find_child(parent: _Element, name: str, namespace: str = ...) -> Optional[_Element]: ...
+def find_child(parent: _Element, name: str, namespace: str = ...) -> _Element | None: ...
 @overload
-def find_node(node: _Element, name: str) -> Optional[_Element]: ...
+def find_node(node: _Element, name: str) -> _Element | None: ...
 @overload
-def find_node(node: _Element, name: str, namespace: str = ...) -> Optional[_Element]: ...
+def find_node(node: _Element, name: str, namespace: str = ...) -> _Element | None: ...
 @overload
-def find_parent(node: _Element, name: str) -> Optional[_Element]: ...
+def find_parent(node: _Element, name: str) -> _Element | None: ...
 @overload
-def find_parent(node: _Element, name: str, namespace: str = ...) -> Optional[_Element]: ...
+def find_parent(node: _Element, name: str, namespace: str = ...) -> _Element | None: ...


Test patch:
diff --git a/tests/test_type_stubs.py b/tests/test_type_stubs.py
index 3b1c3757..9ed8f1e2 100644
--- a/tests/test_type_stubs.py
+++ b/tests/test_type_stubs.py
@@ -1,7 +1,6 @@
 """Test type stubs for correctness where possible."""
 
 import os
-import sys
 
 import pytest
 
@@ -10,12 +9,6 @@
 black = pytest.importorskip('black')
 
 
-if sys.version_info >= (3, 4):
-    from pathlib import Path
-else:
-    from _pytest.pathlib import Path
-
-
 constants_stub_header = """
 import sys
 from typing import NamedTuple
@@ -25,17 +18,23 @@
 else:
     from typing_extensions import Final
 
-
 class __KeyData(NamedTuple):  # __KeyData type
     href: str
     name: str
 
+class __KeyDataNoHref(NamedTuple):  # __KeyData type
+    href: None
+    name: str
 
 class __Transform(NamedTuple):  # __Transform type
     href: str
     name: str
     usage: int
 
+class __TransformNoHref(NamedTuple):  # __Transform type
+    href: None
+    name: str
+    usage: int
 
 """
 
@@ -51,7 +50,10 @@ def gen_constants_stub():
     def process_constant(name):
         """Generate line in stub file for constant name."""
         obj = getattr(xmlsec.constants, name)
-        return '{name}: Final = {obj!r}'.format(name=name, obj=obj)
+        type_name = type(obj).__name__
+        if type_name in ('__KeyData', '__Transform') and obj.href is None:
+            type_name += 'NoHref'
+        return '{name}: Final[{type_name}]'.format(name=name, type_name=type_name)
 
     names = list(sorted(name for name in dir(xmlsec.constants) if not name.startswith('__')))
     lines = [process_constant(name) for name in names]
@@ -64,8 +66,7 @@ def test_xmlsec_constants_stub(request):
 
     Compare it against the existing stub :file:`xmlsec/constants.pyi`.
     """
-    rootdir = Path(str(request.config.rootdir))
-    stub = rootdir / 'src' / 'xmlsec' / 'constants.pyi'
-    mode = black.FileMode(target_versions=[black.TargetVersion.PY38], line_length=130, is_pyi=True, string_normalization=False)
+    stub = request.config.rootpath / 'src' / 'xmlsec' / 'constants.pyi'
+    mode = black.FileMode(target_versions={black.TargetVersion.PY39}, line_length=130, is_pyi=True, string_normalization=False)
     formatted = black.format_file_contents(gen_constants_stub(), fast=False, mode=mode)
     assert formatted == stub.read_text()