Repository: pyca/cryptography

Issue:
type hints: make Extension generic?
In the official typeshed, `cryptography.x509.Extension` is a [generic class](https://mypy.readthedocs.io/en/stable/generics.html), but in the type hinting added in 3.4, it isn't. I couldn't find a discussion about this, if there was I apologize for the duplicate. If there wasn't, anything speaking against making that change (I would create a PR).

Making Extension a generic class would allow more precise type hints, for a (very simplified, just to get the point across) example:

```
def get_pathlen(ext: x509.Extension[x509.BasicConstraints]) -> int:
    return ext.value.path_length  # this works now
```

Like I said, if you think this is a good idea, I can definitely create a PR for this!


Discussion:
This makes sense to me.

On Sun, Feb 14, 2021 at 6:06 AM Mathias Ertl <notifications@github.com> wrote:
>
> In the official typeshed, cryptography.x509.Extension is a generic class, but in the type hinting added in 3.4, it isn't. I couldn't find a discussion about this, if there was I apologize for the duplicate. If there wasn't, anything speaking against making that change (I would create a PR).
>
> Making Extension a generic class would allow more precise type hints, for a (very simplified, just to get the point across) example:
>
> def get_pathlen(ext: x509.Extension[x509.BasicConstraints]) -> int:
>     return ext.value.path_length  # this works now
>
> Like I said, if you think this is a good idea, I can definitely create a PR for this!
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or unsubscribe.



-- 
All that is necessary for evil to succeed is for good people to do nothing.


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
--- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py
+++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py
@@ -5,6 +5,7 @@
 
 import datetime
 import ipaddress
+import typing
 
 from cryptography import x509
 from cryptography.hazmat._der import DERReader, INTEGER, NULL, SEQUENCE
@@ -185,7 +186,7 @@ def __init__(self, backend, ext_count, get_ext, handlers):
         self._backend = backend
 
     def parse(self, x509_obj):
-        extensions = []
+        extensions: typing.List[x509.Extension[x509.ExtensionType]] = []
         seen_oids = set()
         for i in range(self.ext_count(x509_obj)):
             ext = self.get_ext(x509_obj, i)
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -33,6 +33,8 @@
     ObjectIdentifier,
 )
 
+ExtensionTypeVar = typing.TypeVar("ExtensionTypeVar", bound="ExtensionType")
+
 
 def _key_identifier_from_public_key(public_key: _PUBLIC_KEY_TYPES) -> bytes:
     if isinstance(public_key, RSAPublicKey):
@@ -108,17 +110,19 @@ def oid(self) -> ObjectIdentifier:
 
 
 class Extensions(object):
-    def __init__(self, extensions: typing.List["Extension"]):
+    def __init__(self, extensions: typing.List["Extension[ExtensionType]"]):
         self._extensions = extensions
 
-    def get_extension_for_oid(self, oid: ObjectIdentifier) -> "Extension":
+    def get_extension_for_oid(
+        self, oid: ObjectIdentifier
+    ) -> "Extension[ExtensionType]":
         for ext in self:
             if ext.oid == oid:
                 return ext
 
         raise ExtensionNotFound("No {} extension was found".format(oid), oid)
 
-    def get_extension_for_class(self, extclass) -> "Extension":
+    def get_extension_for_class(self, extclass) -> "Extension[ExtensionType]":
         if extclass is UnrecognizedExtension:
             raise TypeError(
                 "UnrecognizedExtension can't be used with "
@@ -1220,9 +1224,9 @@ def __hash__(self):
     excluded_subtrees = utils.read_only_property("_excluded_subtrees")
 
 
-class Extension(object):
+class Extension(typing.Generic[ExtensionTypeVar]):
     def __init__(
-        self, oid: ObjectIdentifier, critical: bool, value: ExtensionType
+        self, oid: ObjectIdentifier, critical: bool, value: ExtensionTypeVar
     ):
         if not isinstance(oid, ObjectIdentifier):
             raise TypeError(


Test patch: