Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci-constraints-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ tomli==2.4.1 ; python_full_version <= '3.11'
# nox
# pytest
# sphinx
ty==0.0.69
# via cryptography (pyproject.toml:dev)
typing-extensions==4.16.0
# via
# cryptography (pyproject.toml)
Expand Down
17 changes: 17 additions & 0 deletions docs/development/submitting-patches.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ running ``ruff`` against it. If you've installed the development requirements
this will automatically use our configuration. You can also run the ``nox``
job with ``nox -e flake``.

We type check with both ``mypy`` and ``ty``. The two use different suppression
comments, so a line that needs suppressing in both must carry both, with the
``mypy`` one first (``mypy`` only recognizes ``# type: ignore`` when it is the
first comment on the line):

.. code-block:: python

key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]

``ty`` does not understand ``mypy``'s error codes (see `ty#3127`_) and ignores
a ``# type: ignore[...]`` comment that has any codes in brackets, so a
``mypy``-only suppression needs nothing extra. A bare ``# type: ignore``, on
the other hand, is honored by both, and needs no ``ty`` comment.

Don't add a ``# ty: ignore`` that isn't needed: ``ty`` reports unused ones.

`Write comments as complete sentences.`_

Class names which contains acronyms or initialisms should always be
Expand Down Expand Up @@ -148,3 +164,4 @@ So, specifically:
.. _`syntax`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists
.. _`Studies have shown`: https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/
.. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev
.. _`ty#3127`: https://github.com/astral-sh/ty/issues/3127
31 changes: 23 additions & 8 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ def load_pyproject_toml() -> dict:
return tomllib.load(f)


def run_ty(session: nox.Session) -> None:
# ty rejects every `pytest.skip("...")` under pytest 8, the newest pytest
# supporting 3.9 (https://github.com/astral-sh/ty/issues/2797).
if sys.version_info < (3, 10):
session.log("Skipping ty: needs pytest 9, which needs Python 3.10+")
return

session.run(
"ty",
"check",
# ty targets `project.requires-python`, not the running interpreter.
f"--python-version={sys.version_info[0]}.{sys.version_info[1]}",
"src/cryptography/",
"vectors/cryptography_vectors/",
"tests/",
"release.py",
"noxfile.py",
)


@nox.session
@nox.session(name="tests-ssh")
@nox.session(name="tests-randomorder")
Expand Down Expand Up @@ -232,6 +252,7 @@ def flake(session: nox.Session) -> None:
"release.py",
"noxfile.py",
)
run_ty(session)
session.run("check-sdist", "--no-isolation")


Expand Down Expand Up @@ -323,14 +344,8 @@ def local(session: nox.Session):
external=True,
)

session.run(
"mypy",
"src/cryptography/",
"vectors/cryptography_vectors/",
"tests/",
"release.py",
"noxfile.py",
)
# `local` runs ty instead of mypy; `flake` runs both.
run_ty(session)

session.run(
"maturin",
Expand Down
16 changes: 16 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ sdist = ["build >=1.0.0"]
pep8test = [
"ruff >=0.11.11",
"mypy >=1.14",
"ty >=0.0.69",
"check-sdist",
"click >=8.0.1",
]
Expand Down Expand Up @@ -159,6 +160,21 @@ warn_unused_configs = true
strict_equality = true
strict_bytes = true

# ty does not understand mypy's error codes, so a line both checkers flag
# carries a `# type: ignore[...]` followed by a `# ty: ignore[...]`. If ty
# grows support for mypy's names (https://github.com/astral-sh/ty/issues/3127)
# the `ty:` halves can be dropped.
[tool.ty.rules]
# In `sys.version_info`-gated code one branch is always unreachable, and ty
# reports the blanket ignore there as unused while mypy stays quiet. Whichever
# version we check, one arm of such a pair gets flagged. mypy's
# `warn_unused_ignores` already covers `# type: ignore` comments, so turn this
# off. `unused-ignore-comment` (for `# ty: ignore`) stays enabled, since mypy
# can't check those.
# Tracked upstream as https://github.com/astral-sh/ty/issues/2681; this can be
# re-enabled once ty stops reporting suppressions in unreachable code.
unused-type-ignore-comment = "ignore"

[[tool.mypy.overrides]]
module = ["pretend"]
ignore_missing_imports = true
Expand Down
2 changes: 1 addition & 1 deletion src/cryptography/hazmat/bindings/openssl/binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def build_conditional_library(
conditional_names: Mapping[str, Callable[[], list[str]]],
) -> typing.Any:
conditional_lib = types.ModuleType("lib")
conditional_lib._original_lib = lib # type: ignore[attr-defined]
conditional_lib._original_lib = lib # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
excluded_names = set()
for condition, names_cb in conditional_names.items():
if not getattr(lib, condition):
Expand Down
2 changes: 1 addition & 1 deletion src/cryptography/x509/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2510,7 +2510,7 @@ def __init__(self, oid: ObjectIdentifier, value: bytes) -> None:
self._value = value

@property
def oid(self) -> ObjectIdentifier: # type: ignore[override]
def oid(self) -> ObjectIdentifier: # type: ignore[override] # ty: ignore[invalid-attribute-override]
return self._oid

@property
Expand Down
10 changes: 5 additions & 5 deletions tests/hazmat/asn1/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ class Example:
with pytest.raises(
TypeError, match="got an unexpected keyword argument 'bar'"
):
Example(bar=3) # type: ignore[call-arg]
Example(bar=3) # type: ignore[call-arg] # ty: ignore[missing-argument, unknown-argument]

def test_fail_init_missing_field_name(self) -> None:
@asn1.sequence
Expand All @@ -239,7 +239,7 @@ class Example:
)

with pytest.raises(TypeError, match=expected_err):
Example() # type: ignore[call-arg]
Example() # type: ignore[call-arg] # ty: ignore[missing-argument]

def test_fail_positional_field_initialization(self) -> None:
@asn1.sequence
Expand Down Expand Up @@ -475,7 +475,7 @@ def test_fail_choice_with_non_literal_tag(self) -> None:
class Example:
foo: typing.Union[
Annotated[
asn1.Variant[int, str],
asn1.Variant[int, str], # ty: ignore[invalid-type-arguments]
asn1.Implicit(0),
],
Annotated[
Expand Down Expand Up @@ -542,7 +542,7 @@ class Example:
with pytest.raises(
TypeError, match="got an unexpected keyword argument 'bar'"
):
Example(bar=3) # type: ignore[call-arg]
Example(bar=3) # type: ignore[call-arg] # ty: ignore[missing-argument, unknown-argument]

def test_fail_init_missing_field_name(self) -> None:
@asn1.set
Expand All @@ -556,7 +556,7 @@ class Example:
)

with pytest.raises(TypeError, match=expected_err):
Example() # type: ignore[call-arg]
Example() # type: ignore[call-arg] # ty: ignore[missing-argument]

def test_fail_positional_field_initialization(self) -> None:
@asn1.set
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_dh.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ def test_public_key_equality(self):
assert key1 != object()

with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_public_key_copy(self):
key_bytes = load_vectors_from_file(
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_dsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ def test_public_key_equality(self):
assert key1 != key3
assert key1 != object()
with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_public_key_copy(self):
key_bytes = load_vectors_from_file(
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_ec.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ def test_public_key_equality(self, backend):
assert key1 != key3
assert key1 != object()
with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_public_key_copy(self, backend):
_skip_curve_unsupported(backend, ec.SECP256R1())
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_ed25519.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def test_public_key_equality():
assert key1 != object()

with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]


def test_public_key_copy():
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_ed448.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def test_public_key_equality():
assert key1 != object()

with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]


@pytest.mark.supported(
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_hkdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def test_private_extract_exists(self):
# This was DeprecatedIn47 but we can't raise a warning
# because the scapy tests are fragile butterflies
hkdf = HKDF(hashes.SHA256(), 32, salt=b"0", info=None)
prk = hkdf._extract(b"0") # type:ignore[attr-defined]
prk = hkdf._extract(b"0") # type:ignore[attr-defined] # ty: ignore[unresolved-attribute]
assert len(prk) == 32

def test_buffer_protocol(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -2817,7 +2817,7 @@ def test_public_key_equality(self, rsa_key_2048: rsa.RSAPrivateKey):
assert key1 != key3
assert key1 != object()
with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_public_key_copy(self, rsa_key_2048: rsa.RSAPrivateKey):
key1 = rsa_key_2048.public_key()
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_x25519.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def test_public_key_equality():
assert key1 != key3
assert key1 != object()
with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]


@pytest.mark.supported(
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/test_x448.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ def test_public_key_equality():
assert key1 != key3
assert key1 != object()
with pytest.raises(TypeError):
key1 < key2 # type: ignore[operator]
key1 < key2 # type: ignore[operator] # ty: ignore[unsupported-operator]


@pytest.mark.supported(
Expand Down
2 changes: 1 addition & 1 deletion tests/hazmat/primitives/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def aead_exception_test(cipher_factory, mode_factory):
with pytest.raises(AlreadyUpdated):
decryptor.authenticate_additional_data(b"b" * 16)
with pytest.raises(AttributeError):
decryptor.tag # type: ignore[attr-defined]
decryptor.tag # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]


def generate_aead_tag_exception_test(cipher_factory, mode_factory):
Expand Down
10 changes: 5 additions & 5 deletions tests/x509/test_x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def test_comparison(self):
x509.load_der_x509_crl,
)
with pytest.raises(TypeError):
crl1 < crl1 # type: ignore[operator]
crl1 < crl1 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_update_dates(self):
crl = _load_cert(
Expand Down Expand Up @@ -1585,7 +1585,7 @@ def test_ordering_unsupported(self):
x509.load_pem_x509_certificate,
)
with pytest.raises(TypeError, match="'>' not supported"):
cert > cert2 # type: ignore[operator]
cert > cert2 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_hash(self):
cert1 = _load_cert(
Expand Down Expand Up @@ -2385,7 +2385,7 @@ def test_ordering_unsupported(self):
x509.load_pem_x509_csr,
)
with pytest.raises(TypeError, match="'>' not supported"):
csr > csr2 # type: ignore[operator]
csr > csr2 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_hash(self):
request1 = _load_cert(
Expand Down Expand Up @@ -4921,7 +4921,7 @@ def test_sign_without_private_key(self, rsa_key_2048: rsa.RSAPrivateKey):
)

with pytest.raises(TypeError):
builder.sign(None, None) # type:ignore[arg-type]
builder.sign(None, None) # type:ignore[arg-type] # ty: ignore[invalid-argument-type]

def test_build_unsigned_cert(self, rsa_key_2048: rsa.RSAPrivateKey):
subject_private_key = rsa_key_2048
Expand Down Expand Up @@ -6697,7 +6697,7 @@ def test_comparison(self):
oid1 = x509.ObjectIdentifier("2.999.1")
oid2 = x509.ObjectIdentifier("2.999.2")
with pytest.raises(TypeError):
oid1 < oid2 # type: ignore[operator]
oid1 < oid2 # type: ignore[operator] # ty: ignore[unsupported-operator]

def test_repr(self):
oid = x509.ObjectIdentifier("2.5.4.3")
Expand Down