From 5f09ea84b97202a41430fed5bb3cbd489d04c18e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 26 Aug 2021 15:24:42 -0700 Subject: [PATCH] Add type annotations to package Adding type annotations will allow packages consuming certifi to have access to its typing information. The py.typed data file is include for PEP 561 compliance. If the API is ever expanded, the mypy type checker will ensure it is also typed. For example, right now, typeshed is incomplete after certifi.contents() was added: https://github.com/python/typeshed/blob/ac2ef6e8c963a45841ad91e068e06748caa1fce6/stubs/certifi/certifi.pyi --- .github/workflows/ci.yml | 12 ++++++++++++ certifi/__init__.py | 1 + certifi/core.py | 18 ++++++++++++++---- certifi/py.typed | 0 certifi/tests/test_certify.py | 9 +++++++-- setup.py | 2 +- 6 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 certifi/py.typed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84496648..cadb14f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,18 @@ on: pull_request: {} jobs: + mypy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install dependencies + run: pip install mypy + - name: Run mypy + run: mypy --strict certifi + test: runs-on: ubuntu-latest strategy: diff --git a/certifi/__init__.py b/certifi/__init__.py index 8db1a0e5..41a722e4 100644 --- a/certifi/__init__.py +++ b/certifi/__init__.py @@ -1,3 +1,4 @@ from .core import contents, where +__all__ = ["contents", "where"] __version__ = "2021.10.08" diff --git a/certifi/core.py b/certifi/core.py index 5d2b8cd3..d768be79 100644 --- a/certifi/core.py +++ b/certifi/core.py @@ -7,6 +7,8 @@ This module returns the installation location of cacert.pem or its contents. """ import os +import types +from typing import Union try: from importlib.resources import path as get_path, read_text @@ -14,7 +16,7 @@ _CACERT_CTX = None _CACERT_PATH = None - def where(): + def where() -> str: # This is slightly terrible, but we want to delay extracting the file # in cases where we're inside of a zipimport situation until someone # actually calls where(), but we don't want to re-extract the file @@ -40,21 +42,29 @@ def where(): except ImportError: + Package = Union[types.ModuleType, str] + Resource = Union[str, "os.PathLike"] + # This fallback will work for Python versions prior to 3.7 that lack the # importlib.resources module but relies on the existing `where` function # so won't address issues with environments like PyOxidizer that don't set # __file__ on modules. - def read_text(_module, _path, encoding="ascii"): + def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict' + ) -> str: with open(where(), "r", encoding=encoding) as data: return data.read() # If we don't have importlib.resources, then we will just do the old logic # of assuming we're on the filesystem and munge the path directly. - def where(): + def where() -> str: f = os.path.dirname(__file__) return os.path.join(f, "cacert.pem") -def contents(): +def contents() -> str: return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/certifi/py.typed b/certifi/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/certifi/tests/test_certify.py b/certifi/tests/test_certify.py index 7fb8c77f..4b443f27 100755 --- a/certifi/tests/test_certify.py +++ b/certifi/tests/test_certify.py @@ -7,9 +7,14 @@ class TestCertifi(unittest.TestCase): - def test_cabundle_exists(self): + def test_cabundle_exists(self) -> None: assert os.path.exists(certifi.where()) - def test_read_contents(self): + def test_read_contents(self) -> None: content = certifi.contents() assert "-----BEGIN CERTIFICATE-----" in content + + def test_py_typed_exists(self) -> None: + assert os.path.exists( + os.path.join(os.path.dirname(certifi.__file__), 'py.typed') + ) diff --git a/setup.py b/setup.py index c3519635..48dc8912 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ 'certifi', ], package_dir={'certifi': 'certifi'}, - package_data={'certifi': ['*.pem']}, + package_data={'certifi': ['*.pem', 'py.typed']}, # data_files=[('certifi', ['certifi/cacert.pem'])], include_package_data=True, zip_safe=False,